1 Building an package
Here we present how to efficiently create and build a package using RStudio IDE, and the devtools
and usethis
packages.
More details are provided in the reference material on this subject: the book R packages1 by Hadley Wickham & Jennifer Bryan, freely available online.
1.1 Initializing a package
A simple way, built into RStudio, to initialize a package is to follow those steps:
👉 Your turn (already)!
create a new project (top left
File
drop down menu in RStudio)choose “New Directory”
choose “R package using devtools” (if it is not available, choose “R package”, the difference being that with “R package” you will have to delete unnecessary files that are automatically created but not useful)
give a name to your package, for example
mypkgr
.
NB: if you a using Posit Cloud and not your local computer, you need to rather execute the following command into the console: usethis::create_package(".")
and accept the proposed overwrites.
From that, we get the minimal structure for an package, namely:
a DESCRIPTION file whose
Title
,Version
,Authors@R
andDescription
are to be edited (other parts can be edited or even added automatically, see below)a NAMESPACE file which will be later edited automatically (so hands off for now)
a folder R/ in which we will add
.R
script files
devtools
also adds three optional files:
.gitignore, relative to
git
, a version control tool that we will see in detail in the following section ongit
& GitHubmypkgr.Rproj which is a specific file of RStudio, and allows to define the characteristics and preferences of the project we just created
.Rbuildignore which allows to ignore some files when we build the package down the road (for example, the
mypkgr.Rproj
file should not be included in the package)
1.2 Adding a function: common theme example
We first invite you to code the following function, which we will use throughout the course:
We want to compute the value of the density of a multivariate normal distribution on \(\mathbb{R}^p\) at \(n\) points. Our function must be applicable for any multivariate normal distribution (i.e. any mean vector \(\boldsymbol\mu\) in \(\mathbb{R}^p\) and variance-covariance matrix \(\boldsymbol\Sigma\) of order \(p\)), and we wish to compute all the values of the density evaluated at the \(n\) points \(\mathbf{x}\) in a single call of the function.
As a reminder, the density function for a multivariate Gaussian distribution is: \[\displaystyle (2\pi )^{-p/2}\det({\boldsymbol {\Sigma }})^{-1/2}\,\exp \left(-{\frac {1}{2}}(\mathbf {x} -{\boldsymbol {\mu }})^{\mathsf {T}}{\boldsymbol {\Sigma }}^{-1}(\mathbf {x} -{\boldsymbol {\mu }})\right)\]
So you need to create a function mvnpdf()
in a file named mvnpdf.R
in the R/
folder of your package, which :
-
takes as arguments:
x
a matrix, with \(n\) columns (the observations) and \(p\) rowsmean
a vector of meansvarcovM
a variance-covariance matrixLog
a logical parameter, with default value toTRUE
.
returns a list containing the matrix
x
, and a vector of length \(n\) of the multivariate normal distribution density values at those points.
👉 Your turn !
Here is a function proposal that you can download here. ⚠️ WARNING ! if you click too quickly on this link, it will invalidate your participation in the class !
For advice on writing code, see the R code section from Wickham & Bryan R packages (2023) by Wickham & Bryan2.
1.3 Documenting a function
It is important to properly document your code. Every project has at least two developers:
yourself,
and yourself in 6 months.
For the sake of your future self, do yourself a favor and take the time to document your code !
We strongly advise you to use the roxygen2
package to document your packages. The main advantage being to have the help of a function in the same file as the code defining this function.
👉 Your turn !
Start by inserting the skeleton of the helper with “Insert Roxygen Skeleton” located in the “Code” menu or the Magic Wand sub-menu in the script window.
Complete the documentation by filling in:
the title of the function (first line)
the description of what the function does (second paragraph)
if you fill in a third paragraph, this part will go in the “Details” section of the help page
the meaning of the parameters
the output, after the
@return
tagGenerate the documentation using “Document” in the “More” menu of the “Build” tab (or alternatively run
devtools::document()
or useCtrl+Shift+D
). The effect of this command is multiple:
a
man
folder is created and inside it, amvnpdf.Rd
file contains the help information about the functionthe
NAMESPACE
file is modified
In case of problems OR out of curiosity but only once you are done, you can have a look at this proposal.
For more details on package documentation and roxygen2
tags, see the Object documentation section from Wickham & Bryan R packages (2023)
Let us finish by mentioning a function from the usethis
package which initializes a home help page for the whole package:
usethis::use_package_doc()
The generated help page will then be accessible, once the package is installed, with the following command:
?mypkgr
1.4 Interactively test the package
To test the package, you have to load it in using “Load All” (or Ctrl+Shift+L
) in the “More” menu from the “Build” tab, or alternatively devtools::load_all()
).
You can then use your package directly in : look at the function help page with ?mvnpdf
and for example execute the commands given in the example section of this help page.
?mvndpf
During code development, you can thus:
Add/Modify the code
Re-load the package
Ctrl+Shift+L
Experiment with it in the console
And so on…
1.5 Automatically test your package
To initialize automatic testing capabilities in your package, execute the following command:
usethis::use_testthat()
This command creates a tests
folder which includes a testthat.R
file – not to be modified – and a testthat/
folder in which we will add our automated tests. This tool is based on the theory of unit tests
.
For example, here is the content of a file, containing two tests, that should be named test-mvnpdf.R
and be put into the testthat
folder (instead of doing this by hand, you can simply use the helper function usethis::use_test()
that will create the file at the right place for you):
test_that("correct result for univariate gaussian", {
expect_equal(mvnpdf(x=matrix(1.96), Log=FALSE)$y, dnorm(1.96))
expect_equal(mvnpdf(x=matrix(c(1.96, -0.5), ncol = 2), Log=FALSE)$y,
dnorm(c(1.96, -0.5)))
})
test_that("correct results for bivariate gaussian", {
expect_equal(mvnpdf(x=matrix(rep(1.96,2), nrow=2, ncol=1), Log=FALSE)$y,
mvtnorm::dmvnorm(rep(1.96, 2)))
})
To execute these tests, you can click on “Test package” (Ctrl+Shift+T
) in the “More” menu from the “Build” tab, or run devtools::test()
The advantage of these automatic tests is that they will be run every time you check the package (see just below).
A good practice is to add a unit test each time a bug is identified and fixed, so that we can immediately identify and prevent the same error from happening again in the future.
1.6 Checking your package
Running a check means ensuring that everything in your package is correct and will work as expected, and that it can be installed properly under various OS. “Passing R CMD CHECK
” is mandatory for successfully uploading your package on CRAN.
To perform R CMD CHECK
on your package, you can click on “Check” (Ctrl+Shift+E
) from the “Build” tab, or run devtools::check()
.
During the check, the automated unit tests that we have developed previously are executed. This is the advantage of having written these tests, we don’t need to worry about it, simply react and adjust the code when errors are returned and flagged.
1.7 Install your package
For the moment, the package exists only in the environment associated with the RStudio project we have created. To be able to use it anywhere (on your computer) in in a general way, you have to install it (like a CRAN package for example).
To do this, click on “Install” (Ctrl+Shift+B
) from the “Build” tab, or alternatively you can run devtools::install()
.
And finally, you can configure RStudio’s behavior so that at the time of installation, it simultaneously documents the package: go to the “More” menu from the “Build” tab, then “Configure Build Tools…”. Then click on “Configure” next to “Generate documentation with Roxygen”, and check the box “Install and Restart”.
Appendix 1.1: add an S3 method
In most packages, we often have to implement so called S3 methods
so that, from a result object res
, one can run print(res)
, summary(res)
, plot(res)
, …
Here is an example of a plot()
method that we can add to our package:
#' Plot of the mvnpdf function
#'
#' @param x an object of class \code{mvnpdf} resulting from a call of
#' \code{mnvpdf()} function.
#' @param ... graphical parameters passed to \code{plot()} function.
#'
#' @return Nothing is returned, only a plot is given.
#' @export
#'
#' @examples
#' pdfvalues <- mvnpdf(x=matrix(seq(-3, 3, by = 0.1), nrow = 1), Log=FALSE)
#' plot(pdfvalues)
plot.mvnpdf <- function(x, ...) {
plot(x$x, x$y, type = "l", ...)
}
⚠️ WARNING ! In order for this S3 method to do what we want it to do when we apply it to the result of our function mvnpdf()
, we have to declare that mvnpdf()
returns a result of class mvnpdf
.
Test this function, by executing the example.
Don’t forget to re-install the package (“Install” or Ctrl+Shift+B
).
Look at the contents of the man
folder and the changes that have been made to NAMESPACE
.
Here is a proposed solution: the file contains the complete code of the mvnpdf()
function and the associated plot()
method.
Appendix 1.2: submit one’s package on CRAN
Run the two following commands: devtools::check()
followed devtools::submit_cran()
.
For more details, see Wickham & Bryan’s recommended pipeline in their book R packages