I am trying to wrap dplyr::filter
within a function where when there is more than one filter
condition, then they are passed as a vector or list. See this minimal example:
filter_wrap <- function(x, filter_args) {
filter_args_enquos <- rlang::enquos(filter_args)
dplyr::filter(x, !!!filter_args_enquos)
}
When there is a single condition I can make it work:
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))
When I try to pass more than one condition I get a problem. I was expecting that the !!!
operator in the dplyr::filter
call would splice my arguments but given the error message I guess I am understanding it wrong.
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))
Using a list does change the error message:
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)
I don't want to use ...
as my function will have other arguments and I may want to use dots for something else.
How can I expand my filter_args
argument when passing it to dplyr::filter
?
Basically your problem is that when you call enquos()
on the single parameter, you are also quoting the list()
call (which is a single call). So basically you are creating
filter_args_enquos <- quo(list(Sepal.Length > 5, Petal.Length > 5))
and when you call
dplyr::filter(iris, !!!filter_args_enquos)
that's the same as
dplyr::filter(iris, list(Sepal.Length > 5, Petal.Length > 5))
which isn't valid dplyr syntax. The !!!
needs to work on a proper list-like object, not an un-evaluated call to a list, note that this would work
filter_args_enquos <- list(quo(Sepal.Length > 5), quo(Petal.Length > 5))
dplyr::filter(iris, !!!filter_args_enquos)
because here we are actually evaluating the list, and only quoting the things inside the list. This is basically the type of object created by enquos when using ...
filter_wrap <- function(x, ...) {
filter_args_enquos <- rlang::enquos(...)
dplyr::filter(x, !!!filter_args_enquos)
}
filter_wrap(iris, Sepal.Length > 5, Petal.Length > 5)
The enquos()
function expects multiple parameters, not just a single list. That's why it's meant to be used with ...
because that will expand to multiple parameters. If you want to possibly pass in a list, you can write a helper function that can look for this special case and expand the quosure properly. For example
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)
}
}
Then you can use it with
filter_wrap <- function(x, filter_args) {
filter_args <- expand_list_quos(rlang::enquo(filter_args))
dplyr::filter(x, !!!filter_args)
}
and both of these will work
filter_wrap(iris, Petal.Length > 5)
filter_wrap(iris, list(Sepal.Length > 5, Petal.Length > 5))
but this is not really the way this enquo
stuff is "meant" to be used. The ...
method is much more idiomatic. Or calling quos()
explicitly if you need more control
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))
I hope I understood it right, here is a quick'n'dirty solution: The problem is / was that by combining your logic queries combined by c which results in a vector as long as x * n of comparisons.
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))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With