dplyr中基于字符串的筛选 - NSE

7

我想使用dplyr的新非标准语言符号(版本>=0.6)对我的数据进行动态筛选。假设我有以下虚拟数据集:

df = data_frame(x = 1:10, y = 10:1, z = 10 * runif(10))

如果现在我想要筛选列 tofilter = "x" 中大于5的值,我知道可以这样做:

df %>% 
  filter((!!rlang::sym(tofilter)) >= 5)

问题1

如果我想要动态更改筛选器的操作符(比如说我有一个Shiny应用程序,在其中用户可以动态地selectInput确定是否要过滤大于5、等于5或小于5的值),该怎么办?

我想要做的是类似下面这样的事情:

op = ">="
val = 5
filt_expr = paste("x", op, val)
df %>% 
  filter(filt_expr)

显然,这样是不行的。我已经尝试使用rlang quosore/symbols等方法,但没有找到合适的方法来“引用”我的输入。
问题2
附加问题是,如果我想应用多个过滤器怎么办?我需要循环还是可以创建一个过滤表达式列表并一次性应用它们?
这种情况的一个例子是Shiny应用程序,用户可以输入他/她想要应用于数据的多个条件,以便我们有一个动态变化的格式列表:
filt_expr_list = list("x >= 5", "y <= 10", "z >= 2")

我们希望动态应用它们,以使输出等效于:

df %>%
  filter(x >= 5, y <= 10, z >= 2)

我觉得这在某种程度上是问题1的一个子集,因为当我知道如何正确引用参数时,我认为可以做如下操作:

filt_expr = paste0(unlist(filt_expr_list), collapse = ", ")
df %>%
  filter(filt_expr)

但是如果有更好、更清晰的方式,那将会很不错。

2个回答

10

如果我想要动态更改过滤器的操作符怎么办

您可以使用整洁评估通过取消引用表示操作符的符号来实现(请注意,我使用expr()来说明取消引用的结果):

lhs <- "foo"

# Storing the symbol `<` in `op`
op <- quote(`<`)

expr(`!!`(op)(!!sym(lhs), 5))
#> foo < 5

然而,使用普通的 R 代码在整洁评估之外完成操作会更加清晰。仅当您引用的符号代表数据框中的列即不在上下文中时,解引用才是必要的。在这种情况下,您可以将运算符存储在变量中,然后在筛选表达式中调用该变量:

# Storing the function `<` in `op`
op <- `<`

expr(op(!!sym(lhs), 5))
#> op(foo, 5)

如果我想应用多个过滤器怎么办?

您可以将表达式保存在列表中,然后使用!!!在调用中拼接它们:

filters <- list(
  quote(x >= 5),
  quote(y <= 10),
  quote(z >= 2)
)

expr(df %>% filter(!!!filters))
#> df %>% filter(x >= 5, y <= 10, z >= 2)`

注意:我之前说过,在上下文中不必取消引用变量,但如果您正在编写将数据框作为输入的函数,这样做仍然是一个好主意。由于数据框是变量,您无法预先知道它包含哪些列。列始终优先于您在环境中定义的对象。在这种情况下,这不是一个问题,因为我们正在谈论一个函数,如果在数据框中找到一个类似命名的对象,R将继续查找函数。


你说得完全正确!很棒的解决方案,谢谢!!简单总结一下,可以创建一个列表来运行多个动态过滤器,例如 filt_expr = list( quo(\>=`(!!sym("x"), 5)), quo(`<=`(!!sym("y"), 10)), quo(`>=`(!!sym("z"), 2)) )然后使用!!!运算符应用于df %>% filter(!!!filt_expr)`。 - Lorenzo Rossi
在你的列表示例中,rlang::quo似乎与quote执行相同的操作。在这种情况下,它们是否有任何区别或优先使用其中之一? - Danny
实际上,使用quo()而不是quote()expr()几乎从来没有用处。Quosures仅对于外部表达式而言才是重要的,而不是本地表达式。这就是为什么你几乎总是应该使用enquo(),但不必使用quo()。您可以在自己的上下文中构建原始表达式,并通过!!传递它们。如果该函数使用enquo(),则原始表达式将被适当地包装在quosure中。 - Lionel Henry

2
您可以这样做:

您可以实际执行此操作:

    df = data_frame(x = 1:10, y = 10:1, z = 10 * runif(10))
    op = ">="
    val = 5
    filt_expr = paste("x", op, val)

    df %>% filter(eval(parse(filt_expr)))

2
你需要指定 parse(text = filt_expr) - Erik Bülow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接