Why Package Your R Code
Turning a collection of useful R functions into a formal package — rather than leaving them as loose scripts you source() — buys you automatic dependency management, documentation that stays next to the code it describes, a built-in testing framework, and the ability to install and share the code via install.packages(), devtools::install_github(), or CRAN itself. The moment you find yourself copy-pasting the same helper function into three different analysis scripts, that's the signal it belongs in a package instead.
Cricket analogy: Packaging loose R scripts is like a franchise formalizing a scouting network instead of relying on one scout's private notebook — once it's an institution (a package), it can be shared, reused, and trusted across the whole organization.
Package Structure and Scaffolding
usethis::create_package("mypkg") scaffolds the minimal required structure: a DESCRIPTION file listing the package name, version, and dependencies; a NAMESPACE file (usually auto-generated, never hand-edited) declaring which functions are exported; and an R/ folder holding your .R source files. From there, usethis::use_r("my_function") creates a correctly named script inside R/, and usethis::use_mit_license(), use_git(), or use_readme_md() scaffold the surrounding project conventions so you're not hand-writing boilerplate files from scratch.
Cricket analogy: create_package() scaffolding DESCRIPTION and NAMESPACE files is like the BCCI issuing a standard franchise charter template for a new IPL team — squad list, budget cap, ground affiliation — so no team starts from a blank page.
Documenting Functions with roxygen2
Documentation lives directly above each function as specially formatted comments starting with #', using tags like @param x Description of the argument, @return Description of what's returned, @examples for runnable usage snippets, and @export to mark the function as part of the public API. Running devtools::document() parses these comments and regenerates both the .Rd help files under man/ and the NAMESPACE file automatically, so documentation and code are edited in the same place and can never silently drift apart the way separately maintained documentation often does.
Cricket analogy: Roxygen2 comments living directly above a function are like a player's stats card printed right on their scorecard entry rather than in a separate archive — the documentation travels with the code exactly like a player's numbers travel with their name.
#' Calculate customer lifetime value
#'
#' Estimates the lifetime value of a customer given their average
#' order value, purchase frequency, and expected retention period.
#'
#' @param avg_order_value Numeric. Average value per order in dollars.
#' @param purchase_frequency Numeric. Average number of orders per year.
#' @param retention_years Numeric. Expected number of years retained.
#'
#' @return A numeric value representing estimated lifetime value.
#' @export
#'
#' @examples
#' customer_ltv(avg_order_value = 45, purchase_frequency = 6, retention_years = 3)
customer_ltv <- function(avg_order_value, purchase_frequency, retention_years) {
if (avg_order_value <= 0 || purchase_frequency <= 0) {
stop("avg_order_value and purchase_frequency must be positive")
}
avg_order_value * purchase_frequency * retention_years
}During development, use devtools::load_all() instead of install.packages() or restarting R every time you edit a function — it simulates loading the package into memory in seconds, letting you iterate rapidly. Run devtools::document() first if you changed any roxygen2 comments, since load_all() does not regenerate documentation on its own.
Dependencies, Testing, and Checking
usethis::use_package("dplyr") adds a package to the Imports field of DESCRIPTION, which means it will be installed alongside your package but its functions must still be called with dplyr::filter() or explicitly imported via a roxygen2 @importFrom dplyr filter tag — R packages deliberately avoid attaching dependencies to the search path the way library() does interactively, to prevent silent namespace collisions between packages. Reserve Depends (rarely used today) for cases where your package is meaningless without another package loaded into the user's session, such as extending a base class.
Cricket analogy: Requiring dplyr::filter() instead of a bare filter() call is like a commentator always saying 'Kohli of RCB' instead of just 'Kohli' when multiple players share a surname across different franchises — explicit namespacing avoids ambiguity.
devtools::check() runs the same automated R CMD check process CRAN uses to validate submissions, checking for syntax errors, undocumented arguments, missing dependencies, examples that fail to run, and test failures, categorizing issues as ERRORs, WARNINGs, or NOTEs. Integrating testthat (via usethis::use_testthat() and usethis::use_test("customer_ltv")) means devtools::test() and devtools::check() both run your unit tests automatically, so a broken function is caught before it ships rather than discovered by a user.
Cricket analogy: devtools::check() running the same validation CRAN uses is like a bowler going through the exact same ICC action-legality test before an official match that they'd face during a real tournament — no surprises later.
A CRAN submission with any ERROR or WARNING from R CMD check will be rejected outright, and even NOTEs can require justification in your submission comments — common causes include undocumented function arguments, examples that take too long to run (over 5 seconds), and non-ASCII characters in source files. Run devtools::check() locally and on rhub::check_for_cran() or GitHub Actions across multiple platforms before ever submitting, since CRAN checks on Windows, macOS, and Linux, and a package that passes on your machine can still fail on another OS.
- Packaging reusable R code buys dependency management, colocated documentation, testing, and shareable installation.
usethis::create_package()scaffolds the requiredDESCRIPTION,NAMESPACE, andR/structure automatically.- Roxygen2 comments (
#'with@param,@return,@export) live directly above functions and generate.Rdhelp files viadevtools::document(). usethis::use_package()adds dependencies toImports, requiring explicitpkg::function()calls to avoid namespace collisions.devtools::check()runs the sameR CMD checkprocess CRAN uses, flagging ERRORs, WARNINGs, and NOTEs before submission.testthatintegration viausethis::use_testthat()means unit tests run automatically as part ofdevtools::check().- CRAN checks packages across Windows, macOS, and Linux, so cross-platform testing via
rhubor CI catches issues invisible on your own machine.
Practice what you learned
1. Which function scaffolds the minimal file structure (DESCRIPTION, NAMESPACE, R/) for a new R package?
2. What roxygen2 tag marks a function as part of the package's public API?
3. Why must functions from a package listed in `Imports` typically be called as `pkg::function()` rather than bare `function()`?
4. What does devtools::check() do?
5. What is the purpose of devtools::load_all() during package development?
Was this page helpful?
You May Also Like
Testing R Code with testthat
Learn how to write, organize, and run automated unit tests for R functions and packages using the testthat framework.
R and Machine Learning
Explore how to train, evaluate, and tune machine learning models in R using caret, tidymodels, and core algorithms like random forests and logistic regression.
R Markdown Basics
Learn how to combine R code, output, and narrative text into reproducible documents, reports, and slides using R Markdown.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics