Как определить даты и преобразовать в тип данных datetime64

#python #pandas #datetime

#python #pandas #дата-время

Вопрос:

Я считываю данные из CSV с помощью pandas.read_csv . Один из столбцов содержит информацию о дате в разных форматах (без стандартного ISO 8601 или подобного). Я хочу убедиться, что Pandas может определять формат даты, не требуя дополнительного ввода от пользователя. Честно говоря, я не совсем уверен, с чего начать. Я знаю, что Pandas может infer_datetime_format однако он не улавливает все изменения данных или может выдавать ошибку.

Мои наборы данных содержат несколько миллионов строк, поэтому процесс может занять довольно много времени. Моя идея заключалась в том, чтобы загрузить только первые 100 строк ( nrows=100 ), а затем позволить функции определять формат даты. Из наборов данных, которые я видел до сих пор, это может быть дд-мм-гг, дд-мм-гггг, гггг-мм-дд (и различные варианты с,.-разделителями), 19 января 2019 года, 1 января 2019 года и т.д. Также у меня есть текст на английском (декабрь) и немецком (декабрь).

Я подумал о том, чтобы запустить что-то вроде цикла for для каждого элемента в столбце, подготовить обращения с разными форматами, возможно, в try except блоке, и позволить Pyton определить правильный формат для первых 100 записей. (создайте разные варианты с форматами дат из:https://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior )

Имеет ли вообще смысл такой подход? Или как бы вы поступили? Заранее спасибо!

Ответ №1:

pd.to_datetime может быть на порядок медленнее, когда вы позволяете ему пытаться определить формат. В смешанных форматах вы можете попытаться проанализировать его несколько раз:

 import pandas as pd
from functools import reduce
                           # dd-mm-yy    dd-mm-YYYY    YYYY-mm-dd
df = pd.DataFrame({'date': ['12-01-01', '12-01-2001', '2001-07-05',
                            'Jan 19', 'January 2019', '1 January 2019']})
  

Код:

 formats = ['%d-%m-%y', '%d-%m-%Y', '%Y-%m-%d', '%b %y', '%B %Y', '%d %B %Y']
reduce(lambda l,r: l.combine_first(r), 
       [pd.to_datetime(df.date, format=fmt, errors='coerce') for fmt in formats])

0   2001-01-12
1   2001-01-12
2   2001-07-05
3   2019-01-01
4   2019-01-01
5   2019-01-01
Name: date, dtype: datetime64[ns]
  

В общем, pd.to_datetime можно гибко анализировать большинство этих форматов, если вы укажете dayfirst . Хотя это все равно будет медленнее, чем пытаться проанализировать его несколько раз с указанными форматами.

 pd.to_datetime(df.date, errors='coerce', dayfirst=True)
#0   2001-01-12
#1   2001-01-12
#2   2001-07-05
#3          NaT
#4   2019-01-01
#5   2019-01-01
#Name: date, dtype: datetime64[ns]

df = pd.concat([df]*10000, ignore_index=True)
%timeit reduce(lambda l,r: l.combine_first(r), [pd.to_datetime(df.date, format=fmt, errors='coerce') for fmt in formats])
#287 ms ± 2.35 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit pd.to_datetime(df.date, errors='coerce', dayfirst=True)
#5.79 s ± 36.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
  

Таким образом, вы все равно выигрываете, даже пытаясь разобрать его несколько раз, плюс вы не пропускаете некоторые нестандартные форматы.

Комментарии:

1. Отлично, это было именно то, что я искал. И спасибо вам за подробное объяснение. Действительно полезно! Мне очень нравится первый вариант с многократным синтаксическим анализом с указанными форматами. Если формат не может быть обнаружен, он будет сохранен как NaT правильный? Знаете ли вы о каких-либо дополнительных изменениях в отношении производительности (я просматриваю около 10 миллионов строк …)?

2. @pythoneer да NaT , значение datetime64 равно нулю. Что касается производительности, я не уверен в каких-либо других простых настройках. Это, безусловно, будет быстрее, чем перебор строк, но анализ даты, как правило, выполняется медленнее.

Ответ №2:

Вы могли бы попробовать dateparser, который поддерживает синтаксический анализ дат на нескольких языках.

Из их документации:

 >>> # parsing ambiguous date
>>> parse('02-03-2016')  # assumes english language, uses MDY date order
datetime.datetime(2016, 3, 2, 0, 0)
>>> parse('le 02-03-2016')  # detects french, uses DMY date order
datetime.datetime(2016, 3, 2, 0, 0)