Python pandas中类似于R groupby mutate的函数

42

如果我在 R 中有一个由4列组成的数据框,称为df,并且我想计算一组的和积比,我可以这样做:

// generate data
df = data.frame(a=c(1,1,0,1,0),b=c(1,0,0,1,0),c=c(10,5,1,5,10),d=c(3,1,2,1,2));
| a   b   c    d |
| 1   1   10   3 |
| 1   0   5    1 |
| 0   0   1    2 |
| 1   1   5    1 |
| 0   0   10   2 |
// compute sum product ratio
df = df%>% group_by(a,b) %>%
      mutate(
          ratio=c/sum(c*d)
      );
| a   b   c    d  ratio |
| 1   1   10   3  0.286 |
| 1   1   5    1  0.143 |
| 1   0   5    1  1     |
| 0   0   1    2  0.045 |
| 0   0   10   2  0.454 |

但在Python中,我需要使用循环。我知道Python应该有比原始循环更优雅的方式,你们有什么想法吗?


7
mutate非常强大的另一个部分是不需要进行作用域限定,只需简单地编写c/sum(cd),它就知道它是在引用左侧来自数据框。我发现,在Python中,需要编写lambda g: g.c/(g.cg.d).sum()显得冗长且难以理解。 - jedi
1
@jedi 我同意,但是当你必须使用Python时,你无能为力... - asosnovsky
3个回答

38

使用类似的语法可以通过groupby()apply()完成:

df['ratio'] = df.groupby(['a','b'], group_keys=False).apply(lambda g: g.c/(g.c * g.d).sum())

在此输入图片描述


1
group_keys=False是什么作用? - asosnovsky
3
默认情况下,groupby()函数会将分组的列添加为结果的额外索引,导致索引与原始数据框不同,因此无法轻松地将其赋值给数据框。避免将分组列添加为键,只要每行具有唯一的索引,就可以实现赋值。 - Psidom
9
嗯,这比mutate实际得多。mutate的最大优势是你可以在管道中创建一个新变量并保持链接,而这需要你专门分配一行来分配新列。有没有一种"inplace=True"方法可以为pandas创建额外的列? - agenis

27
根据pandas github上的帖子,我们可以使用transform()方法来复制dplyr::groupby()dplyr::mutate()的组合。对于这个例子,它会如下所示:
df = pd.DataFrame(
    dict(
        a=(1 , 1, 0, 1, 0 ), 
        b=(1 , 0, 0, 1, 0 ),
        c=(10, 5, 1, 5, 10),
        d=(3 , 1, 2, 1, 2 ),
    )
).assign(
    prod_c_d = lambda x: x['c'] * x['d'], 
    ratio    = lambda x: x['c'] / (x.groupby(['a','b']).transform('sum')['prod_c_d'])
)

这个例子使用了pandas方法链接。如果想要了解如何使用方法链接来复制dplyr工作流程的更多信息,请参阅此博客文章
使用apply()groupby()的方法对我没有用,因为它似乎不可适应。例如,如果我们从lambda表达式中删除g.c/,它就无法工作。
df['ratio'] = df.groupby(['a','b'], group_keys=False)\
    .apply(lambda g: (g.c * g.d).sum() )

1
当我们想在同一个调用中分配2个或更多列,并且每个都使用 .groupby(['a','b']) 方法时,有没有比像这样为每个列重复 groupby 方法更“简洁”的方法?df = df.assign(c_lag1 = lambda x: x['c'].groupby(['a','b']).shift(-1), c_lag2 = lambda x: x['c'].groupby(['a','b']).shift(-2)) - Anders Swanson
1
你可以预定义分组对象,但这会打破方法链。 gr = df['c'].groupby(['a','b']) df.assign(c_lag = gr.shift(-1), c_lag2 = gr.shift(-2)) - datistics
1
你能进一步解释为什么需要在assign中使用 lambda吗?我无法确定x是指原始的DataFrame还是一些子组? - jakes
一个 lambda 表达式是一种编写单行单关键字函数的方法。当在 assign 方法中使用它时,x 调用了原始数据框,并应用了当前方法链上游已经进行的所有更改。https://tomaugspurger.github.io/method-chaining.html - datistics
2
这还是最好的方法吗?在Pandas 1.0中是否有更好的解决方案?...这种方式是我找到的最好的,但似乎不太高效(对所有列进行transform('sum'),而你只需要一个)。 - Hernando Casas
我认为您可以通过添加一列1来适应第一种方法:df['ones'] = 1; df['sumofprod'] = df.groupby(['a','b'], group_keys=False).apply(lambda g: g.ones*(g.c * g.d).sum()) - Richard DiSalvo

11

使用datar可以很容易地将你的R代码翻译成Python:

>>> from datar.all import f, c, tibble, sum, group_by, mutate
[2021-06-24 13:32:29][datar][WARNING] Builtin name "sum" has been overriden by datar.
>>> 
>>> df = tibble(a=c(1,1,0,1,0),b=c(1,0,0,1,0),c=c(10,5,1,5,10),d=c(3,1,2,1,2))
>>> df
        a       b       c       d
  <int64> <int64> <int64> <int64>
0       1       1      10       3
1       1       0       5       1
2       0       0       1       2
3       1       1       5       1
4       0       0      10       2
>>> df >> group_by(f.a, f.b) >> mutate(ratio=f.c/sum(f.c*f.d))
        a       b       c       d     ratio
  <int64> <int64> <int64> <int64> <float64>
0       1       1      10       3  0.285714
1       1       0       5       1  1.000000
2       0       0       1       2  0.045455
3       1       1       5       1  0.142857
4       0       0      10       2  0.454545

[Groups: a, b (n=3)]

免责声明:我是datar软件包的作者。


这个软件包看起来非常有前途!我得去试试。我是一个热衷于R语言的人,也涉猎一些Python。 - alexb523

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接