Specifying multiple variables to group by via explicit argument with unquoted elements

最后都变了- 提交于 2020-01-16 16:48:35

问题


Based on the section regarding capturing multiple arguments in Programming with dplyr, I am trying to specify

  1. multiple variables to group by in dplyr::group_by

  2. without relying on ... but using an explicit list argument group_vars instead

  3. without needing to quote the list elements in arg group_vars

Example data

df <- tibble::tribble(
  ~a,   ~b,  ~c,
  "A",  "a", 10,
  "A",  "a", 20,
  "A",  "b", 1000,
  "B",  "a", 5,
  "B",  "b", 1
)

Approach based on ... from Programming with dplyr

# Approach 1 -----
my_summarise <- function(df, ...) {
  group_vars <- dplyr::enquos(...)

  df %>%
    dplyr::group_by(!!!group_vars) %>%
    dplyr::summarise(x = mean(c))
}

my_summarise(df, a, b)
#> # A tibble: 4 x 3
#> # Groups:   a [2]
#>   a     b         x
#>   <chr> <chr> <dbl>
#> 1 A     a        15
#> 2 A     b      1000
#> 3 B     a         5
#> 4 B     b         1

Approach based on list argument with quoted elements:

# Approach 2 -----
my_summarise_2 <- function(df, group_vars = c("a", "b")) {
  group_vars <- dplyr::syms(group_vars)

  df %>%
    dplyr::group_by(!!!group_vars) %>%
    dplyr::summarise(x = mean(c))
}

my_summarise_2(df)
#> # A tibble: 4 x 3
#> # Groups:   a [2]
#>   a     b         x
#>   <chr> <chr> <dbl>
#> 1 A     a        15
#> 2 A     b      1000
#> 3 B     a         5
#> 4 B     b         1

my_summarise_2(df, group_vars = "a")
#> # A tibble: 2 x 2
#>   a         x
#>   <chr> <dbl>
#> 1 A      343.
#> 2 B        3

I can't find an approach that lets me supply unquoted column names:

# Approach 3 -----
my_summarise_3 <- function(df, group_vars = list(a, b)) {
  group_vars <- dplyr::enquos(group_vars)

  df %>%
    dplyr::group_by(!!!group_vars) %>%
    dplyr::summarise(x = mean(c))
}

my_summarise_3(df)
#> Error: Column `list(a, b)` must be length 5 (the number of rows) or one, not 2

I guess the crucial thing is to end up with an identical list structure as the one after calling group_vars <- dplyr::enquos(...):

<list_of<quosure>>

[[1]]
<quosure>
expr: ^a
env:  global

[[2]]
<quosure>
expr: ^b
env:  global

I tried to tackle it with group_vars %>% purrr::map(dplyr::enquo), but of course R complains about a and b as they need to be evaluated.


回答1:


The main issue is that list(a, b) does not capture unevaluated expressions a and b, but instead evaluates those expressions and creates a two-element list with results. You basically have two options:

Solution one: Use rlang::exprs() to capture the actual expressions. Since the expressions are already unevaluated, you no longer need an enquos inside your function, which simply becomes

my_summarise_3 <- function(df, group_vars = rlang::exprs(a, b)) {
  df %>%
    dplyr::group_by(!!!group_vars) %>%
    dplyr::summarise(x = mean(c))
}

my_summarise_3(df)
# # A tibble: 4 x 3
# # Groups:   a [2]
#   a     b         x
#   <chr> <chr> <dbl>
# 1 A     a        15
# 2 A     b      1000
# 3 B     a         5
# 4 B     b         1

The down side of this interface is that the user is now responsible for quoting (i.e, capturing the expressions of) the arguments:

# Note that it can be done using quote() from base R
my_summarise_3(df, group_vars=quote(a))
# # A tibble: 2 x 2
#   a         x
#   <chr> <dbl>
# 1 A      343.
# 2 B        3 

Solution two: Capture the unevaluated expression list(a,b) in its entirety and parse it by hand.

## Helper function to recursively construct an abstract syntax tree
getAST <- function( ee ) { as.list(ee) %>% map_if(is.call, getAST) }

my_summarise_3 <- function(df, group_vars = list(a,b)) {
  ## Capture the expression and parse it
  ast <- rlang::enexpr(group_vars) %>% getAST()

  ## Identify symbols present in the data
  gvars <- unlist(ast) %>% map_chr(deparse) %>%
      intersect(names(df)) %>% rlang::syms()

  df %>%
      dplyr::group_by(!!!gvars) %>%
      dplyr::summarise(x = mean(c))
}

my_summarise_3(df, list(a,b))
# # A tibble: 4 x 3
# # Groups:   a [2]
#   a     b         x
#   <chr> <chr> <dbl>
# 1 A     a        15
# 2 A     b      1000
# 3 B     a         5
# 4 B     b         1

my_summarise_3(df, b)
# # A tibble: 2 x 2
#   b         x
#   <chr> <dbl>
# 1 a      11.7
# 2 b     500. 



回答2:


I think you just want to reinvent vars() :

library(magrittr)
library(dplyr,warn.conflicts = FALSE)
#> Warning: package 'dplyr' was built under R version 3.6.1
df <- tibble::tribble(
  ~a,   ~b,  ~c,
  "A",  "a", 10,
  "A",  "a", 20,
  "A",  "b", 1000,
  "B",  "a", 5,
  "B",  "b", 1
)

my_summarise <- function(data, group_vars) {
  data %>%
    group_by_at(group_vars) %>%
    summarise(x = mean(c))
}

my_summarise(df, c("a","b"))
#> # A tibble: 4 x 3
#> # Groups:   a [2]
#>   a     b         x
#>   <chr> <chr> <dbl>
#> 1 A     a        15
#> 2 A     b      1000
#> 3 B     a         5
#> 4 B     b         1

my_summarise(df, vars(a, b))
#> # A tibble: 4 x 3
#> # Groups:   a [2]
#>   a     b         x
#>   <chr> <chr> <dbl>
#> 1 A     a        15
#> 2 A     b      1000
#> 3 B     a         5
#> 4 B     b         1

Created on 2019-07-26 by the reprex package (v0.3.0)

Here's a variation on @Artem's solution if you really want this (but why ?) :

my_summarise <- function(df, group_vars) {
  quoted_group_vars <- rlang::list2(
    !!!as.list(enexpr(group_vars)[-1]))
  df %>%
    dplyr::group_by(!!!quoted_group_vars) %>%
    dplyr::summarise(x = mean(c))
}

my_summarise(df, list(a, b))
#> # A tibble: 4 x 3
#> # Groups:   a [2]
#>   a     b         x
#>   <chr> <chr> <dbl>
#> 1 A     a        15
#> 2 A     b      1000
#> 3 B     a         5
#> 4 B     b         1


来源:https://stackoverflow.com/questions/57007865/specifying-multiple-variables-to-group-by-via-explicit-argument-with-unquoted-el

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!