Как параметризовать вызовы функций в dplyr 0.7?

Версия dplyr 0.7 включает значительную переработку программирования с помощью dplyr. Я внимательно прочитал этот документ и пытаюсь понять, как он повлияет на мое использование 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, но это также не так уж плохо для интерактивной работы.

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

Я рад принять квазуры, но как именно мне перейти от строк к квазурам, ожидаемым dplyr здесь? Кажется невозможным ожидать, что вся экосистема R будет предоставлять квазуры для dplyr — много раз мы будем получать строки, и их придется преобразовывать.

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

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

person Paul    schedule 14.04.2017    source источник
comment
Легче помочь, если вы предоставите воспроизводимый пример с образцом исходные данные, которые можно использовать для проверки возможных решений.   -  person MrFlick    schedule 14.04.2017
comment
@MrFlick Я только что попытался настроить его, но в процессе обнаружил, что даже не могу заставить работать пример на основе quosure. Поэтому я подал заявку на github github.com/tidyverse/dplyr/issues/2661.   -  person Paul    schedule 14.04.2017
comment
Получил ответ и соответственно обновил свой пост.   -  person Paul    schedule 14.04.2017


Ответы (3)


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(), или вектор символов имен столбцов, или числовой вектор позиций столбцов.

person mt1022    schedule 14.04.2017
comment
Переключение моей галочки, так как похоже, что это будет стандартный путь в будущем. - person Paul; 14.04.2017

Вот быстрый и грязный справочник, который я написал для себя.

# 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::sym и rlang::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::sym и rlang::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, а не quo. quos в порядке!?

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)
person alexpghayes    schedule 20.05.2017

Если вы хотите сгруппировать, возможно, более чем по одному столбцу, вы можете использовать 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

Прямо сейчас не похоже, что есть отличный способ превратить строки в quo. Вот один из способов, который работает

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
person MrFlick    schedule 14.04.2017
comment
@joran с моей стороны, ваша версия без rlang::parse_quosures дает groupby только с am, а не с am и gear вместе. Отличается ли он для вас? - person Paul; 14.04.2017
comment
@ Пол Нет, вы оба правы, я не внимательно смотрел. - person joran; 14.04.2017
comment
@MrFlick это было бы не так уж плохо, если бы шаблон parse_quosures был заключен в функцию: as_quosure <- function(strs) rlang::parse_quosures(paste(strs, collapse=";")). Тогда вы можете просто сделать group_by(!!!as_quosure(cols)) - person Paul; 14.04.2017
comment
@Paul Или я просто хочу, чтобы rlang::parse_quosures мог взять правильный вектор, а не рушиться. Я был бы удивлен, если бы не было хорошей удобной функции. - person MrFlick; 14.04.2017
comment
@Paul Действительно, когда я обдумывал это, я просто подумал, что этот новый метод кажется ужасно ориентированным на написание функций, которые принимают голые имена столбцов в качестве аргументов. Странно чувствуется сосредоточенность на написании интерактивных функций, а не функций вообще. - person joran; 14.04.2017
comment
Что мне понравилось в старой функции dplyr, так это то, что они переводили версии NSE в версии SE, с которыми намного проще работать/программировать. Эта новая версия, кажется, идет ва-банк на нестандартном сумасшествии. - person MrFlick; 14.04.2017
comment
@joran MrFlick Я тоже сначала подумал об этом, но я думаю, что гениальная идея Хэдли заключается в том, что надежность новой структуры NSE / quosure должна упростить использование NSE для SE. Этот пример является хорошим начальным ударом колес - надеюсь, он продолжит работать. Я собираюсь поднять это в выпуске github. - person Paul; 14.04.2017
comment
@Paul Пол, меня так разозлил мой ответ, что я сделал то же самое: github.com/tidyverse/dplyr /вопросы/2662 - person MrFlick; 14.04.2017
comment
syms может быть полезно, например. (function(data, ...){ data %>% group_by(!!!rlang::syms(list(...))) %>% summarize(mpg = mean(mpg)) })(mtcars, 'cyl', 'am') - person alistaire; 14.04.2017
comment
@алистер. Да, это то, что Хэдли указал на проблему с github. Но я думаю, что group_by_at - лучшее решение. - person MrFlick; 14.04.2017
comment
Упс, поздно кликнул. Однако я до сих пор не могу найти тот, который идет прямо от точек строк к quosures/символам без list. - person alistaire; 14.04.2017