TITLE: Writing R package documentation
DATE: 2020-05-30
AUTHOR: John L. Godlee
====================================================================


There's already a tonne of stuff on how to write R packages, see
here, here, here and here. Part of the reason for the breadth of
articles is that there are many different workflows for how to
write them. Here I'm only going to share my thoughts on writing
package documentation, because that's the area where I didn't find
one complete resource that answered all of my questions and
provided a workflow I liked, when I was writing my first serious
package.

 [here]:
https://tinyheero.github.io/jekyll/update/2015/07/26/making-your-fir
st-R-package.html
 [1]: https://r-pkgs.org/
 [2]:
https://hilaryparker.com/2014/04/29/writing-an-r-package-from-scratc
h/
 [3]: https://kbroman.org/pkg_primer/

To briefly explain the basic structure of my package, I took advice
from Hadley and kept functions in my package inside thematic files,
like biomass.R and taxonomy.R, with each of these files holding
multiple functions. It's somewhere between keeping all functions in
one file and keeping each function in its own file. I think both of
these extremes ignore the natural sorting which can come from
keeping a tidy directory structure. I found it more intuitive to
find a particular function based on its theme when I used these
thematic files.

 [advice from Hadley]: https://r-pkgs.org/r.html

I used roxygen2 to store the documentation for each package
function alongside the code for that function in my R/*.R files.
For example, my convenience function for concatenating genus and
species names to one string (picked as an example purely because
it's short):

 [roxygen2]:
https://cran.r-project.org/web/packages/roxygen2/vignettes/roxygen2.
html

   #' Combine genus and species character vectors to a species name
   #'
   #' @param x vector of genus names
   #' @param y corresponding vector of species names
   #'
   #' @return vector of genus and species
   #'
   #' @export
   #'
   combineSpecies <- function(x, y) {
     vec <- speciesFormat(data.frame(genus = x, species = y))
     vec <- paste(vec[[1]], vec[[2]])
     return(vec)
   }

This function has the @export tag, meaning that when my package is
loaded with library() by a user, this function can be accessed
without prefixing with the package name. Functions with @export are
automatically written into the package manual when I compile it
with devtools::document(). I have a tonne of functions in this
package that are not useful to the average user however, mostly
functions which check the contents of a particular column in the
standardised datasets used by this package. These functions are
purposely not written into the package manual with the @noRd tag,
which stops a .Rd file being written for that function and
therefore keeps it out of the manual. These functions also have the
@keywords internal, which means that the function can only be
accessed by the user with package:::function(), but can still be
accessed by other functions in the package with function(). This
means that the user can still use the function if they need to, but
are discouraged from doing so, normally because that function is
better implemented in a higher-level wrapper function which
provides checks or preprocessing. As an example, my function
genus() checked whether genus names are formatted sensibly, but is
only meant to be called from within colValCheck(), which wraps a
bunch of column checking functions in a neater interface:

   #' Check validity of stem genus column
   #'
   #' @param x vector of stem genera
   #'
   #' @return vector of class "character"
   #' @keywords internal
   #' @noRd
   #'
   genus <- function(x, ...) {
     x <- fillNA(x)
     x <- coerce_catch(x, as.character, ...)
     na_catch(x, warn = TRUE, ...)
     if (any(!grepl("^[[:alpha:]]+$", x[!is.na(x)]))) {
       stop("Non-letter characters found in genus")
     }
     else if (any(!grepl("^[A-Z]", x[!is.na(x)]))) {
       stop("Genera must start with a capital letter [A-Z]")
     }
     else if (any(grepl("[A-Z]", substring(x[!is.na(x)], 2)))) {
       stop("Genera must not have multiple capital letters")
     }
     structure(x, class = "character")
   }

It's nice to have a package level description at the start of a
package manual before launching into the technicalities of the
function definitions. To do this, I added a roxygen entry like the
one below (cut for brevity), which has the object NULL and uses the
key tags: @docType package and @name packagename-package.

   #' silvR: Clean and analyse SEOSAW style data
   #'
   #' The \code{silvr} package facilitates three important
activities:
   #' \itemize{
   #'   \item{Checking and cleaning new data for the SEOSAW
dataset}
   #'   \item{Manipulating the SEOSAW dataset to provide
informative summary data}
   #'   \item{Analysing the SEOSAW dataset}
   #' }
   #'
   #' @details The functions in the \code{silvr} package form a
workflow for
   #'     checking data prior to ingestion into the SEOSAW
database. The package
   #'     deals with 4 principle data objects:
   #'
   #'     ...
   #'
   #'     The package contains various functions for quickly
creating useful
   #'     summary data objects such as abundance matrices and
maps, ...
   #'
   #' @author The \code{silvr} package is a collaborative effort,
bringing code
   #'     together from various SEOSAW members ...
   #'
   #' @section Key top-level functions:
   #' For ingesting new data into the SEOSAW database, it is
recommended to run
   #' these top level functions in this order to catch errors.
   #'
   #' \itemize{
   #'   \item{\code{plotTableGen()} - Checks for value and column
errors and
   #'   return a clean SEOSAW style plot metadata dataframe.}
   #'   \item{\code{stemTableGen()} - Checks for value and column
errors and
   #'   return a clean SEOSAW style stem data dataframe.}
   #'   \item{...}
   #' }
   #'
   #' @docType package
   #' @name silvr-package
   NULL

This longer description takes advantage of @section and @details
for structuring blocks of text in the 'roxygen2 block.

The manual frontmatter comes mostly from the DESCRIPTION file. Most
important are the package dependencies, which are also specified by
minimum version number. Annoyingly, these package versions don't
get populated directly from the package dependencies in the
roxygen2 function blocks. Instead they have to be written manually
into the DESCRIPTION.

Roxygen2 autopopulates NAMESPACE from the @import and @importFrom
tags in the function blocks. I tend to use @importFrom vegan
diversity rather than @import vegan where I can, to avoid potential
conflicts in function names if I start loading lots of packages,
but I don't think there is any hard rule on this.

To write a vignette, I used RMarkdown rather than Sweave. It seems
to be the modern approach to vignette writing and is much more
straightforward when including figures and code chunks in the
document. To set this up I created a directory in the package root
call vignettes/ and created a packagename.Rmd file in there. Then
in the YAML frontmatter I included this:

   output: rmarkdown::html_vignette
   vignette: >
     %\VignetteIndexEntry{Cleaning and analysing SEOSAW data}
     %\VignetteEngine{knitr::rmarkdown}
     \usepackage[utf8]{inputenc}

Then in my DESCRIPTION I added:

   Suggests:
       knitr (>= 1.28),
       rmarkdown (>= 2.1)
   VignetteBuilder: knitr

Which ensures the tools for building the vignette are present. I
can then build the vignette with: devtools::build_vignettes().

Finally, a short R script I have sitting above my package directory
contains this code to build the package:

   setwd("silvr")
   devtools::document()  # Generate .Rd files
   devtools::build_manual()  # Generate .pdf manual
   devtools::build_vignettes()  # Generate .html vignette
   setwd("..")
   devtools::install("silvr")  # Install the package
   library(silvr)  # Load the package