Reputation: 3158
I'm struggling to get quosures to work inside a map
call.
Some toy data:
library(tidyverse)
df <- tibble(
g1 = letters[1:2] %>%
rep(each = 3),
g2 = letters[3:5] %>%
rep(times = 2),
y = runif(6)
)
I can get this function to work, where I enquo
a variable before I pass it to group_by
:
sum1 <- function(df, g){
g <- enquo(g)
df %>%
group_by(!! g) %>%
summarize(
mu = y %>%
mean
)
}
Calling this function
sum1(df, g2)
gets me the expected result. But if I want to map
over multiple grouping variables, (ie g1
& g2
)
str_c("g", 1:2) %>%
map(
function(i)
sum1(df, i)
)
Returns the error
Error in grouped_df_impl(data, unname(vars), drop) :
Column `i` is unknown
How can I set up quosures
in a map
call?
Upvotes: 2
Views: 364
Reputation: 12819
str_c("g", 1:2) %>%
syms() %>%
map(sum1, df = df)
syms()
turns character
s into symbols (expected by sum1
).
Rewriting map(function(i) sum1(df, i))
as map(sum1, df = df)
prevents unwanted evaluation of the promise i
that happens when sum1
is wrapped in another function.
Rewriting map(function(i) sum1(df, i))
as map(sum1, df = df)
allows to pass the symbols g1
and g2
directly to sum1()
, rather than the symbol i
.
(Alternatively, str_c("g", 1:2) %>% syms() %>% map(function(i) sum1(df, !! i))
or str_c("g", 1:2) %>% map(function(i) sum1(df, !! sym(i)))
work, where !!
unquotes i
before passing it to sum1()
.
(Actually this is a bit oversimplified: unquoting doesn't happen before, but when you do enquo(g)
in the body of sum1
).
Upvotes: 0
Reputation: 887213
We can use group_by_at
and it can take a string as argument
library(tidyverse)
sum1 <- function(df, grps){
map(grps, ~
df %>%
group_by_at(.x) %>%
summarise(mu = mean(y))
)
}
sum1(df, str_c("g", 1:2))
#[[1]]
# A tibble: 2 x 2
# g1 mu
# <chr> <dbl>
#1 a 0.440
#2 b 0.469
#[[2]]
# A tibble: 3 x 2
# g2 mu
# <chr> <dbl>
#1 c 0.528
#2 d 0.592
#3 e 0.243
Regarding the usage of parameters with quosure in function, it is not clear whether it should be a single parameter or multiple parametr
In case if we are going with the string as argument, convert it to symbol (sym
) and then evaluate (!!
)
sum2 <- function(df, grps){
map(grps, ~
df %>%
group_by(!! rlang::sym(.x)) %>%
summarise(mu = mean(y))
)
}
sum2(df, str_c("g", 1:2))
#[[1]]
# A tibble: 2 x 2
# g1 mu
# <chr> <dbl>
#1 a 0.440
#2 b 0.469
#[[2]]
# A tibble: 3 x 2
# g2 mu
# <chr> <dbl>
#1 c 0.528
#2 d 0.592
#3 e 0.243
Another with quosure to pass multiple groups would be
sum3 <- function(df, ...){
gs <- enquos(...)
map(gs, ~
df %>%
group_by(!! .x) %>%
summarise(mu = mean(y)))
}
sum3(df, g1, g2)
#[[1]]
# A tibble: 2 x 2
# g1 mu
# <chr> <dbl>
#1 a 0.440
#2 b 0.469
#[[2]]
# A tibble: 3 x 2
# g2 mu
# <chr> <dbl>
#1 c 0.528
#2 d 0.592
#3 e 0.243
Upvotes: 2