Step-by-step actions in Tidyeval

Understanding Tidyeval gradually

Illustration from unsplash

Motivation

Provide some examples of using Tidy evaluation (also called non-standard evaluation (NSE) or delayed evaluation).

ℹ️ This post may be useful for your if you have read Chapters 17-20 of Advanced R book and you are looking to find more additional examples of Tidyeval. You may also want to have a look at the great Tidyeval resource put together by Mara.

Libraries needed for this post

library(rlang)
library(tidyverse)
library(testthat)

Standard function to coefficient of variation

Let’s write a function that calculates coefficient of variation:

cv <- function(var) {
    sd(var) / mean(var)
}

We can test that the function is behaving correctly

testthat::expect_equal( cv(c(3,3)), 0)
testthat::expect_equal( round(cv(c(3,6)),2), 0.47)

Great! The function seems to be doing what we want! Baby steps

Capture and uncapture expression

We can capture and uncapture expressions with enexpr and bang-bang !!

cv <- function(var) {
    var <- enexpr(var) 
    expr(sd(!!var) / mean(!!var)) 
}

There is a lot is going on here:

  • enexpr: it captures what the caller supplied to the function and allows delayed evaluation.
  • !!: it unquotes. Sort of like make available what it was captured by enexpr. No evaluation has happened yet.
  • expr: it captures what it was unquoted by !!. No evaluation has happened yet.

Let’s see what happens:

testthat::expect_equal(eval(cv(c(3,3))), 0) 
testthat::expect_type((cv(c(3,3))), "language")

If we evaluate the function, the cv is equal to 0. Note that we have delayed the evaluation up the point when used eval.

If we don’t evaluate the function it remains as a call object.

Modiying the enviroment - no issues

Let’s modify the function’s environment a little:

cv <- function(var) {
    x=6
    var <- enexpr(var)
    expr(sd(!!var) / mean(!!var))
}

Let’s see what happens:

x=3
testthat::expect_equal( eval(cv(c(3,x))), 0)
x=6
testthat::expect_equal(round(eval(cv(c(3,x))),2), 0.47)

This works even if we put x in the environment where the function is written because x is not an argument of cv function.

Modiying the enviroment - ISSUES!

adding_cv <- function(df,var) {
    x=c(3,6)
    var <- enexpr(var)
    mutate(df, sd(!!var) / mean(!!var))
}

Let’s see what happens:

df<- tibble(n=3)
x=c(3,3)
adding_cv(df,x)
## # A tibble: 1 x 2
##       n `sd(x)/mean(x)`
##   <dbl>           <dbl>
## 1     3           0.471

Wait, what?! The new column contains coefficient of variation. If x=c(3,3) the value in the new column should have been 0. However adding_cv is using x=c(3,6) included in the function environment and not x=c(3,3).

Capturing with enquo

To capture the function and the environment we need enquo

adding_cv <- function(df,var) {
    x=c(3,6)
    var <- enquo(var)
    mutate(df, sd(!!var) / mean(!!var))
}

Let’s see what happens now:

df<- tibble(n=3)
x=c(3,3)
adding_cv(df,x)
## # A tibble: 1 x 2
##       n `sd(x)/mean(x)`
##   <dbl>           <dbl>
## 1     3               0

Changing name with :=

We can make the name of the new column prettier with :=

adding_cv <- function(df,var,nm) {
    x=c(3,3)
    nm_name <- quo_name(nm)
    var <- enquo(var)
    mutate(df, !!nm_name:= sd(!!var) / mean(!!var))
}

Let’s see if that allows changing the name:

x=c(3,6)
df<- tibble(n=3)
adding_cv(df,x,"pretty_name")
## # A tibble: 1 x 2
##       n pretty_name
##   <dbl>       <dbl>
## 1     3       0.471

Insert a list of expressions into a call

What if we want to do delayed filtering? We need to unquote multiple arguments. For this we can use !!!

adding_cv <- function(df,var,nm, ...) {
    x=c(3,6)
    nm_name <- quo_name(nm)
    var <- enquo(var)
    filtering <- enquos(...)
    df %>%  filter(!!!filtering) %>% 
    mutate( !!nm_name:= sd(!!var) / mean(!!var))}

Let’s see if we can filter rows 3 and 6 from column n of our df:

df<- tibble(n=c(3,6,9),m=c(3,6,9))
x=c(3,3)
adding_cv(df,x,"pretty_name", n %in% c(3,6))
## # A tibble: 2 x 3
##       n     m pretty_name
##   <dbl> <dbl>       <dbl>
## 1     3     3           0
## 2     6     6           0

Conclusions

These were just few examples to illustrate why tidyeval can be useful and when it might be needed. I hope it helped you!

Maria Dermit
Maria Dermit
Postdoctoral Research Scientist

Postdoctoral researcher interested in translation control and data science for biomedical research.

Related