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

问题描述 投票:0回答:2

我正在尝试根据信息对及其先前的值在数据框中创建一个新列。尽管我运行的代码是正确的,并且可以提供所需的结果,但是在大型数据帧上运行它的速度非常慢。因此,我怀疑我没有使用所有Python功能来完成此任务。在Python中,有没有更有效,更快速的方法?]]

为了让您了解具体情况,让我向您解释一下我要寻找的内容:

我有一个描述比赛结果的数据框,对于每个'date'

,您可以看到参加比赛的'type',其得分称为'xx'

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

下面您将看到数据和模型及其输出。

## I. DATA AND MODEL ##

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')

看起来像这样:

                  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

I.2。型号

在大型数据框中非常慢]
# 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])

下面您将看到模型的输出是什么样子:

                  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

如果您难以理解用户定义的函数(def)的功能,请在下面让我向您解释

对于这个海豚,我将与数据框的groupby中的一组一起工作。

下面您将看到有关用户定义功能的工作方式的说明。

## II. EXPLANATION OF THE USER-DEFINED FUNCTION ##

所以,让您看一下用户定义函数的工作方式,让我选择groupby的特定组。

II.1选择特定的组

gb = df.groupby('date')
gb2 = gb.get_group((list(gb.groups)[2]))

看起来像这样:

                    xx
  date       type
  2018-03-01 A     2.0
             B     7.0
             C     3.0
             D     1.0
             E     6.0

II.2创建竞争对手(团队)列表]]

teams = gb2.index.get_level_values(1)

II.3创建“类型”之间的“ xx”差异的数据框。

df_comp= pd.DataFrame(gb2.xx[:,None]-gb2.xx[None,:],columns = teams.values,index=teams.values)

看起来像这样:

    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

至此,我使用stack()函数作为返回原始数据帧的中间步骤。其余的您可以在I.数据和模型中遵循它。

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

我正在尝试根据信息对及其先前的值在数据框中创建一个新列。尽管我运行的代码是正确的,并且可以提供所需的结果,但是当我...

我仅修改get_diff。要点是将stack移到get_diff的外面,并采用stack的优势,即降低NaN以避免在get_diff内部进行过滤。

新的get_diff_s使用np.fill将所有对角线值填充到NaN,并返回一个数据框,而不是过滤后的序列。

def get_diff_s(x):
    teams = x.index.get_level_values(1)
    arr = x[:,None]-x[None,:]
    np.fill_diagonal(arr, np.nan)    
    return pd.DataFrame(arr,columns = teams.values,index=teams.values)

df['win_comp_past_difs'] = (df.groupby('date').xx.apply(get_diff_s)
                              .groupby(level=1).cumsum().stack()
                              .groupby(level=[1,2]).shift().sum(level=[0, 1]))

Out[1348]:
                  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

Timing

原始解决方案:(我将您的所有命令都链接成一行)

In [1352]: %timeit df.groupby('date').xx.apply(get_diff).groupby(level=[1,2]).a
      ...: pply(lambda x: x.cumsum().shift()).sum(level=[0,1])
82.9 ms ± 2.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

修订的解决方案:

In [1353]: %timeit df.groupby('date').xx.apply(get_diff_s).groupby(level=1).cum
      ...: sum().stack().groupby(level=[1,2]).shift().sum(level=[0,1])
47.1 ms ± 1.51 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

因此,在样本数据上,速度提高了约40%。但是,我不知道它如何在您的真实数据集中执行]

您的多层索引会产生巨大的开销。

我认为解决此问题的最佳方法是通过并行处理不同线程中的每个groupby。我在这里有关于我的话题,可能会有帮助。

作为替代,您可以通过自己管理索引来减少索引开销。

f, s, t, d = [], [], [], []

for _, sub in df.groupby('date').xx:
  date = sub.index.get_level_values(0)
  i    = sub.index.get_level_values(1)
  tmp  = (sub.values[:, None] - sub.values).ravel()

  f.extend(np.repeat(i, len(i)))
  s.extend(np.tile(i, len(i)))
  t.extend(tmp)
  d.extend(np.repeat(date, len(i)))

然后过滤并处理cumsum + sum

inter = pd.DataFrame({'i0': d, 'i1': f, 'i2': s, 'i3': t}).query('i1 != i2')
df['rf'] = inter.assign(v=inter.groupby(['i1','i2']).i3.apply(lambda s: s.cumsum().shift())).set_index(['i0', 'i1']).v.sum(level=[0,1])

即使对于巨大的数据帧,第二个块也应该运行得非常快。繁重的处理工作在groupby中,这就是为什么映射减少/多处理方法可能超级有用的原因。

在这种情况下,手动索引处理的增强速度大约快5倍

1 loop, best of 3: 3.5 s per loop
1 loop, best of 3: 738 ms per loop

想法是尝试为您提供一些改进方面的指导。这些操作是独立的,因此在不同的线程中执行每个迭代应该是可行的。您也可以考虑numba

python pandas performance cython numba
2个回答
2
投票

我仅修改get_diff。要点是将stack移到get_diff的外面,并采用stack的优势,即降低NaN以避免在get_diff内部进行过滤。


2
投票

您的多层索引会产生巨大的开销。

我认为解决此问题的最佳方法是通过并行处理不同线程中的每个groupby。我在这里有关于我的话题,可能会有帮助。

© www.soinside.com 2019 - 2024. All rights reserved.