如何将表示表达式的字符串传递给dplyr 0.7动词?

5
我想了解如何将表示表达式的字符串传递给dplyr,以便在数据框中对列上的表达式求值。关于这个主题的主要说明文档涵盖了传递quosure,但根本没有讨论字符串。
显然,当表示表达式时,quosure比字符串更安全、更清晰,所以当quosure可以代替字符串时,我们应该避免使用字符串。然而,在与R生态系统外的工具(如JavaScript或YAML配置文件)一起工作时,人们通常必须使用字符串而不是quosure。
例如,假设我想要一个使用用户/调用者传入的表达式进行分组计数的函数。正如预期的那样,以下代码不起作用,因为dplyr使用非标准评估来解释group_by的参数。
library(tidyverse)

group_by_and_tally <- function(data, groups) {
  data %>%
    group_by(groups) %>%
    tally()
}

my_groups <- c('2 * cyl', 'am')
mtcars %>%
  group_by_and_tally(my_groups)
#> Error in grouped_df_impl(data, unname(vars), drop): Column `groups` is unknown

在dplyr 0.5中,我们使用标准评估,例如group_by_(.dots = groups)来处理这种情况。现在下划线动词已被弃用,那么在dplyr 0.7中我们应该如何处理这种情况?
对于仅为列名的表达式,我们可以使用这个问题的解决方案,但对于像2 * cyl这样不仅是列名的更复杂的表达式则不适用。
3个回答

11

需要注意的是,在这个简单的例子中,我们可以控制表达式的创建方式。因此,传递表达式的最佳方法是直接使用quos()构造并传递quosures:

library(tidyverse)
library(rlang)

group_by_and_tally <- function(data, groups) {
  data %>%
    group_by(UQS(groups)) %>%
    tally()
}

my_groups <- quos(2 * cyl, am)
mtcars %>%
  group_by_and_tally(my_groups)
#> # A tibble: 6 x 3
#> # Groups:   2 * cyl [?]
#>   `2 * cyl`    am     n
#>       <dbl> <dbl> <int>
#> 1         8     0     3
#> 2         8     1     8
#> 3        12     0     4
#> 4        12     1     3
#> 5        16     0    12
#> 6        16     1     2

然而,如果我们从外部来源以字符串形式接收表达式,则可以先解析这些表达式,将它们转换为 quosures:

my_groups <- c('2 * cyl', 'am')
my_groups <- my_groups %>% map(parse_quosure)
mtcars %>%
  group_by_and_tally(my_groups)
#> # A tibble: 6 x 3
#> # Groups:   2 * cyl [?]
#>   `2 * cyl`    am     n
#>       <dbl> <dbl> <int>
#> 1         8     0     3
#> 2         8     1     8
#> 3        12     0     4
#> 4        12     1     3
#> 5        16     0    12
#> 6        16     1     2

如果我们从一个提供字符串形式表达式的外部来源获取表达式,那么我们应该这样做 - 否则我们应该直接在R源代码中进行quo化。


是的,但我对Hadley在某些地方提到的否定模糊感感到有些担心。 - Paul
嗯,目前还没有遇到任何问题。我认为只要在dplyr动词内部使用!!!!!,你就应该没问题了。 - yeedle
你可能是对的。就我个人而言,我发现这种转换相当令人困惑,当我使用 UQUQS 时,我更容易知道自己在做什么。 - Paul
请注意,使用点来表示函数参数会导致字符串用例出现问题。针对所提出的编辑,请做出回应。 - Paul
用户输入字符串和字符向量在Shiny应用程序中非常常见。我之前不知道rlang::parse_exprrlang::parse_quosure。谢谢!我已经将您的建议应用于我在https://groups.google.com/forum/#!topic/manipulatr/UyzWc-s_bos上获得的Shiny应用程序输入。 - Vincent

5

使用字符串很诱人,但通常最好使用表达式。现在您已经有了准引用,可以以灵活的方式轻松构建表达式:

lhs <- "cyl"
rhs <- "disp"
expr(!!sym(lhs) * !!sym(rhs))
#> cyl * disp

vars <- c("cyl", "disp")
expr(sum(!!!syms(vars)))
#> sum(cyl, disp)

1
我理解你的意思,但这并不能证明使用表达式总是更好。当调用者是R生态系统之外的用户,并且该用户想要传递比仅仅列名更复杂的表达式时,这种解决方案就不起作用了。 - Paul
1
是的,如果代码来自于R之外的地方,比如任何源文件,解析它是可以并且有必要的。然后你可以使用parse_expr()或parse_quosure()来进行解析。 - Lionel Henry
顺便说一句,这个笼统的陈述是有道理的,因为使用字符串编程是糟糕的 R 代码的主要来源,人们将使用您的帖子来使用 tidyverse 工具进行编程。 - Lionel Henry
如果你没有为人们提供一个好的方式来完成他们需要做的事情,他们会想出一种不好的方式来完成它(并且责怪你)。我将进行一些编辑,以鼓励负责任地使用这种技术。 - Paul
1
我不会在这里详细说明,但它是非结构化的。因此,您最终需要使用临时代码来对其进行结构化(我需要在那里添加逗号吗?我需要转义这个字符吗?等等)。 - Lionel Henry
显示剩余5条评论

2

如果您遇到此类问题,那么friendlyeval软件包可以帮助您解决:

library(tidyverse)
library(friendlyeval)

group_by_and_tally <- function(data, groups) {
  data %>%
    group_by(!!!friendlyeval::treat_strings_as_exprs(groups)) %>%
    tally()
}

my_groups <- c('2 * cyl', 'am')
mtcars %>%
  group_by_and_tally(my_groups)

# # A tibble: 6 x 3
# # Groups:   2 * cyl [?]
# `2 * cyl`    am     n
# <dbl> <dbl> <int>
# 1         8     0     3
# 2         8     1     8
# 3        12     0     4
# 4        12     1     3
# 5        16     0    12
# 6        16     1     2

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