Почему pandas.to_datetime работает медленно для нестандартного формата времени, такого как '2014/12/31'

У меня есть файл .csv в таком формате

timestmp, p
2014/12/31 00:31:01:9200, 0.7
2014/12/31 00:31:12:1700, 1.9
...

а при чтении через pd.read_csv и преобразовании времени str в datetime с помощью pd.to_datetime производительность резко падает. Вот минимальный пример.

import re
import pandas as pd

d = '2014-12-12 01:02:03.0030'
c = re.sub('-', '/', d)

%timeit pd.to_datetime(d)
%timeit pd.to_datetime(c)
%timeit pd.to_datetime(c, format="%Y/%m/%d %H:%M:%S.%f")

и выступления:

10000 loops, best of 3: 62.4 µs per loop
10000 loops, best of 3: 181 µs per loop
10000 loops, best of 3: 82.9 µs per loop

Итак, как я могу улучшить производительность pd.to_datetime при чтении даты из файла csv?


person liubenyuan    schedule 16.08.2015    source источник


Ответы (3)


Это связано с тем, что pandas возвращается к dateutil.parser.parse для синтаксического анализа строк, когда он имеет формат, отличный от формата по умолчанию, или когда строка format не указана (это гораздо более гибко, но также медленнее).

Как вы показали выше, вы можете повысить производительность, указав строку format в to_datetime. Или другой вариант - использовать infer_datetime_format=True


Судя по всему, infer_datetime_format не может сделать вывод, когда есть микросекунды. На примере без них вы можете увидеть большое ускорение:

In [28]: d = '2014-12-24 01:02:03'

In [29]: c = re.sub('-', '/', d)

In [30]: s_c = pd.Series([c]*10000)

In [31]: %timeit pd.to_datetime(s_c)
1 loops, best of 3: 1.14 s per loop

In [32]: %timeit pd.to_datetime(s_c, infer_datetime_format=True)
10 loops, best of 3: 105 ms per loop

In [33]: %timeit pd.to_datetime(s_c, format="%Y/%m/%d %H:%M:%S")
10 loops, best of 3: 99.5 ms per loop
person joris    schedule 16.08.2015
comment
infer_datetime_format=True медленнее, результаты 1000 loops, best of 3: 908 µs per loop. Есть ли более быстрый способ преобразования нестандартных строк времени в datetime? - person liubenyuan; 16.08.2015
comment
Это потому, что вы рассчитываете одну строку. Если вы сделаете это для более крупной серии, вы увидите ускорение. - person joris; 16.08.2015
comment
И, как я уже сказал, существует более быстрый способ, тот, который вы сами использовали в вопросе: предоставить строку format. - person joris; 16.08.2015
comment
Спасибо ! еще один вопрос, буду ли я вручную e = re.sub('/', '-', c) применять to_datetime(e), что может улучшить производительность? - person liubenyuan; 16.08.2015
comment
Я так не думаю. Если вы можете это сделать, это означает, что вы точно знаете, что это за формат, и тогда лучше указать строку format. Подача строки даты в формате ISO немного быстрее для синтаксического анализа, но это не перевешивает время, необходимое для замены / на - - person joris; 16.08.2015
comment
На моей машине использование строки формата дало 20-кратное ускорение синтаксического анализа даты для фрейма данных в 50 тыс. Строк (4000 мс = ›180 мс). Определенно стоит использовать строку формата, если вы знаете ее заранее. - person Swaraj; 14.02.2016

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

Я получил этот формат из API: «8 февраля, среда, 17:58:56 +0000 2017».

При использовании значения по умолчанию pd.to_datetime(SERIES) с неявным преобразованием потребовалось более часа для обработки примерно 20 миллионов строк (в зависимости от того, сколько свободной памяти у меня было).

Тем не менее, я протестировал три разных преобразования:

# explicit conversion of essential information only -- parse dt str: concat
def format_datetime_1(dt_series):

    def get_split_date(strdt):
        split_date = strdt.split()
        str_date = split_date[1] + ' ' + split_date[2] + ' ' + split_date[5] + ' ' + split_date[3]
        return str_date

    dt_series = pd.to_datetime(dt_series.apply(lambda x: get_split_date(x)), format = '%b %d %Y %H:%M:%S')

    return dt_series

# explicit conversion of what datetime considers "essential date representation" -- parse dt str: del then join
def format_datetime_2(dt_series):

    def get_split_date(strdt):
        split_date = strdt.split()
        del split_date[4]
        str_date = ' '.join(str(s) for s in split_date)
        return str_date

    dt_series = pd.to_datetime(dt_series.apply(lambda x: get_split_date(x)), format = '%c')

    return dt_series

# explicit conversion of what datetime considers "essential date representation" -- parse dt str: concat
def format_datetime_3(dt_series):

    def get_split_date(strdt):
        split_date = strdt.split()
        str_date = split_date[0] + ' ' + split_date[1] + ' ' + split_date[2] + ' ' + split_date[3] + ' ' + split_date[5]
        return str_date

    dt_series = pd.to_datetime(dt_series.apply(lambda x: get_split_date(x)), format = '%c')

    return dt_series

# implicit conversion
def format_datetime_baseline(dt_series):

    return pd.to_datetime(dt_series)

Вот результаты:

# sample of 250k rows
dt_series_sample = df['created_at'][:250000]

%timeit format_datetime_1(dt_series_sample)        # best of 3: 1.56 s per loop
%timeit format_datetime_2(dt_series_sample)        # best of 3: 2.09 s per loop
%timeit format_datetime_3(dt_series_sample)        # best of 3: 1.72 s per loop
%timeit format_datetime_baseline(dt_series_sample) # best of 3: 1min 9s per loop

Первый тест показал впечатляющее сокращение времени выполнения на 97,7%!

Несколько удивительно, но похоже, что даже «соответствующее представление» занимает больше времени, вероятно, потому, что оно является полунявным.

Вывод: чем более откровенны вы будете, тем быстрее будет работать.

person Zach    schedule 04.09.2017
comment
Этот тест бесполезен без учета pd.to_datetime(dt_series, infer_datetime_format=True). Если все ваши строки имеют один и тот же формат, это может быть огромным ускорением по сравнению с infer_datetime_format=False по умолчанию. - person C8H10N4O2; 22.01.2018
comment
Что ж, я бы не сказал, что это бесполезно, но вы правы, это было бы хорошим дополнением (на самом деле я думал, что это значение по умолчанию, которое было бы охвачено базовой функцией). Может быть, вы можете добавить это в качестве ответа? - person Zach; 23.01.2018

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

В этих случаях, вместо использования pd.to_datetime, я счел более эффективным написать собственную оболочку для dateutil.parser.parse:

import pandas as pd
from dateutil.parser import parse
import numpy as np

def parseDateStr(s):
    if s != '':
        try:
            return np.datetime64(parse(s))
        except ValueError:
            return np.datetime64('NaT')
    else: return np.datetime64('NaT')             

# Example data:
someSeries=pd.Series(  ['NotADate','','1-APR-16']*10000 )

# Compare times:
%timeit pd.to_datetime(someSeries, errors='coerce') #1 loop, best of 3: 1.78 s per loop
%timeit someSeries.apply(parseDateStr)              #1 loop, best of 3: 904 ms per loop

# The approaches return identical results:
someSeries.apply(parseDateStr).equals(pd.to_datetime(someSeries, errors='coerce')) # True

В этом случае время выполнения сокращается вдвое, но YMMV.

person C8H10N4O2    schedule 08.12.2016