在回答问题Vectorize calculation of a Pandas Dataframe时,我注意到一个有关性能的有趣问题。
我的印象是df.min()
,df.mean()
,df.cumsum()
等功能都是矢量化的。但是,我发现df.cumsum()
和numpy
替代方案之间存在巨大差异。
鉴于pandas
在其基础架构中使用numpy
数组,我预计性能会更接近。我尝试调查df.cumsum()
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
,但发现它难以处理。有人能解释为什么这么慢吗?
从@HYRY的回答中可以看出,这个问题减少了为什么以下两个命令在时间上产生如此大的差异的问题:
json_column
(由其中一位评论员执行时间,因为我的numpy v1.11没有nancumsum。)
答案 0 :(得分:6)
这里似乎有几件事情一无所获。
首先,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 函数调用,。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')
答案 1 :(得分:1)
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)