tidyselect::all_of()向dplyr::select()传递了何种特殊内容?
问题场景
在tidyselect 1.1.0及以后版本中,直接在dplyr::select()等函数里使用外部字符向量会触发弃用警告:
在tidyselect 1.1.0版本中,在选择操作中使用外部向量已被弃用。
但使用tidyselect::all_of()包裹外部向量后,警告就会消失。让人疑惑的是,单独调用all_of()时,用class()或attributes()查看返回值,看不到任何特殊标识,那它是如何让下游函数识别的?
示例代码:
library(dplyr) # 1.0.10 library(tidyselect) # 1.2.0 my_var <- c("Petal.Length", "Petal.Width") # 无警告 iris %>% select(all_of(my_var)) # 触发警告:Using an external vector in selections was deprecated in tidyselect 1.1.0. iris %>% select(my_var)
核心原因:tidyselect的上下文求值机制
all_of()的作用不是修改输入向量本身,而是在tidyselect的专属求值上下文中,给这个向量的引用打上明确的标记:
延迟求值与上下文捕获
tidyselect的选择函数(比如select())不会像普通R函数那样直接求值所有参数,而是会先捕获整个调用表达式,在自己的求值框架里解析。当它发现参数是all_of(my_var)这样的调用时,会识别出这是用户明确指定要解析外部向量作为列名的操作,从而跳过警告触发逻辑。普通环境下无特殊标识
当你单独调用all_of(my_var)时,是在普通R环境中执行,此时tidyselect的特殊上下文不存在,all_of()只是简单返回原向量:all_of(my_var) #> [1] "Petal.Length" "Petal.Width" class(all_of(my_var)) #> [1] "character" attributes(all_of(my_var)) #> NULL那些用于识别的标记只存在于tidyselect的求值过程中,不会附加到返回的向量对象上。
为什么直接传my_var会触发警告
当你直接在select()里传入my_var时,tidyselect默认会把my_var当作一个列名符号来处理——它会尝试寻找名为my_var的列,而不是解析my_var指向的外部向量。当它发现这个符号指向外部环境的向量时,会判定这是模糊的、不推荐的用法,因此触发弃用警告。
而all_of()的存在就是为了消除这种模糊性:它明确告诉tidyselect,"我要使用这个向量里的字符串作为列名",让tidyselect正确解析外部向量的内容,同时避免警告。
内容的提问来源于stack exchange,提问作者Patrick

