| name | r-project |
| description | Use when working on an R project — editing `DESCRIPTION`, `NAMESPACE`, `renv.lock`, R scripts, R Markdown, testthat tests, roxygen2 docs, or Bioconductor packages. Also use when setting up a new R project, troubleshooting `install.packages` / `BiocManager`, or preparing an R package for CRAN/Bioc submission. |
R projects in bioinformatics
Three flavors — pick one deliberately
- Analysis project — R scripts + R Markdown, no package structure. Has
renv/, data/, analysis/, results/. For one-off studies.
- Package (CRAN-style) —
DESCRIPTION, R/, man/, tests/. For
reusable code.
- Bioconductor package — package with Bioc-specific conventions: S4
classes, Bioc release cadence, BiocCheck compliance.
.claude/project.yml declares which one. Don't mix conventions — analysis
projects with a half-baked DESCRIPTION are the worst of both worlds.
Analysis project layout
project/
├── renv.lock # locked versions
├── renv/ # renv library (gitignored except activate.R)
├── .Rprofile # auto-activates renv
├── R/ # shared functions, sourced by scripts
├── analysis/ # numbered scripts: 01-load.R, 02-qc.R, ...
├── reports/ # .Rmd → .html knits
├── data/ # raw, read-only
├── data-derived/ # outputs of analysis/ (gitignored usually)
└── results/ # final figures, tables (tracked in git)
- Scripts in
analysis/ are numbered and run top-to-bottom. Each one should
be independently re-runnable given the outputs of earlier ones.
R/ holds functions shared between scripts. source("R/utils.R") at the
top of each analysis/*.R.
data/ is read-only. Never overwrite. Copy, transform, write to
data-derived/.
Package layout (Bioc-aware)
pkg/
├── DESCRIPTION
├── NAMESPACE # generated by roxygen2
├── R/ # .R source files; one file per topic, not per function
├── man/ # generated; don't edit
├── tests/testthat/
├── vignettes/
├── inst/extdata/ # small test data (<1 MB per file)
└── .Rbuildignore
DESCRIPTION essentials
Package: pkgname
Type: Package
Title: Short Title Case, Max 65 Chars, No Period
Version: 0.1.0
Authors@R:
person("Petr", "Novak", email = "petr@umbr.cas.cz",
role = c("aut", "cre"),
comment = c(ORCID = "0000-0000-0000-0000"))
Description: One paragraph. Starts with a capital. Ends with a period.
Wrapped at 80 cols, indented four spaces on continuation lines.
Do not repeat the Title here.
License: GPL-3
Encoding: UTF-8
LazyData: true
Depends:
R (>= 4.4.0)
Imports:
Biostrings (>= 2.70.0),
GenomicRanges,
data.table
Suggests:
testthat (>= 3.0.0),
knitr,
rmarkdown
biocViews: Genetics, SequenceMatching, Annotation
VignetteBuilder: knitr
RoxygenNote: 7.3.1
Config/testthat/edition: 3
Imports: = used by the package, loaded with the package.
Suggests: = optional, used only by tests/vignettes/optional features.
Gate them with requireNamespace("pkg", quietly = TRUE).
Depends: = avoid for anything except R itself. Attaches to user
search path. Disruptive.
biocViews: required for Bioconductor submission.
renv
renv::init()
renv::snapshot()
renv::restore()
Things to know:
renv/library/ is platform-specific. Don't commit it. renv.lock is
platform-portable (sort of).
renv::status() before every commit. Flags out-of-sync state.
- If you use Bioc:
options(repos = BiocManager::repositories()) in
.Rprofile so renv can find Bioc packages.
testthat patterns
test_that("parse_gff handles empty files", {
tmp <- tempfile()
writeLines(character(0), tmp)
expect_equal(nrow(parse_gff(tmp)), 0)
})
test_that("parse_gff rejects malformed rows", {
tmp <- tempfile()
writeLines("not\ta\tvalid\tgff", tmp)
expect_error(parse_gff(tmp), "malformed")
})
- One
test_that = one behavior. Multi-assertion tests hide failures.
- Name files
test-<topic>.R. devtools finds them automatically.
- Test data lives in
inst/extdata/ (shipped) or tests/testthat/fixtures/
(test-only).
roxygen2 — the minimum that passes R CMD check
parse_gff <- function(path, feature = NULL) {
}
Missing @return, @param for every arg, or @examples that error will
make R CMD check fail.
Common errors and fixes
| R CMD check warning | Fix |
|---|
| "no visible binding for global variable X" | Use .data$X in dplyr, or utils::globalVariables() |
| "undefined exports: X" | Add @export or remove from NAMESPACE |
| "examples with CPU time > 5s" | Wrap in \dontrun{} or move to vignette |
| "Imports includes X but no X:: calls" | Remove from Imports or add X:: calls |
| "License stub mismatches DESCRIPTION" | Update LICENSE file or DESCRIPTION License: |
What not to do
source() between files instead of using the package. Makes
R CMD check impossible later.
library(foo) inside package code. Use foo::bar() or @importFrom.
- Call
setwd() inside scripts. Use here::here() or relative paths from
project root.
- Commit
.Rhistory, .Rproj.user/, renv/library/.