dplyr中的summarise函数在返回向量值时如何处理?

23

dplyr::summarize()函数可以在数据上应用任意函数,但似乎这些函数必须返回一个标量值。我很好奇是否有一种合理的方法来处理返回向量值的函数,而不需要多次调用该函数。

这里有一个有点傻的最小示例。考虑一个给出多个值的函数,例如:

f <- function(x,y){
  coef(lm(x ~ y, data.frame(x=x,y=y)))
}

以及类似于以下数据的信息:

df <- data.frame(group=c('A','A','A','A','B','B','B','B','C','C','C','C'), x=rnorm(12,1,1), y=rnorm(12,1,1))

我想要做类似这样的事情:

df %>% 
group_by(group) %>%
summarise(f(x,y))

而不是通常的1列,返回一个每个返回值添加了2列的表格。但是,这会出错:Expecting single value

当然,我们可以通过将函数参数多次传递给dlpyr::summarise()来获得多个值:

f1 <- function(x,y) coef(lm(x ~ y, data.frame(x=x,y=y)))[[1]]
f2 <- function(x,y) coef(lm(x ~ y, data.frame(x=x,y=y)))[[2]]

df %>% 
group_by(group) %>%
summarise(a = f1(x,y), b = f2(x,y))

这将产生所需的输出:

  group         a            b
1     A 1.7957245 -0.339992915
2     B 0.5283379 -0.004325209
3     C 1.0797647 -0.074393457

但是,以这种方式编码太过粗糙且丑陋。

data.table 更为简洁地处理了这种情况:

dt <- as.data.table(df)
dt[, f(x,y), by="group"]

但是它创建的输出会使用额外的行来扩展表格,而不是使用额外的列,导致输出结果既令人困惑,又更难处理:

 group           V1
1:     A  1.795724536
2:     A -0.339992915
3:     B  0.528337890
4:     B -0.004325209
5:     C  1.079764710
6:     C -0.074393457

当然,这里我们还可以使用更多经典的apply策略,

sapply(levels(df$group), function(x) coef(lm(x~y, df[df$group == x, ])))


                     A            B           C
(Intercept)  1.7957245  0.528337890  1.07976471
y           -0.3399929 -0.004325209 -0.07439346

但这种做法既牺牲了代码的优雅性,也降低了代码的执行速度。 特别地,需要注意的是,在这种情况下我们不能使用预定义的函数f,而必须将分组硬编码到函数定义中。

是否有dplyr函数可以处理这种情况?如果没有,有没有更优雅的方式来处理按组计算数据框中的向量值函数的过程?

2个回答

18
你可以尝试 do
library(dplyr)
 df %>%
    group_by(group) %>%
    do(setNames(data.frame(t(f(.$x, .$y))), letters[1:2]))
 # group         a           b
 #1     A 0.8983217 -0.04108092
 #2     B 0.8945354  0.44905220
 #3     C 1.2244023 -1.00715248

基于 f1f2 的输出结果为:
df %>% 
  group_by(group) %>%
  summarise(a = f1(x,y), b = f2(x,y))
#  group         a           b
#1     A 0.8983217 -0.04108092
#2     B 0.8945354  0.44905220
#3     C 1.2244023 -1.00715248

更新

如果您正在使用data.table,可以使用以下选项获得类似的结果

 library(data.table)
 setnames(setDT(df)[, as.list(f(x,y)) , group], 2:3, c('a', 'b'))[]

do()看起来像是解决方案。语法看起来有点疯狂,但我猜如果我修改我的函数以返回一个带有适当名称的数据框,那可能会改善一些。 - cboettig
@cboettig 你可以尝试稍微修改一下这个函数。 - akrun
1
谢谢,太棒了。如果我编写一个函数来接收数据框并返回数据框,f <- function(df) setNames(data.frame(t(coef(lm(x ~ y, df)))), c("a", "b")),那么我可以执行 df %>% group_by(group) %>% do(f(.)),这样就非常干净整洁了。 - cboettig
@cboettig 看起来更好了。 - akrun

10

这就是为什么我仍然喜欢plyr::ddply()的原因:

library(plyr)
f <- function(z) setNames(coef(lm(x ~ y, z)), c("a", "b"))
ddply(df, ~ group, f)
#   group           a          b
# 1     A   0.5213133 0.04624656
# 2     B   0.3020656 0.01450137
# 3     C   0.2189537 0.22998823

1
很好,这避免了dplyr::do()版本中晦涩的.。更多评论请参见此线程:https://twitter.com/JennyBryan/status/585871909776072707 - cboettig

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