dplyr中get()、.data[[]]和!!按字符选列的差异及原理
关于dplyr中字符串列名指定方法的差异与统一方案
问题背景
我需要在dplyr中编写函数,实现「当数据集里变量a等于值b时,将变量c修改为值d」的操作,且能在不同场景传入不同变量名。测试了三种用字符串指定列名的方法,但它们在select、filter、mutate中的表现各不相同,示例代码如下:
riib = function(dataset, selectvar) { selectname=deparse(substitute(selectvar)) # 注:原代码中replacename、equalval未定义,此处保留原代码结构 print(replacename) print(selectname) print(equalval) print("Selecting") # 选择列时,!! 可用,.data[[]] 可用,get() 报错 print(dataset %>% select(!!selectname)) print(dataset %>% select(.data[[selectname]])) # print(dataset %>% select(get(selectname))) print("Filtering") # 筛选时,!!selectname 无结果,.data[[]] 和 get() 可用 print(dataset %>% filter(!!selectname == 3)) print(dataset %>% filter(.data[[selectname]] == 3)) print(dataset %>% filter(get(selectname) == 3)) print("Mutating") # 修改列时,!! 可用,.data[[]] 和 get() 报错 print(dataset %>% mutate(!!selectname := 3)) # print(dataset %>% mutate(.data[[selectname]] := 3)) # print(dataset %>% mutate(get(selectname) := 3)) print(dataset) } bob=tibble(x=c(1,2), y=c(3,4)) bob riib(bob,y)
想请教为何会有这些差异,以及是否存在统一的指定字符列名的语法。
差异原因分析
1. !!(bang-bang)的原理与局限
!!是rlang包的非标准求值工具,作用是把字符串或符号强制解引用,转换成dplyr函数能识别的列名符号:
- 在
select中有效:select接受符号作为列名输入,!!selectname把字符串转成符号,符合参数要求。 - 在
filter中失效:!!selectname == 3会被解析成字符串与数值的比较(比如"y" == 3),结果永远为FALSE,所以返回空数据集。正确写法应该是先把字符串转成符号再比较:!!sym(selectname) == 3。 - 在
mutate中有效:!!selectname := 3利用命名注入,把字符串作为列名(覆盖已有列),符合赋值语法。
2. .data[[]]的原理与局限
.data是dplyr提供的数据代词,明确指向当前数据集的列,属于标准求值工具:
- 在
select中有效:.data[[selectname]]直接提取列,select可接受这种列引用。 - 在
filter中有效:.data[[selectname]] == 3直接对列值做比较,逻辑正确。 - 在
mutate中失效:mutate赋值左侧需要列名符号,而.data[[selectname]]是列的取值,不能作为赋值目标,因此语法报错。
3. get()的原理与局限
get()是R基础函数,从当前环境提取对应字符串的对象:
- 在
select中失效:select需要列名符号或位置,get(selectname)会尝试从全局环境提取对象,而非数据集内的列,因此报错。 - 在
filter中有效:filter的表达式在数据集环境中求值,get(selectname)能正确找到数据集内的列并取值比较。 - 在
mutate中失效:和.data[[]]类似,get(selectname)是列的取值,不能作为赋值的目标列名,语法不允许。
统一的字符列名指定方案
现在dplyr推荐使用统一的标准求值语法,以下三种方案可适配所有核心dplyr函数:
方案1:.data代词+标准求值
适合明确传入字符串列名的场景:
# 实现需求的示例函数 modify_col <- function(dataset, a, b, c, d) { dataset %>% mutate({{c}} := if_else(.data[[a]] == b, d, .data[[c]])) } # 调用:传入字符串列名 bob <- tibble(x=c(1,2), y=c(3,4)) modify_col(bob, a = "x", b = 1, c = "y", d = 100)
方案2:{{}}(curly-curly)
适合直接传入列名符号(非字符串)的场景,也可提取列名字符串:
modify_col <- function(dataset, a, b, c, d) { # 若需提取列名字符串,用ensym()+as_string() a_name <- as_string(ensym(a)) c_name <- as_string(ensym(c)) dataset %>% mutate({{c}} := if_else({{a}} == b, d, {{c}})) } # 调用:直接传入列名 modify_col(bob, a = x, b = 1, c = y, d = 100)
方案3:兼容字符串和符号输入的通用写法
如果需要同时支持两种输入方式,可用ensym()+!!组合:
modify_col <- function(dataset, a, b, c, d) { a_sym <- ensym(a) c_sym <- ensym(c) dataset %>% mutate(!!c_sym := if_else(!!a_sym == b, d, !!c_sym)) } # 两种调用方式都支持 modify_col(bob, a = "x", b = 1, c = "y", d = 100) modify_col(bob, a = x, b = 1, c = y, d = 100)
内容的提问来源于stack exchange,提问作者Holt Dwyer
相关产品推荐
相关产品推荐

