在 tidyeval 中将单个参数作为点传递

Pass a single argument as dots in tidyeval

我试图将 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 时扩展它?

希望我没有理解错,这是一个快速的解决方案: 问题是/是通过组合由 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))

基本上你的问题是,当你在单个参数上调用 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 东西 "meant" 的真正使用方式。 ... 方法更为惯用。或者,如果您需要更多控制权,则显式调用 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))