15  Writing Functions and Packages

Learning Objectives

  • Explain the importance of using and developing functions
  • Create custom functions using R code
  • Document functions to improve understanding and code communication

15.1 R Functions

A set of statements or expressions of code that are organized together to perform a specific task.

The statements or expressions of code within the function accept accept user input(s), does something with it, and returns a useful output.

Syntax: result_value <- function_name(argument1 = value1, argument2 = value2, ...)

Many people write R code as a single, continuous stream of commands, often drawn from the R Console itself and simply pasted into a script. While any script brings benefits over non-scripted solutions, there are advantages to breaking code into small, reusable modules. This is the role of a function in R. In this lesson, we will review the advantages of coding with functions, practice by creating some functions and show how to call them, and then do some exercises to build other simple functions.

15.1.1 Why Functions?

DRY: Don’t Repeat Yourself

“You should consider writing a function whenever you’ve copied and pasted a block of code more than twice (i.e. you now have three copies of the same code).”

Chapter 19 Functions in R for Data Science (Grolemund & Wickham)

By creating small functions that only complete one logical task and do it well, we quickly gain:

  • Improved understanding
  • Reuse via decomposing tasks into bite-sized chunks
  • Improved error testing
Naming Functions

The name of a function is important. Ideally, function names should be short, but still clearly captures what the function does.

Best Practices from Chapter 19 Functions in R for Data Science:

  • Function names should be verbs and arguments should be nouns (there are exceptions).
  • Use the snake_case naming convention for functions that are multiple words.
  • For a “family” of functions, use a common prefix to indicate that they are connected.

15.1.2 Exercise: Temperature Conversion

Imagine you have a bunch of data measured in Fahrenheit and you want to convert that for analytical purposes to Celsius. You might have an R script that does this for you.

airtemps <- c(212, 30.3, 78, 32)
celsius1 <- (airtemps[1] - 32) * 5/9
celsius2 <- (airtemps[2] - 32) * 5/9
celsius3 <- (airtemps[3] - 32) * 5/9

Note the duplicated code, where the same formula is repeated three times. This code would be both more compact and more reliable if we didn’t repeat ourselves.

Create a Function that Converts Fahrenheit to Celsius

Functions in R are a mechanism to process some input and return a value. Similarly to other variables, functions can be assigned to a variable so that they can be used throughout code by reference. To create a function in R, you use the function function (so meta!) and assign its result to a variable. Let’s create a function that calculates Celsius temperature outputs from Fahrenheit temperature inputs.

fahr_to_celsius <- function(fahr) {
  celsius <- (fahr - 32) * 5/9
  return(celsius)
}

By running this code, we have created a function and stored it in R’s global environment. The fahr argument to the function function indicates that the function we are creating takes a single parameter (the temperature in Fahrenheit), and the return statement indicates that the function should return the value in the celsius variable that was calculated inside the function. Let’s use it, and check if we got the same value as before:

celsius4 <- fahr_to_celsius(airtemps[1])
celsius4
[1] 100
celsius1 == celsius4
[1] TRUE

Excellent. So now we have a conversion function we can use. Note that, because most operations in R can take multiple types as inputs, we can also pass the original vector of airtemps, and calculate all of the results at once:

celsius <- fahr_to_celsius(airtemps)
celsius
[1] 100.0000000  -0.9444444  25.5555556   0.0000000

This takes a vector of temperatures in Fahrenheit, and returns a vector of temperatures in Celsius.

Your Turn: Create a Function that Converts Celsius to Fahrenheit

Exercise

Create a function named celsius_to_fahr that does the reverse, it takes temperature data in Celsius as input, and returns the data converted to Fahrenheit.

Create the function celsius_to_fahr in a new R Script file.

Then use that formula to convert the celsius vector back into a vector of Fahrenheit values, and compare it to the original airtemps vector to ensure that your answers are correct.

Hint: the formula for Celsius to Fahrenheit conversions is celsius * 9/5 + 32.

Did you encounter any issues with rounding or precision?

Don’t peek until you write your own…

celsius_to_fahr <- function(celsius) {
    fahr <- celsius * 9/5 + 32
    return(fahr)
}

result <- celsius_to_fahr(celsius)
airtemps == result
[1] TRUE TRUE TRUE TRUE

15.1.3 Documenting R Functions

Functions need documentation so that we can communicate what they do, and why. The roxygen2 R package provides a simple means to document your functions so that you can explain what the function does, the assumptions about the input values, a description of the value that is returned, and the rationale for decisions made about implementation.

Documentation in roxygen2 is placed immediately before the function definition, and is indicated by a special comment line that always starts with the characters #'. Here’s a documented version of a function:

#' 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))

fahr_to_celsius <- function(fahr) {
  celsius <- (fahr - 32) * 5/9
  return(celsius)
}

Note the use of the @param keyword to define the expectations of input data, and the @return keyword for defining the value that is returned from the function. The @examples function is useful as a reminder as to how to use the function. Finally, the @export keyword indicates that, if this function were added to a package, then the function should be available to other code and packages to utilize.

Check it out: Function Documentation Section from R Packages (2e)

For more best practices on function documentation, review Hadley Wickham and Jennifer Bryan’s online book R Packages (2e) - Chapter 10, Section 16: Function Documentation.

15.1.4 Exercise: Minimizing Work with Functions

Functions can of course be as simple or complex as needed. They can be be very effective in repeatedly performing calculations, or for bundling a group of commands that are used on many different input data sources. For example, we might create a simple function that takes Fahrenheit temperatures as input, and calculates both Celsius and Kelvin temperatures. All three values are then returned in a list, making it very easy to create a comparison table among the three scales.

convert_temps <- function(fahr) {
  celsius <- (fahr - 32) * 5/9
  kelvin <- celsius + 273.15
  return(list(fahr = fahr, celsius = celsius, kelvin = kelvin))
}

temps_df <- data.frame(convert_temps(seq(-100,100,10)))

Once we have a dataset like that, we might want to plot it. One thing that we do repeatedly is set a consistent set of display elements for creating graphs and plots. By using a function to create a custom ggplot theme, we can enable to keep key parts of the formatting flexible. For example, in the custom_theme function, we provide a base_size argument that defaults to using a font size of 9 points. Because it has a default set, it can safely be omitted. But if it is provided, then that value is used to set the base font size for the plot.

custom_theme <- function(base_size = 9) {
    ggplot2::theme(
      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(),
      panel.border     = ggplot2::element_blank(),
      panel.grid.minor = ggplot2::element_blank(),
      panel.grid.major = ggplot2::element_line(colour = 'grey90', 
                                               linewidth = 0.25),
      legend.position  = 'right',
      legend.key       = ggplot2::element_rect(colour = NA, 
                                               fill = NA),
      axis.ticks       = ggplot2::element_blank(),
      axis.line        = ggplot2::element_blank()
      )
}

library(ggplot2)

ggplot(temps_df, mapping = aes(x = fahr, y = celsius, color = kelvin)) +
    geom_point() +
    custom_theme(10)

In this case, we set the font size to 10, and plotted the air temperatures. The custom_theme function can be used anywhere that one needs to consistently format a plot.

But we can go further. One can wrap the entire call to ggplot in a function, enabling one to create many plots of the same type with a consistent structure. For example, we can create a scatterplot function that takes a data frame as input, along with a point_size for the points on the plot, and a font_size for the text.

scatterplot <- function(df, point_size = 2, font_size = 9) {
  ggplot(df, mapping = aes(x = fahr, y = celsius, color = kelvin)) +
    geom_point(size = point_size) +
    custom_theme(font_size)
}

Calling that let’s us, in a single line of code, create a highly customized plot but maintain flexibility via the arguments passed in to the function. Let’s set the point size to 3 and font to 16 to make the plot more legible.

scatterplot(temps_df, point_size = 3, font_size = 16)

Once these functions are set up, all of the plots built with them can be reformatted by changing the settings in just the functions, whether they were used to create 1, 10, or 100 plots.

15.1.5 Summary

  • Functions are useful to reduce redundancy, reuse code, and reduce errors
  • Build functions with function()
  • Document functions with roxygen2 comments
Workflow for Creating Functions
  1. Have a clear goal (sometimes it helps to create a visual).
  2. Outline the plan and then add more detailed steps or tasks.
  3. Build it up bit-by-bit and start with a minimum viable example. As your function becomes more complex, it can harder to track all the bits.
  4. Always check intermediates!

15.2 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.
Packages for Creating and Maintaining Packages

The usethis, devtools and roxygen2 packages make creating and maintining a package to be a straightforward experience.

15.2.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:

usethis::create_package("~/mytools")
✔ 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>'
What did the create_package() function do?
  1. Open a new project called mytools (the name of the package) in a new RStduio session.
  2. Create a top-level directory structure, including a number of critical files under the standard R package structure:
    1. DESCRIPTIONfile: The most important file, which provides metadata about your package. Edit this file to provide reasonable values for each of the fields, including your contact information.
    2. NAMESPACE file declares the functions your package exports for external use and the external functions your package imports from other packages.
    3. R/ directory is where you save all your function scripts and other .R files.
    4. .Rbuildignore lists files that we need to have around but that should not be included when building the R package from source.
    5. .Rproj.user is a directory used internally by RStudio.
  3. Add the Build Tab to the Environment Pane.

15.2.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:

usethis::use_apache_license()
✔ 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:

Package: mytools
Title: Halina Do-Linh's Utility R Functions
Version: 0.0.0.9000
Authors@R: 
    person("Halina", "Do-Linh", email = "dolinh@nceas.ucsb.edu", role = c("aut", "cre"),
           comment = c(ORCID = "YOUR-ORCID-ID"))
Description: A collection of useful R functions that I use for general utilities.
License: Apache License (>= 2)
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3

15.2.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:

usethis::use_r("custom_theme")
✔ 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:

custom_theme <- function(base_size = 9) {
    ggplot2::theme(
      axis.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()
      )
}
Power of Packages

Remember when we created custom_theme() from the Functions Lesson Section 15.1.4? Now that we’ve added it to our mytools package, we don’t have to worry about coyping the code from another file, sourcing the file from another directory, or copying the script from an R Project.

Instead we can leverage the portable functionality of a package to easily access our custom functions and maintain the code in one location.

15.2.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:

usethis::use_package("ggplot2")
✔ 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.

Package: mytools
Title: Halina Do-Linh's Utility R Functions
Version: 0.0.0.9000
Authors@R: 
    person("Halina", "Do-Linh", email = "dolinh@nceas.ucsb.edu", role = c("aut", "cre"),
           comment = c(ORCID = "YOUR-ORCID-ID"))
Description: A collection of useful R functions that I use for general utilities.
License: Apache License (>= 2)
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
Imports: 
    ggplot2

15.2.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)
custom_theme <- function(base_size = 9) {
  ggplot2::theme(
    axis.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().

devtools::document()
ℹ 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.

15.2.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:

usethis::use_r("fahr_to_celsius")

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))

fahr_to_celsius <- function(fahr) {
  celsius <- (fahr-32)*5/9
  return(celsius)
}

Now, set up your package for testing:

usethis::use_testthat()
✔ 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:

usethis::use_test("fahr_to_celsius")
✔ 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():

devtools::test()
ℹ 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!

15.2.7 Checking and Installing

Now that you’ve completed testing your package, you can check it for consistency and completeness using devtools::check().

devtools::check()
══ 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

devtools::install()
── 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!

Check out the Build Tab

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

15.2.8 Sharing and Releasing

  • GitHub: The simplest way to share your package with others is to upload it to a GitHub repository, which allows others to install your package using the install_github('mytools','github_username') function from devtools.

  • CRAN: If your package might be broadly useful, also consider releasing it to CRAN, using the release() method from devtools(). Releasing a package to CRAN requires a significant amount of work to ensure it follows the standards set by the R community, but it is entirely tractable and a valuable contribution to the science community. If you are considering releasing a package more broadly, you may find that the supportive community at ROpenSci provides incredible help and valuable feeback through their onboarding process.

  • R-Universe: A newer approach is to link your package release to R-Universe, which is an effective way to make it easy to test and maintain packages so that many people can install them using the familiar install.pacakges() function in R. In R-Universe, people and organizations can create their own universe of packages, which represent a collection of packages that appear as a CRAN-compatible repository in R. For example, for DataONE we maintain the DataONE R-Universe, which lists the packages we actively maintain as an organization. So, any R-user that wants to install these packages can do so by adding our universe to their list of repositories, and then installing packages as normal. For example, to install the codyn package, one could use:

install.packages('codyn', repos = c('https://dataoneorg.r-universe.dev', 'https://cloud.r-project.org'))

15.2.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(), and install()
Don’t forget to update the version number before you install!

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:

usethis::use_version()
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

15.2.10 Additional Resources