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.

A statistical scatter plot with code braces and a variable symbol for R programming.
Inside this lesson 10 sections 5 questions 5 figures

SECTION 1

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.

SECTION 2

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.

SECTION 3

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.

R Package Directory Structuremypackage/← Package root directoryDESCRIPTIONMetadata (required)NAMESPACEExports/Imports (required)R/Source code (required)man/Documentation (.Rd files)data/Bundled datasets (.rda)tests/Unit tests (testthat)vignettes/Long-form guides (.Rmd)src/Compiled code (C/C++/Fortran)LegendRequired componentsDocumentationDataTesting & Vignettes
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.

SECTION 4

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

Package Build PipelinePHASE 1: BUILDR CMD build1. Parse DESCRIPTION2. Process NAMESPACE3. Collate R/ files4. Build vignettes5. Compress → .tar.gzpkg_1.0.tar.gzPHASE 2: CHECKR CMD checkValidate metadataCheck dependenciesRun examplesRun tests/Report NOTE/WARN/ERR0 errors ✓PHASE 3: INSTALLR CMD INSTALLUnpack tarballByte-compile R/Compile src/ (if any)Install to library/Register namespacelibrary(mypackage)Source package → validated tarball → installed library entry
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.

SECTION 5

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.

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

Standard R package components and their roles

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.

SECTION 6

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. 1

    Step 1 — Create the Directory Structure

    Create 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. 2

    Step 2 — Write the DESCRIPTION File

    Populate 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. 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. 4

    Step 4 — Generate NAMESPACE and man/ Pages

    Run 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. 5

    Step 5 — Build, Check, and Install

    Run 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.

SECTION 7

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.

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).

Strengths and limitations of R's package system

✦

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.

SECTION 8

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

From basic structure to advanced package development

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.

SECTION 9

Practice Problems

Question 1: CONCEPTUAL

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.
Show answer
The three required components are: (1) DESCRIPTION, which provides metadata including the package name, version, author, license, and dependency declarations; (2) NAMESPACE, which specifies which functions are exported (public API) and which functions are imported from other packages; and (3) the R/ directory, which contains the actual R source code files defining the package's functions. Without any one of these, R CMD build and R CMD check will fail.

Question 2: BASIC 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.
Show answer
When a user installs mypkg, the packages listed under Imports — dplyr and ggplot2 — are automatically installed because they are required for the package to function. The Depends field specifies a minimum R version (≥ 4.0), which is checked but does not install another package. The packages under Suggests — testthat and knitr — are NOT automatically installed; they are only needed for running tests and building vignettes, respectively. The distinction: Imports are hard dependencies your code cannot run without, while Suggests are soft dependencies needed only for optional functionality.

Question 3: INTERMEDIATE

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.
Show answer
The NAMESPACE file should contain: export(run_analysis) export(plot_results) The helper functions helper_a() and helper_b() are intentionally omitted from the exports. They remain accessible to other functions within the package namespace (run_analysis can still call them), but users cannot call them directly after library(mypkg). Keeping helpers unexported preserves a clean public API, reduces the surface area you commit to maintaining, and prevents users from depending on internal implementation details that may change between versions. In a roxygen2 workflow, you would simply not add the @export tag to the helper functions.

Question 4: APPLIED

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.
Show answer
A well-structured package for this project would include: (1) DESCRIPTION with metadata, listing Imports like BiocGenerics or SummarizedExperiment and Suggests like testthat and knitr; (2) NAMESPACE exporting normalization functions while keeping internal utility functions private; (3) R/ containing files such as normalize.R, filter.R, and utils.R with the normalization algorithms; (4) data/ containing the housekeeping gene reference dataset saved as an .rda file, along with data-raw/ holding the script that produced it; (5) vignettes/ containing the R Markdown tutorial walking users through the normalization workflow; (6) tests/testthat/ with unit tests verifying that normalization produces expected outputs on known inputs; and (7) man/ with auto-generated .Rd documentation for every exported function. This structure ensures the project is installable, testable, documented, and reproducible by any collaborator.

Question 5: CRITICAL 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.
Show answer
R's package system excels in several areas relative to competitors. Compared to Python's setuptools/pip ecosystem, R's mandatory directory layout eliminates the configuration sprawl of setup.py vs. setup.cfg vs. pyproject.toml — there is exactly one way to structure an R package, which makes tooling (devtools, roxygen2, testthat) extremely cohesive. Compared to npm, R's CRAN repository enforces quality gates (R CMD check must pass) before publication, whereas npm allows virtually any content, leading to supply chain security issues. However, R's system has notable weaknesses. Unlike Rust's Cargo, R lacks a built-in lock file for reproducible dependency resolution — renv fills this gap but is a third-party tool, not part of the package specification itself. JavaScript's npm supports semantic versioning enforcement and workspace monorepos natively, while R packages are designed as standalone units with no first-class monorepo support. Additionally, R's lack of a true language-level module system means that NAMESPACE is doing double duty: it is both an access control mechanism and a dependency declaration mechanism, which can lead to confusion (e.g., the difference between Depends, Imports, and importFrom). Docker and containerization bypass the package system entirely for deployment, and R packages were not designed with container-based deployment in mind. Ultimately, R's convention-over-configuration approach is a net advantage for its target audience — statisticians and data scientists who benefit from low-friction standardization — but it shows its age when applied to modern software engineering workflows that demand reproducibility guarantees, monorepo support, and cross-language interoperability.

SUMMARY

Summary — R Package Structure at a Glance

Summary

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)