我正在尝试将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
时如何扩展?
答案 0 :(得分:5)
基本上,您的问题是,当您在单个参数上调用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()
函数需要多个参数,而不仅仅是一个列表。这就是为什么要与...
一起使用的原因,因为它将扩展为多个参数。如果您想传递一个列表,则可以编写一个辅助函数,该函数可以查找这种特殊情况并适当地扩展quosure。例如
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))
答案 1 :(得分:1)
我希望我没看错,这是一个快速的解决方案: 问题是/是通过将您的逻辑查询与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))