У меня есть огромные файлы, которые выглядят так:

05 /31 / 2012,15 : 30 : 00.029,1306.25,1 , Е, 0 , , 1306,25

05 /31 / 2012,15 : 30 : 00.029,1306.25,8 , Е, 0 , , 1306,25

Я могу легко прочитать их, используя следующее:

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

Есть ли способ эффективно разобрать даты, подобные этой, в метки времени панд? Если нет, есть ли руководство по написанию функции Cython, которая может быть передана в date_parser =?

Я попытался написать свою собственную функцию синтаксического анализа, и это все еще занимает слишком много времени для проекта, над которым я работаю.

8
Michael WS 22 Янв 2013 в 00:33

3 ответа

Лучший ответ

Улучшение предыдущего решения Michael WS:

  • преобразование в pandas.Timestamp лучше выполнять вне кода Cython
  • atoi и обработка строк native-c немного быстрее, чем функции Python
  • количество вызовов datetime - lib уменьшено до одного с 2 (+1 иногда для даты)
  • микросекунды также обрабатываются

NB ! Порядок даты в этом коде - день / месяц / год.

В целом код кажется примерно в 10 раз быстрее оригинального convert_date_cython. Однако если это вызывается после read_csv, то на жестком диске SSD разница в общем времени составляет всего несколько процентов из-за накладных расходов на чтение. Я предполагаю, что на обычных HDD разница будет еще меньше.

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 = <bytes> 'xx/xx/xxxx'
    cdef char *date_str = <char *> malloc(20)
    cdef char *time_str = <char *> 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)
7
mx0 26 Авг 2017 в 14:46

Количество строк datetime невелико. Например, число временных строк в формате %H-%M-%S равно 24 * 60 * 60 = 86400. Если количество строк в вашем наборе данных намного больше, чем это, или ваши данные содержат много повторяющихся временных отметок, добавление кэша в процессе анализа может существенно ускорить процесс.

Для тех, у кого нет Cython, вот альтернативное решение на чистом Python:

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

Со следующим конкретным набором данных ускорение составляет 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

В 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

Чтобы ограничить использование памяти, просто замените кэш dict чем-то вроде LRU.

2
blurrcat 8 Апр 2015 в 06:41

Я получил невероятное ускорение (50X) со следующим кодом Cython:

Вызов из python: timestamps = convert_date_cython (df ["date"]. values, df ["time"]. values)

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
7
Michael WS 4 Апр 2013 в 13:32