使用 pandas 解析大量日期 - 可扩展性 - 性能下降速度比线性快

Posted

技术标签:

【中文标题】使用 pandas 解析大量日期 - 可扩展性 - 性能下降速度比线性快【英文标题】:Parsing large amount of dates with pandas - scalability - performance drops faster than linear 【发布时间】:2016-07-28 19:55:40 【问题描述】:

在使用 Pandas 0.17.1 解析大量日期时,我遇到了一个奇怪的性能问题。为了演示,我创建了只有一列的 CSV 文件,其中包含格式为“2015-12-31 13:01:01”的日期时间。示例文件包含 10k、100k、1M 和 10M 记录。我是这样解析的:

start = timer()
pd.read_csv('10k_records.csv', parse_dates=['date'])
end = timer()
print(end - start)

经过的时间是: 10k:0.011 秒 100k:0.10 秒 1m:1.2 秒 10m:300 秒

你看,时间与记录数成线性关系,直到 100 万,但随后出现了巨大的下降。

这不是内存问题。我有 16GB,我使用这种大小的数据帧在 Pandas 中没有任何问题,只是日期解析似乎很慢。

我尝试使用 infer_datetime_format=True,但速度差不多。 1000 万条记录也大幅下降。

然后我尝试注册自己的幼稚日期解析器:

def parseDate(t):
    if type(t) is str :
        st = str(t)
        try:
          return datetime.datetime(int(st[:4]),int(st[5:7]),int(st[8:10]),int(st[11:13]),int(st[14:16]),int(st[17:19]))
        except:
          return None
    return datetime.datetime(0,0,0,0,0,0)

pd.read_csv(
    '10k_records.csv', parse_dates=['date'],
    date_parser=parseDate
)

现在是时候了: 10k:0.045 秒 100k:0.36 秒 1m:3.7 秒 10m:36 秒

对于较小的文件,该例程比默认的 pandas 解析器要慢,但对于较大的文件,它可以完美地线性扩展。因此,在标准日期解析例程中看起来确实存在某种性能泄漏。

好吧,我可以使用我的解析器,但它非常简单、愚蠢,而且显然很慢。我更喜欢使用智能、强大和快速的 Pandas 解析器,前提是我能以某种方式解决可伸缩性问题。如果可以通过一些深奥的参数或其他东西来解决,有人会有任何想法吗?

更新

感谢大家迄今为止的帮助。

毕竟,日期解析似乎存在可重现的性能问题,但与可伸缩性无关。我最初的分析是错误的。

您可以尝试下载此文件 https://www.dropbox.com/s/c5m21s1uif329f1/slow.csv.tar.gz?dl=0 并在 Pandas 中解析它。格式和一切都是正确的,所有数据都是有效的。只有 10 万条记录,但解析它们需要 3 秒 - 而从生成的规则序列中解析 10 万条记录需要 0.1 秒。

发生了什么:我没有像@exp1orer 那样将原始测试数据生成为常规序列。我正在对我们的真实数据进行抽样,它们的分布并不那么规律。该序列整体以恒定的速度增长,但存在一些局部不规则和无序的部分。而且,显然,在我的 10M 样本中,恰好有一个部分,这让 pandas 特别不高兴,并且解析花了这么长时间。造成所有缓慢的只是文件内容的一小部分。但我无法发现该部分与文件其余部分之间的任何主要差异。

更新 2

所以,缓慢的原因是有一些奇怪的日期,例如 20124-10-20。显然,在将数据导入 Pandas 之前,我需要做更多的预处理。

【问题讨论】:

这真的很有趣,也是一个定义非常明确的问题。您介意发布代码以生成 csv 文件吗? 如果您不介意,我会从 Java 生成它。我会发布它。 @exp1orer 就在那里,我只花了几分钟就让它可移植 - 删除对我们专有内容的引用。 嘿,我最终自己在 python 中完成了。但我实际上无法重现你的问题——我也得到大约 1s 的 100 万,但我得到约 11s 的 1000 万,这仍然是线性的。这就是我正在做的事情:pastebin.com/92NaTgxn 我正在使用 pandas '0.17.1'。你有什么版本? 【参考方案1】:

更新:

看看这个比较:

In [507]: fn
Out[507]: 'D:\\download\\slow.csv.tar.gz'

In [508]: fn2
Out[508]: 'D:\\download\\slow_filtered.csv.gz'

In [509]: %timeit df = pd.read_csv(fn, parse_dates=['from'], index_col=0)
1 loop, best of 3: 15.7 s per loop

In [510]: %timeit df2 = pd.read_csv(fn2, parse_dates=['from'], index_col=0)
1 loop, best of 3: 399 ms per loop

In [511]: len(df)
Out[511]: 99831

In [512]: len(df2)
Out[512]: 99831

In [513]: df.dtypes
Out[513]:
from    object
dtype: object

In [514]: df2.dtypes
Out[514]:
from    datetime64[ns]
dtype: object

这两个 DF 之间的唯一区别在于第 36867 行,我已在 D:\\download\\slow_filtered.csv.gz 文件中手动更正:

In [518]: df.iloc[36867]
Out[518]:
from    20124-10-20 10:12:00
Name: 36867, dtype: object

In [519]: df2.iloc[36867]
Out[519]:
from   2014-10-20 10:12:00
Name: 36867, dtype: datetime64[ns]

结论:Pandas 花费了 39 倍的时间,因为一行有一个“错误”日期,最后 Pandas 在 df DF 中留下了 from 列作为字符串

旧答案:

它对我来说很公平(熊猫 0.18.0):

设置:

start_ts = '2000-01-01 00:00:00'

pd.DataFrame('date': pd.date_range(start_ts, freq='1S', periods=10**4)).to_csv('d:/temp/10k.csv', index=False)

pd.DataFrame('date': pd.date_range(start_ts, freq='1S', periods=10**5)).to_csv('d:/temp/100k.csv', index=False)

pd.DataFrame('date': pd.date_range(start_ts, freq='1S', periods=10**6)).to_csv('d:/temp/1m.csv', index=False)

pd.DataFrame('date': pd.date_range(start_ts, freq='1S', periods=10**7)).to_csv('d:/temp/10m.csv', index=False)

dt_parser = lambda x: pd.to_datetime(x, format="%Y-%m-%d %H:%M:%S")

检查:

In [360]: fn = 'd:/temp/10m.csv'

In [361]: %timeit pd.read_csv(fn, parse_dates=['date'], dtype=0: pd.datetime, date_parser=dt_parser)
1 loop, best of 3: 22.6 s per loop

In [362]: %timeit pd.read_csv(fn, parse_dates=['date'], dtype=0: pd.datetime)
1 loop, best of 3: 29.9 s per loop

In [363]: %timeit pd.read_csv(fn, parse_dates=['date'])
1 loop, best of 3: 29.9 s per loop

In [364]: fn = 'd:/temp/1m.csv'

In [365]: %timeit pd.read_csv(fn, parse_dates=['date'], dtype=0: pd.datetime, date_parser=dt_parser)
1 loop, best of 3: 2.32 s per loop

In [366]: %timeit pd.read_csv(fn, parse_dates=['date'], dtype=0: pd.datetime)
1 loop, best of 3: 3.06 s per loop

In [367]: %timeit pd.read_csv(fn, parse_dates=['date'])
1 loop, best of 3: 3.06 s per loop

In [368]: %timeit pd.read_csv(fn)
1 loop, best of 3: 1.53 s per loop

结论:当我使用date_parser 指定日期格式时,它会快一点,所以read_csv 不必猜测它。差异约为。 30%

【讨论】:

澄清一下,你是说你也不能复制,对吧? @exp1orer,是的,这对我来说非常线性 酷。我也在 18.0 上进行了尝试,并使其也成为线性的。 所以,实际上似乎存在性能问题 - 请参阅我的更新 - 但与我最初想象的不同。 @JanXMarek,我不确定,但它可能是由错误的日期引起的,比如这一行:36867,20124-10-20 10:12:00【参考方案2】:

好的——根据 cmets 和 chat room 中的讨论,似乎 OP 的数据存在问题。使用下面的代码他无法重现他自己的错误:

import pandas as pd
import datetime
from time import time

format_string = '%Y-%m-%d %H:%M:%S'
base_dt = datetime.datetime(2016,1,1)
exponent_range = range(2,8)


def dump(number_records):
    print 'now dumping %s records' % number_records
    dts = pd.date_range(base_dt,periods=number_records,freq='1s')
    df = pd.DataFrame('date': [dt.strftime(format_string) for dt in dts])
    df.to_csv('%s_records.csv' % number_records)


def test(number_records):
    start = time()
    pd.read_csv('%s_records.csv' % number_records, parse_dates=['date'])
    end = time()
    print str(number_records), str(end - start)

def main():
    for i in exponent_range:
        number_records = 10**i
        dump(number_records)
        test(number_records)

if __name__ == '__main__':
    main()

【讨论】:

以上是关于使用 pandas 解析大量日期 - 可扩展性 - 性能下降速度比线性快的主要内容,如果未能解决你的问题,请参考以下文章

将 xlsb 文件读取为 pandas 数据框并将日期列解析为日期时间格式

pandas使用read_csv函数读取文件并解析日期数据列(parse dates)pandas使用read_csv函数读取文件并将缺失值转化为空字符串

从 SQL 数据库导入表并按日期过滤行时,将 Pandas 列解析为 Datetime

从 SQL 数据库导入表并按日期过滤行时,将 Pandas 列解析为 Datetime

pandas使用read_csv函数读取csv数据设置parse_dates参数将csv数据中的指定字段数据列解析为时间日期对象

Python Pandas read_csv 函数不允许将解析日期更改为所需格式