gpt4 book ai didi

python - 推断日期格式与传递解析器

转载 作者:太空狗 更新时间:2023-10-29 17:50:06 28 4
gpt4 key购买 nike

Pandas 内部问题:我惊讶地发现有几次明确地将可调用对象传递给 date_parserpandas.read_csv导致读取时间比简单地使用 infer_datetime_format=True 慢得多.

这是为什么?这两个选项之间的时间差异是否特定于日期格式,或者还有哪些其他因素会影响它们的相对时间?

在以下情况下,infer_datetime_format=True传递具有指定格式的日期解析器所需的时间是其十分之一。我会天真地认为后者会更快,因为它是明确的。

文档确实指出,

[if True,] pandas will attempt to infer the format of the datetime strings in the columns, and if it can be inferred, switch to a faster method of parsing them. In some cases this can increase the parsing speed by 5-10x.



但是没有给出太多细节,我无法完全了解源代码。

设置:
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() .该函数(仅可作为 pd.to_datetime() 访问)同时具有 infer_datetime_format和一个 format论据。

但是,在这种情况下,相对时间非常不同,并不能反射(reflect)上述情况:
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 函数。

由于您询问有关 Pandas 源代码的更多详细信息,我将尝试检查每个 read_test在下面拨打更多详细信息以了解我们在 to_datetime 中的最终位置以及最终为什么时间与您观察到的数据一样。
read_test()
这非常快,因为在没有关于可能的日期格式的任何提示的情况下,pandas 将尝试解析类似列表的字符串列,就好像它们大约在 ISO8601 format 中一样。 (这是一个非常常见的情况)。

一头扎进to_datetime ,我们迅速到达this code branch :

if result is None and (format is None or infer_datetime_format):
result = tslib.array_to_datetime(...)

从这里开始,它是 compiled Cython code一路。

array_to_datetime 遍历字符串列以将每个字符串转换为日期时间格式。对于每一行,我们点击 _string_to_dts this line ;然后我们转到另一个简短的内联代码片段( _cstring_to_dts ),这意味着 parse_iso_8601_datetime 被调用以将字符串实际解析为日期时间对象。

此函数不仅能够解析 YYYY-MM-DD 格式的日期,因此只需进行一些内务处理即可完成工作(由 parse_iso_8601_datetime 填充的 C 结构成为正确的日期时间对象,检查了一些边界)。

如您所见, dateutil.parser.parser根本没有被调用。
read_test(infer_datetime_format=True)
让我们看看为什么这几乎和 read_test() 一样快.

要求 Pandas 推断日期时间格式(并且不传递 format 参数)意味着我们登陆 hereto_datetime :
if infer_datetime_format and format is None:
format = _guess_datetime_format_for_array(arg, dayfirst=dayfirst)

这叫 _guess_datetime_format_for_array ,它采用列中的第一个非空值并将其提供给 _guess_datetime_format .这会尝试构建一个日期时间格式字符串以用于将来的解析。 ( My answer here 在它能够识别的格式之上有更多细节。)

幸运的是,YYYY-MM-DD 格式是这个函数可以识别的格式。更幸运的是,这种特殊格式有一个通过 Pandas 代码的快速路径!

你可以看到 Pandas 集 infer_datetime_format返回 False here :
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

这允许代码采用 same path as aboveparse_iso_8601_datetime功能。
read_test(date_parser=lambda dt: strptime(dt, '%Y-%m-%d'))
我们提供了一个用于解析日期的函数,因此 pandas 执行 this code block .

但是,这在内部引发异常:
strptime() argument 1 must be str, not numpy.ndarray

这个异常立即被捕获,pandas 回退到使用 try_parse_dates 调用前 to_datetime .
try_parse_dates意味着不是在数组上调用,而是为 this loop 中数组的每个值重复调用 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 代码进行函数调用而付出代价。与上述其他方法相比,这使得它非常慢。

返回 to_datetime ,我们现在有一个填充了 datetime 的对象数组对象。我们再次点击 array_to_datetime ,但这一次 pandas sees a date object并使用另一个函数( pydate_to_dt64 )使其成为 datetime64 对象。

减速的原因实际上是由于对 lambda 函数的重复调用。

关于您的更新和 MM/DD/YYYY 格式

系列 s具有 MM/DD/YYYY 格式的日期字符串。

这不是 ISO8601 格式。 pd.to_datetime(s, infer_datetime_format=False)尝试使用 parse_iso_8601_datetime 解析字符串但这失败了 ValueError .错误处理 here :pandas 将使用 parse_datetime_string 反而。这意味着 dateutil.parser.parse is used将字符串转换为日期时间。这就是为什么在这种情况下它很慢:在循环中重复使用 Python 函数。
pd.to_datetime(s, format='%m/%d/%Y')之间没有太大区别和 pd.to_datetime(s, infer_datetime_format=True)在速度方面。后者使用 _guess_datetime_format_for_array 再次推断 MM/DD/YYYY 格式。然后两者都点击 array_strptime here :
if format is not None:
...
if result is None:
try:
result = array_strptime(arg, format, exact=exact, errors=errors)

array_strptime 是一个快速的 Cython 函数,用于将字符串数组解析为给定特定格式的日期时间结构。

关于python - 推断日期格式与传递解析器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48728307/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com