我有一个用Python编写的机器学习应用程序,其中包括一个数据处理步骤。当我编写它时,我最初在Pandas DataFrames上进行了数据处理,但是当这导致了糟糕的性能时,我最终用普通的Python重写了它,使用的是for循环,而不是矢量化的操作,列表和切分,而不是DataFrames和Series。令我惊讶的是,用vanilla编写的代码的性能最终远远高于使用Pandas编写的代码。
由于我的手工编码的数据处理代码比原来的Pandas代码要大得多,而且更加混乱,所以我还没有完全放弃使用Pandas,而且我目前正在尝试优化Pandas代码,但没有成功。
数据处理步骤的核心包括以下内容:我首先将行划分为几个组,因为数据由数千个时间序列(每个“个体”一个)组成,然后对每个组进行相同的数据处理:大量汇总,将不同的列组合成新的列,等等。
我使用朱庇特笔记本的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]
(...)...a混合矢量化和非矢量化处理.我知道,非矢量化操作不会比我手写的循环更快,因为这基本上就是它们在引擎盖下的情况,但是它们怎么会慢得多呢?我们讨论的是我的手写代码和Pandas代码之间的性能下降了10-20倍。
我是不是做错了什么事?
发布于 2017-11-25 02:50:51
不,我认为你不应该放弃熊猫。肯定有更好的方法来做你想做的事。诀窍是尽可能避免任何形式的apply/transform。像躲避瘟疫一样避开它们。它们基本上是作为循环实现的,所以您最好直接使用python for循环,这些循环以C的速度工作,并给您更好的性能。
真正的速度增长是在哪里你摆脱了循环,并使用熊猫的函数,隐含地向量化他们的操作。例如,您的第一行代码可以大大简化,我很快就会向您展示。
在这篇文章中,我概述了设置过程,然后,针对您问题中的每一行,提供一个改进,以及时间和正确性的并行比较。
设置
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 + shift→diff
您的第一行代码可以替换为一个简单的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 +[val_cols]调用:
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的操作往往比熊猫等价物快得多(例如,np.sum比pd.DataFrame.sum快,np.where比pd.DataFrame.where快等等)。
有时,循环是无法避免的。在这种情况下,您可以创建一个基本的循环函数,然后可以使用numba或cython对其进行矢量化。这方面的例子在这里的提高绩效,直接从马的嘴。
在其他情况下,您的数据太大,无法合理地适合于numpy数组。在这种情况下,是时候放弃使用dask或spark了,它们都为处理大数据提供了高性能的分布式计算框架。
https://stackoverflow.com/questions/47392758
复制相似问题