如何用summarize_at实现分组变量的子集化与汇总?
解决dplyr中summarize_at自定义函数无法访问分组列的问题
我明白你遇到的麻烦了——手动写summarise里的每一列计算实在太啰嗦,想用summarize_at批量处理却碰到了object '.' not found的错误,这是因为summarize_at的自定义函数只能拿到当前处理列的数值,没办法直接通过.访问整个分组后的数据集。下面给你几个可行的解决办法:
先回顾你的数据和目标
首先是你的测试数据:
library(dplyr) set.seed(100) test_df <- data.frame(var_name=c(rep(LETTERS[1:3],each=3),"C"), group_name=c(1,1,0,0,1,0,1,1,1,1), obs_1=rnorm(10), obs_2=rnorm(10))
你想要的目标输出是按var_name分组后,计算每个观测列的group_name=1与group_name=0的中位数差值,同时统计两组的样本量:
# 手动实现的目标输出(修正了样本量统计的小错误) test_df %>% group_by(var_name) %>% summarise(delta_obs1 = median(obs_1[group_name==1])-median(obs_1[group_name==0]), delta_obs2 = median(obs_2[group_name==1])-median(obs_2[group_name==0]), n_group1 = sum(group_name==1), n_group0 = sum(group_name==0)) # A tibble: 3 x 5 var_name delta_obs1 delta_obs2 n_group1 n_group0 <fct> <dbl> <dbl> <int> <int> 1 A -0.106 0.295 2 1 2 B -0.486 -0.232 1 2 3 C NA NA 4 0
你遇到的错误原因
你之前写的报错代码:
fun_obs_median <- function(x) { median(x[.$group_name == 1]) - median(x[.$group_name == 0]) } test_df %>% group_by(var_name) %>% summarize_at(.vars = colnames(.)[3:4], .funs=fun_obs_median)
错误信息:
Error in summarise_impl(.data, dots) : Evaluation error: object '.' not found.
问题出在:summarize_at传递给fun_obs_median的参数x只是当前列的向量(比如obs_1的所有值),函数内部的.并没有指向分组后的数据集,所以找不到.$group_name。
解决方案
方法1:使用dplyr 1.0.0+推荐的across()函数(最简洁)
across()可以直接在summarise里批量处理列,而且能直接访问分组内的其他列:
test_df %>% group_by(var_name) %>% summarise( # 批量处理观测列,计算中位数差值 across(starts_with("obs_"), ~median(.x[group_name == 1]) - median(.x[group_name == 0]), .names = "delta_{.col}"), # 统计两组样本量 n_group1 = sum(group_name == 1), n_group0 = sum(group_name == 0) )
这里starts_with("obs_")匹配所有以obs_开头的列,.x代表当前处理的列,.names参数可以自动生成带前缀的列名,非常方便。
方法2:自定义函数时传入group_name参数
如果一定要用summarize_at(比如旧版dplyr),可以把group_name作为额外参数传入函数:
fun_obs_median <- function(x, group) { median(x[group == 1]) - median(x[group == 0]) } test_df %>% group_by(var_name) %>% summarize_at( .vars = vars(starts_with("obs_")), .funs = list(delta = ~fun_obs_median(.x, group_name)) ) %>% # 再合并样本量统计 left_join( test_df %>% group_by(var_name) %>% summarise(n_group1 = sum(group_name == 1), n_group0 = sum(group_name == 0)), by = "var_name" )
这里通过~fun_obs_median(.x, group_name)把当前列和group_name都传入自定义函数,解决了无法访问分组列的问题。
方法3:先宽转长再计算(更直观)
把数据转成长格式后,分组计算会更清晰,最后再转宽:
test_df %>% pivot_longer(cols = starts_with("obs_"), names_to = "obs_var", values_to = "value") %>% group_by(var_name, obs_var) %>% summarise( delta = median(value[group_name == 1]) - median(value[group_name == 0]), n_group1 = sum(group_name == 1), n_group0 = sum(group_name == 0) ) %>% pivot_wider(names_from = obs_var, values_from = delta, names_prefix = "delta_")
这种方式适合观测列非常多的场景,逻辑更清晰,不容易出错。
注意点
- 当某组中group_name只有1或只有0时,中位数差值会返回NA,这和你手动实现的结果一致,是合理的。
- 建议使用
sum(group_name == 1)代替length(which(group_name == 1)),代码更简洁高效。
内容的提问来源于stack exchange,提问作者nadizan
相关产品推荐
相关产品推荐

