web-dev-qa-db-ja.com

「2014/12/31」などの非標準の時刻形式でpandas.to_datetimeが遅いのはなぜですか

そのような形式の.csvファイルがあります

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

pd.read_csvを介して読み取られ、pd.to_datetimeを使用して時刻strを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

では、csvファイルから日付を読み取るときに、pd.to_datetimeのパフォーマンスをどのように向上させることができますか?

43
liubenyuan

これは、pandasがデフォルト以外の形式の場合、またはformat文字列が指定されていない場合に文字列を解析するためにdateutil.parser.parseにフォールバックするためです(これははるかに多くなります)柔軟ですが、速度も遅くなります)。

上記で示したように、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
45
joris

この質問はすでに十分に回答されていますが、自分のコードを最適化するために実行しているいくつかのテストの結果を追加したいと思いました。

私はこのフォーマットをAPIから取得していました: "Wed Feb 08 17:58:56 +0000 2017"。

暗黙的な変換でデフォルトのpd.to_datetime(SERIES)を使用すると、約2000万行を処理するのに1時間以上かかりました(使用していた空きメモリの量によって異なります)。

つまり、3つの異なる変換をテストしました。

# 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%と大幅に削減されました。

多少意外なことに、「適切な表現」でさえ、おそらくそれが半暗黙的であるため、時間がかかるように見えます。

結論:明示的であるほど、実行が速くなります。

5
Zach

多くの場合、各クライアントがどのように送信するかを知らないので、標準の日付形式を事前に指定できません。日付は予期せずフォーマットされ、欠落していることがよくあります。

このような場合、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です。

3
C8H10N4O2