【问题标题】:Pass a single argument as dots in tidyeval在 tidyeval 中将单个参数作为点传递
【发布时间】:2019-04-16 06:38:27
【问题描述】:

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

【问题讨论】:

    标签: r dplyr rlang


    【解决方案1】:

    基本上,您的问题是,当您在单个参数上调用 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))
    

    【讨论】:

    • 谢谢!我现在明白了!我会考虑您关于不是真正使用enquo 的方式的建议,而是使用quos...无论如何,rlang::eval_tidy(expr) 调用实际上应该是rlang::eval_tidy(expr, env = rlang::quo_get_env(x)) 所以它是在来自x的环境中评估?
    • 当然。如果您的实际用例是使用来自 data.frame 列以外的位置的变量,则您需要捕获正确的环境。
    【解决方案2】:

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

    【讨论】:

    • 理想情况下,我想了解如何使其与 rlang 一起使用,但您的快速'n'dirty 解决方案解决了我的问题 :-)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-02
    • 2010-11-10
    • 1970-01-01
    • 1970-01-01
    • 2021-09-21
    • 2021-08-24
    相关资源
    最近更新 更多