Search code examples
rdplyrrlangtidyeval

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?


Solution

  • 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