6  Dependencies

Published

2025-01-16

Managing dependencies:

  • Exports: export objects from using @export. Exported functions are the functions that an app-package offers to the world (i.e., someone installs and loads the package, these are the functions they can directly use).

  • Imports: import functions from add-on packages using a ‘fully qualified variable reference’ (i.e., pkg::fun()) in the code below R/ and add the package name to the Imports field in the DESCRIPTION.

    • If the object can’t be imported using :: (i.e., an operator), use the @importFrom tag from roxygen2

    • If your code uses a lot of functions from another package (such as shiny in app-packages), use the @import tag from roxygen2

Workflow: List the add-on package in the Imports field of the DESCRIPTION file (i.e., with usethis::use_package('pkg')), then decide if you’re going to the functions in pkg with pkg::fun() (preferred), @importFrom, or @import.


Dependencies are the must-have components for your app-package, and they can be divided into imports and exports. - Imports are the functions we’re borrowing from add-on/third party packages (i.e., any packages not automatically loaded in a new R session).
- Exports are the functions, data, and other R objects our app-package offers to users.

In this chapter we’ll cover how to manage dependencies in your new app-package.

I’ve created the shinypak R package in an effort to make each section accessible and easy to follow. Install shinypak using pak (or remotes):

install.packages('pak')
pak::pak('mjfrigaard/shinypak')
library(shinypak)

List the apps in this chapter:

list_apps(regex = '^06')

Launch apps with launch()

launch(app = '06.1_exports')

Download apps with get_app()

get_app(app = '06.1_exports')

Dependencies are handled with in the NAMESPACE directives (generated via roxygen2 tags) and three fields in the DESCRIPTION file (Suggests, Imports, or Depends). Together, these files determine which functions and packages our app-package depends on, and which functions and object we make available to users of our app-package. This chapter will pick up where we left off with the 05_roxygen2 branch of sap.

Below is a folder tree of it’s contents:

sap/
  ├── DESCRIPTION
  ├── NAMESPACE
  ├── R/
  │   ├── mod_scatter_display.R
  │   ├── mod_var_input.R
  │   ├── launch_app.R
  │   ├── movies_server.R
  │   ├── movies_ui.R
  │   └── utils.R
  ├── README.md
  ├── app.R
  ├── man/
  │   ├── mod_scatter_display_server.Rd
  │   ├── mod_scatter_display_ui.Rd
  │   ├── mod_var_input_server.Rd
  │   ├── mod_var_input_ui.Rd
  │   ├── launch_app.Rd
  │   ├── movies_server.Rd
  │   ├── movies_ui.Rd
  │   └── scatter_plot.Rd
  ├── movies.RData
  ├── sap.Rproj
  └── www/
      └── shiny.png

4 directories, 21 files
1
The man folder now contains the help (.Rd) files for the functions in R/

Required @tags for all functions:

  • Make sure all functions have a documented title & description (@title and @description tags optional), function inputs and outputs (@param and @return), and demonstrations of how the function works (@examples)

Shiny-specific documentation:

  • Use @seealso to link module UI and server functions, and @family to link functions within a similar topic (i.e., ‘import data’ or ‘scatter plot’)

  • Provide shiny-specific information (use within the app, reactive state, more details about the @params, etc.) in @section blocks.

See the documentation chapter for more information

When in doubt…load, document, and install

During development, you might lose track of the last devtools function you called (I know I do). If this happens, I’ve found loading, documenting, and installing helps to re-orient me to the current state of the package.


Ctrl/Cmd + Shift + L

ℹ Loading sap


Ctrl/Cmd + Shift + D

==> devtools::document(roclets = c('rd', 'collate', 'namespace'))

ℹ Updating sap documentation
ℹ Loading sap
Documentation completed


Ctrl/Cmd + Shift + B

==> R CMD INSTALL --preclean --no-multiarch --with-keep.source sap

* installing to library ‘/path/to/local/install/sap-090c61fc/R-4.2/x86_64-apple-darwin17.0’
* installing *source* package ‘sap’ ...
** using staged installation
** R
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (sap)

Restarting R session...

It’s also satisfying to see all three functions execute without any errors!

Identifying dependencies

The first step in managing dependencies is identifying which add-on packages sap relies on.1 The diagram below outlines the basic process for importing functions from add-on packages to use in our locally developed package, and then exporting those functions for people to use when they install/load our package.

%%{init: {'theme': 'neutral', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"13px"}}}%%
flowchart TD
    subgraph RPkg["R Package:<code>pkg</code>"]
        fun("Has <code>fun()</code> function")
    end
    subgraph SapPkg["<code>sap</code> Package"]
    sap("Uses <code>pkg::fun()</code> in local<br><code>foo()</code> function")
    end
    Users("Users install/load <code>sap</code> to use <code>foo()</code>")
    RPkg -->|"import <code>fun()</code> from <code>pkg</code>"| SapPkg
    SapPkg -->|"export <code>foo()</code> from <code>sap</code>"| Users

Handling package dependencies

Our goal is to limit the dependencies to only those critical to the functioning of our app, because each additional dependency is a potential point of failure (should this package become unavailable or significantly change).

A great place to start is our app.R file:

# pkgs <- c("shiny", "shinythemes", "stringr", "ggplot2", "rlang")
# install.packages(pkgs, quiet = TRUE)

# packages ------------------------------------
library(shiny)
library(shinythemes)
library(stringr)
library(ggplot2)
library(rlang)

# launch_app ------------------------------------
launch_app()

Ideally, we’ll want to replace these calls to libary(), but first we have to make sure the functions we’re using in these packages will be available in sap.

When we run the contents of app.R, we see the following:

> launch_app()
Error in launch_app() : could not find function "launch_app"

Why can’t R find the "launch_app" function in app.R?

Let’s recap what we’ve done so far:

app.R

  1. The app.R file loads the necessary packages and calls launch_app():

sap/
    └── app.R

R/

  1. The R/launch_app.R file contains the code and roxygen2 documentation for launch_app() function:

sap/
    └── R/
        └── launch_app.R

man/

  1. roxygen2 generates the man/launch_app.Rd documentation file:

sap/
    └── man/
          └── launch_app.Rd

The error above is telling us that despite having documentation for launch_app() in the R/ folder and generating the corresponding .Rd file in man/, launch_app() isn’t being exported from sap.

6.1 Package exports

The exact cause of the error above becomes more apparent when we try to explicitly namespace launch_app() from sap:2

sap::launch_app()
Error: 'launch_app' is not an exported object from 'namespace:sap'

Launch app with the shinypak package:

launch('06.1_pkg-exports')

To make the launch_app() function available to users of our package, we need to export it by including the @export tag in the roxygen2 comment block:

  • @export: make function available to users of sap.

    #' @export my_func
    #' my_func <- function() {
    #' 
    #' }
    1
    Placed above the function we want to export (function name is not required)

Export launch_app()

We’ll export launch_app() from sap by placing the @export tag above the function in R/launch_app.R:

#' Launch the Movies Review Application
#'
#' Starts the Movies Review Shiny application, which provides a customizable
#' scatter plot interface for analyzing movie data.
#'
#' @return A **Shiny application** object.
#'
#' @section Details:
#' The application uses:
#' - **UI**: Defined in [`movies_ui()`].
#' - **Server Logic**: Defined in [`movies_server()`].
#'
#' @seealso
#' - [`movies_ui()`] for the user interface.
#' - [`movies_server()`] for the server logic.
#'
#' @family **Standalone Application**
#'
#' @examples
#' if (interactive()) {
#'   launch_app()
#' }
#'
#' @export
launch_app <- function() {
  shiny::shinyApp(ui = movies_ui, server = movies_server)
}

In app.R, we’ll replace the calls to library() with a single call to library(sap)

# packages ------------------------------------
library(sap)

# launch_app ------------------------------------
launch_app()

We’ll document the package to generate the NAMESPACE changes:


Ctrl/Cmd + Shift + D

Now, when we run the code app.R, we see the following:

launch_app()

launch_app()

We’ve lost the Shiny icon (www/shiny.png) in the UI, but we’ll address this in Chapter 9.

launch_app() launches our application!

The NAMESPACE file now contains a single export (launch_app), and when we enter sap:: in the Console, we see the launch_app() function help file in the tab completion.

(a) updated NAMESPACE
(b) launch_app() from the sap namespace
Figure 6.1: The launch_app() is now part of the sap namespace

What @export does

We’ll pause here to notice a few things about what @export does. When we documented our package, the code was automatically loaded before the NAMESPACE was updated with export(launch_app).

> devtools::document()
 Updating sap documentation
 Loading sap
Writing NAMESPACE
1
Call to devtools::load_all()

document() will call load_all() to make sure all the changes in the R/ folder are included in the updated documentation.

%%{init: {'theme': 'neutral', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"13px"}}}%%
flowchart
    subgraph R["<strong>R/ folder</strong>"]
        Tag("Add <code>@export</code><br>to function<br>documentation")
    end
  subgraph NS["<strong>NAMESPACE</strong>"]
        Exported(["<code>export(launch_app)</code>"])
  end
  subgraph Man["<strong>man/ folder</strong>"]
        RdFile(["<code>.Rd</code> files created"])
  end
  Document[["Run <code>document()</code>"]]
  Load("Calls <code>load_all()</code>")
  

  Tag --> Document
  Document -.-> Load --> NS & Man

What @export does

We can confirm launch_app() has been exported with ls(), which returns “the names of the objects in the specified environment.

ls(name = "package:sap")
[1] "launch_app"

The search() list

library(sap) attaches sap to the search list. We can view all the attached packages in the string returned from search():

"package:sap" %in% search()
[1] TRUE

What about the add-on/third-party package functions launch_app() relies on, like ggplot2? Let’s check to see if ggplot2 is also attached to the search() list:

c("package:ggplot2") %in% search()
[1] FALSE

Why does this matter? Because if these packages aren’t attached to the search() list, we can’t call their functions directly (the way we would if we’d loaded the package with library()). For example, if we try to use ggplot2 to build a plot (similar to the one we have in the app), we see the following:

ggplot(data = mtcars, 
  aes(x = disp, y = mpg)) + 
  geom_point()
# Error in ggplot(data = mtcars, aes(x = disp, y = mpg)) : 
#  could not find function "ggplot"

We can use the add-on/third-party package functions sap relies on, but we need to explicitly namespace these functions from their original package namespaces (i.e., using pkg::fun()):

ggplot2::ggplot(data = mtcars, 
  ggplot2::aes(x = disp, y = mpg)) + 
  ggplot2::geom_point()

We can use ggplot2 if we explicitly namespace it’s functions

What happens to add-on/third party functions that are exported from sap?

When a user loads sap with library(sap), any add-on/third-party package functions used in exports are available to users if they use pkg::fun() (or if they load the package themselves with library()).

Access to add-on/third-party package functions has implications for the other functions in sap–for example, the scatter_plot() function uses ggplot2 functions. But we’re not exporting scatter_plot(), so when we attempt to run the examples, we see the following error:

(a) Error in scatter_plot() examples
Figure 6.2: Examples in scatter_plot() function without exporting

Examples for ‘sap::scatter_plot’

The message at the top of the Help pane is informative because it tells us that despite scatter_plot() being functional when we run launch_app(), it’s not part of the package namespace (and thus, not accessible to users in the help file).

Export scatter_plot()

Let’s add the @export tag to R/scatter_plot.R so it’s exported from sap.

#' Create scatter plot 
#'
#' Custom [`ggplot2`](https://ggplot2.tidyverse.org/) function for building 
#' scatter plots in `sap`.
#' 
#' 
#' @param df `data.frame` or `tibble`
#' @param x_var string variable mapped to `x` axis
#' @param y_var string variable mapped to `y` axis
#' Generate a Scatter Plot
#'
#' Creates a scatter plot using `ggplot2` with the specified data and 
#' aesthetics.
#'
#' @param df *(data.frame)* The dataset containing the variables to plot.
#' @param x_var *(character)* Name of the variable for the x-axis.
#' @param y_var *(character)* Name of the variable for the y-axis.
#' @param col_var *(character)* Name of the variable for the color aesthetic.
#' @param alpha_var *(numeric)* Transparency level of points (0 to 1).
#' @param size_var *(numeric)* Size of points.
#'
#' @return A `ggplot` object representing the scatter plot.
#'
#' @section Details:
#' `scatter_plot()` is designed for use in Shiny applications but can also be 
#'  used independently.
#' It supports customization of transparency, size, and color aesthetics.
#'
#' @seealso
#' - [`mod_scatter_display_server()`] for integrating this function into the
#'   scatter plot module.
#' - [`ggplot2::ggplot()`](https://ggplot2.tidyverse.org/) for details on 
#'   `ggplot2` usage.
#'
#' @family **Utility Functions**
#'
#' @examples
#' scatter_plot(
#'   df = mtcars,
#'   x_var = "mpg",
#'   y_var = "hp",
#'   col_var = "cyl",
#'   alpha_var = 0.7,
#'   size_var = 3
#' )
#'
#' @export

After documenting sap, the NAMESPACE is updated with the export() directive:


Ctrl/Cmd + Shift + D

The contents of the updated NAMESPACE file and typing sap:: in the Console now displays the scatter_plot() help file in the tab completion:

(a) @export the scatter_plot function
(b) scatter_plot()
Figure 6.3: scatter_plot() is now part of the sap namespace

Below, we confirm users can access the help file for scatter_plot() and run the examples:

Running examples in ?scatter_plot

Running examples in ?scatter_plot

loadedNamespaces()

We’ve already confirmed that ggplot2 isn’t attached with sap (and hence, it is not included in the search() list)

c("package:ggplot2") %in% search()
[1]  FALSE

However, we can access the functions we used the pkg::fun() syntax with because those functions are included in the loaded namespaces (which we can view with loadedNamespaces())

c("ggplot2") %in% loadedNamespaces()
[1] TRUE

What about functions that aren’t exported from sap?

Functions that are not exported (i.e., do not include the @export tag) are still accessible after installing and loading a package using the pkg:::fun()

What to @export

‘Always err on the side of caution, and simplicity. It’s easier to give people more functionality than it is to take away stuff they’re used to’ - What to export, R Packages, 2ed

When determining which functions to export, consider the question: “When a user installs and loads sap, what functions do I want to be available?

In app-packages, I’ll take the following general approach:

  • Start by exporting the standalone app function (launch_app())

  • Then selectively export modules and/or functions that perform distinct tasks with potentially reusable functionality (i.e., generate specific UI components, perform data processing tasks, etc.).

It’s rare that I don’t export functions from app-packages, but I like to make sure users have the ability to get ‘under to hood’ and see how each part of an application works.

If you’d like to the Low-key @exports with @keywords internal box below for exporting functions without including them in your package index.

If you’d like a function to be exported, but not listed in your app-package index, you can use @export in combination with @keywords internal:

#' @export
#' 
#' @keywords internal

For example, adding @export and @keywords internal to R/scatter_plot.R will make the function accessible to users and generate a help file:

But if a user were to click on the Index for sap (at the bottom of the help file) scatter_plot would not be listed:

6.2 Package imports

Launch app with the shinypak package:

launch('06.2_imports')

Importing dependencies is slightly more involved than exports because imports are managed by both the DESCRIPTION and the NAMESPACE:

  1. The DESCRIPTION file handles package-level dependencies, specifying which add-on packages our app-package uses.

  2. The NAMESPACE manages function-level access, importing functions from add-on packages to be used in our app-package, and–as we’ve seen above–exporting functions from our app-package for others to use.

Package-level depencencies

The DESCRIPTION file manages dependencies with three fields: Depends, Imports, and Suggests. Most add-on packages belong under the Imports field (i.e., functions from these packages are used in the code below R/).3

Depends

Packages listed under Depends are essential for our app-package to work. These packages will be attached before our package when library(sap) is called.

Imports

Packages listed under Imports are necessary for our app-package to work. These packages are loaded (but not attached) when our app-package is installed.

Suggests

The Suggests field should include any packages that enhance our app-package, but aren’t necessary for the basic functionality. This might include packages used in examples, vignettes, tests, etc.

Generally speaking, you want to keep your app-package lightweight (i.e., limit the number of add-on/third-party dependencies, other than base-R packages and shiny). Doing this ensures you’ll be able to safely use this app-packages as a dependencies in the next app-packages. We’ll cover tracking and exploring dependencies in Chapter 31.

Function-level access

Function-level access is managed using namespace-qualified references (or ‘explicit namespacing’) in the code below R/. The NAMESPACE can also be used to include add-on packages or functions with the @import and @importFrom tags.4

  1. Namespace-qualified referencing: Refer to add-on package functions using pkg::fun() syntax in the code below R/.

  2. Special imports: @importFrom should be used when 1) “You can’t call an operator from another package via :: 2) “importing a function makes your code much more readable” (not easier to write)

  3. Importing everything: @import should be used if “you make such heavy use of so many functions from another package that you want to import its entire namespace”

Handling imports

The workflow I use to manage add-on dependencies comes from the advice in the roxygen2 documentation:

“if you are using just a few functions from another package, we recommending adding the package to the Imports: field of the DESCRIPTION file and calling the functions explicitly using ::, e.g., pkg::fun()

…“If the repetition of the package name becomes annoying you can @importFrom and drop the pkg::fun(). - Importing functions

  1. Include the add-on package to the Imports field with usethis::use_package().

  2. Refer to add-on functions using explicit namespacing (i.e., pkg::fun()) in the code beneath R/.

We have some special considerations for the imported add-on functions in our app-package:

Using @import

A substantial portion of the code in sap comes from shiny, so we’ll remove the explicit namespacing and place the @import tag in R/launch_app.R5

show/hide R/launch_app.R
#' Launch the Movies Review Application
#'
#' Starts the Movies Review Shiny application, which provides a customizable
#' scatter plot interface for analyzing movie data.
#'
#' @return A **Shiny application** object.
#'
#' @section Details:
#' The application uses:
#' - **UI**: Defined in [`movies_ui()`].
#' - **Server Logic**: Defined in [`movies_server()`].
#'
#' @seealso
#' - [`movies_ui()`] for the user interface.
#' - [`movies_server()`] for the server logic.
#'
#' @family **Standalone Application**
#'
#' @examples
#' if (interactive()) {
#'   launch_app()
#' }
#'
#' @import shiny
#' 
1
Import entire shiny package namespace

Using @importFrom

.data can’t be exported using ::, so we’ll include @importFrom in R/scatter_plot.R. On the other hand, ggplot2 has over 400 functions, so we’ll add the package to the Imports field and use the namespace-qualified references.6

show/hide R/scatter_plot.R
#' Create scatter plot 
#'
#' Custom [`ggplot2`](https://ggplot2.tidyverse.org/) function for building 
#' scatter plots in `sap`.
#' 
#' 
#' @param df `data.frame` or `tibble`
#' @param x_var string variable mapped to `x` axis
#' @param y_var string variable mapped to `y` axis
#' Generate a Scatter Plot
#'
#' Creates a scatter plot using `ggplot2` with the specified data and 
#' aesthetics.
#'
#' @param df *(data.frame)* The dataset containing the variables to plot.
#' @param x_var *(character)* Name of the variable for the x-axis.
#' @param y_var *(character)* Name of the variable for the y-axis.
#' @param col_var *(character)* Name of the variable for the color aesthetic.
#' @param alpha_var *(numeric)* Transparency level of points (0 to 1).
#' @param size_var *(numeric)* Size of points.
#'
#' @return A `ggplot` object representing the scatter plot.
#'
#' @section Details:
#' `scatter_plot()` is designed for use in Shiny applications but can also be 
#'  used independently.
#' It supports customization of transparency, size, and color aesthetics.
#'
#' @seealso
#' - [`mod_scatter_display_server()`] for integrating this function into the
#'   scatter plot module.
#' - [`ggplot2::ggplot()`](https://ggplot2.tidyverse.org/) for details on 
#'   `ggplot2` usage.
#'
#' @family **Utility Functions**
#'
#' @examples
#' scatter_plot(
#'   df = mtcars,
#'   x_var = "mpg",
#'   y_var = "hp",
#'   col_var = "cyl",
#'   alpha_var = 0.7,
#'   size_var = 3
#' )
#'
#' @export
#' 
#' @importFrom rlang .data
#' 
1
Import a the .data operator from rlang

use_package('pkg')

As an example, we’ll add the bslib package and update our app UI layout:

usethis::use_package('bslib')
 Setting active project to '/Users/mjfrigaard/projects/apps/sap'
 Adding 'bslib' to Imports field in DESCRIPTION
 Refer to functions with `bslib::fun()`

In movies_ui(), we’ll change the fluidPage() to the bslib::page_fillable() and adjust move the data source information to the bslib::card_footer():

updated movies_ui() bslib function
#' User Interface for the Movies Review Application
#'
#' Creates the user interface (UI) for the Movies Review application, which 
#' allows users to create customizable scatter plots based on movie data.
#'
#' @return A Shiny `tagList` object containing the UI elements.
#'
#' @section Details:
#' The interface is built using [`bslib`](https://rstudio.github.io/bslib/)
#' - **Page (fillable)**: [`bslib::page_fillable()`](https://rstudio.github.io/bslib/reference/page_fillable.html) 
#'   displays the app title. 
#' - **Sidebar**: [`bslib::layout_sidebar()`](https://rstudio.github.io/bslib/reference/sidebar.html) 
#'   includes a logo and the variable 
#'   selection module.
#'   ([`mod_var_input_ui`]).
#' - **Card**: [`bslib::card()`](https://rstudio.github.io/bslib/reference/card.html) 
#'   displays the scatter plot module 
#'   ([`mod_scatter_display_ui`]).
#'
#' @seealso
#' - [`movies_server()`] for the server logic of the app.
#' - [`mod_var_input_ui()`] and [`mod_scatter_display_ui()`] for the modules 
#'   included in the UI.
#'
#' @family **Application Components**
#'
#' @examples
#' if (interactive()) {
#'   shiny::shinyApp(ui = movies_ui(), server = movies_server)
#' }
#'
movies_ui <- function() {
  tagList(
    bslib::page_fillable(
      h1("Movie Reviews"),
      bslib::layout_sidebar(
        sidebar =
          bslib::sidebar(
            title = tags$h4("Sidebar inputs"),
            img(
              src = "shiny.png",
              height = 60,
              width = 55,
              style = "margin:10px 10px"
            ),
            mod_var_input_ui("vars")
          ),
        bslib::card(
          full_screen = TRUE,
          bslib::card_header(
            tags$h4("Scatter Plot")
          ),
          mod_scatter_display_ui("plot"),
          bslib::card_footer(
            tags$blockquote(
              tags$em(
                tags$p(
                  "The data for this application comes from the ",
                  tags$a("Building web applications with Shiny",
                    href = "https://rstudio-education.github.io/shiny-course/"
                  ),
                  "tutorial"
                )
              )
            )
          )
        )
      )
    )
  )
}

After adding the add-on packages to the DESCRIPTION with usethis::use_package(), then deciding if/where to use @importFrom and @import, we’ll load, document, and install sap:



Ctrl/Cmd + Shift + L / D / B

When we review the updated NAMESPACE and DESCRIPTION files, we should see the following:

Updated NAMESPACE with @import and @importFrom:

NAMESPACE

NAMESPACE

Updated DESCRIPTION with all Imports:

DESCRIPTION

DESCRIPTION

roxygen2 will update the NAMESPACE, but usethis::use_package() is needed to update the DESCRIPTION.

When we run launch_app(), we see the application launches and we can still run the scatter_plot() examples:

(a) launch_app() works
(b) Examples in ?scatter_plot
Figure 6.4: Confirming we still have full functionality in sap

What @import does

The figure below attempts to capture some of confusion between the dependencies listed in the NAMESPACE and the Imports field in the DESCRIPTION file.7

%%{init: {'theme': 'neutral', 'themeVariables': { 'fontFamily': 'monospace', "fontSize":"13px"}}}%%
flowchart TD
    subgraph DESCRIPTION["<strong>DESCRIPTION</strong>"]
        UsePkg["<code>use_package('pkg')</code>"]
        Imports("<code>Imports</code><br> in DESCRIPTION")
    end
    subgraph R["<strong>R/ Folder</strong>"]
        Tag{{"Use <code>pkg::fun()</code><br>"}}
        Import("Use <code>@import</code>")
        ImportFrom("Use <code>@importFrom</code>")
    end
    subgraph NAMESPACE["<strong>NAMESPACE</strong>"]
        NSImport("<code>import(pkg)</code>")
        NSImportFrom("<code>importFrom(fun,pkg)</code>")
    end
    Document(["<code>devtools::document()</code></strong>"])

  UsePkg -->|"Adds <code>pkg</code> to"|Imports
  DESCRIPTION ==>|"Use <code>fun()</code> from <code>pkg</code>"|Tag
  DESCRIPTION -.-> |"Everything from <code>pkg</code>"|Import 
  DESCRIPTION -.-> |"Special cases from <code>pkg</code>"|ImportFrom
  Tag ==> Document
  Import -.-> Document -.-> |"Adds <code>pkg</code> to"|NSImport
  ImportFrom -.-> Document -.-> |"Adds <code>fun, pkg</code> to"|NSImportFrom

devtools::document does not change the DESCRIPTION file

Figure 6.5: devtools::document() (or Ctrl/Cmd + Shift + D) updates the NAMESPACE with any @import, @importFrom or @export tags. However, no changes are made to the DESCRIPTION file.

Let’s confirm we’re still only exporting launch_app() and scatter_plot() from sap:

ls(name = "package:sap")
[1] "launch_app"   "scatter_plot"

Great. Now we’ve listed six packages in the Imports field of the DESCRIPTION file:

Imports: 
    bslib,
    ggplot2,
    rlang,
    shiny,
    stringr,
    tools

The search() list

Are these packages on the search list?

pkgs <- c("package:bslib", "package:ggplot2", 
          "package:rlang", "package:shiny", 
          "package:stringr", "package:tools")
pkgs %in% search()
[1] FALSE FALSE FALSE FALSE FALSE FALSE

This demonstrates that none of these packages are attached with sap.

loadedNamespaces()

However, the rlang and shiny packages are included in the loadedNamespaces() (because we included them with @import/@importFrom).

pkgs <- c("bslib", "ggplot2", "rlang", 
          "shiny", "stringr", "tools")
pkgs %in% loadedNamespaces()
[1] FALSE FALSE  TRUE  TRUE FALSE  TRUE

We can still access the add-on package functions in sap using the pkg::fun() syntax:

ggplot2::ggplot(data = mtcars, 
  ggplot2::aes(
      x = disp, 
      y = mpg)) + 
  ggplot2::geom_point()

ggplot2 functions are still available if we explicitly namespace

ggplot2 functions are still available if we explicitly namespace

Imports FAQ

Below are handful of questions and answers I’ve encountered regarding package imports:

Question 1: How can I include an add-on package to my DESCRIPTION file?

Answer 1: usethis::use_package() automatically adds a package in the Imports section, and has options for specifying the minimum version.

Question 2: Will users of my app-package have access to the packages listed in the Imports field of my DESCRIPTION file?

Answer 3: library(pkg) loads the namespace of imported packages, but they are not attached to the search() path.8

Question 4: How can I tell the difference between functions written by a package author and imported functions in the code below R/?

Answer 4: using pkg::fun() makes calls to add-on packages explicit and easy to differentiate from the native functions developed in sap.9

Question 5: What does the NAMESPACE do when my package is installed by a user?

Answer 5: managing the NAMESPACE ensures your app-package works when it’s installed and loaded on another machine, because R will read your package namespace to find what it imports and exports.10

Question 6: Where should I place the @importFrom tag in the code below R/?

Answer 6: place the @importFrom pkg fun tag directly above the code using the add-on function. You can also consolidate all @import and @importFrom tags into a single package doc file (i.e., R/[sap]-package.R) by calling usethis::use_package_doc().

Question 7: Should I be using @importFrom or @import from?

Answer 7: prefer @importFrom over @import, but try to avoid using either.11 12

Question 8: Where can I find more information about package namespaces and imports?

Answer 8a: Imports are described briefly in R Packages, 2ed13 and covered in-depth in Advanced R, 2ed.14

Answer 8b: “Each namespace has an imports environment that can contain bindings to functions used by the package that are defined in another package.”

Answer 8c: “The imports environment is controlled by the package developer with the NAMESPACE file. Specifically, directives such as importFrom() and imports() populate this environment.”

In order for app-package to work, users needs to have access to any add-on packages that are called in the code below R/. Knowing when, why, how and what happens to imports helps you decide how to fit these habits into your package development workflow.

Checking dependencies

With all the moving parts in dependency management, it can be easy to forget if you’ve documented everything correctly. So far we haven’t covered using devtools::check() as part of your app-package habits (which is fine), but this is one area it’s particularly helpful.

For example, if I had listed shiny as an import using the @import tag (resulting in the import(shiny) directive in the NAMESPACE), devtools::check() would produce the following error:

── R CMD check results ────────────────────────── sap 0.0.0.9000 ────
Duration: 7.4s

❯ checking package dependencies ... ERROR
  Namespace dependency missing from DESCRIPTION Imports/Depends entries: ‘shiny’
  
  See section ‘The DESCRIPTION file’ in the ‘Writing R Extensions’
  manual.

1 error ✖ | 0 warnings ✔ | 0 notes ✔
Error: R CMD check found ERRORs
Execution halted

Exited with status 1.

Recap

Below are the main takeaways from managing the imports and exports from your app-package:

Dependencies recap

This chapter covered:

  • Exports: Aim for a balance between simplicity and utility when deciding which functions to export (i.e., what functions should be available to users who install your package). Export objects from using @export

  • Imports: Use pkg::fun() syntax when you use add-on package functions and include them in the Imports field of the DESCRIPTION file. App-packages use so many shiny functions it makes sense to include @import shiny to 1) ensure all of these functions are available, and 2) you won’t need to use pkg::fun().

    • DESCRIPTION Imports This field lists the packages your app-package uses. All add-on packages used in the R/ folder must be listed in the Imports field. These functions can be called using the pkg::fun() syntax (or with @importFrom()). Functions from these packages will be available for your package, but not for the user unless they use the :: operator or load the package themselves with library().

    • NAMESPACE imports: The @import/@importFrom tags make the functions from add-on packages available to your package. Favor using @importFrom over @import for add-on package functions (the only exception being shiny, which you’d want to use @import).

The Imports field in the DESCRIPTION handles package-level dependencies (and it is managed manually or with usethis::use_package()), while the NAMESPACE handles function-level access (with @export and @import/@importFrom). Multiple packages can help you manage the dependencies in your app-package, and these are covered in the Dependency hell chapter.

If you’d like to read more about package dependencies, I recommend Writing R Extensions (specifically the sections on dependencies 15 and namespaces 16).

Please open an issue on GitHub

In the next section, we’ll cover how to ensure the movies.RData can be stored and loaded in our app-package!


  1. I’ve made this process somewhat easier by explicitly namespacing all of the add-on package functions in sap (i.e., with pkg::fun()).↩︎

  2. Read more in the Exports section of R Packages, 2nd Ed↩︎

  3. Additional fields exists (i.e., Remotes), but these are special circumstances.↩︎

  4. Read more about this in the section titled, ‘In code below R/’ in R Packages, 2ed↩︎

  5. Using @import is not generally considered best practice, but it makes sense for app-packages: …for Shiny apps, I recommend using @import shiny to make all the functions in the Shiny package easily available. Mastering Shiny, R CMD check↩︎

  6. Read more about using ggplot2 in packages in the section titled, ‘Referring to ggplot2 functions↩︎

  7. See the section titled, ‘Confusion about Imports’ in R Packages, 2ed, “Listing a package in Imports in DESCRIPTION does not ‘import’ that package.↩︎

  8. Users can access functions from add-on packages with the pkg::fun syntax.↩︎

  9. Our recommended default is to call external functions using the package::function() syntax.” - R Packages, 2ed↩︎

  10. The namespace controls the search strategy for variables used by functions in the package. If not found locally, R searches the package namespace first, then the imports, then the base namespace and then the normal search path (so the base namespace precedes the normal search rather than being at the end of it). - Writing R Extensions↩︎

  11. Using importFrom selectively rather than Imports is good practice and recommended notably when importing from packages with more than a dozen exports and especially from those written by others (so what they export can change in future).” - Specifying imports and exports.↩︎

  12. Specifically, we recommend that you default to not importing anything from [add-on packages] into your namespace. This makes it very easy to identify which functions live outside of your package, which is especially useful when you read your code in the future. This also eliminates any concerns about name conflicts between [add-on packages] and your package.” - R Packages, 2ed↩︎

  13. See the Function lookup inside a package section of R Packages, 2ed↩︎

  14. See the Package environments and the search path of Advanced R, 2ed↩︎

  15. See section 1.1.3 Package Dependencies in Writing R Extensions↩︎

  16. See section 1.5, Package namespaces in Writing R Extensions↩︎