R style and engineering practice
This page is the R counterpart to the Python guidance. It is deliberately short: for style, follow the Tidyverse style guide (which builds on the Google R style guide), and that’s most of it. IDM has no house R style rules beyond Tidyverse. What this page adds is IDM’s expectations for R engineering practice — dependencies, testing, project structure, documentation, and CI — organized by code tier.
Style
- Follow the Tidyverse style guide: snake_case names,
<-for assignment, meaningful function names (verbs), pipes for readable chains. - Enforce mechanically rather than by hand: lintr for static analysis (a
.lintrfile at the repo root, vialintr::use_lintr()), and Air (or styler) for formatting. Air is the modern, fast option (the R analogue ofruff format), configured viaair.toml(usethis::use_air()). - As with Python, the aim is not complete uniformity — these tools handle the mechanical part so review can focus on substance.
Managing R dependencies
The R equivalents of the Python dependency guidance:
- Packages (Tier 1): dependencies go in
DESCRIPTION(Imports, notDepends;Suggestsfor test/documentation-only dependencies), as loosely as possible — version bounds only when older versions are genuinely known not to work. Packages should not include a lock file. - Research code where results must be reproducible (Tier 2/3): use renv. A healthy renv setup commits
renv.lock,.Rprofile,renv/activate.R, andrenv/settings.json, andrenv::status()reports the project synchronized. Note that renv does not capture the R version itself beyond recording it — state the R version in the README too. - Lightweight alternative: for simple projects where full renv feels heavy, a
DESCRIPTIONfile plus a dated Posit Public Package Manager snapshot URL in the README gives approximate reproducibility at much lower cost. As with Python, “no lock artifact” is a legitimate choice for the project owner if results don’t need exact reproduction. - packrat is deprecated: if a project still uses
packrat/, migrate withrenv::migrate().
Testing
- Use testthat (edition 3:
Config/testthat/edition: 3inDESCRIPTION). Test files intests/testthat/test-*.R, mirroring the files inR/. - testthat also works outside packages via
testthat::test_dir()— shared research code should have at least a few tests covering the core scientific functions. - As with Python, tests should primarily check end-to-end scientific correctness (parameter behavior, edge cases), not just units; see the testing guidance.
- For packages,
R CMD checkshould pass with 0 errors, 0 warnings, 0 notes.
Project structure (research code)
- One repo = one project: never
setwd()orrm(list = ls())in committed code; build all paths with here::here()relative to the project root, never absolute paths. - Factor reusable functions into
R/, separate from the scripts that run the analysis; number run scripts if order matters, or use a targets pipeline (_targets.R) for anything with multiple slow, interdependent steps. - Keep raw data immutable and separate from derived data (for example,
data/raw/vsdata/derived/). set.seed()wherever randomness affects results; keep credentials in.Renviron(gitignored), accessed viaSys.getenv().
Documentation
- Document functions with roxygen2 comments; for packages, every exported function needs
@param,@return, and a runnable@examples, andNAMESPACEis generated (never hand-edited). - Packages: README (ideally
README.Rmdrendered toREADME.md),NEWS.mdas the changelog, at least one vignette, and a pkgdown site. - Research code: a README with purpose, installation (
renv::restore()), how to run, and repo structure — see the documentation guidance.
Continuous integration
- Use r-lib/actions (v2) for GitHub Actions; install standard workflows with
usethis::use_github_action(). - Packages (Tier 1):
R-CMD-check.yaml(the standard 3-OS × R release/devel/oldrel matrix), plus test coverage, lint, and pkgdown workflows. - Research code (Tier 2): a single workflow that restores the renv library (
r-lib/actions/setup-renv) and runs the tests or pipeline is usually enough.
Expectations by tier
Using the IDM code tiers (Tier 1 = library/package, Tier 2 = shared research code, Tier 3 = one-off):
- Tier 3: git repo; README with purpose and entry point; dependencies discoverable (
library()calls at the top of scripts); nosetwd()/absolute paths;set.seed()where randomness matters; no secrets in the repo. - Tier 2: all of the above, plus healthy renv (or
DESCRIPTION+ dated snapshot); functions factored intoR/;here::here()for paths; some testthat tests; Tidyverse style with lintr; LICENSE file; raw vs derived data separated; CI recommended. - Tier 1: all of the above, plus the standard package skeleton (
DESCRIPTION, generatedNAMESPACE,man/,tests/testthat/,vignettes/,NEWS.md);R CMD check0/0/0; full CI; pkgdown site; published on CRAN (or r-universe as an interim); nolibrary()calls insideR/(use namespacing or@importFrom).