代码之家  ›  专栏  ›  技术社区  ›  zeehio

在tidyeval中以点形式传递单个参数

  •  6
  • zeehio  · 技术社区  · 6 年前

    我在试着包装 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::筛选器 调用将拼接我的论点,但鉴于错误信息,我想我理解它是错误的。

    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::筛选器 ?

    2 回复  |  直到 6 年前
        1
  •  5
  •   MrFlick    6 年前

    基本上你的问题是 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)
    

    函数需要多个参数,而不仅仅是一个列表。这就是为什么它要和 ... 因为这会扩展到多个参数。如果您想传入一个列表,可以编写一个helper函数来查找这个特例并正确地扩展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))
    
        2
  •  2
  •   floe    6 年前

    我希望我理解得对,这里有一个快速的解决方法: 问题是,通过将逻辑查询与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))