python - example - ¿Por qué es pandas.to_datetime lento para formato de hora no estándar como ''2014/12/31''
string to datetime python (3)
Tengo un archivo .csv en dicho formato
timestmp, p
2014/12/31 00:31:01:9200, 0.7
2014/12/31 00:31:12:1700, 1.9
...
y cuando se lee a través de pd.read_csv
y convierte la franja de tiempo a datetime usando pd.to_datetime
, el rendimiento disminuye drásticamente. Aquí hay un ejemplo mínimo.
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")
y las actuaciones son:
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
Entonces, ¿cómo podría mejorar el rendimiento de pd.to_datetime
al leer la fecha de un archivo csv?
A menudo no puedo especificar un formato de fecha estándar antes de tiempo porque simplemente no sé cómo cada cliente elegirá enviarlo. Las fechas están impredeciblemente formateadas y a menudo faltan.
En estos casos, en lugar de usar pd.to_datetime
, he encontrado que es más eficiente escribir mi propio contenedor en 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
En este caso, el tiempo de ejecución se reduce a la mitad, pero a YMMV.
Esta pregunta ya ha recibido la respuesta suficiente, pero quería agregar los resultados de algunas pruebas que estaba ejecutando para optimizar mi propio código.
Estaba obteniendo este formato de una API: "mié 08 feb 17:58:56 +0000 2017".
Utilizando el predeterminado pd.to_datetime(SERIES)
con una conversión implícita, tomar más de una hora procesar aproximadamente 20 millones de filas (dependiendo de la cantidad de memoria libre que tenía).
Dicho esto, probé tres conversiones diferentes:
# 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)
Este fue el resultado:
# sample of 250k rows
dt_series_sample = df[''created_at''][:250000]
%timeit format_datetime_1(dt_series_sample)
%timeit format_datetime_2(dt_series_sample)
%timeit format_datetime_3(dt_series_sample)
%timeit format_datetime_baseline(dt_series_sample)
1 loop, best of 3: 1.56 s per loop
1 loop, best of 3: 2.09 s per loop
1 loop, best of 3: 1.72 s per loop
1 loop, best of 3: 1min 9s per loop
¡La primera prueba da como resultado una asombrosa reducción del tiempo de ejecución del 97.7%!
Sorprendentemente, parece que incluso la "representación adecuada" lleva más tiempo, probablemente porque es semi implícita.
Conclusión: cuanto más explícito seas, más rápido se ejecutará.
Esto se debe a que pandas dateutil.parser.parse
a dateutil.parser.parse
para analizar las cadenas cuando tiene un formato no predeterminado o cuando no se proporciona una cadena de format
(esto es mucho más flexible, pero también más lento).
Como ha mostrado anteriormente, puede mejorar el rendimiento suministrando una cadena de format
a to_datetime
. Otra opción es usar infer_datetime_format=True
Aparentemente, infer_datetime_format
no puede inferir cuando hay microsegundos. Con un ejemplo sin ellos, puede ver una gran aceleración:
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