【问题标题】:Using tidy eval for multiple, arbitrary filter conditions对多个任意过滤条件使用 tidy eval
【发布时间】:2021-12-03 13:57:50
【问题描述】:

我想使用整洁的评估来编写多个完全灵活的过滤条件。一个相关但不太复杂的问题已解决in this Stackoverflow Question。以下代码(这是对提到的其他问题的改编)正在运行。它对 gapminder 数据集应用两个过滤条件,并返回过滤后的数据。

library(tidyverse)
library(gapminder)

my_filter <- function(df, cols, vals){    
  paste_filter <- function(x, y) quo(!!sym(x) %in% {{y}})
  fp <- pmap(list(cols, vals), paste_filter)
  filter(df, !!!fp)
}

cols <- list("country", "year")
vals = list(c("Albania", "France"), c(2002, 2007))
gapminder %>% my_filter(cols, vals) 

问题:到目前为止,此解决方案仅限于一种类型的过滤器运算符 (%in%)。我想扩展这种方法以接受任意类型的运算符(==%in%&gt;,...)。预期的函数my_filter 应该处理以下内容:

cols <- list("country", "year")
ops <- list("%in%", ">=")
vals = list(c("Albania", "France"), 2007))
gapminder %>% my_filter(cols, ops, vals)

我脑海中的用例是闪亮的应用程序。使用这样的功能,我们可以更轻松地让用户对数据集的变量设置任意过滤条件。

【问题讨论】:

    标签: r shiny tidyverse tidyeval


    【解决方案1】:

    创建调用列表并将它们拼接起来:

    library(dplyr)
    library(gapminder)
    
    cols <- list("country", "year")
    ops <- list("%in%", ">=")
    vals <- list(c("Albania", "France"), 2007)
    
    # Assumes LHS is the name of a variable and OP is
    # the name of a function
    op_call <- function(op, lhs, rhs) {
      call(op, sym(lhs), rhs)
    }
    
    my_filter <- function(data, cols, ops, vals) {
      exprs <- purrr::pmap(list(ops, cols, vals), op_call)
      data %>% dplyr::filter(!!!exprs)
    }
    
    gapminder %>% my_filter(cols, ops, vals)
    #> # A tibble: 2 × 6
    #>   country continent  year lifeExp      pop gdpPercap
    #>   <fct>   <fct>     <int>   <dbl>    <int>     <dbl>
    #> 1 Albania Europe     2007    76.4  3600523     5937.
    #> 2 France  Europe     2007    80.7 61083916    30470.
    

    在这里我们不必担心范围问题,因为 (a) 列名假定在数据掩码中定义,(b) 值按值传递并在创建的调用中内联,以及 (c ) 这些函数被假定为二元运算符,并且很少重新定义。

    要允许自定义用户功能,我们可以采用两种方法。首先,我们可以使用new_quosure() 手动创建一个环境并创建quosures:

    op_call <- function(op, lhs, rhs, env = caller_env()) {
      new_quosure(call(op, sym(lhs), rhs), env)
    }
    
    my_filter <- function(data, cols, ops, vals, env = caller_env()) {
      exprs <- purrr::pmap(list(ops, cols, vals), op_call, env)
      data %>% dplyr::filter(!!!exprs)
    }
    
    gapminder %>% my_filter(cols, ops, vals)
    
    local({
      my_op <- `%in%`
      gapminder %>% my_filter(cols, list("my_op", ">="), vals)
    })
    #> # A tibble: 2 × 6
    #>   country continent  year lifeExp      pop gdpPercap
    #>   <fct>   <fct>     <int>   <dbl>    <int>     <dbl>
    #> 1 Albania Europe     2007    76.4  3600523     5937.
    #> 2 France  Europe     2007    80.7 61083916    30470.
    

    另一种可能更简单的方法是允许调用包含内联函数。为此,请使用rlang::call2() 而不是base::call()

    op_call <- function(op, lhs, rhs) {
      call2(op, sym(lhs), rhs)
    }
    
    my_filter <- function(data, cols, ops, vals) {
      exprs <- purrr::pmap(list(ops, cols, vals), op_call)
      data %>% dplyr::filter(!!!exprs)
    }
    
    local({
      my_op <- `%in%`
      gapminder %>% my_filter(cols, list(my_op, ">="), vals)
    })
    #> # A tibble: 2 × 6
    #>   country continent  year lifeExp      pop gdpPercap
    #>   <fct>   <fct>     <int>   <dbl>    <int>     <dbl>
    #> 1 Albania Europe     2007    76.4  3600523     5937.
    #> 2 France  Europe     2007    80.7 61083916    30470.
    

    内联函数的缺点是这将阻止优化和传输到其他 dplyr 后端。

    【讨论】:

    • 由于您将它与基本运算符一起使用,因此无需担心特定的范围问题。为了在技术上更正确,或者允许用户定义比较函数,您可以将调用环境作为参数并使用new_quosure() 创建quosures,而不是创建裸表达式。
    • 这个简单又漂亮。非常感谢!
    • 您能否提供代码如何在技术上更正确?
    • 来吧,添加了两种允许自定义用户功能的方法。
    猜你喜欢
    • 2018-08-11
    • 2019-09-21
    • 1970-01-01
    • 1970-01-01
    • 2019-03-29
    • 1970-01-01
    • 2019-04-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多