简单 subset/summarise 函数的 dplyr'ish 方法

dplyr'ish approach to simple subset/summarise function

背景

提供的函数实现如下:

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")

语法,另外 已经工作的解决方案。这该怎么做?到目前为止,上面的调用产生错误:

错误

Error: Problem with filter() input ..1. x Input ..1 must be a logical vector, not a character. ℹ Input ..1 is "am == 1". Run rlang::last_error() to see where the error occurred.

mincyl 可以由 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}} is shorthand for !!enquo(x) 它捕获提供给函数参数的表达式以及该表达式所在的上下文应该被评估。因为,您的上下文是由 df 有效定义的,所以可以将 enquo 放宽到 enexpr(捕获表达式但不捕获求值上下文)和 ensym(捕获符号或包含符号名称的字符串)。