R package development workshop
June 27, 2024
This session assumes familiarity with the C++ and Rcpp session of the R programming workshop.
Create a new package:
Make your package a Git repo:
Link to GitHub:
Commit your changes to git with a message “use GitHub”.
Use Rcpp and create an initial .cpp
file:
/src
directory for our C++ filessrc/.gitignore
to ignore compiled filesadd_cpp.cpp
ready for editing.It also copies some code to the clipboard for us to add to R/<packagename>-package.R
which we must create.
<packagename>-package.R
Then copy the code from the clipboard into this file:
## usethis namespace: start
#' @useDynLib pkgrcpp, .registration = TRUE
#' @importFrom Rcpp sourceCpp
## usethis namespace: end
NULL
The roxygen2 comments direct to
Rcpp::sourceCpp
useDynLib(pkgrcpp,".registration=TRUE")
to the NAMESPACE, so that the compiled code can be loaded and all C++ routines will be registered.add_cpp.cpp
should already have the following code:
Edit add_cpp.cpp
to add a C++ function and the Rcpp export comment, e.g.
Build > Document or Ctrl/Cmd + Shift + D now runs two functions:
Rcpp::compileAttributes()
looks for // [[Rcpp::export]]
and generates
src/RcppExports.cpp
: C routines wrapping exported C++ functionsR/RcppExports.R
: R wrappers that call the C routines.devtools::document()
that converts roxygen comments as usual, in particular updating the NAMESPACE.
importFrom(Rcpp,sourceCpp)
useDynLib(pkgrcpp, .registration = TRUE)
The R wrapper generated by Rcpp is as follows, in R/RcppExports.R
:
Currently this is an internal R function – we have not taken any action to add it as an export in the NAMESPACE.
devtools::load_all()
, Ctrl/Cmd + Shift + L, makes both internal and exported functions available for testing (without using :::
or ::
).
.cpp
file in the /src
directory (using use_rcpp
or File > New File). Save the file as sumC.cpp
. Add the C++ functiondevtools::load_all()
and try out the new function.The development workflow is the same as for R:
devtools::load_all()
will detect changes in the C++ code and
src/RcppExports.cpp
and R/RcppExports.R
We only need to document the R wrapper if we plan to export it.
The R wrapper is generated by Rcpp::compileAttributes()
, so we cannot add roxygen comments to export or document the function there.
Instead, we add comments above our C++ function, using \\'
instead of #'
.
In add_cpp.cpp
add_cpp
and add it to add_cpp.cpp
.add_cpp
as a guide, add documentation to sumC.cpp
.If you are running code on Taskfarm that uses a package with code compiled by Rcpp, you may see “illegal operations” errors in your jobs.
For a fix, see the Taskfarm FAQ.
Note that this can occur with any package that includes code compiled with Rcpp, not just packages that you wrote.
As we have seen, Rcpp::compileAttributes()
creates an R interface to our exported C++ functions.
To make a C++ interface for code in a .cpp
file, we should add a custom Rcpp::interfaces()
attribute as follows:
We can specify r
and/or cpp
to ask for an R and/or C++ interface.
Rcpp::compileAttributes()
generates the following header files in /inst/include
:
<packagename>_RcppExports.h
: inline definitions for all exported C++ functions.<packagename>.h
: includes the <packagename>_RcppExports.h
. Package authors may add additional C++ code here for sharing.All directories in inst
are moved to the top level of the package directory on installation.
Rcpp::interfaces()
attribute to add_cpp.cpp
to request both R and C++ interfaces for the add_cpp
function.To use C++ code from another package that has created a C++ interface for their Rcpp functions, in DESCRIPTION add
LinkingTo: otherpkg
e.g. with usethis::use_package(otherpkg, type = "LinkingTo")
. Then in .cpp
file, add the header file to includes
Then either using namespace otherpkg;
to make the functions from otherpackage
available globally, or otherpkg::fn()
to access functions directly.
In the R Programming workshop (final exercise of the Rcpp session), we wrote an Rcpp function to approximate \(\pi\):
We can try using the dqrunif
C++ functions provided by the dqrng package instead of Rcpp::runif
.
See https://daqana.github.io/dqrng/index.html for more info on this package.
usethis::use_package()
to add dqrng
to LinkingTo
. It will show possible headers to include, including dqrng.h
which provides the Rcpp exports..cpp
file and add the approx_pi
function. Make sure to add the necessary headers and Rcpp attribute, so that you can use it after running devtools::load_all()
.approx_pi
function that uses the dqrunif
C++ functions from dqrng
instead.bench::mark()
to benchmark the two versions with N = 1e7
, setting check = FALSE
in bench::mark()
as the generated random numbers will differ.Some packages provide a custom <packagename>.h
or other C++ headers.
<packagename>.h
!LinkingTo
.The documentation should clarify what is needed for a particular package.
The BH package provides header files for the Boost C++ libraries.
The header files have .hpp
extension and are nested in directories. The namespace is similarly nested:
Include BH
in LinkingTo:
to add this function to a package.
The RcppArmadillo package provides headers for the Armadillo library for linear algebra & scientific computing.
The header to include is RcppArmadillo.h
, which provides the armadillo
header defining the arma
namespace, e.g.
To use RcppArmadillo in a package, we need to
RcppArmadillo
to LinkingTo
src/Makevars
and src/Makevars.win
.Both can be done with usethis::use_rcpp_armadillo()
.
The first time you call it, you may be asked whether to install RcppArmadillo. Choose the affirmative option.
use_rcpp_armadillo()
As well as doing the general setup to allow a package to use RcppArmadillo, it also creates a src
directory and a .cpp
file to edit.
If you already have a .cpp
file in a src
directory, by default it may try to overwrite it. If it does, select one of the negative options.
You need to specify the name of a new .cpp
as an argument instead, e.g. use_rcpp_armadillo("new_file")
The function adds to the top of the .cpp
file
#include <Rcpp.h>
using namespace Rcpp;
#include <RcppArmadillo.h>
// [[Rcpp::depends(RcppArmadillo)]]
If you have an Apple Silicon Mac, you will also need to do the following:
open your global Makevars by calling usethis::edit_r_makevars()
Add the following code:
FLIBS= -L/opt/R/arm64/gfortran/lib
Session > Restart R (or Cmd + Shift + 0)
Run usethis::use_rcpp_armadillo("inner_prod")
to set the package up to use RcppArmadillo and create a new .cpp
file.
In inner_prod.cpp
, edit the #include
statement for RcppArmadillo and add the code
devtools::load_all()
to load the function and try it out.Congratulations! You’ve completed the course!
We now need to take a register to see who’s still here so we can award SkillsForge and Warwick Award credit.
Licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0).