Skip to Content
Course content

128: Quasiquotation with rlang

Click on the "Edit" button in the top corner of the screen to edit your slide content.

If you've spent any time writing functions that wrap around dplyr or ggplot2, you've probably hit a wall where R seems to be ignoring your variables. You pass a column name into a function, but instead of filtering by that column, R starts looking for a column actually named "column_name". It's a frustrating moment because you know the value is right there in the argument, but the function is treating it as a literal symbol.

The "Where did my column go?" problem

Let's look at how most of us try to solve this the first time. Imagine you want a helper function that filters a data frame based on a column and a value. You'd probably write something like this:

my_filter <- function(data, col_name, value) {
  data %>% filter(col_name == value)
}

# Trying to filter mtcars for cylinders == 6
my_filter(mtcars, cyl, 6)

This will fail. Why? Because filter() uses non-standard evaluation (NSE). It doesn't look for a variable called col_name in your environment; it looks for a column literally named col_name inside the data frame. Since your data frame doesn't have a column called "col_name", it throws an error. I've seen a lot of developers try to "hack" their way out of this using get() or eval(parse(text = ...)). Please, don't do that. Parsing strings into code is slow, hard to debug, and opens you up to some nasty security risks if you're handling user input.

Bridging the gap with Bang-Bang

This is where quasiquotation comes in. The term sounds academic, but the concept is simple: we want to "quote" an expression (tell R "don't evaluate this yet") and then "unquote" a specific part of it (tell R "actually, put this value right here"). In the rlang ecosystem, the tool for unquoting is !!, which we call "bang-bang".

To make this work, we first need to make sure the column name is a symbol, not a string or a raw value. We use ensym() for this because it's flexible—it handles both quoted strings ("cyl") and unquoted symbols (cyl) gracefully. Here is how I'd actually write that filter function:

library(rlang)
library(dplyr)

my_filter <- function(data, col_name, value) {
  # Convert the input to a symbol
  col_sym <- ensym(col_name)
  
  # Use !! to "splice" the symbol into the expression
  data %>% filter(!!col_sym == value)
}

# Now both of these work perfectly
my_filter(mtcars, cyl, 6)
my_filter(mtcars, "cyl", 6)

The trade-off: Complexity vs. Control

You might be wondering why we don't just use .data[[col_name]], which is another common way to handle dynamic columns in tidyverse. For a simple filter, .data[[col_name]] is actually cleaner. But as soon as you start building complex macros or functions that need to manipulate the actual expression tree—like building a dynamic ggplot layer or a complex summarise call—the rlang approach is the only one that gives you full control.

The "cost" here is a steeper learning curve. You're no longer just writing R code; you're writing code that generates R code. It feels like a meta-layer of programming. But once you get comfortable with ensym() and !!, you stop fighting the Tidyverse's NSE and start using it to your advantage. You're essentially telling R: "I know you usually handle these names automatically, but for this one specific spot, I want you to use this exact symbol I've captured."




📋 Practical Task

Build a Dynamic Column Summarizer

Your task is to create a function called summarize_column that takes a data frame and a column name (either as a symbol or a string) and returns the mean and standard deviation of that column. You must use rlang::ensym() and the !! operator to inject the column name into a dplyr::summarise call.

Requirements:

  • The function should accept (data, col_name).
  • It should work whether the user passes the column as a symbol (e.g., mpg) or a string (e.g., "mpg").
  • The resulting data frame should have columns named avg and std_dev.
  • Test your function using the mtcars dataset.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.