在tidyeval

时间:2018-11-13 12:16:59

标签: r dplyr rlang

我正在尝试将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 :(得分: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))