Tidyeval de vs enquo - PullRequest
       10

Tidyeval de vs enquo

0 голосов
/ 30 октября 2018

Я наткнулся на это поведение и не совсем его понимаю. Может ли кто-нибудь пролить свет?

Я написал следующую функцию, которая выдает следующую ошибку:

> 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

Однако я все еще озадачен вышеуказанным поведением, и любое объяснение будет оценено.

Ответы [ 2 ]

0 голосов
/ 30 октября 2018

TLDR: в первой версии вы создали собственную ссылку (символ, указывающий на себя). Другие версии работают, но вам на самом деле здесь не нужны выражения или захват аргументов, потому что вы не ссылаетесь на столбцы фрейма данных. Это также объясняет, почему версии 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() найти строки, в которых Species соответствует элементам filtersVector. В вашем фрейме данных нет столбца filtersVector, поэтому он ищет определение в среде quosure. Вы создали предложение с quo(), в котором записано ваше выражение (в данном случае символ filtersVector) и ваше окружение (среда вашей функции). Так что он ищет объект filtersVector, который содержит символ, ссылающийся на себя. Он вычисляется только один раз, поэтому бесконечного цикла нет, но вы фактически пытаетесь сравнить вектор с символом, что является ошибкой типа:

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

Во второй попытке вы дадите другому названию название. Теперь это работает, потому что 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")))

Теперь в предложении содержится вызов, который создает вектор на месте, который %in% прекрасно понимает.

Обратите внимание, что вы на самом деле не ссылаетесь на столбцы фрейма данных. Вы передаете векторы. Это означает, что вам вообще не нужно никаких предложений, и вам не нужно захватывать выражение, переданное в аргумент. enquo() полезен только для задержки оценки до самого конца, поэтому его можно оценить в кадре данных. Если версии 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">)
0 голосов
/ 30 октября 2018

Нам нужно использовать 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
...