Primer on R package development

How to work with an R package

There are a few things to know when creating a package before you jump in. These are not strict rules, but they make your life easier when bug-fixing and make the package much easier to use for the users. Learn about the dos and don’ts in the following.

Dependencies

The one strict rule is Never use library("package") within a package!

Instead, add the packages your are going to use to the DESCRIPTION file and in the function descriptions. This is done by running usethis::use_package("packageName") in the console and adding @import package (OK) or @importFrom package function1 function2 ... (Best). Using the functions in your package is then done with package::function() (e.g., dplyr::mutate()) or omitting the package::.

This way, it is easy to read what functions are from your package, and your package namespace does not get cluttered. Read more in the Namespace section.

It should also be a goal to make your package depend on as few other packages as possible. The user will need to install all packages your package depends on, but then also every package those depends on - that list quickly becomes quite long if you are not careful.

Functions

A package is typically a collections of functions.

These functions are stored in .R files in the R folder. A good starting point is to create an .R file for each function. But, as the package becomes bigger, it often makes sense to combine related functions into bigger files.

You can quickly create a new .R file with usethis::use_r("function_name"). Or do it manually, as you are used to.

Documenting functions

Try running ?mean in the Console.

If you have every wondered how to write a manual like the one that pops up, please click here and read - if not, consider reading it anyway, as you will use it later.

When you have made a function, or have at least defined one, you should describe what it does and how to use it. The manual you write for your function is the function documentation, and it should describe the purpose of the function and how to use it.

You can read extensively about it here, but I will give you the most essential information to get you started.

The R package roxygen2 makes this easy as 1-2-3. It is part of devtools and is already installed. It uses #' comments above the function. @ tags lets you add specific details to the function documentation.

Create an roxygen skeleton by clicking somewhere in your function. Go to the ‘Code’ tab in the top of your window and select ‘Insert Roxygen Skeleton’.

This will look something like this:

#' Title
#'
#' @param foo 
#' @param bar 
#'
#' @return
#' @export
#'
#' @examples
myFunction <- function(foo, bar){
  # Do stuff with foo and bar
  foobar <- (foo * bar) / (foo + bar)
  return(foobar)
}

This allows you to add the most basic descriptions. To begin with, the Title, @param, and @export are the most important, you may remove the other tags for now. A more detailed example is given here. There, you can also read about documenting datasets and other object types - even the package itself.

Namespace

Your package namespace can quickly become very cluttered, if you are not careful.

Therefore, follow these rules:

  • Only @export the functions the users will use. Removing the tag makes the function internal and hides it from your package namespace. It can still be freely used within your package and accessed outside your package with package:::internal_function()
  • Make your code explicit with package::function().
    • This step is not mandatory, but makes reading the code easier.
  • Add your dependencies in the DESCRIPTION file with usethis::use_package("packageName")
  • Only very rarely use the @import tag. Aim to use the @importFrom tags in your function descriptions instead.

You can read more extensively about namespace here.

Testing

Testing is essential

to ensure your package runs smoothly and that no bugs are introduced when you make a seemingly minor change. It is handled with the testthat package, which is also installed with devtools.

I will not go into too much detail here, but know that testing is an important, but often neglected, part of building a package. You can read more about it here.

Every time you run the usethis::use_r() function to create a new script, the function encourages you to create a test alongside the new function. I recommend you follow that advise.

You create a test by running usethis::use_test("function name").

The function creates a new folder tests and creates a test script for the function. The good R package creator writes a few tests for every function.

The exercises will ask you to make a simple test for every function, introducing you to the concept.

The Package Workflow

When creating a package, it is important to test your work along the way.

You can do that in many ways, but I recommend the following workflow:

  1. Write a function / make a change
    • If it is a new function, document it
  2. Save your files: rstudioapi::documentSaveAll()
  3. Create package documentation: devtools::document()
  • If at this point, you get a warning that NAMESPACE already exists, delete it and try again.
  1. Load package: devtools::load_all()
  2. Your package is now loaded, and you can test that it works as intended.

Optionally, you can save the three lines of code in dev/load.R and run the lines with source("dev/load.R"). If you do, add the dev folder to the .Rbuildignore file.