Pandas / Python - 使用 stack() groupby() 和 apply() 的性能非常慢

Mar*_*end 6 python performance cython pandas numba

我正在尝试根据信息对及其以前的值在数据框中创建一个新列。虽然我运行的代码是正确的,并给出了我需要的结果,但当我在大型数据帧上运行它时速度非常慢。所以我怀疑我没有使用所有的 Python 功能来完成这项任务。在 Python 中是否有更有效、更快的方法来做到这一点?

为了让您了解上下文,让我向您解释一下我在寻找什么:

我有一个数据框,它描述了比赛结果,对于每个“日期”,您可以看到参加比赛的“类型”及其名为'xx' 的分数。

我的代码所做的是获取每个“日期”的“类型”之间的分数“xx”的差异,然后获取过去所有类型相互竞争的先前比赛结果的差异总和( 'win_comp_past_difs')。

您可以在下面看到数据和模型及其输出。

## I. DATA AND MODEL ##
Run Code Online (Sandbox Code Playgroud)

I.1. 数据

import pandas as pd
import numpy as np

idx = [np.array(['Jan-18', 'Jan-18', 'Feb-18', 'Mar-18', 'Mar-18', 'Mar-18','Mar-18', 'Mar-18', 'May-18', 'Jun-18', 'Jun-18', 'Jun-18','Jul-18', 'Aug-18', 'Aug-18', 'Sep-18', 'Sep-18', 'Oct-18','Oct-18', 'Oct-18', 'Nov-18', 'Dec-18', 'Dec-18',]),np.array(['A', 'B', 'B', 'A', 'B', 'C', 'D', 'E', 'B', 'A', 'B', 'C','A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'A', 'B', 'C'])]
data = [{'xx': 1}, {'xx': 5}, {'xx': 3}, {'xx': 2}, {'xx': 7}, {'xx': 3},{'xx': 1}, {'xx': 6}, {'xx': 3}, {'xx': 5}, {'xx': 2}, {'xx': 3},{'xx': 1}, {'xx': 9}, {'xx': 3}, {'xx': 2}, {'xx': 7}, {'xx': 3}, {'xx': 6}, {'xx': 8}, {'xx': 2}, {'xx': 7}, {'xx': 9}]
df = pd.DataFrame(data, index=idx, columns=['xx'])
df.index.names=['date','type']
df=df.reset_index()
df['date'] = pd.to_datetime(df['date'],format = '%b-%y') 
df=df.set_index(['date','type'])
df['xx'] = df.xx.astype('float')
Run Code Online (Sandbox Code Playgroud)

看起来像这样:

                  xx
date       type
2018-01-01 A     1.0
           B     5.0
2018-02-01 B     3.0
2018-03-01 A     2.0
           B     7.0
           C     3.0
           D     1.0
           E     6.0
2018-05-01 B     3.0
2018-06-01 A     5.0
           B     2.0
           C     3.0
2018-07-01 A     1.0
2018-08-01 B     9.0
           C     3.0
2018-09-01 A     2.0
           B     7.0
2018-10-01 C     3.0
           A     6.0
           B     8.0
2018-11-01 A     2.0
2018-12-01 B     7.0
           C     9.0
Run Code Online (Sandbox Code Playgroud)

I.2. 模型在大型数据帧中非常慢

                  xx
date       type
2018-01-01 A     1.0
           B     5.0
2018-02-01 B     3.0
2018-03-01 A     2.0
           B     7.0
           C     3.0
           D     1.0
           E     6.0
2018-05-01 B     3.0
2018-06-01 A     5.0
           B     2.0
           C     3.0
2018-07-01 A     1.0
2018-08-01 B     9.0
           C     3.0
2018-09-01 A     2.0
           B     7.0
2018-10-01 C     3.0
           A     6.0
           B     8.0
2018-11-01 A     2.0
2018-12-01 B     7.0
           C     9.0
Run Code Online (Sandbox Code Playgroud)

您可以在下面看到模型的输出如何:

# get differences of pairs, useful for win counts and win_difs
def get_diff(x):
    teams = x.index.get_level_values(1)
    tmp = pd.DataFrame(x[:,None]-x[None,:],columns = teams.values,index=teams.values).stack()
    return tmp[tmp.index.get_level_values(0)!=tmp.index.get_level_values(1)]
new_df = df.groupby('date').xx.apply(get_diff).to_frame()

# group by players
groups = new_df.groupby(level=[1,2])

# sum function
def cumsum_shift(x):
    return x.cumsum().shift()

# assign new values
df['win_comp_past_difs'] = groups.xx.apply(cumsum_shift).sum(level=[0,1])
Run Code Online (Sandbox Code Playgroud)

以防万一您难以理解用户定义函数 (def) 的作用,让我在下面向您解释

对于这个海豚,我将使用数据帧的一组 groupby。

下面您将看到用户定义功能如何工作的说明。

                  xx  win_comp_past_difs
date       type
2018-01-01 A     1.0                 0.0
           B     5.0                 0.0
2018-02-01 B     3.0                 NaN
2018-03-01 A     2.0                -4.0
           B     7.0                 4.0
           C     3.0                 0.0
           D     1.0                 0.0
           E     6.0                 0.0
2018-05-01 B     3.0                 NaN
2018-06-01 A     5.0               -10.0
           B     2.0                13.0
           C     3.0                -3.0
2018-07-01 A     1.0                 NaN
2018-08-01 B     9.0                 3.0
           C     3.0                -3.0
2018-09-01 A     2.0                -6.0
           B     7.0                 6.0
2018-10-01 C     3.0               -10.0
           A     6.0               -10.0
           B     8.0                20.0
2018-11-01 A     2.0                 NaN
2018-12-01 B     7.0                14.0
           C     9.0               -14.0

Run Code Online (Sandbox Code Playgroud)

因此,为了让您了解用户定义函数的工作原理,让我选择 groupby 的特定组。

II.1 选择特定组

## II. EXPLANATION OF THE USER-DEFINED FUNCTION ##
Run Code Online (Sandbox Code Playgroud)

看起来像这样:

                    xx
  date       type
  2018-03-01 A     2.0
             B     7.0
             C     3.0
             D     1.0
             E     6.0
Run Code Online (Sandbox Code Playgroud)

II.2 创建参赛者(团队)名单'

gb = df.groupby('date')
gb2 = gb.get_group((list(gb.groups)[2]))
Run Code Online (Sandbox Code Playgroud)

II.3 创建'type'之间'xx'差异的数据帧

                    xx
  date       type
  2018-03-01 A     2.0
             B     7.0
             C     3.0
             D     1.0
             E     6.0
Run Code Online (Sandbox Code Playgroud)

看起来像这样:

    A    B    C    D    E
  A  0.0 -5.0 -1.0  1.0 -4.0
  B  5.0  0.0  4.0  6.0  1.0
  C  1.0 -4.0  0.0  2.0 -3.0
  D -1.0 -6.0 -2.0  0.0 -5.0
  E  4.0 -1.0  3.0  5.0  0.0
Run Code Online (Sandbox Code Playgroud)

从这一点开始,我使用 stack() 函数作为返回原始数据帧的中间步骤。其余的您可以在 I. DATA AND MODEL 中遵循它。

如果您能详细说明代码以使其更高效且执行速度更快,我将不胜感激。

And*_* L. 4

我只修改了get_diff. 要点是移到stack外部get_diff,利用stack其下降的特点NaN,避免内部过滤get_diff

\n\n

get_diff_s功能np.fill用于填充所有对角线值NaN并返回数据帧,而不是过滤后的系列。

\n\n
def get_diff_s(x):\n    teams = x.index.get_level_values(1)\n    arr = x[:,None]-x[None,:]\n    np.fill_diagonal(arr, np.nan)    \n    return pd.DataFrame(arr,columns = teams.values,index=teams.values)\n\ndf['win_comp_past_difs'] = (df.groupby('date').xx.apply(get_diff_s)\n                              .groupby(level=1).cumsum().stack()\n                              .groupby(level=[1,2]).shift().sum(level=[0, 1]))\n\nOut[1348]:\n                  xx  win_comp_past_difs\ndate       type\n2018-01-01 A     1.0                 0.0\n           B     5.0                 0.0\n2018-02-01 B     3.0                 NaN\n2018-03-01 A     2.0                -4.0\n           B     7.0                 4.0\n           C     3.0                 0.0\n           D     1.0                 0.0\n           E     6.0                 0.0\n2018-05-01 B     3.0                 NaN\n2018-06-01 A     5.0               -10.0\n           B     2.0                13.0\n           C     3.0                -3.0\n2018-07-01 A     1.0                 NaN\n2018-08-01 B     9.0                 3.0\n           C     3.0                -3.0\n2018-09-01 A     2.0                -6.0\n           B     7.0                 6.0\n2018-10-01 C     3.0               -10.0\n           A     6.0               -10.0\n           B     8.0                20.0\n2018-11-01 A     2.0                 NaN\n2018-12-01 B     7.0                14.0\n           C     9.0               -14.0\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

时间安排

\n\n

原始解决方案:(我将所有命令链接到一行中)

\n\n
In [1352]: %timeit df.groupby('date').xx.apply(get_diff).groupby(level=[1,2]).a\n      ...: pply(lambda x: x.cumsum().shift()).sum(level=[0,1])\n82.9 ms \xc2\xb1 2.12 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n\n

修改后的解决方案:

\n\n
In [1353]: %timeit df.groupby('date').xx.apply(get_diff_s).groupby(level=1).cum\n      ...: sum().stack().groupby(level=[1,2]).shift().sum(level=[0,1])\n47.1 ms \xc2\xb1 1.51 ms per loop (mean \xc2\xb1 std. dev. of 7 runs, 10 loops each)\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,在样本数据上,速度大约快了 40%。但是,我不知道它在您的真实数据集上的表现如何

\n