R Package Development Workflow

Hacky Hour by the Statistical Society of Australia Victorian and Tasmanian Branch

Author
Affiliation

Emi Tanaka

Australian National University

Published

October 30, 2023

This note will be available at https://rpubs.com/emitanaka/ssavictas-hackyhour-2023.

Software awards by the Statistical Society of Australia

What are some of your favorite packages?

library(tidyverse)
library(plotly)

What is an R-package?

A container:

  • for a set of R functions,
  • to share data,
  • to share an app,
  • and more, e.g. Rmd templates,

Motivation

What’s the goal?
  • What does your package (and functions) do?
  • Who is it for?
  • Why use it?
  • Where do we find and install it?
  • How do we use it?

💡 Idea

  • Let’s make my own pipable functions to do some matrix operations.

M = \begin{bmatrix}1 & 2 \\3 & 4\end{bmatrix}

  • The user interface would look like:
matrix(1:4, nrow = 2, ncol = 2, byrow = TRUE) %>% 
  entry_add(e(1, 1), 2) %>% 
  entry_subtract(e(2, 2), 3) %>% 
  entry_multiply_by(e(1, 2), 4) %>% 
  entry_divide_by(e(2, 1), 5)

\begin{bmatrix}1 + 2 & 2 \times 4 \\3/5 & 4 - 3\end{bmatrix} = \begin{bmatrix}3 & 8 \\0.6 & 1\end{bmatrix}

Functions

entry_add <- function(M, entry, x) { 
  M[entry[1], entry[2]] <- M[entry[1], entry[2]] +  x
  M
}

entry_subtract <- function(M, entry, x) {
  M[entry[1], entry[2]] <- M[entry[1], entry[2]] -  x
  M
}

entry_multiply_by <- function(M, entry, x) {
  M[entry[1], entry[2]] <- M[entry[1], entry[2]] *  x
  M
}

entry_divide_by <- function(M, entry, x) {
  M[entry[1], entry[2]] <- M[entry[1], entry[2]] /  x
  M
}
  
e <- function(i, j) {
  if(i < 0) stop("Row entry is not a positive integer.")
  if(j < 0) stop("Column entry is not a positive integer.")
  c(i, j)
}

R-package development helper packages

  • devtools
  • usethis
  • roxygen2
  • pkgdown
  • testthat
install.packages(c("devtools", "usethis", "roxygen2", "pkgdown", "testthat"))

Anatomy of an R-package

  • DESCRIPTION file
  • R/ directory for R files that contain your functions
  • NAMESPACE file (you don’t need to manually create this)

Optionally,

  • data/ for binary data available to the user
  • data-raw/ for raw data
  • inst/ for arbitrary additional files that you want include in your package
  • and so on.

DESCRIPTION file

  • Metadata for the package
    • Package name
    • Title and description
    • Authors
    • Dependencies (depends, imports, suggests)
    • Licensing
    • Version number
    • Bug report location, and so on.

Creating an R-package

available::available("matrixops") # check if package is available
usethis::create_package("matrixops")
usethis::use_r("new-r-file") # creating new R file in the R/ directory

# add R functions to the R file

devtools::load_all()

Documenting R functions with roxygen2

  • use #' above a function to write documentation for that function

  • roxygen2 uses @ tags to structure documentation, e.g. 

    • any text after @description is the description
    • any text after @param describes the arguments of the function
    • @export signals that it is an exported function
    • any text after @return describes the return object
  • The full list of Rd tags are found at https://roxygen2.r-lib.org/articles/rd.html

  • Then devtools::document() converts the Rd tags to appropriate sections of .Rd files written in the man/ folder

devtools::document()

Unit testing with testthat

usethis::use_test()
  • This creates a file test-active-filename.R in tests/testthat/ directory
test_that("operations works", {
  M <- matrix(1:4, nrow = 2, ncol = 2, byrow = TRUE)
  expect_equal(entry_add(M, e(1, 1), 2),
               matrix(c(3, 2, 3, 4), 2, 2, byrow = TRUE))
})
devtools::test_active_file()
devtools::test()

Master the keyboard shortcuts

  • Cmd/Ctrl + Shift + L: Load all
  • Cmd/Ctrl + Shift + D: Document
  • Cmd/Ctrl + Shift + T: Test
  • Cmd/Ctrl + Shift + B: Build and Reload
  • plus more… see RStudio IDE > Tools > Keyboard Shortcuts Help

Share and collaborate on your package

usethis::use_git()
usethis::use_github()
  • Install GitHub hosted packages like:
devtools::install_github("user/repo")

Add README

usethis::use_readme_rmd()

Building a website for your package with pkgdown

usethis::use_pkgdown()
# OR, automatic build and deploy with
usethis::use_pkgdown_github_pages()

Whole R package development workflow

available::available("pkgname") # check if package name is available (if planning to publish publicly)
usethis::create_package("pkgname")
usethis::use_git() # set up version control
usethis::use_github() # optional
usethis::use_r("myfile")
# write some functions in a script
usethis::use_data_raw() # if adding data
devtools::load_all() # try it out in the console
usethis::use_package("import-pkgname") # add package to import (or depends or suggests)
usethis::use_package_doc() # add package documentation
usethis::use_pipe() # if you want to add %>% from `magrittr`
usethis::use_vignette("vignette-name") # add vignette
usethis::use_test() # make test file for active R file
# write some test
devtools::test_active_file() # test active file
devtools::test() # test whole package
devtools::build() # build vignettes
devtools::install() # to install package
devtools::check() # to build and check a package 
usethis::use_readme_rmd() # to add a README Rmd file
styler::style_pkg() # optional (commit first, then review changes carefully)
usethis::use_pkgdown_github_pages() # for setting up pkgdown website on github
# `usethis::use_pkgdown()` if not using github pages

Useful reference