为啥 pandas.to_datetime 对于非标准时间格式(例如“2014/12/31”)很慢
Posted
技术标签:
【中文标题】为啥 pandas.to_datetime 对于非标准时间格式(例如“2014/12/31”)很慢【英文标题】:Why is pandas.to_datetime slow for non standard time format such as '2014/12/31'为什么 pandas.to_datetime 对于非标准时间格式(例如“2014/12/31”)很慢 【发布时间】:2015-11-09 03:55:22 【问题描述】:我有一个这种格式的 .csv 文件
timestmp, p
2014/12/31 00:31:01:9200, 0.7
2014/12/31 00:31:12:1700, 1.9
...
当通过pd.read_csv
读取并使用pd.to_datetime
将时间str 转换为日期时间时,性能会急剧下降。这是一个最小的例子。
import re
import pandas as pd
d = '2014-12-12 01:02:03.0030'
c = re.sub('-', '/', d)
%timeit pd.to_datetime(d)
%timeit pd.to_datetime(c)
%timeit pd.to_datetime(c, format="%Y/%m/%d %H:%M:%S.%f")
表演如下:
10000 loops, best of 3: 62.4 µs per loop
10000 loops, best of 3: 181 µs per loop
10000 loops, best of 3: 82.9 µs per loop
那么,在从 csv 文件读取日期时,如何提高 pd.to_datetime
的性能?
【问题讨论】:
【参考方案1】:这是因为当 pandas 具有非默认格式或未提供 format
字符串时,它会回退到 dateutil.parser.parse
来解析字符串(这更灵活,但也更慢)。
如上所示,您可以通过向to_datetime
提供format
字符串来提高性能。或者另一种选择是使用infer_datetime_format=True
显然,infer_datetime_format
无法推断何时有微秒。没有这些的例子,你可以看到一个很大的加速:
In [28]: d = '2014-12-24 01:02:03'
In [29]: c = re.sub('-', '/', d)
In [30]: s_c = pd.Series([c]*10000)
In [31]: %timeit pd.to_datetime(s_c)
1 loops, best of 3: 1.14 s per loop
In [32]: %timeit pd.to_datetime(s_c, infer_datetime_format=True)
10 loops, best of 3: 105 ms per loop
In [33]: %timeit pd.to_datetime(s_c, format="%Y/%m/%d %H:%M:%S")
10 loops, best of 3: 99.5 ms per loop
【讨论】:
infer_datetime_format=True
比较慢,结果是1000 loops, best of 3: 908 µs per loop
。有没有更快的方法将非标准时间字符串转换为日期时间?
那是因为你在单个字符串上计时。如果你为更大的系列做这件事,你会看到加速。
正如我所说,有一种更快的方法,您自己在问题中使用的方法:提供format
字符串。
谢谢!还有一个问题,我会手动e = re.sub('/', '-', c)
并应用to_datetime(e)
可以提高性能吗?
我不这么认为。如果你能做到这一点,这意味着你肯定知道格式是什么,那么最好提供一个format
字符串。提供 ISO 格式的日期字符串的解析速度会更快一些,但这不会超过将 /
替换为 -
的时间【参考方案2】:
通常我无法提前指定标准日期格式,因为我根本不知道每个客户会选择如何提交它。日期的格式无法预测,并且经常丢失。
在这些情况下,我发现将自己的包装器编写到dateutil.parser.parse
而不是使用pd.to_datetime
更有效:
import pandas as pd
from dateutil.parser import parse
import numpy as np
def parseDateStr(s):
if s != '':
try:
return np.datetime64(parse(s))
except ValueError:
return np.datetime64('NaT')
else: return np.datetime64('NaT')
# Example data:
someSeries=pd.Series( ['NotADate','','1-APR-16']*10000 )
# Compare times:
%timeit pd.to_datetime(someSeries, errors='coerce') #1 loop, best of 3: 1.78 s per loop
%timeit someSeries.apply(parseDateStr) #1 loop, best of 3: 904 ms per loop
# The approaches return identical results:
someSeries.apply(parseDateStr).equals(pd.to_datetime(someSeries, errors='coerce')) # True
在这种情况下,运行时间减半,但 YMMV。
【讨论】:
【参考方案3】:这个问题已经得到了充分的回答,但我想添加一些我正在运行的测试的结果来优化我自己的代码。
我从 API 获得了这种格式:“Wed Feb 08 17:58:56 +0000 2017”。
使用带有隐式转换的默认 pd.to_datetime(SERIES)
,处理大约 2000 万行需要一个多小时(取决于我有多少可用内存)。
也就是说,我测试了三种不同的转换:
# explicit conversion of essential information only -- parse dt str: concat
def format_datetime_1(dt_series):
def get_split_date(strdt):
split_date = strdt.split()
str_date = split_date[1] + ' ' + split_date[2] + ' ' + split_date[5] + ' ' + split_date[3]
return str_date
dt_series = pd.to_datetime(dt_series.apply(lambda x: get_split_date(x)), format = '%b %d %Y %H:%M:%S')
return dt_series
# explicit conversion of what datetime considers "essential date representation" -- parse dt str: del then join
def format_datetime_2(dt_series):
def get_split_date(strdt):
split_date = strdt.split()
del split_date[4]
str_date = ' '.join(str(s) for s in split_date)
return str_date
dt_series = pd.to_datetime(dt_series.apply(lambda x: get_split_date(x)), format = '%c')
return dt_series
# explicit conversion of what datetime considers "essential date representation" -- parse dt str: concat
def format_datetime_3(dt_series):
def get_split_date(strdt):
split_date = strdt.split()
str_date = split_date[0] + ' ' + split_date[1] + ' ' + split_date[2] + ' ' + split_date[3] + ' ' + split_date[5]
return str_date
dt_series = pd.to_datetime(dt_series.apply(lambda x: get_split_date(x)), format = '%c')
return dt_series
# implicit conversion
def format_datetime_baseline(dt_series):
return pd.to_datetime(dt_series)
这是结果:
# sample of 250k rows
dt_series_sample = df['created_at'][:250000]
%timeit format_datetime_1(dt_series_sample) # best of 3: 1.56 s per loop
%timeit format_datetime_2(dt_series_sample) # best of 3: 2.09 s per loop
%timeit format_datetime_3(dt_series_sample) # best of 3: 1.72 s per loop
%timeit format_datetime_baseline(dt_series_sample) # best of 3: 1min 9s per loop
第一次测试的结果是令人印象深刻的运行时间减少了 97.7%!
有点令人惊讶的是,看起来即使是“适当的表示”也需要更长的时间,可能是因为它是半隐式的。
结论:越明确,运行速度越快。
【讨论】:
如果不考虑pd.to_datetime(dt_series, infer_datetime_format=True)
,这个基准是非常没用的
好吧,我不会说它没用,但你说得对,那将是一个很好的补充(我实际上认为这是默认设置,基线函数中已经涵盖了它) )。也许您可以将其添加为答案?以上是关于为啥 pandas.to_datetime 对于非标准时间格式(例如“2014/12/31”)很慢的主要内容,如果未能解决你的问题,请参考以下文章
Python Pandas:当日期小于 13 时,pandas.to_datetime() 正在切换日期和月份
使用 pandas.to_datetime 转换时指定日期格式