用dplyr :: filter“匹配任何内容”的正确方法是什么?

时间:2018-10-26 20:03:56

标签: r dplyr tidyeval

根据过滤器准则的值,以编程方式使用dplyr::filter来匹配任何值的正确方法是什么?

例如,我想写

my_filter(df, some_var == 1, another_var == 'any')

并使其返回与

相同的结果
filter(df, some_var == 1))

也就是说,特殊值'any'的意思是“完全不要对此变量进行过滤”。

我当时正在考虑制作一个使用省略号...的包装器,并删除所有具有特殊值但由于dplyr的语义而无法正常工作的参数。简单的论点和tidyeval的保证。

2 个答案:

答案 0 :(得分:2)

在功能级别而不是在元编程方面,这种事情更自然。例如,您可以创建自己的==版本,将"any"视为特殊值:

my_equals <- function(x, y) {
  if (y == "any") {
    TRUE
  } else {
    x == y
  }
}

然后您可以在filter()中使用它:

filter(mtcars, my_equals(cyl, "6"), my_equals(am, "1"))
#>    mpg cyl disp  hp drat    wt  qsec vs am gear carb
#> 1 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
#> 2 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
#> 3 19.7   6  145 175 3.62 2.770 15.50  0  1    5    6

filter(mtcars, my_equals(cyl, "6"), my_equals(am, "any"))
#>    mpg cyl  disp  hp drat    wt  qsec vs am gear carb
#> 1 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
#> 2 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
#> 3 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
#> 4 18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
#> 5 19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
#> 6 17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
#> 7 19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6

如果您真的想使用==而不是普通函数,则可以捕获等价物并修改其环境,以便==被您的函数掩盖。幸运的是,这很容易做到:

library("rlang")

quo_mask_equals <- function(quo) {
  # Create a child of the quosure environment which contains a binding
  # that masks `==` with our own function:
  env <- env(quo_get_env(quo), `==` = my_equals)
  quo_set_env(quo, env)
}

my_filter <- function(.data, ...) {
  quos <- lapply(enquos(...), quo_mask_equals)
  filter(.data, !!!quos)
}

my_filter(mtcars, cyl == "6", am == "1")
my_filter(mtcars, cyl == "6", am == "any")
#> *Same results as above*

但是,我不建议编写或使用这种UI,因为它与==的常规R语义不兼容。我至少会使用一个特殊的哨兵值,而不是一个很自然地出现在数据中的值:

ANY <- function() structure(list(), class = "my_any")

my_equals <- function(x, y) {
  if (inherits(x, "my_any") || inherits(y, "my_any")) {
    TRUE
  } else {
    x == y
  }
}

my_filter(mtcars, cyl == "6", am == ANY())

答案 1 :(得分:0)

我找到了一种用tidyeval来做到这一点的方法,尽管在我看来这是一个hack,因为它需要将裸参作为字符串进行比较:

# Like dplyr::filter, but with a special value that means “match anything”.
# If the RHS of any filter expression is 'all', that expression has no effect.
# For example:
#    my_filter(data, some_var == 'all')
# would return all rows.
my_filter = function(data, ...) {
  args = discard(quos(...), function(x) endsWith(quo_name(x), "\"all\""))
  data %>% filter(!!! args)
}