purrr::pmap与rlang配合的混淆行为:参数是否需引号?
问题解答
1. 为什么pmap不带引号时会报错?
这本质是求值环境与非标准求值(NSE)的冲突:
- 你的
tryfn用rlang::enquo()捕捉参数的表达式和调用环境,当直接调用tryfn(iris, Sepal.Length, Sepal.Width)时,Sepal.Length是在函数调用的上下文里被捕捉的,dplyr的NSE机制会自动在传入的data(也就是iris)环境中解析这个符号,所以能找到对应的列。 - 但在
pmap里,你写list(Sepal.Length, wt, len)时,R会立即在全局环境中尝试求值这些变量名——而Sepal.Length是iris的列,不是全局环境里的独立对象,所以自然会抛出“找不到对象”的错误。 - 当你传带引号的字符串时,这些是字面量,在全局环境中是合法的对象,
pmap可以正常传递,而enquo()捕捉到字符串表达式后,dplyr的select()可以直接解析字符串作为列名,所以能正常工作。
2. 如何让函数在pmap中无需引号也能运行?
这里有几种简洁的解决方案,核心思路是让参数以符号/quosure对象的形式传递,避免在全局环境中提前求值:
方案1:用rlang::ensym()改造函数,兼容符号与字符串
ensym()可以自动把输入的符号或字符串转换成符号,让函数同时支持两种输入方式,且适配pmap的调用:
library(rlang) library(dplyr) library(purrr) tryfn <- function(data, x, y) { # 将输入转换为符号 x <- ensym(x) y <- ensym(y) data <- select( .data = data, x = !!x, y = !!y ) data.frame(mean.x = mean(data$x), mean.y = mean(data$y)) } # pmap调用时传递符号对象(用sym()创建) pmap(.l = list( data = list(iris, mtcars, ToothGrowth), x = list(sym("Sepal.Length"), sym("wt"), sym("len")), y = list(sym("Sepal.Width"), sym("mpg"), sym("dose")) ), .f = tryfn)
也可以用quote()直接创建符号,写法更简洁:
pmap(.l = list( data = list(iris, mtcars, ToothGrowth), x = list(quote(Sepal.Length), quote(wt), quote(len)), y = list(quote(Sepal.Width), quote(mpg), quote(dose)) ), .f = tryfn)
方案2:用curly-curly语法简化函数
dplyr 1.0.0+支持的{{ }}语法可以替代enquo()+!!的组合,让函数代码更简洁,同时保持兼容性:
tryfn <- function(data, x, y) { data <- select( .data = data, x = {{x}}, y = {{y}} ) data.frame(mean.x = mean(data$x), mean.y = mean(data$y)) } # 调用方式和方案1完全一致 pmap(.l = list( data = list(iris, mtcars, ToothGrowth), x = list(quote(Sepal.Length), quote(wt), quote(len)), y = list(quote(Sepal.Width), quote(mpg), quote(dose)) ), .f = tryfn)
方案3:传递quosure对象(无需修改原函数)
如果你不想改动原函数,可以在pmap里直接传递quosure对象,让enquo()能正确捕捉到表达式的解析环境:
pmap(.l = list( data = list(iris, mtcars, ToothGrowth), x = list(quo(Sepal.Length), quo(wt), quo(len)), y = list(quo(Sepal.Width), quo(mpg), quo(dose)) ), .f = tryfn)
内容的提问来源于stack exchange,提问作者Indrajeet Patil
相关产品推荐
相关产品推荐

