Passing a List of Arguments to a Function with Quasiquotation

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



Leave a reply



Submit