Skip to content

dplyr summarise() 分组输出消息解析

了解如何理解和处理 dplyr 的 summarise() 分组消息,掌握分组控制的正确方法

问题描述

当使用 dplyr 的 group_by()summarise() 函数进行数据汇总时,你可能会遇到类似这样的消息:

`summarise()` regrouping output by 'year' (override with `.groups` argument)

或者:

`summarise()` ungrouping output (override with `.groups` argument)

这些消息虽然不影响计算结果的正确性,但可能会让用户感到困惑。本文将详细解析这些消息的含义,并介绍如何通过 .groups 参数来控制分组行为。

消息的含义

默认分组行为

在 dplyr 1.0.0 版本之前,summarise() 函数在处理分组数据时的行为是:自动剥离最后一级分组。这种设计在当时没有明确的提示,导致许多用户在后续操作中对数据的分组状态产生误解。

为了增加透明度,dplyr 现在会在执行 summarise() 时显示分组变化的消息:

消息类型说明

  • ungrouping output: 当输入数据只有一级分组时,汇总后会完全取消分组
  • regrouping output by 'x': 当输入数据有多级分组时,汇总后会保留除最后一级外的所有分组

示例分析

r
library(dplyr)

# 单级分组示例
mtcars %>%
  group_by(am) %>% 
  summarise(mpg = sum(mpg))
# 输出: `summarise()` ungrouping output (override with `.groups` argument)
# 结果: 数据不再有任何分组属性

# 多级分组示例  
mtcars %>% 
  group_by(am, vs) %>% 
  summarise(mpg = sum(mpg))
# 输出: `summarise()` regrouping output by 'am' (override with `.groups` argument)
# 结果: 数据按照 'am' 分组(去除了 'vs' 分组)

.groups 参数详解

dplyr 1.0.0 引入了 .groups 参数,让用户可以显式控制汇总后的分组结构。

可用选项

r
# 四种分组控制选项
mtcars %>% 
  group_by(cyl, am) %>% 
  summarise(avg_mpg = mean(mpg), .groups = "drop")    # 完全取消分组

mtcars %>% 
  group_by(cyl, am) %>% 
  summarise(avg_mpg = mean(mpg), .groups = "drop_last") # 默认行为:去除最后一级分组

mtcars %>% 
  group_by(cyl, am) %>% 
  summarise(avg_mpg = mean(mpg), .groups = "keep")    # 保持原有分组结构

mtcars %>% 
  group_by(cyl, am) %>% 
  summarise(avg_mpg = mean(mpg), .groups = "rowwise") # 每行作为独立分组

默认行为规则

当未指定 .groups 参数时,dplyr 会根据结果自动选择:

  • 如果所有汇总结果只有 1 行,使用 "drop_last"
  • 如果汇总结果行数不同,使用 "keep"

实用技巧

你可以通过设置全局选项来禁用这些消息:

r
options(dplyr.summarise.inform = FALSE)

实际应用建议

避免后续操作混淆

分组消息的主要目的是防止用户在不知情的情况下基于错误的分组结构进行后续操作:

r
# 可能产生意外结果的操作
df %>%
  group_by(year, month) %>%
  summarise(avg = mean(value)) %>%  # 此时只剩下 year 分组
  mutate(relative = avg / first(avg))  # 按 year 分组计算,可能不是预期行为

明确指定分组意图

为了代码清晰和可预测性,建议显式指定 .groups 参数:

r
# 推荐做法:明确指定分组意图
df %>%
  group_by(year, month) %>%
  summarise(avg = mean(value), .groups = "drop") %>%  # 明确取消分组
  mutate(relative = avg / first(avg))  # 在整个数据框上计算

常见问题解答

为什么只显示部分分组字段?

消息中只显示保留的分组字段(如 'year'),这是因为 dplyr 的默认行为是去除最后一级分组,而不是完全取消所有分组。

如何保持原有分组结构?

使用 .groups = "keep" 选项可以保持原有的分组结构:

r
df %>%
  group_by(year, week) %>%
  summarise(average = mean(total_rodents), .groups = "keep")

这与 summarise_all() 有何不同?

summarise_all()summarise(across(everything())) 在处理分组时行为一致,都会产生相同的分组消息。

总结

dplyr 的 summarise() 分组消息是一种友善的提醒机制,旨在提高分组操作透明度。通过理解和正确使用 .groups 参数,你可以:

  1. 控制输出分组结构:明确指定汇总后数据的组织方式
  2. 避免后续操作错误:防止基于错误分组假设的操作
  3. 提高代码可读性:显式表达你的分组意图

虽然 .groups 参数目前仍处于实验阶段,但其设计思路体现了 dplyr 对可预测性和用户友好性的持续改进。