::create_package("~/mytools") usethis
16.1 R Packages
Most R users are familiar with loading and utilizing packages in their work. And they know how rich CRAN is in providing for many conceivable needs. Most people have never created a package for their own work, and most think the process is too complicated. Really it’s pretty straighforward and super useful in your personal work. Creating packages serves two main use cases:
- Mechanism to redistribute reusable code (even if just for yourself)
- Mechanism to reproducibly document analysis and models and their results
Even if you don’t plan on writing a package with such broad appeal such as, say, ggplot2
or dplyr
, you still might consider creating a package to contain:
- Useful utility functions you write (i.e. a Personal Package). Having a place to put these functions makes it much easier to find and use them later.
- A set of shared routines for your lab or research group, making it easier to remain consistent within your team and also to save time.
- The analysis accompanying a thesis or manuscript, making it all that much easier for others to reproduce your results.
16.1.1 Create a Basic Package
To create a package we’re going to use the following packages:
devtools
: Provides R functions that make package development easier by expediting common development tasks.usethis
: Commonly referred to as a “workflow package” and provides functions that automate common tasks in project setup and development for both R packages and non-package projects.roxygen2
: Provides a structure for describing your functions in the scripts you’re creating them in. It will additionally process the source code and the documentation within it to automatically create the necessary files for the documentation to appear in your R Package.
Thanks to the great usethis package, it only takes one function call to create the skeleton of an R package using create_package()
. Which eliminates pretty much all reasons for procrastination. To create a package called mytools
, all you do is:
✔ Creating '/home/dolinh/mytools/'
✔ Setting active project to '/home/dolinh/mytools'
✔ Creating 'R/'
✔ Writing 'DESCRIPTION'
Package: mytools
Title: What the Package Does (One Line, Title Case)
Version: 0.0.0.9000
Authors@R (parsed):
* First Last <first.last@example.com> [aut, cre] (YOUR-ORCID-ID)
Description: What the package does (one paragraph).
License: `use_mit_license()`, `use_gpl3_license()` or friends to
pick a license
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
✔ Writing 'NAMESPACE'
✔ Writing 'mytools.Rproj'
✔ Adding '^mytools\\.Rproj$' to '.Rbuildignore'
✔ Adding '.Rproj.user' to '.gitignore'
✔ Adding '^\\.Rproj\\.user$' to '.Rbuildignore'
✔ Opening '/home/dolinh/mytools/' in new RStudio session
✔ Setting active project to '<no active project>'
16.1.2 Add a License
Information about choosing a LICENSE
is provided in the R Package (2e) book Chapter 12: Licensing.
The DESCRIPTION
file expects the license to be chose from a predefined list, but you can use its various utility methods for setting a specific license file, such as the MIT
license or the Apache 2
license:
::use_apache_license() usethis
✔ Setting License field in DESCRIPTION to 'Apache License (>= 2.0)'
✔ Writing 'LICENSE.md'
✔ Adding '^LICENSE\\.md$' to '.Rbuildignore'
Once your license has been chosen, and you’ve edited your DESCRIPTION
file with your contact information, a title, and a description, it will look like this:
16.1.3 Add Code
The skeleton package created contains a directory R
which should contain your source files. Add your functions and classes in files to this directory, attempting to choose names that don’t conflict with existing packages. For example, you might add a file custom_theme
that contains a function custom_theme()
that you might want to reuse. The usethis::use_r()
function will help set up you files in the right places. For example, running:
::use_r("custom_theme") usethis
✔ Setting active project to '/home/dolinh/mytools'
• Modify 'R/custom_theme.R'
• Call `use_test()` to create a matching test file
creates the file R/custom_theme
and stores it in the R
directory, which you can then modify as needed:
<- function(base_size = 9) {
custom_theme ::theme(
ggplot2axis.ticks = ggplot2::element_blank(),
text = ggplot2::element_text(family = 'Helvetica',
color = 'gray30',
size = base_size),
plot.title = ggplot2::element_text(size = ggplot2::rel(1.25),
hjust = 0.5,
face = 'bold'),
panel.background = ggplot2::element_blank(),
legend.position = 'right',
panel.border = ggplot2::element_blank(),
panel.grid.minor = ggplot2::element_blank(),
panel.grid.major = ggplot2::element_line(colour = 'grey90',
linewidth = .25),
legend.key = ggplot2::element_rect(colour = NA,
fill = NA),
axis.line = ggplot2::element_blank()
) }
16.1.4 Add Dependencies
If your R code depends on functions from another package, you must declare it. In the Imports
section in the DESCRIPTION
file, list all the packages your functions depend upon.
In our custom_theme()
function, we depend on the ggplot2
package, and so we need to list it as a dependency.
Once again, usethis
provides a handy helper method:
::use_package("ggplot2") usethis
✔ Adding 'ggplot2' to Imports field in DESCRIPTION
• Refer to functions with `ggplot2::fun()`
Take a look at the DESCRIPTION
file again, and you’ll see the Imports section has been added, with ggplot2
underneath.
16.1.5 Add Documentation
Documentation is crucial to add to each of your functions. In the Functions Lesson, we did this using the roxygen2
package and that same package and approach can be used for packages.
The roxygen2
approach allows us to add comments in the source code, where are then converted into Help pages that we can access by typing ?function_name
in the Console.
Let’s add documentation for the custom_theme()
function.
#' My custom ggplot theme
#'
#' @param base_size Numeric value of font size of all text elements in plot
#'
#' @return A theme used for ggplot point or line plots
#' @export
#'
#' @examples
#' library(ggplot2)
#'
#' ggplot(data = mtcars, aes(x = mpg, y = disp)) +
#' geom_point() +
#' custom_theme(base_size = 30)
<- function(base_size = 9) {
custom_theme ::theme(
ggplot2axis.ticks = ggplot2::element_blank(),
text = ggplot2::element_text(family = 'Helvetica',
color = 'gray30',
size = base_size),
plot.title = ggplot2::element_text(size = ggplot2::rel(1.25),
hjust = 0.5,
face = 'bold'),
panel.background = ggplot2::element_blank(),
legend.position = 'right',
panel.border = ggplot2::element_blank(),
panel.grid.minor = ggplot2::element_blank(),
panel.grid.major = ggplot2::element_line(colour = 'grey90',
linewidth = .25),
legend.key = ggplot2::element_rect(colour = NA,
fill = NA),
axis.line = ggplot2::element_blank()
) }
Once your files are documented, you can then process the documentation using devtools::document()
to generate the appropriate .Rd
files that your package needs. The .Rd
files will appear in the man/
directory, which is automatically created by devtools::document()
.
::document() devtools
ℹ Updating mytools documentation
ℹ Loading mytools
Writing custom_theme.Rd
We now have a package that we can check()
and install()
and release()
. These functions come from the devtools
package, but first let’s do some testing.
16.1.6 Testing
You can test your code using the testthat
package’s testing framework. The ussethis::use_testthat()
function will set up your package for testing, and then you can use the use_test()
function to setup individual test files. For example, in the Functions Lesson we created some tests for our fahr_to_celsius
functions but ran them line by line in the console.
First, lets add that function to our package. Run the use_r
function in the console:
::use_r("fahr_to_celsius") usethis
Then copy the function and documentation into the R script that opens and save the file.
#' Convert temperature values from Fahrenheit to Celsius
#'
#' @param fahr Numeric or numeric vector in degrees Fahrenheit
#'
#' @return Numeric or numeric vector in degrees Celsius
#' @export
#'
#' @examples
#' fahr_to_celsius(32)
#' fahr_to_celsius(c(32, 212, 72))
<- function(fahr) {
fahr_to_celsius <- (fahr-32)*5/9
celsius return(celsius)
}
Now, set up your package for testing:
::use_testthat() usethis
✔ Setting active project to '/home/dolinh/mytools'
✔ Adding 'testthat' to Suggests field in DESCRIPTION
✔ Setting Config/testthat/edition field in DESCRIPTION to '3'
✔ Creating 'tests/testthat/'
✔ Writing 'tests/testthat.R'
• Call `use_test()` to initialize a basic test file and open it for editing.
Then write a test for fahr_to_celsius
:
::use_test("fahr_to_celsius") usethis
✔ Writing 'tests/testthat/test-fahr_to_celsius.R'
• Modify 'tests/testthat/test-fahr_to_celsius.R'
You can now add tests to the test-fahr_to_celsius.R
, and you can run all of the tests using devtools::test()
. For example, if you add a test to the test-fahr_to_celsius.R
file:
test_that("fahr_to_celsius works", {
expect_equal(fahr_to_celsius(32), 0)
expect_equal(fahr_to_celsius(212), 100)
})
Then you can run the tests to be sure all of your functions are working using devtools::test()
:
::test() devtools
ℹ Testing mytools
✔ | F W S OK | Context
✔ | 2 | fahr_to_celsius [0.2s]
══ Results ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Duration: 0.4 s
[ FAIL 0 | WARN 0 | SKIP 0 | PASS 2 ]
Yay, all tests passed!
16.1.7 Checking and Installing
Now that you’ve completed testing your package, you can check it for consistency and completeness using devtools::check()
.
::check() devtools
══ Documenting ════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
ℹ Updating mytools documentation
ℹ Loading mytools
══ Building ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Setting env vars:
• CFLAGS : -Wall -pedantic -fdiagnostics-color=always
• CXXFLAGS : -Wall -pedantic -fdiagnostics-color=always
• CXX11FLAGS: -Wall -pedantic -fdiagnostics-color=always
• CXX14FLAGS: -Wall -pedantic -fdiagnostics-color=always
• CXX17FLAGS: -Wall -pedantic -fdiagnostics-color=always
• CXX20FLAGS: -Wall -pedantic -fdiagnostics-color=always
── R CMD build ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
✔ checking for file ‘/home/dolinh/mytools/DESCRIPTION’ (610ms)
─ preparing ‘mytools’:
✔ checking DESCRIPTION meta-information (338ms)
─ checking for LF line-endings in source and make files and shell scripts
─ checking for empty or unneeded directories
─ building ‘mytools_0.0.0.9000.tar.gz’
══ Checking ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Setting env vars:
• _R_CHECK_CRAN_INCOMING_REMOTE_ : FALSE
• _R_CHECK_CRAN_INCOMING_ : FALSE
• _R_CHECK_FORCE_SUGGESTS_ : FALSE
• _R_CHECK_PACKAGES_USED_IGNORE_UNUSED_IMPORTS_: FALSE
• NOT_CRAN : true
── R CMD check ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
─ using log directory ‘/tmp/Rtmp1UgqFD/file6d79323df6fae/mytools.Rcheck’ (649ms)
─ using R version 4.2.2 (2022-10-31)
─ using platform: x86_64-pc-linux-gnu (64-bit)
─ using session charset: UTF-8
─ using options ‘--no-manual --as-cran’
✔ checking for file ‘mytools/DESCRIPTION’
─ this is package ‘mytools’ version ‘0.0.0.9000’
─ package encoding: UTF-8
✔ checking package namespace information
✔ checking package dependencies (2.1s)
✔ checking if this is a source package
✔ checking if there is a namespace
✔ checking for executable files
✔ checking for hidden files and directories
✔ checking for portable file names
✔ checking for sufficient/correct file permissions
✔ checking serialization versions
✔ checking whether package ‘mytools’ can be installed (3.2s)
✔ checking installed package size
✔ checking package directory
✔ checking for future file timestamps (412ms)
✔ checking DESCRIPTION meta-information (584ms)
✔ checking top-level files ...
✔ checking for left-over files
✔ checking index information
✔ checking package subdirectories ...
✔ checking R files for non-ASCII characters ...
✔ checking R files for syntax errors ...
✔ checking whether the package can be loaded (481ms)
✔ checking whether the package can be loaded with stated dependencies ...
✔ checking whether the package can be unloaded cleanly ...
✔ checking whether the namespace can be loaded with stated dependencies ...
✔ checking whether the namespace can be unloaded cleanly (450ms)
✔ checking loading without being on the library search path (522ms)
✔ checking dependencies in R code (1.2s)
✔ checking S3 generic/method consistency (1s)
✔ checking replacement functions ...
✔ checking foreign function calls ...
✔ checking R code for possible problems (5.2s)
✔ checking Rd files (449ms)
✔ checking Rd metadata ...
✔ checking Rd line widths ...
✔ checking Rd cross-references ...
✔ checking for missing documentation entries ...
✔ checking for code/documentation mismatches (885ms)
✔ checking Rd \usage sections (1.3s)
✔ checking Rd contents ...
✔ checking for unstated dependencies in examples ...
✔ checking examples (2.7s)
✔ checking for unstated dependencies in ‘tests’ ...
─ checking tests (418ms)
✔ Running ‘testthat.R’ (1.4s)
✔ checking for non-standard things in the check directory
✔ checking for detritus in the temp directory
── R CMD check results ──────────────────────────────────────────────────────────────────────────────────── mytools 0.0.0.9000 ────
Duration: 27.3s
0 errors ✔ | 0 warnings ✔ | 0 notes ✔
Then you can install it locally using devtools::install()
, which needs to be run from the parent directory of your module
::install() devtools
── R CMD build ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
✔ checking for file ‘/home/dolinh/mytools/DESCRIPTION’ (541ms)
─ preparing ‘mytools’:
✔ checking DESCRIPTION meta-information ...
─ checking for LF line-endings in source and make files and shell scripts
─ checking for empty or unneeded directories
─ building ‘mytools_0.0.0.9000.tar.gz’
Running /opt/R/4.2.2/lib/R/bin/R CMD INSTALL /tmp/Rtmp1UgqFD/mytools_0.0.0.9000.tar.gz --install-tests
* installing to library ‘/home/dolinh/R/x86_64-pc-linux-gnu-library/4.2’
* installing *source* package ‘mytools’ ...
** using staged installation
** R
** tests
** byte-compile and prepare package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded from temporary location
** testing if installed package can be loaded from final location
** testing if installed package keeps a record of temporary installation path
* DONE (mytools)
After installing, your package is now available for use in your local environment, yay!
Remember when we ran usethis::create_package()
and after we ran it we saw the Build Tab added to the Environment pane?
In the Build Tab, each of the buttons correspond with one of the devtools
functions we ran, meaning:
- Test button is equivalent to running
devtools::test()
in the Console - Check button is equivalent to running
devtools::check()
in the Console - Install button is equivalent to running
devtools::install()
in the Console
16.1.9 Exercise: Add More Functions
Add additional temperature conversion functions to the mytools
package and:
- Add full documentation for each function
- Write tests to ensure the functions work properly
- Rebuild the package using
document()
,check()
, andinstall()
Version information is located in the DESCRIPTION
file and when you first create a package the version is 0.0.0.9000.
This version number follows the format major.minor.patch.dev. The different parts of the version represent different things:
- Major: A significant change to the package that would be expected to break users code. This is updated very rarely when the package has been redesigned in some way.
- Minor: A minor version update means that new functionality has been added to the package. It might be new functions to improvements to existing functions that are compatible with most existing code.
- Patch: Patch updates are bug fixes. They solve existing issues but don’t do anything new.
- Dev: Dev versions are used during development and this part is missing from release versions. For example you might use a dev version when you give someone a beta version to test. A package with a dev version can be expected to change rapidly or have undiscovered issues.
After you’ve made some changes to a package, but before you install run the code:
::use_version() usethis
Current version is 0.0.0.9000.
What should the new version be? (0 to exit)
1: major --> 1.0.0
2: minor --> 0.1.0
3: patch --> 0.0.1
4: dev --> 0.0.0.9001
Since we’re adding new functions, we can consider this a minor change and can select option 2.
Selection: 2
✔ Setting Version field in DESCRIPTION to '0.1.0'
Source: COMBINE’s R package workshop, Ch 9: Versioning
16.1.10 Additional Resources
- Hadley Wickham and Jenny Bryan’s awesome book: R Packages
- ROpenSci Blog Post: How to create your personal CRAN-like repository on R-universe
- Karl Broman’s: R package primer: a minimal tutorial on writing R packages
- Thomas Westlake’s Short Tutorial: Writing an R package from scratch (his post is an updated version of Hilary Parker’s blog post)