【问题标题】:How to parametrize function calls in dplyr 0.7?如何在 dplyr 0.7 中参数化函数调用?
【发布时间】:2017-09-10 22:42:10
【问题描述】:

dplyr 0.7 的发布包括使用 dplyr 编程的major overhaul。我仔细阅读了这份文档,并试图了解它将如何影响我对 dplyr 的使用。

以下是我在使用 dplyr 构建报告和聚合函数时使用的常用习惯用法:

my_report <- function(data, grouping_vars) {
  data %>%
    group_by_(.dots=grouping_vars) %>%
    summarize(x_mean=mean(x), x_median=median(x), ...)
}

这里,grouping_vars 是一个字符串向量。

我喜欢这个习惯用法,因为我可以从其他地方传入字符串向量,比如文件或 Shiny 应用的反应式 UI,但对于交互式工作来说也不错。

但是,在新的programming with dplyr vignette 中,我没有看到如何使用新的 dplyr 完成类似操作的示例。我只看到了传递字符串不再是正确方法的示例,我必须改用 quosures。

我很高兴采用 quosures,但我究竟如何从字符串中得到 dplyr 所期望的 quosures?期望整个 R 生态系统为 dplyr 提供 quosures 似乎是不可行的——很多时候我们会得到字符串并且它们必须被转换。

这是一个示例,说明您现在应该做什么,以及我的旧习语如何不起作用:

library(dplyr)
grouping_vars <- quo(am)
mtcars %>%
  group_by(!!grouping_vars) %>%
  summarise(mean_cyl=mean(cyl))
#> # A tibble: 2 × 2
#>      am mean_cyl
#>   <dbl>    <dbl>
#> 1     0 6.947368
#> 2     1 5.076923

grouping_vars <- "am"
mtcars %>%
  group_by(!!grouping_vars) %>%
  summarise(mean_cyl=mean(cyl))
#> # A tibble: 1 × 2
#>   `"am"` mean_cyl
#>    <chr>    <dbl>
#> 1     am   6.1875

【问题讨论】:

  • 如果您向reproducible example 提供可用于测试可能解决方案的示例输入数据,则更容易提供帮助。
  • @MrFlick 我刚刚尝试设置一个,但在此过程中我发现我什至无法让基于 quosure 的示例工作。所以我提交了一个github问题github.com/tidyverse/dplyr/issues/2661
  • 得到了回复并相应地更新了我的帖子。

标签: r dplyr rlang


【解决方案1】:

dplyr 将有一个专门的 group_by 函数group_by_at 来处理多个分组变量。使用_at 家族的新成员会容易得多:

# using the pre-release 0.6.0

cols <- c("am","gear")

mtcars %>%
    group_by_at(.vars = cols) %>%
    summarise(mean_cyl=mean(cyl))

# Source: local data frame [4 x 3]
# Groups: am [?]
# 
# am  gear mean_cyl
# <dbl> <dbl>    <dbl>
# 1     0     3 7.466667
# 2     0     4 5.000000
# 3     1     4 4.500000
# 4     1     5 6.000000

.vars 参数接受vars 生成的字符/数字向量或列名:

.vars

由 vars() 生成的列列表,或 列名,或列位置的数字向量。

【讨论】:

  • 切换我的复选标记,因为这看起来将是前进的标准方式。
【解决方案2】:

这是我为自己写的快速而肮脏的参考。

# install.packages("rlang")
library(tidyverse)

dat <- data.frame(cat = sample(LETTERS[1:2], 50, replace = TRUE),
                  cat2 = sample(LETTERS[3:4], 50, replace = TRUE),
                  value = rnorm(50))

用字符串表示列名

使用rlang::symrlang::syms 将字符串转换为符号对象。

summ_var <- "value"
group_vars <- c("cat", "cat2")

summ_sym <- rlang::sym(summ_var)  # capture a single symbol
group_syms <- rlang::syms(group_vars)  # creates list of symbols

dat %>%
  group_by(!!!group_syms) %>%  # splice list of symbols into a function call
  summarize(summ = sum(!!summ_sym)) # slice single symbol into call

如果您在dplyr 函数之外使用!!!!!,则会出现错误。

rlang::symrlang::syms 在函数内部的用法是相同的。

summarize_by <- function(df, summ_var, group_vars) {

  summ_sym <- rlang::sym(summ_var)
  group_syms <- rlang::syms(group_vars)

  df %>%
    group_by(!!!group_syms) %>%
    summarize(summ = sum(!!summ_sym))
}

然后我们可以使用字符串参数调用summarize_by

summarize_by(dat, "value", c("cat", "cat2"))

对列/变量名使用非标准评估

summ_quo <- quo(value)  # capture a single variable for NSE
group_quos <- quos(cat, cat2)  # capture list of variables for NSE

dat %>%
  group_by(!!!group_quos) %>%  # use !!! with both quos and rlang::syms
  summarize(summ = sum(!!summ_quo))  # use !! both quo and rlang::sym

内部函数使用enquo 而不是quoquos 没关系!?

summarize_by <- function(df, summ_var, ...) {

  summ_quo <- enquo(summ_var)  # can only capture a single value!
  group_quos <- quos(...)  # captures multiple values, also inside functions!?

  df %>%
    group_by(!!!group_quos) %>%
    summarize(summ = sum(!!summ_quo))
}

然后我们的函数调用是

summarize_by(dat, value, cat, cat2)

【讨论】:

    【解决方案3】:

    如果您想按可能多于一列进行分组,可以使用quos

    grouping_vars <- quos(am, gear)
    mtcars %>%
      group_by(!!!grouping_vars) %>%
      summarise(mean_cyl=mean(cyl))
    #      am  gear mean_cyl
    #   <dbl> <dbl>    <dbl>
    # 1     0     3 7.466667
    # 2     0     4 5.000000
    # 3     1     4 4.500000
    # 4     1     5 6.000000
    

    目前,似乎没有一种很好的方法可以将字符串变为 quos。这是一种可行的方法

    cols <- c("am","gear")
    grouping_vars <- rlang::parse_quosures(paste(cols, collapse=";"))
    mtcars %>%
      group_by(!!!grouping_vars) %>%
      summarise(mean_cyl=mean(cyl))
    #      am  gear mean_cyl
    #   <dbl> <dbl>    <dbl>
    # 1     0     3 7.466667
    # 2     0     4 5.000000
    # 3     1     4 4.500000
    # 4     1     5 6.000000
    

    【讨论】:

    • @joran 在我这边,没有rlang::parse_quosures 的版本会产生一个只有am 的groupby,而不是amgear。对你来说有什么不同吗?
    • @Paul 或者我只是希望rlang::parse_quosures 可以采用适当的向量,而不必折叠。如果没有一个好的便利功能即将推出,我会感到惊讶
    • @Paul 实际上,当我在思考这个问题时,我只是在想这个新方法似乎非常针对编写将裸列名称作为参数的函数。奇怪地专注于编写 interactive 函数而不是一般的函数。
    • 我喜欢旧的 dplyr 功能的地方在于,他们将 NSE 版本翻译为更易于使用/编程的 SE 版本。这个新版本似乎全力以赴。
    • @Paul 我对我的回答非常恼火,所以我做了同样的事情:github.com/tidyverse/dplyr/issues/2662
    猜你喜欢
    • 2018-01-20
    • 2018-12-09
    • 2018-01-29
    • 2011-05-10
    • 1970-01-01
    • 2015-02-10
    • 2018-04-21
    • 2023-02-21
    • 1970-01-01
    相关资源
    最近更新 更多