如何向多列变异函数传递字符串/直接列名形式的列参数?
问题描述
我想编写一个可对用户指定列执行变异操作的函数,需要同时支持两种传参方式:
- 以字符串形式传递列名
- 直接传递列名(无需加引号)
目前我找到了一种处理字符串参数的可行方法,但觉得不够简洁;而尝试直接传递列名的函数无法正常运行,附上示例代码及报错信息:
data <- tibble( a = 1:10, b = 21:30, c = 101:110 ) # 可用但写法繁琐的字符串版本 test_strings <- function(data, ...) { data %>% mutate_at(unlist(list(...)), function(x){x+5}) } test_strings(data, "a", "b") # 输出结果: # A tibble: 10 x 3 # a b c # <dbl> <dbl> <int> # 1 6 26 101 # 2 7 27 102 # 3 8 28 103 # 4 9 29 104 # 5 10 30 105 # 6 11 31 106 # 7 12 32 107 # 8 13 33 108 # 9 14 34 109 #10 15 35 110 # 无法运行的直接传列名版本 test_sym <- function(data, ...) { data %>% mutate_at(c(...), function(x){x+5}) } test_sym(data, a, b) # 报错信息:Error in check_dot_cols(.vars, .cols) : object 'b' not found
解决方案
一、优化字符串传参的写法
你不需要用unlist(list(...))绕弯子,直接用c(...)就能接收字符串参数,因为mutate_at本身支持字符向量作为列选择器,写法更简洁:
test_strings <- function(data, ...) { data %>% mutate_at(c(...), ~ .x + 5) } test_strings(data, "a", "b") # 正常运行,结果和之前一致
二、让直接传列名生效的写法
直接传列名报错是因为函数会把a、b当作全局环境中的对象查找,而不是数据框里的列名。用dplyr的tidyeval语法就能解决这个问题:
方法1:用across配合c(...)(推荐,dplyr 1.0.0+版本)
across是mutate_at的替代方案,语法更直观,能直接解析传入的裸列名:
test_sym <- function(data, ...) { data %>% mutate(across(c(...), ~ .x + 5)) } test_sym(data, a, b) # 正常运行,列a、b都加了5
方法2:用ensyms捕获符号(兼容旧版dplyr)
如果还在使用旧版dplyr,可以用rlang::ensyms把传入的裸列名转换成符号,再传给mutate_at:
test_sym <- function(data, ...) { cols <- rlang::ensyms(...) data %>% mutate_at(cols, ~ .x + 5) } test_sym(data, a, b) # 正常运行
三、同时支持两种传参方式的通用函数
想让同一个函数既能接字符串列名,又能接裸列名?用all_of()配合across就能实现:
test_universal <- function(data, ...) { cols <- c(...) data %>% mutate(across(all_of(cols), ~ .x + 5)) } # 两种传参方式都能正常工作 test_universal(data, "a", "b") test_universal(data, a, b)
内容的提问来源于stack exchange,提问作者SiriO
相关产品推荐
相关产品推荐

