dplyr group_by несколько аргументов функции через нестандартную оценку

Я читал виньетку dplyr, пытаясь понять, как использовать dplyr в моих кодах функций. В середине рассказа о том, как использовать enquos на ... для передачи нескольких аргументов в group_by.

краткий пример того, как это будет работать

grp <- rlang::enquos(...)
df %>%
    group_by(!!!grp)

Я не знал, есть ли способ присвоить несколько значений выражений без резервирования ... без какого-либо сомнительного кодирования.

Чтобы получить представление о том, как будет выглядеть вызов, используйте следующий пример:

#reproducable data
df <- datasets::USJudgeRatings
df$name <- rownames(df)
df <- tidyr::gather(df, key = "key", value = "value", -name)
df$dummy <- c("1","2")


test_summarize <- function(df, sum.col, grp = NULL, filter = NULL) {
  filter <- rlang::enquo(filter)
  sum.col <- rlang::enquo(sum.col)
  if(!is.null(rlang::get_expr(filter))){
    df <- dplyr::filter(df, !!filter)
  }

  #how grp is turned into a character vector to be passed to .dots in group_by
  grp <- substitute(grp)
  if(!is.null(grp)){
    grp <- deparse(grp)
    grp <- strsplit(gsub(pattern = "list\\(|c\\(|\\)|", replacement = "", x = grp), split =",")[[1]]
    grp <- gsub(pattern = "^ | $", replacement = "", x = grp)
   df %>%
      dplyr::group_by(.dots=grp) %>%
      dplyr::summarise(mean = mean(!!sum.col), sum = sum(!!sum.col), n = n())
  } else{
    df %>%
      dplyr::summarise(mean = mean(!!sum.col), sum = sum(!!sum.col), n = n())
  }

}

test_summarize(df, sum.col=value, grp = c(name, dummy))

# A tibble: 86 x 5
# Groups:   name [?]
   name           dummy  mean   sum     n
   <chr>          <fct> <dbl> <dbl> <int>
 1 AARONSON,L.H.  1      7.17  43       6
 2 AARONSON,L.H.  2      7.42  44.5     6
 3 ALEXANDER,J.M. 1      8.35  50.1     6
 4 ALEXANDER,J.M. 2      7.95  47.7     6
 5 ARMENTANO,A.J. 1      7.53  45.2     6
 6 ARMENTANO,A.J. 2      7.7   46.2     6
 7 BERDON,R.I.    1      8.67  52       6
 8 BERDON,R.I.    2      8.25  49.5     6
 9 BRACKEN,J.J.   1      5.65  33.9     6
10 BRACKEN,J.J.   2      5.82  34.9     6
# ... with 76 more rows

Это работает для того, что я пытался сделать, но мне было интересно, есть ли лучший способ принять аргументы и обработать их. Каждая моя попытка превратить исходный вызов grp во что-то похожее на то, что enquos(...) терпела неудачу, поэтому я выполнял депарсинг и превращал их в вектор символов, который, честно говоря, я, вероятно, должен был просто ожидать, что пользователь будет передавать символы?

Я предпочитаю не использовать вектор символов в качестве ожидаемого ввода, потому что я пытался оставаться последовательным, учитывая, что аргументы sum.col и filter функции ожидают выражения NSE. Может быть, в пакете rlang есть что-то, что преобразует каждый элемент исходного выражения в список запросов?

Изменить: исправлен воспроизводимый пример и предоставлен ожидаемый результат


person Justin Landis    schedule 25.12.2019    source источник
comment
@RonakShah, исправил ошибку, пропустил строку, где я преобразовал имена строк в вектор-столбец   -  person Justin Landis    schedule 25.12.2019
comment
Вы можете использовать {{}} для переменных без кавычек, и, возможно, лучше передать цитируемый вектор и использовать group_by_at   -  person akrun    schedule 25.12.2019


Ответы (1)


Если мы используем group_by_at, нам может не понадобиться аргумент if/else

test_summarize <- function(df, sum.col, grp = NULL, filter = NULL) {
df %>% 
     group_by_at(grp) %>%
     summarise(mean = mean({{sum.col}}), 
               sum = sum({{sum.col}}), n = n())

   }


test_summarize(df, sum.col=value, grp = c("name", "dummy"))
# A tibble: 86 x 5
# Groups:   name [43]
#   name           dummy  mean   sum     n
#   <chr>          <chr> <dbl> <dbl> <int>
# 1 AARONSON,L.H.  1      7.17  43       6
# 2 AARONSON,L.H.  2      7.42  44.5     6
# 3 ALEXANDER,J.M. 1      8.35  50.1     6
# 4 ALEXANDER,J.M. 2      7.95  47.7     6
# 5 ARMENTANO,A.J. 1      7.53  45.2     6
# 6 ARMENTANO,A.J. 2      7.7   46.2     6
# 7 BERDON,R.I.    1      8.67  52       6
# 8 BERDON,R.I.    2      8.25  49.5     6
# 9 BRACKEN,J.J.   1      5.65  33.9     6
#10 BRACKEN,J.J.   2      5.82  34.9     6
# … with 76 more rows



test_summarize(df, sum.col=value)
# A tibble: 1 x 3
#   mean   sum     n
#  <dbl> <dbl> <int>
#1  7.57 3908.   516

который совпадает с

df %>%
   summarise(mean = mean(value), sum = sum(value), n = n())
#     mean    sum   n
#1 7.57345 3907.9 516

Если мы используем filter, то одним из вариантов будет ... и передать столько условий фильтрации

test_summarize <- function(df, sum.col, grp = NULL, ...) {
    df %>% 
         filter(!!! rlang::enexprs(...)) %>%
         group_by_at(grp) %>%
         summarise(mean = mean({{sum.col}}), sum = sum({{sum.col}}), n = n())

}


test_summarize(df, sum.col=value, grp = c("name", "dummy"),
        key %in% c("CONT", "INTG"), value > 6.5)
# A tibble: 77 x 5
# Groups:   name [43]
#   name           dummy  mean   sum     n
#   <chr>          <chr> <dbl> <dbl> <int>
# 1 AARONSON,L.H.  2       7.9   7.9     1
# 2 ALEXANDER,J.M. 1       8.9   8.9     1
# 3 ALEXANDER,J.M. 2       6.8   6.8     1
# 4 ARMENTANO,A.J. 1       7.2   7.2     1
# 5 ARMENTANO,A.J. 2       8.1   8.1     1
# 6 BERDON,R.I.    1       8.8   8.8     1
# 7 BERDON,R.I.    2       6.8   6.8     1
# 8 BRACKEN,J.J.   1       7.3   7.3     1
# 9 BURNS,E.B.     1       8.8   8.8     1
#10 CALLAHAN,R.J.  1      10.6  10.6     1
# … with 67 more rows

и это также будет оцениваться, когда нет аргументов фильтра

test_summarize(df, sum.col=value, grp = c("name", "dummy"))
# A tibble: 86 x 5
# Groups:   name [43]
#   name           dummy  mean   sum     n
#   <chr>          <chr> <dbl> <dbl> <int>
# 1 AARONSON,L.H.  1      7.17  43       6
# 2 AARONSON,L.H.  2      7.42  44.5     6
# 3 ALEXANDER,J.M. 1      8.35  50.1     6
# 4 ALEXANDER,J.M. 2      7.95  47.7     6
# 5 ARMENTANO,A.J. 1      7.53  45.2     6
# 6 ARMENTANO,A.J. 2      7.7   46.2     6
# 7 BERDON,R.I.    1      8.67  52       6
# 8 BERDON,R.I.    2      8.25  49.5     6
# 9 BRACKEN,J.J.   1      5.65  33.9     6
#10 BRACKEN,J.J.   2      5.82  34.9     6
# … with 76 more rows

что то же самое, что и первый вывод

person akrun    schedule 25.12.2019
comment
Спасибо за совет, я полагаю, что использование вектора символов - это на самом деле самый лаконичный способ ожидать группировки столбцов. - person Justin Landis; 25.12.2019
comment
@JustinLandis Можете ли вы показать свои filter выражения, как вы проходите? - person akrun; 25.12.2019
comment
выражения фильтра должны быть написаны так же, как в функции subset. Задание может выглядеть примерно так filter = key%in%c("CONT", "INTG")&value>6.5 - person Justin Landis; 25.12.2019
comment
определенно более лаконичный вариант - Спасибо! - person Justin Landis; 25.12.2019