对多个任意过滤条件使用 tidy eval
Using tidy eval for multiple, arbitrary filter conditions
我想使用 tidy evaluation 编写多个完全灵活的过滤条件。一个相关但不太复杂的问题已解决 。以下代码(改编自上述其他问题)正在运行。它对gapminder数据集应用了两个过滤条件,returns过滤后的数据。
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%
、>
、...)。预期的功能 my_filter
应该处理以下内容:
cols <- list("country", "year")
ops <- list("%in%", ">=")
vals = list(c("Albania", "France"), 2007))
gapminder %>% my_filter(cols, ops, vals)
我脑海中的用例是闪亮的应用程序。使用这样的功能,我们可以更方便地让用户对数据集的变量设置任意的过滤条件。
创建调用列表并将它们拼接在:
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 后端。
我想使用 tidy evaluation 编写多个完全灵活的过滤条件。一个相关但不太复杂的问题已解决
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%
、>
、...)。预期的功能 my_filter
应该处理以下内容:
cols <- list("country", "year")
ops <- list("%in%", ">=")
vals = list(c("Albania", "France"), 2007))
gapminder %>% my_filter(cols, ops, vals)
我脑海中的用例是闪亮的应用程序。使用这样的功能,我们可以更方便地让用户对数据集的变量设置任意的过滤条件。
创建调用列表并将它们拼接在:
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()
:
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 后端。