| name | tidyverse-style |
| description | Apply the tidyverse style guide whenever the deliverable is R code, or text inside an R project, that must follow tidyverse conventions: writing new R functions, scripts, or packages; restyling or cleaning up existing .R files; reviewing R pull requests for style; and answering questions about how R code should be laid out (naming, spacing, indentation, line breaks, pipes, ggplot2 chains, braces, return(), comments). Also covers roxygen2 documentation, testthat file layout, cli error-message wording, NEWS.md bullets, and commit or PR messages for R packages. Do not use when the user only wants existing R code explained or walked through, a runtime error diagnosed, wrong results or empty joins debugged, or R tooling installed, and not for non-R languages or ordinary prose. |
| metadata | {"upstream":"https://github.com/tidyverse/style","upstream_commit":"2aed77e452a059f4b5c9c4a991d00ea0c5e70cab","license":"CC BY-SA 3.0 (the tidyverse team)"} |
Tidyverse style guide
Complete, compacted version of https://style.tidyverse.org (every rule kept; prose and
examples shortened). The verbatim chapters are in references/*.md, regenerated from
upstream by scripts/sync.py. Style guides are opinionated; the point is consistency, so
fewer decisions are needed. Tools: styler restyles code (RStudio add-in); lintr
checks it.
Rules most often missed
Verified failures from agents writing R without this skill:
- Base pipe
|>, never magrittr %>% (R >= 4.3 covers everything recommended).
- 80 characters per line, counted. One-line pipes and
map(xs, \(x) ...) calls are the usual
offenders; split them one argument per line, or extract a function.
- Pipe data into
ggplot(); never filter/slice inside the data argument.
- NEWS bullets end
(@user, #issue). in that order, with the parentheses before the full stop.
While in development a bullet is one line, however long; wrapping happens at release.
- Commit subject: under 50 characters, sentence case, no period;
Fixes #<n> (not "Closes")
in the body.
return(), stop(), break, next always get their own {} block, never a one-line if.
- Anonymous functions:
\(x) x + 1, not ~ .x + 1; never \() for multi-line or named functions.
- ASCII only in
.R files (local rule, below).
Local rules
Not part of style.tidyverse.org; kept here so they survive upstream syncs. Apply them with
the same force as the guide.
- ASCII only in R source files. No character above 0x7F anywhere in a
.R file: not in
code, comments, or string literals. That rules out smart quotes, em and en dashes,
non-breaking spaces, accented letters, box-drawing characters, and emoji, including
the info and cross bullet glyphs that cli renders (name the bullets "i" and "x" in
the cli call and let cli draw them). Files cross Windows and Linux machines with
different default encodings, R CMD check warns on non-ASCII in R/, and a stray
non-breaking space is invisible yet breaks parsing. When a non-ASCII character is
genuinely needed in a string, write it as an escape: "\u00e9" for e-acute,
"\u2014" for an em dash.
scripts/check.R reports every offending line.
Part 1: Analyses
Files
Names. Machine readable: no spaces, symbols, or special characters; all lower case; never
two names differing only in case; words delimited by - or _; extension .R. Human
readable: the name describes the contents (report-draft-notes.txt, not temp.r); closely
related files share one structure (fig-eda.png, fig-model-3.png). Sort correctly by
default: dates as yyyy-mm-dd (ISO 8601); numbers zero-padded so 11 does not sort before 2;
if order matters, number at the start, not the end (01-load-data.R,
02-exploratory-analysis.R). If you missed a step, rename all files rather than adding
02a, 02b. Never use "final" or similar in a name; rely on Git, or failing that, date the
file (report-2022-03-20.qmd, not FinalReport-2.qmd).
# Good # Bad
fit_models.R fit models.R
exploratory-data-analysis.R ExploratoryDataAnalysis.r
2025-01-01-report.Rmd jan 01 report.Rmd
Organisation. If a file can be given a concise name that still evokes its contents, the
organisation is good. Getting there is hard.
Internal structure. Break a file into chunks with commented lines of - and =. Load all
add-on packages together at the very top of the file; do not sprinkle library() calls
through the script or hide dependencies in .Rprofile.
Syntax
Object names
Only lowercase letters, numbers, and _; separate words with _ (snake case): day_one,
day_1, not DayOne, dayone. Reserve . for the S3 system (methods are
function.class; dots elsewhere give things like as.data.frame.data.frame()). If you are
cramming data into names (model_2018, model_2019), use a list or data frame instead.
Variables are nouns, functions are verbs. Concise and meaningful: day_one, not
first_day_of_the_month or djm1. Do not reuse names of common functions or variables:
never T <- FALSE, c <- 10, mean <- function(x) sum(x).
Spacing
- Commas: space after, never before:
x[, 1], not x[,1], x[ ,1], x[ , 1].
- Parentheses: no spaces inside or outside for function calls:
mean(x, na.rm = TRUE).
Space before and after () with if, for, while: if (debug) {. Space after () in
a function definition: function(x) {}, not function (x) {} or function(x){}.
- Embracing
{{ }} always has inner spaces: group_by({{ by }}), not {{by}}.
- Infix operators (
==, +, -, <-, = in arguments, etc.) always surrounded by
spaces: height <- (feet * 12) + inches. Exceptions, never spaced:
- high-precedence operators
::, :::, $, @, [, [[, ^, unary -, unary +,
: (sqrt(x^2 + y^2), df$z, 1:10);
- single-sided formulas whose right-hand side is a single identifier (
~foo;
tribble(~col1, ~col2, ...)), but a complex right-hand side does take a space:
~ .x + .y;
!! and !!! in tidy evaluation: call(!!xyz);
- the help operator:
?mean, package?stats.
- Extra spaces are fine to align
= or <- (mean = (a + b + c) / n under
total = a + b + c); never add space where it is not usually allowed.
Vertical space
Sparingly, to separate "thoughts" like paragraph breaks. No empty lines at the start or end
of a function; a single empty line only where needed to separate functions or pipes; an
empty line before a comment block often helps tie the comment to its code.
Function calls
-
Named arguments. Arguments are either data or details. Omit names of data
arguments; name every detail argument whose default you override:
mean(1:10, na.rm = TRUE), not mean(x = 1:10, , FALSE). Never partial-match:
rep(1:2, times = 3), not rep(1:2, t = 3).
-
Assignment in calls. Avoid: x <- f(); if (nzchar(x) < 1), not
if (nzchar(x <- f()) < 1). Only exception: functions that capture side effects,
output <- capture.output(x <- f()).
-
Long calls. Limit lines to 80 characters; running out of room means extract a
function or use early returns to reduce nesting. When a call does not fit, one line each
for the function name, every argument, and the closing ):
do_something_very_complicated(
something = "that",
requires = many,
arguments = "some of which may be long"
)
Unnamed common arguments may stay unnamed, but if that makes line lengths very
uneven, name them anyway (x = x, y = long_argument_name, ...). Closely related unnamed
arguments may share a line, typically so one line of code matches one line of output:
paste0(
"Requirement: ", requires, "\n",
"Result: ", result, "\n"
)
Braced expressions
{} defines the main hierarchy of R code (function bodies, control flow, calls such as
tryCatch() and test_that()). { is the last character on its line, with the related
code (the if clause, function declaration, trailing comma) on that same line; contents
indented two spaces; } first character on its line; else on the same line as }.
if (y < 0 && debug) {
message("y is negative")
}
test_that("call1 returns an ordered factor", {
expect_s3_class(call1(x, y), c("factor", "ordered"))
})
tryCatch(
{
x <- scan()
cat("Total: ", sum(x), "\n", sep = "")
},
interrupt = function(e) {
message("Aborted by user")
}
)
An empty braced expression is written {} with no space or blank line inside:
function(...) {}.
Control flow
-
Loops (for, while, repeat): the body must be braced, even one statement.
A waiting while loop may have an empty {} body.
-
If statements. A single-line if never contains braces and is only for very simple
expressions with no side effects and no control-flow change:
message <- if (x > 10) "big" else "small". Bad: if (x > 10) { "big" } else { "small" };
if (x > 0) message <- "big" else message <- "small"; if (x > 0) return(x). A multi-line
if must use braces (an unbraced if ... else over lines only parses inside {} or a
call). Avoid implicit coercion in the condition: if (length(x) > 0), not
if (length(x)). Never & or | in an if condition (they return vectors); always &&
and ||. ifelse(x, a, b) is not a drop-in for if (x) a else b: it is vectorised
(recycles a, b to length(x)) and eager (evaluates both).
-
Control flow modifiers (return(), stop(), break, next) always in their own
{} block:
if (y < 0) {
stop("Y is negative")
}
for (x in xs) {
if (is_done(x)) {
break
}
}
-
Switch. Prefer names to positions (never switch(y, 1, 2, 3)). Each element on its
own line unless all fit on one. Fall-through elements have a space after = (a = ,).
Provide a fall-through error unless input was validated earlier:
switch(x,
a = ,
b = 1,
c = 2,
stop("Unknown `x`", call. = FALSE)
)
Semicolons, assignment, data, comments
- Semicolons: never; not at line ends, not to join commands on one line.
- Assignment:
<-, not =: x <- 5.
- Strings:
" not '. Only exception: text containing double quotes and no single
quotes, 'Text with "quotes"'. Never 'Text with "double" and \'single\' quotes'.
- Logicals:
TRUE/FALSE, not T/F.
- Comments: every line starts
# (symbol plus one space). In analysis code, record
findings and decisions. If comments are needed to explain what the code does, rewrite the
code; if there are more comments than code, switch to R Markdown/Quarto.
Functions
-
Naming: verbs. add_row(), permute(); not row_adder(), permutation().
-
Anonymous functions: \(x) x + 1 for short lambdas defined inline in an argument.
map(xs, \(x) mean((x + 5)^2)) or function(x) ...; not map(xs, ~ mean((.x + 5)^2)).
Never \() for multi-line functions (use function(x) {) or for named functions
(cv <- function(x) {, not cv <- \(x) sd(x) / mean(x)). Avoid \() inside a pipe. Use
informative argument names.
-
Multi-line definitions. Each argument on its own line, in one of two forms.
Single-indent: arguments indented two spaces, ) and { together on a new line.
Hanging-indent: arguments aligned with the opening (, ) { on the last argument's
line. Never indent the continuation arguments only two spaces under a hanging first
argument (hides where the definition ends). An argument that will not fit on one line
should be reworked to be short and sweet.
long_function_name <- function(
a = "a long argument",
b = "another argument"
) {