如何在data.table中使用rlang包的{{双大括号运算符
rlang包提供的{{}}运算符(又称准引用Quasiquotation)可极大简化将列名作为函数参数传递的实现。已知rlang包主要为tidyverse生态设计,请问是否存在方法可在data.table中使用{{}}运算符?
test_dplyr <- function(dt, col1, col2){ temp <- dt %>% group_by( {{col2}} ) %>% summarise(test = mean( {{col1}} )) return(temp) } test_dplyr(dt=iris, col1=Sepal.Length, col2=Species)
运行返回结果:
# A tibble: 3 x 2 Species test <fct> <dbl> 1 setosa 5.01 2 versicolor 5.94 3 virginica 6.59
以下是理想中想要实现的写法,但运行会返回错误。
test_dt2 <- function(dt, col1, col2){ data.table::setDT(dt) temp <- dt[, .( test = mean({{col1}})), by = {{col2}} ] return(temp) } # 报错 test_dt2(dt=iris, col1= Sepal.Length, col2= Species) # 同样报错 test_dt2(dt=iris, col1= 'Sepal.Length', col2= 'Species')
以下是目前可实现rlang与data.table结合使用的替代方案,该方案存在两处不便:一是需要对每个列名变量调用rlang::ensym()处理,二是必须将data.table操作放在rlang::inject()内执行。
test_dt <- function(dt, col1, col2){ # 处理列名 col1 <- rlang::ensym(col1) col2 <- rlang::ensym(col2) data.table::setDT(dt) temp <- rlang::inject( dt[, .( test = mean(!!col1)), by = !!col2] ) return(temp) } test_dt(dt=iris, col1='Sepal.Length', col2='Species')
运行返回结果:
Species test 1: setosa 5.006 2: versicolor 5.936 3: virginica 6.588
解决方案
完全可以直接在data.table中使用{{}}语法,不需要逐个参数调用ensym()处理,只需要把包含{{}}的data.table查询语句整体放到rlang::inject()内部即可:
test_dt_better <- function(dt, col1, col2){ data.table::setDT(dt) temp <- rlang::inject( dt[, .(test = mean({{col1}})), by = {{col2}}] ) return(temp) }
这个写法同时兼容裸列名和字符串列名两种传参方式,和dplyr的使用体验完全一致:
- 传裸列名:
test_dt_better(iris, Sepal.Length, Species)正常运行 - 传字符串列名:
test_dt_better(iris, "Sepal.Length", "Species")正常运行
返回结果和之前的替代方案完全相同。
报错原因
{{}}是tidy eval体系的专属语法糖,本质是!!enquo(参数)的简写,只有原生支持tidy eval的函数(比如dplyr的所有数据处理函数)会自动解析这个语法。data.table本身不识别tidy eval规则,直接写{{}}会被当成普通R代码执行,自然会抛出错误。rlang::inject()的作用就是在代码正式执行前,先把包裹范围内的所有tidy eval语法(包括{{}}、!!、!!!)完成解析替换,替换后的代码再交给data.table执行,就不会出现语法不兼容的问题。
如果日常高频使用,可以写个简单的辅助函数减少重复输入:
dt_tidy <- function(expr) { rlang::inject({{ expr }}) } # 调用示例 test_dt_short <- function(dt, col1, col2){ data.table::setDT(dt) dt_tidy(dt[, .(test = mean({{col1}})), by = {{col2}}]) }
内容的提问来源于stack exchange,提问作者rafa.pereira

