6  Dependencies

Published

2024-09-12

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.


The previous chapter showed how to document the functions with roxygen2. 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')

Review the chapters in each section:

library(shinypak)
list_apps(regex = '^06')
## # A tibble: 2 × 2
##   branch           last_updated       
##   <chr>            <dttm>             
## 1 06.1_pkg-exports 2024-09-03 22:10:17
## 2 06.2_pkg-imports 2024-09-04 21:31:54

Launch an app:

launch(app = "06.2_pkg-imports")

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.

Dependencies are handled with in the NAMESPACE directives (generated via roxygen2 tags) and three fields in the DESCRIPTION file (Suggests, Imports, or Depends).

%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Inconsolata', 'primaryColor': '#89D6FB', 'edgeLabelBackground':'#02577A', 'secondaryColor': '#98DC96'}}}%%
flowchart TD
    sap[[Our App-Package: sap]]
    Pkg_A[[R Package A: <code>fun_a</code>]]
    Pkg_B[[R Package B: <code>fun_b</code>]]
    User_1((User 1))
    User_2((User 2))

    sap -->|<code>Imports</code>| Pkg_A
    sap -->|<code>Imports</code>| Pkg_B

    sap -->|<code>export</code>| User_1
    sap -->|<code>export</code>| User_2

The NAMESPACE and DESCRIPTION handle package dependencies

Multiple packages can help you manage the dependencies in your app-package, and these are covered in the Dependency hell chapter. 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...

> library(sap)

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 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).

In the last chapter, we moved and documented the standalone app function (launch_app()) in the R/ folder. The app.R file now only contains the following:

# 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

6.1 Exports

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.

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

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. We export functions by including the @export tag in the roxygen2 comment block (above the function we want to export):

  • @export: The function name (my_func) is not required.

    #' @export my_func 
    #' my_func <- function() {
    #' 
    #' }

6.1.1 Export launch_app()

Let’s start by exporting the launch_app() function from sap by placing the @export tag above the function in R/launch_app.R:

#' Movies app standalone function
#'
#' Wrapper function for `shiny::shinyApp()`
#'
#' @return Shiny app
#' 
#' 
#' @seealso [mod_var_input_ui()], [mod_var_input_server()], [mod_scatter_display_ui()], [mod_scatter_display_server()]
#'
#' @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 load, document, and install the package to generate the NAMESPACE changes:



Ctrl/Cmd + Shift + L / D / B

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) sap namespace
Figure 6.1: The launch_app() is now part of the sap namespace

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:

Using @keywords internal without @export will result in a helpfile, but the function will only be accessible using pkg:::fun() (three colons, not two!).

6.1.2 What @export does

We’ll pause here to notice a few things about what @export does. After loading and documenting our package, the NAMESPACE is updated with export(launch_app), and the Console automatically calls library(sap).

%%{init: {'theme': 'base', 'themeVariables': { 'fontFamily': 'Inconsolata', 'primaryColor': '#89D6FB', 'edgeLabelBackground':'#02577A'}}}%%

flowchart TD
  Tag("Tag function with <code>@export</code>")
  LoadAll("Run <code>devtools::load_all()</code>")
  Document("Run <code>devtools::document()</code>")
  Exported("Function exported in namespace")
  NAMESPACE("<code>NAMESPACE</code> updated with <code>export(launch_app)</code>")
  RdFile("<code>.Rd</code> file created")

  Tag --> LoadAll
  Tag --> Document
  LoadAll --> Exported
  Document --> NAMESPACE
  Document --> RdFile
  Exported --> NAMESPACE
  
  style Tag fill:#8dd38d,stroke:none,rx:10,ry:10;
  style LoadAll fill:#89D6FB,stroke:none,rx:10,ry:10;
  style Document fill:#89D6FB,stroke:none,rx:10,ry:10;
  style Exported fill:#FFDD67,stroke:none,rx:10,ry:10;
  style NAMESPACE fill:#FFDD67,stroke:none,rx:10,ry:10;
  style RdFile fill:#FFDD67,stroke:none,rx:10,ry:10;

The @export tag makes a function available to anyone using our package

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"

6.1.2.1 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).

6.1.2.2 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
#' @param col_var string variable mapped to `color`
#' @param alpha_var number for point `alpha`
#' @param size_var number for point `size` 
#' 
#' @return A `ggplot2` plot object
#' 
#' 
#' @examples
#' scatter_plot(mtcars, 
#'     x_var = "mpg", 
#'     y_var = "disp", 
#'     col_var = "cyl", 
#'     alpha_var = 0.5, 
#'     size_var = 3)
#'     
#' @seealso [mod_scatter_display_server()]
#' 
#' @export
#' 

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



Ctrl/Cmd + Shift + L / D / B

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

6.1.2.3 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()

6.1.3 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.).

I rarely want to export everything from an R package, but it could be helpful if the primary audience for your app-package is other developers within your organization. See the Low-key @exports with @keywords internal box below for exporting functions without including them in your package index.

6.2 Imports

Launch app with the shinypak package:

launch('06.2_pkg-imports')

Managing imports is slightly more involved than exports because imported dependencies can live in DESCRIPTION and the NAMESPACE:

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

  • 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.

6.2.1 Package-level depencencies

The DESCRIPTION file manages dependencies with three fields: Depends, Imports, and Suggests. 2

6.2.1.1 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.

6.2.1.2 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.

  • Most add-on packages belong under the Imports field (i.e., functions from these packages are used in the code below R/).

6.2.1.3 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 33.

6.2.2 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.3

6.2.2.1 pkg::fun()

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

6.2.2.2 @importFrom

@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)

6.2.2.3 @import

@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”

6.2.3 Handling imports

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

  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
#' Standalone function
#'
#' Wrapper function for `shinyApp()`
#'
#' @return Shiny app
#' 
#' 
#' @seealso [mod_var_input_ui()], [mod_var_input_server()], 
#' [mod_scatter_display_ui()], [mod_scatter_display_server()]
#' 
#' @import shiny
#'
#' @export 
#' 
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
#' @param col_var string variable mapped to `color`
#' @param alpha_var number for point `alpha`
#' @param size_var number for point `size` 
#' 
#' @return A `ggplot2` plot object
#' 
#' 
#' @examples
#' scatter_plot(mtcars, 
#'     x_var = "mpg", 
#'     y_var = "disp", 
#'     col_var = "cyl", 
#'     alpha_var = 0.5, 
#'     size_var = 3)
#'     
#' @seealso [mod_scatter_display_server()]
#' 
#' @importFrom rlang .data
#' 
#' @export
#' 
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
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 all 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

# Generated by roxygen2: do not edit by hand

export(launch_app)
export(scatter_plot)
import(shiny)
importFrom(rlang,.data)

Updated DESCRIPTION with all Imports:

Package: sap
Title: Shiny App-Packages
Version: 0.0.0.9000
Author: John Smith <John.Smith@email.io> [aut, cre]
Maintainer: John Smith <John.Smith@email.io>
Description: An R package with a collection of Shiny applications.
License: GPL-3
Imports: 
    bslib,
    ggplot2,
    rlang,
    shiny,
    shinythemes,
    stringr,
    tools
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.2

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

6.2.4 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': 'base', 'themeVariables': { 'fontFamily': 'Inconsolata', 'primaryColor': '#89D6FB', 'edgeLabelBackground':'#02577A'}}}%%

flowchart TD
  Imports("List package under <code>Imports</code> in <code>DESCRIPTION</code>")
  Reference("Reference <code>package::function()</code> in code")
  LoadAll("Run <code>devtools::load_all()</code>")
  Document("Run <code>devtools::document()</code>")
  NamespaceUpdated("<code>NAMESPACE</code> updated")
  RdFileUpdated("<code>.Rd</code> file updated")

  Imports --> Reference
  Reference --> LoadAll
  Reference --> Document
  LoadAll --> NamespaceUpdated
  Document --> RdFileUpdated
  NamespaceUpdated --> RdFileUpdated
  
  style Imports fill:#8dd38d,stroke:none,rx:10,ry:10;
  style Reference fill:#8dd38d,stroke:none,rx:10,ry:10;
  style LoadAll fill:#89D6FB,stroke:none,rx:10,ry:10;
  style Document fill:#89D6FB,stroke:none,rx:10,ry:10;
  style NamespaceUpdated fill:#FFDD67,stroke:none,rx:10,ry:10;
  style RdFileUpdated fill:#FFDD67,stroke:none,rx:10,ry:10;

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 five packages in the Imports field of the DESCRIPTION file:

Imports: 
    ggplot2,
    rlang,
    shiny,
    shinythemes,
    stringr

6.2.4.1 The search() list

Are these packages on the search list?

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

This demonstrates that none of these packages are attached with sap. However, the rlang and shiny packages are included in the loadedNamespaces() (because we included them with @import/@importFrom).

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

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

6.3 More information on imports

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

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

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

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

  • library(pkg) loads the namespace of imported packages, but they are not attached to the search() path8

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

  • Using pkg::fun() makes calls to add-on packages explicit and easy to differentiate from the native functions developed in sap9

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

  • 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 exports10

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

  • 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().

Should I be using @importFrom or @import from?

  • Prefer @importFrom over @import (but try to avoid using either 11 12)

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

  • Imports are described briefly in R Packages, 2ed13 and covered in-depth in Advanced R, 2ed14

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

    • “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.

6.4 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).

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. Additional fields exists (i.e., Remotes), but these are special circumstances.↩︎

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

  4. “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(). Read more in the Importing functions section on the package website:↩︎

  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↩︎