| name | Package Dependencies |
| description | Complete guide to managing R package dependencies, including Imports vs Suggests, namespace imports, and handling dependencies in code, tests, examples, and vignettes |
Package Dependencies
Overview
Managing dependencies correctly is critical for R packages. This skill covers the different dependency types, how to declare and use them, and common patterns for conditional dependencies.
CRITICAL Concept: Listing vs Importing
Most important rule: Listing a package in Imports: does NOT make its functions available!
Imports:
dplyr
my_function <- function(data) {
filter(data, value > 0)
}
my_function <- function(data) {
dplyr::filter(data, value > 0)
}
my_function <- function(data) {
filter(data, value > 0)
}
Listing in DESCRIPTION ensures the package is installed.
Importing to NAMESPACE makes functions available in your code.
Dependency Types
Imports
Packages required for your package to work.
# DESCRIPTION:
Imports:
dplyr (>= 1.0.0),
rlang (>= 1.0.0),
tidyr
Guarantees:
- Installed when your package is installed
- Loaded when your package is loaded (but not attached)
- Functions available via
pkg::fun()
Use for:
- Packages your code depends on
- Packages used in most functions
- Critical dependencies
Usage pattern:
my_function <- function(x) {
dplyr::mutate(x, new_col = value * 2)
}
my_function <- function(x) {
x %>%
filter(value > 0) %>%
mutate(doubled = value * 2)
}
Suggests
Optional packages for enhanced functionality, tests, or documentation.
# DESCRIPTION:
Suggests:
ggplot2,
testthat (>= 3.0.0),
knitr,
rmarkdown,
covr
No guarantees:
- May or may not be installed
- Must check availability before use
- Cannot use
pkg::fun() without checking
Use for:
- Packages for optional features
- Testing frameworks (testthat, covr)
- Vignette builders (knitr, rmarkdown)
- Packages for examples only
- Heavy dependencies users may not need
Usage pattern:
my_plot <- function(data) {
if (!requireNamespace("ggplot2", quietly = TRUE)) {
stop("Package 'ggplot2' required but not installed.\n",
"Install with: install.packages('ggplot2')",
call. = FALSE)
}
ggplot2::ggplot(data, ggplot2::aes(x, y)) +
ggplot2::geom_point()
}
my_plot <- function(data) {
rlang::check_installed("ggplot2", reason = "to create plots")
ggplot2::ggplot(data, ggplot2::aes(x, y)) +
ggplot2::geom_point()
}
Depends
Makes another package's functions available in user's workspace (rarely recommended).
# DESCRIPTION:
Depends:
R (>= 4.1.0),
methods
Effects:
- Package is attached when yours is attached
- Functions available in user's search path
- Modifies user's environment
Modern usage:
R (>= version) - minimum R version (ALWAYS use this)
methods - if defining S4 classes
- Almost never use for other packages
Why avoid:
library(mypackage)
filter
Better approach:
Imports: dplyr
dplyr::filter(...)
LinkingTo
For packages with C/C++ code using headers from other packages.
# DESCRIPTION:
LinkingTo:
Rcpp,
RcppArmadillo
Use for:
- Rcpp packages
- Packages providing C++ headers
- Compiled code dependencies
Often combined with Imports:
Imports:
Rcpp (>= 1.0.0)
LinkingTo:
Rcpp
Config/Needs/*
Dependencies for development tools, not package functionality.
# DESCRIPTION:
Config/Needs/website:
pkgdown
Config/Needs/coverage:
covr
Config/Needs/development:
devtools,
usethis,
roxygen2
Use for:
- pkgdown for website
- covr for coverage
- Development tools
- CI-specific packages
Not installed by default:
pak::pak("mypackage", dependencies = TRUE)
Adding Dependencies
Using usethis Helpers
usethis::use_package("dplyr")
usethis::use_package("rlang", min_version = "1.0.0")
usethis::use_package("ggplot2", type = "Suggests")
usethis::use_import_from("dplyr", c("filter", "mutate", "select"))
usethis::use_package("R", min_version = "4.1.0", type = "Depends")
Manual Addition
# DESCRIPTION:
Imports:
dplyr (>= 1.1.0),
rlang (>= 1.0.0),
tidyr,
purrr
Suggests:
ggplot2 (>= 3.4.0),
testthat (>= 3.0.0)
Version specifications:
dplyr # Any version
dplyr (>= 1.0.0) # At least 1.0.0
dplyr (>= 1.0.0, < 2.0.0) # Rarely used, not recommended
Importing Functions to Namespace
Pattern 1: Explicit Namespace (Recommended)
my_function <- function(data) {
data %>%
dplyr::filter(value > 0) %>%
dplyr::mutate(doubled = value * 2) %>%
dplyr::select(id, doubled)
}
Advantages:
- Clear where functions come from
- No NAMESPACE management needed
- Easy to understand
- No import conflicts
Disadvantages:
- More typing
- Slightly verbose
Pattern 2: Selective Import (@importFrom)
my_function <- function(data) {
data %>%
filter(value > 0) %>%
mutate(doubled = .data$value * 2) %>%
select(id, doubled)
}
When to use:
- Functions used many times
- Infix operators (%>%, %||%, :=)
- Core package dependencies
- Reduces verbosity
Where to put @importFrom:
my_function <- function() { ... }
"_PACKAGE"
NULL
Pattern 3: Full Import (@import) - Rare
Only for:
- rlang (if building tidy evaluation package)
- Your own internal package
Avoid for most packages:
- Namespace pollution
- Potential conflicts
- Unclear provenance
Operators and Infix Functions
Always import operators:
data %>% dplyr::filter(x > 0)
data %>% dplyr::filter(x > 0)
data |> dplyr::filter(x > 0)
Common operators to import:
Using Dependencies in Different Contexts
In Package Code (R/)
my_function <- function(data) {
filter(data, value > 0)
}
my_function <- function(data) {
dplyr::filter(data, value > 0)
}
my_optional_feature <- function(data) {
rlang::check_installed("ggplot2", reason = "for plotting")
ggplot2::ggplot(data, ggplot2::aes(x, y)) +
ggplot2::geom_point()
}
In Examples (@examples)
In Tests (tests/testthat/)
test_that("function works", {
result <- my_function(data)
expect_equal(result$value, expected)
})
test_that("plotting works", {
skip_if_not_installed("ggplot2")
plot <- my_plot(data)
expect_s3_class(plot, "gg")
})
test_that("integration with optional package", {
skip_if_not_installed("dplyr")
library(dplyr)
result <- data %>%
my_transform() %>%
summarize(mean = mean(value))
expect_equal(result$mean, 5)
})
In Vignettes (vignettes/)
Alternative: Use separate vignettes:
# DESCRIPTION:
Suggests:
knitr,
rmarkdown,
ggplot2
# vignettes/basic-usage.Rmd - no optional deps
# vignettes/advanced-plotting.Rmd - requires ggplot2
In Documentation (roxygen2)
Minimum Version Specifications
When to Specify Versions
# Always specify if you need specific features:
Imports:
dplyr (>= 1.1.0), # Uses .by argument
rlang (>= 1.0.0), # Uses check_installed()
tidyr (>= 1.3.0) # Uses separate_wider_*
# Don't specify if any version works:
Imports:
jsonlite, # Basic read/write - any version fine
httr # Standard requests - any version fine
Finding Minimum Versions
usethis::use_package("dplyr", min_version = "1.1.0")
R Version
Always specify minimum R version:
# DESCRIPTION:
Depends:
R (>= 4.1.0)
Why specify:
- Uses R 4.1+ features (native pipe |>, lambda (x))
- Uses R 4.0+ features (stringsAsFactors = FALSE default)
- Package developed/tested on specific version
How to choose:
R (>= 4.0.0)
R (>= 4.1.0)
R (>= 4.3.0)
Advanced Patterns
Soft Dependencies
Functions work differently with/without optional package:
my_function <- function(data, use_fast = TRUE) {
if (use_fast && requireNamespace("data.table", quietly = TRUE)) {
dt <- data.table::as.data.table(data)
result <- dt[, .(mean = mean(value)), by = group]
return(as.data.frame(result))
}
aggregate(value ~ group, data, FUN = mean)
}
Conditional Method Registration
.onLoad <- function(libname, pkgname) {
if (requireNamespace("dplyr", quietly = TRUE)) {
s3_register("dplyr::dplyr_reconstruct", "myclass")
}
}
s3_register <- function(generic, class, method = NULL) {
stopifnot(is.character(generic), length(generic) == 1)
stopifnot(is.character(class), length(class) == 1)
pieces <- strsplit(generic, "::")[[1]]
stopifnot(length(pieces) == 2)
package <- pieces[[1]]
generic <- pieces[[2]]
caller <- parent.frame()
get_method_env <- function() {
top <- topenv(caller)
if (isNamespace(top)) {
asNamespace(environmentName(top))
} else {
caller
}
}
get_method <- function(method, env) {
if (is.null(method)) {
get(paste0(generic, ".", class), envir = get_method_env())
} else {
method
}
}
method_fn <- get_method(method)
stopifnot(is.function(method_fn))
setHook(
packageEvent(package, "onLoad"),
function(...) {
ns <- asNamespace(package)
method_name <- paste0(generic, ".", class)
env <- get_method_env()
registerS3method(generic, class, method_fn, envir = ns)
}
)
if (!isNamespaceLoaded(package)) {
return(invisible())
}
envir <- asNamespace(package)
if (exists(generic, envir)) {
registerS3method(generic, class, method_fn, envir = envir)
}
invisible()
}
Feature Flags
has_plotting <- function() {
requireNamespace("ggplot2", quietly = TRUE)
}
has_fast_processing <- function() {
requireNamespace("data.table", quietly = TRUE)
}
package_capabilities <- function() {
list(
plotting = has_plotting(),
fast_processing = has_fast_processing()
)
}
Deprecation Paths
my_function <- function() {
oldpkg::old_way()
}
my_function <- function() {
lifecycle::deprecate_soft(
"1.1.0", "my_function()",
details = "oldpkg will become optional in next version"
)
oldpkg::old_way()
}
my_function <- function(use_old = FALSE) {
if (use_old) {
lifecycle::deprecate_warn(
"2.0.0", "my_function(use_old)",
details = "Old method will be removed in next version"
)
rlang::check_installed("oldpkg")
return(oldpkg::old_way())
}
new_way()
}
my_function <- function() {
new_way()
}
Common Pitfalls
1. Listing Package But Not Using It
Problem: Package in Imports but functions not accessible.
Imports: dplyr
my_function <- function(data) {
filter(data, x > 0)
}
my_function <- function(data) {
dplyr::filter(data, x > 0)
}
2. Using Suggests Without Checking
Problem: Assumes suggested package is installed.
Suggests: ggplot2
my_plot <- function(data) {
ggplot2::ggplot(data, ggplot2::aes(x, y))
}
my_plot <- function(data) {
rlang::check_installed("ggplot2")
ggplot2::ggplot(data, ggplot2::aes(x, y))
}
3. Using Depends Unnecessarily
Depends: dplyr
Imports: dplyr
4. Not Specifying Minimum Versions
Imports: dplyr
my_function <- function(data) {
dplyr::filter(data, x > 0, .by = group)
}
Imports: dplyr (>= 1.1.0)
5. Importing Entire Packages
6. Missing skip_if_not_installed() in Tests
test_that("optional feature works", {
library(ggplot2)
})
test_that("optional feature works", {
skip_if_not_installed("ggplot2")
library(ggplot2)
})
7. Hard-Coding Package Checks
if (!requireNamespace("pkg", quietly = TRUE)) {
stop("Please install pkg")
}
rlang::check_installed(
"pkg",
reason = "to use this feature"
)
8. Circular Dependencies
9. Not Using Config/Needs/*
Suggests:
testthat,
devtools,
usethis,
pkgdown,
covr
Suggests:
testthat
Config/Needs/website:
pkgdown
Config/Needs/development:
devtools,
usethis
Config/Needs/coverage:
covr
10. Forgetting :: for Suggests
Suggests: ggplot2
if (requireNamespace("ggplot2", quietly = TRUE)) {
ggplot(data)
}
if (requireNamespace("ggplot2", quietly = TRUE)) {
ggplot2::ggplot(data)
}
Quick Reference
Dependency Checklist
usethis::use_package("dplyr")
usethis::use_package("ggplot2", "Suggests")
dplyr::filter(...)
filter(...)
rlang::check_installed("ggplot2")
ggplot2::ggplot(...)
skip_if_not_installed("ggplot2")
Imports: dplyr (>= 1.1.0)
DESCRIPTION Template
Package: mypackage
Version: 0.1.0
Depends:
R (>= 4.1.0)
Imports:
dplyr (>= 1.1.0),
rlang (>= 1.0.0),
tidyr (>= 1.3.0)
Suggests:
ggplot2 (>= 3.4.0),
testthat (>= 3.0.0),
knitr,
rmarkdown
Config/Needs/website:
pkgdown
Config/Needs/development:
devtools,
usethis
Common Import Patterns
"_PACKAGE"
Resources