Дати и времеви редове
Накратко: Времевият ред е данни, подредени по дата; в pandas текстът става дата с to_datetime, resample събира по дни, седмици и месеци, а rolling дава плъзгащо средно.
Проблем
„Как върви годината?“
Магазинът има поръчките за цяла година: от ноември 2025 до края на октомври 2026. Управителят пита кой месец е най-силен, в кой ден от седмицата се продава най-много и как се мени оборотът от месец на месец.
Поръчките са 11 116. Колоната ordered_at обаче е str: за pandas „2025-11-01 07:20“ е само текст. Месецът не може да се вземе от него:
Идеята
Текстът става дата
parse_dates казва на read_csv кои колони да прочете като дати. Същото прави pd.to_datetime(orders['ordered_at']) върху готова колона, както видя в урока за типовете:
Типът е datetime64: момент във времето, с дата и час. С него можеш да сравняваш, да вадиш и да взимаш части. Разликата между последната и първата поръчка е 364 дни и 15 часа и 41 минути. Тя е времева разлика (Timedelta), не число.
Таблица, в която всеки ред е момент или период и редовете вървят по времето, се казва времеви ред (time series).
Идеята
Части от датата: .dt
.dt дава частите на всяка дата в колоната: year, month, day, hour, dayofweek (0 е понеделник), day_name(). Кога през деня се поръчва най-много?
Има два върха: на обед (между 12 и 14 часа, около 800 поръчки на час) и вечер (между 19 и 21 часа, около 1200 на час). Сутрин и късно вечер поръчките са по-малко от половината.
day_name() дава имената на английски („Saturday“). Имената на български ще ги сложиш сам с речник, малко по-нататък.
Идеята
Филтър по дата
Колона с дати се сравнява с текст като „2025-12-01“: pandas го превръща в дата. Поръчките през декември:
Правилно е 1285. С <= '2025-12-31' излизат 1250, а последната поръчка в тях е от 30 декември. Причината е часът: „2025-12-31“ е 31 декември в 00:00, а всички поръчки от 31-ви са след полунощ.
Затова за период пишеш „от първия ден включително до първия ден на следващия период без него“: >= '2025-12-01' и < '2026-01-01'.
Пробвай
Седмицата преди Коледа
Колко поръчки има от 22 до 28 декември 2025 включително? Запиши в week тези поръчки.
Решение
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
week = orders[(orders['ordered_at'] >= '2025-12-22') & (orders['ordered_at'] < '2025-12-29')]
print(len(week))orders[(orders["ordered_at"] >= "2025-12-22") & (orders["ordered_at"] < "2025-12-29")] дава 309 поръчки. С <= "2025-12-28" излизат 279: изпускаш целия 28 декември, защото датата без час е полунощ в началото на деня.
Идеята
По месеци: resample
За оборот по месеци трябва първо оборотът на всяка поръчка. Той е сборът на редовете ѝ: groupby по order_id, после merge с поръчките. Отказаните поръчки не са оборот, затова sold е без тях.
После resample събира редовете по периоди от времето. Той е groupby, само че групите са периоди: дни ('D'), седмици ('W'), месеци ('MS'). Работи върху индекс от дати, затова първо set_index('ordered_at'):
Декември е най-силният месец: 15 915,90 €. Януари (7 228,20 €) и февруари (7 622,30 €) са слаби, както често е след празниците. Най-слаб обаче е септември: 7 128,80 €. Защо, ще разберем в проекта в края на курса.
По-точно, за любопитните
'MS' (month start) слага на всеки месец етикета на първия му ден, а 'ME' (month end) на последния. Сумите са същите.
'W' слага на седмицата етикета на неделята, с която свършва. Първата седмица тук е само 1 и 2 ноември (събота и неделя), затова е много по-малка от другите. Първият и последният период често са непълни: провери ги, преди да ги сравняваш.
Ако няма поръчки в някой ден, resample('D').sum() пак дава реда, с 0. В тези данни всеки ден има поръчки.
Пробвай
Най-силната седмица
Коя седмица е донесла най-голям оборот? Запиши в weekly оборота по седмици (без отказаните поръчки), а в best_week датата на най-силната седмица.
Решение
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
items['revenue'] = items['quantity'] * items['unit_price']
per_order = items.groupby('order_id', as_index=False)['revenue'].sum()
orders = orders.merge(per_order, left_on='id', right_on='order_id', how='left', validate='one_to_one')
# Оборотът е без отказаните поръчки.
sold = orders[orders['status'] != 'cancelled']
weekly = sold.set_index('ordered_at')['revenue'].resample('W').sum()
best_week = weekly.idxmax()
print(best_week, weekly.max().round(2))sold.set_index("ordered_at")["revenue"].resample("W").sum() дава 53 седмици. idxmax() е 28 декември 2025: седмицата от 22 до 28 декември, с 3 865,80 €. Ако сметнеш и отказаните поръчки, същата седмица излиза 4 039,40 €. max() щеше да даде сумата, а не коя седмица е.
Идеята
Плъзгащо средно: rolling
Оборотът по дни скача нагоре-надолу: в петък е висок, в неделя е нисък. Зад този шум е трудно да видиш дали върви нагоре. Плъзгащото средно (moving average) смята за всеки ден средното на последните 7 дни, заедно с него:
rolling(7) е прозорец от 7 реда, който се плъзга с един ред надолу. Първите 6 стойности са NaN: преди 7 ноември няма 7 дни назад. На 7 ноември средното на първите седем дни е 374,23 €. После всеки ден влиза един нов и излиза най-старият, затова линията се мени плавно.
Идеята
Ден от седмицата
Колко е средният оборот в понеделник, във вторник и така нататък? Групираш оборота по дни по номера на деня от индекса, daily.index.dayofweek, и взимаш средната. Групираш по номера, а не по името, защото имената се подреждат по азбука. После с речник (и map) даваш на номерата имена:
Петъкът е най-силен: средно 350,79 €. Неделята е най-слаба: 250,85 €. Това е седмичният ритъм, който rolling(7) изглажда: прозорец от 7 дни съдържа всеки ден от седмицата точно веднъж.
Тук индексът вече е от дати, затова dayofweek е направо на индекса. При колона пишеш .dt.dayofweek.
Идеята
Месец спрямо месец: pct_change
Управителят не пита „колко“, а „с колко повече или по-малко от миналия месец“. pct_change() дели всяка стойност на предишната и вади 1: дава промяната като дял. По 100 е в проценти:
Ноември е NaN: преди него няма месец. Декември е с 71,2% над ноември, а януари е с 54,6% под декември. Септември е с 19,1% под август, а октомври се връща с 39,2% нагоре.
Дали спадът през септември е истински, или е шум, и каква е причината, е въпросът на проекта. Засега знаеш, че го има и колко е голям.
Упражнение
Кога поскъпна кафето?
Кафето на зърна (продукт 17) поскъпна някъде през годината. Запиши в price_by_month средната цена на кафето за всеки месец (по реда на месеците във времето), а в first_new датата и часа на първата продажба по новата цена 14,9.
Решение
import pandas as pd
orders = pd.read_csv('year_orders.csv', parse_dates=['ordered_at'])
items = pd.read_csv('year_items.csv')
coffee = items[items['product_id'] == 17]
coffee = coffee.merge(orders[['id', 'ordered_at']], left_on='order_id', right_on='id', validate='many_to_one')
price_by_month = coffee.set_index('ordered_at')['unit_price'].resample('MS').mean()
first_new = coffee[coffee['unit_price'] == 14.9]['ordered_at'].min()
print(price_by_month)
print(first_new)coffee.set_index("ordered_at")["unit_price"].resample("MS").mean() дава 13,90 € от ноември до февруари и 14,90 € от март нататък. Първата продажба по 14,90 € е на 3 март 2026 в 18:00, а последната по старата цена е на 28 февруари. С groupby по .dt.month месеците губят годината: ноември и декември 2025 отиват след октомври 2026 и редът във времето се губи.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Нови колони · Типове и липсващи стойности · Групи: groupby · Съединяване: merge
- Продължава в: Коя графика за какъв въпрос · Честни графики · Проверка на хипотеза · Корелация и причина
- Задачи по темата: Подреждане на данни