tomw
tomw

Reputation: 3158

Getting quosures to work inside a map call

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

Answers (2)

Aur&#232;le
Aur&#232;le

Reputation: 12819

str_c("g", 1:2) %>% 
  syms() %>%
  map(sum1, df = df)

syms() turns characters 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

akrun
akrun

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

Related Questions