【问题标题】:How to pass strings denoting expressions to dplyr 0.7 verbs?如何将表示表达式的字符串传递给 dplyr 0.7 动词?
【发布时间】:2017-11-19 11:42:27
【问题描述】:

我想了解如何将表示表达式的字符串传递给 dplyr,以便将字符串中提到的变量作为数据帧中列上的表达式进行评估。该主题的main vignette 涵盖了quosures 的传递,根本不讨论字符串。

很明显,在表示表达式时,quosures 比字符串更安全、更清晰,所以当可以使用quosures 代替时,我们当然应该避免使用字符串。但是,在使用 R 生态系统之外的工具(例如 javascript 或 YAML 配置文件)时,通常必须使用字符串而不是 quosures。

例如,假设我想要一个使用用户/调用者传入的表达式进行分组计数的函数。正如预期的那样,以下代码不起作用,因为 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 中我们应该如何做这种事情呢?

在只是列名的表达式的特殊情况下,我们可以使用this question 的解决方案,但它们不适用于像2 * cyl 这样不只是列名的更复杂的表达式。

【问题讨论】:

    标签: r dplyr rlang tidyeval


    【解决方案1】:

    请务必注意,在这个简单的示例中,我们可以控制表达式的创建方式。所以传递表达式的最好方法是直接使用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 源代码中使用 quosures。

    【讨论】:

    • 是的,但我对 Hadley 在某些地方提到的含糊不清的否定感到偏执。
    • 嗯,还没有遇到任何问题。我认为只要在 dplyr 动词中使用 !!!!! 就可以了。
    • 你可能是对的。就我个人而言,我发现这种过渡非常令人困惑,而且当我使用 UQUQS 时,我发现更容易知道自己在做什么。
    • 响应建议的编辑,请注意使用点表示函数参数会导致字符串用例中断。
    • 字符串和字符向量形式的用户输入在 Shiny 应用中非常常见。我不知道rlang::parse_exprrlang::parse_quosure。谢谢!我将您的建议应用于输入我在groups.google.com/forum/#!topic/manipulatr/UyzWc-s_bos 的闪亮应用程序中获得的输入
    【解决方案2】:

    使用字符串很诱人,但使用表达式几乎总是更好。现在您有了 quasiquotation,您可以轻松地以灵活的方式构建表达式:

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

    【讨论】:

    • 我理解表达式优于字符串的偏好,但它并不涵盖所有用例。如果表达式不是来自程序员键入 R 源代码怎么办?如果它们来自 CSV、YAML/JSON 配置文件或来自网站的用户输入表单数据怎么办?
    • 我认为@lionel 的观点是您可以将字符串转换为syms,然后您可以使用它来构建一个quosure。
    • 再次,我明白了,但这并不能证明使用表达式总是更好的笼统陈述是合理的。当调用者是 R 生态系统之外的用户并且该用户想要传递他们自己的表达式时,此解决方案不起作用,这比列名更复杂。
    • 是的,如果代码来自 R 外部,就像在任何源文件中一样,可以并且需要解析 ;) 然后您可以使用 parse_expr() 或 parse_quosure()。
    • 这里就不细说了,但它是非结构化的。因此,您最终会使用临时代码来构建它(我是否需要在此处添加逗号,是否需要转义此字符等)
    【解决方案3】:

    包裹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
    

    【讨论】:

      猜你喜欢
      • 2018-09-22
      • 2018-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-13
      • 2017-04-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多