
Package development
In the R world, versions of this are attributed to Hadley Wickham:
Any time you copy-and-paste code three times, write a function.
Any time you copy-and-paste a function three times, write a package.
Also known as the rule of three: popularized by Martin Fowler (1999), attributed to Don Roberts
In every project you have at least one other collaborator; future-you. You donβt want future-you to curse past-you
.R extensionlibrary() callssource()DESCRIPTION fileDESCRIPTION, made available in NAMESPACE fileRoxygen comments
BLT βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R β βββ AR1.R β βββ BLT-package.R β βββ data.R β βββ perc_missing.R β βββ perc_missing_tidy.R βββ data β βββ bacon.rda β βββ lettuce.rda β βββ tomatoes.rda
(Does this remind you of anything?)
Figure adapted from Packages, by Hannah Frick

Figure adapted from Packages, by Hannah Frick
BLT βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R β βββ AR1.R β βββ BLT-package.R β βββ data.R β βββ perc_missing.R β βββ perc_missing_tidy.R βββ data β βββ bacon.rda β βββ lettuce.rda β βββ tomatoes.rda
unchanged
changed
changed by you
(Does this remind you of anything?)

BLT
βββ BLT.Rproj
βββ DESCRIPTION
βββ NAMESPACE
βββ R
unchanged
changed
changed by you
Be deliberate about where you create your package
Do not nest inside another RStudio project, R package or git repo.
That includes if you created an RStudio project for these materials!
My practice is to put all packages under my home directory, but your Documents folder is also okay (as long as thatβs not synced by OneDrive or other file-tracking software!).
create_package()What happens when we run create_package()?
R will create a folder called BLT which is a package and an RStudio project
restart R in the new project
create some infrastructure for your package
start the RStudio Build pane
create_package()What happens when we run create_package()?
BLT.Rproj
DESCRIPTION provides metadata about your package.
The R/ directory is where we will put .R files with function definitions.
NAMESPACE declares the functions your package exports and the functions your package imports from other packages.
create_package()What happens when we run create_package()?
.Rbuildignore lists files that we need but that should not be included when building the R package from source.
.gitignore anticipates Git usage and ignores some standard, behind-the-scenes files created by R and RStudio.
usethis::create_package("../BLT") # set the path to suit you
# getwd() will remind you where you currently arecreates a minimal set of files for an installable package
opens a new RStudio project
BLT βββ .gitignore βββ .Rbuildignore βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R
unchanged
changed
changed by you

BLT βββ .gitignore βββ .Rbuildignore βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R
unchanged
changed
changed by you
creates a git repository in your (package) project
makes a first commit on your behalf
restarts RStudio
BLT βββ .gitignore βββ .Rbuildignore βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R
unchanged
changed
changed by you
you will get some messages sort of like this:
β Setting active project to "/Users/amcnamara2/BLT".
β Initialising Git repo.
β Adding ".Rhistory", ".RData", ".httr-oauth", ".DS_Store", and ".quarto" to .gitignore.
βΉ There are 5 uncommitted files:
β’ .gitignore
β’ .Rbuildignore
β’ BLT.Rproj
β’ DESCRIPTION
β’ NAMESPACE
! Is it ok to commit them?
1: No
2: Absolutely not
3: YupThe specific language and order of items will change over time, to make it harder to make mistakes!
Choose the option that means yes!
this is the closest you may see to actual magic:
creates GitHub repository
creates git remote origin, sets to GitHub repository
pushes your master branch to git remote
adds information to DESCRIPTION file
opens your repository page at GitHub

BLT βββ .gitignore βββ .Rbuildignore βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R β βββ AR1.R
unchanged
changed
changed by you
Thereβs a usethis helper for adding .R files!
In real life, I like the idea of documenting, then writing functions:
claim what the function will do, then do it
if your explanation is getting heavy, split into more functions
For today, weβre going to push off documentation.
Start by writing a shell for the function:
BLT βββ .gitignore βββ .Rbuildignore βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R β βββ AR1.R
unchanged
changed
changed by you
Write the function body
BLT βββ .gitignore βββ .Rbuildignore βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R β βββ AR1.R
unchanged
changed
changed by you
βοΈ Use this notation to be explicit about where R looks for the function Arima().
πΎ Save AR1.R.
π€ What happened?
BLT βββ .gitignore βββ .Rbuildignore βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R β βββ AR1.R
unchanged
changed
changed by you
β― checking DESCRIPTION meta-information ... WARNING
Non-standard license specification:
`use_mit_license()`, `use_gpl3_license()` or friends to pick a
license
Standardizable: FALSE
β― checking dependencies in R code ... WARNING
'::' or ':::' import not declared from: βforecastβ
β― checking for future file timestamps ... NOTE
unable to verify current time
1 error β | 2 warnings β | 1 note βOur warnings are because we have used packages that we have not declared officially.
We need to document our package dependencies
Our users, and the package installation machinery, need to know what our package depends on before installing
Levels of dependency
Depends: R (>= 3.4.0). Think critically: downstream effects on packages that depend on your package.usethis again!
use_package(package, type = "Imports")
usethis::use_package()π¬ Use usethis::use_package() to add the forecast package to Imports
Look how your DESCRIPTION file changed!
BLT βββ .gitignore βββ .Rbuildignore βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R β βββ AR1.R
unchanged
changed
changed by you
usethis::use_package()Note: we get reminded to βRefer to functions with forecast::fun()β
That is, we do NOT use library(forecast) to make functions available to our package.
devtools::check()π¬ Run devtools::check() on your package again
R CMD check is the gold standard for checking that an R package is in full working order.
It is a program that is executed in the shell.
However, devtools has the check() function to allow you to run this without leaving your R session.
π¬ Check your package:
On running devtools::check() you may get an error if you are using a networked drive.
This is covered here and can be fixed.
Save a copy of this file:
Save it somewhere other than the BLT directory
Open the file from the BLT project session
Run the whole file
You should now find that check() proceeds normally
Now would be a good time to commit your changes.
Jason Long, CC BY 3.0 <https://creativecommons.org/licenses/by/3.0>, via Wikimedia Commons
Now that weβve document()ed, load_all()ed and check()ed, we can try out our AR1() function in the console.
Now that weβve made one function in our package, letβs try making another. Iβd like this function to find the percent of a time series that is missing.
There are lots of approaches to this, I wrote a wrapper for imputeTS::statsNA().
Some functions you may find useful as you work:
Now would be a good time to commit your changes.
Jason Long, CC BY 3.0 <https://creativecommons.org/licenses/by/3.0>, via Wikimedia Commons
Letβs take this up one more notch by making our function take the dataset and variable name as two separate arguments, so we could use it in a tidy pipeline.
Some functions you may find useful as you work:
perc_missing_tidy <- function(dataset, variable){
var <- substitute(variable)
var_eval <- eval(var, envir = dataset)
na_stats <- imputeTS::statsNA(var_eval, print_only = FALSE)
tibble_perc <- tibble::tibble(perc = readr::parse_number(x = na_stats$percentage_NAs))
return(tibble_perc)
}
This one gets tricky for a couple of reasons. One is non-standard evaluation.
If youβre masking specific tidyverse functions, you can get away with { } and passing arguments along with ....
But if weβre doing more general-purpose programming, we need to leverage non-standard evaluation in R.
| purpose | base | rlang | rlang (quosure) |
|---|---|---|---|
| capture unevaluated expression | quote() | expr() | quo() |
| substitute name for value | substitute() | enexpr() | enquo() |
| evaluate a captured expression | eval() | eval_tidy() | !! |
There are many more NSE functions, but I find these three concepts usually get me pretty far.
Could we change my tidy percent missing function so it uses rlang syntax rather than base R?
I believe enquo() would work in place of enexpr(), as well.
Now would be a good time to commit your changes.
Jason Long, CC BY 3.0 <https://creativecommons.org/licenses/by/3.0>, via Wikimedia Commons
Many packages come with example datasets bundled with them. There are many reasons for this, but the most salient is that if you include your own dataset you know it has exactly the features you need for examples and testing.
The most common location for package data is data/. We recommend that each file in this directory be an .rda file created by save() containing a single R object, with the same name as the file.
Of course, usethis has a helper function for this, usethis::use_data().
Add the bacon data to the package,
β Adding R to Depends field in DESCRIPTION.
β Creating data/.
β Setting LazyData to "true" in DESCRIPTION.
β Saving "bacon" to "data/bacon.rda".
β Document your data (see <https://r-pkgs.org/data.html>).
BLT βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R β βββ AR1.R β βββ BLT-package.R βββ data β βββ bacon.rda
unchanged
changed
changed by you
Letβs also add lettuce and tomato data, for completeness.
There is a lot more detail specific to including data in packages. Sometimes you want to include raw data, sometimes you need data only available to the package and not the user, etc. There is much more detail in the R packages book.
Now would be a good time to commit your changes.
Jason Long, CC BY 3.0 <https://creativecommons.org/licenses/by/3.0>, via Wikimedia Commons

Figure adapted from Packages, by Hannah Frick
BLT βββ BLT.Rproj βββ DESCRIPTION βββ NAMESPACE βββ R β βββ AR1.R β βββ BLT-package.R β βββ data.R β βββ perc_missing.R β βββ perc_missing_tidy.R βββ data β βββ bacon.rda β βββ lettuce.rda β βββ tomatoes.rda
unchanged
changed
changed by you
devtoolsDESCRIPTION, NAMESPACE, .Rbuildignore .gitignoreR/ directory for functionsusethis:use_r()devtools::load_all()devtools::check() to execute R CMD check