Writing Tidyverse R
This skill covers modern tidyverse patterns for R 4.3+ and dplyr 1.1+, style guidelines, and migration from legacy patterns.
Core Principles
- Use modern tidyverse patterns - Prioritize dplyr 1.1+ features, native pipe, and current APIs
- Write readable code first - Optimize only when necessary
- Follow tidyverse style guide - Consistent naming, spacing, and structure
Pipe Usage
Always use native pipe |> instead of magrittr %>%
R 4.3+ provides all needed features. See pipe-examples.md for usage patterns.
Join Syntax (dplyr 1.1+)
Use join_by() instead of character vectors for joins
Modern join syntax supports:
- Equality joins:
join_by(company == id)
- Inequality joins:
join_by(company == id, year >= since)
- Rolling joins:
join_by(company == id, closest(year >= since))
See join-examples.md for complete patterns.
Multiple Match Handling
Use multiple and unmatched arguments for quality control:
multiple = "error" - Expect 1:1 matches
multiple = "all" - Allow multiple matches explicitly
unmatched = "error" - Ensure all rows match
Data Masking vs Tidy Selection
Understand the difference:
- Data masking functions:
arrange(), filter(), mutate(), summarise()
- Tidy selection functions:
select(), relocate(), across()
Key patterns:
- Use
{{}} (embrace) for function arguments
- Use
.data[[]] for character vectors
- Use
across() for multiple columns
See data-masking-examples.md for patterns.
Modern Grouping and Column Operations
Use .by for per-operation grouping (dplyr 1.1+)
This replaces the old group_by() |> ... |> ungroup() pattern.
Additional modern operations:
pick() - Column selection inside data-masking functions
across() - Apply functions to multiple columns
reframe() - Multi-row summaries
See grouping-examples.md for complete examples.
String Manipulation with stringr
Use stringr over base R string functions
Benefits:
- Consistent
str_ prefix
- String-first argument order
- Pipe-friendly and vectorized
See stringr-examples.md for common patterns and base R equivalents.
Style Guide Essentials
Object Names
- Use snake_case for all names
- Variable names = nouns, function names = verbs
- Avoid dots except for S3 methods
Good: day_one, calculate_mean, user_data
Avoid: DayOne, calculate.mean, userData
Spacing and Layout
See style-examples.md for proper spacing and pipe formatting.
Naming and Arguments
- Use snake_case for variables and functions
- Prefix non-standard arguments with
. (e.g., .data, .by)
Anti-Patterns to Avoid
Legacy Patterns
| Avoid |
Use Instead |
%>% |
` |
by = c("a" = "b") |
by = join_by(a == b) |
sapply() |
map_*() |
| `group_by() |
> ... |
Performance Anti-Patterns
- Don't grow objects in loops - Pre-allocate or use purrr
- Don't use
sapply() - Type-unstable, use map_*() instead
See anti-patterns.md for examples of what to avoid and correct alternatives.
Migration Reference
Base R to Modern Tidyverse
| Base R |
Modern Tidyverse |
subset(data, condition) |
filter(data, condition) |
data[order(data$x), ] |
arrange(data, x) |
aggregate(x ~ y, data, mean) |
summarise(data, mean(x), .by = y) |
sapply(x, f) |
map(x, f) |
grepl("pattern", text) |
str_detect(text, "pattern") |
gsub("old", "new", text) |
str_replace_all(text, "old", "new") |
Old to New Tidyverse Patterns
| Old Pattern |
New Pattern |
data %>% function() |
`data |
| `group_by(x) |
> summarise() |
by = c("a" = "b") |
by = join_by(a == b) |
gather()/spread() |
pivot_longer()/pivot_wider() |
map_dfr(x, f) |
`map(x, f) |
separate(col, into = ...) |
separate_wider_delim() |
See migration-examples.md for complete migration patterns.
source: Sarah Johnson's gist https://gist.github.com/sj-io/3828d64d0969f2a0f05297e59e6c15ad
1---2name: writing-tidyverse-r3description: Modern tidyverse patterns, style guide, and migration guidance for R development. Use this skill when writing R code with dplyr, reviewing tidyverse code, updating legacy R code to modern patterns, or enforcing consistent style. Covers native pipe usage, join_by() syntax, .by grouping, pick/across/reframe operations, tidy selection, stringr patterns, naming conventions, spacing, and migration from base R or older tidyverse APIs.4---56# Writing Tidyverse R78This skill covers modern tidyverse patterns for R 4.3+ and dplyr 1.1+, style guidelines, and migration from legacy patterns.910## Core Principles11121. **Use modern tidyverse patterns** - Prioritize dplyr 1.1+ features, native pipe, and current APIs132. **Write readable code first** - Optimize only when necessary143. **Follow tidyverse style guide** - Consistent naming, spacing, and structure1516## Pipe Usage1718**Always use native pipe `|>` instead of magrittr `%>%`**1920R 4.3+ provides all needed features. See [pipe-examples.md](references/pipe-examples.md) for usage patterns.2122## Join Syntax (dplyr 1.1+)2324**Use `join_by()` instead of character vectors for joins**2526Modern join syntax supports:27- Equality joins: `join_by(company == id)`28- Inequality joins: `join_by(company == id, year >= since)`29- Rolling joins: `join_by(company == id, closest(year >= since))`3031See [join-examples.md](references/join-examples.md) for complete patterns.3233## Multiple Match Handling3435Use `multiple` and `unmatched` arguments for quality control:36- `multiple = "error"` - Expect 1:1 matches37- `multiple = "all"` - Allow multiple matches explicitly38- `unmatched = "error"` - Ensure all rows match3940## Data Masking vs Tidy Selection4142Understand the difference:43- **Data masking functions**: `arrange()`, `filter()`, `mutate()`, `summarise()`44- **Tidy selection functions**: `select()`, `relocate()`, `across()`4546Key patterns:47- Use `{{}}` (embrace) for function arguments48- Use `.data[[]]` for character vectors49- Use `across()` for multiple columns5051See [data-masking-examples.md](references/data-masking-examples.md) for patterns.5253## Modern Grouping and Column Operations5455**Use `.by` for per-operation grouping (dplyr 1.1+)**5657This replaces the old `group_by() |> ... |> ungroup()` pattern.5859Additional modern operations:60- `pick()` - Column selection inside data-masking functions61- `across()` - Apply functions to multiple columns62- `reframe()` - Multi-row summaries6364See [grouping-examples.md](references/grouping-examples.md) for complete examples.6566## String Manipulation with stringr6768**Use stringr over base R string functions**6970Benefits:71- Consistent `str_` prefix72- String-first argument order73- Pipe-friendly and vectorized7475See [stringr-examples.md](references/stringr-examples.md) for common patterns and base R equivalents.7677## Style Guide Essentials7879### Object Names8081- **Use snake_case for all names**82- **Variable names = nouns, function names = verbs**83- **Avoid dots except for S3 methods**8485Good: `day_one`, `calculate_mean`, `user_data`86Avoid: `DayOne`, `calculate.mean`, `userData`8788### Spacing and Layout8990See [style-examples.md](references/style-examples.md) for proper spacing and pipe formatting.9192### Naming and Arguments9394- Use snake_case for variables and functions95- Prefix non-standard arguments with `.` (e.g., `.data`, `.by`)9697## Anti-Patterns to Avoid9899### Legacy Patterns100101| Avoid | Use Instead |102|-------|-------------|103| `%>%` | `|>` |104| `by = c("a" = "b")` | `by = join_by(a == b)` |105| `sapply()` | `map_*()` |106| `group_by() |> ... |> ungroup()` | `.by` argument |107108### Performance Anti-Patterns109110- **Don't grow objects in loops** - Pre-allocate or use purrr111- **Don't use `sapply()`** - Type-unstable, use `map_*()` instead112113See [anti-patterns.md](references/anti-patterns.md) for examples of what to avoid and correct alternatives.114115## Migration Reference116117### Base R to Modern Tidyverse118119| Base R | Modern Tidyverse |120|--------|------------------|121| `subset(data, condition)` | `filter(data, condition)` |122| `data[order(data$x), ]` | `arrange(data, x)` |123| `aggregate(x ~ y, data, mean)` | `summarise(data, mean(x), .by = y)` |124| `sapply(x, f)` | `map(x, f)` |125| `grepl("pattern", text)` | `str_detect(text, "pattern")` |126| `gsub("old", "new", text)` | `str_replace_all(text, "old", "new")` |127128### Old to New Tidyverse Patterns129130| Old Pattern | New Pattern |131|-------------|-------------|132| `data %>% function()` | `data |> function()` |133| `group_by(x) |> summarise() |> ungroup()` | `summarise(..., .by = x)` |134| `by = c("a" = "b")` | `by = join_by(a == b)` |135| `gather()/spread()` | `pivot_longer()/pivot_wider()` |136| `map_dfr(x, f)` | `map(x, f) |> list_rbind()` |137| `separate(col, into = ...)` | `separate_wider_delim()` |138139See [migration-examples.md](references/migration-examples.md) for complete migration patterns.140141source: Sarah Johnson's gist https://gist.github.com/sj-io/3828d64d0969f2a0f05297e59e6c15ad