提高熊猫groupby的性能

haroba:

我有一个用Python编写的机器学习应用程序,其中包括一个数据处理步骤。当我写它的时候,我最初是在Pandas DataFrames上进行数据处理的,但是当这导致糟糕的性能时,我最终使用vanilla Python重写了它,使用了for循环,而不是矢量化操作,使用列表和字典,而不是DataFrames和Series。令我惊讶的是,使用香草Python编写的代码的性能最终高于使用Pandas编写的代码。

由于我的手动编码数据处理代码比原始的Pandas代码更大且更混乱,因此我还没有完全放弃使用Pandas的工作,而我目前正在尝试优化Pandas代码,但没有取得太大的成功。

数据处理步骤的核心包括以下内容:由于数据包含数千个时间序列(每个“个体”一个),因此我首先将行分为几组,然后对每个组进行相同的数据处理。 :大量摘要,将不同的列合并为新列,等等。

我使用Jupyter Notebook的代码分析了我的代码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]
(...)

矢量化和非矢量化处理的混合 我知道,非矢量化操作不会比我的手写for循环快,因为这基本上是它们的内幕,但是它们怎么会这么慢呢?我们正在谈论的是我的手写代码和Pandas代码之间的性能下降10-20倍。

我是在做非常非常错误的事情吗?

cs95:

不,我不认为你应该放弃熊猫。绝对有更好的方法来做您要尝试的事情。诀窍是尽可能避免apply/ transform以任何形式出现。避免他们像瘟疫一样。它们基本上是针对for循环实现的,因此您不妨直接使用for以C速度运行并提供更好性能的python 循环。

真正的速度增益是摆脱循环并使用熊猫函数隐式向量化其操作的地方。例如,正如我很快向您展示的那样,您的第一行代码可以大大简化。

在这篇文章中,我概述了设置过程,然后针对问题中的每一行进行了改进,并对时间和正确性进行了并排比较。

建立

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)
g = df.groupby('pk')
c = ['Val{}'.format(i) for i in range(100)]

transform+ sub+ shiftdiff

您的第一行代码可以用一个简单的diff语句代替

v1 = df.groupby('pk')[c].diff().fillna(0)

完整性检查

v2 = df.groupby('pk')[c].transform(lambda x: x - x.shift(1)).fillna(0)

np.allclose(v1, v2)
True

性能

%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

删除多余的索引操作

就您的第二行代码而言,我认为没有太多改进的空间,尽管如果您的groupby语句不考虑作为索引,则可以摆脱reset_index()+ [val_cols]调用pk

g = df.groupby('pk', as_index=False)

然后,第二行代码减少为:

v3 = g[c].rolling(4).mean().shift(1)

完整性检查

g2 = df.groupby('pk')
v4 = g2[c].rolling(4).mean().shift(1).reset_index()[c]

np.allclose(v3.fillna(0), v4.fillna(0))
True

性能

%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

请注意,时间在不同的机器上会有所不同,因此请确保您对代码进行彻底的测试,以确保数据确实有所改善。

尽管这次的差异不大,但是您可以体会到可以进行改进的事实!这可能会对更大的数据产生更大的影响。


后记

总之,大多数操作速度很慢,因为它们可以加快速度。关键是摆脱任何不使用向量化的方法。

为此,走出熊猫空间并进入numpy空间有时是有益的。上numpy的阵列或使用numpy的趋向操作要远远快于大熊猫当量(例如,np.sum快于pd.DataFrame.sum,且np.where快于pd.DataFrame.where,等等)。

有时,循环是无法避免的。在这种情况下,您可以创建一个基本的循环功能,然后可以使用numba或cython将其向量化。直接从马口开始的“ 提高演奏”便是其中的例子

在其他情况下,您的数据太大,无法合理地放入numpy数组中。在这种情况下,是时候放弃并切换到daskor了spark,两者都提供了用于处理大数据的高性能分布式计算框架。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章