分组不会修改数据值,而是改变后续动词的计算范围。同一个 mean(score) 在未分组数据中返回全局均值,在分组数据中则对每组分别计算。
library(dplyr)
scores <- tibble(
group = c("A", "A", "B", "B", "C"),
sex = c("F", "M", "F", "F", "M"),
score = c(10, 12, 15, 14, 13)
)
group_by() 建立分组状态
grouped <- scores |>
group_by(group)
group_vars(grouped)
group_by() 返回带分组元数据的表,后续 summarise()、mutate()、filter() 和 slice_*() 会识别该状态。
summarise():每组压缩为汇总行
scores |>
group_by(group) |>
summarise(
n = n(),
mean_score = mean(score),
sd_score = sd(score),
.groups = "drop"
)
多个变量共同定义组:
scores |>
group_by(group, sex) |>
summarise(
mean_score = mean(score),
.groups = "drop"
)
.groups 控制结果是否继续分组:
| 值 | 结果 |
|---|---|
"drop" |
移除全部分组 |
"drop_last" |
移除最后一层 |
"keep" |
保留全部分组 |
"rowwise" |
结果按行分组 |
如果后续不再需要分组,显式使用 "drop" 最容易预测。
组内 mutate()
mutate() 保留原行数,并把每组统计量写回各行:
scores |>
group_by(group) |>
mutate(
group_mean = mean(score),
centered = score - group_mean,
rank_in_group = min_rank(desc(score))
) |>
ungroup()
这适合组内中心化、标准化、比例和排名。
组内 filter()
scores |>
group_by(group) |>
filter(score >= mean(score)) |>
ungroup()
条件中的 mean(score) 按组计算。若分组状态被意外保留,后面的筛选也会继续按组执行。
ungroup() 清除分组状态
scores |>
group_by(group) |>
mutate(rank_in_group = min_rank(desc(score))) |>
ungroup() |>
mutate(overall_rank = min_rank(desc(score)))
ungroup() 只删除分组元数据,不删除列或行。需要只移除部分分组时,可以指定变量:
scores |>
group_by(group, sex) |>
ungroup(sex)
.by:一次性的局部分组
只在一个动词中需要分组时,可以把分组规则写在 .by 中:
scores |>
summarise(
n = n(),
mean_score = mean(score),
.by = group
)
scores |>
mutate(group_mean = mean(score), .by = group)
scores |>
filter(score >= mean(score), .by = group)
多列局部分组:
scores |>
summarise(
mean_score = mean(score),
.by = c(group, sex)
)
.by 不会让分组状态流入后续管道,适合一次性计算;group_by() 适合多个连续步骤共享同一分组。
count():分组计数的快捷方式
scores |> count(group)
scores |> count(group, sex)
scores |> count(group, sort = TRUE, name = "frequency")
它相当于分组后计算 n(),但表达计数意图更直接。
加权计数对权重列求和:
sales <- tibble(
category = c("A", "A", "B"),
units = c(10, 15, 8)
)
sales |> count(category, wt = units, name = "units")
加权结果不是行数,列名应反映实际含义。
从计数到比例
scores |>
count(group, sex) |>
mutate(proportion = n / sum(n), .by = group)
这里的分母是每个 group 的总数。需要总体比例时不要使用 .by。比例表最常见的错误就是分母层级不清。
选择持久分组还是局部分组
- 连续多个步骤都依赖同一分组:
group_by(),结束后ungroup(); - 单个动词中的临时分组:
.by; - 只做频数统计:
count(); - 输出表不应继续带分组:在
summarise()中设置.groups = "drop"。
分组是数据对象的状态。让它在代码中明确开始、明确结束,可以避免后续步骤在错误的计算范围中运行。