Tidyeval Qua vs Enquo

时间:2018-10-30 15:10:02

标签: r tidyverse tidyeval

我偶然发现了这种行为,并不太了解。有人可以照亮一下吗?

我编写了以下函数,该函数给出以下错误:

let tokenSoundTrack;

function sound() {
tokenSoundTrack = 4;
}

function sound2() {
tokenSoundTrack = 2;
}

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

> 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

但是,我仍然对上述行为感到困惑,将不胜感激。

2 个答案:

答案 0 :(得分:5)

TLDR:在第一个版本中,您已经创建了一个自引用(一个指向自身的符号)。其他版本也可以使用,但是实际上您不需要在这里使用quasure和捕获参数,因为您没有在引用数据框列。这也解释了为什么quo()enquo()版本都工作相同的原因。您可以按常规方式传递参数,而不用引号,尽管用!!取消引号仍然是一个好主意,以避免任何数据掩盖错误。

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

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

在第二次尝试中,您给quasure取了另一个名字。由于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")))

现在,quoquo包含一个在现场创建矢量的调用,%in%非常清楚。

请注意,您实际上不是在实际上指数据框列。您正在传递矢量。这意味着您根本不需要任何保证,也不需要捕获传递给参数的表达式。 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">)

答案 1 :(得分:1)

当我们传递带引号的向量字符串而不是未引用的字符串时,我们需要使用syms中的rlang

MyFilter <- function(data, filtersVector) {
   filtersVector <- rlang::syms(filtersVector)
    data %>% 
      filter(Species %in% !!filtersVector)

 }

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