在dplyr的mutate/across中使用字符串存储的变量名进行计算
问题
需要在dplyr::mutate()结合across()的计算中,使用字符串形式传入的列名作为乘数。尝试用{{col}}准引用失败,报错类型不匹配,目前可通过eval(parse())或get()实现,但想找到更贴合tidyverse风格的方法。
原错误代码
library(dplyr) library(purrr) vec_cols <- c('aa','bb') atest <- data.frame(aa = 1:3, bb = rep(0, 3), cc = -(1:3), dd = rep(10,3)) map(vec_cols, \(col) atest %>% rowwise() %>% mutate(across(everything(), ~ {{col}} * .x)))
触发错误
Error in `map()`: ℹ In index: 1. Caused by error in `mutate()`: ℹ In argument: `across(.cols = everything(), .fns = ~"aa" * .x)`. ℹ In row 1. Caused by error in `across()`: ! Can't compute column `aa`. Caused by error in `"aa" * aa`: ! non-numeric argument to binary operator Run `rlang::last_trace()` to see where the error occurred.
问题根源
{{col}}是tidyverse的准引用语法,仅适用于符号(symbol)类型的列名输入。当传入字符串时,{{col}}不会解析为对应列,而是直接保留字符串字面量,导致出现字符串与数值相乘的非法运算。
符合tidy风格的解决方案
1. 使用.data代词(推荐)
dplyr官方推荐用.data代词引用数据框列,支持字符串索引,完全符合tidy规范:
map(vec_cols, \(col) atest %>% mutate(across(everything(), ~ .data[[col]] * .x)))
2. 字符串转符号后解引用
通过rlang::sym()将字符串转为符号,再用!!解引用,属于tidyverse元编程的标准用法:
map(vec_cols, \(col) atest %>% mutate(across(everything(), ~ !!rlang::sym(col) * .x)))
额外优化:移除rowwise()
以上两种方案的计算都是向量化的,不需要rowwise()分组,移除后能显著提升运行效率。
输出结果
两种方案均能得到与eval(parse())/get()一致的结果:
[[1]] aa bb cc dd 1 1 0 -1 10 2 4 0 -4 20 3 9 0 -9 30 [[2]] aa bb cc dd 1 0 0 0 0 2 0 0 0 0 3 0 0 0 0
内容的提问来源于stack exchange,提问作者Brandon
相关产品推荐
相关产品推荐

