Ответ 1
Вы определили две важные функции: read_csv
готовит функцию для синтаксического анализа столбцов даты с помощью _make_date_converter
, и это всегда будет делать вызов to_datetime
(pandas "первичная строка-дата инструмент преобразования).
Ответы @WillAyd и @bmbigbang кажутся мне правильными, потому что они идентифицируют причину медленности как повторные вызовы лямбда-функции.
Поскольку вы запрашиваете более подробные сведения о исходном коде pandas, я попробую и рассмотрю каждый вызов read_test
более подробно ниже, чтобы узнать, где мы закончили в to_datetime
, и, в конечном счете, почему тайминги вы наблюдали за своими данными.
read_test()
Это очень быстро, потому что без каких-либо подсказок о возможном формате даты pandas будет пытаться проанализировать столбцы строк, похожие на список, как если бы они были примерно в ISO8601 формат (это очень распространенный случай).
Переходя в to_datetime
, мы быстро достигаем этой ветки кода:
if result is None and (format is None or infer_datetime_format):
result = tslib.array_to_datetime(...)
С этого момента он скомпилированный код Cython полностью.
array_to_datetime
итерации через столбец строк для преобразования каждого в формат даты и времени. Для каждой строки мы нажимаем _string_to_dts
в эта строка; затем переходим к другому короткому фрагменту встроенного кода (_cstring_to_dts
), что означает parse_iso_8601_datetime
вызывается для фактического синтаксического анализа строки для объекта datetime.
Эта функция более чем способна анализировать даты в формате YYYY-MM-DD, и поэтому для завершения задания требуется только некоторое обслуживание (структура C, заполненная parse_iso_8601_datetime
, становится надлежащим объектом datetime, некоторые ограничения проверяются).
Как вы можете видеть, dateutil.parser.parser
вообще не вызывается.
read_test(infer_datetime_format=True)
Посмотрим, почему это почти так же быстро, как read_test()
.
Запрос pandas на вывод формата даты и времени (и передача аргумента format
) означает, что мы приземляемся здесь в to_datetime
:
if infer_datetime_format and format is None:
format = _guess_datetime_format_for_array(arg, dayfirst=dayfirst)
Это вызывает _guess_datetime_format_for_array
, который принимает первое ненулевое значение в столбце и присваивает его _guess_datetime_format
. Это пытается создать строку формата даты и времени, которая будет использоваться для будущего анализа. (Мой ответ здесь содержит более подробные сведения о форматах, которые он может распознать.)
К счастью, формат YYYY-MM-DD - это тот, который может быть распознан этой функцией. К счастью, этот конкретный формат имеет быстрый путь через код pandas!
Вы можете видеть pandas наборы infer_datetime_format
обратно на False
здесь:
if format is not None:
# There is a special fast-path for iso8601 formatted
# datetime strings, so in those cases don't use the inferred
# format because this path makes process slower in this
# special case
format_is_iso8601 = _format_is_iso(format)
if format_is_iso8601:
require_iso8601 = not infer_datetime_format
format = None
Это позволяет коду принимать тот же путь, что и выше, к функции parse_iso_8601_datetime
.
read_test(date_parser=lambda dt: strptime(dt, '%Y-%m-%d'))
Мы предоставили функцию для синтаксического анализа даты, поэтому pandas выполняет этот блок кода.
Однако это возникает как исключение изнутри:
strptime() argument 1 must be str, not numpy.ndarray
Это исключение сразу же попадает, а pandas возвращается к использованию try_parse_dates
перед вызовом to_datetime
.
try_parse_dates
означает, что вместо вызова в массиве функция лямбда вызывается повторно для каждого значения массива в этот цикл:
for i from 0 <= i < n:
if values[i] == '':
result[i] = np.nan
else:
result[i] = parse_date(values[i]) # parse_date is the lambda function
Несмотря на скомпилированный код, мы платим штраф за вызовы функций для кода Python. Это делает его очень медленным по сравнению с другими подходами выше.
Вернемся в to_datetime
, теперь у нас есть массив объектов, заполненный объектами datetime
. Снова мы нажали array_to_datetime
, но на этот раз pandas видит объект даты и использует другую функцию (pydate_to_dt64
), чтобы превратить ее в объект datetime64.
Причина замедления на самом деле связана с повторными вызовами лямбда-функции.
О вашем обновлении и формате MM/DD/YYYY
Серия s
имеет строки даты в формате MM/DD/YYYY.
Это не формат ISO8601. pd.to_datetime(s, infer_datetime_format=False)
пытается проанализировать строку, используя parse_iso_8601_datetime
, но это не удается с помощью ValueError
. Ошибка обрабатывается здесь: pandas будет использовать parse_datetime_string
. Это означает, что dateutil.parser.parse
используется для преобразования строки в дату и время. Поэтому в этом случае он медленный: повторное использование функции Python в цикле.
Здесь нет большой разницы между pd.to_datetime(s, format='%m/%d/%Y')
и pd.to_datetime(s, infer_datetime_format=True)
с точки зрения скорости. Последний использует _guess_datetime_format_for_array
для вывода формата MM/DD/YYYY. И тогда нажмите array_strptime
здесь:
if format is not None:
...
if result is None:
try:
result = array_strptime(arg, format, exact=exact, errors=errors)
array_strptime
- это быстрая функция Cython для синтаксического анализа массива строк в datetime-структурах, заданных в определенном формате.