knitr::opts_chunk$set(echo = TRUE)
Resources: R Packages Advanced R
https://github.com/3mmaRand/workshops/tree/master/York2020_01
To find out 1) where you R installation is stored 2) where your packages are stored 3) Which version of R you're using
#1 R.home() #2 .libPaths() .Library #3 R.version
devtools
contains all the function for doing lots of package development stuff automatically
library(devtools)
Navigate to the directory you want to create your package by going to Session > Set Working Directory > Choose Directory Then run this chunk. A new session will open which is a project containing the package.
usethis::create_package("mypackage")
You will notice a new tab in the top right pane called 'Build' and a directory that has been populated with a skeleton:
.Rbuildignore
lists the files that won't be included in the build file, kind of like .gitifnore
DESCRIPTION
contains a basic metadata description of your package. You'll need to edit this.
NAMESPACE
contans info on the functions that will need to be imported to make your package work and functions that will be exported. Roxygen populates this automatically as you go along
R/
firectory contains your functions
Version control is optional but highly recommended.
Open up the shell in Rstudio
Tools > Shell...
git config --global user.name Jo Watson
git config --global user.email jowatson2011@hotmail.co.uk
git config --global --list
Close the project or Rstudio, reopen it again and then reload devtools
Now we need to tell R that it's going to be using git with devtools
use_git()
Select yes for both of the questions to commit your repo and restart Rstudio. You should see the git pane in the top right. Reload devtools.
To put a function in your package, such as this simple one which calculates the sum of squares, we use use_r()
where the argument is the name of our function. This will automatically create a .R
file in our R/
directory.
sum_squares <- function(v){ sum((v - mean(v))^2) } use_r("sum_squares")
Copy and paste your function code into the file.
If we remove the function and then reload using the devtools function load_all()
, which loads all the function saved in R/
, we should see our new package
rm(sum_squares) load_all() sum_squares(rnorm(100,10,2))
Using devtools we can check whether our package has all the documentation and is working internally, with the function check()
check()
A package won't be accepted on CRAN with any error messages!! You should just see that R/sum_squares.R
is undocumented and you haven't specified a lisence in DESCRIPTION
use_github()
Select https and yes to the subsequent questions.
NOTE: error saying I hadn't committed everything before running this:
Error in git2r::commit(r, "Add GitHub links to DESCRIPTION") :
Error in 'git2r_commit': Nothing added to commit``
was followed by:
Error: This repo already has an 'origin' remote, with value 'https://github.com/JoWatson2011/mypackage.git'.How to remove this setting in the shell:
``git remote rm origin``
was followed by:
Error: Repo 'JoWatson2011/mypackage' already exists on 'github.com'.`
... deleting the repo on GitHub (which was empty) made everything okay :)
Open the DESCRIPTION
file and chnge the:
Title
to a useful description of your package and what it does
The info in the speech marks in Authors@R
License
provides legally binding guidelines for the use and distribution of your software. A MIT license is commonly used for open source software. We can automatically populate this section of DESCRIPTION
with a MIT license using use_mit_license()
Note that GitHub links have been addded automatically.
options(usethis.full_name = "Jo Watson") use_mit_license()
Note the License
line in DESCRIPTION
is now filled in and two more files are in your repository: LICENSE
and LICENSE.md
Time to commit again! :)
load_all() ?sum_squares
Uh-oh. Our function doesn't have any documentation!
This is where Roxygen
comes in.
Open the R/sum_squares.R
file.
Put your cursor to the top of the script.
Go to : code > Insert Roxygen Skeleton
It will insert a section of specially formated LaTeX-style text. Any line beginning #'
will be in roxygen format.
Running the function document()
willbuild the documentation for that file based on what you've included in the roxygen output
document()
A directory called man/
will be automatically generated which contains your documentation, as well as a file called NAMESPACE
.
?sum_squares check()
You should have no warnings!
# standardError <- function(v){ # sd(v) / length(v) # } use_r("standardError") #Code > Insert Roxygen Skeleton document() load_all() ?standardError check()
Encountered the following 'Note' in the check()
output:
standardError: no visible global function definition for 'sd'
Undefined global functions or variables:
sd
Consider adding
importFrom("stats", "sd")
to your NAMESPACE file.
Added a line to the roxygen text at the top of standardError.R
:
#' @importFrom stats sd
This will import the nesecary packages for the function and update NAMESPACE
accordingly
# oneSampleZ <- function(v, u){ # mean(v) - u/(sd(v)/ sqrt(length(v))) # } use_r("oneSampleZ") document() load_all() check()
WE DON'T IMPORT PACKAGES INTO PACKAGES WITH library()
Imports: required. Installed automatically
Suggests: optional. Used in development only, in a vignette or example, or for testing. Not installed automatically
Depends: rare... perhaps if you need a specific build of a package/R.
Let's consider the following function
density_plot <- function(v) { if(!requireNamespace("ggplot2", quietly = T)){ stop("Need ggplot2!") } ggplot2::ggplot(data.frame(values = v), ggplot2::aes(x = values)) + ggplot2::geom_density() }
use_r("density_plot") document() load_all
If we need functions from a different package very regularly for our functions to work, we need to specify it in the DESCRIPTION
file using use_package()
use_package("ggplot2") use_package("magrittr") use_package("stats") # or: use_package("ggplot2", type = "Suggests") # or: use_package("ggplot2", min_version = x)
From the 'Writing R Extensions' manual
The ‘Imports’ field lists packages whose namespaces are imported from (as specified in the NAMESPACE file) but which do not need to be attached. Namespaces accessed by the ‘::’ and ‘:::’ operators must be listed here, or in ‘Suggests’ or ‘Enhances’ (see below). Ideally this field will include all the standard packages that are used, and it is important to include S4-using packages (as their class definitions can change and the DESCRIPTION file is used to decide which packages to re-install when this happens). Packages declared in the ‘Depends’ field should not also be in the ‘Imports’ field. Version requirements can be specified and are checked when the namespace is loaded.
Then, if to import the functions we need from those specified packages, we use the same roxygen
syntax as above:
#' @importFrom magrittr %>%
#' @importFrom ggplot2 ggplot aes geom_density
Now this has been specified, when a user imports our package it will only load if they have ggplot2
and magrittr installed.
You might want to do this at the top of specific functions or you can store it centrally:
use_r("imports.R") document() load_all() check()
R/imports.R
contains only a title and the lines above in the roxygen
format. Note: You cannot use the roxygen
skeleton for this.
Importing the functions from the packages rather than the whole package makes our package more robust. It avoid problems with functions named the same thing.
There are two classes of functions in packages, internal and external.
Internal are only for use within the package, don't need documenting and we can change them without much effect on the user. They are the 'behind the scenes' functions that make everything external work smoothly.
External are the functions that our users will call. They need documenting and We need to specify whether it is to be exported, by stating in the roxygen
text at the top.
In the code skeleton, just remove the '@ export
line if your function is internal.
So far we have been following a workflow... write code > reload code > load_all() and check() > ...
The package testthat
will help us with this!
It will generate a directory for all the tests for our functions in R/
library(testthat) use_package("testthat", type = "Suggests") usethis::use_test("sum_squares.R")
Open the tests/testthat/test-sum_squares.R
file and add some tests
Four expectations cover 90% of cases:
expect_equal(object, experession)
expect_error(code, regexp)
expect_warning(code, regexp)
expect_warning(code, NA)
expect_known_output(code)
devtools::test()
When writing tests, think about: what are the expected failures and expected responses. Also, special cases. * How much of your code has been tested and what else needs testing?
devtools::test_coverage() check()
devtools::build()
You can share the mypackage_0.0.0.9000.tar.gz that has just been built! If the person you shared it with wants to install it: ` install.packages("mypackage_0.0.0.9000.tar.gz", )
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.