#r #function #dplyr #rlang
#r #функция #dplyr #rlang
Вопрос:
Предыстория
Предоставленная функция обеспечивает следующее:
- Подмножество предоставленного фрейма данных с использованием предоставленного пользователем выражения
- Выбирает нужный столбец
- Применяет пользовательскую итоговую функцию к результирующему вектору и возвращает скалярный
base
подход
summarise_filtered <-
function(df,
subset_arg,
summary_fun = c("min", "max", "median"),
select_col) {
summary_fun <- match.arg(summary_fun)
sbst_vals <-
subset.data.frame(
df,
subset = eval(parse(text = subset_arg)),
drop = TRUE,
select = eval(parse(text = select_col))
)
do.call(match.fun(summary_fun), list(sbst_vals))
}
Результаты
summarise_filtered(mtcars, "am == 1", "min", "cyl")
# [1] 4
summarise_filtered(mtcars, "am == 1", "max", "cyl")
# [1] 8
Вызов
Я заинтересован в переписывании функции, описанной выше, с использованием dplyr
синтаксиса канала. Моя первоначальная попытка удовлетворяет основным требованиям:
summarise_filtered_dplyrish <-
function(df,
subset_arg,
summary_fun,
select_col) {
df %>%
filter({{subset_arg}}) %>%
summarise(across(.cols = {{select_col}}, .fns = summary_fun)) %>%
pull({{select_col}})
}
когда звонили:
summarise_filtered_dplyrish(mtcars, am == 1, min, cyl)
# [1] 4
Проблема
Я бы хотел, чтобы функция работала с использованием:
summarise_filtered_dplyrish(mtcars, "am == 1", "min", "cyl")
синтаксис, в дополнение к уже работающему решению. Как это сделать? Пока что приведенный выше вызов генерирует ошибку:
Ошибка
Ошибка: Проблема с
filter()
вводом..1
. ввод x..1
должен быть логическим вектором, а не символом...1
ℹ Вход есть"am == 1"
. Запуститеrlang::last_error()
, чтобы узнать, где произошла ошибка.
Ответ №1:
min
и cyl
может быть легко обработан ensym()
, который работает как со строками, так и с символами. Выражение am == 1
требует немного больше работы. Давайте определим вспомогательную функцию, которая анализирует объект, только если это строка:
str2expr <- function(.x) {if( is.character(.x) ) rlang::parse_expr(.x) else .x}
Теперь мы можем захватить аргумент, предоставленный subset_arg
и проанализировать его, если это строка:
summarise_filtered_dplyrish <-
function(df,
subset_arg,
summary_fun,
select_col) {
subset_expr <- rlang::enexpr(subset_arg) %>% str2expr()
df %>%
filter( !!subset_expr ) %>%
summarise(across(.cols = {{select_col}}, .fns = !!ensym(summary_fun))) %>%
pull( !!ensym(select_col) )
}
summarise_filtered_dplyrish( mtcars, am == 1, min, cyl ) # Works
summarise_filtered_dplyrish( mtcars, "am == 1", "min", "cyl" ) # Also works
Краткое объяснение: {{x}}
это сокращение, для !!enquo(x)
которого фиксируется выражение, предоставленное аргументу функции, и контекст, в котором это выражение должно быть вычислено. Поскольку ваш контекст эффективно определяется df
, можно расслабиться enquo
enexpr
(который фиксирует выражения, но не контекст вычисления) и ensym
(который фиксирует символы или строки, содержащие имена символов).