Packages: using and writing them

R · Advanced · 15 min

Packages are how R code is shared, tested and documented, even inside a single team.

Install from CRAN with install.packages("pkg"), from Bioconductor with BiocManager::install, and from GitHub with remotes::install_github("user/repo") or pak::pak(). library(pkg) attaches a package to the search path; pkg::fun() calls one function without attaching, which is clearer in scripts and required inside packages. When two attached packages export the same name, the last attached wins (masking), which is another reason to prefer ::.

For reproducible projects, renv records exact package versions in a lockfile (renv::init(), renv::snapshot(), renv::restore()), so collaborators and servers get the same library.

A package is a directory with a standard layout:

  • DESCRIPTION: name, version, authors, and dependencies in Imports.
  • NAMESPACE: which functions are exported and which are imported, usually generated by roxygen2 comments (#' @export).
  • R/ for code, tests/testthat/ for tests, man/ for generated help, vignettes/ for long-form docs.

The usethis and devtools packages automate the workflow: usethis::create_package(), usethis::use_r("file"), devtools::load_all() to reload during development, devtools::document(), devtools::test() and devtools::check(), which runs R CMD check just as CRAN does.

Example

# Uses only base R: a package-shaped directory and the :: operator
print(stats::median(c(5, 1, 9)))           #> [1] 5
print("package:stats" %in% search())       #> [1] TRUE
print(exists("filter"))                    #> [1] TRUE (stats::filter)
print(environmentName(environment(median))) #> [1] "stats"

dir <- file.path(tempdir(), "tempconv")
dir.create(file.path(dir, "R"), recursive = TRUE, showWarnings = FALSE)
writeLines(c("Package: tempconv", "Version: 0.1.0", "Title: Convert Temperatures",
             "Imports: stats", "License: MIT"), file.path(dir, "DESCRIPTION"))
writeLines("export(c_to_f)", file.path(dir, "NAMESPACE"))
writeLines(c("#' @export", "c_to_f <- function(c) c * 9 / 5 + 32"),
           file.path(dir, "R", "convert.R"))
print(sort(list.files(dir, recursive = TRUE)))
#> [1] "DESCRIPTION" "NAMESPACE"   "R/convert.R"
# then: devtools::load_all(dir); devtools::check(dir)

Remember

  • pkg::fun() avoids masking and is required inside packages
  • renv pins package versions per project
  • A package = DESCRIPTION + NAMESPACE + R/ + tests/
  • usethis/devtools automate create, document, test and check

Watch out for

  • Calling library() inside package code instead of declaring dependencies in DESCRIPTION Imports.

Check yourself

  1. Two attached packages both export filter(). Which one does filter() call?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: cran.r-project.org/doc/manuals/r-release/R-exts.html

Builds on

Unlocks

Nothing builds on this yet.