Skip to content
Development
Skill

/rlang-patterns

rlang metaprogramming patterns for data-masking, injection operators, and dynamic dots. Use when writing functions that use tidy evaluation.

From plugin
r-skills
2128 skills3 agents4 commands4 hooks

Claude Code

$ npx -y skills add ab604/claude-code-r-skills --skill rlang-patterns --agent claude-code

SKILL.md

rlang-patterns.SKILL.md
name: rlang-patterns
description: rlang metaprogramming patterns for data-masking, injection operators, and dynamic dots. Use when writing functions that use tidy evaluation.

Modern rlang Patterns for Data-Masking

*Metaprogramming framework that powers tidyverse data-masking*

Core Concepts

**Data-masking** allows R expressions to refer to data frame columns as if they were variables in the environment. rlang provides the metaprogramming framework that powers tidyverse data-masking.

Key rlang Tools

  • **Embracing `{{}}`** - Forward function arguments to data-masking functions
  • **Injection `!!`** - Inject single expressions or values
  • **Splicing `!!!`** - Inject multiple arguments from a list
  • **Dynamic dots** - Programmable `...` with injection support
  • **Pronouns `.data`/`.env`** - Explicit disambiguation between data and environment variables

Function Argument Patterns

Forwarding with `{{}}`

**Use `{{}}` to forward function arguments to data-masking functions:**

# Single argument forwarding
my_summarise <- function(data, var) {
  data |> dplyr::summarise(mean = mean({{ var }}))
}

# Works with any data-masking expression
mtcars |> my_summarise(cyl)
mtcars |> my_summarise(cyl * am)
mtcars |> my_summarise(.data$cyl)  # pronoun syntax supported

Forwarding `...` (No Special Syntax Needed)

# Simple dots forwarding
my_group_by <- function(.data, ...) {
  .data |> dplyr::group_by(...)
}

# Works with tidy selections too
my_select <- function(.data, ...) {
  .data |> dplyr::select(...)
}

# For single-argument tidy selections, wrap in c()
my_pivot_longer <- function(.data, ...) {
  .data |> tidyr::pivot_longer(c(...))
}

Names Patterns with `.data`

**Use `.data` pronoun for programmatic column access:**

# Single column by name
my_mean <- function(data, var) {
  data |> dplyr::summarise(mean = mean(.data[[var]]))
}

# Usage - completely insulated from data-masking
mtcars |> my_mean("cyl")  # No ambiguity, works like regular function

# Multiple columns with all_of()
my_select_vars <- function(data, vars) {
  data |> dplyr::select(all_of(vars))
}

mtcars |> my_select_vars(c("cyl", "am"))

Injection Operators

When to Use Each Operator

| Operator | Use Case | Example | |----------|----------|---------| | `{{ }}` | Forward function arguments | `summarise(mean = mean({{ var }}))` | | `!!` | Inject single expression/value | `summarise(mean = mean(!!sym(var)))` | | `!!!` | Inject multiple arguments | `group_by(!!!syms(vars))` | | `.data[[]]` | Access columns by name | `mean(.data[[var]])` |

Advanced Injection with `!!`

# Create symbols from strings
var <- "cyl"
mtcars |> dplyr::summarise(mean = mean(!!sym(var)))

# Inject values to avoid name collisions
df <- data.frame(x = 1:3)
x <- 100
df |> dplyr::mutate(scaled = x / !!x)  # Uses both data and env x

# Use data_sym() for tidyeval contexts (more robust)
mtcars |> dplyr::summarise(mean = mean(!!data_sym(var)))

Splicing with `!!!`

# Multiple symbols from character vector
vars <- c("cyl", "am")
mtcars |> dplyr::group_by(!!!syms(vars))

# Or use data_syms() for tidy contexts
mtcars |> dplyr::group_by(!!!data_syms(vars))

# Splice lists of arguments
args <- list(na.rm = TRUE, trim = 0.1)
mtcars |> dplyr::summarise(mean = mean(cyl, !!!args))

Dynamic Dots Patterns

Using `list2()` for Dynamic Dots Support

my_function <- function(...) {
  # Collect with list2() instead of list() for dynamic features
  dots <- list2(...)
  # Process dots...
}

# Enables these features:
my_function(a = 1, b = 2)           # Normal usage
my_function(!!!list(a = 1, b = 2))  # Splice a list
my_function("{name}" := value)      # Name injection
my_function(a = 1, )               # Trailing commas OK

Name Injection with Glue Syntax

# Basic name injection
name <- "result"
list2("{name}" := 1)  # Creates list(result = 1)

# In function arguments with {{
my_mean <- function(data, var) {
  data |> dplyr::summarise("mean_{{ var }}" := mean({{ var }}))
}

mtcars |> my_mean(cyl)        # Creates column "mean_cyl"
mtcars |> my_mean(cyl * am)   # Creates column "mean_cyl * am"

# Allow custom names with englue()
my_mean <- function(data, var, name = englue("mean_{{ var }}")) {
  data |> dplyr::summarise("{name}" := mean({{ var }}))
}

# User can override default
mtcars |> my_mean(cyl, name = "cylinder_mean")

Pronouns for Disambiguation

`.data` and `.env` Best Practices

# Explicit disambiguation prevents masking issues
cyl <- 1000  # Environment variable

mtcars |> dplyr::summarise(
  data_cyl = mean(.data$cyl),    # Data frame column
  env_cyl = mean(.env$cyl),      # Environment variable
  ambiguous = mean(cyl)          # Could be either (usually data wins)
)

# Use in loops and programmatic contexts
vars <- c("cyl", "am")
for (var in vars) {
  result <- mtcars |> dplyr::summarise(mean = mean(.data[[var]]))
  print(result)
}

Programming Patterns

Bridge Patterns

**Converting between data-masking and tidy selection behaviors:**

# across() as selection-to-data-mask bridge
my_group_by <- function(data, vars) {
  data |> dplyr::group_by(across({{ vars }}))
}

# Works with tidy selection
mtcars |> my_group_by(starts_with("c"))

# across(all_of()) as names-to-data-mask bridge
my_group_by <- function(data, vars) {
  data |> dplyr::group_by(across(all_of(vars)))
}

mtcars |> my_group_by(c("cyl", "am"))

Transformation Patterns

# Transform single arguments by wrapping
my_mean <- function(data, var) {
  data |> dplyr::summarise(mean = mean({{ var }}, na.rm = TRUE))
}

# Transform dots with across()
my_means <- function(data, ...) {
  data |> dplyr::summarise(across(c(...), ~ mean(.x, na.rm = TRUE)))
}

# Manual transformation (advanced)
my_means_manual <- function(.data, ...) {
  vars <- enquos(..., .named = TRUE)
  vars <- purrr::map(vars, ~ expr(mean(!!.x, na.rm = TRUE)))
  .data |> dplyr::summarise(!!!var
Read more
@ab604
Ships withr-skills

A curated collection of Claude Code configurations for modern R use. These skills, rules, commands, and agents help Claude Code understand R best practices and generate idiomatic, high-quality R code.

2128 skills3 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
212
Star history of ab604/claude-code-r-skills on GitHub, a chart by star-history.com

Other skills on r-skills.

r-bayes

r-bayes

Patterns for Bayesian inference in R using brms, including multilevel models, DAG validation, and marginal effects. Use when…

@ab604@ab604Install Skill
r-oop

r-oop

R object-oriented programming guide for S7, S3, S4, and vctrs. Use when designing R classes or choosing an OOP system.

@ab604@ab604Install Skill
r-package-development

r-package-development

R package development guide covering dependencies, API design, testing, and documentation. Use when developing R packages.

@ab604@ab604Install Skill
r-performance

r-performance

R performance best practices including profiling, benchmarking, vctrs, and optimization strategies. Use when optimizing R code.

@ab604@ab604Install Skill
r-style-guide

r-style-guide

R style guide covering naming conventions, spacing, layout, and function design best practices. Use when writing R code.

@ab604@ab604Install Skill
tdd-workflow

tdd-workflow

Test-driven development workflow for R using testthat. Use when writing new features, fixing bugs, or refactoring code. Enforces…

@ab604@ab604Install Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills12d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc13d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react2d252k
@facebook@facebookInstall Skill

Questions about rlang-patterns.

Which coding agents can use rlang-patterns?
rlang-patterns installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.