熊猫内部的问题:我很惊讶地发现有几次,在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一样快。
让熊猫推断日期时间格式(并且传递nodateutil.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_datetimepydate_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结构。

08-20 03:38
查看更多