如何编写支持任意长度列名字符向量的灵活分组R函数
实现支持任意长度列名字符向量的灵活分组函数
问题背景
需要编写一个函数,接收tibble对象和任意长度的列名字符向量,执行分组聚合操作。现有实现仅支持0、1、2列,尝试!!as.name()或{{}}均无法适配任意长度的列名向量:
!!as.name(group_by_cols)仅取向量第一个元素,忽略其余列{{group_by_cols}}在向量长度不为1时直接报错
解决方案
方法1:使用.data代词 + all_of()(推荐)
tidyverse 1.0+推荐使用.data代词配合all_of()引用字符向量中的列名,这种方式直观且符合现代tidyverse规范:
library(tidyverse) ex = crossing(abc=LETTERS[1:3], xyz=LETTERS[24:26]) %>% mutate(n = row_number()) group_flexibly = function(tbl, group_by_cols = character(0)) { tbl %>% group_by(across(all_of(group_by_cols))) %>% summarize(.groups='keep', mean_n = mean(n)) }
方法2:使用rlang的syms() + !!!解引用
通过syms()将字符向量转换为符号列表,再用!!!(解引用运算符)将其展开为group_by()的多个参数:
group_flexibly = function(tbl, group_by_cols = character(0)) { tbl %>% group_by(!!!syms(group_by_cols)) %>% summarize(.groups='keep', mean_n = mean(n)) }
测试验证
运行以下测试代码,结果与预期一致:
group_flexibly(ex) group_flexibly(ex, 'abc') group_flexibly(ex, 'xyz') group_flexibly(ex, c('abc','xyz'))
输出示例:
> group_flexibly(ex) # A tibble: 1 × 1 mean_n <dbl> 1 5 > group_flexibly(ex, 'abc') # A tibble: 3 × 2 # Groups: abc [3] abc mean_n <chr> <dbl> 1 A 2 2 B 5 3 C 8 > group_flexibly(ex, 'xyz') # A tibble: 3 × 2 # Groups: xyz [3] xyz mean_n <chr> <dbl> 1 X 4 2 Y 5 3 Z 6 > group_flexibly(ex, c('abc','xyz')) # A tibble: 9 × 3 # Groups: abc, xyz [9] abc xyz mean_n <chr> <chr> <dbl> 1 A X 1 2 A Y 2 3 A Z 3 4 B X 4 5 B Y 5 6 B Z 6 7 C X 7 8 C Y 8 9 C Z 9
原理解释
across(all_of(group_by_cols)):all_of()将字符向量中的每个元素解析为数据框的列名,across()把这些列传递给group_by(),完美适配任意长度的列名向量!!!syms(group_by_cols):syms()将字符向量转为符号对象列表,!!!将列表展开为group_by()的独立参数,等价于手动逐个传入列名
内容的提问来源于stack exchange,提问作者ericminikel
相关产品推荐
相关产品推荐

