Historical Context & Motivation
The story of R packages is inseparable from the evolution of R itself as a language designed for statistical computing and data analysis. In the early days of S (R's predecessor at Bell Labs), statistical routines were shared informally — researchers passed around scripts, functions lived in personal libraries, and reproducibility depended on social convention rather than tooling. As the community grew, this ad-hoc approach became untenable: version conflicts emerged, dependencies were undocumented, and distributing code to collaborators required painstaking manual effort. The package system was the R Core Team's answer to this growing chaos, providing a standardized structure for bundling functions, documentation, data, and metadata into a single distributable unit.
The central question that package structure addresses is deceptively simple: how do you transform a collection of R functions, datasets, and documentation into something that another person (or your future self) can install, load, and rely upon with a single command? The answer is a well-defined directory layout and a set of metadata files that R's tooling can parse, validate, and build automatically. Understanding this structure conceptually — before writing a single line of package code — provides the mental model you need to navigate the ecosystem effectively.
Core Principles & Definitions
An R package is not merely a folder of .R files. It is a formally structured unit of software governed by conventions that the R build system enforces. Several foundational principles undergird the design decisions behind this structure, and grasping them will make every subsequent detail click into place.
Convention over Configuration
R/, documentation in man/, and data in data/. By adhering to these conventions, the build system can automate compilation, checking, and installation without custom configuration.Separation of Concerns
Metadata-Driven Discovery
Encapsulation via Namespaces
Reproducibility by Design
Visual Explanation — The Package Directory Tree
The canonical structure of an R package can be visualized as a directory tree. Every package contains a small set of mandatory components and a larger set of optional directories that provide additional capabilities. The following diagram illustrates the standard layout, color-coded by component type. Blue elements are mandatory for any valid R package; other colors represent optional but common components.
mypackage. Blue-outlined components (DESCRIPTION, NAMESPACE, R/) are mandatory. Purple, green, gold, pink, and orange components are optional but standard in mature packages.Notice the hierarchy in the diagram. At the top level sits the package root directory, whose name typically matches the package name declared in DESCRIPTION. Within this root, the three mandatory elements — DESCRIPTION, NAMESPACE, and R/ — form the minimal viable package. You could technically build and install a package with just these three, though any CRAN submission would also require documentation in man/. The optional directories each extend the package's capabilities: data/ bundles datasets that users can load with data(), tests/ holds automated test suites (commonly using the testthat framework), vignettes/ provides long-form tutorials in R Markdown, and src/ houses compiled source code for performance-critical routines written in C, C++, or Fortran.
How the Build System Processes Package Structure
Understanding the directory layout is only half the picture. Equally important is grasping how R's build system traverses this structure when you run commands like R CMD build, R CMD check, or R CMD INSTALL. The build pipeline is deterministic: it reads files in a specific order, validates them against expected formats, and produces either a source tarball or an installed package in your local library. If we think of the directory structure as a blueprint, the build system is the construction crew that follows it.
The Build Pipeline in Three Phases
library().During Phase 1 (Build), the system reads DESCRIPTION first to determine the package name, version, and which R files to include. It then parses NAMESPACE to understand export/import declarations, collates all .R files in the R/ directory (respecting the optional Collate field in DESCRIPTION if source-order dependencies exist), builds any vignettes, and compresses everything into a .tar.gz source tarball. Phase 2 (Check) is the quality gate: R CMD check unpacks the tarball and runs over 20 categories of validation — from verifying that every exported function has documentation to executing all code examples and test suites. CRAN requires zero errors and zero warnings before accepting a submission. Phase 3 (Install) byte-compiles the R source, compiles any C/C++/Fortran code in src/, and installs the finished package into a local library path, after which library(mypackage) makes it available in your R session.
devtools::load_all() during iterative development, which simulates installation by sourcing all files in R/ and processing the NAMESPACE without creating a tarball. This shortcut makes the build–check–install cycle rapid, but understanding the full pipeline matters when you encounter check failures or deployment issues.Detailed Breakdown — Key Files and Directories
Now that we have the high-level map, let us zoom into each component of the package structure and understand its role, format, and the conventions that govern it. The following table provides a comprehensive reference for every standard file and directory in an R package.
| Component | Type | Purpose | Required? |
|---|---|---|---|
DESCRIPTION | File | Package metadata: name, version, title, author, license, dependencies (Imports, Suggests, Depends), and description. Uses Debian Control File (DCF) format. | Yes |
NAMESPACE | File | Declares exported functions (public API) and imported functions from other packages. Generated automatically by roxygen2 in modern workflows. | Yes |
R/ | Directory | Contains all R source files (.R). Each file typically defines one or more functions. Files are sourced into the package namespace at build time. | Yes |
man/ | Directory | Contains .Rd (R documentation) files — one per documented object. These are rendered by the help system when users call ?function_name. Typically auto-generated by roxygen2. | Effectively yes (CRAN requires it) |
data/ | Directory | Stores bundled datasets in .rda or .RData format. Users access them via data("dataset_name"). A data-raw/ directory (not shipped) often contains the scripts that produced these files. | No |
tests/ | Directory | Contains test scripts run during R CMD check. Typically structured for the testthat framework with a tests/testthat/ subdirectory and a tests/testthat.R driver script. | No (strongly recommended) |
vignettes/ | Directory | Holds long-form documentation, typically R Markdown (.Rmd) or Sweave (.Rnw) files. Vignettes are built into HTML/PDF during package build and are accessible via browseVignettes(). | No |
src/ | Directory | Contains C, C++, or Fortran source files compiled into shared libraries (.so/.dll). A Makevars file can customize compilation flags. Rcpp packages use this extensively. | No |
inst/ | Directory | Arbitrary files copied as-is into the installed package. Common subdirectories include inst/extdata/ for external data files and inst/shiny/ for Shiny app files. | No |
The DESCRIPTION File — A Closer Look
The DESCRIPTION file deserves special attention because it is the single most important metadata file in any package. Written in Debian Control File (DCF) format — a simple key-value syntax — it declares everything the build system and repository need to know about your package. The Imports field lists packages that your code uses and that must be installed; Suggests lists packages used only in tests, vignettes, or examples; and Depends (rarely used in modern packages) attaches packages to the user's search path. Understanding this tripartite dependency declaration is crucial because it directly affects how install.packages() resolves the dependency graph.
Worked Example — Creating a Minimal Package
Let us walk through creating a minimal but valid R package from scratch. Our package, tempconv, will provide two functions for converting temperatures between Celsius and Fahrenheit. This example illustrates how each component of the package structure comes together.
tempconv/ with the required subdirectory R/ inside it. Alternatively, call usethis::create_package("tempconv") to scaffold everything automatically. The resulting structure starts as: tempconv/DESCRIPTION, tempconv/NAMESPACE, and tempconv/R/.Package: tempconv
Title: Temperature Conversion Utilities
Version: 0.1.0
Authors@R: person("Jane", "Doe", email = "jane@example.com", role = c("aut", "cre"))
Description: Provides functions to convert between Celsius and Fahrenheit scales.
License: MIT + file LICENSE
Encoding: UTF-8
RoxygenNote: 7.3.1
Notice the DCF key-value pairs. The Authors@R field uses an R expression to declare authorship with roles (author and creator).R/conversions.R containing the two functions with roxygen2 documentation comments:
#' Convert Celsius to Fahrenheit
#' @param celsius Numeric vector of temperatures in Celsius.
#' @return Numeric vector of temperatures in Fahrenheit.
#' @export
c_to_f <- function(celsius) {
celsius * 9/5 + 32
}
#' Convert Fahrenheit to Celsius
#' @param fahrenheit Numeric vector of temperatures in Fahrenheit.
#' @return Numeric vector of temperatures in Celsius.
#' @export
f_to_c <- function(fahrenheit) {
(fahrenheit - 32) * 5/9
}
The #' @export tag tells roxygen2 to add these functions to the NAMESPACE exports.devtools::document() (which internally calls roxygen2::roxygenise()). This reads the roxygen comments in R/conversions.R, generates man/c_to_f.Rd and man/f_to_c.Rd, and updates NAMESPACE with:
export(c_to_f)
export(f_to_c)devtools::check() to execute the full R CMD check suite. If there are zero errors and zero warnings, build the tarball with devtools::build() and install locally with devtools::install(). After installation, test it:
library(tempconv)
c_to_f(100) # returns 212
f_to_c(32) # returns 0Strengths, Limitations, and Alternatives
R's package system is remarkably mature and well-integrated into the language, but it is not without tradeoffs. Understanding its strengths and limitations in context helps you make informed decisions about when to package your code versus when simpler alternatives (like sourced scripts or R projects) might suffice.
| Aspect | Strength | Limitation |
|---|---|---|
| Standardization | Uniform structure means any R user can navigate any package. Tools like devtools, roxygen2, and testthat all assume this structure. | Rigid conventions can feel heavy for small, one-off projects. Creating a package for a single utility function adds overhead. |
| Dependency Management | The DESCRIPTION file provides declarative dependency specification. install.packages() handles transitive dependencies automatically. | No built-in lock file mechanism (unlike npm's package-lock.json). Tools like renv fill this gap but are separate from the package system. |
| Documentation | Integrated help system (?) tied directly to package functions. Vignettes provide long-form documentation. roxygen2 enables literate-programming-style inline docs. | The .Rd format is verbose and archaic when written manually. roxygen2 mitigates this, but the underlying format remains complex. |
| Testing | R CMD check provides comprehensive automated validation. testthat integrates naturally with the package structure. | No built-in CI/CD — you must configure GitHub Actions, Travis, or similar systems separately for continuous testing. |
| Distribution | CRAN provides a trusted, curated repository with automated cross-platform builds. GitHub installation via devtools::install_github() is trivial. | CRAN submission involves a manual review process with strict policies. Private package hosting requires additional infrastructure (e.g., Posit Package Manager). |
Connection to Advanced Package Development
The conceptual understanding of package structure you have built in this lesson is the foundation upon which several advanced topics rest. As you progress, you will encounter more sophisticated uses of the package machinery — from compiled code integration to object-oriented systems and beyond. The table below maps each component of the basic structure to its advanced extensions.
| Basic Concept | Advanced Extension | What It Enables |
|---|---|---|
R/ directory with functions | S4 classes, R6 classes, S3 method dispatch | Full object-oriented programming within the package namespace, enabling formal class hierarchies and method dispatch. |
NAMESPACE exports | S4 method exports, re-exports, importFrom | Fine-grained control: import only specific functions from dependencies, export S4 generics/methods, re-export functions from other packages. |
DESCRIPTION dependencies | LinkingTo, SystemRequirements, Remotes | LinkingTo enables header-only C++ library sharing (e.g., Rcpp, BH). SystemRequirements declares external system dependencies. Remotes specifies non-CRAN install sources. |
src/ compiled code | Rcpp integration, Rust via extendr, parallel C++ with RcppParallel | Write performance-critical code in compiled languages with seamless R interoperability. Rcpp alone powers thousands of CRAN packages. |
tests/ with testthat | Snapshot testing, test coverage with covr, CI/CD integration | Snapshot tests capture expected output for complex objects. covr measures what percentage of your code is exercised by tests. GitHub Actions automates this on every commit. |
It is worth emphasizing that the conceptual model does not change as you move into these advanced topics. The directory layout remains the same; you are simply populating it with more sophisticated contents. An Rcpp-based package still has DESCRIPTION, NAMESPACE, and R/ — it just also has src/ with C++ files and a LinkingTo: Rcpp declaration in DESCRIPTION. This is the power of a well-designed convention: it scales from a two-function utility package to a thousand-function computational library without structural changes.
Practice Problems
Depends: R (>= 4.0)
Imports: dplyr, ggplot2
Suggests: testthat, knitr
If a user runs install.packages("mypkg"), which packages are automatically installed as hard dependencies, and which are not? Explain the distinction between Imports and Suggests.R/ directory containing three files: utils.R (defines helper_a() and helper_b()), analysis.R (defines run_analysis() which calls both helpers), and plot.R (defines plot_results()). You want users to access only run_analysis() and plot_results(). Write the NAMESPACE directives needed, and explain why the helpers should remain unexported.Summary — R Package Structure at a Glance
An R package is a standardized directory structure that bundles code, documentation, data, and metadata into a distributable unit. The three mandatory components are the DESCRIPTION file (declaring identity, version, and dependencies), the NAMESPACE file (controlling which functions are exported and imported), and the R/ directory (containing source code). Optional components — man/ for documentation, data/ for datasets, tests/ for testing, vignettes/ for tutorials, and src/ for compiled code — extend the package's capabilities while maintaining the same structural conventions.
The build pipeline processes this structure in three phases: build (assemble a source tarball), check (validate correctness), and install (deploy to a local library). The key design principles are convention over configuration, separation of concerns, metadata-driven discovery, and encapsulation via namespaces. This conceptual model scales from trivial utility packages to complex computational libraries without structural changes — understanding it is the prerequisite for effective R package development.