我有一个用Python编写的机器学习应用程序,它包括一个数据处理步骤.当我编写它时,我最初在Pandas DataFrames上进行数据处理,但是当这导致糟糕的性能时,我最终使用vanilla Python重写了它,使用for循环而不是矢量化操作以及列表和dicts而不是DataFrames和Series.令我惊讶的是,用vanilla Python编写的代码的性能最终远远高于使用Pandas编写的代码.
正如我的手工编写数据处理代码基本上是大和混乱比原来的熊猫代码,我还没有完全放弃使用熊猫,目前我想没有多少成功优化大熊猫代码.
数据处理步骤的核心包括以下内容:我首先将行分成几组,因为数据由几千个时间序列组成(每个"个体"一个),然后我对每个组进行相同的数据处理:很多摘要,将不同的列组合成新的列等.
我使用Jupyter Notebook's我的代码lprun,大部分时间花在以下和其他类似的行上:
grouped_data = data.groupby('pk')
data[[v + 'Diff' for v in val_cols]] = grouped_data[val_cols].transform(lambda x: x - x.shift(1)).fillna(0)
data[[v + 'Mean' for v in val_cols]] = grouped_data[val_cols].rolling(4).mean().shift(1).reset_index()[val_cols]
(...)
Run Code Online (Sandbox Code Playgroud)
...矢量化和非矢量化处理的混合.我知道非矢量化操作不会比我的手写循环更快,因为这基本上是他们在幕后的东西,但他们怎么会这么慢?我们谈论的是我的手写代码和Pandas代码之间的性能下降了10-20倍.
我做的非常非常错吗?
cs9*_*s95 14
不,我认为你不应该放弃大熊猫.肯定有更好的方法来做你想做的事情.诀窍是尽可能避免apply/ transform以任何形式.像瘟疫一样避免它们.它们基本上都是针对循环实现的,所以你也可以直接使用for以C速度运行的python 循环,并为你提供更好的性能.
真正的速度增益是你摆脱循环并使用pandas的函数隐藏矢量化操作的地方.例如,正如我很快向您展示的那样,您的第一行代码可以大大简化.
在这篇文章中,我概述了设置过程,然后,对于问题中的每一行,提供一个改进,以及时间和正确性的并排比较.
建立
data = {'pk' : np.random.choice(10, 1000)}
data.update({'Val{}'.format(i) : np.random.randn(1000) for i in range(100)})
df = pd.DataFrame(data)
Run Code Online (Sandbox Code Playgroud)
g = df.groupby('pk')
c = ['Val{}'.format(i) for i in range(100)]
Run Code Online (Sandbox Code Playgroud)
transform+sub+shift→diff
您的第一行代码可以用简单的diff语句替换:
v1 = df.groupby('pk')[c].diff().fillna(0)
Run Code Online (Sandbox Code Playgroud)
完整性检查
v2 = df.groupby('pk')[c].transform(lambda x: x - x.shift(1)).fillna(0)
np.allclose(v1, v2)
True
Run Code Online (Sandbox Code Playgroud)
性能
%timeit df.groupby('pk')[c].transform(lambda x: x - x.shift(1)).fillna(0)
10 loops, best of 3: 44.3 ms per loop
%timeit df.groupby('pk')[c].diff(-1).fillna(0)
100 loops, best of 3: 9.63 ms per loop
Run Code Online (Sandbox Code Playgroud)
删除冗余索引操作
就你的第二行代码而言,我没有看到太大的改进空间,尽管如果你的groupby语句没有考虑作为索引你可以摆脱reset_index()+ [val_cols]调用pk:
g = df.groupby('pk', as_index=False)
Run Code Online (Sandbox Code Playgroud)
然后你的第二行代码减少到:
v3 = g[c].rolling(4).mean().shift(1)
Run Code Online (Sandbox Code Playgroud)
完整性检查
g2 = df.groupby('pk')
v4 = g2[c].rolling(4).mean().shift(1).reset_index()[c]
np.allclose(v3.fillna(0), v4.fillna(0))
True
Run Code Online (Sandbox Code Playgroud)
性能
%timeit df.groupby('pk')[c].rolling(4).mean().shift(1).reset_index()[c]
10 loops, best of 3: 46.5 ms per loop
%timeit df.groupby('pk', as_index=False)[c].rolling(4).mean().shift(1)
10 loops, best of 3: 41.7 ms per loop
Run Code Online (Sandbox Code Playgroud)
请注意,不同计算机上的计时时间会有所不同,因此请确保彻底测试您的代码,以确保数据确实有所改进.
虽然这次的差异不是很大,但你可以欣赏这样一个事实:你可以做出改进!这可能会对更大的数据产生更大的影响.
后记
总之,大多数操作都很慢,因为它们可以加快速度.关键是要摆脱任何不使用矢量化的方法.
为此,走出大熊猫空间并步入笨拙的空间有时是有益的.numpy数组或使用numpy的操作往往比pandas等价物快得多(例如,np.sum比它快pd.DataFrame.sum,并且np.where快于pd.DataFrame.where等等).
有时,循环无法避免.在这种情况下,您可以创建一个基本的循环函数,然后您可以使用numba或cython进行矢量化.这方面的例子就是提高性能,直接来自马口.
在其他情况下,您的数据太大而无法合理地适应numpy数组.在这种情况下,是时候放弃并切换到,dask或者spark两者都提供高性能的分布式计算框架来处理大数据.