dplyr’иш подход к простой функции подмножества / суммирования

#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 (который фиксирует символы или строки, содержащие имена символов).