使用 pandas 解析大量日期 - 可扩展性 - 性能下降速度快于线性
Parsing large amount of dates with pandas - scalability - performance drops faster than linear
在使用 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 秒
1 米: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 秒
1 米:3.7 秒
10米:36秒
对于较小的文件,例程比默认的 pandas 解析器慢,但对于较大的文件,它可以完美地线性扩展。所以它看起来真的像是标准日期解析例程中的某种性能泄漏。
好吧,我可以使用我的解析器,但它非常简单、愚蠢,而且显然很慢。我宁愿使用智能、健壮且快速的 Pandas 解析器,前提是我能以某种方式解决可伸缩性问题。如果可以通过一些深奥的参数或其他方法解决,有人知道吗?
更新
感谢大家迄今为止的帮助。
毕竟,日期解析似乎存在可重现的性能问题,但与可伸缩性无关。我原来的分析是错误的。
您可以试试下载这个文件
https://www.dropbox.com/s/c5m21s1uif329f1/slow.csv.tar.gz?dl=0
并在 Pandas 中解析它。格式和一切都是正确的,所有数据都是有效的。只有 100k 条记录,但解析它们需要 3 秒 - 而从生成的常规序列中解析 100k 条记录需要 0.1s。
发生了什么:我没有像@exp1orer 那样按常规序列生成原始测试数据。我正在对我们的真实数据进行抽样,它们的分布并不是那么规律。该序列总体上以恒定的速度增长,但存在一些局部不规则和无序片段。而且,显然,在我的 10M 样本中,恰好有一个部分,这让 pandas 特别不高兴,解析时间很长。导致所有速度缓慢的只是文件内容的一小部分。但我无法发现该部分与文件其余部分之间的任何主要差异。
更新 2
所以,缓慢的原因是有一些奇怪的日期,比如 20124-10-20。显然,在将数据导入 Pandas.
之前,我需要做更多的预处理
更新:
看看这个对比:
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 倍的时间,因为其中一行有 "bad" 日期,最后 Pandas 离开了 from
df
DF 中的列作为字符串
旧答案:
它对我来说很公平 (pandas 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%
好的——根据评论和 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 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 秒
1 米: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 秒
1 米:3.7 秒
10米:36秒
对于较小的文件,例程比默认的 pandas 解析器慢,但对于较大的文件,它可以完美地线性扩展。所以它看起来真的像是标准日期解析例程中的某种性能泄漏。
好吧,我可以使用我的解析器,但它非常简单、愚蠢,而且显然很慢。我宁愿使用智能、健壮且快速的 Pandas 解析器,前提是我能以某种方式解决可伸缩性问题。如果可以通过一些深奥的参数或其他方法解决,有人知道吗?
更新
感谢大家迄今为止的帮助。
毕竟,日期解析似乎存在可重现的性能问题,但与可伸缩性无关。我原来的分析是错误的。
您可以试试下载这个文件 https://www.dropbox.com/s/c5m21s1uif329f1/slow.csv.tar.gz?dl=0 并在 Pandas 中解析它。格式和一切都是正确的,所有数据都是有效的。只有 100k 条记录,但解析它们需要 3 秒 - 而从生成的常规序列中解析 100k 条记录需要 0.1s。
发生了什么:我没有像@exp1orer 那样按常规序列生成原始测试数据。我正在对我们的真实数据进行抽样,它们的分布并不是那么规律。该序列总体上以恒定的速度增长,但存在一些局部不规则和无序片段。而且,显然,在我的 10M 样本中,恰好有一个部分,这让 pandas 特别不高兴,解析时间很长。导致所有速度缓慢的只是文件内容的一小部分。但我无法发现该部分与文件其余部分之间的任何主要差异。
更新 2
所以,缓慢的原因是有一些奇怪的日期,比如 20124-10-20。显然,在将数据导入 Pandas.
之前,我需要做更多的预处理更新:
看看这个对比:
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 倍的时间,因为其中一行有 "bad" 日期,最后 Pandas 离开了 from
df
DF 中的列作为字符串
旧答案:
它对我来说很公平 (pandas 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%
好的——根据评论和 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()