【问题标题】:Quickest way to groupby for multiple variables/columns对多个变量/列进行分组的最快方法
【发布时间】:2019-12-09 01:04:20
【问题描述】:

请多多包涵,我不确定我能多好地解释这个问题。我有一个数据框df

df = pd.DataFrame({"var1":["A","B","B","A","B","C","A","C"],"var2":["foo","bar","bar","foo","foo","foo","bar","foo"],
                   "var3":["apple","apple","peach","plum","pear","peach","plum","pear"],"actual":[13,2,14,6,8,4,12,9],
                   "expected":[11,4,9,4,12,0,10,14]})

print(df)

  var1 var2   var3  actual  expected
0    A  foo  apple      13        11
1    B  bar  apple       2         4
2    B  bar  peach      14         9
3    A  foo   plum       6         4
4    B  foo   pear       8        12
5    C  foo  peach       4         0
6    A  bar   plum      12        10
7    C  foo   pear       9        14

我的总体目标是通过变量 (var1, var2, var3) 按变量中的唯一类别对每个变量进行分组,并将每个组的实际值和预期值相加。

例如,当我按var1 分组时,我希望得到这样的结果

df1=pd.DataFrame({"var1":["A","B","C"],"actual":[31,24,13],"expected":[25,25,14]})
print(df1)

  var1  actual  expected
0    A      31        25
1    B      24        25
2    C      13        14

var2这个:

df2=pd.DataFrame({"var2":["foo","bar"],"actual":[40,28],"expected":[41,23]})
print(df2)

  var2  actual  expected
0  foo      40        41
1  bar      28        23

var3这个:

df3=pd.DataFrame({"var3":["apple","peach","plum","pear"],"actual":[15,18,18,17],"expected":[15,9,14,26]})
print(df3)
    var3  actual  expected
0  apple      15        15
1  peach      18         9
2   plum      18        14
3   pear      17        26

创建后,我想对每个 groupby 的每个组的绝对偏差求和(然后对这些求和)。

基本上是这样的。

print(abs(df1["actual"]-df1["expected"]).sum())
print(abs(df2["actual"]-df2["expected"]).sum())
print(abs(df3["actual"]-df3["expected"]).sum())

print(abs(df1["actual"]-df1["expected"]).sum()+abs(df2["actual"]-df2["expected"]).sum()+abs(df3["actual"]-df3["expected"]).sum())

分别给出 8、6、22、36。

我目前的做法是这样的:

variables = ["var1","var2","var3"]
expecteds = []
actuals = []
for var in variables:
    expecteds = np.append(expecteds, (df["expected"].groupby(df[var])).sum())
    actuals = np.append(actuals, (df["actual"].groupby(df[var])).sum())

print(np.sum(abs(expecteds-actuals)))

给出 36.0.

答案是正确的,但我想知道是否有人看到更快的方法来做到这一点。我不需要中间步骤的输出,所以只要输出 36 就可以完美地工作。

作为参考,在我的电脑上运行这个方法: 16.6 ms ± 959 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

到目前为止,我从来没有在数据帧上使用过 Pandas,以至于我必须注意运行时。

【问题讨论】:

  • 如果您按每个组计算,然后对每个组求和,除非您想重复计算重叠(var1/var2/var3 之间存在差异),那么 36 有意义吗?您在原始表上的绝对差异只有 26...
  • @JonClements 这在直觉上没有意义,但对于我正在从事的项目来说,这是查看它的更可取的方式。但是,是的,它确实有很多重叠和重复计算。

标签: python pandas numpy dataframe pandas-groupby


【解决方案1】:

不确定它是否是最有效的,或者甚至更有效,但您至少可以通过利用apply() 来缩短必须编写的代码量。

s = 0
for var in ('var1','var2','var3'):
    s = s + df.groupby(var).sum().apply(lambda row: np.abs(row['actual']-row['expected']), axis=1).sum()

这里的主要思想是沿axis=1 使用apply,它返回单独的行。然后除此之外,所有的逻辑都是一样的。

【讨论】:

    【解决方案2】:

    看看这是否适合你。数据和分组的选择(全部使用 pandas 方法完成)不同,但数据框和打印语句与您输入的相同。这也给出了输出 8、6、22 和 36。

    关于速度,你必须在你的电脑上检查。

    import pandas as pd
    df = pd.DataFrame({"var1":["A","B","B","A","B","C","A","C"],"var2":  ["foo","bar","bar","foo","foo","foo","bar","foo"],
                   "var3":["apple","apple","peach","plum","pear","peach","plum","pear"],"actual":[13,2,14,6,8,4,12,9],
                   "expected":[11,4,9,4,12,0,10,14]})
    df1=df[['var1','actual','expected']]
    df2=df[['var2','actual','expected']]
    df3=df[['var3','actual','expected']]
    df1=df1.groupby('var1').sum()
    df2=df2.groupby('var2').sum()
    df3=df3.groupby('var3').sum()
    
    print(abs(df1["actual"]-df1["expected"]).sum())
    print(abs(df2["actual"]-df2["expected"]).sum())
    print(abs(df3["actual"]-df3["expected"]).sum())
    
    print(abs(df1["actual"]-df1["expected"]).sum()+abs(df2["actual"]- df2["expected"]).sum()+abs(df3["actual"]-df3["expected"]).sum())
    

    【讨论】:

      【解决方案3】:

      这里有两种方法会比你现在做的更快,虽然你的方法没有问题,但它似乎工作正常。


      set_index + concat + sum

      u = df.set_index(['var1', 'var2', 'var3'])
      
      f = pd.concat([u.sum(level=n) for n in range(u.index.nlevels)], ignore_index=True)
      
      f['actual'].sub(f['expected']).abs().sum()
      

      36
      

      melt + groupby + sum

      um = df.melt(['actual', 'expected']).groupby('value')[['actual', 'expected']].sum()
      
      um['actual'].sub(um['expected']).abs().sum()
      

      36
      

      【讨论】:

      • 这两个选项在我的试用数据框上都非常有效(比原始数据框快约 20%),但是当我尝试将其应用于整个数据集(超过 300,000 行和 200 多个 groupby 语句)时,它比我原来的方式花费了 5 倍的时间。理论上它看起来很棒(也许我实施错了?我会继续尝试)。
      • 此解决方案基于与您的解决方案不同的指标进行扩展,根据您的数据,您的解决方案可能效果最佳。我确定您正确实施了它,但它可能不是您的数据的正确方法。
      • 无论如何我感谢您的帮助!谢谢
      • @Paulfryy 如果你有时间 - 我很想知道 pivot_table 在这里的比较......虽然它的声音(以及你拥有的列数) - 我没有感觉现在过于乐观了:)
      • @JonClements - 刚刚将它实现到我的真实代码中,还不错!它比测试数据帧上的原始代码花费了相当长的时间,但是当带入我的真实代码时,它花费了大约 2 倍的时间(1.4 秒平均对 3 秒平均)。考虑到创建的数据透视表的大小,我认为这相对较好
      【解决方案4】:

      一种使用pivot_table 创建汇总表的方法。

      df2 = df.pivot_table(columns=['var1', 'var2', 'var3'])
      diff = df2['actual'] - df2['expected']
      

      这给了你:

      var1  var2  var3 
      A     bar   plum     2
            foo   apple    2
                  plum     2
      B     bar   apple   -2
                  peach    5
            foo   pear    -4
      C     foo   peach    4
                  pear    -5
      

      然后你可以取每个级别总和的绝对和,例如:

      total = sum(diff.sum(level=n).abs().sum() for n in range(diff.index.nlevels))
      

      这给你 36。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-11-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多