You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 10:04:52