Writing R Packages

Statistical Computing, 36-350

Friday August 2, 2019

Logistics

Last time: Version control

Why make an R Package?

“… from user to programmer to contributor, in the gradual progress that R encourages.”

~ John Chambers (Software for Data Analysis: Programming with R)

R package: Goals

What is an R Package?

library(tidyverse)
search()
##  [1] ".GlobalEnv"        "package:forcats"   "package:stringr"  
##  [4] "package:dplyr"     "package:purrr"     "package:readr"    
##  [7] "package:tidyr"     "package:tibble"    "package:ggplot2"  
## [10] "package:tidyverse" "package:stats"     "package:graphics" 
## [13] "package:grDevices" "package:utils"     "package:datasets" 
## [16] "package:methods"   "Autoloads"         "package:base"

The 5 stages of an R package

R provides tools (R CMD INSTALL, R CMD BUILD, R CMD CHECK, etc…) to move to different stages

Packages to help with developing an R package

library(devtools)
library(usethis)
# documentation
library(roxygen2) 
# tests
library(testthat)

devtools and usethis allows us to get our package up an running

“The goal of devtools is to make package development as painless as possible”

~ Hadley Wickham (R Packages)

  install.packages("devtools")
  library("devtools")

Devtools serves as a wrapper to many other useful packages

usethis focuses on automating repetitive tasks in building packages

  install.packages("usethis")
  library("usethis")

“usethis is a workflow package: it automates repetitive tasks that arise during project setup and development, both for R packages and non-package projects.”

~ usethis package description

Step 0: Setting up your github (“Github First”)

Just like in the version control we are going to us the “Github First” Approach. Here are the reminder of the steps:

In GitHub, do the following:

…then RStudio

In RStudio, do the following:

Step 1: Create the minimal source package

An R (source) package is just files in a directory, formatted in a specific way.

To create the bare bones R package, just type:

usethis::create_package("tartan") 

This should give us:

  1. Directory R/
  2. Directory man/
  3. NAMESPACE file
  4. DESCRIPTION file
  5. tartan.Rproj file (if using RStudio)

and anything else you included from intializing your git repo (.gitignore, LICENSE, etc)

The description file contains package meta-data

usethis creates the “bare minimum” description file

This is fine for now, but becomes more important when you want to release your package

Step 2: Writing a function

All of your R code goes into the R/ directory. Generally, this code is made up of functions

For example, lets create the file R/welcome.R and add the function:

usethis::use_r("welcome") # opens up file (creates if need be)
call_scotty_demo <- function(your_name) {
  if (nchar(your_name) > 30){
    warning("your name is beyond 30 characters, and has been truncated")
    your_name <- substr(your_name, 1, 30)
  }
  
  name_length <- nchar(your_name)
  
  # Hiiiii
  cat(paste("\n",
    paste0(c("", rep("-", name_length + 8), "\n"), collapse = ""),
    paste0(c("| Hi ", your_name, "! | \n"), collapse=""),
    paste0(c("|   ", rep("-", name_length + 4), "\n"), collapse = ""),
    paste0(c("| /\n")))
  )

  # from Scotty.
  cat(paste0(
      " |/  |\\_/|          \n",
      " |   |q p|   /}      \n",
      " |   ( 0 )\"\"\"\\   \n",
      "  \\  |\"-\"`    |    \n",
      "     || /=\\\\  |     \n",
      "     \"'\"  '\"\"\"'"))
  
}

Now, we can load the package into memory:

devtools::load_all()

More on devtools::load_all()

devtools::load_all() loads your source package into memory

This is important because in developing a package, you often need to re-install the package over and over

Contrasting with the library() function, which loads (then attaches) already installed packages

Step 2: Documentation our functions

Devtools connects with roxygen2 for more convinient documentation

The easiest way to document your R code is roxygen2 package (😱 which we already learned how to use - how useful).

Primarily, this allows you to combine code and documentation into a single file, and handles the .Rd formatting (and NAMESPACE) for you

install.packages("roxygen2")
library(roxygen2)

With roxygen2, we write documentation on top of the function

#' Meet Scotty, the Scottie Dog
#'
#' @param your_name string of your name (max length 30 characters)
#'
#' @return NULL. Though scotty appears on your screen and says Hi to you.
#' @export
#'
#' @examples
#' call_scotty_demo("Andrew Carnegie")
call_scotty_demo <- function(your_name) {
  
  if (nchar(your_name) > 30){
    warning("your name is beyond 30 characters, and has been truncated")
    your_name <- substr(your_name, 1, 30)
  }
  
  name_length <- nchar(your_name)
  
  # Hiiiii
  cat(paste("\n",
    paste0(c("", rep("-", name_length + 8), "\n"), collapse = ""),
    paste0(c("| Hi ", your_name, "! | \n"), collapse=""),
    paste0(c("|   ", rep("-", name_length + 4), "\n"), collapse = ""),
    paste0(c("| /\n")))
  )

  # from Scotty.
  cat(paste0(
      " |/  |\\_/|          \n",
      " |   |q p|   /}      \n",
      " |   ( 0 )\"\"\"\\   \n",
      "  \\  |\"-\"`    |    \n",
      "     || /=\\\\  |     \n",
      "     \"'\"  '\"\"\"'"))
  
}

Creating Documentation

As a standard workflow, we can use:

devtools::document()

Let’s see what this looks like:

% Generated by roxygen2: do not edit by hand
% Please edit documentation in R/welcome.R
\name{call_scotty_demo}
\alias{call_scotty_demo}
\title{Meet Scotty, the Scottie Dog}
\usage{
call_scotty_demo(your_name)
}
\arguments{
\item{your_name}{string of your name (max length 30 characters)}
}
\value{
\code{NULL}. Though scotty appears on your screen and says Hi to you.
}
\description{
Meet Scotty, the Scottie Dog
}
\examples{
call_scotty_demo("Andrew Carnegie")
}

We can now view our documentation in the standard R format

devtools::load_all()
?call_scotty_demo

Step 4: Testing

Testing is critical to making sure your packages do what you expect.

The easiest way to get started with testing is the testthat package, which also integrates with devtools

install.packages("testthat")
usethis::use_testthat()

This will set up the tests/testthat directory where we can store tests

How does testthat work?

Test-that works hierarchically:

  1. Expectation: Fundamental unit of testing. They describe what result is expected of your various computations. We can use these to make sure our functions are giving the expected output.
  library(testthat)
  expect_equal(1, 1)
  expect_equal(1, 2)
## Error: 1 not equal to 2.
## 1/1 mismatches
## [1] 1 - 2 == -1
  1. Test: Group of Expectations
test_that(paste("calling scotty works well (returns null",
                 "and provides warning nchar > 30)"), {
  expect_equal(call_scotty_demo("Oski Bear"), NULL)
  expect_warning(call_scotty_demo("Oski Bear, your a wonderful, but slightly creepy Berkeley Bear"))
  expect_equal(1, 2)
})
## 
##  -----------------
##  | Hi Oski Bear! | 
##  |   -------------
##  | /
##  |/  |\_/|          
##  |   |q p|   /}      
##  |   ( 0 )"""\   
##   \  |"-"`    |    
##      || /=\\  |     
##      "'"  '"""'
##  --------------------------------------
##  | Hi Oski Bear, your a wonderful, b! | 
##  |   ----------------------------------
##  | /
##  |/  |\_/|          
##  |   |q p|   /}      
##  |   ( 0 )"""\   
##   \  |"-"`    |    
##      || /=\\  |     
##      "'"  '"""'
## Error: Test failed: 'calling scotty works well (returns null and provides warning nchar > 30)'
## * 1 not equal to 2.
## 1/1 mismatches
## [1] 1 - 2 == -1
  1. File: File which contains tests. Must start with test

Testing our function

Next, let’s create a file tests/testthat/test-welcome.R

usethis::use_test("welcome")
✔ Setting active project to './tartan'
✔ Adding 'testthat' to Suggests field in DESCRIPTION
✔ Creating 'tests/testthat/'
✔ Writing 'tests/testthat.R'
✔ Writing 'tests/testthat/test-welcome.R'
● Modify 'tests/testthat/test-welcome.R'

What we put in the test-welcome.R file:

context("test welcome from scotty")

test_that(paste("calling scotty works well (returns null",
                 "and provides warning nchar > 30)"), {
  expect_equal(call_scotty_demo("Oski Bear"), NULL)
  expect_warning(call_scotty_demo("Oski Bear, your a wonderful, but slightly creepy Berkeley Bear"))
  expect_equal(1, 2)
})

Now run:

devtools::test()

Example output from testthat:

Step 5: Syncronizing with Github

This allows users to install your package without being hosted on CRAN.

For example, you could install the R package I just made using:

devtools::install_github(repo = "benjaminleroy/tartan",
                         force = F) 
# so making the sides does do it a ton of times
library(tartan)
#?call_scotty_demo
call_scotty_demo("Big Bird")
## 
##  ----------------
##  | Hi Big Bird! | 
##  |   ------------
##  | /
##  |/  |\_/|          
##  |   |q p|   /}      
##  |   ( 0 )"""\   
##   \  |"-"`    |    
##      || /=\\  |     
##      "'"  '"""'

Remarkably, the package is available to everyone with an internet connection

Instructions for syncronizing with Github

Remember, now that we’ve done the basics we can use Rstudio’s built in git and connect to Github to commit all changes and push to Github.

Now check out the Github page, all of the code is there!

Congratulations! You now have your own personal R Package

There are many more great features of to look into, including:

Summary

Final comments:

You don’t have to do use the “Github first”, you can find other resources online about how to connect a package to github after you make it (using usethis even).

Good Resources: