| name | r-package |
| description | Develop R packages following best practices. Use when creating packages, writing package functions, documentation, or tests. |
R Package Development
Build R packages following tidyverse and CRAN best practices.
Package Structure
mypkg/
โโโ DESCRIPTION
โโโ NAMESPACE
โโโ R/
โ โโโ *.R
โโโ man/
โ โโโ *.Rd
โโโ tests/
โ โโโ testthat/
โโโ vignettes/
โโโ inst/
Core Rules
- Never use
library() or require() in package code
- Always use
pkg::fun() syntax for external functions
- Use roxygen2 for documentation and NAMESPACE management
- Run
devtools::check() before committing
Dependencies
DESCRIPTION Imports
Imports:
dplyr (>= 1.0.0),
rlang,
cli
Using Dependencies in Code
my_function <- function(df) {
df |>
dplyr::filter(x > 0) |>
dplyr::mutate(y = x * 2)
}
my_function <- function(df) {
df |>
filter(x > 0) |>
mutate(y = x * 2)
}
Documentation (roxygen2)
positive_mean <- function(x, na.rm = TRUE) {
x <- x[x > 0]
mean(x, na.rm = na.rm)
}
Key Tags
@export - make function available to users
@param - document each parameter
@return - describe return value
@examples - runnable examples
@noRd - internal function, no documentation
@keywords internal - document but hide from index
File Organization
- One main function per file, or group related functions
- File name matches primary function:
positive_mean.R
- Helper functions can share file with main function
R/utils.R for small utilities used across functions
Error Handling
Use rlang/cli for informative errors:
my_function <- function(x) {
if (!is.numeric(x)) {
cli::cli_abort(
"{.arg x} must be numeric, not {.obj_type_friendly {x}}."
)
}
}
State Management
Use withr for temporary state changes:
my_function <- function() {
withr::local_options(list(digits = 3))
withr::local_envvar(c(TZ = "UTC"))
}
my_function <- function() {
old <- options(digits = 3)
on.exit(options(old))
}
Testing (testthat)
Test File Structure
tests/
โโโ testthat.R
โโโ testthat/
โโโ test-positive-mean.R
โโโ test-utils.R
Writing Tests
testthat::test_that("positive_mean calculates correctly", {
testthat::expect_equal(positive_mean(c(1, 2, 3)), 2)
testthat::expect_equal(positive_mean(c(-1, 2, 4)), 3)
testthat::expect_equal(positive_mean(c(-1, -2)), NaN)
})
testthat::test_that("positive_mean handles NA", {
testthat::expect_equal(positive_mean(c(1, NA, 3)), 2)
testthat::expect_equal(positive_mean(c(1, NA, 3), na.rm = FALSE), NA_real_)
})
testthat::test_that("positive_mean errors on non-numeric", {
testthat::expect_error(positive_mean("a"), "must be numeric")
})
Build-time vs Run-time
Code outside functions runs at build time, not when users call functions:
data_path <- system.file("data", package = "mypkg")
get_data_path <- function() {
system.file("data", package = "mypkg")
}
Development Workflow
pkgload::load_all()
testthat::test_package()
1. Build package in terminal `R CMD BUILD <pkg-directory>`
2. Check package with `R CMD check <pkg>_<version>.tar.gz`
roxygen2::roxygenize()
renv::install()
CRAN Considerations
- No
Depends on packages (use Imports)
- Examples must run in < 5 seconds
- No writing to user directories without permission
- Use
\dontrun{} for examples that can't run on CRAN
- Check with
--as-cran flag
Anti-patterns
- Using
library() or require() in R/ files
- Using
source() to load code
- Modifying global options without restoration
- Using
setwd()
- Relying on side effects at build time
- Writing to user's home directory
- Using
::: to access internal functions from other packages
- Skipping
@return in documentation
References