时间:2017-11-20 13:05:26

标签: python pandas

我有一个用Python编写的机器学习应用程序,它包括一个数据处理步骤。当我编写它时,我最初在Pandas DataFrames上进行数据处理,但是当这导致糟糕的性能时,我最终使用vanilla Python重写了它,使用for循环而不是矢量化操作以及列表和dicts而不是DataFrames和Series。令我惊讶的是,用vanilla 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]
(...)

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

我做的非常非常错吗?

1 个答案:

答案 0 :(得分:11)

不,我不认为你应该放弃大熊猫。做你正在尝试的事情肯定是更好的方法。诀窍是尽可能避免任何形式的apply / transform。像瘟疫一样避免它们。它们基本上都是针对循环实现的,所以你也可以直接使用以C速度运行的python for循环,并为你提供更好的性能。

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

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

  

设置

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的操作往往比pandas等效快(例如,np.sumpd.DataFrame.sum快,np.wherepd.DataFrame.where快,因此上)。

有时,循环无法避免。在这种情况下,您可以创建一个基本的循环函数,然后您可以使用numba或cython进行矢量化。这方面的例子是Enhancing Performance,直接来自马口。

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