Продажи по дням, температура по дням, курс валюты по дням — всё это временные ряды: значения, у каждого из которых есть момент времени. Главный вопрос к таким данным — «что было в таком-то периоде». Отвечать на него проще всего, когда даты стоят в индексе.
Даты в индексе
Возьмём погоду в Москве и сделаем дату индексом:
import pandas as pd
weather = pd.read_csv("data/weather.csv", parse_dates=["date"])
moscow = weather[weather["city"] == "Москва"].set_index("date").drop(columns=["city"])
moscow.head(3)| temp_min | temp_max | precip_mm | wind_ms | |
|---|---|---|---|---|
| date | ||||
| 2025-01-01 | -9.9 | -3.2 | 0.0 | 1.9 |
| 2025-01-02 | -9.7 | -3.2 | 0.0 | 2.3 |
| 2025-01-03 | -9.0 | -3.7 | 0.0 | 5.2 |
Индекс из дат — особый тип, DatetimeIndex:
print(type(moscow.index))
print(moscow.index.min(), moscow.index.max())<class 'pandas.DatetimeIndex'> 2025-01-01 00:00:00 2025-12-31 00:00:00
С ним loc понимает даты, записанные строкой. Один день:
moscow.loc["2025-05-09"]temp_min 10.7 temp_max 15.3 precip_mm 10.2 wind_ms 3.3 Name: 2025-05-09 00:00:00, dtype: float64
Часть даты вместо целой
Если указать не полную дату, а только год и месяц, loc вернёт все дни этого месяца:
moscow.loc["2025-05"].head(3)| temp_min | temp_max | precip_mm | wind_ms | |
|---|---|---|---|---|
| date | ||||
| 2025-05-01 | 8.9 | 13.3 | 0.0 | 2.2 |
| 2025-05-02 | 12.0 | 16.3 | 14.7 | 5.1 |
| 2025-05-03 | 6.8 | 12.5 | 0.0 | 2.2 |
print(len(moscow.loc["2025-05"]))
print(moscow.loc["2025-05", "temp_max"].mean())31 15.545161290322582
Сравните с двумя сравнениями дат через & — запись loc["2025-05"] короче и читается как обычная речь. Так же работает год: loc["2025"].
Сколько строк вернёт moscow.loc["2025-02"]?
"2025-02" — это весь февраль 2025 года: 28 дней, по строке на день.Сохраните в march мартовские дни таблицы moscow. В march_max — наибольшее значение temp_max за март,
в march_warmest — дату этого дня.
moscow.loc["2025-03"].idxmax(); раз в индексе даты, это и будет дата.Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
march = ...
march_max = ...
march_warmest = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
march = moscow.loc["2025-03"]
march_max = march["temp_max"].max()
march_warmest = march["temp_max"].idxmax()idxmax вернул дату — ещё одно удобство дат в индексе: «когда был максимум» получается сразу.
Срезы по датам
Период задают срезом. Как у любого среза по меткам, обе границы входят:
moscow.loc["2025-12-29":"2025-12-31"]| temp_min | temp_max | precip_mm | wind_ms | |
|---|---|---|---|---|
| date | ||||
| 2025-12-29 | -10.9 | -6.4 | 0.0 | 0.8 |
| 2025-12-30 | -10.1 | -4.8 | 4.1 | 2.4 |
| 2025-12-31 | -7.1 | -2.0 | 0.0 | 3.0 |
Границами могут быть и месяцы: от начала первого до конца последнего.
spring = moscow.loc["2025-03":"2025-05"]
print(len(spring))
print(spring.index.min(), spring.index.max())92 2025-03-01 00:00:00 2025-05-31 00:00:00
Сохраните в summer дни с июня по август, в summer_mean — средний temp_max за лето. В holidays
сохраните temp_max с 1 по 11 мая включительно, в holidays_mean — его среднее.
moscow.loc["2025-06":"2025-08"].moscow.loc["2025-05-01":"2025-05-11", "temp_max"].Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
summer = ...
summer_mean = ...
holidays = ...
holidays_mean = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
summer = moscow.loc["2025-06":"2025-08"]
summer_mean = summer["temp_max"].mean()
holidays = moscow.loc["2025-05-01":"2025-05-11", "temp_max"]
holidays_mean = holidays.mean()Части даты у индекса
У столбца дат части доставали через .dt. У индекса из дат приставка не нужна — атрибуты есть у него самого:
print(moscow.index.month[:3].tolist())
print(moscow.index.dayofweek[:3].tolist())
print((moscow.index.dayofweek >= 5).sum())[1, 1, 1] [2, 3, 4] 104
Дни, которых нет
Соберём ряд выручки магазина по дням:
orders = pd.read_csv("data/shop_orders.csv", parse_dates=["date"])
orders["revenue"] = orders["price"] * orders["quantity"]
daily = orders.groupby("date")["revenue"].sum()
print(len(daily))
daily.head(3)358
date 2025-01-01 9060 2025-01-02 10450 2025-01-03 18620 Name: revenue, dtype: int64
Дней в году 365, а в ряду — 358. В семь дней покупок не было, и в таблице продаж этих дат просто нет. Группировка не может создать строку для даты, которая не встретилась:
daily.loc["2025-07-02"]KeyError: '2025-07-02'
Для временного ряда это проблема. Среднее по 358 дням завышает «выручку в день»; неделя из шести дней сравнивается с неделей из семи; на графике пропавший день незаметен. Нужен ряд с полным календарём.
Функция pd.date_range создаёт набор дат от начала до конца — по дню на каждую:
pd.date_range("2025-01-01", "2025-01-05")DatetimeIndex(['2025-01-01', '2025-01-02', '2025-01-03', '2025-01-04',
'2025-01-05'],
dtype='datetime64[us]', freq='D')freq='D' в конце значит «шаг — один день» (day).
А reindex из прошлого модуля приводит ряд к этому набору: для дат, которых не было, ставит указанное
значение.
Сохраните в calendar все дни 2025 года. Приведите daily к этому календарю и сохраните в sales: в дни без
продаж — ноль. В empty_days сохраните число дней без продаж, в december — выручку декабря.
pd.date_range("2025-01-01", "2025-12-31").daily.reindex(calendar, fill_value=0); декабрь — sales.loc["2025-12"].sum().Заготовка упражнения обновилась с тех пор, как вы сохранили свой код.
Вернуть заготовку? Ваш код в этом упражнении будет удалён.
calendar = ...
sales = ...
empty_days = ...
december = ...Упражнение пока не решено, поэтому для ячеек ниже выполнено эталонное решение — ваш код не изменён.
calendar = pd.date_range("2025-01-01", "2025-12-31")
sales = daily.reindex(calendar, fill_value=0)
empty_days = (sales == 0).sum()
december = sales.loc["2025-12"].sum()Индекс должен быть отсортирован
Срезы по датам рассчитаны на то, что даты идут по порядку. Если строки перемешаны, срез не сработает:
shuffled = moscow.sort_values("temp_max")
shuffled.loc["2025-03-01":"2025-03-05"]KeyError: 'Value based partial slicing on non-monotonic DatetimeIndexes with non-existing keys is not allowed.'
Итоги
set_index("date")— даты в индексе; дальшеlocпонимает даты строкой.loc["2025-03"]— весь месяц,loc["2025"]— весь год,loc["2025-06":"2025-08"]— период, обе границы входят.- У индекса из дат есть
index.month,index.dayofweekи другие части — без.dt. - В данных может не быть строк для «пустых» дней:
pd.date_rangeиreindexвосстанавливают календарь. - Срезы по датам требуют отсортированного индекса.