Skip to content
Development
Skill

/tidyverse-patterns

Modern tidyverse patterns for R including pipes, joins, grouping, purrr, and stringr. Use when writing tidyverse R code.

From plugin
r-skills
2128 skills3 agents4 commands4 hooks

Claude Code

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

SKILL.md

tidyverse-patterns.SKILL.md
name: tidyverse-patterns
description: Modern tidyverse patterns for R including pipes, joins, grouping, purrr, and stringr. Use when writing tidyverse R code.

Modern Tidyverse Patterns

*Best practices for modern tidyverse development with dplyr 1.1+ and R 4.3+*

Core Principles

1. **Use modern tidyverse patterns** - Prioritize dplyr 1.1+ features, native pipe, and current APIs 2. **Profile before optimizing** - Use profvis and bench to identify real bottlenecks 3. **Write readable code first** - Optimize only when necessary and after profiling 4. **Follow tidyverse style guide** - Consistent naming, spacing, and structure

Pipe Usage (`|>` not `%>%`)

  • **Always use native pipe `|>` instead of magrittr `%>%`**
  • R 4.3+ provides all needed features
# Good - Modern native pipe
data |>
  filter(year >= 2020) |>
  summarise(mean_value = mean(value))

# Avoid - Legacy magrittr pipe
data %>%
  filter(year >= 2020) %>%
  summarise(mean_value = mean(value))

Join Syntax (dplyr 1.1+)

  • **Use `join_by()` instead of character vectors for joins**
  • **Support for inequality, rolling, and overlap joins**
# Good - Modern join syntax
transactions |>
  inner_join(companies, by = join_by(company == id))

# Good - Inequality joins
transactions |>
  inner_join(companies, join_by(company == id, year >= since))

# Good - Rolling joins (closest match)
transactions |>
  inner_join(companies, join_by(company == id, closest(year >= since)))

# Avoid - Old character vector syntax
transactions |>
  inner_join(companies, by = c("company" = "id"))

Join Quality Control

  • **Declare cardinality with `relationship` to validate join assumptions**
  • **Use `unmatched = "error"` to catch unexpected non-matches**
  • **Use `na_matches = "never"` to prevent silent NA joins**
  • **Use `tidylog::` prefix interactively to verify join results**
# Validate 1:1 relationship — errors if violated
inner_join(x, y, by = join_by(id),
  relationship = "one-to-one")

# Validate many-to-one (left has duplicates, right does not)
left_join(transactions, companies, by = join_by(company == id),
  relationship = "many-to-one")

# Ensure all rows from left match something in right
inner_join(x, y, by = join_by(id),
  unmatched = "error")

# Prevent NA values from matching each other silently
left_join(x, y, by = join_by(id),
  na_matches = "never")

# Combine for strict joins
inner_join(x, y, by = join_by(id),
  relationship = "one-to-one",
  unmatched = "error",
  na_matches = "never")

# Interactive verification with tidylog
# tidylog prints a summary of rows matched/dropped
tidylog::inner_join(x, y, by = join_by(id))

Data Masking and Tidy Selection

  • **Understand the difference between data masking and tidy selection**
  • **Use `{{}}` (embrace) for function arguments**
  • **Use `.data[[]]` for character vectors**
# Data masking functions: arrange(), filter(), mutate(), summarise()
# Tidy selection functions: select(), relocate(), across()

# Function arguments - embrace with {{}}
my_summary <- function(data, group_var, summary_var) {
  data |>
    group_by({{ group_var }}) |>
    summarise(mean_val = mean({{ summary_var }}))
}

# Character vectors - use .data[[]]
for (var in names(mtcars)) {
  mtcars |> count(.data[[var]]) |> print()
}

# Multiple columns - use across()
data |>
  summarise(across({{ summary_vars }}, ~ mean(.x, na.rm = TRUE)))

Modern Grouping and Column Operations

  • **Use `.by` for per-operation grouping (dplyr 1.1+)**
  • **Use `pick()` for column selection inside data-masking functions**
  • **Use `across()` for applying functions to multiple columns**
  • **Use `reframe()` for multi-row summaries**
# Good - Per-operation grouping (always returns ungrouped)
data |>
  summarise(mean_value = mean(value), .by = category)

# Good - Multiple grouping variables
data |>
  summarise(total = sum(revenue), .by = c(company, year))

# Good - pick() for column selection
data |>
  summarise(
    n_x_cols = ncol(pick(starts_with("x"))),
    n_y_cols = ncol(pick(starts_with("y")))
  )

# Good - across() for applying functions
data |>
  summarise(across(where(is.numeric), mean, .names = "mean_{.col}"), .by = group)

# Good - reframe() for multi-row results
data |>
  reframe(quantiles = quantile(x, c(0.25, 0.5, 0.75)), .by = group)

# Avoid - Old persistent grouping pattern
data |>
  group_by(category) |>
  summarise(mean_value = mean(value)) |>
  ungroup()

NA-Safe Row Filtering

  • **Use `filter_out()` instead of negating conditions** — negation (`!condition`) silently drops NAs
  • **Use `when_any()` and `when_all()` for multi-column OR/AND filters (dplyr 1.2+)**
# Problem: negation silently drops rows where condition is NA
filter(data, !(value < 0))       # drops rows where value is NA — silent!

# Good - filter_out() passes NAs through safely
filter_out(data, value < 0)      # rows where value is NA are kept

# Good - when_any() for OR across columns (dplyr 1.2+)
filter(data, when_any(x, y, z, \(col) col > 0))  # any column > 0

# Good - when_all() for AND across columns
filter(data, when_all(x, y, z, \(col) !is.na(col)))  # no NAs in any

# Avoid - verbose base patterns
filter(data, !(value < 0) | is.na(value))   # workaround, not idiomatic

Recoding and Conditional Updates

  • **Use `replace_when()` for in-place conditional updates** — avoids `case_when()` with `.default = x`
  • **Use `case_when()` with `.unmatched = "error"` when all cases should be handled**
# Good - replace_when() for in-place updates (type-stable, NAs unaffected)
mutate(data, status = replace_when(status,
  value < 0  ~ "negative",
  value == 0 ~ "zero"
))

# Avoid - case_when() requires restating the variable in .default
mutate(data, status = case_when(
  value < 0  ~ "negative",
  value == 0 ~ "zero",
  .default   = status    # repetitive
))

# Good - case_when() with strict exhaustiveness check
mutate(data, grade = case_when(
  score >= 90 ~ "A",
  score >= 80 ~ "B",
  s
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
rlang-patterns

rlang-patterns

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

@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 tidyverse-patterns.

Which coding agents can use tidyverse-patterns?
tidyverse-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.