web-dev-qa-db-fra.com

Pourquoi pandas.to_datetime est lent pour un format d'heure non standard tel que '2014/12/31'

J'ai un fichier .csv dans un tel format

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

et lorsqu'il est lu via pd.read_csv et convertissez le temps str en datetime en utilisant pd.to_datetime, les performances chutent considérablement. Voici un exemple minimal.

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")

et les performances sont:

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

alors, comment pourrais-je améliorer les performances de pd.to_datetime lors de la lecture de la date d'un fichier csv?

43
liubenyuan

En effet, pandas revient à dateutil.parser.parse pour analyser les chaînes lorsqu'elle a un format différent de celui par défaut ou lorsqu'aucune chaîne format n'est fournie (c'est beaucoup plus flexible, mais aussi plus lent).

Comme vous l'avez montré ci-dessus, vous pouvez améliorer les performances en fournissant une chaîne format à to_datetime. Ou une autre option consiste à utiliser infer_datetime_format=True


Apparemment, le infer_datetime_format ne peut pas déduire lorsqu'il y a des microsecondes. Avec un exemple sans ceux-ci, vous pouvez voir une grande accélération:

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

Cette question a déjà été suffisamment répondue, mais je voulais ajouter les résultats de certains tests que j'exécutais pour optimiser mon propre code.

J'obtenais ce format d'une API: "Wed Feb 08 17:58:56 +0000 2017".

En utilisant la fonction par défaut pd.to_datetime(SERIES) avec une conversion implicite, cela prenait plus d'une heure pour traiter environ 20 millions de lignes (selon la quantité de mémoire libre dont j'avais).

Cela dit, j'ai testé trois conversions différentes:

# 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)

Ce sont les résultats:

# 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

Le premier test se traduit par une réduction de l'exécution impressionnante de 97,7%!

De façon assez surprenante, il semble que même la "représentation appropriée" prenne plus de temps, probablement parce qu'elle est semi-implicite.

Conclusion: plus vous êtes explicite, plus vite il fonctionnera.

5
Zach

Souvent, je ne suis pas en mesure de spécifier un format de date standard à l'avance car je ne sais tout simplement pas comment chaque client choisira de le soumettre. Les dates sont formatées de façon imprévisible et manquent souvent.

Dans ces cas, au lieu d'utiliser pd.to_datetime, J'ai trouvé plus efficace d'écrire mon propre wrapper dans 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

Dans ce cas, le temps d'exécution est réduit de moitié, mais YMMV.

3
C8H10N4O2