简单 subset/summarise 函数的 dplyr'ish 方法
dplyr'ish approach to simple subset/summarise function
背景
提供的函数实现如下:
- 使用用户提供的表达式子集提供的数据框
- 选择所需的列
- 对结果向量和 returns 标量应用自定义汇总函数
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.
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}}
is shorthand for !!enquo(x)
它捕获提供给函数参数的表达式以及该表达式所在的上下文应该被评估。因为,您的上下文是由 df
有效定义的,所以可以将 enquo
放宽到 enexpr
(捕获表达式但不捕获求值上下文)和 ensym
(捕获符号或包含符号名称的字符串)。
背景
提供的函数实现如下:
- 使用用户提供的表达式子集提供的数据框
- 选择所需的列
- 对结果向量和 returns 标量应用自定义汇总函数
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"
. Runrlang::last_error()
to see where the error occurred.
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}}
is shorthand for !!enquo(x)
它捕获提供给函数参数的表达式以及该表达式所在的上下文应该被评估。因为,您的上下文是由 df
有效定义的,所以可以将 enquo
放宽到 enexpr
(捕获表达式但不捕获求值上下文)和 ensym
(捕获符号或包含符号名称的字符串)。