Chapter 39 Package Development Basics

What You’ll Learn:

  • Creating R packages
  • Package structure
  • devtools workflow
  • Common package errors
  • DESCRIPTION and NAMESPACE

Key Errors Covered: 20+ package errors

Difficulty: ⭐⭐⭐ Advanced

39.1 Introduction

R packages organize and share code:

library(devtools)
library(usethis)

# Create new package
create_package("~/mypackage")

39.2 Package Structure

💡 Key Insight: Essential Package Files

mypackage/
├── R/                  # R code (.R files)
├── man/                # Documentation (.Rd files)
├── tests/              # Unit tests
│   └── testthat/
├── DESCRIPTION         # Package metadata
├── NAMESPACE           # Exports and imports
├── LICENSE             # License file
├── README.md           # Package description
├── NEWS.md             # Version history
└── .Rbuildignore       # Files to ignore

39.3 Creating a Package

🎯 Best Practice: Modern Package Creation

# Use usethis for package creation
library(usethis)

# Create package
create_package("~/mypackage")

# Set up Git
use_git()

# Add license
use_mit_license()

# Create README
use_readme_md()

# Set up testing
use_testthat()

# Add package dependencies
use_package("dplyr")
use_package("ggplot2")

39.4 DESCRIPTION File

💡 Key Insight: Package Metadata

Package: mypackage
Title: What the Package Does (One Line)
Version: 0.1.0
Authors@R: 
    person("First", "Last", email = "first.last@example.com",
           role = c("aut", "cre"))
Description: What the package does (one paragraph).
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
Imports:
    dplyr,
    ggplot2
Suggests:
    testthat (>= 3.0.0)

Key Fields: - Package: Name (letters, numbers, dots only) - Title: Short description (< 65 chars) - Version: Major.Minor.Patch - : Package authors - Description: Detailed description - License: Legal terms - Imports: Required packages - Suggests: Optional packages

39.5 Error #1: Invalid Package Name

⭐ BEGINNER 📝 NAME

39.5.1 The Error

# Invalid package names
create_package("my-package")     # Hyphens not allowed
create_package("my_package")     # OK but not recommended
create_package("123package")     # Can't start with number

🔴 ERROR

Error: 'my-package' is not a valid package name

39.5.2 Solutions

SOLUTION: Valid Package Names

# ✅ Good names
create_package("mypackage")
create_package("MyPackage")
create_package("my.package")     # OK but uncommon
create_package("mypackage2")

# Package name rules:
# - Letters, numbers, and dots only
# - Must start with letter
# - No hyphens or underscores
# - Case sensitive
# - 2+ characters recommended

39.6 Adding Functions

🎯 Best Practice: Add Functions with Documentation

# Create new R file
use_r("my_function")

# In R/my_function.R:

#' Calculate Mean with NA Removal
#'
#' @param x A numeric vector
#' @param na.rm Logical; remove NA values?
#'
#' @return The mean of x
#' @export
#'
#' @examples
#' safe_mean(c(1, 2, 3, NA))
#' safe_mean(c(1, 2, 3, NA), na.rm = TRUE)
safe_mean <- function(x, na.rm = FALSE) {
  if (!is.numeric(x)) {
    stop("x must be numeric")
  }
  mean(x, na.rm = na.rm)
}

# Generate documentation
devtools::document()

39.7 NAMESPACE

💡 Key Insight: Exports and Imports

NAMESPACE controls what functions are exported (visible to users) and what external functions are imported.

# Generated by roxygen2
# Export your functions
export(safe_mean)
export(my_function)

# Import from other packages
importFrom(dplyr, filter)
importFrom(ggplot2, ggplot)

# Import entire package (not recommended)
import(dplyr)

Best Practices: - Use @export in roxygen comments - Use @importFrom pkg function for imports - Let roxygen2 manage NAMESPACE - Never edit NAMESPACE manually

39.8 Error #2: Function Not Exported

⭐⭐ INTERMEDIATE 📦 NAMESPACE

39.8.1 The Problem

# Function defined but not accessible
# In package: my_function exists
# After install: Error: object 'my_function' not found

39.8.2 Solutions

SOLUTION: Export Functions

# Add @export to roxygen comment
#' My Function
#'
#' @export
my_function <- function(x) {
  x * 2
}

# Then document
devtools::document()

# Check NAMESPACE was updated
# Should see: export(my_function)

39.9 Dependencies

🎯 Best Practice: Manage Dependencies

# Add required package
use_package("dplyr")
# Adds to DESCRIPTION Imports

# Add suggested package
use_package("ggplot2", type = "Suggests")

# Using imported functions
# Option 1: Import in NAMESPACE
#' @importFrom dplyr filter
#' @export
my_filter <- function(data, condition) {
  filter(data, {{ condition }})
}

# Option 2: Use :: (no import needed)
my_summarize <- function(data) {
  dplyr::summarize(data, mean = mean(value))
}

# Option 3: Import entire package (not recommended)
#' @import dplyr

39.10 Development Workflow

🎯 Best Practice: Iterative Development

# 1. Load package in development
devtools::load_all()
# Simulates installing and loading

# 2. Try functions
safe_mean(c(1, 2, 3, NA), na.rm = TRUE)

# 3. Update documentation
devtools::document()

# 4. Run tests
devtools::test()

# 5. Check package
devtools::check()

# 6. Install locally
devtools::install()

# Keyboard shortcuts in RStudio:
# Ctrl/Cmd + Shift + L  - Load all
# Ctrl/Cmd + Shift + D  - Document
# Ctrl/Cmd + Shift + T  - Test
# Ctrl/Cmd + Shift + E  - Check
# Ctrl/Cmd + Shift + B  - Build and reload

39.11 Error #3: Missing Dependencies

⭐⭐ INTERMEDIATE 📦 DEPENDENCIES

39.11.1 The Error

# Using dplyr without declaring dependency
my_function <- function(data) {
  filter(data, value > 0)  # Error: could not find function "filter"
}

39.11.2 Solutions

SOLUTION: Declare and Import

# 1. Add to DESCRIPTION
use_package("dplyr")

# 2. Import in function
#' @importFrom dplyr filter
#' @export
my_function <- function(data) {
  filter(data, value > 0)
}

# Or use ::
#' @export  
my_function <- function(data) {
  dplyr::filter(data, value > 0)
}

# 3. Document
devtools::document()

39.12 Package Documentation

🎯 Best Practice: Complete Documentation

# Package-level documentation
use_package_doc()

# In R/mypackage-package.R:
#' mypackage: A Short Title
#'
#' A longer description of what the package does.
#'
#' @docType package
#' @name mypackage
NULL

# Function documentation with roxygen2
#' Calculate Summary Statistics
#'
#' This function calculates mean, median, and standard deviation
#' for a numeric vector.
#'
#' @param x A numeric vector
#' @param na.rm Logical; should NA values be removed?
#' @param digits Integer; number of decimal places
#'
#' @return A named vector with mean, median, and sd
#' @export
#'
#' @examples
#' x <- c(1, 2, 3, 4, 5, NA)
#' summary_stats(x, na.rm = TRUE)
#' summary_stats(x, na.rm = TRUE, digits = 2)
#'
#' @seealso \code{\link{mean}}, \code{\link{median}}, \code{\link{sd}}
summary_stats <- function(x, na.rm = FALSE, digits = 3) {
  c(
    mean = round(mean(x, na.rm = na.rm), digits),
    median = round(median(x, na.rm = na.rm), digits),
    sd = round(sd(x, na.rm = na.rm), digits)
  )
}

39.13 Data in Packages

💡 Key Insight: Including Data

# Create data
my_data <- data.frame(
  x = 1:10,
  y = rnorm(10)
)

# Add to package
use_data(my_data)
# Creates data/my_data.rda

# Document the data
# In R/data.R:
#' My Example Data
#'
#' A dataset containing example values.
#'
#' @format A data frame with 10 rows and 2 variables:
#' \describe{
#'   \item{x}{Integer values from 1 to 10}
#'   \item{y}{Random normal values}
#' }
#' @source Generated for package examples
"my_data"

# Use in examples
#' @examples
#' data(my_data)
#' plot(my_data$x, my_data$y)

39.14 Summary

Key Takeaways:

  1. Use devtools/usethis - Modern package development
  2. Valid package names - Letters, numbers, dots only
  3. Document everything - roxygen2 comments
  4. Export functions - Use (export?)
  5. Declare dependencies - DESCRIPTION and (importFrom?)
  6. Iterative workflow - load_all, document, test, check
  7. Include data properly - use_data() and document

Quick Reference:

# Create package
usethis::create_package("mypackage")

# Add function
usethis::use_r("function_name")

# Document
devtools::document()

# Test
devtools::test()

# Check
devtools::check()

# Install
devtools::install()

# Add dependency
usethis::use_package("dplyr")

# Add data
usethis::use_data(my_data)

Package Name Rules: - Letters, numbers, and dots only - Start with letter - No hyphens or underscores - Case sensitive - Keep it short and memorable

Best Practices:

# ✅ Good
Use roxygen2 for documentation
Declare all dependencies
Use :: for external functions
Test thoroughly
Keep functions focused
Document all parameters

# ❌ Avoid
Editing NAMESPACE manually
Missing @export
Undeclared dependencies
No examples
Long function names