如何实现DataFrame的分层聚合?寻求tidyverse/data.table优雅方案
分层递增分组聚合的优雅实现(Tidyverse & Data.table)
需求说明
需要对数据集执行粒度递增的独立分组聚合:以mtcars为例,依次按am、am+gear、am+gear+carb分组,分别计算mpg的均值,三次聚合是独立操作,并非一次性多层级聚合。
现有可行代码
你已经实现了基础版本:
list("am", "gear", "carb") |> Reduce(f = c, accumulate = TRUE) |> Map(f = \(vars){ mtcars |> group_by(!!!syms(vars)) |> summarise(mpg = mean(mpg)) })
Tidyverse优化实现
方法1:更直观的分组语法
用across(all_of())替代!!!syms(),语法更符合tidyverse的现代风格,同时显式控制分组清理:
library(tidyverse) group_vars <- c("am", "gear", "carb") # 生成递增的分组变量列表 accumulate(group_vars, ~c(.x, .y), .init = character(0)) |> tail(-1) |> # 移除初始空向量 map(\(vars) { mtcars |> group_by(across(all_of(vars))) |> summarise(mpg = mean(mpg), .groups = "drop") })
.groups = "drop"确保聚合后自动取消分组,避免后续操作的分组残留问题。
方法2:整合所有结果到单个数据框
如果需要把各粒度的聚合结果统一管理,可结合expand_grid生成分组步骤标识:
expand_grid( group_level = seq_along(group_vars), group_vars = accumulate(group_vars, c) ) |> mutate( aggregated_data = map(group_vars, ~{ mtcars |> group_by(across(all_of(.x))) |> summarise(mpg = mean(mpg), .groups = "drop") }) )
这样能同时保留分组粒度的序号和对应聚合结果,方便后续查看或分析。
Data.table高效实现
对于大数据集,data.table的语法更简洁且性能更优:
library(data.table) dt <- as.data.table(mtcars) group_vars <- c("am", "gear", "carb") accumulate(group_vars, c) |> map(\(vars) dt[, .(mpg = mean(mpg)), by = vars])
data.table直接支持字符向量作为by参数,无需额外的符号转换,代码更精简。
关于递归/嵌套结构的问题
递归方法需要注意:每次聚合是独立的,并非基于上一次分组的结果进行细分;而group_by/across不支持直接传入嵌套结构,因为它们需要明确的一维分组变量列表。因此,用accumulate生成逐步递增的变量列表是最贴合需求的思路,上述实现只是将你的原始逻辑用更符合tidyverse/data.table惯用写法的方式重构。
内容的提问来源于stack exchange,提问作者I_O
相关产品推荐
相关产品推荐

