Передача одного аргумента в виде точек в tidyeval - PullRequest
0 голосов
/ 13 ноября 2018

Я пытаюсь обернуть dplyr::filter в функцию, где, когда существует более одного filter условия, они передаются как вектор или список.Посмотрите этот минимальный пример:

filter_wrap <- function(x, filter_args) {
  filter_args_enquos <- rlang::enquos(filter_args)
  dplyr::filter(x, !!!filter_args_enquos)
}

Когда есть одно условие, я могу заставить его работать:

data(iris)
message("Single condition works:")
expected <- dplyr::filter(iris, Sepal.Length > 5)
obtained <- filter_wrap(iris, filter_args = Sepal.Length > 5)
stopifnot(identical(expected, obtained))

Когда я пытаюсь пройти более одного условия, у меня возникает проблема.Я ожидал, что оператор !!! в вызове dplyr::filter склеит мои аргументы, но, учитывая сообщение об ошибке, я думаю, что я неправильно его понимаю.

message("Multiple conditions fail:")
expected <- dplyr::filter(iris, Sepal.Length > 5, Petal.Length > 5)
obtained <- filter_wrap(iris, c(Sepal.Length > 5, Petal.Length > 5))
# Error in filter_impl(.data, quo) : Result must have length 150, not 300
# Called from: filter_impl(.data, quo)
stopifnot(identical(expected, obtained))

Использование списка действительно изменяет сообщение об ошибке:

obtained <- filter_wrap(iris, list(Sepal.Length > 5, Petal.Length > 5))
# Error in filter_impl(.data, quo) : 
#  Argument 2 filter condition does not evaluate to a logical vector
# Called from: filter_impl(.data, quo)

Я не хочу использовать ..., поскольку у моей функции будут другие аргументы, и я, возможно, захочу использовать точки для чего-то другого.

Как я могу развернуть свой filter_args аргумент при передаче его на dplyr::filter?

Ответы [ 2 ]

0 голосов
/ 13 ноября 2018

В основном ваша проблема заключается в том, что когда вы вызываете enquos() для одного параметра, вы также цитируете вызов list() (который является одним вызовом). Итак, в основном вы создаете

filter_args_enquos <- quo(list(Sepal.Length > 5, Petal.Length > 5))

и при звонке

dplyr::filter(iris, !!!filter_args_enquos)

это то же самое, что и

dplyr::filter(iris, list(Sepal.Length > 5, Petal.Length > 5))

, который не является допустимым синтаксисом dplyr. !!! должен работать с правильным спискообразным объектом, а не безоценочным вызовом списка, обратите внимание, что это будет работать

filter_args_enquos <- list(quo(Sepal.Length > 5), quo(Petal.Length > 5))
dplyr::filter(iris, !!!filter_args_enquos)

потому что здесь мы на самом деле оцениваем список и цитируем только то, что находится внутри списка. Это в основном тип объекта, созданного enquos при использовании ...

filter_wrap <- function(x, ...) {
  filter_args_enquos <- rlang::enquos(...)
  dplyr::filter(x, !!!filter_args_enquos)
}
filter_wrap(iris, Sepal.Length > 5, Petal.Length > 5)

Функция enquos() ожидает несколько параметров, а не только один список. Вот почему он предназначен для использования с ..., потому что это расширится до нескольких параметров. Если вы хотите передать список, вы можете написать вспомогательную функцию, которая может искать этот особый случай и правильно расширять предложение. Например

expand_list_quos <- function(x) {
  expr <- rlang::quo_get_expr(x)
  if (expr[[1]]==as.name("list")) {
    expr[[1]] <- as.name("quos")
    return(rlang::eval_tidy(expr, env = rlang::quo_get_env(x)))
  } else {
    return(x)
  }
}

Тогда вы можете использовать его с

filter_wrap <- function(x, filter_args) {
  filter_args <- expand_list_quos(rlang::enquo(filter_args))
  dplyr::filter(x, !!!filter_args)
}

и оба они будут работать

filter_wrap(iris, Petal.Length > 5)
filter_wrap(iris, list(Sepal.Length > 5, Petal.Length > 5))

но это не совсем то, что этот enquo материал «предназначен» для использования. Метод ... гораздо более идиоматичен. Или вызовите quos() явно, если вам нужно больше контроля

filter_wrap <- function(x, filter_args) {
  dplyr::filter(x, !!!filter_args)
}
filter_wrap(iris, quo(Petal.Length > 5))
filter_wrap(iris, quos(Sepal.Length > 5, Petal.Length > 5))
0 голосов
/ 13 ноября 2018

Надеюсь, я правильно понял, вот быстрое и грязное решение: Проблема была в том, что, комбинируя ваши логические запросы, объединенные символом c, вы получаете вектор, равный x * n сравнений.

filter_wrap <- function(x, filter_args) {
     filter_args_enquos <- rlang::enquos(filter_args)
     LogVec <- rowwise(x) %>% mutate(LogVec = all(!!!filter_args_enquos)) %>%             
     pull(LogVec)
     dplyr::filter(x, LogVec)
}

expected <- dplyr::filter(iris, Sepal.Length > 5, Petal.Length > 5)
obtained <- filter_wrap(iris, c(Sepal.Length > 5, Petal.Length > 5))    

stopifnot(identical(expected, obtained))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...