【问题标题】:Speed-improvement on large pandas read_csv with datetime index使用日期时间索引提高大熊猫 read_csv 的速度
【发布时间】:2013-01-21 20:33:03
【问题描述】:

我有很多看起来像这样的文件:

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

05/31/2012,15:30:00.029,130​​6.25,8,E,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]])

有什么方法可以有效地将这样的日期解析为 pandas 时间戳?如果没有,是否有编写可以传递给 date_parser= 的 cython 函数的指南?

我尝试编写自己的解析器函数,但我正在进行的项目仍然需要很长时间。

【问题讨论】:

  • 那么read_csv函数满足你的解析需求但是太慢了?
  • 是的,基本上。如果没有简单的解决方案,我想看看是否有人可以提供在 cython 中处理此问题的指南。
  • pd.Timestamp 不起作用(它单独起作用,例如 pd.Timestamp('05/31/2012,15:30:00.029'))让我感到困惑。它没有的事实很可能是一个错误。
  • 发布为an issue on github
  • 修复了 git master 中的 bug

标签: python performance pandas date-formatting


【解决方案1】:

对之前solution of Michael WS的改进:

  • 转换为pandas.Timestamp 最好在 Cython 代码之外执行
  • atoi 和处理 native-c 字符串比 python funcs 快一点
  • datetime-lib 调用的数量从 2 减少到 1(+1 偶尔为日期)
  • 微秒也被处理

注意!此代码中的日期顺序为日/月/年。

总而言之,代码似乎比原来的convert_date_cython 快了大约 10 倍。但是,如果在read_csv 之后调用它,那么在 SSD 硬盘驱动器上,由于读取开销,总时间只有几个百分点。我猜在普通硬盘上差异会更小。

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)

【讨论】:

    【解决方案2】:

    使用以下 cython 代码,我获得了令人难以置信的加速 (50X):

    从 python 调用: 时间戳 = 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
    

    【讨论】:

      【解决方案3】:

      日期时间字符串的基数并不大。例如,%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')
      

      使用以下特定数据集,加速比为 150 倍以上:

      $ 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 之类的东西。

      【讨论】:

      • 在我的例子中,它的毫秒标记。这是巨大的 15:30:00.029
      • @MichaelWS 所以你可以在缓存中使用 1k 额外的项目,你明白了
      猜你喜欢
      • 2014-02-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-06
      • 2013-07-20
      相关资源
      最近更新 更多