Урок по треку Python для анализа данных 7 мин

Даты и время в pandas

to_datetime, .dt, resample, периоды, часовые пояса, когорты и retention по датам.

Почти любая продуктовая метрика привязана ко времени: DAU, выручка по неделям, retention. Ошибки с датами самые коварные: код работает, а цифры неверны на часовой пояс или на неделю.

Превратить строку в дату

import pandas as pd

df['ts'] = pd.to_datetime(df['ts'])                          # угадает формат
df['ts'] = pd.to_datetime(df['ts'], format='%d.%m.%Y')       # явный формат
df['ts'] = pd.to_datetime(df['ts'], errors='coerce')         # мусор станет NaT
  • Явный format быстрее и надёжнее: дата 03.04.2026 в американской логике это 4 марта, а не 3 апреля. Для формата «день первым» без явного формата есть dayfirst=True.
  • errors='coerce' превращает нераспознанные значения в NaT (пропуск для дат). После этого проверьте, сколько NaT получилось.
  • Unix-время: pd.to_datetime(df['ts'], unit='s').

Аксессор .dt

df['date'] = df['ts'].dt.date            # объект date, удобно для печати
df['day'] = df['ts'].dt.normalize()      # полночь того же дня, остаётся datetime
df['hour'] = df['ts'].dt.hour
df['dow'] = df['ts'].dt.dayofweek        # понедельник = 0
df['month'] = df['ts'].dt.to_period('M') # период "2026-10"

Для группировки по дням лучше dt.normalize() или dt.floor('D'): колонка остаётся datetime и дальше работает resample.

Разница дат: Timedelta

(df['end'] - df['start']).dt.days                    # целые дни, отбрасывает часы
(df['end'] - df['start']).dt.total_seconds() / 3600  # часы с дробью

dt.days у Timedelta это целая часть суток, а не округление: 1 день 23 часа даст 1.

resample: группировка по времени

Работает, когда время в индексе (или через on=):

daily = df.set_index('ts')['amount'].resample('D').sum()
weekly = df.resample('W-MON', on='ts')['amount'].sum()

Главное отличие от groupby(dt.date): resample создаёт все периоды в диапазоне, включая пустые. Для суммы пустой день получит 0, для среднего NaN. Это важно для графиков и скользящих средних.

Псевдонимы частот в pandas 2.2+: 'D' день, 'h' час, 'min' минута, 'W' неделя (по умолчанию неделя заканчивается в воскресенье), 'MS' начало месяца, 'ME' конец месяца. Старые 'M' и 'H' устарели в pandas 2.2, а в pandas 3.0 выдают ошибку.

Недели

Неделя в России начинается с понедельника. df['ts'].dt.to_period('W') по умолчанию даёт неделю, заканчивающуюся в воскресенье, то есть с понедельника по воскресенье: это то, что нужно. А вот resample('W') подписывает неделю датой воскресенья, её конца. Если в отчёте нужна дата начала недели, используйте dt.to_period('W').dt.start_time.

Часовые пояса

s = pd.to_datetime(df['ts'], utc=True)       # явно UTC
s.dt.tz_convert('Europe/Moscow')             # перевести в Москву
naive.dt.tz_localize('Europe/Moscow')        # приписать пояс наивному времени
  • tz_localize говорит «эти часы были по Москве», tz_convert пересчитывает уже известное время в другой пояс.
  • Нельзя сравнивать или вычитать aware и naive даты: pandas выбросит ошибку.
  • События в базе обычно хранятся в UTC. Если считать «заказы за день» по UTC, московский день сдвинется на 3 часа, и ночные заказы уедут в соседние сутки.

Когорты и периоды

df['cohort'] = df.groupby('user')['ts'].transform('min').dt.to_period('M')
df['age'] = (df['ts'].dt.to_period('M') - df['cohort']).apply(lambda x: x.n)

Разность двух периодов это смещение, его .n даёт число месяцев.

Частые ошибки

  • Даты остались строками, и сортировка идёт по тексту: "10.01" раньше "9.01".
  • Перепутаны день и месяц.
  • Подсчёт «по дням» в UTC вместо местного времени.
  • groupby(dt.date) теряет пустые дни, график проваливается в линию без нулей.
  • Неделя, подписанная воскресеньем, принята за начало недели.

Что спросят на собесе

  • Как перевести строку в дату и что делать с битыми значениями?
  • Чем resample отличается от groupby по дате?
  • Как посчитать DAU по московскому времени, если события в UTC?
  • Чем tz_localize отличается от tz_convert?
  • Как посчитать номер месяца жизни пользователя для когорт?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.