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
函数可以处理这种情况?如果没有,有没有更优雅的方式来处理按组计算数据框中的向量值函数的过程?
do()
看起来像是解决方案。语法看起来有点疯狂,但我猜如果我修改我的函数以返回一个带有适当名称的数据框,那可能会改善一些。 - cboettigf <- function(df) setNames(data.frame(t(coef(lm(x ~ y, df)))), c("a", "b"))
,那么我可以执行df %>% group_by(group) %>% do(f(.))
,这样就非常干净整洁了。 - cboettig