Day 17: rlang’s {{ }}

Wrap function arguments containing data-frame variables in rlang’s embracing operator ({{) for simpler tidy eval of “data-masked” arguments in functions.

Published

December 17, 2022

Introduced in rlang 0.4.0, the {{ (read as curly curly, or the embracing operator) can be used to write functions that call other data-masking functions. What’s data masking? Well, according to the rlang docs1:

  • 1 More specifically, the topic page What is data-masking and why do I need {{?

  • Data-masking is a distinctive feature of R whereby programming is performed directly on a data set, with columns defined as normal objects (Henry and Wickham 2022).

    Data-masking makes it easier to program interactively with data frames by letting you pass column names as normal objects.

    Here’s an example using base R’s with() function, which allows you to evaluate an expression in a data environment:

    # No data masking
    mean(mtcars$cyl + mtcars$am)
    #> [1] 6.59375
    
    # With data masking
    with(mtcars, mean(cyl + am))
    #> [1] 6.59375

    The functions tidyverse packages like ggplot2 and dplyr use data-masking to allow you to pass bare (unquoted) column names from the data frame as arguments. Though, as mentioned, this makes life easier for interactive programming, it means that writing functions that use data-masking functions requires special interpolation of variables (a pattern of quote-and-unquoting2).

  • 2 Before {{, this required you to use the enquo() and !! operators.

  • So, what does this look like? When writing a function around a tidyverse pipeline, you embrace your data-frame variables in {{ }}. Let’s say I want to write a function that calculates the mean by a group using dplyr’s group_by() and summarise() functions. I’ll need to interpolate the variables in those functions, since they are inside of the data mask.

    library(tidyverse)
    
    mean_by <- function(data, var, by) {
      data |>
        group_by({{ by }}) |>
        summarise(the_mean = mean({{ var }}, na.rm = TRUE))
    }
    
    mtcars |> 
      mean_by(mpg, carb)
    # A tibble: 6 × 2
       carb the_mean
      <dbl>    <dbl>
    1     1     25.3
    2     2     22.4
    3     3     16.3
    4     4     15.8
    5     6     19.7
    6     8     15  

    Learn more

    For more details on {{, data-masking, and how they work, see What is data-masking and why do I need {{?.

    For even more on data-masking and non-standard-evaluation patterns you can use with rlang’s tidy evaluation framework, check out Data mask programming patterns (and go even further with the other guides found in the Tidy-evaluation dropdown section of the rlang docs).

    Want to use glue-like { and {{ operators for names (as opposed to function arguments). Well, that’s not what the bare embracing operator is for. However, you can still learn how to do that in Name injection with "{" and "{{".

    References

    Henry, Lionel, and Hadley Wickham. 2022. rlang: Functions for Base Types and Core R and ’Tidyverse’ Features. https://rlang.r-lib.org.