- 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!
```r
# DESCRIPTION:
Imports:
dplyr
# This does NOT work:
my_function <- function(data) {
filter(data, value > 0) # ERROR: object 'filter' not found
}
# You must ALSO either:
# Option 1: Use explicit namespace (RECOMMENDED for most cases):
my_function <- function(data) {
dplyr::filter(data, value > 0)
}
# Option 2: Import to namespace via roxygen2:
#' @importFrom dplyr filter
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.
```dcf
# 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:**
```r
# Default: Use pkg::fun()
my_function <- function(x) {
dplyr::mutate(x, new_col = value * 2)
}
# Or import specific functions:
#' @importFrom dplyr mutate select filter
my_function <- function(x) {
x %>%
filter(value > 0) %>%
mutate(doubled = value * 2)
}
```
### Suggests
Optional packages for enhanced functionality, tests, or documentation.
```dcf
# 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:**
```r
# MUST check availability:
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()
}
# Better: Use rlang::check_installed()
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).
```dcf
# 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:**
```r
# If you use Depends: dplyr:
library(mypackage) # Also attaches dplyr
# Now user's environment has all dplyr functions:
filter # Available (might conflict with stats::filter)
```
**Better approach:**
```r
# Use Imports + explicit namespace:
Imports: dplyr
# In code:
dplyr::filter(...)
```
### LinkingTo
For packages with C/C++ code using headers from other packages.
```dcf
# DESCRIPTION:
LinkingTo:
Rcpp,
RcppArmadillo
```
**Use for:**
- Rcpp packages
- Packages providing C++ headers
- Compiled code dependencies
**Often combined with Imports:**
```dcf
Imports:
Rcpp (>= 1.0.0)
LinkingTo:
Rcpp
```
### Config/Needs/*
Dependencies for development tools, not package functionality.
```dcf
# 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:**
```r
# Install with:
pak::pak("mypackage", dependencies = TRUE) # Includes Config/Needs/*
```
## Adding Dependencies
### Using usethis Helpers
```r
# Add to Imports:
usethis::use_package("dplyr")
usethis::use_package("rlang", min_version = "1.0.0")
# Add to Suggests:
usethis::use_package("ggplot2", type = "Suggests")
# Add to Imports with @importFrom:
usethis::use_import_from("dplyr", c("filter", "mutate", "select"))
# Adds to DESCRIPTION AND creates roxygen2 skeleton
# Add minimum R version:
usethis::use_package("R", min_version = "4.1.0", type = "Depends")
```
### Manual Addition
```dcf
# DESCRIPTION:
Imports:
dplyr (>= 1.1.0),
rlang (>= 1.0.0),
tidyr,
purrr
Suggests:
ggplot2 (>= 3.4.0),
testthat (>= 3.0.0)
```
**Version specifications:**
```dcf
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)
```r
# No NAMESPACE imports needed
# Just use pkg::fun() everywhere:
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)
```r
# Import specific functions:
#' @importFrom dplyr filter mutate select
#' @importFrom rlang .data .env
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:**
```r
# Option 1: In function documentation:
#' My function
#' @importFrom dplyr filter mutate
my_function <- function() { ... }
# Option 2: In package-level doc (R/mypackage-package.R):
#' @importFrom dplyr filter mutate select arrange
#' @importFrom rlang .data .env %||%
"_PACKAGE"
# Option 3: Dedicated imports file (R/aaa-imports.R):
#' @importFrom dplyr filter mutate select
#' @importFrom rlang .data %||%
NULL
```
### Pattern 3: Full Import (@import) - Rare
```r
#' @import rlang
```
**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:**
```r
# WRONG - doesn't work:
data %>% dplyr::filter(x > 0) # Error: %>% not found
# RIGHT:
#' @importFrom magrittr %>%
data %>% dplyr::filter(x > 0)
# Or use base pipe (R >= 4.1):
data |> dplyr::filter(x > 0) # No import needed
```
**Common operators to import:**
```r
#' @importFrom magrittr %>%
#' @importFrom rlang %||% !! !!!
#' @importFrom data.table := .N .SD
```
## Using Dependencies in Different Contexts
### In Package Code (R/)
```r
# Imports dependencies - use pkg::fun() or @importFrom:
#' @importFrom dplyr filter
my_function <- function(data) {
filter(data, value > 0) # OK: imported
}
# Or:
my_function <- function(data) {
dplyr::filter(data, value > 0) # OK: explicit namespace
}
# Suggests dependencies - MUST check first:
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)
```r
# Imports - can use freely:
#' @examples
#' my_function(mtcars)
# Suggests - must check or wrap:
#' @examples
#' \dontrun{
#' # Requires ggplot2
#' my_plot(mtcars)
#' }
#'
#' @examplesIf requireNamespace("ggplot2", quietly = TRUE)
#' my_plot(mtcars)
```
### In Tests (tests/testthat/)
```r
# Imports - can use freely:
test_that("function works", {
result <- my_function(data)
expect_equal(result$value, expected)
})
# Suggests - MUST skip if not available:
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/)
```r
# YAML header for Suggests dependency:
# ---
# title: "My Vignette"
# vignette: >
# %\VignetteIndexEntry{My Vignette}
# %\VignetteEngine{knitr::rmarkdown}
# ---
# First chunk - setup with conditional evaluation:
# ```{r setup, include=FALSE}
# knitr::opts_chunk$set(
# eval = requireNamespace("ggplot2", quietly = TRUE)
# )
# ```
# Now code chunks only run if ggplot2 available:
# ```{r}
# library(ggplot2)
# ggplot(data, aes(x, y)) + geom_point()
# ```
```
**Alternative: Use separate vignettes:**
```dcf
# DESCRIPTION:
Suggests:
knitr,
rmarkdown,
ggplot2
# vignettes/basic-usage.Rmd - no optional deps
# vignettes/advanced-plotting.Rmd - requires ggplot2
```
### In Documentation (roxygen2)
```r
# References to Suggests packages:
#' @description
#' This function provides plotting capabilities. Requires the
#' \pkg{ggplot2} package to be installed.
#'
#' @seealso [ggplot2::ggplot()] for more plotting options
```
## Minimum Version Specifications
### When to Specify Versions
```dcf
# Always specify if you need specific features:
在 GitHub 查看