R语言tidyverse中向函数传递列名作为变量的最佳实践
Tidyeval体系下R包函数的规范写法
你现在的写法可以正常运行,但存在传参逻辑不统一的问题,不符合dplyr 1.0+版本的官方最佳实践,也和tidyverse系列函数的用户使用习惯有差异。
现有写法的问题
你当前混合使用了两套tidyeval语法:
{{ }}(拥抱运算符)的设计目标是处理未求值的裸列名/表达式(比如用户直接写dateIn = a而非打引号).data[[ ]]是专门用于处理字符串形式列名的索引方式
两套语法混写虽然暂时能跑,但后续扩展功能(比如支持列选择、表达式传入)时很容易出现难以排查的求值错误,且只能支持字符串传参,不符合大多数tidyverse用户的使用直觉。
推荐写法(R包开发通用规范)
官方推荐的写法全程使用{{ }}处理列名参数,同时兼容裸列名和字符串两种传参方式,和dplyr自带函数的行为完全一致,CRAN检查也不会产生非标准求值相关的NOTE:
library(tidyverse) dat0 <- tibble( a = seq(as.Date('2022-02-10'), as.Date('2022-03-01'), by = "5 days"), b = seq(10, 40, 10) ) myCalc <- function(data, dateIn, numIn, yearOut, numOut) { # R包建议添加基础参数校验 stopifnot(is.data.frame(data)) data %>% mutate( {{ yearOut }} := lubridate::year({{ dateIn }}), {{ numOut }} := 10 * {{ numIn }} ) %>% filter( {{ numOut }} > 250 ) } # 用法1:和dplyr原生函数一致,直接传裸列名(无需引号,用户最熟悉的方式) dat1 <- myCalc(dat0, dateIn = a, numIn = b, yearOut = c, numOut = d) # 用法2:完全兼容你原来的字符串传参方式,不需要修改原有调用代码 dat2 <- myCalc(dat0, dateIn = "a", numIn = "b", yearOut = "c", numOut = "d")
两种调用方式返回的结果完全一致,都是你预期的2行4列tibble。
特殊场景选择
如果你的函数明确只接受字符串形式的列名(比如参数是批量循环生成的列名字符串),可以全程使用.data[[ ]]保持逻辑自洽,不需要混用{{ }}:
# 仅支持字符串传参的版本 myCalc_chr <- function(data, dateIn, numIn, yearOut, numOut) { stopifnot( is.data.frame(data), is.character(dateIn), is.character(numIn), is.character(yearOut), is.character(numOut) ) data %>% mutate( "{yearOut}" := lubridate::year(.data[[dateIn]]), "{numOut}" := 10 * .data[[numIn]] ) %>% filter( .data[[numOut]] > 250 ) }
注意事项
- 动态赋值列名时必须使用
:=(海象运算符),不能用常规的=,因为=左侧不支持动态传入的列名参数 - 不需要在管道中显式写
.占位符,除非你需要在函数参数的非第一位置引用传入的数据集,多余的.会降低代码可读性 - 不需要使用早期tidyeval版本的
enquo()+!!写法,{{ }}是官方封装后的简化语法,已经自动处理了环境绑定、求值上下文等容易出错的细节,更适合R包开发使用 - 如果需要支持多列传入,可以配合
...和all_of()/any_of()选择器使用,完全符合tidyselect规范
内容的提问来源于stack exchange,提问作者greengrass62
相关产品推荐
相关产品推荐

