Даты в pandas: один раз разобрать их правильно
Даты — это столбец, который молча разрывает конвейер. Неправильный номер ошибочен или выглядит абсурдно. Неправильная дата выглядит как дата, находится в правом столбце, сортируется и неверна только в те дни, которые не могут быть месяцем.
Что вы делаете: анализируете один раз, при загрузке, с явным форматом и подсчитываете, что не удалось.
Краткая версия. Если вы не указали формат, синтаксический анализатор догадался и никогда не скажет вам, в какую сторону.
Демонстрация
import pandas as pd
s = pd.Series(['03/04/2026', '11/12/2026'])
pd.to_datetime(s) # 2026-03-04, 2026-11-12
pd.to_datetime(s, format='%d/%m/%Y') # 2026-04-03, 2026-12-11
Те же две строки, два разных результата. Предоставленные сами себе, pandas читают их в первый месяц. Согласно заявленному формату, они идут первым днем. Ни один из вызовов ничего не дал, и оба дали действительные даты.
В реальном файле это хуже, чем кажется. Дни выше 12 однозначны и в любом случае анализируются правильно, поэтому затрагиваются только первые двенадцать дней каждого месяца. Примерно треть ваших строк неверны, разбросаны, а столбец по-прежнему отлично сортируется и отображается в диаграммах.
Разбор при загрузке
df = pd.read_csv('orders.csv',
parse_dates=['order_date'],
date_format='%d/%m/%Y')
Делаю это в read_csv «а не потом» означает, что каждый последующий шаг видит реальную дату и время, и он удаляет окно, в котором кто-то записывает сравнение со строкой, и получает сортировку, которая выполняется в алфавитном порядке.
Коды форматов, которые стоит знать: %Y четырехзначный год, %y двузначный, %m номер месяца, %b короткое название месяца, %B полное название месяца, %d день, %H:%M:%S время. строки ISO, 2026-08-23, вообще не нуждаются в формате и везде однозначны, поэтому на них стоит настаивать в любой системе, на которую вы можете повлиять.
Подсчет неудач
parsed = pd.to_datetime(df['order_date'], format='%d/%m/%Y', errors='coerce')
bad = parsed.isna() & df['order_date'].notna()
print(bad.sum())
print(df.loc[bad, 'order_date'].head(10))
errors='coerce' превращает неудачи в NaT вместо того, чтобы останавливать скрипт, что правильно, только если вы потом на них посмотрите. Печать десяти ошибочных строк обычно мгновенно определяет проблему: второй формат в том же файле, строка нижнего колонтитула или заполнитель, например 0000-00-00.
Опасная закономерность заключается в том, что coerce за которым позже последовал dropna(), который удаляет именно те строки, даты которых были необычными. Это одна из шести операций, которым предстоит бросить вызов. просмотр сценария очистки.
Аксессор, в котором находится значение
d = df['order_date']
d.dt.year # 2026
d.dt.month # 4
d.dt.day_name() # 'Friday'
d.dt.quarter # 2
d.dt.to_period('M') # 2026-04
d.dt.days_in_month
(d.max() - d.min()).days # span in days
.dt существует только тогда, когда столбец является реальным датой и временем, что делает его полезным тестом: если .dt выдает ошибку атрибута, столбец по-прежнему является текстовым, хотя и выглядит как дата.
Группировка по месяцам, два способа
# labels, only the months that occur
df.groupby(df['order_date'].dt.to_period('M'))['amount'].sum()
# a continuous axis, including months with no rows
df.set_index('order_date').resample('MS')['amount'].sum()
Разница имеет значение для отчетности. to_period возвращает только те месяцы, по которым есть данные, поэтому в результате отсутствует месяц без заказов, и диаграмма соединяется прямо через него. resample вставляет его как ноль, что обычно и требуется для временного ряда, и это именно тот разрыв, который обсуждался в чтение восходящей линии.
Арифметика дат
df['days_to_ship'] = (df['shipped_date'] - df['order_date']).dt.days
cutoff = pd.Timestamp('2026-01-01')
df[df['order_date'] >= cutoff]
df['order_date'] + pd.DateOffset(months=1) # calendar-aware
df['order_date'] + pd.Timedelta(days=30) # exactly 30 days
Последние два не одинаковы, и разница ощутима в конце месяца. DateOffset(months=1) с 31 января дает 28 февраля; Timedelta(days=30) дает 2 марта. Выбирайте осознанно и скажите, какое из них означает бизнес-правило.
Часовые пояса, кратко
Наивная временная метка не имеет зоны, и сравнение ее со знакомой меткой повышает значение. Если какая-либо часть ваших данных зависит от зоны, сделайте это все и храните в формате UTC:
d.dt.tz_localize('Europe/London').dt.tz_convert('UTC')
Последствия для отчетности, о которых следует знать: событие в 23:30 UTC принадлежит другому местному дню, поэтому дневные счетчики могут отличаться на количество строк за целый день в зависимости от того, какую зону использовала группировка. Скажите в отчете, в какой зоне находятся дни.
Как применить это в своей работе
- добавлять
format=каждомуto_datetimeназывать тебя своим. Это изменение одной строки для каждого вызова, которое устраняет целый класс скрытых ошибок. - После анализа выведите минимальное и максимальное значения, а также количество по месяцам. Две строки, и они обнаруживают неправильный анализ.
- Подсчитайте вынужденные неудачи и посмотрите на десять из них, а не отбрасывайте их.
- Спросите любую систему, которой вы управляете, о датах ISO. Это самое дешевое улучшение качества данных.
- Решите, нужно ли отображать в ежемесячном отчете пустые месяцы, и выберите
resampleилиto_periodсоответственно.
Единственная привычка, которую нужно сохранить
Проверьте проанализированные даты перед их использованием. Мин, макс и количество по месяцам. Десять секунд, и это единственный надежный способ поймать столбец даты, который был проанализирован успешно или ошибочно.
Знаете ли вы, в каком порядке были прочитаны даты в вашем основном файле?
pd.to_datetime(pd.Series(["03/04/2026"])) возвращает 04.03.2026 и тот же вызов с format="%d/%m/%Y" возвращается 03 апреля 2026 г.Python для аналитиков — это рабочая книга по аналитике pandas: прочитайте файл, исправьте его типы, сгруппируйте его, присоединитесь к нему и проверьте ответ, прежде чем его увидит кто-либо еще.
Python для аналитиков, 19 $ →read_csv охватывает шаг нагрузки, просмотр сценария очистки охватывает решения и даты в SQL это версия на стороне базы данных.
Читать read_csv →