【问题标题】:Error when using curly-curly (`{{ }}`) operator with `if` clause使用带有 `if` 子句的卷曲 (`{{ }}`) 运算符时出错
【发布时间】:2023-04-14 11:24:01
【问题描述】:

我很难理解如何使用{{ }} 运算符在自定义函数中传递裸变量名。将运算符与 if 子句结合使用时出现错误。

此功能有效:

f <- function(.data, .vars=NULL){
  require(dplyr)
  df = select(.data, {{ .vars }})
  print(head(df))
}

f(iris, c(Species, Sepal.Length))
#> Loading required package: dplyr
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
#>   Species Sepal.Length
#> 1  setosa          5.1
#> 2  setosa          4.9
#> 3  setosa          4.7
#> 4  setosa          4.6
#> 5  setosa          5.0
#> 6  setosa          5.4

reprex package (v2.0.1) 于 2021 年 12 月 20 日创建

如果我尝试添加 if 子句,则会引发错误:

f <- function(.data, .vars=NULL){
  require(dplyr)
  if(!is.null(.vars)) df = select(.data, {{ .vars }})
  else df = .data
  print(head(df))
}

f(iris, c(Species, Sepal.Length))
#> Loading required package: dplyr
#> 
#> Attaching package: 'dplyr'
#> The following objects are masked from 'package:stats':
#> 
#>     filter, lag
#> The following objects are masked from 'package:base':
#> 
#>     intersect, setdiff, setequal, union
#> Error in f(iris, c(Species, Sepal.Length)): object 'Species' not found

reprex package (v2.0.1) 于 2021 年 12 月 20 日创建

我错过了什么?

【问题讨论】:

    标签: r rlang


    【解决方案1】:

    我认为最简单的解释是当.vars 不是NULL 时,R 会将值(在您的示例中:c(Species, Sepal.Length))解释为变量向量,并在您的环境中查找这些变量。由于您没有任何名为 Species 的变量,因此会引发错误。

    你可以这样修复它:

    library(dplyr)
    
    
    f <- function(.data, .vars = NULL){
      
      vars <- enquo(.vars)
      
      if(!rlang::quo_is_null(vars)) df = select(.data, !!vars)
      else df = .data
      
      print(head(df))
      
    }
    
    f(iris) 
    f(iris, c(Species, Sepal.Length))
    

    请注意,{{x}} 实际上是 !!enquo(x) 的简写。

    细化(更新)

    • 当您不使用if 时,唯一使用.vars 的地方是在dplyr::select(.data, {{.vars}}) 内部。在这种情况下,.vars 中的变量名称被解释为数据帧.data 中的变量。

    • 当您添加if 语句时,.vars 被评估为您环境中的变量。由于它们在您的环境中不存在,您会收到错误消息。

    这称为数据屏蔽。 Here 是一个 关于它的好文章。

    【讨论】:

    • 只是想看看我是否明白这一点:当我不使用 if 子句时,它会起作用,因为 {{ 运算符会分解然后强制评估;当我使用if 子句时,{{ 运算符不会消除值?
    • 让我详细说明:当您不使用if 时,唯一使用.vars 的地方是在dplyr::select(.data, {{.vars}}) 内部。在这种情况下,.vars 中的变量名称被解释为数据帧.data 中的变量。添加if 语句时,.vars 被评估为环境中的变量。由于它们在您的环境中不存在,因此您会收到错误消息。这称为数据屏蔽。这是一篇关于它的好文章:rlang.r-lib.org/reference/topic-data-mask.html 希望我现在更清楚了。如果您还有其他问题,请告诉我。
    • 明白,谢谢!可能评论比答案本身更有用,请考虑将其包含在答案中。
    • 不客气。我现在已将其添加到答案中:)
    【解决方案2】:

    @jpiversen 的回答和解释是正确的,但这里有一个更简单的函数修复方法。而不是寻找NULL的默认值,只需检查.vars是否丢失:

    library(dplyr)
    
    f <- function(.data, .vars){
      if(!missing(.vars)) df = select(.data, {{ .vars }})
      else df = .data
      print(head(df))
    }
    
    f(iris, c(Species, Sepal.Length))
    

    顺便说一句,我还从你的函数中删除了require(dplyr)。由于更改搜索列表的副作用,在函数中使用它通常是一个坏主意。如果您不确定它是否可用,请使用 requireNamespace("dplyr") 和使用 dplyr:: 的前缀函数。

    【讨论】:

      最近更新 更多