使用purrr与dplyr时,rlang::sym是否为最优实现方式?
关于dplyr编程中字符串变量名与tidy求值的问题解答
一、用rlang::sym()处理字符串变量名是合理的实现方式
你当前的代码不仅可行,而且是在dplyr中处理字符串形式变量名的标准方法之一,完全没问题。
rlang::sym()的作用就是把字符串转换成dplyr能识别的符号(symbol)——也就是我们平时写未引号变量名时的底层对象。配合!!(解引用运算符),就能让group_by()正确识别这个变量,完成分组操作。
不过你的输出里出现了NA,是因为map_df会自动合并结果,每个分组变量对应的结果列会在另一个分组的行里填充NA。如果想要更清晰的输出,可以给函数加一列标记分组变量名:
library(tidyverse) my_summarise <- function(df, group_var) { group_sym <- rlang::sym(group_var) df %>% group_by(!!group_sym) %>% summarise(mpg = mean(mpg), .groups = "drop") %>% mutate(grouped_by = group_var) # 新增列标记分组变量 } purrr::map_df(c("cyl", "am"), my_summarise, df = mtcars)
输出会变成:
# A tibble: 5 × 3 cyl mpg grouped_by <dbl> <dbl> <chr> 1 4 26.7 cyl 2 6 19.7 cyl 3 8 15.1 cyl 4 NA 17.1 am 5 NA 24.4 am
另外,还有两种替代方法可以实现同样的效果,供你参考:
- 使用
.data代词:不需要转换符号,直接通过字符串索引数据框的列my_summarise <- function(df, group_var) { df %>% group_by(.data[[group_var]]) %>% summarise(mpg = mean(mpg), .groups = "drop") %>% mutate(grouped_by = group_var) } - 使用
across()(适合同时处理多个变量的场景):my_summarise <- function(df, group_var) { df %>% group_by(across(all_of(group_var))) %>% summarise(mpg = mean(mpg), .groups = "drop") %>% mutate(grouped_by = group_var) }
二、为什么select(mtcars, !!x)可行但group_by(mtcars, !!x)不行?
这个差异和解引用本身无关,而是select()和group_by()的参数设计不同:
select()的参数非常灵活,它原生支持直接传入字符串、符号、列位置甚至选择辅助函数(比如starts_with())。所以哪怕你直接写select(mtcars, x)(不用!!),只要x是字符串,它也能正常工作。当你用!!x时,本质上还是把字符串传给了select(),而它刚好能处理。group_by()的标准接口只接受符号或表达式,不支持直接传入字符串。当你写group_by(mtcars, !!x)时,x是字符串,!!x解引用后还是字符串,group_by()无法识别这个字符串对应的列,所以会报错。
要让group_by()处理字符串变量名,要么用sym()转成符号再解引用,要么用.data[[x]]的方式,这两种都是tidy求值框架下的规范写法。
内容的提问来源于stack exchange,提问作者karldw
相关产品推荐
相关产品推荐

