Passing a list of arguments to a function with quasiquotation
You can rewrite the function using a combination of dplyr::group_by()
, dplyr::across()
, and curly curly embracing {{
. This works with dplyr version 1.0.0 and greater.
I've edited the original example and code for clarity.
library(tidyverse)
my_data <- tribble(
~foo, ~bar, ~baz,
"A", "B", 3,
"A", "C", 5,
"D", "E", 6,
"D", "E", 1
)
sum_fun <- function(.data, group, sum_var) {
.data %>%
group_by(across({{ group }})) %>%
summarize("sum_{{sum_var}}" := sum({{ sum_var }}))
}
sum_fun(my_data, group = c(foo, bar), sum_var = baz)
#> `summarise()` has grouped output by 'foo'. You can override using the `.groups` argument.
#> # A tibble: 3 x 3
#> # Groups: foo [2]
#> foo bar sum_baz
#> <chr> <chr> <dbl>
#> 1 A B 3
#> 2 A C 5
#> 3 D E 7
Created on 2021-09-06 by the reprex package (v2.0.0)
Passing on missing quasiquotation arguments
We could do a !!
and an enexpr
in foo2
foo2 <- function(a) {
print(is_missing(enexpr(a)))
bar(!!maybe_missing(enexpr(a)))
}
foo2()
#[1] TRUE
#[1] TRUE
foo2(b)
#[1] FALSE
#[1] FALSE
Pass a list of vectors of arguments as quosures to a function with purrr and rlang
The thing that makes this difficult is the use of c()
here. We really need some sort of rlang
object to hold your parameters. Here's an altered function to generate your list
q_list <- function(...) {
q <- enexprs(...)
transenv <- new_environment(list(c=exprs))
purrr::map(q, function(x) {
eval_tidy(x, env = transenv)
})
}
This takes your expressions and evaulates them treating c()
like enexprs()
. Then you can inject those values into your function call
my_q_list <- q_list(
c(cyl, sort = TRUE),
c(cyl, gear, sort = TRUE)
)
purrr::map(my_q_list, ~eval_tidy(quo(count(mtcars, !!!.x))))
This would have been easier if you just make the expressions in a list without using c()
my_q_list <- list(
exprs(cyl, sort = TRUE),
exprs(cyl, gear, sort = TRUE)
)
purrr::map(my_q_list, ~eval_tidy(quo(count(mtcars, !!!.x))))
Passing quasiquoted arguments within nested functions
Use ... in foobar:
foobar <- function(data, ...) {
data <- snafu(data, ...)
data %>% mutate(foo_var = snafu_var + 1)
}
using quasiquotation in functions with formula interface
The nice philosophy of rlang
is that you get to control when you want values to be evaluated via the !!
and {{}}
operators. You seem to want to make a function that takes strings, symbols, and (possibly evaluated) expressions all in the same parameter. Using symbols or bare strings is actually easy with ensym
but also wanting to allow for code like colnames(mtcars)[9]
that has to be evaulated before returning a string is the problem. This potentially can be quite confusing. For example, what's the behavior you expect when you run the following?
am <- 'disp'
cyl <- 'gear'
foo(mtcars, am, cyl)
You could write a helper function if you want to assume all "calls" should be evaluated but symbols and literals should not. Here's a "cleaner" function
clean_quo <- function(x) {
if (rlang::quo_is_call(x)) {
x <- rlang::eval_tidy(x)
} else if (!rlang::quo_is_symbolic(x)) {
x <- rlang::quo_get_expr(x)
}
if (is.character(x)) x <- rlang::sym(x)
if (!rlang::is_quosure(x)) x <- rlang::new_quosure(x)
x
}
and you could use that in your function with
foo <- function(data, x, y) {
x <- clean_quo(rlang::enquo(x))
y <- clean_quo(rlang::enquo(y))
# function without formula
print(table(data %>% dplyr::pull(!!x), data %>% dplyr::pull(!!y)))
# function with formula
print(
broom::tidy(stats::t.test(
formula = rlang::new_formula(rlang::quo_get_expr(y), rlang::quo_get_expr(x)),
data = data
))
)
}
Doing so will allow all these to return the same values
foo(mtcars, am, cyl)
foo(mtcars, "am", "cyl")
foo(mtcars, colnames(mtcars)[9], colnames(mtcars)[2])
But you are probably just delaying possible other problems. I would not recommend over-interpreting user intentions with this kind of code. That's why it's better to explicitly allow them to un-escape themselves. Perhaps provide two different versions of the function that can be used with parameter that require evaluation and those that do not.
Using a quasiquoted argument to a function within a purrr:map call
The enquo()
will attempt to track the enviroment of the symbol you pass in, but you don't really want that included in the formula you are passing to lm
. It would be better to capture that as a symbol rather than a quosure. Try this
model <- function(var) {
var <- ensym(var)
df %>%
group_by(a) %>%
nest() %>%
mutate(model=map(data, ~ lm(b ~ !!var, data=.)))
}
Worked for me with dplyr_0.7.6
and purrr_0.2.5
using quasiquotation in a function with summarize in dplyr
Use ensym
with !!
so that it can take both unquoted and quoted actual arguments
my_function <- function(my_df, columnA,columnB){
my_df %>%
group_by(cyl) %>%
summarise(base_mean = mean(!! ensym(columnA)),
contrast_mean = mean(!! ensym(columnB)), .groups = 'drop' )
}
-testing
> my_function(mtcars, !!base, !!cont)
# A tibble: 3 × 3
cyl base_mean contrast_mean
<dbl> <dbl> <dbl>
1 4 4.07 2.29
2 6 3.59 3.12
3 8 3.23 4.00
Related Topics
How to Programmatically Create Binary Columns Based on a Categorical Variable in Data.Table
Extract Names of Deeply Nested Lists
How to Make Install.Packages Return an Error If an R Package Cannot Be Installed
Disable Gui, Graphics Devices in R
How to Get Proportions and Counts of a Data Frame in R
Group/Bin/Bucket Data in R and Get Count Per Bucket and Sum of Values Per Bucket
Data.Table Join (Multiple) Selected Columns with New Names
R Plotly: Cannot Re-Arrange X-Axis When Axis Type Is Category
Calculate a 2D Spline Curve in R
Creating an Equal Distance Spatial Grid in R
Strange Behaviour Dropping Column from Data.Frame in R
Ggplot: Recommended Colour Palettes Also Distinguishable for B&W Printing
Download Multiple CSV Files with One Button (Downloadhandler) with R Shiny
How to Define Multiple Variables with Lapply
R: Apply Function to Matrix with Elements of Vector as Argument
R Produces "Unsupported Url Scheme" Error When Getting Data from Https Sites