熊猫内部的问题:我很惊讶地发现有几次,在date_parser
内显式地传递一个call topandas.read_csv
会导致比简单地使用infer_datetime_format=True
慢得多的读取时间。
这是为什么?这两个选项之间的时间差异是特定于日期格式,还是其他什么因素会影响它们的相对时间?
在下面的情况下,infer_datetime_format=True
需要十分之一的时间来传递具有指定格式的日期分析器。我会天真地认为后者会更快,因为它是明确的。
医生会做记录,
[如果为true,]pandas将尝试推断列中日期时间字符串的格式,如果可以推断,则切换到更快的分析方法。在某些情况下,这可以将解析速度提高5-10倍。
但没有太多的细节,我无法完全通过来源工作。
设置:
from io import StringIO
import numpy as np
import pandas as pd
np.random.seed(444)
dates = pd.date_range('1980', '2018')
df = pd.DataFrame(np.random.randint(0, 100, (len(dates), 2)),
index=dates).add_prefix('col').reset_index()
# Something reproducible to be read back in
buf = StringIO()
df.to_string(buf=buf, index=False)
def read_test(**kwargs):
# Not ideal for .seek() to eat up runtime, but alleviate
# this with more loops than needed in timing below
buf.seek(0)
return pd.read_csv(buf, sep='\s+', parse_dates=['index'], **kwargs)
# dateutil.parser.parser called in this case, according to docs
%timeit -r 7 -n 100 read_test()
18.1 ms ± 217 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit -r 7 -n 100 read_test(infer_datetime_format=True)
19.8 ms ± 516 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
# Doesn't change with native Python datetime.strptime either
%timeit -r 7 -n 100 read_test(date_parser=lambda dt: pd.datetime.strptime(dt, '%Y-%m-%d'))
187 ms ± 4.05 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)
我有兴趣了解一下
infer
内部的情况,以获得这一优势。我以前的理解是,一开始就已经有了某种类型的推理,因为如果两者都不通过,就使用了dateutil.parser.parser
。更新:对此进行了一些挖掘,但还没有回答问题。
read_csv()
调用一个helper function,然后调用一个pd.core.tools.datetimes.to_datetime()
。该函数(可访问为justpd.to_datetime()
)有一个infer_datetime_format
和一个format
参数。但是,在这种情况下,相对计时是非常不同的,并不反映上述情况:
s = pd.Series(['3/11/2000', '3/12/2000', '3/13/2000']*1000)
%timeit pd.to_datetime(s,infer_datetime_format=True)
19.8 ms ± 1.54 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit pd.to_datetime(s,infer_datetime_format=False)
1.01 s ± 65.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
# This was taking the longest with i/o functions,
# now it's behaving "as expected"
%timeit pd.to_datetime(s,format='%m/%d/%Y')
19 ms ± 373 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
最佳答案
您已经确定了两个重要的函数:read_csv
准备一个函数来使用_make_date_converter
分析日期列,这总是要调用to_datetime
(pandas的主字符串到日期转换工具)。
@willayd和@bmbigbang的答案对我来说似乎都是正确的,因为它们将缓慢的原因识别为lambda函数的重复调用。
既然您要求更多关于熊猫源代码的详细信息,我将尝试在下面详细检查每个read_test
调用,以找出我们在to_datetime
中的结束位置,以及最终为什么时间安排与您观察到的数据相同。read_test()
这是非常快的,因为在没有任何关于可能日期格式的提示的情况下,pandas将尝试分析字符串的类似列表的列,就好像它们大约在ISO8601 format中一样(这是非常常见的情况)。
投入到“CC”,我们很快就达到了“AA”:
if result is None and (format is None or infer_datetime_format):
result = tslib.array_to_datetime(...)
从这里开始,一切都是this code branch的。
compiled Cython code遍历字符串列,将每个字符串转换为datetime格式。对于每一行,我们点击
to_datetime
,然后转到另一小段内联代码(array_to_datetime
),这意味着调用this line来实际分析字符串到日期时间对象。此函数不仅能够以YYYY-MM-DD格式分析日期,因此只需要一些内务处理来完成作业(由
_string_to_dts
填充的C结构将成为适当的日期时间对象,并检查一些边界)。如您所见,
_cstring_to_dts
根本不被调用。parse_iso_8601_datetime
让我们看看为什么这几乎和
parse_iso_8601_datetime
一样快。让熊猫推断日期时间格式(并且传递no
dateutil.parser.parser
参数)意味着我们在read_test(infer_datetime_format=True)
中着陆。if infer_datetime_format and format is None:
format = _guess_datetime_format_for_array(arg, dayfirst=dayfirst)
这将调用
read_test()
,它获取列中的第一个非空值并将其赋给format
。这将尝试构建一个日期时间格式字符串,以用于将来的分析。(here在其能够识别的格式之上有更多细节。)幸运的是,该函数可以识别YYYY-MM-DD格式。更幸运的是,这种特殊的格式有一个快速通过熊猫代码的路径!
您可以看到熊猫组返回到:
if format is not None:
# There is a special fast-path for iso8601 formatted
# datetime strings, so in those cases don't use the inferred
# format because this path makes process slower in this
# special case
format_is_iso8601 = _format_is_iso(format)
if format_is_iso8601:
require_iso8601 = not infer_datetime_format
format = None
这允许代码将
to_datetime
带到_guess_datetime_format_for_array
函数。_guess_datetime_format
我们提供了一个函数来解析日期,所以pandas会执行
infer_datetime_format
。但是,这在内部引发异常:
strptime() argument 1 must be str, not numpy.ndarray
这个异常会立即被捕获,熊猫会在调用
False
之前返回到使用My answer here的状态。parse_iso_8601_datetime
表示不是在数组上调用lambda函数,而是在here中为数组的每个值重复调用lambda函数:for i from 0 <= i < n:
if values[i] == '':
result[i] = np.nan
else:
result[i] = parse_date(values[i]) # parse_date is the lambda function
尽管我们被编译了代码,但是我们要承担对python代码进行函数调用的惩罚。这使得它与上面的其他方法相比非常缓慢。
回到
read_test(date_parser=lambda dt: strptime(dt, '%Y-%m-%d'))
中,我们现在有了一个对象数组,其中填充了try_parse_dates
对象。我们再次点击same path as above,但这次点击this code block并使用另一个函数(to_datetime
)使其成为datetime64对象。减速的原因实际上是由于对lambda函数的重复调用。
关于您的更新和mm/dd/yyyy格式
序列
try_parse_dates
具有mm/dd/yyyy格式的日期字符串。这不是ISO8601格式。
to_datetime
尝试使用this loop分析字符串,但使用datetime
失败。处理错误:熊猫将改为使用。这意味着要将字符串转换为日期时间。这就是为什么在这种情况下速度较慢:在循环中重复使用python函数。在速度方面,
array_to_datetime
和pydate_to_dt64
没有太大的差异。后者再次使用s
来推断mm/dd/yyyy格式。然后同时点击pd.to_datetime(s, infer_datetime_format=False)
pandas sees a date object:if format is not None:
...
if result is None:
try:
result = array_strptime(arg, format, exact=exact, errors=errors)
parse_iso_8601_datetime
是一个快速的cython函数,用于将字符串数组解析为给定特定格式的datetime结构。