Даты и время в pandas: периоды, недели и когорты без ошибок
Как работать с датами в pandas: преобразовать строки, задать границы периода, группировать по неделям и не потерять события из-за часового пояса.

Строка “2026-08-01 00:30” выглядит как дата, пока не выясняется, в какой таймзоне она записана и входит ли в отчёт за июль. В pandas ошибки времени обычно не ломают код: запрос просто считает не тот период. Поэтому даты нужно привести к типу, явно задать границы и проверить результат на нескольких строках.
Коротко
Для аналитического периода используй левую включённую и правую исключённую границу: `>= start` и `< next_start`. Строки превращай в datetime через `pd.to_datetime`, недели и месяцы группируй после приведения к единой таймзоне, а неполный текущий период не сравнивай с полным прошлым.
- Сначала проверь dtype и диапазон дат.
- Не смешивай naive datetime и timezone-aware datetime.
- Для недель зафиксируй, с какого дня начинается неделя.
- Возраст когорты считай относительно даты старта пользователя, а не календарной даты отчёта.
Преобразовать строки в datetime
Не полагайся на автоматическое угадывание формата, если источник нестабилен. `errors="coerce"` помогает найти нераспарсенные значения через `isna`, но не должен незаметно превращать ошибки в пропуски. После преобразования проверь минимум, максимум и число новых NaT.
orders['created_at'] = pd.to_datetime(
orders['created_at'],
errors='coerce',
utc=True,
)
invalid_dates = orders['created_at'].isna().sum()
print('invalid dates:', invalid_dates)
print(orders['created_at'].min(), orders['created_at'].max())Период: месяц, неделя и день
Период лучше задавать границей следующего периода. Так июль включает все события до 1 августа, включая последний день с любым временем. `dt.to_period` удобен для подписи и группировки, а `date_range` или явные границы лучше подходят для фильтров.
start = pd.Timestamp('2026-07-01', tz='UTC')
next_start = pd.Timestamp('2026-08-01', tz='UTC')
july = orders.loc[
(orders['created_at'] >= start)
& (orders['created_at'] < next_start)
].copy()
july['week'] = july['created_at'].dt.to_period('W-MON').astype(str)
weekly = july.groupby('week', as_index=False).agg(
orders=('order_id', 'nunique'),
)Когорты и возраст пользователя
Для retention важен не только день события, но и расстояние от регистрации. Пользователь, который сделал действие через семь дней после signup, относится к D7 независимо от календарного месяца. В pandas для этого удобно привести обе даты к началу дня и посчитать разницу в днях.
events['event_date'] = events['occurred_at'].dt.floor('D')
users['signup_date'] = users['signup_at'].dt.floor('D')
events = events.merge(users[['user_id', 'signup_date']], on='user_id', how='left')
events['day_age'] = (events['event_date'] - events['signup_date']).dt.days
d7 = events.loc[events['day_age'].eq(7)]
d7_users = d7['user_id'].nunique()Часовой пояс — часть определения метрики
Если продукт работает в нескольких странах, “день” может означать UTC, локальный день пользователя или бизнесовую таймзону компании. Для событий около полуночи эти варианты дадут разные DAU и выручку. Выбери правило до расчёта и запиши его в названии или описании отчёта.
- Храни исходное время с timezone, если источник его передаёт.
- Не локализуй naive datetime, пока не знаешь, в какой зоне он записан.
- Проверь несколько событий около полуночи после конвертации.
- Сравни период с SQL-версией расчёта на контрольной выборке.
Материалы по теме
Как тестировать аналитические расчёты на Python и pandas
Практический гайд по тестам для аналитика: проверить метрики на маленьком датасете, поймать регрессию и защитить расчёт от тихих изменений.
Читать материал
Как ускорить pandas: память, типы и обработка больших файлов
Что делать, если pandas медленно работает или не помещает файл в память: категории, downcast, chunksize, Parquet и контроль размера данных.
Читать материал
Pandas apply и векторизация: как писать быстрее и понятнее
Когда использовать apply, почему векторные операции быстрее и как переписать медленный построчный расчёт в pandas.
Читать материал