R Packages: vignettes + dissemination



Grayson White

Math 241
Week 12 | Spring 2026

Week’s Goals

Mon Lecture

  • R package documentation and metadata

Wed Lecture

  • R package testing

  • R package dissemination:

    • Vignettes, and
    • package websites

Recap OuR Package: pdxHoles



  • Other questions so far?

Testing

Testing – Informally

Return to our first function:

coef_of_var <- function(x, na.rm = FALSE, trim = 0){
  stopifnot(is.numeric(x))
  sd(x, na.rm = na.rm) / mean(x, na.rm = na.rm, trim = trim)
}


Ran so many tests!

coef_of_var(x = rnorm(n = 10000, mean = 1, sd = 4))
[1] 3.931291

Testing – Informally

Ran so many tests!

library(pdxTrees)
pdxTrees <- get_pdxTrees_parks()
DBH_new <- c(pdxTrees$DBH, Inf)
coef_of_var(DBH_new)
[1] NaN
coef_of_var(pdxTrees)
Error in `coef_of_var()`:
! is.numeric(x) is not TRUE

Testing – Informally

Ran so many tests!

coef_of_var(pdxTrees$Condition)
Error in `coef_of_var()`:
! is.numeric(x) is not TRUE
coef_of_var(c(TRUE, FALSE, FALSE))
Error in `coef_of_var()`:
! is.numeric(x) is not TRUE

How should we formally test our package functions?

Testing our Package Functions

Standard workflow in package development:

  1. Write/revise the function in an .R script.
    • (Also write documentation using roxygen2)
  2. Load the function with devtools::load_all()
  3. Experiment with the function, giving it different inputs.
  4. Repeat.


  • Step 3 likely involves similar informal testing as we were doing for our functions outside an R package.

  • Good to transition to automated testing (also called unit testing) using testthat!

Why Automated Testing?

  • Fewer bugs.
    • Describe the behavior of your function in both the code and the test. Check against each other.
    • When adding a new feature to a function, you reduce the chances it breaks the existing features.
  • Can help motivate more refactoring of your code.
    • Functions that are easier to test are usually easier to understand.

testthat

  • Within your package RStudio Project, run the following to get started:
usethis::use_testthat()
  • Modifies your DESCRIPTION file:
Suggests: testthat (>= 3.0.0)
Config/testthat/edition: 3
  • Creates the tests/testthat folder.

  • In particular, we now have a testthat.R file that will initiate all your tests every time you run devtools::check().

Test File Structure

  • For every insert_function_name.R function in the R folder that you want tests for, create an test-insert_function_name.R for storing your tests.
    • You can automate this process with:
usethis::use_test("plot_holes")
  • Each test file holds one or more test_that() tests for a particular function in your package.
  • Each test:
    • Describes what it is testing.
    • Has one or more expectations.

Test File Structure

library(testthat)
test_that("multiplication works", {
  expect_equal(2 * 2, 4)
})
Test passed 😀


test_that("multiplication works", {
  expect_equal(2 * 2, 5)
})
── Failure: multiplication works ───────────────────────────────────────────────
2 * 2 not equal to 5.
1/1 mismatches
[1] 4 - 5 == -1
Error:
! Test failed

Expectations

Expectation: Expected result of a computation

  • Start with expect_---()

  • Two main arguments:

    • 1st: Actual result
    • 2nd: What you expect


expect_equal(2 * 2, 4)
expect_equal(2 * 2, 5)
Error:
! 2 * 2 not equal to 5.
1/1 mismatches
[1] 4 - 5 == -1
  • They automate the visual checking of results in the console.

Expectations

Expectation: Expected result of a computation


Key Questions:

  • Does it have the correct value(s) or dimensions?
  • Does it have the correct class?
  • Does it produce an error when it should?

Let’s explore some of the expect_*() functions in testthat!

Testing Equality

expect_equal(2 * 2, 4)
expect_equal(2 * 2, 4 + 0.0000001)
Error:
! 2 * 2 not equal to 4 + 1e-07.
1/1 mismatches
[1] 4 - 4 == -1e-07
expect_equal(2 * 2, 4 + 0.00000001)
expect_identical(2 * 2, 4 + 0.0000001)
Error:
! 2 * 2 not identical to 4 + 1e-07.
1/1 mismatches
[1] 4 - 4 == -1e-07
expect_equal(2 * 2, 4L)
expect_identical(2 * 2, 4L)
Error:
! 2 * 2 not identical to 4L.
Objects equal but not identical

Testing Class

Check the class of your output:

library(pdxHoles)
test_that("check class", {
  expect_s3_class(plot_holes(), "data.frame")
})
── Failure: check class ────────────────────────────────────────────────────────
plot_holes() inherits from 'ggplot2::ggplot'/'ggplot'/'ggplot2::gg'/'S7_object'/'gg' not 'data.frame'.
Error:
! Test failed


test_that("check class", {
  expect_s3_class(plot_holes(), "ggplot")
})
Test passed 😀

Testing Errors

  • Remember to use defensive coding in your function and then check them with your tests.
test_that("errors when it should", {
  expect_error(plot_holes(colors = c("green", "red")))
})
── Failure: errors when it should ──────────────────────────────────────────────
`plot_holes(colors = c("green", "red"))` did not throw an error.
Error:
! Test failed
  • Need to put better checks in plot_holes().

  • There are similar functions for warnings and messages: expect_warning(), expect_message()

Testing Guidelines

  • Focus on outputs and behaviors of your functions.
    • The defensive code in your functions focuses on the inputs.
  • Test each behavior in one test.
  • If you find a bug, write a test!

Running Tests – Three Options

Option 1: Refine a specific test

# Load the function
devtools::load_all()

# Write the body test
expect_equal(2 * 2, 4)

# Run the test
test_that("multiplication works", {
  expect_equal(2 * 2, 4)
})

Running Tests – Three Options

Option 2: Run all tests on a function

testthat::test_file("tests/testthat/test-plot_holes.R")


Option 3: Run all tests on all functions

devtools::test()
# OR
devtools::check()
  • Will error out if any of the tests don’t pass.

Vignettes

“The vignette format is perfect for showing a workflow that solves that particular problem, start to finish. Vignettes afford you different opportunities than help topics: you have much more control over the integration of code and prose and it’s a better setting for showing how multiple functions work together.” – Hadley Wickham and Jenny Bryan


browseVignettes()

Vignettes Set-up

To get started, run:

usethis::use_vignette("pdxHoles")

Which

  • Creates the vignettes folder.
  • Updates the DESCRIPTION with new dependencies.
  • Provides a template in the file pdxHoles.Rmd.
  • Updates your .gitignore.

Vignettes Workflow

  • Run devtools::load_all() to make sure you have access to the data and functions in the package.
  • Edit the narrative and code chunks.
  • To render the vignette using a temporarily installed, development version of your package, run
devtools::build_rmd("vignettes/pdxHoles.Rmd")
  • Inspect the output file.
  • Repeat!

Vignettes Workflow

Eventually,

  • To make the current state available locally with an install of the package:
devtools::install(dependencies = TRUE, build_vignettes = TRUE)
  • To get vignettes when installing a package from GitHub:
devtools::install_github(dependencies = TRUE, build_vignettes = TRUE)
  • Vignettes are always built when you submit to CRAN:
devtools::submit_cran()

Suggestions for Writing a Good Vignette

  • Think of your vignette as an instructive tutorial.
  • First, identify (for yourself) what you want the vignette to accomplish.
  • Be careful with the curse of knowledge.
  • Break up the code with concise but clear narrative that motivates the next block of code.
  • Hyper-link to other relevant packages and functions.


Important Note:

  • Any package used in a vignette must be listed in the DESCRIPTION file under either Imports or Suggests.

Website for Your Package with pkgdown

Create the Site

Run:

usethis::use_pkgdown()
  • Creates pkgdown.yml: The configuration file for your package.
  • Adds the correct files to the .Rbuildignore.
  • Creates the docs folder which will store all the pages for the website.

Build the Site

To create the site, run:

pkgdown::build_site()
  • The site is only as good as the documentation included in your package.
    • But, if you have created useful help files, an informative Readme, a complete DESCRIPTION file, and a vignette, then your default pkgdown site will be pretty great!

Deploy the Site

To set it up so that your site can be published to a GitHub pages, run (just once):

usethis::use_pkgdown_github_pages()
  • Creates a branch in GitHub that will store the files for your website.
  • Turns on GitHub Pages for your repo
    • Let’s GitHub know where to find the files for the website.
  • Adds the URL for the site to the appropriate files.

Deploy the Site

Big Wrinkle: Can’t deploy on our private GitHub Organization.

  • Have to deploy through a repo tied to your individual account

  • Requires changing a GitHub Action setting!

  • Not a requirement of Project 2!

Hex Sticker


  • A strong tradition in the R community.

  • Fun way to remember and advertise a package.

Hex Sticker


  • A strong tradition in the R community.

  • Fun way to remember and advertise a package.

Hex Sticker


  • A strong tradition in the R community.

  • Fun way to remember and advertise a package.

  • But there are rules…

Next week

  • The final week of classes… 😿

  • Some project work time (Monday)

  • Building your data science profile (Wednesday)