(Nested?) tidy evaluation with glue strings

Hello my dear RStudio Community!

I'm having a bit of trouble with tidy evaluation in a mutate statement. The problem is the following:

I have a tibble:

library(tidyverse)

my_tibble <- tibble(
  quantity = 10:20
)

my_tibble
# A tibble: 11 x 1
   quantity
      <int>
 1       10
 2       11
 3       12
 4       13
 5       14
 6       15
 7       16
 8       17
 9       18
10       19
11       20

I want to create two summary variables: the mean of quantity and a transformation of such mean. For example:

my_tibble %>% 
  summarise(
    avg_3 = mean(quantity) / 3,
    second_avg = sqrt(avg_3)
  )

A tibble: 1 x 2
  avg_3 second_avg
  <dbl>      <dbl>
1     5       2.24

Note that the second summary variable (second_avg) depends on the first one (avg_3).
The key here is that I want the "avg" string to be divided by a number (parameter) in this case, 3.

My goal is to create a function that does this with the following variables:

  1. The target tibble containing the data.
  2. The target variable on which to take the mean.
  3. The divisor by which the mean is divided (in my example, that's 3).

Recall that I want to name the first variable with the divisor parameter. For example, if divisor = 10, I want the first variable to be named avg_10. I can do that easily with tidy evaluation glue strings (https://www.tidyverse.org/blog/2020/02/glue-strings-and-tidy-eval/).

THE PROBLEM is getting the second summary function to work. Here's what I've tried so far:

average_and_divide <- function(.target_tbl = my_tibble, 
                               .target_var = quantity, 
                               .divisor = 3) {
  
  avg_tbl <-  .target_tbl %>% 
    summarise(
      "avg_{{ .divisor }}" := mean({{ .target_var }}) / .divisor,

     # here's the problem: I try to use the just-created variable but it won't work:
      second_avg =  sqrt("avg_{{ divisor }}") # here's the problem!!! 
    )
  
  return(avg_tbl)
}

I know there should be a simple solution, but I couldn't find it. I've tried using enquo() but that didn't work.

It seems the glue syntax doesn't work inside sqrt(), I think it must be a more elegant solution but this is a walk around.

library(tidyverse)

my_tibble <- tibble(
    quantity = 10:20
)

average_and_divide <- function(.target_tbl = my_tibble, 
                               .target_var = quantity, 
                               .divisor = 3) {
    
    avg_tbl <-  .target_tbl %>% 
        summarise(
            "avg_{{ .divisor }}" := mean({{ .target_var }}) / .divisor,
            second_avg = sqrt(eval(as.name(paste0("avg_", {{.divisor}}))))
        )
    
    return(avg_tbl)
}

average_and_divide()
#> # A tibble: 1 x 2
#>   avg_3 second_avg
#>   <dbl>      <dbl>
#> 1     5       2.24

Created on 2020-10-15 by the reprex package (v0.3.0)

1 Like

Thank you so much!! This worked beautifully. As you mention, there should be a more elegant solution but this works just fine!

:smiley: :smiley: :smiley:

You could rename at the end, which avoids the tidyeval issues:

library(tidyverse)

average_and_divide <- function(.target_tbl = my_tibble, 
                               .target_var = quantity, 
                               .divisor = 3) {
  
  avg_tbl <-  .target_tbl %>% 
    summarise(
      {{.target_var}} := mean({{ .target_var }}) / .divisor,
      second_avg =  sqrt({{.target_var}}) 
    ) %>% 
    rename("avg_{{.divisor}}":={{.target_var}})
  
  return(avg_tbl)
}

average_and_divide(mtcars, mpg)
#>      avg_3 second_avg
#> 1 6.696875   2.587832

average_and_divide(iris, Petal.Width)
#>       avg_3 second_avg
#> 1 0.3997778  0.6322798

Actually, I just realized what was going wrong in your original function. First, change divisor to .divisor. Second, we don't need to control the evaluation of .divisor itself. We just want to paste its current value onto "avg_", convert the result to a symbol and then evaluate that. I've used paste0 below, as I'm not sure how to make this work with glue. I'm not sure if this is really any different than @andresrcs's solution (other than translating into a tidyeval idiom).

average_and_divide <- function(.target_tbl = my_tibble, 
                               .target_var = quantity, 
                               .divisor = 3) {
  
  avg_tbl <-  .target_tbl %>% 
    summarise(
      "avg_{{ .divisor }}" := mean({{ .target_var }}) / .divisor,
      second_avg =  sqrt(!!sym(paste0("avg_", .divisor)))
    )
  
  return(avg_tbl)
}
2 Likes

Man, this is awesome! Both solutions are great! Thank you, thank you, thank you!!! :heart_eyes::heart_eyes:

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.

If you have a query related to it or one of the replies, start a new topic and refer back with a link.