快速熊猫groupby与cumprod计算

时间:2015-08-21 10:28:14

标签: pandas

此问题与Speedup of pandas groupby相关联。这是关于加快groubby cumproduct计算。 DataFrame是2D并且具有由3个整数组成的多索引。

数据框的HDF5文件可在此处找到:http://filebin.ca/2Csy0E2QuF2w/phi.h5

我正在执行的实际计算与此类似:

   >>> phi = pd.read_hdf('phi.h5', 'phi')
   >>> %timeit phi.groupby(level='atomic_number').cumprod()
   100 loops, best of 3: 5.45 ms per loop

可能的另一个加速是我使用相同的索引结构但使用不同的数字进行大约100次计算。我想知道它是否能以某种方式缓存索引。

任何帮助将不胜感激。

3 个答案:

答案 0 :(得分:1)

Numba似乎在这里工作得很好。事实上,这些结果看起来好得令人难以置信,下面的numba函数比原始方法快4,000倍,而比没有cumprod groupby快5倍。希望这些是正确的,如果有错误,请告诉我。

np.random.seed(1234)
df=pd.DataFrame({ 'x':np.repeat(range(200),4), 'y':np.random.randn(800) })
df = df.sort('x')
df['cp_groupby'] = df.groupby('x').cumprod()

from numba import jit

@jit
def group_cumprod(x,y):
    z = np.ones(len(x))
    for i in range(len(x)):
        if x[i] == x[i-1]:
            z[i] = y[i] * z[i-1]
        else:
            z[i] = y[i]
    return z

df['cp_numba'] = group_cumprod(df.x.values,df.y.values)

df['dif'] = df.cp_groupby - df.cp_numba

测试两种方式给出相同的答案:

all(df.cp_groupby==df.cp_numba)
Out[1447]: True

时序:

%timeit df.groupby('x').cumprod()
10 loops, best of 3: 102 ms per loop

%timeit df['y'].cumprod()
10000 loops, best of 3: 133 µs per loop

%timeit group_cumprod(df.x.values,df.y.values)
10000 loops, best of 3: 24.4 µs per loop

答案 1 :(得分:1)

纯粹的numpy解决方案,假设数据按索引排序,但没有处理NaN:

res = np.empty_like(phi.values)
l = 0
r = phi.index.levels[0]
for i in r:
    phi.values[l:l+i,:].cumprod(axis=0, out=res[l:l+i])
    l += i

来自问题的多指数数据快了大约40倍。 虽然问题在于这确实依赖于pandas如何将数据存储在其后端阵列中。所以当熊猫改变时它可能会停止工作。

>>> phi = pd.read_hdf('phi.h5', 'phi')
>>> %timeit phi.groupby(level='atomic_number').cumprod()
100 loops, best of 3: 4.33 ms per loop
>>> %timeit np_cumprod(phi)
10000 loops, best of 3: 111 µs per loop

答案 2 :(得分:0)

如果您想要一个快速但不太漂亮的解决方法,您可以执行以下操作。这是一些示例数据和您的默认方法。

df=pd.DataFrame({ 'x':np.repeat(range(200),4), 'y':np.random.randn(800) })
df = df.sort('x')
df['cp_group'] = df.groupby('x').cumprod()

这是解决方法。它看起来很长(确实如此),但每个步骤都很简单快捷。 (时间安排在最底层。)关键是在这种情况下完全避免使用groupby替换为shift等 - 但由于这一点,您还需要确保您的数据按groupby列排序。

df['cp_nogroup'] = df.y.cumprod()
df['last'] = np.where( df.x == df.x.shift(-1), 0, df.y.cumprod() )
df['last'] = np.where( df['last'] == 0., np.nan, df['last'] )
df['last'] = df['last'].shift().ffill().fillna(1)
df['cp_fast'] = df['cp_nogroup'] / df['last']
df['dif'] = df.cp_group - df.cp_fast

这就是它的样子。 ' cp_group'是您的默认设置,并且' cp_fast'是上面的解决方法。如果你看看' dif'你可以看到,其中有几个是非常少量的。这只是一个精确的问题而无需担心。

    x         y  cp_group  cp_nogroup      last   cp_fast           dif
0   0  1.364826  1.364826    1.364826  1.000000  1.364826  0.000000e+00
1   0  0.410126  0.559751    0.559751  1.000000  0.559751  0.000000e+00
2   0  0.894037  0.500438    0.500438  1.000000  0.500438  0.000000e+00
3   0  0.092296  0.046189    0.046189  1.000000  0.046189  0.000000e+00
4   1  1.262172  1.262172    0.058298  0.046189  1.262172  0.000000e+00
5   1  0.832328  1.050541    0.048523  0.046189  1.050541  2.220446e-16
6   1 -0.337245 -0.354289   -0.016364  0.046189 -0.354289 -5.551115e-17
7   1  0.758163 -0.268609   -0.012407  0.046189 -0.268609 -5.551115e-17
8   2 -1.025820 -1.025820    0.012727 -0.012407 -1.025820  0.000000e+00
9   2  1.175903 -1.206265    0.014966 -0.012407 -1.206265  0.000000e+00

<强>计时

默认方法:

In [86]: %timeit df.groupby('x').cumprod()
10 loops, best of 3: 100 ms per loop

标准cumprod但没有groupby。这应该是您可以达到的最大可能速度的良好近似值。

In [87]: %timeit df.cumprod()
1000 loops, best of 3: 536 µs per loop

以下是解决方法:

In [88]: %%timeit
...: df['cp_nogroup'] = df.y.cumprod()
...: df['last'] = np.where( df.x == df.x.shift(-1), 0, df.y.cumprod() )
...: df['last'] = np.where( df['last'] == 0., np.nan, df['last'] )
...: df['last'] = df['last'].shift().ffill().fillna(1)
...: df['cp_fast'] = df['cp_nogroup'] / df['last']
...: df['dif'] = df.cp_group - df.cp_fast

100 loops, best of 3: 2.3 ms per loop

因此,对于此示例数据帧,解决方法的速度提高了约40倍,但加速将取决于数据帧(特别是组数)。