Собесов

Сценарий: разношёрстные форматы дат в одной колонке

PythonОчистка данныхСредняяMiddle

Условие

В колонке ts встречаются форматы:

  • 2024-03-15 12:30:00
  • 15.03.2024 12:30
  • 03/15/2024
  • Mar 15, 2024
  • пустые строки и 'N/A'

Привести всё к единому datetime.

Решение

Подход

pd.to_datetime(errors='coerce') пробует dateutil-парсер для каждой строки. На странных форматах он медленный, но универсальный.

import pandas as pd
import numpy as np
 
df['ts'] = df['ts'].replace({'N/A': np.nan, '': np.nan})
 
# Универсально, но медленно (dateutil на каждой строке):
df['ts_parsed'] = pd.to_datetime(df['ts'], errors='coerce', dayfirst=True)
# dayfirst=True важно: '03/04' = 4 марта, не 3 апреля

Быстрее — несколько проходов с явным форматом

def parse_multiformat(s: pd.Series) -> pd.Series:
    out = pd.to_datetime(s, format='%Y-%m-%d %H:%M:%S', errors='coerce')
    mask = out.isna()
    out.loc[mask] = pd.to_datetime(s[mask], format='%d.%m.%Y %H:%M', errors='coerce')
    mask = out.isna()
    out.loc[mask] = pd.to_datetime(s[mask], format='%m/%d/%Y', errors='coerce')
    mask = out.isna()
    out.loc[mask] = pd.to_datetime(s[mask], format='%b %d, %Y', errors='coerce')
    return out
 
df['ts_parsed'] = parse_multiformat(df['ts'])

В 10–100 раз быстрее, чем dateutil.

Контроль качества

n_failed = df['ts_parsed'].isna().sum() - df['ts'].isna().sum()
print(f'{n_failed} строк не распарсились — посмотреть:')
print(df[df['ts_parsed'].isna() & df['ts'].notna()]['ts'].value_counts())

Подводные камни

  1. dayfirst решает неоднозначность 01/02/2024. Без него pandas использует US-формат (Jan 2). Для RU-данных всегда dayfirst=True.
  2. errors='coerce' молча превращает мусор в NaT — обязательно логировать сколько строк потерялось.
  3. Таймзоны: 2024-03-15T12:00:00+03:00 парсится, а 2024-03-15 12:00 MSK — нет. Сначала чистить.
  4. Excel даёт даты как «43891.5» (Excel-serial) — pd.to_datetime(..., unit='D', origin='1899-12-30').
  5. Mixed формат + format='mixed' (pandas ≥ 2.0) — однострочное решение, но медленное.

Эталонный ответ

pd.to_datetime(errors='coerce', dayfirst=True) для универсальности; для скорости — каскад с явными форматами и mask = out.isna(). Логировать число неудачных парсингов.

Хочешь увидеть разбор?

Зарегистрируйся бесплатно — откроется развёрнутое решение этой задачи и ещё 4 на выбор.

Зарегистрироваться и увидеть разбор
Уже есть аккаунт? Войти