【问题标题】:Pandas: slow date conversion熊猫:缓慢的日期转换
【发布时间】:2015-07-05 03:05:30
【问题描述】:

我正在阅读一个巨大的CSV,其日期字段格式为YYYYMMDD,我在阅读时使用以下 lambda 进行转换:

import pandas as pd

df = pd.read_csv(filen,
                 index_col=None,
                 header=None,
                 parse_dates=[0],
                 date_parser=lambda t:pd.to_datetime(str(t),
                                            format='%Y%m%d', coerce=True))

虽然这个函数很慢。

有什么改进的建议吗?

【问题讨论】:

    标签: python optimization pandas


    【解决方案1】:

    注意:正如@ritchie46's answer 所述,此解决方案可能是多余的,因为熊猫版本 0.25 每个新参数 cache_dates 默认为 True

    尝试使用此函数解析日期:

    def lookup(date_pd_series, format=None):
        """
        This is an extremely fast approach to datetime parsing.
        For large data, the same dates are often repeated. Rather than
        re-parse these, we store all unique dates, parse them, and
        use a lookup to convert all dates.
        """
        dates = {date:pd.to_datetime(date, format=format) for date in date_pd_series.unique()}
        return date_pd_series.map(dates)
    

    像这样使用它:

    df['date-column'] = lookup(df['date-column'], format='%Y%m%d')
    

    基准测试:

    $ python date-parse.py
    to_datetime: 5799 ms
    dateutil:    5162 ms
    strptime:    1651 ms
    manual:       242 ms
    lookup:        32 ms
    

    来源:https://github.com/sanand0/benchmarks/tree/master/date-parse

    【讨论】:

    • NaN 有问题:return s.apply(lambda v: dates[v]) KeyError: nan
    • 哇:这太棒了!我有 1M 行测试文件。读取它需要 1 秒(无日期解析),但如果我打开 parse_dates 则需要 1 分 20 秒。您的 lookup() 解决方案仅在最初的 1 秒 read_csv() 基础上增加了 0.4 秒。
    • @fixxer 这太不可思议了.. 巧妙地使用 apply!多么伟大的原则..可以用作任何类型的 dict 查找的函数。
    • 上述基准测试结果基于解析 100,000 个相同日期。我以前没有遇到过这样的数据。我使用 100,000 个随机日期重新运行了相同的测试,现在发现“手动”比“查找”快得多。不出所料,当我介绍更多重复的日期时,“查找”性能更好。在我的系统上,它超过了“手动”,每个日期大约重复 125 次。
    • 我还在您的pd.to_datetime 行中提供了最佳format= 参数。无论如何,这段代码疯狂地加速了它。我有一个 13 GB 的 csv,每个日期重复了大约 340,000 次。我之前在pd.to_datetime 中包含cache=True,认为它的性能会相似,但这要快得多。
    【解决方案2】:

    很好的建议@EdChum!正如@EdChum 建议的那样,使用infer_datetime_format=True 可以显着更快。下面是我的例子。

    我有一个来自传感器日志的温度数据文件,如下所示:

    RecNum,Date,LocationID,Unused
    1,11/7/2013 20:53:01,13.60,"117","1",
    2,11/7/2013 21:08:01,13.60,"117","1",
    3,11/7/2013 21:23:01,13.60,"117","1",
    4,11/7/2013 21:38:01,13.60,"117","1",
    ...
    

    我的代码读取 csv 并解析日期 (parse_dates=['Date'])。 使用infer_datetime_format=False,需要 8min 8sec:

    Tue Jan 24 12:18:27 2017 - Loading the Temperature data file.
    Tue Jan 24 12:18:27 2017 - Temperature file is 88.172 MB.
    Tue Jan 24 12:18:27 2017 - Loading into memory. Please be patient.
    Tue Jan 24 12:26:35 2017 - Success: loaded 2,169,903 records.
    

    使用infer_datetime_format=True,需要 13 秒:

    Tue Jan 24 13:19:58 2017 - Loading the Temperature data file.
    Tue Jan 24 13:19:58 2017 - Temperature file is 88.172 MB.
    Tue Jan 24 13:19:58 2017 - Loading into memory. Please be patient.
    Tue Jan 24 13:20:11 2017 - Success: loaded 2,169,903 records.
    

    【讨论】:

      【解决方案3】:

      使用缓存简化日期解析

      读取所有数据然后转换它总是比读取 CSV 时转换慢。因为如果您立即执行此操作,您将不需要对所有数据进行两次迭代。您也不必将其作为字符串存储在内存中。

      我们可以定义我们自己的日期解析器,它利用缓存来存储它已经看到的日期。

      import pandas as pd
      
      cache = {}
      
      def cached_date_parser(s):
          if s in cache:
              return cache[s]
          dt = pd.to_datetime(s, format='%Y%m%d', coerce=True)
          cache[s] = dt
          return dt
          
      df = pd.read_csv(filen,
                       index_col=None,
                       header=None,
                       parse_dates=[0],
                       date_parser=cached_date_parser)
      

      与@fixxxer 的答案具有相同的优势,只需解析每个字符串一次,额外的好处是不必读取所有数据然后解析它。节省内存和处理时间。

      【讨论】:

      • 我收到错误to_datetime() got an unexpected keyword argument 'coerce'。当我尝试传递参数errors='coerce' 时,我得到了NaT。
      【解决方案4】:

      无需指定date_parser,pandas 可以轻松解析,而且速度更快:

      In [21]:
      
      import io
      import pandas as pd
      t="""date,val
      20120608,12321
      20130608,12321
      20140308,12321"""
      df = pd.read_csv(io.StringIO(t), parse_dates=[0])
      df.info()
      <class 'pandas.core.frame.DataFrame'>
      Int64Index: 3 entries, 0 to 2
      Data columns (total 2 columns):
      date    3 non-null datetime64[ns]
      val     3 non-null int64
      dtypes: datetime64[ns](1), int64(1)
      memory usage: 72.0 bytes
      In [22]:
      
      df
      Out[22]:
              date    val
      0 2012-06-08  12321
      1 2013-06-08  12321
      2 2014-03-08  12321
      

      【讨论】:

      • 不是推断会让事情变得更慢吗?
      • @MarcusMüller 除非您尝试,否则不知道,文档似乎暗示,一旦嗅探格​​式,它可能会优化日期解析而不是每次都猜测
      【解决方案5】:

      由于pandas version 0.25 函数pandas.read_csv 接受cache_dates=boolean(默认为True)关键字参数。因此,无需像接受的答案那样编写自己的缓存函数。

      【讨论】:

        【解决方案6】:

        试试标准库:

        import datetime
        parser = lambda t: datetime.datetime.strptime(str(t), "%Y%m%d")
        

        但是,我真的不知道这是否比 pandas 快得多。

        既然你的格式这么简单,那呢

        def parse(t):
             string_ = str(t)
             return datetime.date(int(string_[:4]), int(string[4:6]), int(string[6:]))
        

        EDIT你说你需要处理无效数据。

        def parse(t):
             string_ = str(t)
             try:
                 return datetime.date(int(string_[:4]), int(string[4:6]), int(string[6:]))
             except:
                 return default_datetime #you should define that somewhere else
        

        总而言之,我对你的问题的有效性有点矛盾:

        • 您需要速度很快,但您仍然可以从 CSV 获取数据
        • 您需要快速,但仍需要处理无效数据

        这有点矛盾;我个人的做法是假设您的“巨大”CSV 只需要一次转换为性能更好的格式,并且您不应该关心转换过程的速度(因为它只发生一次)或者您应该可能带上生成 CSV 的任何内容,以便为您提供更好的数据——有很多格式不依赖于字符串解析。

        【讨论】:

        • 不幸的是,它不适用于某些行中的无效日期(我不确定如何使用您的方法coerce
        • 您没有提到您需要处理无效数据;这有点不公平
        【解决方案7】:

        如果您的日期时间有 UTC 时间戳,而您只需要其中的一部分。将其转换为字符串,切片您需要的内容,然后应用以下内容以更快地访问。

        created_at
        2018-01-31 15:15:08 UTC
        2018-01-31 15:16:02 UTC
        2018-01-31 15:27:10 UTC
        2018-02-01 07:05:55 UTC
        2018-02-01 08:50:14 UTC
        
        df["date"]=  df["created_at"].apply(lambda x: str(x)[:10])
        
        
        df["date"] = pd.to_datetime(df["date"])
        

        【讨论】:

          【解决方案8】:

          我有一个大约 150k 行的 csv。在尝试了这篇文章中的几乎所有建议后,我发现:

          1. 使用Python3.7原生csv.reader逐行读取文件
          2. 使用float() 和
          3. 转换所有4 个数字列
          4. 用datetime.datetime.fromisoformat()解析日期列

          然后看:

          1. 最终将列表转换为 DataFrame (!)**

          这让我感到困惑,这怎么能比原生 pandas pd.read_csv(...)... 有人能解释一下吗?

          【讨论】:

            猜你喜欢
            • 2012-04-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-23
            • 2017-06-18
            • 1970-01-01
            • 2021-09-16
            • 1970-01-01
            相关资源
            最近更新 更多