【问题标题】:Pandas vectorised function cumsum versus numpyPandas 向量化函数 cumsum 与 numpy
【发布时间】:2018-08-16 06:38:11
【问题描述】:

在回答Vectorize calculation of a Pandas Dataframe的问题时,我注意到一个关于性能的有趣问题。

我的印象是,df.min()、df.mean()、df.cumsum() 等函数是矢量化的。但是,我发现df.cumsum() 和numpy 替代方案之间的性能存在巨大差异。

鉴于pandas 在其基础架构中使用numpy 数组,我预计性能会更接近。我尝试为df.cumsum() 调查source code,但发现它难以处理。有人可以解释为什么它这么慢吗?

从@HYRY 的回答中可以看出,问题归结为为什么以下两个命令会在时间上产生如此巨大的差异:

import pandas as pd, numpy as np
df_a = pd.DataFrame(np.arange(1,1000*1000+1).reshape(1000,1000))

%timeit pd.DataFrame(np.nancumsum(df_a.values))    #  4.18 ms
%timeit df_a.cumsum()                              # 15.7  ms

(由一位评论员执行的时间,因为我的 numpy v1.11 没有 nancumsum。)

【问题讨论】:

    标签: python arrays performance pandas numpy


    【解决方案1】:

    这里似乎有些东西一文不值。

    首先,df_a.cumsum() 默认为axis=0(Pandas 没有在一次调用中对整个 DataFrame 求和的概念),而 NumPy 调用默认为axis=None。因此,通过在一个操作上指定一个轴并有效地压平另一个操作,您就是在将苹果与橙子进行比较。

    也就是说,您可以比较三个调用:

    >>> np.cumsum(df_a, axis=0)
    >>> df_a.cumsum()
    >>> val.cumsum(axis=0)  # val = df_a.values
    

    其中,在最终调用中,val 是底层 NumPy 数组,我们不计算在运行时获取 .values 属性。

    所以,如果您在 IPython shell 中工作,请尝试使用 %prun 进行行分析:

    >>> %prun -q -T pdcumsum.txt df_a.cumsum()
    
    >>> val = df_a.values
    >>> %prun -q -T ndarraycumsum.txt val.cumsum(axis=0)
    
    >>> %prun -q -T df_npcumsum.txt np.cumsum(df_a, axis=0)
    

    -T 将输出保存为文本,以便您可以查看所有三个相互匹配的内容。这是你最终的结果:

    • df_a.cumsum():186 个函数调用,0.022 秒。其中 0.013 用于numpy.ndarray.cumsum()。 (我的猜测是,如果没有 NaN,则不需要 nancumsum(),但请不要引用我的话)。另一个块用于复制数组。
    • val.cumsum(axis=0):5 次函数调用,0.020 秒。不进行复制(尽管这不是就地操作)。
    • np.cumsum(df_a, axis=0):204 个函数调用,0.026 秒。可以这么说,将 Pandas 对象传递给顶级 NumPy 函数似乎最终会调用 Pandas 对象上的等效方法,这会经历一大堆开销,然后重新调用 NumPy 函数。

    现在,与%timeit 不同,您在这里只打了 1 个电话,就像在%time 中一样,所以我不会过分依赖与%prun 的相对时间差异;也许比较内部函数调用是有用的。但在这种情况下,当您为两者指定相同的轴时,时间差异实际上并没有那么大,即使 Pandas 的调用次数比 NumPy 的调用次数相形见绌。换句话说,在这种情况下,所有三个调用的时间都以np.ndarray.cumsum() 为主,辅助的 Pandas 调用不会占用太多时间。在其他情况下,辅助 Pandas 调用确实会消耗更多的运行时间,但这似乎不是其中之一。

    大图——正如 Wes McKinney 的 acknowledged,

    相当简单的操作,从索引到汇总统计,在到达最低层计算之前可能会经过多层脚手架。

    在灵活性和增加功能之间进行权衡,你可以争论。

    最后一个细节:在 NumPy 中,您可以通过调用实例方法 ndarray.cumsum() 而不是调用顶级函数 np.cumsum() 来 avoid a tiny bit of overhead,因为后者只是以 routing 结束于前者。但正如一位智者所说,过早的优化是万恶之源。


    供参考:

    >>> pd.__version__, np.__version__
    ('0.22.0', '1.14.0')
    

    【讨论】:

      【解决方案2】:

      Pandas 可以处理 NaN,您可以通过以下方式检查差异:

      a = np.random.randn(1000000)
      %timeit np.nancumsum(a)
      %timeit np.cumsum(a)
      

      输出:

      9.02 ms ± 189 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
      4.37 ms ± 18.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
      

      【讨论】:

      • 所以nan 覆盖率有约 2 倍的性能影响,而我看到约 3.2 倍 [忽略 numba 版本]。当然,这占了大部分差异。 pandas 真的使用np.nancumsum 吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-09-30
      • 2016-12-07
      • 2021-12-27
      • 2022-01-08
      • 2019-05-05
      • 1970-01-01
      相关资源
      最近更新 更多