Tidyeval quo vs enquo

Tidyeval quo vs enquo

我偶然发现了这种行为,但不是很理解。有人可以解释一下吗?

我编写了以下函数,但出现了以下错误:

> MyFilter <- function(data, filtersVector) {
    filtersVector <- quo(filtersVector)
    result <- data %>% filter(Species %in% !!filtersVector)
    result
  }

> MyFilter(iris, c("setosa", "virginica"))
Error in filter_impl(.data, quo) : 
Evaluation error: 'match' requires vector arguments.

但是,如果我按以下方式修改它,它会按预期工作:

> MyFilter <- function(data, filtersVector) {
    otherName <- quo(filtersVector)
    result <- data %>% filter(Species %in% !!otherName)
    result
  }

> MyFilter(iris, c("setosa", "virginica"))
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
1            5.1         3.5          1.4         0.2    setosa
2            4.9         3.0          1.4         0.2    setosa
3            4.7         3.2          1.3         0.2    setosa
4            4.6         3.1          1.5         0.2    setosa
5            5.0         3.6          1.4         0.2    setosa
6            5.4         3.9          1.7         0.4    setosa

我也意识到在一个函数中我应该使用 enqou 来代替并且它工作正常。

> MyFilter <- function(data, filtersVector) {
        filtersVector<- enquo(filtersVector)
        result <- data %>% filter(Species %in% !!filtersVector)
        result
      }

> MyFilter(iris, c("setosa", "virginica"))
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
1            5.1         3.5          1.4         0.2    setosa
2            4.9         3.0          1.4         0.2    setosa
3            4.7         3.2          1.3         0.2    setosa
4            4.6         3.1          1.5         0.2    setosa
5            5.0         3.6          1.4         0.2    setosa
6            5.4         3.9          1.7         0.4    setosa

不过,我仍然对上述行为感到困惑,如有任何解释,我们将不胜感激。

当我们传递带引号的向量字符串而不是不带引号的

时,我们需要使用 rlang 中的 syms
MyFilter <- function(data, filtersVector) {
   filtersVector <- rlang::syms(filtersVector)
    data %>% 
      filter(Species %in% !!filtersVector)

 }

out <- MyFilter(iris, c("setosa", "virginica"))
dim(out)
#[1] 100   5

TLDR:在第一个版本中,您创建了一个自引用(指向自身的符号)。其他版本可以工作,但实际上您不需要在此处使用 quosures 或捕获参数,因为您没有引用数据框列。这也解释了为什么 quo()enquo() 版本工作相同。您可以以正常方式传递参数,无需任何引号,但使用 !! 取消引号以避免任何数据屏蔽错误仍然是个好主意。

您可以在 filter() 调用周围使用 qq_show() 来发现语法差异:

MyFilter <- function(data, filtersVector) {
  filtersVector <- quo(filtersVector)

  rlang::qq_show(
    result <- data %>% filter(Species %in% !!filtersVector)
  )
}

MyFilter(iris, c("setosa", "virginica"))
#> result <- data %>% filter(Species %in% (^filtersVector))

所以这里我们要求 filter() 找到 SpeciesfiltersVector 的元素匹配的行。您的数据框中没有 filtersVector 列,因此它会在 quosure 环境中查找定义。您已经使用 quo() 创建了一个 quosure,它记录了 您的 表达式(在本例中是一个符号 filtersVector)和 您的环境(你的功能的环境)。所以它查找一个 filtersVector 对象,其中包含一个引用自身的符号。它只被评估一次,所以没有无限循环,但你实际上是在尝试将向量与符号进行比较,这是一个类型错误:

"setosa" %in% quote(filtersVector)
#> Error in match(x, table, nomatch = 0L) :
#> 'match' requires vector arguments

在你的第二次尝试中,你给 quosure 起了另一个名字。它现在可以工作了,因为 filtersVector,在你的函数环境中,仍然代表传递给它的参数(向量)。

第三次尝试,这次你用enquo()enquo() 不是捕捉你的表情和你的环境,而是捕捉你的功能用户的表情和环境。让我们再次使用 qq_show() 来查看差异:

MyFilter <- function(data, filtersVector) {
  filtersVector<- enquo(filtersVector)

  rlang::qq_show(
    data %>% filter(Species %in% !!filtersVector)
  )
}

MyFilter(iris, c("setosa", "virginica"))
#> data %>% filter(Species %in% (^c("setosa", "virginica")))

现在,quosure 包含一个当场创建向量的调用,%in% 完全可以理解。

请注意您实际上并没有引用数据框列。你正在传递向量。这意味着您根本不需要任何 quosure,也不需要捕获传递给参数的表达式。 enquo() 仅对 delay 计算直到最后有用,因此可以在数据帧内进行计算。如果 quo()enquo() 版本产生相同的结果,这是一个很好的迹象,你根本不需要任何引用。由于不需要它们,让我们通过删除等式的等式来简化函数:

MyFilter <- function(data, filtersVector) {
  data %>% filter(Species %in% filtersVector)
}

MyFilter(iris, c("setosa", "virginica"))
#> # A tibble: 100 x 5
#>    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#>           <dbl>       <dbl>        <dbl>       <dbl> <fct>  
#>  1          5.1         3.5          1.4         0.2 setosa 
#>  2          4.9         3            1.4         0.2 setosa 
#>  3          4.7         3.2          1.3         0.2 setosa 
#>  4          4.6         3.1          1.5         0.2 setosa 
#>  5          5           3.6          1.4         0.2 setosa 
#>  6          5.4         3.9          1.7         0.4 setosa 
#>  7          4.6         3.4          1.4         0.3 setosa 
#>  8          5           3.4          1.5         0.2 setosa 
#>  9          4.4         2.9          1.4         0.2 setosa 
#> 10          4.9         3.1          1.5         0.1 setosa 
#> # ... with 90 more rows

有效!但是,如果数据框包含一个 filtersVector 列,会发生什么情况呢?它优先于环境中的对象:

iris %>%
  mutate(filtersVector = "parasite vector") %>%
  MyFilter(c("setosa", "virginica"))
#> # A tibble: 0 x 6
#> # ... with 6 variables: Sepal.Length <dbl>, Sepal.Width <dbl>,
#> #   Petal.Length <dbl>, Petal.Width <dbl>, Species <fct>, filtersVector <chr>

所以取消引用仍然是个好主意,因为这样会立即计算向量并将其粘贴在过滤器表达式中。它不能再被列屏蔽。内联显示为 qq_show():

MyFilter <- function(data, filtersVector) {
  rlang::qq_show(
    data %>% filter(Species %in% !!filtersVector)
  )
}
MyFilter(iris2, c("setosa", "virginica"))
#> data %>% filter(Species %in% <chr: "setosa", "virginica">)