Mejora de la velocidad en pandas grandes read_csv con índice de fecha y hora

Tengo archivos enormes que se parecen a esto:

31/05 / 2012,15: 30: 00.029,1306.25,1, E, 0,, ​​1306.25

31/05 / 2012,15: 30: 00.029,1306.25,8, E, 0,, ​​1306.25

Puedo leerlos fácilmente usando lo siguiente:

pd.read_csv(gzip.open("myfile.gz"), header=None,names= ["date","time","price","size","type","zero","empty","last"], parse_dates=[[0,1]]) 

¿Hay alguna manera de analizar eficientemente fechas como esta en marcas de tiempo de pandas? Si no, ¿hay alguna guía para escribir una función de cython que pueda pasar a date_parser =?

Intenté escribir mi propia función de analizador y aún así toma demasiado tiempo para el proyecto en el que estoy trabajando.

Una mejora de la solución anterior de Michael WS :

  • la conversión a pandas.Timestamp es mejor realizar fuera del código de Cython
  • atoi y el procesamiento de cadenas atoi -C es un poco más rápido que las funciones de Python
  • el número de llamadas de datetime -lib se reduce a uno desde 2 (+1 ocasional para la fecha)
  • Los microsegundos también se procesan.

¡NÓTESE BIEN! El orden de fecha en este código es día / mes / año.

En general, el código parece ser aproximadamente 10 veces más rápido que el convert_date_cython original. Sin embargo, si se llama después de read_csv , en la unidad de disco duro SSD, la diferencia es que el tiempo total es solo un porcentaje debido a la sobrecarga de lectura. Supongo que en el disco duro normal la diferencia sería aún más pequeña.

 cimport numpy as np import datetime import numpy as np import pandas as pd from libc.stdlib cimport atoi, malloc, free from libc.string cimport strcpy ### Modified code from Michael WS: ### https://stackoverflow.com/a/15812787/2447082 def convert_date_fast(np.ndarray date_vec, np.ndarray time_vec): cdef int i, d_year, d_month, d_day, t_hour, t_min, t_sec, t_ms cdef int N = len(date_vec) cdef np.ndarray out_ar = np.empty(N, dtype=np.object) cdef bytes prev_date =  'xx/xx/xxxx' cdef char *date_str =  malloc(20) cdef char *time_str =  malloc(20) for i in range(N): if date_vec[i] != prev_date: prev_date = date_vec[i] strcpy(date_str, prev_date) ### xx/xx/xxxx date_str[2] = 0 date_str[5] = 0 d_year = atoi(date_str+6) d_month = atoi(date_str+3) d_day = atoi(date_str) strcpy(time_str, time_vec[i]) ### xx:xx:xx:xxxxxx time_str[2] = 0 time_str[5] = 0 time_str[8] = 0 t_hour = atoi(time_str) t_min = atoi(time_str+3) t_sec = atoi(time_str+6) t_ms = atoi(time_str+9) out_ar[i] = datetime.datetime(d_year, d_month, d_day, t_hour, t_min, t_sec, t_ms) free(date_str) free(time_str) return pd.to_datetime(out_ar) 

Obtuve una increíble aceleración (50X) con el siguiente código de cython:

call from python: timestamps = convert_date_cython (df [“fecha”]. valores, df [“tiempo”]. valores)

 cimport numpy as np import pandas as pd import datetime import numpy as np def convert_date_cython(np.ndarray date_vec, np.ndarray time_vec): cdef int i cdef int N = len(date_vec) cdef out_ar = np.empty(N, dtype=np.object) date = None for i in range(N): if date is None or date_vec[i] != date_vec[i - 1]: dt_ar = map(int, date_vec[i].split("/")) date = datetime.date(dt_ar[2], dt_ar[0], dt_ar[1]) time_ar = map(int, time_vec[i].split(".")[0].split(":")) time = datetime.time(time_ar[0], time_ar[1], time_ar[2]) out_ar[i] = pd.Timestamp(datetime.datetime.combine(date, time)) return out_ar 

La cardinalidad de las cadenas de fecha y hora no es enorme. Por ejemplo, la cantidad de cadenas de tiempo en el formato %H-%M-%S es 24 * 60 * 60 = 86400 . Si el número de filas de su conjunto de datos es mucho mayor que esto o si sus datos contienen muchas marcas de tiempo duplicadas, agregar una memoria caché en el proceso de análisis podría acelerar considerablemente las cosas.

Para aquellos que no tienen Cython disponible, aquí hay una solución alternativa en python puro:

 import numpy as np import pandas as pd from datetime import datetime def parse_datetime(dt_array, cache=None): if cache is None: cache = {} date_time = np.empty(dt_array.shape[0], dtype=object) for i, (d_str, t_str) in enumerate(dt_array): try: year, month, day = cache[d_str] except KeyError: year, month, day = [int(item) for item in d_str[:10].split('-')] cache[d_str] = year, month, day try: hour, minute, sec = cache[t_str] except KeyError: hour, minute, sec = [int(item) for item in t_str.split(':')] cache[t_str] = hour, minute, sec date_time[i] = datetime(year, month, day, hour, minute, sec) return pd.to_datetime(date_time) def read_csv(filename, cache=None): df = pd.read_csv(filename) df['date_time'] = parse_datetime(df.loc[:, ['date', 'time']].values, cache=cache) return df.set_index('date_time') 

Con el siguiente conjunto de datos en particular, la aceleración es de 150x +:

 $ ls -lh test.csv -rw-r--r-- 1 blurrcat blurrcat 1.2M Apr 8 12:06 test.csv $ head -n 4 data/test.csv user_id,provider,date,time,steps 5480312b6684e015fc2b12bc,fitbit,2014-11-02 00:00:00,17:47:00,25 5480312b6684e015fc2b12bc,fitbit,2014-11-02 00:00:00,17:09:00,4 5480312b6684e015fc2b12bc,fitbit,2014-11-02 00:00:00,19:10:00,67 

En ipython:

 In [1]: %timeit pd.read_csv('test.csv', parse_dates=[['date', 'time']]) 1 loops, best of 3: 10.3 s per loop In [2]: %timeit read_csv('test.csv', cache={}) 1 loops, best of 3: 62.6 ms per loop 

Para limitar el uso de la memoria, simplemente reemplace el caché de dict con algo como un LRU.