| name | r-package-development |
| description | R package development guide covering dependencies, API design, testing, and documentation. Use when developing R packages. |
R Package Development Decision Guide
Dependencies, API design, testing, documentation, and best practices for R packages
Dependency Strategy
When to Add Dependencies vs Base R
str_detect(x, "pattern")
length(x) > 0
parse_dates(x)
x + 1
Tidyverse Dependency Guidelines
dplyr
purrr
stringr
tidyr
lubridate
forcats
readr
ggplot2
tidyverse
shiny
Dependency Specification in DESCRIPTION
# Strong dependencies (required)
Imports:
dplyr (>= 1.1.0),
rlang (>= 1.0.0)
# Suggested dependencies (optional)
Suggests:
testthat (>= 3.0.0),
knitr,
rmarkdown
# Enhanced functionality (optional but loaded if available)
Enhances:
data.table
API Design Patterns
Function Design Strategy
my_summarise <- function(.data, ..., .by = NULL) {
}
my_select <- function(.data, cols) {
.data |> select({{ cols }})
}
my_mutate <- function(.data, ..., .by = NULL) {
.data |> mutate(..., .by = {{ .by }})
}
my_function <- function(.data) {
result tibbleas_tibble
Input Validation Strategy
user_function <- function(x, threshold = 0.5) {
if (!is.numeric(x)) stop("x must be numeric")
if (!is.numeric(threshold) || length(threshold) != 1) {
stop("threshold must be a single number")
}
}
.internal_function <- function(x, threshold) {
}
safe_function x y
x vec_castx double
y vec_casty double
Error Handling Patterns
if (length(x) == 0) {
cli::cli_abort(
"Input {.arg x} cannot be empty.",
"i" = "Provide a non-empty vector."
)
}
validate_input <- function(x, call = caller_env()) {
if (!is.numeric(x)) {
cli::cli_abort("Input must be numeric", call = call)
}
}
Error Classes
my_error <- function(message, ..., call = caller_env()) {
cli::cli_abort(
message,
...,
class = "my_package_error",
call = call
)
}
validation_error <- function(message, ..., call = caller_env()) {
cli::cli_abort(
message,
...,
class = c("validation_error", "my_package_error"),
call = call
)
}
When to Create Internal vs Exported Functions
Export Function When
process_data <- function(.data, ...) {
}
Keep Function Internal When
.validate_input <- function(x, y) {
}
.compute_metrics <- function(data) { ... }
Testing and Documentation Strategy
Testing Levels
test_that("function handles edge cases", {
expect_equal(my_func(c()), expected_empty_result)
expect_error(my_func(NULL), class = "my_error_class")
})
test_that("pipeline works end-to-end", {
result <- data |>
step1() |>
step2() |>
step3()
expect_s3_class(result, "expected_class")
})
test_that("function properties hold", {
})
Test File Organization
tests/
testthat/
test-validation.R # Input validation tests
test-processing.R # Core processing tests
test-output.R # Output format tests
test-integration.R # End-to-end tests
helper-fixtures.R # Shared test fixtures
testthat.R # Test runner
Snapshot Testing
test_that("summary output is correct", {
expect_snapshot(summary(my_object))
})
test_that("errors are informative",
expect_snapshot(my_function(bad_input), error = TRUE)
})
Documentation Priorities
roxygen2 Documentation
process_data <- function(data, vars, .by = NULL) {
}
Package Structure
Recommended Directory Layout
mypackage/
DESCRIPTION
NAMESPACE
LICENSE
README.md
R/
utils.R # Internal utilities
validation.R # Input validation
core.R # Core functionality
methods.R # S3/S7 methods
zzz.R # .onLoad, .onAttach
man/ # Generated by roxygen2
tests/
testthat/
testthat.R
vignettes/
getting-started.Rmd
inst/
extdata/ # Example data files
data/ # Package data (lazy-loaded)
data-raw/ # Scripts to create package data
DESCRIPTION Best Practices
Package: mypackage
Title: What The Package Does (One Line)
Version: 0.1.0
Authors@R:
person("First", "Last", email = "email@example.com",
role = c("aut", "cre"))
Description: A longer description that spans multiple lines.
Use four spaces for continuation lines.
License: MIT + file LICENSE
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
Imports:
dplyr (>= 1.1.0),
rlang (>= 1.0.0)
Suggests:
testthat (>= 3.0.0)
Config/testthat/edition: 3
Release Checklist
devtools::check()
devtools::test()
devtools::document()
urlchecker::url_check()
spelling::spell_check_package()
usethis::use_version("minor")
devtools::check(remote = TRUE, manual = TRUE)
Common Package Development Mistakes
library(dplyr)
dplyr::filter(data, x > 0)
options(my_option = TRUE)
old_opts <- options(my_option = TRUE)
on.exit(options(old_opts), add = TRUE)
read.csv("/home/user/data.csv")
system.file("extdata", "data.csv", package = "mypackage")