The provided function achieves the following:
base approachsummarise_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
I'm interested in re-writing the function above using dplyr pipe syntax. My initial attempt fulfils the basic requirements:
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}})
}
when called:
summarise_filtered_dplyrish(mtcars, am == 1, min, cyl)
# [1] 4
I would like for the function to work using:
summarise_filtered_dplyrish(mtcars, "am == 1", "min", "cyl")
syntax, in addition to the the already working solution. How to do this? So far, the call above generates error:
Error: Problem with
filter()input..1. x Input..1must be a logical vector, not a character. ℹ Input..1is"am == 1". Runrlang::last_error()to see where the error occurred.
min and cyl can be easily handled by ensym(), which works with both strings and symbols. The expression am == 1 requires a little bit more work. Let's define a helper function that parses an object only if it's a string:
str2expr <- function(.x) {if( is.character(.x) ) rlang::parse_expr(.x) else .x}
We can now capture the argument provided to subset_arg and parse it if it's a string:
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
Brief explanation: {{x}} is shorthand for !!enquo(x) which captures the expression provided to the function argument and the context where that expression should be evaluated. Since, your context is effectively defined by df, it's OK to relax enquo down to enexpr (which captures expressions but not the evaluation context) and ensym (which captures symbols or strings containing names of symbols).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With