R PROGRAMMING • FUNCTIONS AND PROGRAM STRUCTURE

Package Structure — Understand package structure conceptually (intro)

Learn how R packages organize code, data, and documentation into reusable, distributable units of functionality.

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.

1993
R Language Born
Ross Ihaka and Robert Gentleman at the University of Auckland begin developing R as a free implementation of the S language. Code sharing is entirely informal — scripts are emailed or posted on FTP servers.
1997
CRAN Established
The Comprehensive R Archive Network (CRAN) launches, creating a centralized repository and mandating a standard package format for submissions. This is the pivotal moment that formalizes package structure in R.
2003
Namespaces Introduced
R 1.7.0 introduces the NAMESPACE file mechanism, allowing packages to explicitly control which functions they export and import, preventing naming collisions across the growing ecosystem.
2011
devtools & roxygen2 Emerge
Hadley Wickham's devtools and roxygen2 packages dramatically lower the barrier to creating packages, making in-source documentation generation and automated builds accessible to everyday R users.
2020s
Modern Ecosystem Maturity
CRAN surpasses 20,000 packages. Bioconductor hosts thousands more for bioinformatics. The R package structure has become the de facto standard for distributing reproducible analytical pipelines and data products.

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.

1

Convention over Configuration

R packages follow a rigid directory layout. R code goes in 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.
2

Separation of Concerns

Each component — source code, documentation, test suites, compiled code, datasets — has its own dedicated directory. This separation ensures that modifying documentation cannot accidentally break executable code, and vice versa.
3

Metadata-Driven Discovery

The DESCRIPTION file is the identity card of a package. It declares the name, version, authors, license, and dependencies. CRAN, Bioconductor, and R itself parse this file to resolve the dependency graph during installation.
4

Encapsulation via Namespaces

The NAMESPACE file controls visibility. Functions marked for export become part of the public API; everything else remains internal. This mirrors the public/private access control familiar from object-oriented languages.
5

Reproducibility by Design

By bundling code, data, tests, and documentation into a versioned archive, R packages become reproducible research artifacts. Anyone can install a specific version and recreate the same analytical environment months or years later.
KEY TAKEAWAY
Think of an R package like a well-organized shipping container in a global logistics network. The container has standardized dimensions (the directory layout), a manifest describing its contents (the DESCRIPTION file), and security seals that control what gets exposed at the destination port (the NAMESPACE). Because every container follows the same standard, cranes (R's build tools), ships (CRAN), and warehouses (your local library) all interoperate seamlessly without custom handling.

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.

The canonical directory tree for an R package named 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

The three-phase build pipeline. Phase 1 (Build) assembles the source tarball. Phase 2 (Check) validates correctness. Phase 3 (Install) deploys the package to a local library, making it available via 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 Shortcut
In practice, most developers use 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.

Standard R package components and their roles
ComponentTypePurposeRequired?
DESCRIPTIONFilePackage metadata: name, version, title, author, license, dependencies (Imports, Suggests, Depends), and description. Uses Debian Control File (DCF) format.Yes
NAMESPACEFileDeclares exported functions (public API) and imported functions from other packages. Generated automatically by roxygen2 in modern workflows.Yes
R/DirectoryContains 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/DirectoryContains .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/DirectoryStores 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/DirectoryContains 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/DirectoryHolds 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/DirectoryContains 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/DirectoryArbitrary 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.

Building the tempconv Package
1
Step 1 — Create the Directory StructureCreate a root directory called 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/.
Three mandatory components created: DESCRIPTION, NAMESPACE, R/
2
Step 2 — Write the DESCRIPTION FilePopulate DESCRIPTION with the required fields. A minimal example: 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).
Package identity, versioning, and license established.
3
Step 3 — Write R Functions in R/Create a file 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.
Two exported functions with inline documentation defined.
4
Step 4 — Generate NAMESPACE and man/ PagesRun 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)
NAMESPACE auto-generated with two export directives; man/ populated with .Rd files.
5
Step 5 — Build, Check, and InstallRun 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 0
Package built, checked, installed, and loaded successfully.

Strengths, 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.

Strengths and limitations of R's package system
AspectStrengthLimitation
StandardizationUniform 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 ManagementThe 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.
DocumentationIntegrated 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.
TestingR 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.
DistributionCRAN 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).
KEY TAKEAWAY
The threshold question is reuse: if you plan to use your code across multiple projects, share it with collaborators, or publish it for the community, a package is almost always the right choice. The upfront cost of setting up the directory structure pays dividends in maintainability, testability, and discoverability. For truly one-off analyses, an R script or RMarkdown document may suffice — but even experienced R developers often find that "one-off" code has a surprising tendency to grow into something reusable.

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.

From basic structure to advanced package development
Basic ConceptAdvanced ExtensionWhat It Enables
R/ directory with functionsS4 classes, R6 classes, S3 method dispatchFull object-oriented programming within the package namespace, enabling formal class hierarchies and method dispatch.
NAMESPACE exportsS4 method exports, re-exports, importFromFine-grained control: import only specific functions from dependencies, export S4 generics/methods, re-export functions from other packages.
DESCRIPTION dependenciesLinkingTo, SystemRequirements, RemotesLinkingTo enables header-only C++ library sharing (e.g., Rcpp, BH). SystemRequirements declares external system dependencies. Remotes specifies non-CRAN install sources.
src/ compiled codeRcpp integration, Rust via extendr, parallel C++ with RcppParallelWrite performance-critical code in compiled languages with seamless R interoperability. Rcpp alone powers thousands of CRAN packages.
tests/ with testthatSnapshot testing, test coverage with covr, CI/CD integrationSnapshot 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

PROBLEM 1CONCEPTUAL
Name the three components that are absolutely required for a valid R package. For each, explain in one sentence what role it plays in the package system.
PROBLEM 2BASIC CALCULATION
A DESCRIPTION file contains the following dependency fields: 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.
PROBLEM 3INTERMEDIATE
You have a package with the following 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.
PROBLEM 4APPLIED
You are collaborating on a bioinformatics project. Your team has developed a set of R functions for normalizing RNA-seq count matrices, plus a reference dataset of housekeeping genes. You also have a detailed tutorial explaining the normalization workflow. Describe the complete package directory structure you would create, mapping each project artifact to the appropriate package component. Include at least six directories or files in your answer.
PROBLEM 5CRITICAL THINKING
R's package structure predates modern software engineering practices like containerization (Docker), language-level module systems (Python's import, Rust's mod), and monorepo tooling. Critically evaluate whether R's convention-over-configuration approach to packages is an advantage or a limitation in the modern software landscape. Consider at least two specific comparisons to other languages' packaging systems (e.g., Python's pip/setuptools, JavaScript's npm, Rust's Cargo) and discuss what R's system does better and worse.

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.

Varsity Tutors • R Programming • Package Structure — Understand package structure conceptually (intro)