Converting strings to symbols then to quosure recognized by !! in tidyverse

Viewed 94

I've written a function which takes symbol as input and then is converted to a quosure by enquo() then evaluated in tidyverse functions by the !! operator. A simplified version of the function is as follows (the full function returns a dataframe and does more instead of just calculating a number but just for illustration purposes it should suffice):

query <- function(test){
  #colnames are different tests, obtain quosure of the test name
  test = enquo(test)
   avg_test_score = scores %>% 
    group_by(student) %>% summarise(mean.score = mean(!!test))
}

The function works fine if I do query(english.test) for example. However, now I want to put it in a for loop for all the different columns and get their names and run it through the function then store the output into a list

output_list = list()
for (i in length(test.name)){
  this.test = test.name[i] #test.name is a vector of strings
  output_list[[i]] = query(test = this.test)
}

But I get this error:

In mean.default(~this.test) :
  argument is not numeric or logical: returning NA

So I've kind of figured out I have to convert the character string into a symbol before inputting it into the function but I've tried different ways of converting, such as test.name[i] %>% as.symbol() or test.name[i] %>% rlang::sym() yet they still don't quite work. Is there any way to properly handle these conversions so the for loop can run my function? Thanks!

2 Answers

Here's a reproducible approach to compute the mean of multiple columns in a dataframe/tibble:

library(tidyverse)
library(rlang)
#> 
#> Attaching package: 'rlang'
#> The following objects are masked from 'package:purrr':
#> 
#>     %@%, as_function, flatten, flatten_chr, flatten_dbl, flatten_int,
#>     flatten_lgl, flatten_raw, invoke, list_along, modify, prepend,
#>     splice

nms <- c('cyl', 'hwy', 'cty')

nms %>%
map(~ mpg %>% 
                group_by(manufacturer) %>% 
                summarise('mean_{.x}' := mean(eval_tidy(sym(.x)))) )
#> [[1]]
#> # A tibble: 15 x 2
#>    manufacturer mean_cyl
#>    <chr>           <dbl>
#>  1 audi             5.22
#>  2 chevrolet        7.26
#>  3 dodge            7.08
#>  4 ford             7.2 
#>  5 honda            4   
#>  6 hyundai          4.86
#>  7 jeep             7.25
#>  8 land rover       8   
#>  9 lincoln          8   
#> 10 mercury          7   
#> 11 nissan           5.54
#> 12 pontiac          6.4 
#> 13 subaru           4   
#> 14 toyota           5.12
#> 15 volkswagen       4.59
#> 
#> [[2]]
#> # A tibble: 15 x 2
#>    manufacturer mean_hwy
#>    <chr>           <dbl>
#>  1 audi             26.4
#>  2 chevrolet        21.9
#>  3 dodge            17.9
#>  4 ford             19.4
#>  5 honda            32.6
#>  6 hyundai          26.9
#>  7 jeep             17.6
#>  8 land rover       16.5
#>  9 lincoln          17  
#> 10 mercury          18  
#> 11 nissan           24.6
#> 12 pontiac          26.4
#> 13 subaru           25.6
#> 14 toyota           24.9
#> 15 volkswagen       29.2
#> 
#> [[3]]
#> # A tibble: 15 x 2
#>    manufacturer mean_cty
#>    <chr>           <dbl>
#>  1 audi             17.6
#>  2 chevrolet        15  
#>  3 dodge            13.1
#>  4 ford             14  
#>  5 honda            24.4
#>  6 hyundai          18.6
#>  7 jeep             13.5
#>  8 land rover       11.5
#>  9 lincoln          11.3
#> 10 mercury          13.2
#> 11 nissan           18.1
#> 12 pontiac          17  
#> 13 subaru           19.3
#> 14 toyota           18.5
#> 15 volkswagen       20.9


# or 
nms %>%
    map_dfc(~ mpg %>% 
            group_by(manufacturer) %>% 
            summarise('mean_{.x}' := mean(eval_tidy(sym(.x)))) %>% 
            `[`(, 2) ) %>% #avoid duplicated manufacturer columns
    mutate(group_by(mpg, manufacturer) %>% summarise())
#> # A tibble: 15 x 4
#>    mean_cyl mean_hwy mean_cty manufacturer
#>       <dbl>    <dbl>    <dbl> <chr>       
#>  1     5.22     26.4     17.6 audi        
#>  2     7.26     21.9     15   chevrolet   
#>  3     7.08     17.9     13.1 dodge       
#>  4     7.2      19.4     14   ford        
#>  5     4        32.6     24.4 honda       
#>  6     4.86     26.9     18.6 hyundai     
#>  7     7.25     17.6     13.5 jeep        
#>  8     8        16.5     11.5 land rover  
#>  9     8        17       11.3 lincoln     
#> 10     7        18       13.2 mercury     
#> 11     5.54     24.6     18.1 nissan      
#> 12     6.4      26.4     17   pontiac     
#> 13     4        25.6     19.3 subaru      
#> 14     5.12     24.9     18.5 toyota      
#> 15     4.59     29.2     20.9 volkswagen

Created on 2021-06-13 by the reprex package (v2.0.0)

You can change the function to accept strings.

library(dplyr)

query <- function(test){
  avg_test_score = scores %>% 
    group_by(student) %>% summarise(mean.score = mean(.data[[test]]))
}

For one column it would work with query("english.test").

For multiple columns you can use lapply/map -

multiple_cols <- c('english.test', 'french.test')
purrr::map(multiple_cols, query)
Related