diff --git a/_freeze/slides/01-package-foundations/index/execute-results/html.json b/_freeze/slides/01-package-foundations/index/execute-results/html.json index 3852890..a62084d 100644 --- a/_freeze/slides/01-package-foundations/index/execute-results/html.json +++ b/_freeze/slides/01-package-foundations/index/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "dff2f2969daf9b49a410e037f46b8dac", + "hash": "b3e08ae0b16df967bc368dc279b931fa", "result": { "engine": "knitr", - "markdown": "---\ntitle: Package foundations\nsubtitle: R package development workshop
Module 1\nauthor: Forwards teaching team\nformat: forwardspres-revealjs\n---\n\n## Overview\n\n- Why write a package?\n- Package structure and state\n- Package development setup\n- Creating a package with a working function\n\n# Why write a package? {.inverse}\n\n## Why write a package?\n\n- You want to **generalise** code\n- You want to **document** code\n- You want to **test** code\n- You want to **share** code\n- You want to create **impact** from your work\n\n## Script vs package\n\n| R script | Package |\n|--------------------------------|-------------------------------------|\n| One-off data analysis | Provides reusable components |\n| Defined by `.R` extension | Defined by presence of `DESCRIPTION` file |\n| `library()` calls | Imports defined in `NAMESPACE` file |\n| Documentation in `#` comments | Documentation in files and `Roxygen` comments |\n| Run lines or source file | Install and restart |\n\n::: {.notes}\nreusable components: typically functions, but could also be R markdown template, Shiny app, data\n:::\n\n# Package structure and state {.inverse}\n\n## Package structure\n\nAn R package is developed as a directory of source code files.\n\nThe names of files and directories must follow the specification laid out in \nthe [Writing R Extensions](https://cran.r-project.org/doc/manuals/r-release/R-exts.html) manual - we'll cover the main components in this workshop.\n\nDirectory tree for an example RStudio package project:\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output .cell-output-stdout}\n\n```\nmypackage\n├── DESCRIPTION\n├── NAMESPACE\n├── R\n└── mypackage.Rproj\n```\n\n\n:::\n:::\n\n\n## Package states\n\n:::::: {.columns}\n\n::::: {.column width=\"25%\"}\n- ::::{.larger125}\n :::{.callout-note}\n ## source\n :::\n ::::\n- bundled\n- binary\n- installed\n- in-memory\n:::::\n\n::::: {.column width=\"70%\"}\n:::{.callout-note}\n## source\nWhat you create and work on.\n\nSpecific directory structure with some particular components e.g., `DESCRIPTION`, an `/R` directory.\n:::\n:::::\n::::::\n\n## Package states\n\n:::::: {.columns}\n\n::::: {.column width=\"25%\"}\n- source\n- ::::{.larger125}\n :::{.callout-note}\n ## bundled\n :::\n ::::\n- binary\n- installed\n- in-memory\n:::::\n\n::::: {.column width=\"70%\"}\n:::{.callout-note}\n## bundled\nPackage files compressed to single `.tar.gz` file.\n\nAlso known as \"source tarballs\".\n\nCreated by command line tool `R CMD build`\n\nUnpacked it looks very like the source package.\n:::\n:::::\n::::::\n\n::: {.notes}\nIn the rare case that you need to make a bundle from a package you’re developing locally, use devtools::build(). Under the hood, this calls pkgbuild::build() and, ultimately, R CMD build, which is described further in the Building package tarballs section of Writing R Extensions.\n:::\n\n## Package states\n\n:::::: {.columns}\n\n::::: {.column width=\"25%\"}\n- source\n- bundled\n- ::::{.larger125}\n :::{.callout-note}\n ## binary\n :::\n ::::\n- installed\n- in-memory\n:::::\n\n::::: {.column width=\"70%\"}\n:::{.callout-note}\n## binary\nCompressed copy of the package in installed form.\n\nAlso a single file.\n\nPlatform specific: `.tgz` (Mac) `.zip` (Windows).\n\nPackage developers submit a bundle to CRAN; CRAN makes and distributes binaries.\n:::\n:::::\n::::::\n\n::: {.notes}\n\nA package in binary form is Platform specific.\nIf you write a package for distribution on CRAN, you submit a bundle to CRAN then CRAN makes and distributes binaries\n\n\n`install.packages()` is usually downloading the binary\n\nTo understand the difference between package bundle and a package binary see \n:::\n\n## Package states\n\n:::::: {.columns}\n\n::::: {.column width=\"25%\"}\n- source\n- bundled\n- binary\n- ::::{.larger125}\n :::{.callout-note}\n ## installed\n :::\n ::::\n- in-memory\n:::::\n\n::::: {.column width=\"70%\"}\n:::{.callout-note}\n## installed\nA directory of files in a library directory.\n\nAny C/C++/Fortran code is in compiled form.\n\nHelp files, code and optionally data are in database form.\n\n`install.packages()` can install from source or from a binary\n:::\n:::::\n::::::\n\n## Package states\n\n:::::: {.columns}\n\n::::: {.column width=\"25%\"}\n- source\n- bundled\n- binary\n- installed \n- ::::{.larger125}\n :::{.callout-note}\n ## in-memory\n :::\n ::::\n:::::\n\n::::: {.column width=\"70%\"}\n:::{.callout-note}\n## in-memory\nIf a package is installed, `library()` makes its function available by loading the package into memory and attaching it to the search path.\n:::\n:::::\n::::::\n\n## Building/Installing Packages from Source\n\nThere are various reasons we may wish to build or install from source:\n\n- Installing a CRAN package where a binary has not yet been built for the latest version.\n- Installing a package from GitHub/other version-controlled source code repository, e.g.\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nremotes::install_github(\"r-lib/revdepcheck\")\n```\n:::\n\n- Installing our own package from the source code or building it to submit to CRAN.\n\nIf the package includes C/C++/Fortran code, we will need a suitable compiler.\n\n# Build tools {.inverse}\n\n::: {.notes}\nCovered in the course prerequisites and in Module 6 on R with C++, but put here for completeness \n:::\n\n## Linux\n\nDebian/Ubuntu:\n\n```{.sh}\napt-get update\napt-get install r-base-dev\n```\n\nFedora/RedHat: should be set up already.\n\n## MacOS\n\nOption 1\n\n- [Register as an Apple developer (for free)](https://developer.apple.com/programs/register/)\n - Then, in the terminal:\n \n ```{.sh}\n xcode-select --install\n ```\n\nOption 2 \n\n- Install the current release of full [Xcode from the Mac App Store](https://itunes.apple.com/ca/app/xcode/id497799835?mt=12)\n- More convenient but installs a lot you don't need\n\n## Windows\n\n- Download the Rtools installer that matches your version of R from \n- Run the installer, `Rtools.exe`, keeping the default settings.\n\n# Package development setup {.inverse}\n\n## The setup we'll be using\n\nWe'll be using the following tools for package development:\n\n- RStudio: to manage and edit the package source code\n- git + GitHub: to version control the package source code\n- **devtools** and **usethis** R packages: \n - **devtools** for functions supporting the development workflow\n - **usethis** for setup tasks\n - **devtools** depends on **usethis** package\n - Integrated with RStudio: projects, menu items/shortcuts\n - Uses system utilities internally: `R CMD` utilities bundled with R\n \n## Follow along\n\nFor the rest of this session, follow along on your own computer to make sure you're set up for package development and to create our example package.\n \n## **devtools** and **usethis**\n\nWe can use **devtools** right away to check our system is setup for package development.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ninstall.packages(\"devtools\")\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nlibrary(devtools)\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nhas_devel()\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\nYour system is ready to build packages!\n```\n\n\n:::\n:::\n\n\nInstalling **devtools** will also install **usethis**. \n\nCheck you have the latest version of **usethis** and update if not:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\npackageVersion(\"usethis\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] '3.1.0'\n```\n\n\n:::\n:::\n\n\n## git sitrep\n\n- Ask for a **sit**uation **rep**ort:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::git_sitrep()\n```\n:::\n\n\n- Check whether you have a PAT (will set up in a couple of slides if not)\n\n ```\n Personal access token for 'https://github.com': ''\n ```\n\n## Configure git\n\n- Check in `git_sitrep` output, under `Git global (user)`, that your user name and email are defined. If not, run\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_git_config(\n user.name = \"YOUR NAME\", # your full name\n user.email = \"name@example.com\" # email associated with GitHub account\n)\n```\n:::\n\n\nTo keep your email private:\n\n- Go to , select “Keep my email address private” and “Block command line pushes that expose my email” options\n- Configure git to use the address provided of the form [ID+username@users.noreply.github.com](mailto:ID+username@users.noreply.github.com)\n\n\n## git vaccinate\n\nIt's also a good idea to vaccinate. This implements best practice by adding files to your global `.gitignore`:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::git_vaccinate() \n```\n:::\n\n\n## Create a GitHub PAT\n\nThe **usethis** package uses personal access tokens (PAT) to communicate with GitHub.\n\nFirst, make sure you're signed into GitHub. Then run\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::create_github_token()\n```\n:::\n\n\n:::{.smaller90}\n- Add Note describing the computer or use-case (e.g. personal-macbook-pro-2021, project-xyz)\n- Select expiration (recommend default 30 days)\n- Check scope (default selection is fine)\n- Click 'Generate Token'\n- **Important!** Copy token to clipboard, do not close window until stored (see next slide)!\n- You may want to store token in a secure vault, like 1Password or BitWarden\n:::\n\n## Store your PAT\n\nPut your PAT into the Git credential store by running the following command and entering your copied PAT at the prompt (assume the PAT is on your clipboard).\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ngitcreds::gitcreds_set()\n```\n:::\n\n\n- If you don't have a PAT stored, will prompt you to enter: paste!\n- If you do, you will be given a choice to keep/replace/see the password\n - choose as appropriate\n - if replacing, paste!\n- run `git_sitrep()` again to check the PAT has been discovered \n\n## More on usethis and GitHub creds\n\nIt is well worth reading (and following all the instructions) in the following two **usethis** vignettes:\n\n- [usethis setup](https://usethis.r-lib.org/articles/usethis-setup.html)\n- [Managing Git(Hub) Credentials](https://usethis.r-lib.org/articles/git-credentials.html)\n - See in particular the section on [ongoing PAT maintenance](https://usethis.r-lib.org/articles/git-credentials.html#ongoing-pat-maintenance)\n\n# Create a package! {.inverse}\n\n## Package name\n\nCan only contain the characters [A-Z, a-z, 0-9, .]. Some tips:\n\n- Unique name you can easily Google\n- Avoid mixing upper and lower case\n- Use abbreviations\n- Add an r to make unique, e.g **stringr**\n- Use wordplay, e.g. **lubridate**\n- Avoid trademarked names\n- Use the [**available**](https://r-lib.github.io/available/) package to check name not taken\n\nFor now, we will use **animalsounds**\n\n## Create a package!\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::create_package(\"~/Desktop/animalsounds\")\n```\n:::\n\n\n- Be deliberate about where you create your package.\n\n- Do not nest inside another RStudio project, R package or git repo.\n\n- You may want to use a different path!\n\n## `create_package()`\n\nWhat happens when we run `create_package()`?\n\n- R will create a folder called `animalsounds` which is a package and an RStudio project\n- restart R in the new project\n- create the some infrastructure for your package with the minimal components for a working package\n- start the RStudio Build pane\n\n## R Studio Build pane/menu\n\n:::: {.columns}\n\n::: {.column width=\"50%\"}\n![](images/rstudio_build_pane.png)\n:::\n\n::: {.column width=\"50%\"}\n![](images/rstudio_build_menu.png)\n:::\n\n::::\n\n## Minimal components\n\n**usethis** will create the minimal components of a package that we have already seen:\n\n- `DESCRIPTION` provides metadata about your package. \n- `NAMESPACE` declares the functions your package exports for external use and the external functions your package imports from other packages.\n- The `/R` directory is where we will put `.R` files with function definitions.\n\n## Auxiliary files\n\n**usethis** also adds some auxiliary files:\n\n- `animalsounds.Rproj` is the file that makes this directory an RStudio Project.\n- `.Rbuildignore` lists files that we need but that should not be included when building the R package from source.\n- `.gitignore` anticipates Git usage and ignores some standard, behind-the-scenes files created by R and RStudio.\n\n# git and GitHub {.inverse}\n\n::: {.notes}\nNow will make our package a version controlled project on our local machine and then put it on GitHub.\n\n - version control, best-practice open-source development \n\nusethis has functions to help with this. \n:::\n\n## Use git\n\nTo make our project a git repository, or 'repo', on our local machine we use `usethis::use_git()`\n\nMake your package a git repo:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_git()\n```\n:::\n\n\n## `use_git()` output (part 1)\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n ✔ Initialising Git repo\n ✔ Adding '.Rhistory', '.Rdata', '.httr-oauth', '.DS_Store' to '.gitignore' \n There are 5 uncommitted files:\n * '.gitignore'\n * '.Rbuildignore'\n * 'DESCRIPTION'\n * 'animalsounds.Rproj'\n * 'NAMESPACE'\n Is it ok to commit them?\n\n 1: I agree\n 2: Absolutely not\n 3: No way\n```\n:::\n\n\nChoose the affirmative option! (The exact options may vary.)\n\n## `use_git()` output (part 2)\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n√ Adding files\n√ Commit with message 'Initial commit'\n• A restart of RStudio is required to activate the Git pane\nRestart now?\n\n1: Nope\n2: Definitely\n3: No\n```\n:::\n\n\nChoose the affirmative option! (The exact options may vary.)\n\n::: {.notes}\nYou should find the git pane has opened.\n:::\n\n## Use GitHub\n\nTo create a copy on GitHub we use `usethis::use_github()`.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_github() # creates a public repo\nusethis::use_github(private = TRUE) # private repo (licensing, no Pages)\n```\n:::\n\n\nThis takes a local project, creates an associated repo on GitHub, adds it to your local repo as the \"origin remote\", and makes an initial \"push\" to synchronize.\n\n::: {.notes}\nPromote private repo for novel package development - link to licensing issue, covered in package documentation session \n:::\n\n## `use_github()` output\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_github()\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n ✔ Creating GitHub repository 'Warwick-Stats-Resources/animalsounds'\n ✔ Setting remote 'origin' to 'https://github.com/Warwick-Stats-Resources/animalsounds.git'\n ✔ Setting URL field in DESCRIPTION to 'https://github.com/Warwick-Stats-Resources/animalsounds'\n ✔ Setting BugReports field in DESCRIPTION to 'https://github.com/Warwick-Stats-Resources/animalsounds/issues'\n There is 1 uncommitted file:\n * 'DESCRIPTION'\n Is it ok to commit it?\n\n 1: Nope\n 2: For sure\n 3: No way\n```\n:::\n\n\nChoose the affirmative option! (The exact options may vary.)\n\n::: {.notes}\nTake a look at the repo on GitHub. There is no `/R` folder as that folder is empty at the moment!\n\nDuring the demo, will need to run `use_github(\"Warwick-Stats-Resources\")` as by default use_github goes to my personal repo\n:::\n\n## Adding functions\n\nFunctions go in an `.R` file in the `/R` directory.\n\nThere's a `usethis` helper for adding `.R` files!\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_r(\"file_name\") \n```\n:::\n\n\n`usethis::use_r()` adds the file extension (you don't need to).\n\n. . .\n\nUse a separate `.R` file for each function or closely related set of functions, e.g.\n\n- a top-level function and the internal functions it calls\n- a family of related functions \n- a summary method and its print method\n\n## `usethis::use_r()`\n\nCreate a new R file in your package called `animal_sounds.R`\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_r(\"animal_sounds\")\n```\n:::\n\n\nThe output includes:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n• Modify 'R/animal_sounds.R' \n```\n:::\n\n\n## Add a function\n\nPut the following toy function into your script:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nanimal_sounds <- function(animal, sound) {\n stopifnot(is.character(animal) & length(animal) == 1)\n stopifnot(is.character(sound) & length(sound) == 1)\n paste0(\"The \", animal, \" goes \", sound, \"!\")\n}\n```\n:::\n\n\nDon't try to use the function yet!\n\n## Development workflow\n\nIn a normal script, you might use:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nsource(\"R/animal_sounds.R\")\n```\n:::\n\n\nHowever, for building packages, we need to use the `devtools` approach.\n\nThis will ensure our function has the correct namespace and can find internal functions, functions imported by our package from other packages, etc.\n\n## Development workflow\n\n![](images/dev_cycle_before_testing.png){fig-align=\"center\"}\n\n:::{.center-h}\nYou don't even need to save your code!\n:::\n\n::: {.notes}\ndevtools::load_all() simulates package installation so that you can test your function.\n:::\n\n## Your turn\n\n1. Load all with `devtools::load_all()` and try calling the `animal_sounds()` function with appropriate values for `animal` and `sound`.\n2. Change some tiny thing about your function -- maybe the animal “says” instead \nof “goes”.\n3. Load all with `devtools::load_all()` and try calling the updated function to see the changed behaviour.\n4. Add `animal_sounds.R` so that it is tracked by git. Make a commit with the message `Add animal_sounds()`.\n5. Push all your commits from this session.\n\n## Installing the package\n\n`devtools::load_all()` roughly simulates what happens when a package is installed and loaded with `library()`.\n\nTo actually install the package:\n\n- locally: `devtools::install()`\n\n- from GitHub: `remotes::install_github(\"USERNAME/REPO\")` or `pak::pak(\"USERNAME/REPO\")`\n\n# End matter {.inverse}\n\n## References\n\nWickham, H and Bryan, J, _R Packages_ (2nd edn, in progress), .\n\nR Core Team, _Writing R Extensions_, \n\n## License\n\nLicensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License ([CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/){target=\"_blank\"}).\n\n", + "markdown": "---\ntitle: Package foundations\nsubtitle: R package development workshop
Module 1\nauthor: Forwards teaching team\nformat: forwardspres-revealjs\n---\n\n## Overview\n\n- Why write a package?\n- Package structure and state\n- Package development setup\n- Creating a package with a working function\n\n# Why write a package? {.inverse}\n\n## Why write a package?\n\n- You want to **generalise** code\n- You want to **document** code\n- You want to **test** code\n- You want to **share** code\n- You want to create **impact** from your work\n\n## Script vs package\n\n| R script | Package |\n|--------------------------------|-------------------------------------|\n| One-off data analysis | Provides reusable components |\n| Defined by `.R` extension | Defined by presence of `DESCRIPTION` file |\n| `library()` calls | Imports defined in `NAMESPACE` file |\n| Documentation in `#` comments | Documentation in files and `Roxygen` comments |\n| Run lines or source file | Install and restart |\n\n::: {.notes}\nreusable components: typically functions, but could also be R markdown template, Shiny app, data\n:::\n\n# Package structure and state {.inverse}\n\n## Package structure\n\nAn R package is developed as a directory of source code files.\n\nThe names of files and directories must follow the specification laid out in \nthe [Writing R Extensions](https://cran.r-project.org/doc/manuals/r-release/R-exts.html) manual - we'll cover the main components in this workshop.\n\nDirectory tree for an example RStudio package project:\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output .cell-output-stdout}\n\n```\nmypackage\n├── DESCRIPTION\n├── NAMESPACE\n└── mypackage.Rproj\n```\n\n\n:::\n:::\n\n\n## Package states\n\n:::::: {.columns}\n\n::::: {.column width=\"25%\"}\n- ::::{.larger125}\n :::{.callout-note}\n ## source\n :::\n ::::\n- bundled\n- binary\n- installed\n- in-memory\n:::::\n\n::::: {.column width=\"70%\"}\n:::{.callout-note}\n## source\nWhat you create and work on.\n\nSpecific directory structure with some particular components e.g., `DESCRIPTION`, an `/R` directory.\n:::\n:::::\n::::::\n\n## Package states\n\n:::::: {.columns}\n\n::::: {.column width=\"25%\"}\n- source\n- ::::{.larger125}\n :::{.callout-note}\n ## bundled\n :::\n ::::\n- binary\n- installed\n- in-memory\n:::::\n\n::::: {.column width=\"70%\"}\n:::{.callout-note}\n## bundled\nPackage files compressed to single `.tar.gz` file.\n\nAlso known as \"source tarballs\".\n\nCreated by command line tool `R CMD build`\n\nUnpacked it looks very like the source package.\n:::\n:::::\n::::::\n\n::: {.notes}\nIn the rare case that you need to make a bundle from a package you’re developing locally, use devtools::build(). Under the hood, this calls pkgbuild::build() and, ultimately, R CMD build, which is described further in the Building package tarballs section of Writing R Extensions.\n:::\n\n## Package states\n\n:::::: {.columns}\n\n::::: {.column width=\"25%\"}\n- source\n- bundled\n- ::::{.larger125}\n :::{.callout-note}\n ## binary\n :::\n ::::\n- installed\n- in-memory\n:::::\n\n::::: {.column width=\"70%\"}\n:::{.callout-note}\n## binary\nCompressed copy of the package in installed form.\n\nAlso a single file.\n\nPlatform specific: `.tgz` (Mac) `.zip` (Windows).\n\nPackage developers submit a bundle to CRAN; CRAN makes and distributes binaries.\n:::\n:::::\n::::::\n\n::: {.notes}\n\nA package in binary form is Platform specific.\nIf you write a package for distribution on CRAN, you submit a bundle to CRAN then CRAN makes and distributes binaries\n\n\n`install.packages()` is usually downloading the binary\n\nTo understand the difference between package bundle and a package binary see \n:::\n\n## Package states\n\n:::::: {.columns}\n\n::::: {.column width=\"25%\"}\n- source\n- bundled\n- binary\n- ::::{.larger125}\n :::{.callout-note}\n ## installed\n :::\n ::::\n- in-memory\n:::::\n\n::::: {.column width=\"70%\"}\n:::{.callout-note}\n## installed\nA directory of files in a library directory.\n\nAny C/C++/Fortran code is in compiled form.\n\nHelp files, code and optionally data are in database form.\n\n`install.packages()` can install from source or from a binary\n:::\n:::::\n::::::\n\n## Package states\n\n:::::: {.columns}\n\n::::: {.column width=\"25%\"}\n- source\n- bundled\n- binary\n- installed \n- ::::{.larger125}\n :::{.callout-note}\n ## in-memory\n :::\n ::::\n:::::\n\n::::: {.column width=\"70%\"}\n:::{.callout-note}\n## in-memory\nIf a package is installed, `library()` makes its function available by loading the package into memory and attaching it to the search path.\n:::\n:::::\n::::::\n\n## Building/Installing Packages from Source\n\nThere are various reasons we may wish to build or install from source:\n\n- Installing a CRAN package where a binary has not yet been built for the latest version.\n- Installing a package from GitHub/other version-controlled source code repository, e.g.\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nremotes::install_github(\"r-lib/revdepcheck\")\n```\n:::\n\n- Installing our own package from the source code or building it to submit to CRAN.\n\nIf the package includes C/C++/Fortran code, we will need a suitable compiler.\n\n# Build tools {.inverse}\n\n::: {.notes}\nCovered in the course prerequisites and in Module 6 on R with C++, but put here for completeness \n:::\n\n## Linux\n\nDebian/Ubuntu:\n\n```{.sh}\napt-get update\napt-get install r-base-dev\n```\n\nFedora/RedHat: should be set up already.\n\n## MacOS\n\nOption 1\n\n- [Register as an Apple developer (for free)](https://developer.apple.com/programs/register/)\n - Then, in the terminal:\n \n ```{.sh}\n xcode-select --install\n ```\n\nOption 2 \n\n- Install the current release of full [Xcode from the Mac App Store](https://itunes.apple.com/ca/app/xcode/id497799835?mt=12)\n- More convenient but installs a lot you don't need\n\n## Windows\n\n- Download the Rtools installer that matches your version of R from \n- Run the installer, `Rtools.exe`, keeping the default settings.\n\n# Package development setup {.inverse}\n\n## The setup we'll be using\n\nWe'll be using the following tools for package development:\n\n- RStudio: to manage and edit the package source code\n - Feel free to use any IDE you're comfortable with\n- git + GitHub: to version control the package source code\n - See if unfamiliar\n- **devtools** and **usethis** R packages: \n - **devtools** for functions supporting the development workflow\n - **usethis** for setup tasks\n - **devtools** depends on **usethis** package\n - Integrated with RStudio: projects, menu items/shortcuts\n - Uses system utilities internally: `R CMD` utilities bundled with R\n \n## Follow along\n\nFor the rest of this session, follow along on your own computer to make sure you're set up for package development and to create our example package.\n \n## **devtools** and **usethis**\n\nWe can use **devtools** right away to check our system is setup for package development.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ninstall.packages(\"devtools\")\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nlibrary(devtools)\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nhas_devel()\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\nYour system is ready to build packages!\n```\n\n\n:::\n:::\n\n\nInstalling **devtools** will also install **usethis**. \n\nCheck you have the latest version of **usethis** and update if not:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\npackageVersion(\"usethis\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] '3.2.1'\n```\n\n\n:::\n:::\n\n\n## git sitrep\n\n- Ask for a **sit**uation **rep**ort:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::git_sitrep()\n```\n:::\n\n\n- Check whether you have a PAT (will set up in a couple of slides if not)\n\n ```\n Personal access token for 'https://github.com': ''\n ```\n\n## Configure git\n\n- Check in `git_sitrep` output, under `Git global (user)`, that your user name and email are defined. If not, run\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_git_config(\n user.name = \"YOUR NAME\", # your full name\n user.email = \"name@example.com\" # email associated with GitHub account\n)\n```\n:::\n\n\nTo keep your email private:\n\n- Go to , select “Keep my email address private” and “Block command line pushes that expose my email” options\n- Configure git to use the address provided of the form [ID+username@users.noreply.github.com](mailto:ID+username@users.noreply.github.com)\n\n\n## git vaccinate\n\nIt's also a good idea to vaccinate. This implements best practice by adding files to your global `.gitignore`:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::git_vaccinate() \n```\n:::\n\n\n## Default branch\n\nSet the default branch to \"main\" (globally)\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::git_default_branch_configure(name = \"main\") \n```\n:::\n\n\n## Create a GitHub PAT\n\nThe **usethis** package uses personal access tokens (PAT) to communicate with GitHub.\n\nFirst, make sure you're signed into GitHub. Then run\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::create_github_token()\n```\n:::\n\n\n:::{.smaller90}\n- Add Note describing the computer or use-case (e.g. personal-macbook-pro-2021, project-xyz)\n- Select expiration (recommend default 30 days)\n- Check scope (default selection is fine)\n- Click 'Generate Token'\n- **Important!** Copy token to clipboard, do not close window until stored (see next slide)!\n- You may want to store token in a secure vault, like 1Password or BitWarden\n:::\n\n## Store your PAT\n\nPut your PAT into the Git credential store by running the following command and entering your copied PAT at the prompt (assume the PAT is on your clipboard).\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ngitcreds::gitcreds_set()\n```\n:::\n\n\n- If you don't have a PAT stored, will prompt you to enter: paste!\n- If you do, you will be given a choice to keep/replace/see the password\n - choose as appropriate\n - if replacing, paste!\n- run `git_sitrep()` again to check the PAT has been discovered \n\n## More on usethis and GitHub creds\n\nIt is well worth reading (and following all the instructions) in the following two **usethis** vignettes:\n\n- [usethis setup](https://usethis.r-lib.org/articles/usethis-setup.html)\n- [Managing Git(Hub) Credentials](https://usethis.r-lib.org/articles/git-credentials.html)\n - See in particular the section on [ongoing PAT maintenance](https://usethis.r-lib.org/articles/git-credentials.html#ongoing-pat-maintenance)\n\n# Create a package! {.inverse}\n\n## Package name\n\nCan only contain the characters [A-Z, a-z, 0-9, .]. Some tips:\n\n- Unique name you can easily Google\n- Avoid mixing upper and lower case\n- Use abbreviations\n- Add an r to make unique, e.g **stringr**\n- Use wordplay, e.g. **lubridate**\n- Avoid trademarked names\n- Use the [**available**](https://r-lib.github.io/available/) package to check name not taken\n\nFor now, we will use **animalsounds**\n\n## Create a package!\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::create_package(\"~/Desktop/animalsounds\")\n```\n:::\n\n\n- Be deliberate about where you create your package.\n\n- Do not nest inside another RStudio project, R package or git repo.\n\n- You may want to use a different path!\n\n## `create_package()`\n\nWhat happens when we run `create_package()`?\n\n- R will create a folder called `animalsounds` which is a package and an RStudio project\n- restart R in the new project\n- create the some infrastructure for your package with the minimal components for a working package\n- start the RStudio Build pane\n\n## R Studio Build pane/menu\n\n:::: {.columns}\n\n::: {.column width=\"50%\"}\n![](images/rstudio_build_pane.png)\n:::\n\n::: {.column width=\"50%\"}\n![](images/rstudio_build_menu.png)\n:::\n\n::::\n\n## Minimal components\n\n**usethis** will create the minimal components of a package that we have already seen:\n\n- `DESCRIPTION` provides metadata about your package. \n- `NAMESPACE` declares the functions your package exports for external use and the external functions your package imports from other packages.\n- The `/R` directory is where we will put `.R` files with function definitions.\n\n## Auxiliary files\n\n**usethis** also adds some auxiliary files:\n\n- `animalsounds.Rproj` is the file that makes this directory an RStudio Project.\n- `.Rbuildignore` lists files that we need but that should not be included when building the R package from source.\n- `.gitignore` anticipates Git usage and ignores some standard, behind-the-scenes files created by R and RStudio.\n\n# git and GitHub {.inverse}\n\n::: {.notes}\nNow will make our package a version controlled project on our local machine and then put it on GitHub.\n\n - version control, best-practice open-source development \n\nusethis has functions to help with this. \n:::\n\n## Use git\n\nTo make our project a git repository, or 'repo', on our local machine we use `usethis::use_git()`\n\nMake your package a git repo:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_git()\n```\n:::\n\n\n## `use_git()` output (part 1)\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n ✔ Initialising Git repo\n ✔ Adding '.Rhistory', '.Rdata', '.httr-oauth', '.DS_Store' to '.gitignore' \n There are 5 uncommitted files:\n * '.gitignore'\n * '.Rbuildignore'\n * 'DESCRIPTION'\n * 'animalsounds.Rproj'\n * 'NAMESPACE'\n Is it ok to commit them?\n\n 1: I agree\n 2: Absolutely not\n 3: No way\n```\n:::\n\n\nChoose the affirmative option! (The exact options may vary.)\n\n## `use_git()` output (part 2)\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n√ Adding files\n√ Commit with message 'Initial commit'\n• A restart of RStudio is required to activate the Git pane\nRestart now?\n\n1: Nope\n2: Definitely\n3: No\n```\n:::\n\n\nChoose the affirmative option! (The exact options may vary.)\n\n::: {.notes}\nYou should find the git pane has opened.\n:::\n\n## Use GitHub\n\nTo create a copy on GitHub we use `usethis::use_github()`.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_github() # creates a public repo\nusethis::use_github(private = TRUE) # private repo (licensing, no Pages)\n```\n:::\n\n\nThis takes a local project, creates an associated repo on GitHub, adds it to your local repo as the \"origin remote\", and makes an initial \"push\" to synchronize.\n\n::: {.notes}\nPromote private repo for novel package development - link to licensing issue, covered in package documentation session \n:::\n\n## `use_github()` output\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_github()\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n ✔ Creating GitHub repository 'forwards/animalsounds'\n ✔ Setting remote 'origin' to 'https://github.com/forwards/animalsounds.git'\n ✔ Setting URL field in DESCRIPTION to 'https://github.com/forwards/animalsounds'\n ✔ Setting BugReports field in DESCRIPTION to 'https://github.com/forwards/animalsounds/issues'\n There is 1 uncommitted file:\n * 'DESCRIPTION'\n Is it ok to commit it?\n\n 1: Nope\n 2: For sure\n 3: No way\n```\n:::\n\n\nChoose the affirmative option! (The exact options may vary.)\n\nNote that you will see `/animalsounds` instead of `forwards/animalsounds`.\n\n::: {.notes}\nTake a look at the repo on GitHub. There is no `/R` folder as that folder is empty at the moment!\n\nDuring the demo, will need to run `use_github(\"forwards\")` as by default use_github goes to my personal repo\n:::\n\n## Adding functions\n\nFunctions go in an `.R` file in the `/R` directory.\n\nThere's a `usethis` helper for adding `.R` files!\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_r(\"file_name\") \n```\n:::\n\n\n`usethis::use_r()` adds the file extension (you don't need to).\n\n. . .\n\nUse a separate `.R` file for each function or closely related set of functions, e.g.\n\n- a top-level function and the internal functions it calls\n- a family of related functions \n- a summary method and its print method\n\n## `usethis::use_r()`\n\nCreate a new R file in your package called `animal_sounds.R`\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_r(\"animal_sounds\")\n```\n:::\n\n\nThe output includes:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n• Modify 'R/animal_sounds.R' \n```\n:::\n\n\n## Add a function\n\nPut the following toy function into your script:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nanimal_sounds <- function(animal, sound) {\n stopifnot(is.character(animal) & length(animal) == 1)\n stopifnot(is.character(sound) & length(sound) == 1)\n paste0(\"The \", animal, \" goes \", sound, \"!\")\n}\n```\n:::\n\n\nDon't try to use the function yet!\n\n## Development workflow\n\nIn a normal script, you might use:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nsource(\"R/animal_sounds.R\")\n```\n:::\n\n\nHowever, for building packages, we need to use the `devtools` approach.\n\nThis will ensure our function has the correct namespace and can find internal functions, functions imported by our package from other packages, etc.\n\n## Development workflow\n\n![](images/dev_cycle_before_testing.png){fig-align=\"center\"}\n\n:::{.center-h}\nYou don't even need to save your code!\n:::\n\n::: {.notes}\ndevtools::load_all() simulates package installation so that you can test your function.\n:::\n\n## Your turn\n\n1. Load all with `devtools::load_all()` and try calling the `animal_sounds()` function with appropriate values for `animal` and `sound`.\n2. Change some tiny thing about your function -- maybe the animal “says” instead \nof “goes”.\n3. Load all with `devtools::load_all()` and try calling the updated function to see the changed behaviour.\n4. Add `animal_sounds.R` so that it is tracked by git. Make a commit with the message `Add animal_sounds()`.\n5. Push all your commits from this session.\n\n## Installing the package\n\n`devtools::load_all()` roughly simulates what happens when a package is installed and loaded with `library()`.\n\nTo actually install the package:\n\n- locally: `devtools::install()`\n\n- from GitHub: `remotes::install_github(\"USERNAME/REPO\")` or `pak::pak(\"USERNAME/REPO\")`\n\n# Minute cards {.inverse}\n\n- [Cohort 1](https://tally.so/r/ODdWrY)\n- [Cohort 2](https://tally.so/r/Zj15YB)\n\n# End matter {.inverse}\n\n## References\n\nWickham, H and Bryan, J, _R Packages_ (2nd edn, in progress), .\n\nR Core Team, _Writing R Extensions_, \n\n## License\n\nLicensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License ([CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/){target=\"_blank\"}).\n\n", "supporting": [], "filters": [ "rmarkdown/pagebreak.lua" diff --git a/_freeze/slides/02-documentation/index/execute-results/html.json b/_freeze/slides/02-documentation/index/execute-results/html.json index 9b95d69..66eab7d 100644 --- a/_freeze/slides/02-documentation/index/execute-results/html.json +++ b/_freeze/slides/02-documentation/index/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "d2f2ab63876c689c67a6f70526b09eea", + "hash": "4671c01f0c5859bda5930a6caeab4a96", "result": { "engine": "knitr", - "markdown": "---\ntitle: Function documentation and dependencies\nsubtitle: R package development workshop
Module 2\nauthor: Forwards teaching team\nformat: forwardspres-revealjs\n---\n\n## Overview\n\n- Documenting functions with roxygen2\n- NAMESPACE: exporting functions\n- NAMESPACE: importing functions\n\n# Function documentation with roxygen2 {.inverse}\n\n## roxygen2\n\nThe **roxygen2** package generates documentation from specially formatted comments, that we write above the function code, e.g.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' @param x A numeric vector.\n```\n:::\n\n\n- `#'` is a roxygen comment.\n\n- `@param` is a roxygen tag.\n\n- The `@param` tag takes an argument: the name of the parameter\n\n- The remaining text (until the next tag in the file) is the documentation relevant to the tag.\n\n## Common tags\n\nThere are four tags you’ll use for most functions:\n\n
\n\nTag | Purpose\n---------- | -------------\n@param arg | Describe inputs\n@examples | Show how the function works\n@return | Describe the return value (not needed if `NULL`)\n@export | Add this tag if the function should be user-visible\n\n
\nUsual RStudio shortcuts work in the @examples section, allowing you to run code interactively.\n\n::: {.notes}\nOther important ones:\n@seealso | Pointers to related functions\n@references\n@importFRom\n@method\n@note\n@rdname\n@keywords internal\n@format (data)\n@section\n:::\n\n## The description block\n\nThe roxygen comment should start with a description block.\n\n- First sentence is the **title**.\n- Next paragraph is the **description**.\n- Everything else is the **details** (optional).\n\n```\n#' Title in Title Case of up to 65 Characters\n#'\n#' Mandatory description of what the function does. \n#' Should be a short paragraph of a few lines only.\n#'\n#' The details section is optional and may be several paragraphs. It can even\n#' contain sub-sections (not illustrated here).\n```\n\n## RStudio helps you get started\n\nPut your cursor inside a function, then select 'Insert Roxygen Skeleton' from the Code menu.\n\n:::: {.columns}\n\n::: {.column width=\"50%\"}\n![](images/insert_roxygen_skeleton.png)\n:::\n\n::: {.column width=\"50%\"}\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' Title\n#'\n#' @param animal\n#' @param sound\n#'\n#' @return\n#' @export\n#'\n#' @examples\nanimal_sounds <- function(animal, sound) {\n stopifnot(is.character(animal) & length(animal) == 1)\n stopifnot(is.character(sound) & length(sound) == 1)\n paste0(\"The \", animal, \" goes \", sound, \"!\")\n}\n```\n:::\n\n:::\n\n::::\n\n## Example roxygen documentation\n\n```{.r}\n#' Sort a Numeric Vector in Decreasing Order\n#'\n#' Sort a numeric vector so that the values are in deceasing order. \n#' Missing values are optionally removed or put last.\n#'\n#' @param x A numeric vector.\n#' @param na.rm A logical value indicating whether to remove missing values\n#' before sorting.\n#' @return A vector with the values sorted in descreasing order.\n#' @export\n#'\n#' @examples\n#' x <- c(3, 7, 2, NA)\n#' high_to_low(x)\n#' high_to_low(x, na.rm = TRUE)\n```\n\n## R documentation file\n\nroxygen2 converts the roxygen block to an `.Rd` file in the `/man` directory\n\n```\n% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/high_to_low.R\n\\name{high_to_low}\n\\alias{high_to_low}\n\\title{Sort a Numeric Vector in Decreasing Order}\n\\usage{\nhigh_to_low(x, na.rm = FALSE)\n}\n\\arguments{\n\\item{x}{A numeric vector.}\n\\item{na.rm}{A logical value indicating whether to remove missing values\nbefore sorting.}\n}\n\\value{\n...\n```\n\n::: {.notes}\nThis is in a toy package, **sortnum**, created for the R Forwards package development workshops:\n\nhttps://github.com/forwards/workshops/tree/master/sortnum\n:::\n\n## HTML file\n\nWhen the package is installed, the `.Rd` is converted by R to HTML on demand\n\n![](images/high_to_low.png){fig-align=\"center\"}\n\n## Regular documentation workflow\n\n![](images/documentation_workflow.png){fig-align=\"center\"}\n\n:::{.center-h}\nYou must have loaded the package with `load_all()` at least once.\n:::\n\n# NAMESPACE: exports {.inverse}\n\n## A namespace splits functions into two classes\n\n
\n\nInternal | External\n--------------------------- | -------------\nOnly for use within package | For use by others\nDocumentation optional | Must be documented\nEasily changed | Changing will break other people’s code\n\n\n## Default NAMESPACE\n\n- It is best to export functions explicitly\n- The `NAMESPACE` file as created by `usethis::create_package()` does *not* export anything by default.\n\n. . .\n\n:::{.callout-warning}\nA package created from the RStudio menus via File > New Project > New Directory > R Package creates a `NAMESPACE` that exports everything by default, with `exportPattern(\"^[[:alpha:]]+\")`\n\nThis is a good reason *not* to do this: always call `usethis::create_package()` to create a package.\n\nFor similar reasons, also avoid `package.skeleton()`.\n:::\n\n## Exporting functions\n\n```r\n#' @export\nfun1 <- function(...) {}\n```\n\nWhen we call `devtools::document()`, an `export()` directive will be added to\nNAMESPACE for each function that has an `#' @export` comment.\n\n```\n# Generated by roxygen2: do not edit by hand\n\nexport(fun1)\n```\n\n\n\n## What to export\n\nOnly export functions that you want your package users to use, i.e. those that are relevant to the purpose of the package.\n\nDon't export internal helpers, e.g.\n\n```r\n# Defaults for NULL values\n`%||%` <- function(a, b) if (is.null(a)) b else a\n\n# Remove NULLs from a list\ncompact <- function(x) {\n x[!vapply(x, is.null, logical(1))]\n}\n```\n\n. . .\n\n:::{.callout-note}\nUsing the 'Insert Roxygen Skeleton' option adds an `@export` tag.\n:::\n\n## Your turn\n\nFor the `animal_sounds` function:\n\n:::{.smaller90}\n1. Insert a Roxygen skeleton using the RStudio helper. \n2. Create a draft documentation file with `devtools::document()` or `Cmd/Ctrl + Shift + D`. \n3. Click on \"Diff\" in the Git pane and view the changes that have been made.\n4. Preview the HTML help with `?animal_sounds`.\n5. Fill in the Roxygen skeleton for `animal_sounds()`, recreating the documentation\nfile and previewing the HTML help to view your updates.\n6. When you have finished editing, run `devtools::document()` to ensure the\n`.Rd` file is in sync. Make a git commit with your updated `R/animal_sounds.R`\nfile, the updated NAMESPACE, and the new `man/animal_sounds.Rd` file.\n:::\n\n## .Rd Markup\n\n`.Rd` files recognise LaTeX-like mark-up in most text-based fields, e.g.\n\n```\n#' This is a convenience function that is a wrapper around\n#' \\code{\\link{sort.int}}.\n```\n\nDetails can be found in the [Writing R documentation files](https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Writing-R-documentation-files) section of the Writing R Extensions manual.\n\n## Using markdown\n\nMost commonly used mark-up is easier with markdown (and can be mixed with `.Rd` mark-up).\n\n- Text formatting: `**bold**`, `_italic_`, `` `code` ``\n\n- Create links\n\n * To a function in the same package: `[func()]`\n * To a function in a different package: `[pkg::func()]`\n * With different link text, e.g. `[link text][func()]`\n\nFor more details, see the [(R)Markdown support](https://cran.r-project.org/web/packages/roxygen2/vignettes/rd-formatting.html) vignette.\n\n::: {.notes}\nSeems you no longer need to specify `pkg::` - will find the documentation in any installed package. For functions in multiple packages, e.g. `select()` help pane will offer a choice. Probably still best practice to use `pkg::` for disambiguation, though still may not be necessary for base functions\n:::\n\n## Your turn\n\n1. Add some details to the help page for `animal_sounds()`, with a link to `paste0()` and some markdown syntax.\n2. Add a link to a function from a package you don't have installed (perhaps `basemodels::dummy_classifier()`). \n3. Run `devtools::document()` and check the link in the help page. What happens?\n4. Run `devtools::check()`. Does the link cause problems?\n5. Delete the link to the package you don't have installed and run `devtools::document()` again.\n4. Commit all your changes to the git repo.\n\n::: {.notes}\nIn 3, the link will still be created (and will work if you subsequently install the missing package). It's a good idea to have the package installed so the link can be checked during R CMD check, otherwise you will get a NOTE.\n\n**basemodels** was added to CRAN the week before the workshop was taught, so very likely to be the case that no-one present has it installed.\n:::\n\n# Dependencies {.inverse}\n\n## Dependencies\n\nDependencies are other R packages that our package uses. There are three types of dependency:\n\n**Imports**: required packages, will be installed when our package is installed\nif they are not already installed.\n\n**Suggests**: optional packages, e.g. only used for development; only used in\ndocumentation. *Not* installed automatically with our package.\n\n**Depends**: essentially deprecated for packages, may be used to specify a\nminimum required version of R (i.e., version of the core packages).\n\n## Imported packages\n\nIn DESCRIPTION\n\n```\nImports: \n pkgname1\n pkgname2\n```\n\nUse `::` to access functions\n```r\nnew_function <- function(x, y, z) {\n w <- pkgname1::imported_function(x, y)\n pkgname2::imported_function(w, z)\n}\n```\n\n## Suggested packages\n\nIn DESCRIPTION\n\n```\nSuggests: \n pkgname\n```\n\nIn package functions or examples, handle the case where **pkgname** is not available:\n\n```r\nif (!requireNamespace(\"pkgname\", quietly = TRUE)){\n warning(\"pkgname must be installed to perform this function\",\n \"returning NULL\")\n return(NULL)\n}\n```\n\n::: {.notes}\nAlso explain conditional use in vignettes later\n\nMaybe talk about rlang here as well\n:::\n\n## `use_package()`\n\n`use_package()` will modify the DESCRIPTION and remind you how to use the function.\n\nBy default, packages will be added to \"Imports\".\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_package(\"rlang\")\nusethis::use_package(\"glue\", type = \"Suggests\")\n```\n:::\n\n\n# NAMESPACE: imports {.inverse}\n\n## You might get tired of using `::` all the time\n\nOr you might want to use an infix function\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n`%-=%` <- roperators::`%-=%`\n\ncenter <- function(df, select) {\n for (nm in names(df[select])){\n # overwrite column after subtracting the mean \n df[[nm]] %-=% mean(df[[nm]])\n }\n df\n}\n```\n:::\n\n\n## Aside: `%>%` vs `|>`\n\nThe native pipe, `|>`, has been available in R since 4.1.0, \nand we now prefer this to importing the `%>%` pipe from **magrittr**.\n\nThere are then two options:\n\n- Depend on R >= 4.1.0, with this in DESCRIPTION:\n\n```\nDepends:\n R (>= 4.1.0)\n```\n\n- Use the methods outlined in [this tidyverse blog post](https://www.tidyverse.org/blog/2023/04/base-vs-magrittr-pipe/#using-the-native-pipe-in-packages) to ensure the package can still work with older versions of R.\n\n## You can `import` functions into the package\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' @importFrom purrr keep modify\n\ncol_summary <- function(df, fun) {\n stopifnot(is.data.frame(df))\n\n df |>\n keep(is.numeric) |>\n modify(fun)\n}\n```\n:::\n\n\n`devtools::document()` will add corresponding `import()` statements to the NAMESPACE, e.g. `import(purr, keep, modify)`.\n\nAdding formal imports is slightly more efficient than using `::`.\n\nHere, the `@importFrom` tag is placed above the function in which the imported function is used.\n\n\n## Package-level import file\n\nImports belong to the package, not to individual functions, so alternatively you can recognise this by storing them in a central location, e.g. `R/animalsounds-package.R`\n\n```r\n#' @importFrom purrr keep modify\n#' @importFrom roperators %-=%\nNULL\n```\n\n::: {.notes}\nThis removes the possibility of multiple (redundant) imports of the same function. But harder to remember to remove import if function changes! It's a matter of personal taste.\n:::\n\n## `usethis::use_import_from()`\n\nThere can be several steps to importing a function. `usethis::use_import_from()` takes care of all of them.\n\nIt will first create the package documentation file `R/animalsounds-package.R` (if it doesn't already exist -- you will also need to agree to this).\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_import_from(\"purrr\", c(\"keep\", \"modify\"))\n```\n:::\n\n\n```\n✔ Adding 'purrr' to Imports field in DESCRIPTION\n✔ Adding '@importFrom purrr keep', '@importFrom purrr modify' to 'R/animalsounds-package.R'\n✔ Writing 'NAMESPACE'\n✔ Loading animalsounds\n```\n\n::: {.notes}\nuse_import_from() is opinionated in implementing package-level import (rather than above the function in which they are used).\n\nMay need to close and reopen R/animalsounds-package.R to see the changes. \n:::\n\n## It may be tempting to import a whole package...\n\n```r\n#' @import purrr\ncol_summary <- function(df, fun) {\n stopifnot(is.data.frame(df))\n\n df |>\n keep(is.numeric) |>\n map_dfc(fun)\n}\n\n```\n\n## ...but it is dangerous\n\n```r\n#' @import pkg1\n#' @import pkg2\nfun <- function(x) {\n fun1(x) + fun2(x)\n}\n\n```\n\nWorks today...\n\n... but next year, what if **pkg2** adds a `fun1` function?\n\n## Metapackages\n\nIt is bad practice to import \"metapackages\" (i.e. packages that are collections of packages), such as **tidyverse**. \n\nSee the blog post for more details.\n\n## Documenting dependencies\n\n
\n\nDESCRIPTION | NAMESPACE\n--------------------------- | -------------\nMakes **package** available | Makes function available\nMandatory | Optional (can use :: instead)\n`use_package()` | `use_import_from()`\n\n## Example: rlang and cli\n\nCurrently we are using `stopifnot()` for argument validation\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nstopifnot(is.character(animal) & length(animal) == 1)\nstopifnot(is.character(sound) & length(sound) == 1)\n```\n:::\n\n\nWe might instead use `rlang::is_character()` with `cli::cli_abort()`\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nsound <- c(\"woof\", \"bark\")\n\nif (!rlang::is_character(sound, n = 1)) {\n cli::cli_abort(\"`sound` must be a single string!\")\n}\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError:\n! `sound` must be a single string!\n```\n\n\n:::\n:::\n\n\n::: {.notes}\nin `is_character`, n tests for length of the vector.\n\ncli::cli_abort() has some really nice capability\n- glue interpolation (next slide)\n- inline classes (next slide)\n- features that make it easier to write tests (covered later in this course)\n\nhttps://cli.r-lib.org/reference/inline-markup.html\n\n:::\n\n## Aside: informative messages with `cli`\n\n**cli** functions can combine glue interpolation and inline classes to produce informative, nicely-formatted error messages.\n\nIn `animal_sounds()` we can use\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ncli::cli_abort(\n c(\"{.var animal} must be a single string!\",\n \"i\" = \"It was {.type {animal}} of length {length(animal)} instead.\")\n)\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n:::\n\n\nThis gives the error message\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nanimal_sounds(c(\"dog\", \"cat\"), c(\"woof\", \"miaow\"))\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in `animal_sounds()`:\n! `animal` must be a single string!\nℹ It was a character vector of length 2 instead.\n```\n\n\n:::\n:::\n\n\n::: {.notes}\nInformative error messages will make using your package a much nicer experience for you and others. \n:::\n\n## Your turn\n\n1. Use `use_package()` to add `rlang` and `cli` to `Imports`.\n2. Update `animal_sounds()` to use `is_character()` to check the arguments and `cli_abort` to throw an informative error if necessary, using `::` to fully qualify the function calls.\n3. Load all and try giving `animal_sounds()` invalid inputs for animal and/or sound.\n4. Commit your changes to git.\n5. Push your commits for this session.\n\n# End matter {.inverse}\n\n## References\n\nWickham, H and Bryan, J, _R Packages_ (2nd edn, in progress), .\n\nR Core Team, _Writing R Extensions_, \n\n## License\n\nLicensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License ([CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/){target=\"_blank\"}).\n\n", + "markdown": "---\ntitle: Function documentation and dependencies\nsubtitle: R package development workshop
Module 2\nauthor: Forwards teaching team\nformat: forwardspres-revealjs\n---\n\n## Overview\n\n- Documenting functions with roxygen2\n- NAMESPACE: exporting functions\n- Dependencies\n- NAMESPACE: importing functions\n\n# Function documentation with roxygen2 {.inverse}\n\n## roxygen2\n\nThe [**roxygen2**](https://roxygen2.r-lib.org/index.html) package generates documentation from specially formatted comments.\n\ncomments -> `.Rd files` -> HTML\n\nThis powers R's help system, e.g. `?function` and package websites.\n\n**roxygen2** 8.0.0 was released May 2026, with over 100 improvements and bug fixes.\n\n[This blog post](https://opensource.posit.co/blog/2026-05-01_roxygen2-8-0-0/) outlines the major changes.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\npackageVersion(\"roxygen2\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] '8.0.0'\n```\n\n\n:::\n:::\n\n\n## roxygen2 comments\n\nWe write comments above the function code, e.g.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' @param x A numeric vector.\n```\n:::\n\n\n- `#'` is a roxygen comment.\n\n- `@param` is a roxygen tag.\n\n- The `@param` tag takes an argument: the name of the parameter\n\n- The remaining text (until the next tag in the file) is the documentation relevant to the tag.\n\n## Common tags\n\nThere are four tags you’ll use for most functions:\n\n
\n\nTag | Purpose\n---------- | -------------\n@param arg | Describe inputs\n@examples | Show how the function works\n@return | Describe the return value (not needed if `NULL`)\n@export | Add this tag if the function should be user-visible\n\n
\nUsual RStudio shortcuts work in the @examples section, allowing you to run code interactively.\n\n::: {.notes}\nOther important ones:\n@seealso | Pointers to related functions\n@references\n@importFRom\n@method\n@note\n@rdname\n@keywords internal\n@format (data)\n@section\n:::\n\n## The description block\n\nThe roxygen comment should start with a description block.\n\n- First sentence is the **title**.\n- Next paragraph is the **description**.\n- Everything else is the **details** (optional).\n\n```\n#' Title in Title Case of up to 65 Characters\n#'\n#' Mandatory description of what the function does. \n#' Should be a short paragraph of a few lines only.\n#'\n#' The details section is optional and may be several paragraphs. It can even\n#' contain sub-sections (not illustrated here).\n```\n\n## RStudio helps you get started\n\nPut your cursor inside a function, then select 'Insert Roxygen Skeleton' from the Code menu.\n\n:::: {.columns}\n\n::: {.column width=\"50%\"}\n![](images/insert_roxygen_skeleton.png)\n:::\n\n::: {.column width=\"50%\"}\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' Title\n#'\n#' @param animal\n#' @param sound\n#'\n#' @return\n#' @export\n#'\n#' @examples\nanimal_sounds <- function(animal, sound) {\n stopifnot(is.character(animal) & length(animal) == 1)\n stopifnot(is.character(sound) & length(sound) == 1)\n paste0(\"The \", animal, \" goes \", sound, \"!\")\n}\n```\n:::\n\n:::\n\n::::\n\n## Example roxygen documentation\n\n```{.r}\n#' Sort a Numeric Vector in Decreasing Order\n#'\n#' Sort a numeric vector so that the values are in deceasing order. \n#' Missing values are optionally removed or put last.\n#'\n#' @param x A numeric vector.\n#' @param na.rm A logical value indicating whether to remove missing values\n#' before sorting.\n#' @return A vector with the values sorted in descreasing order.\n#' @export\n#'\n#' @examples\n#' x <- c(3, 7, 2, NA)\n#' high_to_low(x)\n#' high_to_low(x, na.rm = TRUE)\n```\n\n## R documentation file\n\nroxygen2 converts the roxygen block to an `.Rd` file in the `/man` directory\n\n```\n% Generated by roxygen2: do not edit by hand\n% Please edit documentation in R/high_to_low.R\n\\name{high_to_low}\n\\alias{high_to_low}\n\\title{Sort a Numeric Vector in Decreasing Order}\n\\usage{\nhigh_to_low(x, na.rm = FALSE)\n}\n\\arguments{\n\\item{x}{A numeric vector.}\n\\item{na.rm}{A logical value indicating whether to remove missing values\nbefore sorting.}\n}\n\\value{\n...\n```\n\n::: {.notes}\nThis is in a toy package, **sortnum**, created for the R Forwards package development workshops:\n\nhttps://github.com/forwards/workshops/tree/master/sortnum\n:::\n\n## HTML file\n\nWhen the package is installed, the `.Rd` is converted by R to HTML on demand\n\n![](images/high_to_low.png){fig-align=\"center\"}\n\n## Regular documentation workflow\n\n![](images/documentation_workflow.png){fig-align=\"center\"}\n\n:::{.center-h}\nYou must have loaded the package with `load_all()` at least once.\n:::\n\n# NAMESPACE: exports {.inverse}\n\n## A namespace splits functions into two classes\n\n
\n\nInternal | External\n--------------------------- | -------------\nOnly for use within package | For use by others\nDocumentation optional | Must be documented\nEasily changed | Changing could break other people’s code\n\n\n## Default NAMESPACE\n\n- It is best to export functions explicitly\n- The `NAMESPACE` file as created by `usethis::create_package()` does *not* export anything by default.\n\n. . .\n\n:::{.callout-warning}\nA package created from the RStudio menus via File > New Project > New Directory > R Package creates a `NAMESPACE` that exports everything by default, with `exportPattern(\"^[[:alpha:]]+\")`\n\nThis is a good reason *not* to do this: always call `usethis::create_package()` to create a package.\n\nFor similar reasons, also avoid `package.skeleton()`.\n:::\n\n## Exporting functions\n\n```r\n#' @export\nfun1 <- function(...) {}\n```\n\nWhen we call `devtools::document()`, an `export()` directive will be added to\nNAMESPACE for each function that has an `#' @export` comment.\n\n```\n# Generated by roxygen2: do not edit by hand\n\nexport(fun1)\n```\n\n## What to export\n\nOnly export functions that you want your package users to use, i.e. those that are relevant to the purpose of the package.\n\nDon't export internal helpers, e.g.\n\n```r\n# Defaults for NULL values\n`%||%` <- function(a, b) if (is.null(a)) b else a\n\n# Remove NULLs from a list\ncompact <- function(x) {\n x[!vapply(x, is.null, logical(1))]\n}\n```\n\n. . .\n\n:::{.callout-note}\nUsing the 'Insert Roxygen Skeleton' option adds an `@export` tag.\n:::\n\n## Your turn\n\nFor the `animal_sounds` function:\n\n:::{.smaller90}\n1. Insert a Roxygen skeleton using the RStudio helper. \n2. Create a draft documentation file with `devtools::document()` or `Cmd/Ctrl + Shift + D`. \n3. Click on \"Diff\" in the Git pane and view the changes that have been made.\n4. Preview the HTML help with `?animal_sounds`.\n5. Fill in the Roxygen skeleton for `animal_sounds()`, recreating the documentation\nfile and previewing the HTML help to view your updates.\n6. When you have finished editing, run `devtools::document()` to ensure the\n`.Rd` file is in sync. Make a git commit with your updated `R/animal_sounds.R`\nfile, the updated NAMESPACE, and the new `man/animal_sounds.Rd` file.\n:::\n\n## .Rd Markup\n\n`.Rd` files recognise LaTeX-like mark-up in most text-based fields, e.g.\n\n```\n#' This is a convenience function that is a wrapper around\n#' \\code{\\link{sort.int}}.\n```\n\nDetails can be found in the [Writing R documentation files](https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Writing-R-documentation-files) section of the Writing R Extensions manual.\n\n## Using markdown\n\nMost commonly used mark-up is easier with markdown (and can be mixed with `.Rd` mark-up).\n\n- Text formatting: `**bold**`, `_italic_`, `` `code` ``\n\n- Create links\n\n * To a function in the same package: `[func()]`\n * To a function in a different package: `[pkg::func()]`\n * With different link text, e.g. `[link text][func()]`\n\nFor more details, see the [(R)Markdown support](https://roxygen2.r-lib.org/articles/rd-formatting.html) vignette.\n\n::: {.notes}\nSeems you no longer need to specify `pkg::` - will find the documentation in any installed package. For functions in multiple packages, e.g. `select()` help pane will offer a choice. Probably still best practice to use `pkg::` for disambiguation, though still may not be necessary for base functions\n:::\n\n## Your turn\n\n1. Add some details to the help page for `animal_sounds()`, with a link to `paste0()` and some markdown syntax.\n2. Add a link to a function from a package you don't have installed (perhaps `grumpy::read_npy()`). \n3. Run `devtools::document()` and check the link in the help page. What happens?\n4. Run `devtools::check()`. Does the link cause problems?\n5. Delete the link to the package you don't have installed and run `devtools::document()` again.\n4. Commit all your changes to the git repo.\n\n::: {.notes}\nIn 3, the link will still be created (and will work if you subsequently install the missing package). It's a good idea to have the package installed so the link can be checked during R CMD check, otherwise you will get a NOTE.\n\n**basemodels** was added to CRAN the week before the workshop was taught, so very likely to be the case that no-one present has it installed.\n:::\n\n# Dependencies {.inverse}\n\n## No `library()` calls!\n\nWe **cannot** use `library()` calls in R packages.\n\nWe need another way to access functions in other packages, \nand to ensure that users of our package have those other packages installed on their system too.\n\nTo do this, we can take **dependencies** on other packages.\n\n## Dependencies\n\nDependencies are other R packages that our package uses. There are three types of dependency:\n\n**Imports**: required packages, will be installed when our package is installed\nif they are not already installed.\n\n**Suggests**: optional packages, e.g. only used for development; only used in\ndocumentation. *Not* installed automatically with our package.\n\n**Depends**: essentially deprecated for packages, may be used to specify a\nminimum required version of R (i.e., version of the core packages).\n\n## Imported packages\n\nIn DESCRIPTION\n\n```\nImports: \n pkgname1\n pkgname2\n```\n\nUse `::` to access functions\n```r\nnew_function <- function(x, y, z) {\n w <- pkgname1::imported_function(x, y)\n pkgname2::imported_function(w, z)\n}\n```\n\n## Suggested packages\n\nIn DESCRIPTION\n\n```\nSuggests: \n pkgname\n```\n\nIn package functions or examples, handle the case where **pkgname** is not available:\n\n```r\nif (!requireNamespace(\"pkgname\", quietly = TRUE)){\n warning(\"pkgname must be installed to perform this function\",\n \"returning NULL\")\n return(NULL)\n}\n```\n\n::: {.notes}\nAlso explain conditional use in vignettes later\n\nMaybe talk about rlang here as well\n:::\n\n## `use_package()`\n\n`use_package()` will modify the DESCRIPTION and remind you how to use the function.\n\nBy default, packages will be added to \"Imports\".\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_package(\"cli\")\n```\n:::\n\n\n```\n✔ Adding cli to Imports field in DESCRIPTION.\n☐ Refer to functions with `cli::fun()`.\n```\n. . .\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_package(\"withr\", type = \"Suggests\")\n```\n:::\n\n\n```\n✔ Adding withr to Suggests field in DESCRIPTION.\n☐ Use `requireNamespace(\"withr\", quietly = TRUE)` to test if\n withr is installed.\n☐ Then directly refer to functions with `withr::fun()`.\n```\n\n# NAMESPACE: imports {.inverse}\n\n## Accessing functions in other packages\n\nOnce we have taken a dependency on a *package*, \nwe have a choice about how to access the *functions* in that package:\n\n- directly, with `pkgname::function()`\n- importing the function, then just calling `function()`\n\n## Advantages of `pkgname::function()`\n\n- Makes it really clear to you, and other readers of your code, where functions have come from.\n- Requires no further setup\n- Necessary to dissambiguate when have two functions with the same name in two different packages\n\n## Advantages of importing a function\n\n- Don't need to keep typing `pkgname::` - this can get tedious and make code much more noisy\n- Code is slightly more efficient to run (though only by about 2 microseconds)\n - Difference will only be noticeable if running 100,000+ calls in a tight loop\n- Some packages already have function names that make it clear where they are from, e.g. **stringr** with `str_*()`\n- Necessary for infix operators, e.g. `%>%`\n - N.B. recommendation is now to use native pipe `|>` in packages instead.\n\n. . .\n\nLargely a matter of personal preference. A hybrid approach is also fine!\n\n\n## Aside: `%>%` vs `|>`\n\nThe native pipe, `|>`, has been available in R since 4.1.0, \nand we now prefer this to importing the `%>%` pipe from **magrittr**.\n\nThere are then two options:\n\n- Depend on R >= 4.1.0, with this in DESCRIPTION:\n\n```\nDepends:\n R (>= 4.1.0)\n```\n\n- Use the methods outlined in [this tidyverse blog post](https://www.tidyverse.org/blog/2023/04/base-vs-magrittr-pipe/#using-the-native-pipe-in-packages) to ensure the package can still work with older versions of R.\n\nSee [slides 28-31 here](https://warwick-stats-resources.github.io/r-foundations-2025/slides/02-data-wrangling/index.html#/a-note-about-pipes-vs) for more about the pipes, and [this blog post](https://ivelasq.rbind.io/blog/understanding-the-r-pipe/).\n\n## Importing functions into the package manually\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' @importFrom purrr keep modify\n\ncol_summary <- function(df, fun) {\n stopifnot(is.data.frame(df))\n\n df |>\n keep(is.numeric) |>\n modify(fun)\n}\n```\n:::\n\n\n`devtools::document()` will add corresponding `import()` statements to the NAMESPACE, e.g. `import(purr, keep, modify)`.\n\nHere, the `@importFrom` tag is placed above the function in which the imported function is used.\n\n\n## Package-level import file, manually\n\nImports belong to the package, not to individual functions, so alternatively you can recognise this by storing them in a central location, e.g. `R/animalsounds-package.R`\n\n```r\n#' @importFrom purrr keep modify\n#' @importFrom magrittr %>%\nNULL\n```\n\nThis removes the possibility of multiple (redundant) imports of the same function. \nBut harder to remember to remove import if function changes! It's a matter of personal taste.\n\n## `usethis::use_import_from()`\n\nThere can be several steps to importing a function. `usethis::use_import_from()` takes care of all of them.\n\nThis is a (preferred) alternative to writing `@importFrom` tags manually.\n\nIt will first create the package documentation file `R/animalsounds-package.R` (if it doesn't already exist -- you will also need to agree to this).\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_import_from(\"purrr\", c(\"keep\", \"modify\"))\n```\n:::\n\n\n```\n✔ Adding 'purrr' to Imports field in DESCRIPTION\n✔ Adding '@importFrom purrr keep', '@importFrom purrr modify' to 'R/animalsounds-package.R'\n✔ Writing 'NAMESPACE'\n✔ Loading animalsounds\n```\n\n::: {.notes}\nuse_import_from() is opinionated in implementing package-level import (rather than above the function in which they are used).\n\nMay need to close and reopen R/animalsounds-package.R to see the changes. \n:::\n\n## You *can* import all functions from a package...\n\nIt may be tempting to import all the functions from a package:\n\n```r\n#' @import purrr\ncol_summary <- function(df, fun) {\n stopifnot(is.data.frame(df))\n\n df |>\n keep(is.numeric) |>\n map_dfc(fun)\n}\n\n```\n\n## ... but don't!\n\n**It is dangerous:**\n\n```r\n#' @import pkg1\n#' @import pkg2\nfun <- function(x) {\n fun1(x) + fun2(x)\n}\n```\n\nWorks today...\n\n... but next year, what if **pkg2** adds a `fun1` function?\n\n**And not clear:**\n\nYou lose direct evidence in your package of which functions come from which packages.\n\n## Metapackages\n\nIt is bad practice to import \"metapackages\" (i.e. packages that are collections of packages), such as **tidyverse**. \n\nSee the blog post for more details.\n\n## Documenting dependencies\n\n
\n\nDESCRIPTION | NAMESPACE\n--------------------------- | -------------\nMakes **package** available | Makes **function** available\nMandatory | Optional (can use `::` instead)\n`use_package()` | `use_import_from()`\n\n## Example: rlang and cli\n\nCurrently we are using `stopifnot()` for argument validation\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nstopifnot(is.character(animal) & length(animal) == 1)\nstopifnot(is.character(sound) & length(sound) == 1)\n```\n:::\n\n\nWe might instead use `rlang::is_character()` with `cli::cli_abort()`\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nsound <- c(\"woof\", \"bark\")\n\nif (!rlang::is_character(sound, n = 1)) {\n cli::cli_abort(\"`sound` must be a single string!\")\n}\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError:\n! `sound` must be a single string!\n```\n\n\n:::\n:::\n\n\n::: {.notes}\nin `is_character`, n tests for length of the vector.\n\ncli::cli_abort() has some really nice capability\n- glue interpolation (next slide)\n- inline classes (next slide)\n- features that make it easier to write tests (covered later in this course)\n\nhttps://cli.r-lib.org/reference/inline-markup.html\n\n:::\n\n## Aside: informative messages with `cli`\n\n**cli** functions can combine glue interpolation and inline classes to produce informative, nicely-formatted error messages.\n\nIn `animal_sounds()` we can use\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ncli::cli_abort(\n c(\"{.var animal} must be a single string!\",\n \"i\" = \"It was {.type {animal}} of length {length(animal)} instead.\")\n)\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n:::\n\n\nThis gives the error message\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nanimal_sounds(c(\"dog\", \"cat\"), c(\"woof\", \"miaow\"))\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError in `animal_sounds()`:\n! `animal` must be a single string!\nℹ It was a character vector of length 2 instead.\n```\n\n\n:::\n:::\n\n\n::: {.notes}\nInformative error messages will make using your package a much nicer experience for you and others. \n:::\n\n## Your turn\n\n1. Use `use_package()` to add `rlang` and `cli` to `Imports`.\n2. Update `animal_sounds()` to use `is_character()` to check the arguments and `cli_abort` to throw an informative error if necessary, using `::` to fully qualify the function calls.\n3. Load all and try giving `animal_sounds()` invalid inputs for animal and/or sound.\n4. Commit your changes to git.\n5. Push your commits for this session.\n\n## Best practice: helper functions\n\nWe've essentially repeated code to check the `animal` and `sound` arguments. Better practice is to use a helper function.\n\nTry the following:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ncheck_arg <- function(arg, n = 1) {\n if (!rlang::is_character(arg, n = n)) {\n cli::cli_abort(\n c(\"{.var arg} must be a single string!\",\n \"i\" = \"It was {.type {arg}} of length {length(arg)} instead.\")\n )\n }\n}\n```\n:::\n\n\n## Improvement 1: actual arg name\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ncheck_arg <- function(arg, n = 1) {\n if (!rlang::is_character(arg, n = n)) {\n cli::cli_abort(\n c(\n \"{.var {rlang::caller_arg(arg)}} must be a single string!\",\n \"i\" = \"It was {.type {arg}} of length {length(arg)} instead.\"\n )\n )\n }\n}\n```\n:::\n\n\n## Improvement 2: calling function\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ncheck_arg <- function(arg, n = 1) {\n if (!rlang::is_character(arg, n = n)) {\n cli::cli_abort(\n c(\n \"{.var {rlang::caller_arg(arg)}} must be a single string!\",\n \"i\" = \"It was {.type {arg}} of length {length(arg)} instead.\"\n ),\n call = rlang::caller_env()\n )\n }\n}\n```\n:::\n\n\n## Improvement 3: incorporating `n`\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ncheck_arg <- function(arg, n = 1) {\n if (!rlang::is_character(arg, n = n)) {\n cli::cli_abort(\n c(\n \"{.var {rlang::caller_arg(arg)}} must be a character vector of length {n}!\",\n \"i\" = \"It was {.type {arg}} of length {length(arg)} instead.\"\n ),\n call = rlang::caller_env()\n )\n }\n}\n```\n:::\n\n\n\n# Minute cards {.inverse}\n\n- [Cohort 1](https://tally.so/r/A74PDz)\n- [Cohort 2](https://tally.so/r/q4JZW9)\n\n# End matter {.inverse}\n\n## References\n\nWickham, H and Bryan, J, _R Packages_ (2nd edn, in progress), .\n\nR Core Team, _Writing R Extensions_, \n\n## License\n\nLicensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License ([CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/){target=\"_blank\"}).\n\n", "supporting": [], "filters": [ "rmarkdown/pagebreak.lua" diff --git a/_freeze/slides/03-data-testing/index/execute-results/html.json b/_freeze/slides/03-data-testing/index/execute-results/html.json index ee27b5e..c1d6b0a 100644 --- a/_freeze/slides/03-data-testing/index/execute-results/html.json +++ b/_freeze/slides/03-data-testing/index/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "45d045b0a44aaf0f26f99af87efd1d2c", + "hash": "d036baf8b48dfa4a2d01cf5485fdb54b", "result": { "engine": "knitr", - "markdown": "---\ntitle: Testing\nsubtitle: R package development workshop
Module 3\nauthor: Forwards teaching team\nformat: forwardspres-revealjs\n---\n\n## Overview\n\n- Packaging data\n- Unit testing with **testthat**\n- Test driven development\n\n# Packaging data {.inverse}\n\n## Including data\n\nThere are 3 types of data we might want to include:\n\n- Exported data for the user to access: put in `/data`\n- Internal data for functions to access: put in `/R/sysdata.rda`\n- Raw data: put in `/inst/extdata`\n\n## Exported data\n\nThe data should be saved in `/data` as an `.rda` (or `.RData`) file.\n\n`usethis::use_data()` will do this for you, as well as a few other necessary steps:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nletter_indices <- data.frame(letter = letters, index = seq_along(letters))\nusethis::use_data(letter_indices)\n```\n:::\n\n\n```\n✔ Adding 'R' to Depends field in DESCRIPTION\n✔ Creating 'data/'\n✔ Setting LazyData to 'true' in 'DESCRIPTION'\n✔ Saving 'letter_indices' to 'data/letter_indices.rda'\n• Document your data (see 'https://r-pkgs.org/data.html')\n```\n\n. . .\n\n:::{.callout-note}\nFor larger datasets, you can try changing the `compress` argument to get the best compression.\n:::\n\n## Provenance\n\nOften the data that you want to make accessible to the users is one you have created with an R script -- either from scratch or from a raw data set.\n\nIt's a good idea to put the R script and any corresponding raw data in `/data-raw`.\n\n`usethis::use_data_raw(\"dataname\")` will set this up:\n\n - Create `/data-raw`\n - Add `/data-raw/dataname.R` for you to add the code needed to create the data\n - Add `^data-raw$` to `.Rbuildignore` as it does not need to be included in the actual package.\n\nYou should add any raw data files (e.g. `.csv` files) to `/data-raw`.\n\n## Documenting Data\n\nDatasets in `/data` are always exported, so **must** be documented.\n\nTo document a dataset, we must have an `.R` script in `/R` that contains a Roxygen block above the name of the dataset.\n\nAs with functions, you can choose how to arrange this, e.g. in one combined `/R/data.R` or in a separate R file for each dataset.\n\n## Example: letter_indices\n\n```\n#' Letters of the Roman Alphabet with Indices\n#'\n#' A dataset of lower-case letters of the Roman alphabet and their \n#' numeric index from a = 1 to z = 26.\n#'\n#' @format A data frame with 26 rows and 2 variables:\n#' \\describe{\n#' \\item{letter}{The letter as a character string.}\n#' \\item{index}{The corresponding numeric index.}\n#' }\n\"letter_indices\"\n```\n\n`#' @ examples` can be used here too.\n\n## Data source\n\nFor collected data, the (original) source should be documented with `#' @source`.\n\nThis should either be a url, e.g.\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' @source \\url{http://www.diamondse.info/}\n```\n:::\n\n(alternatively `\\href{DiamondSearchEngine}{http://www.diamondse.info/}`), or a reference, e.g.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' @source Henderson and Velleman (1981), Building multiple \n#' regression models interactively. *Biometrics*, **37**, 391–411.\n```\n:::\n\n\n## Internal data\n\nSometimes functions need access to reference data, e.g. constants or look-up tables, that don't need to be shared with users.\n\nThese objects should be saved in a single `R/sysdata.rda` file.\n\nThis can be done with `use_data(..., internal = TRUE)`, e.g.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nx <- sample(1000)\nusethis::use_data(x, mtcars, internal = TRUE)\n```\n:::\n\n\nThe generating code and any raw data can be put in `/data-raw`.\n\nAs the objects are not exported, they don't need to be documented.\n\n## Raw data\n\nSometimes you want to include raw data, to use in examples or vignettes.\n\nThese files can be any format and should be added directly into `/inst/extdata`.\n\nWhen the package is installed, these files will be copied to the `extdata` directory and their path on your system can be found as follows:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nsystem.file(\"extdata\", \"mtcars.csv\", package = \"readr\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"/Users/e.kaye.1@bham.ac.uk/Library/R/arm64/4.5/library/readr/extdata/mtcars.csv\"\n```\n\n\n:::\n:::\n\n\n## Your turn\n\n1. Run `usethis::use_data_raw(\"farm_animals\")`.\n2. In the script `data-raw/farm_animals.R` write some code to create a small data frame with the names of farm animals and the sound they make.\n3. Run all the code (including the already-present call to `usethis::use_data()`) to create the data and save it in `/data`.\n4. Add an `R/farm_animals.R` script and add some roxygen comments to document the data.\n5. Run `devtools::document()` to create the documentation for the `farm_animals` data. Preview the documentation to check it.\n6. Commit all the changes to your repo.\n\n\n\n# Unit testing with testthat {.inverse}\n\n## Why test?\n\nWe build new functions one bit at a time.\n\nWhat if a new thing we add changes the existing functionality?\n\nHow can we check and be sure all the old functionality still works with New Fancy Feature?\n\nUnit Tests!\n\n::: {.notes}\nGives confidence to package users as well \n:::\n\n## Set up test infrastructure\n\nFrom the root of a package project:\n\n```r\nusethis::use_testthat()\n```\n\n```\n✔ Adding 'testthat' to Suggests field in DESCRIPTION\n✔ Setting Config/testthat/edition field in DESCRIPTION to '3'\n✔ Creating 'tests/testthat/'\n✔ Writing 'tests/testthat.R'\n• Call `use_test()` to initialize a basic test file and open it for editing.\n```\n\n`tests/testthat.R` loads **testthat** and the package being tested, so you don't need to add `library()` calls to the test files.\n\n## Tests are organised in three layers\n\n![](images/test_organization.png){fig-align=\"center\"}\n\n::: {.notes}\nA file holds multiple related tests.\n\nA test groups together multiple expectations to test the output from a simple function, a range of possibilities for a single parameter from a more complicated function, or tightly related functionality from across multiple functions.\n\nAn expectation is the atom of testing. It describes the expected result of a computation: Does it have the right value and right class? \n:::\n\n## What to test\n\nTest every individual task the function completes separately.\n\nCheck both for successful situations and for expected failure situations.\n\n## Expectations\n\nThree expectations cover the vast majority of cases\n\n```r\nexpect_equal(object, expected)\n\nexpect_error(object, regexp = NULL, class = NULL)\n\nexpect_warning(object, regexp = NULL, class = NULL)\n```\n\n:::{.notes}\nIt used to be standard practice to test for errors and warnings using regexp, but that has downsides - it's not always clear why a test is failing. Testing via class is a more modern, safer approach, which we'll use below.\n:::\n\n## Our example function\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nanimal_sounds <- function(animal, sound) {\n \n if (!rlang::is_character(animal, 1)) {\n cli::cli_abort(\"{.var animal} must be a single string!\")\n }\n \n if (!rlang::is_character(sound, 1)) {\n cli::cli_abort(\"{.var sound} must be a single string!\")\n }\n \n paste0(\"The \", animal, \" goes \", sound, \"!\")\n}\n```\n:::\n\n\n## Creating test files\n\nFirst, create a test file for this function, in either way:\n\n```{.r}\n# In RStudio, with `animal_sounds.R` the active file:\nusethis::use_test() \n\n# More generally\nusethis::use_test(\"animal_sounds\")\n```\n\n. . .\n\n:::{.callout-note}\nRStudio makes it really easy to swap between associated R scripts and tests.\n\nIf the R file is open, `usethis::use_test()` (with no arguments) opens or creates the test.\n\nWith the test file open, `usethis::use_r()` (with no arguments) opens or creates the R script.\n:::\n\n## Anatomy of a test\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ntest_that(desc, code)\n```\n:::\n\n\n- `desc` is the test name. Should be brief and evocative, e.g. `test_that(\"multiplication works\", { ... }).`\n- `code` is test code containing expectations. Braces ({}) should always be used in order to get accurate location data for test failures.\n - can include several expectations, as well as other code to help define them\n\n## Add a test\n\nIn the now-created and open `tests/testthat/test-animal_sounds.R` script:\n\n\n::: {.cell layout-align=\"center\"}\n\n:::\n\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ntest_that(\"animal_sounds produces expected strings\", {\n dog_woof <- animal_sounds(\"dog\", \"woof\")\n expect_equal(dog_woof, \"The dog goes woof!\")\n expect_equal(animal_sounds(\"cat\", \"miaow\"), \"The cat goes miaow!\")\n})\n```\n:::\n\n\n## Run tests \n\nTests can be run interactively like any other R code. The output will appear in the console, e.g. for a successful test:\n\n```\nTest passed 😀\n```\n\nAlternatively, we can run tests in the background with the output appearing in the build pane.\n\n - `testthat::test_file()` -- run all tests in a file ('Run Tests' button)\n - `devtools::test()` -- run all tests in a package (Ctrl/Cmd + Shift + T, or Build > Test Package)\n\n## Testing equality\n\nFor numeric values, `expect_equal()` allows some tolerance:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nexpect_equal(10, 10 + 1e-7)\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nexpect_equal(10, 10 + 1e-4, tolerance = 1e-4)\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nexpect_equal(10, 10 + 1e-5)\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError:\n! Expected 10 to equal `10 + 1e-05`.\nDifferences:\n1/1 mismatches\n[1] 10 - 10 == -1e-05\n```\n\n\n:::\n:::\n\n\nNote that when the expectation is met, there is nothing printed.\n\n. . . \n\nUse `expect_identical()` to test exact equivalence.\n\nUse `expect_equal(ignore_attr = TRUE)` to ignore different attributes (e.g. names).\n\n## `expect_error()`, `expect_warning()`\n\nWhen we expect an error/warning when the code is run, we need to pass the call \nto `expect_error()`/`expect_warning()` directly. \n\nOne way is to expect a text outcome using a regular expression:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ntest_that(\"handles invalid inputs\", {\n expect_error(animal_sounds(\"dog\", c(\"woof\", \"bow wow wow\")), \n \"`sound` must be a single string\")\n})\n```\n:::\n\n\nHowever, the `regexp` can get fiddly, especially if there are characters to escape. There is a more modern, precise way...\n\n::: {.notes}\nhave to call `animal_sounds` within `expect_error` - if we try calling it first (as we did in `expect_equal`) our code will throw an error before it has a chance to test for it! \n:::\n\n## Using a condition `class`\n\nWhen using `cli::cli_abort()` and `cli::cli_warn()` to throw errors and warnings, we can signal the condition with a `class`, which we can then use in our tests.\n\nFirst, we need to modify the calls to `cli::cli_abort` in `animal_sounds()`\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nif (!rlang::is_character(sound, 1)) {\n cli::cli_abort(\n c(\"{.var sound} must be a single string!\",\n \"i\" = \"It was {.type {sound}} of length {length(sound)} instead.\"),\n class = \"error_not_single_string\"\n )\n}\n\n# and same for `animal` argument\n```\n:::\n\n\n## Using a condition's class in tests\n\nWe can then check for this class in the test\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ntest_that(\"handles invalid inputs\", {\n expect_error(animal_sounds(\"dog\", c(\"woof\", \"bow wow wow\")), \n class = \"error_not_single_string\") \n})\n```\n:::\n\n\nAdvantages of using `class`:\n\n- It is under your control\n- If the condition originates from base R or another package, proceed with caution -- a good reminder to re-consider the wisdom of testing a condition that is not fully under your control in the first place.\n\n[From ]{.smaller80}\n\n::: {.notes}\nNeed to use argument name `class` as not matched by position (regexp comes before first) \n:::\n\n## Your turn\n\n1. Create a test file for `animal_sounds()` and add the tests defined in the \nslides.\n2. Add a new expectation to the test \"handles invalid inputs\" to test the \nexpected behaviour when a factor of length 1 is passed as the `sound` argument.\n3. Run the updated test by sending the code chunk to the console.\n4. Run all the tests.\n5. Commit your changes to the repo.\n\n::: {.notes}\nanimal_sounds(factor(\"cat\"), \"miaow\")) \n:::\n\n## Snapshot tests\n\nSometimes it is difficult to define the expected output, e.g. to test images or \noutput printed to the console. `expect_snapshot()` captures all messages, warnings, errors, and output from code.\n\nWhen we expect the code to throw an error (e.g. if we want to test the appearance of an informative message), we need to specify `error = TRUE`.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ntest_that(\"error message for invalid input\", {\n expect_snapshot(animal_sounds(\"dog\", c(\"woof\", \"bow wow wow\")),\n error = TRUE)\n})\n```\n:::\n\n\nSnapshot tests can not be run interactively by sending to the console, instead \nwe must use `devtools::test()` or `testthat::test_file()`.\n\n::: {.notes}\nexpect_error for testing that an error is thrown, expect_snapshot for testing the appearance of the error message\n\nsnapshot test skipped on CRAN by default - use other functions to test correctness where possible.\n\nEquivalently Build menu \"Test Package\" or RStudio code editor \"Run tests\" button\n:::\n\n## Create snapshot\n\nRun the tests once to create the snapshot\n\n```\n── Warning (test-animal_sounds.R:16:3): error message for invalid input ──\nAdding new snapshot:\nCode\n animal_sounds(\"dog\", c(\"woof\", \"bow wow wow\"))\nError \n `sound` must be a single string!\n i It was a character vector of length 2 instead.\n```\n\nAn `animal_sounds.md` file is created in `tests/testhat/_snaps` with the code \nand output.\n\n## Test against a snapshot\n\n:::{.smaller90}\nNext time the tests are run the output will be compared against this snapshot.\n\nSuppose we update an error message in `animal_sounds` to\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n\"{.var sound} must be a {.cls character} vector of length 1!\"\n```\n:::\n\n\nWhen we rerun the test, we'll get a failure:\n\n```\n── Failure (test-animal_sounds.R:16:3): error message for invalid input ──\nSnapshot of code has changed:\nold vs new\n \"Code\"\n \" animal_sounds(\\\"dog\\\", c(\\\"woof\\\", \\\"bow wow wow\\\"))\"\n \"Error \"\n- \" `sound` must be a single string!\"\n+ \" `sound` must be a vector of length 1!\"\n \" i It was a character vector of length 2 instead.\"\n\n* Run testthat::snapshot_accept('animal_sounds') to accept the change.\n* Run testthat::snapshot_review('animal_sounds') to interactively review the change.\n```\n:::\n\n::: {.notes}\nNote the next steps with snapshot_accept and snapshot_review \n:::\n\n## Snapshot tests for images\n\nWe can use `expect_snapshot_file()` to create snapshots for images. \nThese allow us to compare binary outputs, though the can't provide an automatic diff when the test fails. Instead, call `snapshot_review()` to launch a Shiny app that allows you to visually review the changes.\n\nSee [Whole file snapshotting](https://testthat.r-lib.org/articles/snapshotting.html#whole-file-snapshotting) for further details.\n\nThe [vdiffr](https://vdiffr.r-lib.org) package allows comparisons between SVG images.\n\n# Test driven development {.inverse}\n\n## So far we've done this\n\n![](images/dev_cycle_before_testing.png){fig-align=\"center\"}\n\n## Test driven development is a new workflow\n\n![](images/dev_cycle_with_testing.png){fig-align=\"center\"}\n\n## Your turn\n\n1. Make this test pass\n\n ```r\n giraffe <- animal_sounds(\"giraffe\")\n expect_equal(giraffe, \n \"The giraffe makes no sound.\")\n ```\n Hint: set the default value for the sound argument to `NULL`.\n2. Commit your changes to the git repo.\n3. Push your commits from this session.\n\n## When you stop work, leave a test failing. {.inverse .center .center-h}\n\n# End matter {.inverse}\n\n## References\n\nWickham, H and Bryan, J, _R Packages_ (2nd edn, in progress), .\n\nR Core Team, _Writing R Extensions_, \n\n## License\n\nLicensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License ([CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/){target=\"_blank\"}).\n", + "markdown": "---\ntitle: Packaging data
Testing\nsubtitle: R package development workshop
Module 3\nauthor: Forwards teaching team\nformat: forwardspres-revealjs\n---\n\n## Overview\n\n- Packaging data\n- Unit testing with **testthat**\n- Test driven development\n\n# Packaging data {.inverse}\n\n## Why package data\n\n- The purpose of the package is to make data available\n - e.g. [nycflights13](https://github.com/tidyverse/nycflights13), [palmerpenguins](https://github.com/allisonhorst/palmerpenguins)\n- To share (toy) data to use with the package functions\n - e.g. [readr](https://github.com/tidyverse/readr)\n - Useful for examples and vignettes\n- To use internally, e.g. look-up tables\n\n## Including data\n\nThere are 3 types of data we might want to include:\n\n- Exported data for the user to access: put in `/data`\n- Internal data for functions to access: put in `/R/sysdata.rda`\n- Raw data: put in `/inst/extdata`\n\n## Exported data\n\nThe data should be saved in `/data` as an `.rda` (or `.RData`) file.\n\n`usethis::use_data()` will do this for you, as well as a few other necessary steps:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nletter_indices <- data.frame(letter = letters, index = seq_along(letters))\nusethis::use_data(letter_indices)\n```\n:::\n\n\n```\n✔ Adding 'R' to Depends field in DESCRIPTION\n✔ Creating 'data/'\n✔ Setting LazyData to 'true' in 'DESCRIPTION'\n✔ Saving 'letter_indices' to 'data/letter_indices.rda'\n• Document your data (see 'https://r-pkgs.org/data.html')\n```\n\n. . .\n\n:::{.callout-note}\nFor larger datasets, you can try changing the `compress` argument to get the best compression.\n:::\n\n## Provenance\n\nOften the data that you want to make accessible to the users is one you have created with an R script -- either from scratch or from a raw data set.\n\nIt's a good idea to put the R script and any corresponding raw data in `/data-raw`.\n\n`usethis::use_data_raw(\"dataname\")` will set this up:\n\n - Create `/data-raw`\n - Add `/data-raw/dataname.R` for you to add the code needed to create the data\n - Add `^data-raw$` to `.Rbuildignore` as it does not need to be included in the actual package.\n\nYou should add any raw data files (e.g. `.csv` files) to `/data-raw`.\n\n## Documenting Data\n\nDatasets in `/data` are always exported, so **must** be documented.\n\nTo document a dataset, we must have an `.R` script in `/R` that contains a Roxygen block above the name of the dataset.\n\nAs with functions, you can choose how to arrange this, e.g. in one combined `/R/data.R` or in a separate R file for each dataset.\n\n## Example: letter_indices\n\n```\n#' Letters of the Roman Alphabet with Indices\n#'\n#' A dataset of lower-case letters of the Roman alphabet and their \n#' numeric index from a = 1 to z = 26.\n#'\n#' @format A data frame with 26 rows and 2 variables:\n#' \\describe{\n#' \\item{letter}{The letter as a character string.}\n#' \\item{index}{The corresponding numeric index.}\n#' }\n\"letter_indices\"\n```\n\n`#' @ examples` can be used here too.\n\n## Data source\n\nFor collected data, the (original) source should be documented with `#' @source`.\n\nThis should either be a url, e.g.\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' @source \\url{http://www.diamondse.info/}\n```\n:::\n\n(alternatively `\\href{DiamondSearchEngine}{http://www.diamondse.info/}`), or a reference, e.g.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' @source Henderson and Velleman (1981), Building multiple \n#' regression models interactively. *Biometrics*, **37**, 391–411.\n```\n:::\n\n\n## Internal data\n\nSometimes functions need access to reference data, e.g. constants or look-up tables, that don't need to be shared with users.\n\nThese objects should be saved in a single `R/sysdata.rda` file.\n\nThis can be done with `use_data(..., internal = TRUE)`, e.g.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nx <- sample(1000)\nusethis::use_data(x, mtcars, internal = TRUE)\n```\n:::\n\n\nThe generating code and any raw data can be put in `/data-raw`.\n\nAs the objects are not exported, they don't need to be documented.\n\n## Raw data\n\nSometimes you want to include raw data, to use in examples or vignettes.\n\nThese files can be any format and should be added directly into `/inst/extdata`.\n\nWhen the package is installed, these files will be copied to the `extdata` directory and their path on your system can be found as follows:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nsystem.file(\"extdata\", \"mtcars.csv\", package = \"readr\")\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n[1] \"/Users/e.kaye.1@bham.ac.uk/Library/R/arm64/4.5/library/readr/extdata/mtcars.csv\"\n```\n\n\n:::\n:::\n\n\n## Your turn\n\n1. Run `usethis::use_data_raw(\"farm_animals\")`.\n2. In the script `data-raw/farm_animals.R` write some code to create a small data frame with the names of farm animals and the sound they make.\n3. Run all the code (including the already-present call to `usethis::use_data()`) to create the data and save it in `/data`.\n4. Add an `R/farm_animals.R` script and add some roxygen comments to document the data.\n5. Run `devtools::document()` to create the documentation for the `farm_animals` data. Preview the documentation to check it.\n6. Commit all the changes to your repo.\n\n# Unit testing with testthat {.inverse}\n\n## You (almost certainly) already test your code\n\nYou write code, run it, check outputs (maybe for a few different inputs). \n\nMaybe you also use print statements for debugging.\n\n. . . \n\n**Unit tests make your informal testing systematic so it scales with your package**\n\n## Testing beyond your own workflow\n\nYou are building something that others will use, in possibly unpredictable ways. \n\n- Future you (or contributors) will modify code and need to verify nothing broke\n- Users will pass inputs you didn't anticipate\n- Documentation alone won't prevent misuse\n\n## Practical benefits\n\n- Refactoring confidence: change internals or add new functionality without fear of breaking user-facing behavior\n- Regression prevention: every bug fix becomes a test that ensures it stays fixed\n- Living documentation: tests show how functions are actually meant to be used\n- Faster debugging: when something breaks, tests help isolate exactly where\n\n## Set up test infrastructure\n\nFrom the root of a package project:\n\n```r\nusethis::use_testthat()\n```\n\n```\n✔ Adding 'testthat' to Suggests field in DESCRIPTION\n✔ Setting Config/testthat/edition field in DESCRIPTION to '3'\n✔ Creating 'tests/testthat/'\n✔ Writing 'tests/testthat.R'\n• Call `use_test()` to initialize a basic test file and open it for editing.\n```\n\n`tests/testthat.R` loads **testthat** and the package being tested, so you don't need to add `library()` calls to the test files.\n\n## Tests are organised in three layers\n\n![](images/test_organization.png){fig-align=\"center\"}\n\n::: {.notes}\nA file holds multiple related tests.\n\nA test groups together multiple expectations to test the output from a simple function, a range of possibilities for a single parameter from a more complicated function, or tightly related functionality from across multiple functions.\n\nAn expectation is the atom of testing. It describes the expected result of a computation: Does it have the right value and right class? \n:::\n\n## What to test\n\nTest every individual task the function completes separately.\n\nCheck both for successful situations and for expected failure situations.\n\n::: {.callout-tip}\nLook at the `\"tests/testthat\"` directory and some test files in the GitHub repos for some well-respected packages to see how their tests are organised, e.g.\n\n- \n- \n:::\n\n## Expectations\n\nThree expectations cover the vast majority of cases\n\n```r\nexpect_equal(object, expected)\n\nexpect_error(object, regexp = NULL, class = NULL)\n\nexpect_warning(object, regexp = NULL, class = NULL)\n```\n\n:::{.notes}\nIt used to be standard practice to test for errors and warnings using regexp, but that has downsides - it's not always clear why a test is failing. Testing via class is a more modern, safer approach, which we'll use below.\n:::\n\n## Our example function\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nanimal_sounds <- function(animal, sound) {\n \n if (!rlang::is_character(animal, 1)) {\n cli::cli_abort(\"{.var animal} must be a single string!\")\n }\n \n if (!rlang::is_character(sound, 1)) {\n cli::cli_abort(\"{.var sound} must be a single string!\")\n }\n \n paste0(\"The \", animal, \" goes \", sound, \"!\")\n}\n```\n:::\n\n\n## Creating test files\n\nFirst, create a test file for this function, in either way:\n\n```{.r}\n# In RStudio, with `animal_sounds.R` the active file:\nusethis::use_test() \n\n# More generally\nusethis::use_test(\"animal_sounds\")\n```\n\n. . .\n\n:::{.callout-note}\nRStudio makes it really easy to swap between associated R scripts and tests.\n\nIf the R file is open, `usethis::use_test()` (with no arguments) opens or creates the test.\n\nWith the test file open, `usethis::use_r()` (with no arguments) opens or creates the R script.\n:::\n\n## Anatomy of a test\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ntest_that(desc, code)\n```\n:::\n\n\n- `desc` is the test name. Should be brief and evocative, e.g. `test_that(\"multiplication works\", { ... }).`\n- `code` is test code containing expectations. Braces ({}) should always be used in order to get accurate location data for test failures.\n - can include several expectations, as well as other code to help define them\n\n## Add a test\n\nIn the now-created and open `tests/testthat/test-animal_sounds.R` script:\n\n\n::: {.cell layout-align=\"center\"}\n\n:::\n\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ntest_that(\"animal_sounds produces expected strings\", {\n dog_woof <- animal_sounds(\"dog\", \"woof\")\n expect_equal(dog_woof, \"The dog goes woof!\")\n expect_equal(animal_sounds(\"cat\", \"miaow\"), \"The cat goes miaow!\")\n})\n```\n:::\n\n\n## Run tests \n\nTests can be run interactively like any other R code. The output will appear in the console, e.g. for a successful test:\n\n```\nTest passed 😀\n```\n\nAlternatively, we can run tests in the background with the output appearing in the build pane.\n\n - `testthat::test_file()` -- run all tests in a file ('Run Tests' button)\n - `devtools::test()` -- run all tests in a package (Ctrl/Cmd + Shift + T, or Build > Test Package)\n\n## Testing equality\n\nFor numeric values, `expect_equal()` allows some tolerance:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nexpect_equal(10, 10 + 1e-7)\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nexpect_equal(10, 10 + 1e-4, tolerance = 1e-4)\n```\n:::\n\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nexpect_equal(10, 10 + 1e-5)\n```\n\n::: {.cell-output .cell-output-error}\n\n```\nError:\n! Expected 10 to equal `10 + 1e-05`.\nDifferences:\n1/1 mismatches\n[1] 10 - 10 == -1e-05\n```\n\n\n:::\n:::\n\n\nNote that when the expectation is met, there is nothing printed.\n\n. . . \n\nUse `expect_identical()` to test exact equivalence.\n\nUse `expect_equal(ignore_attr = TRUE)` to ignore different attributes (e.g. names).\n\n## `expect_error()`, `expect_warning()`\n\nWhen we expect an error/warning when the code is run, we need to pass the call \nto `expect_error()`/`expect_warning()` directly. \n\nOne way is to expect a text outcome using a regular expression:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ntest_that(\"handles invalid inputs\", {\n expect_error(animal_sounds(\"dog\", c(\"woof\", \"bow wow wow\")), \n \"`sound` must be a single string\")\n})\n```\n:::\n\n\nHowever, the `regexp` can get fiddly, especially if there are characters to escape. There is a more modern, precise way...\n\n::: {.notes}\nhave to call `animal_sounds` within `expect_error` - if we try calling it first (as we did in `expect_equal`) our code will throw an error before it has a chance to test for it! \n:::\n\n## Using a condition `class`\n\nWhen using `cli::cli_abort()` and `cli::cli_warn()` to throw errors and warnings, we can signal the condition with a `class`, which we can then use in our tests.\n\nFirst, we need to modify the calls to `cli::cli_abort` in `animal_sounds()`\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nif (!rlang::is_character(sound, 1)) {\n cli::cli_abort(\n c(\"{.var sound} must be a single string!\",\n \"i\" = \"It was {.type {sound}} of length {length(sound)} instead.\"),\n class = \"error_not_single_string\"\n )\n}\n\n# and same for `animal` argument\n```\n:::\n\n\n## Using a condition's class in tests\n\nWe can then check for this class in the test\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ntest_that(\"handles invalid inputs\", {\n expect_error(animal_sounds(\"dog\", c(\"woof\", \"bow wow wow\")), \n class = \"error_not_single_string\") \n})\n```\n:::\n\n\nAdvantages of using `class`:\n\n- It is under your control\n- If the condition originates from base R or another package, proceed with caution -- a good reminder to re-consider the wisdom of testing a condition that is not fully under your control in the first place.\n\n[From ]{.smaller80}\n\n::: {.notes}\nNeed to use argument name `class` as not matched by position (regexp comes before first) \n:::\n\n## Your turn\n\n1. Create a test file for `animal_sounds()` and add the tests defined in the \nslides.\n2. Add a new expectation to the test \"handles invalid inputs\" to test the \nexpected behaviour when a factor of length 1 is passed as the `sound` argument.\n3. Run the updated test by sending the code chunk to the console.\n4. Run all the tests.\n5. Commit your changes to the repo.\n\n::: {.notes}\nanimal_sounds(factor(\"cat\"), \"miaow\")) \n:::\n\n## Snapshot tests\n\nSometimes it is difficult to define the expected output, e.g. to test images or \noutput printed to the console. `expect_snapshot()` captures all messages, warnings, errors, and output from code.\n\nWhen we expect the code to throw an error (e.g. if we want to test the appearance of an informative message), we need to specify `error = TRUE`.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\ntest_that(\"error message for invalid input\", {\n expect_snapshot(animal_sounds(\"dog\", c(\"woof\", \"bow wow wow\")),\n error = TRUE)\n})\n```\n:::\n\n\nSnapshot tests can not be run interactively by sending to the console, instead \nwe must use `devtools::test()` or `testthat::test_file()`.\n\n::: {.notes}\nexpect_error for testing that an error is thrown, expect_snapshot for testing the appearance of the error message\n\nsnapshot test skipped on CRAN by default - use other functions to test correctness where possible.\n\nEquivalently Build menu \"Test Package\" or RStudio code editor \"Run tests\" button\n:::\n\n## Create snapshot\n\nRun the tests once to create the snapshot\n\n```\n── Warning (test-animal_sounds.R:16:3): error message for invalid input ──\nAdding new snapshot:\nCode\n animal_sounds(\"dog\", c(\"woof\", \"bow wow wow\"))\nError \n `sound` must be a single string!\n i It was a character vector of length 2 instead.\n```\n\nAn `animal_sounds.md` file is created in `tests/testhat/_snaps` with the code \nand output.\n\n## Test against a snapshot\n\n:::{.smaller90}\nNext time the tests are run the output will be compared against this snapshot.\n\nSuppose we update an error message in `animal_sounds` to\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n\"{.var sound} must be a {.cls character} vector of length 1!\"\n```\n:::\n\n\nWhen we rerun the test, we'll get a failure:\n\n```\n── Failure (test-animal_sounds.R:16:3): error message for invalid input ──\nSnapshot of code has changed:\nold vs new\n \"Code\"\n \" animal_sounds(\\\"dog\\\", c(\\\"woof\\\", \\\"bow wow wow\\\"))\"\n \"Error \"\n- \" `sound` must be a single string!\"\n+ \" `sound` must be a vector of length 1!\"\n \" i It was a character vector of length 2 instead.\"\n\n* Run testthat::snapshot_accept('animal_sounds') to accept the change.\n* Run testthat::snapshot_review('animal_sounds') to interactively review the change.\n```\n:::\n\n::: {.notes}\nNote the next steps with snapshot_accept and snapshot_review \n:::\n\n## Snapshot tests for images\n\nWe can use `expect_snapshot_file()` to create snapshots for images. \nThese allow us to compare binary outputs, though the can't provide an automatic diff when the test fails. Instead, call `snapshot_review()` to launch a Shiny app that allows you to visually review the changes.\n\nSee [Whole file snapshotting](https://testthat.r-lib.org/articles/snapshotting.html#whole-file-snapshotting) for further details.\n\nThe [vdiffr](https://vdiffr.r-lib.org) package allows comparisons between SVG images.\n\n# Test driven development {.inverse}\n\n## So far we've done this\n\n![](images/dev_cycle_before_testing.png){fig-align=\"center\"}\n\n## Test driven development is a new workflow\n\n![](images/dev_cycle_with_testing.png){fig-align=\"center\"}\n\n## Your turn\n\n1. Make this test pass\n\n ```r\n giraffe <- animal_sounds(\"giraffe\")\n expect_equal(giraffe, \n \"The giraffe makes no sound.\")\n ```\n Hint: set the default value for the sound argument to `NULL`.\n2. Commit your changes to the git repo.\n3. Push your commits from this session.\n\n## When you stop work, leave a test failing. {.inverse .center .center-h}\n\n# Minute cards {.inverse}\n\n- [Cohort 1](https://tally.so/r/LZa1Yp)\n- [Cohort 2](https://tally.so/r/aQANEb)\n\n# End matter {.inverse}\n\n## References\n\nWickham, H and Bryan, J, _R Packages_ (2nd edn, in progress), .\n\nR Core Team, _Writing R Extensions_, \n\n## License\n\nLicensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License ([CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/){target=\"_blank\"}).\n", "supporting": [], "filters": [ "rmarkdown/pagebreak.lua" diff --git a/_freeze/slides/04-check-package-documentation/index/execute-results/html.json b/_freeze/slides/04-check-package-documentation/index/execute-results/html.json index 45ffe6f..d06dbe1 100644 --- a/_freeze/slides/04-check-package-documentation/index/execute-results/html.json +++ b/_freeze/slides/04-check-package-documentation/index/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "6fe9049bae6f03f6ce9fd7ce0a02f3f7", + "hash": "023775699ecd86d9b68490be7360afbd", "result": { "engine": "knitr", - "markdown": "---\ntitle: Package check and documentation\nsubtitle: R package development workshop
Module 4\nauthor: Forwards teaching team\nformat: forwardspres-revealjs\n---\n\n## Overview\n\n- Check your package!\n- DESCRIPTION\n- README\n- Continuous Integration (CI)\n- Vignettes\n- NEWS\n- Package website with pkgdown \n\n# Check your package! {.inverse}\n\n## R CMD check\n\n`R CMD check` is the command line utility provided by R to check R packages.\n\nIt checks that all the components are valid and consistent with each other, in particular:\n\n- Metadata in DESCRIPTION\n- Imports and exports in NAMESPACE\n- `.Rd` files in `/man`\n\nIt will also run any examples and tests you have written.\n\n`devtools::check()` will run `R CMD check` in the background, with the results shown in the Build pane.\n\n::: notes\nNote that this doesn't check the correctness of code - this is the role of tests\n:::\n\n## Run `devtools::check()`\n\nYou will get lots of output. It will end with:\n\n``` \n-- R CMD check results ---------- animalsounds 0.0.0.9000 ----\nDuration: 9.3s\n\n> checking DESCRIPTION meta-information ... WARNING\n Non-standard license specification:\n `use_mit_license()`, `use_gpl3_license()` or friends to pick a\n license\n Standardizable: FALSE\n\n0 errors √ | 1 warnings x | 0 notes √\n```\n\nWe haven't yet specified a license for our package.\n\n::: notes\nOn running `check()` you may get an error if you are using a networked drive. There's a fix coming in a few slides.\n:::\n\n## Aside: in case of error\n\nOn running `devtools::check()` you may get an error of the form\n\n``` \nUpdating animalsounds documentation \nError: The specified file is not readable: path-to\\animalsounds\\NAMESPACE \n```\n\nThis can happen if your repo is on a networked drive.\n\nThis is covered in this [Stackoverflow question](https://stackoverflow.com/questions/40530968/overwriting-namespace-and-rd-with-roxygen2) and can be fixed.\n\n## Aside: a fix for networked drives\n\n1. Save a copy of this file: [fix_for_networked_drives.R](../../R/fix_for_networked_drives.R)\n\n Save it somewhere other than the `animalsounds` directory\n\n2. Open the file from the `animalsounds` project session\n\n3. Run the whole file\n\nYou should now find that `devtools::check()` proceeds normally.\n\n## Types of problem\n\n
\n\n| | |\n|-------------|-----------------------------------------------|\n| **ERROR** | Must fix to get the code/example/test working |\n| **WARNING** | Fix if sharing with others |\n| **NOTE** | Fix if submitting to CRAN |\n\n
\n\nIt is possible to submit to CRAN with a NOTE, but it's best avoided.\n\n::: notes\nNOTES best avoid for CRAN as they require a person to respond. One unavoidable case of a NOTE on CRAN submission: first submission of a package\n:::\n\n## Check regularly, fix issues as they arise {.inverse .center .center-h}\n\n::: {.notes}\nWill see later in this session how to do this automatically with Continuous Integration \n:::\n\n# DESCRIPTION {.inverse}\n\n::: {.notes}\nThe are two types of documentation: package level and function level.\n\nThe DESCRIPTION file provides some of the package level documentation (or metadata)\n:::\n\n## Metadata in `DESCRIPTION`\n\n- **Package**: The package name. The `changer` package can help you change the name!\n- **Title**: One line, title case, with no period. Fewer than 65 characters.\n- **Version**\n - for release: MAJOR.MINOR.PATCH version.\n - for development version building on version MAJOR.MINOR.PATCH, use: MAJOR.MINOR.PATCH.9000\n - can be filled in with `usethis::use_version()`\n\n## Notes of version number\n\nGiven a version number MAJOR.MINOR.PATCH, increment the:\n\n- MAJOR version when you make incompatible API changes,\n- MINOR version when you add functionality in a backwards compatible manner, and\n- PATCH version when you make backwards compatible bug fixes.\n\nSee for further guidance on when to bump which part of the version number\n\n \n## Metadata in `DESCRIPTION`\n\n:::{.smaller90}\n- **Authors@R**: A call to `person()` that is run to create the Author field when the package tarball is built. \"aut\" means author, \"cre\" means creator (maintainer), \"ctb\" means contributor. \n\n A placeholder call to `person()` is inserted in `DESCRIPTION` when a package is created with `usethis::create_package()` which can be edited directly:\n\n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n person(\"A\", \"Person\", email = \"a.person@email.com\", \n role = c(\"aut\", \"cre\"),\n comment = c(ORCID = \"0000-0001-2345-6789\"))\n ```\n :::\n\n\n Alternatively, this can be overwritten with a call to `usethis::use_author()`:\n \n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n use_author(\"A\", \"Person\", email = \"a.person@email.com\", \n role = c(\"aut\", \"cre\"),\n comment = c(ORCID = \"0000-0001-2345-6789\"))\n ```\n :::\n\n:::\n\n::: {.notes}\nusethis::use_author() is new in usethis 2.2.0\n:::\n\n## Metadata in `DESCRIPTION`\n\n- **Description**: One paragraph describing what the package does. Keep the width of the paragraph to 80 characters; indent subsequent lines with 4 spaces.\n- **License**: Will discuss later\n- **Encoding**: How to encode text, use UTF-8 encoding.\n- **LazyData**: If `true` data sets in the package are stored in a database during package installation and loaded from the database as required. Recommended if shipping data with package -- `usethis::use_data()` will set this for you.\n\n::: {.notes}\nusethis::create_package() sets UTF-8 encoding\n\nlazy loading means that data is only loaded if needed.\nThis means that they won’t occupy any memory until you use them.\ndo NOT include LazyData: true in DESCRIPTION unless you actually ship data in your package. \n:::\n\n## Open source licenses\n\nThere are three main open source licenses:\n\n- CC0: “public domain”, best for data packages.\n\n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n usethis::use_cc0_license()\n ```\n :::\n\n- MIT: Free for anyone to do anything with (including bundling in closed source product).\n\n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n usethis::use_mit_license()\n ```\n :::\n\n- GPL: Changes and bundles must also be GPL\n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n usethis::use_gpl_license()\n ```\n :::\n\n [If you are including someone else's GPL code directly, you must use GPL yourself.]{.smaller80}\n\n::: {.notes}\nhttps://bookdown.org/rdpeng/RProgDA/open-source-licensing.html\n\nsomewhat personal down to how important you consider open source to be and what you are happy with people doing with your code\n\nSuggest academic: GPL; community/other: MIT \n:::\n\n## Proprietary packages\n\nYou can use `usethis::use_proprietary()` to make it clear that your package isn’t open source.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_proprietary(copyright_holder = \"ACME Ltd\")\n```\n:::\n\n\nIn DESCRIPTION:\n```\nLicense: file LICENSE\n```\nIn LICENSE:\n```\nCopyright 2023 ACME Ltd. All rights reserved.\n```\n\n## Licensing considerations at universities\n\n:::{.callout-note appearance=\"simple\"}\n[This slide is specific for The University of Warwick, but similar considerations are likely to be true at other Universities.]{.smaller80}\n:::\n\n:::{.smaller75}\n- Software is defined as a creative output (unlike scholarly works, e.g. thesis, conference paper)\n- The university owns the IP of any software created by Warwick PhD students and staff in the course of their work\n- Before making code public or publishing software under any license, contact Brendan at [B.Spillane@warwick.ac.uk](mailto:B.Spillane@warwick.ac.uk)\n\n - Permission to publish under an open source license likely to be granted for research code\n - Not necessary to obtain permission if open source software was part of grant proposal (as proposal will have already been checked by Research & Impact Services, who will have identified any IP issues).\n:::\n\n::: {.notes}\nExtra note from Brendan: \"they don’t need to come to me with every single bit of code they open source. I’m more interested in the entirety of the project rather than approving 2000 bits of individual code!\" \n:::\n\n## Your turn\n\n1. Open the DESCRIPTION file and add a title and description.\n2. Add yourself as an author and creator.\n3. Add an MIT license.\n4. Run the package check.\n5. Commit changes to GitHub.\n\n:::{.callout-tip}\nIn RStudio, you can use the 'Go to file/function' box or Ctrl + . [period] and start typing a file name to open a file for editing.\n:::\n\n# README {.inverse}\n\n## README\n\nThe README is the quick start guide to your package.\n\nIt should include \n\n - a brief overview\n - instructions on how to install the package\n - a few examples\n \nYou should be able to borrow from the DESCRIPTION and help files!\n\nIt's readable on the package's GitHub repo and on the homepage of its website (more on that later).\n\n## Creating a README\n\n**usethis** has functions to set up a README with/without R code\n\n```r\nusethis::use_readme_rmd()\nusethis::use_readme_md()\n```\n\n`README.Rmd` must be rendered to make `README.md` each time it \nis changed.\n\n`usethis::use_readme_rmd()` creates a _pre-commit hook_ to check if `README.Rmd` and `README.md` are out of sync before committing.\n\nUse `build_readme()` to render with the latest version of the code.\n\n::: {.notes}\n`build_readme` creates a temporary version of the package\n:::\n\n# Continuous Integration (CI) {.inverse}\n\n::: {.notes}\nThe practice of merging new code into the repository and running checks each time is known as Continuous Integration.\n:::\n\n## Running automatic checks\n\nGitHub Actions (GHAs) allow you to run code every time you push to GitHub. \n\nThe most useful ones for packages can be selected from a call to `usethis::use_github_action()`:\n\n```{.r}\nuse_github_action()\n```\n\n```\nWhich action do you want to add? (0 to exit)\n(See for other options) \n\n1: check-standard: Run `R CMD check` on Linux, macOS, and Windows\n2: test-coverage: Compute test coverage and report to https://about.codecov.io\n3: pr-commands: Add /document and /style commands for pull requests\n```\n\n`check-standard` sets up a GHA that runs `R CMD check` with the latest release of R on Linux, Mac, and Windows and with both the previous release and development release of R on Linux.\n\n::: {.notes}\nThis behaviour of `use_github_action()` is new in usethis 2.2.\n\n`use_github_actions()` is deprecated.\n:::\n\n## [`use_github_action(\"check-release\")`]{.smaller90}\n\nThe `check-standard` GHA is best-practice for 'serious' projects, e.g. those aiming for CRAN, but is overkill for our purposes. \n\nWe can set up a simpler GHA by specifying an alternative:\n\n```{.r}\nuse_github_action(\"check-release\")\n```\n\nThis sets up a bare-minimum workflow that runs `R CMD check` with the latest release of R on Linux.\n\nIt's good for simple package with no OS-sepcific code, and if you want a quick start with R CI.\n\n::: {.notes}\ni.e. it's perfect for us! \n\nReminder to fix any issues as soon as they arise!\n:::\n\n## Your turn\n\n1. Create a README for `animalsounds` with `usethis::use_readme_rmd()`.\n2. Fill in the description and an example.\n3. Try adding the README in a git commit -- it should fail! Render the README with `build_readme()`, then add both `README.Rmd` and `README.md` in a git commit.\n4. Run `usethis::use_github_action(\"check-release\")`. It adds a badge to the README, so you will need to render the README again.\n5. Commit all the changes to git.\n\n# Vignettes {.inverse}\n\n## Vignettes\n\nVignettes are long-form documentation for your package.\n\nThey use R markdown to integrate code and output into the documentation. Typically:\n\n - A single/main vignette showing a complete workflow. \n - Optional further vignette(s) going deeper on one aspect/application\n - Optional further vignette(s) for specialist audience (methodologists or developers)\n \nA vignette with the same name as the package (e.g., `vignettes/animalsounds.Rmd` or `vignettes/articles/animalsounds.Rmd`) automatically becomes a top-level \"Get started\" link.\n \n::: {.notes}\nFairly short read ~10 minutes\n::: \n\n## `use_vignette()`\n\nEasiest way to get started is with `usethis::use_vignette()`\n\n```r\nusethis::use_vignette(\"name\")\n```\n\nAdds to DESCRIPTION\n\n
\nSuggests: knitr\nVignetteBuilder: knitr\n
\n\nCreates `vignettes/`\n\nDrafts `vignettes/name.Rmd`\n\n::: {.notes}\nuse_vignette() will fill both title fields with the \"name\" of the file, but you'll want to edit to something more descriptive.\n\nNeeds to be .Rmd not .qmd. Quarto is not R-specific and CRAN can't publish .qmd files. \n:::\n\n## Vignette = Rmarkdown + special metadata\n\n```\n---\ntitle: \"Vignette Title\"\nauthor: \"Vignette author\"\ndate: \"2025-11-17\"\noutput: rmarkdown::html_vignette\nvignette: >\n%\\VignetteIndexEntry{Vignette Title}\n%\\VignetteEngine{knitr::rmarkdown}\n%\\VignetteEncoding{UTF-8}\n---\n\n```\n* `html_vignette` output uses a custom style sheet to keep the file size of the \nHTML as small as possible.\n* The `vignette:` field contains special metadata needed when the package is \nbuilt. **Don't forget to change the title here too!**\n\n## Vignette workflow\n\n![](images/vignette_workflow.png){fig-align=\"center\"}\n\n::: {.notes}\nNeed to install package so can call with `library()` \n:::\n\n## Articles\n\nVignette code needs to be able to run on CRAN. There are some cases where [this is not possible](https://r-pkgs.org/vignettes.html#sec-vignettes-eval-option).\n\nAs an alternative, you can use articles instead of vignettes:\n\n```{.r}\nusethis::use_article(\"my-article\")\n```\n\n```\n✔ Adding 'rmarkdown' to Config/Needs/website\n✔ Creating 'vignettes/'\n✔ Creating 'vignettes/articles/'\n✔ Adding '*.html', '*.R' to 'vignettes/.gitignore'\n✔ Writing 'vignettes/articles/my-article.Rmd'\n• Modify 'vignettes/articles/my-article.Rmd'\n✔ Adding '^vignettes/articles$' to '.Rbuildignore'\n```\n\n## Your turn\n\n1. Install your **animalsounds** package and restart R (Install button).\n2. Create a simple vignette, `animalsounds`, that shows how to use `animal_sounds()`.\n3. Fix the \"vignette title\" in the YAML header.\n4. Knit the vignette to preview it.\n5. Run `devtools::install(build_vignettes = TRUE)` to install the package with the vignettes. Call `browseVignettes(\"animalsounds\")` to open your vignette.\n6. Commit your changes to git.\n\n# NEWS {.inverse}\n\n## Track changes in a NEWS file\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_news_md()\n```\n:::\n\n\nAdd news for the latest version at the top.\n\nUse a top-level heading for each release version\n\n```r\n# animalsounds 1.0.0\n```\n\n\n## What news to include\n\nAdd each change in a bulleted list:\n\n- If you have many changes, split into subsections (e.g.\n `## Major changes`, `## Minor improvements`, `## Bug fixes`).\n- Wait until release to decide if subsections are necessary\n \nNote connections to GitHub:\n\n - If related to a GitHub issue, add the issue number, e.g. (`#10`). \n - If related to a pull request, add the pull request number and the author, e.g. (`#101, @hadley`). \n\n# Package website with pkgdown {.inverse}\n\n## A package website pkgdown\n\nThe **pkgdown** package is designed to make it quick and easy to build a website for your package:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_pkgdown()\n```\n:::\n\n\nWhy have a website for your package?\n\n - Google-ability\n - Easy-to-read and browse documentation and package information in one place\n - Examples with output!\n \n## Build a website\n\n`pkgdown::build_site()` creates a package website based on the \nstandard documentation files.\n\n**Home page**: based on README\n\n**Reference**: \n\n - one page for each help file\n - generates an index page, with functions listed alphabetically by default \n \n::: {.notes}\nFor packages with lots of functions, it's good to organise them by theme or functionality.\n:::\n\n## Build a website (ctd)\n\n**Articles**: one page for each vignette\n\n**Get Started**: if you have a vignette with filename = package name\n\n**News**: based on NEWS.md\n\nPlus:\n\n - A link to your GitHub repo (if listed in the DESCRIPTION url field).\n - A link to the License\n - Any badges added to your README (e.g. from GitHub Actions)\n\n## Hosting your website\n\n- You can host your website directly from its GitHub repo (repo has to be public) \n- The recommended approach is to let GitHub build your page (instead of calling `pkgdown::build_site()` and committing and pushing the artifacts of the built website (i.e., html files) to GitHub\n- Add an action to your GitHub repo to be run automatically every time you push to it to rebuild the site:\n\n ```{.r}\n usethis::use_pkgdown_github_pages()\n ```\n\n- The URL will be https://USERNAME.github.io/animalsounds \n \n::: {.notes}\nuse_pkgdown_github_pages() will:\n\n - call `use_pkgdown()` - allow it to overwrite existing `_pkgdown.yml` (will add the url)\n - set up a `gh-pages` branch from which to deploy the site\n - add a `pkgdown.yml` to .github/workflows\n\nWill still need to call `pkgdown::build_site()` to preview locally\n:::\n\n## Customising your website\n\nYou can add more information to `_pkgdown.yml` to customise the package website:\n\n- curate the index for the Reference page - functions can be grouped and described in categories\n\n \n\n- customise the appearance\n\n \n\n## Your turn\n\n1. Run `usethis::use_pkgdown_github_pages()` -- this will ask you to install **pkgdown** if you don't already have it.\n2. Read through all the output in the console to see the many things that this function does. \n3. Look at the diffs in the Git pane. Commit and push all changes.\n4. Go to your GitHub repo of the package. Click on **Actions**. If there's a green tick next to \"pages build and deployment\" then your site is ready to view!\n5. Click on the link to the website under the **About** section of the repo.\n6. (Bonus) Change the appearance of the site with a Bootswatch theme: . \n\n# End matter {.inverse}\n\n## References\n\nWickham, H and Bryan, J, *R Packages* (2nd edn, in progress), .\n\nR Core Team, *Writing R Extensions*, \n\n## License\n\nLicensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License ([CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/){target=\"_blank\"}).\n\n", + "markdown": "---\ntitle: Package check and documentation\nsubtitle: R package development workshop
Module 4\nauthor: Forwards teaching team\nformat: forwardspres-revealjs\n---\n\n## Overview\n\n- Check your package!\n- DESCRIPTION\n- README\n- Continuous Integration (CI)\n- Vignettes\n- NEWS\n- Package website with pkgdown \n\n# Check your package! {.inverse}\n\n## R CMD check\n\n`R CMD check` is the command line utility provided by R to check R packages.\n\nIt checks that all the components are valid and consistent with each other, in particular:\n\n- Metadata in DESCRIPTION\n- Imports and exports in NAMESPACE\n- `.Rd` files in `/man`\n\nIt will also run any examples and tests you have written.\n\n`devtools::check()` will run `R CMD check` in the background, with the results shown in the Build pane.\n\n::: notes\nNote that this doesn't check the correctness of code - this is the role of tests\n:::\n\n## Run `devtools::check()`\n\nYou will get lots of output. It will end with:\n\n``` \n-- R CMD check results ---------- animalsounds 0.0.0.9000 ----\nDuration: 9.3s\n\n> checking DESCRIPTION meta-information ... WARNING\n Non-standard license specification:\n `use_mit_license()`, `use_gpl3_license()` or friends to pick a\n license\n Standardizable: FALSE\n\n0 errors √ | 1 warnings x | 0 notes √\n```\n\nWe haven't yet specified a license for our package.\n\n::: notes\nOn running `check()` you may get an error if you are using a networked drive. There's a fix coming in a few slides.\n:::\n\n## A function that fails many checks\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n#' Make an excited animal sound\n#'\n#' Repeat an animal sound in uppercase.\n#'\n#' @param sound A character string containing an animal sound.\n#' @param times Number of times to repeat the sound.\n#'\n#' @return A character vector.\n#'\n#' @examples\n#' excited_sound(\"moo\", 3)\n#'\n#' @export\nexcited_sound <- function(animal, sound, times = 3) {\n\n sound <- str_to_upper(sound)\n\n paste(\"The\", animal, \"says\", paste(rep(sound, times), collapse = \" \"))\n}\n```\n:::\n\n\n## A function that fails many checks (cont.)\n\n```r\n❯ checking examples ... ERROR\n Running examples in 'animalsounds-Ex.R' failed\n The error most likely occurred in:\n \n > base::assign(\".ptime\", proc.time(), pos = \"CheckExEnv\")\n > ### Name: excited_sound\n > ### Title: Make an excited animal sound\n > ### Aliases: excited_sound\n > \n > ### ** Examples\n > \n > excited_sound(\"moo\", 3)\n Error in str_to_upper(sound) : could not find function \"str_to_upper\"\n Calls: excited_sound\n Execution halted\n\n❯ checking Rd \\usage sections ... WARNING\n Undocumented arguments in Rd file 'excited_sound.Rd'\n 'animal'\n \n Functions with \\usage entries need to have the appropriate \\alias\n entries, and all their arguments documented.\n The \\usage entries must correspond to syntactically valid R code.\n See chapter 'Writing R documentation files' in the 'Writing R\n Extensions' manual.\n\n❯ checking R code for possible problems ... NOTE\n excited_sound: no visible global function definition for 'str_to_upper'\n Undefined global functions or variables:\n str_to_upper\n\n1 error ✖ | 1 warning ✖ | 1 note ✖\nError: R CMD check found ERRORs\nExecution halted\n\nExited with status 1.\n```\n\n## Types of problem\n\n
\n\n| | |\n|-------------|-----------------------------------------------|\n| **ERROR** | Must fix to get the code/example/test working |\n| **WARNING** | Fix if sharing with others |\n| **NOTE** | Fix if submitting to CRAN |\n\n
\n\nIt is possible to submit to CRAN with a NOTE, but it's best avoided.\n\n::: notes\nNOTES best avoid for CRAN as they require a person to respond. One unavoidable case of a NOTE on CRAN submission: first submission of a package\n:::\n\n## Check regularly, fix issues as they arise {.inverse .center .center-h}\n\n::: {.notes}\nWill see later in this session how to do this automatically with Continuous Integration \n:::\n\n# DESCRIPTION {.inverse}\n\n::: {.notes}\nThe are two types of documentation: package level and function level.\n\nThe DESCRIPTION file provides some of the package level documentation (or metadata)\n:::\n\n## Metadata in `DESCRIPTION`\n\n- **Package**: The package name. The `changer` package can help you change the name!\n- **Title**: One line, title case, with no period. Fewer than 65 characters.\n- **Version**\n - for release: MAJOR.MINOR.PATCH version.\n - for development version building on version MAJOR.MINOR.PATCH, use: MAJOR.MINOR.PATCH.9000\n - can be filled in with `usethis::use_version()`\n\n## Notes of version number\n\nGiven a version number MAJOR.MINOR.PATCH, increment the:\n\n- MAJOR version when you make incompatible API changes,\n- MINOR version when you add functionality in a backwards compatible manner, and\n- PATCH version when you make backwards compatible bug fixes.\n\nSee for further guidance on when to bump which part of the version number\n\n \n## Metadata in `DESCRIPTION`\n\n:::{.smaller90}\n- **Authors@R**: A call to `person()` that is run to create the Author field when the package tarball is built. \"aut\" means author, \"cre\" means creator (maintainer), \"ctb\" means contributor. \n\n A placeholder call to `person()` is inserted in `DESCRIPTION` when a package is created with `usethis::create_package()` which can be edited directly:\n\n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n person(\"A\", \"Person\", email = \"a.person@email.com\", \n role = c(\"aut\", \"cre\"),\n comment = c(ORCID = \"0000-0001-2345-6789\"))\n ```\n :::\n\n\n Alternatively, this can be overwritten with a call to `usethis::use_author()`:\n \n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n use_author(\"A\", \"Person\", email = \"a.person@email.com\", \n role = c(\"aut\", \"cre\"),\n comment = c(ORCID = \"0000-0001-2345-6789\"))\n ```\n :::\n\n:::\n\n::: {.notes}\nusethis::use_author() is new in usethis 2.2.0\n:::\n\n## Metadata in `DESCRIPTION`\n\n- **Description**: One paragraph describing what the package does. Keep the width of the paragraph to 80 characters; indent subsequent lines with 4 spaces.\n- **License**: Will discuss later\n- **Encoding**: How to encode text, use UTF-8 encoding.\n- **LazyData**: If `true` data sets in the package are stored in a database during package installation and loaded from the database as required. Recommended if shipping data with package -- `usethis::use_data()` will set this for you.\n\n::: {.notes}\nusethis::create_package() sets UTF-8 encoding\n\nlazy loading means that data is only loaded if needed.\nThis means that they won’t occupy any memory until you use them.\ndo NOT include LazyData: true in DESCRIPTION unless you actually ship data in your package. \n:::\n\n## Open source licenses\n\nThere are three main open source licenses:\n\n- CC0: “public domain”, best for data packages.\n\n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n usethis::use_cc0_license()\n ```\n :::\n\n- MIT: Free for anyone to do anything with (including bundling in closed source product).\n\n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n usethis::use_mit_license()\n ```\n :::\n\n- GPL: Changes and bundles must also be GPL\n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n usethis::use_gpl_license()\n ```\n :::\n\n [If you are including someone else's GPL code directly, you must use GPL yourself.]{.smaller80}\n\n::: {.notes}\nhttps://bookdown.org/rdpeng/RProgDA/open-source-licensing.html\n\nsomewhat personal down to how important you consider open source to be and what you are happy with people doing with your code\n\nSuggest academic: GPL; community/other: MIT \n:::\n\n## Proprietary packages\n\nYou can use `usethis::use_proprietary()` to make it clear that your package isn’t open source.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_proprietary(copyright_holder = \"ACME Ltd\")\n```\n:::\n\n\nIn DESCRIPTION:\n```\nLicense: file LICENSE\n```\nIn LICENSE:\n```\nCopyright 2023 ACME Ltd. All rights reserved.\n```\n\n## Your turn\n\n1. Open the DESCRIPTION file and add a title and description.\n2. Add yourself as an author and creator.\n3. Add an MIT license.\n4. Run the package check.\n5. Commit changes to GitHub.\n\n:::{.callout-tip}\nIn RStudio, you can use the 'Go to file/function' box or Ctrl + . [period] and start typing a file name to open a file for editing.\n:::\n\n# README {.inverse}\n\n## README\n\nThe README is the quick start guide to your package.\n\nIt should include \n\n - a brief overview\n - instructions on how to install the package\n - a few examples\n \nYou should be able to borrow from the DESCRIPTION and help files!\n\nIt's readable on the package's GitHub repo and on the homepage of its website (more on that later).\n\n## Creating a README\n\n**usethis** has functions to set up a README with/without R code\n\n```r\nusethis::use_readme_rmd()\nusethis::use_readme_md()\n```\n\n`README.Rmd` must be rendered to make `README.md` each time it \nis changed.\n\n`usethis::use_readme_rmd()` creates a _pre-commit hook_ to check if `README.Rmd` and `README.md` are out of sync before committing.\n\nUse `build_readme()` to render with the latest version of the code.\n\n::: {.notes}\n`build_readme` creates a temporary version of the package\n:::\n\n# Continuous Integration (CI) {.inverse}\n\n::: {.notes}\nThe practice of merging new code into the repository and running checks each time is known as Continuous Integration.\n:::\n\n## Running automatic checks\n\nGitHub Actions (GHAs) allow you to run code every time you push to GitHub. \n\nThe most useful ones for packages can be selected from a call to `usethis::use_github_action()`:\n\n```{.r}\nuse_github_action()\n```\n\n```\nWhich action do you want to add? (0 to exit)\n(See for other options) \n\n1: check-standard: Run `R CMD check` on Linux, macOS, and Windows\n2: test-coverage: Compute test coverage and report to https://about.codecov.io\n3: pr-commands: Add /document and /style commands for pull requests\n```\n\n`check-standard` sets up a GHA that runs `R CMD check` with the latest release of R on Linux, Mac, and Windows and with both the previous release and development release of R on Linux.\n\n::: {.notes}\nThis behaviour of `use_github_action()` is new in usethis 2.2.\n\n`use_github_actions()` is deprecated.\n:::\n\n## [`use_github_action(\"check-release\")`]{.smaller90}\n\nThe `check-standard` GHA is best-practice for 'serious' projects, e.g. those aiming for CRAN, but is overkill for our purposes. \n\nWe can set up a simpler GHA by specifying an alternative:\n\n```{.r}\nuse_github_action(\"check-release\")\n```\n\nThis sets up a bare-minimum workflow that runs `R CMD check` with the latest release of R on Linux.\n\nIt's good for simple package with no OS-sepcific code, and if you want a quick start with R CI.\n\n::: {.notes}\ni.e. it's perfect for us! \n\nReminder to fix any issues as soon as they arise!\n:::\n\n## Your turn\n\n1. Create a README for `animalsounds` with `usethis::use_readme_rmd()`.\n2. Fill in the description and an example.\n3. Try adding the README in a git commit -- it should fail! Render the README with `build_readme()`, then add both `README.Rmd` and `README.md` in a git commit.\n4. Run `usethis::use_github_action(\"check-release\")`. It adds a badge to the README, so you will need to render the README again.\n5. Commit all the changes to git.\n\n# Vignettes {.inverse}\n\n## Vignettes\n\nVignettes are long-form documentation for your package.\n\nThey use R markdown to integrate code and output into the documentation. Typically:\n\n - A single/main vignette showing a complete workflow. \n - Optional further vignette(s) going deeper on one aspect/application\n - Optional further vignette(s) for specialist audience (methodologists or developers)\n \nA vignette with the same name as the package (e.g., `vignettes/animalsounds.Rmd` or `vignettes/articles/animalsounds.Rmd`) automatically becomes a top-level \"Get started\" link.\n \n::: {.notes}\nFairly short read ~10 minutes\n::: \n\n## `use_vignette()`\n\nEasiest way to get started is with `usethis::use_vignette()`\n\n```r\nusethis::use_vignette(\"name\")\n```\n\nAdds to DESCRIPTION\n\n
\nSuggests: knitr\nVignetteBuilder: knitr\n
\n\nCreates `vignettes/`\n\nDrafts `vignettes/name.Rmd`\n\n::: {.notes}\nuse_vignette() will fill both title fields with the \"name\" of the file, but you'll want to edit to something more descriptive.\n\nNeeds to be .Rmd not .qmd. Quarto is not R-specific and CRAN can't publish .qmd files. \n:::\n\n## Vignette = Rmarkdown + special metadata\n\n```\n---\ntitle: \"Vignette Title\"\nauthor: \"Vignette author\"\ndate: \"2026-06-29\"\noutput: rmarkdown::html_vignette\nvignette: >\n%\\VignetteIndexEntry{Vignette Title}\n%\\VignetteEngine{knitr::rmarkdown}\n%\\VignetteEncoding{UTF-8}\n---\n\n```\n* `html_vignette` output uses a custom style sheet to keep the file size of the \nHTML as small as possible.\n* The `vignette:` field contains special metadata needed when the package is \nbuilt. **Don't forget to change the title here too!**\n\n## Vignette workflow\n\n![](images/vignette_workflow.png){fig-align=\"center\"}\n\n::: {.notes}\nNeed to install package so can call with `library()` \n:::\n\n## Articles\n\nVignette code needs to be able to run on CRAN. There are some cases where [this is not possible](https://r-pkgs.org/vignettes.html#sec-vignettes-eval-option).\n\nAs an alternative, you can use articles instead of vignettes:\n\n```{.r}\nusethis::use_article(\"my-article\")\n```\n\n```\n✔ Adding 'rmarkdown' to Config/Needs/website\n✔ Creating 'vignettes/'\n✔ Creating 'vignettes/articles/'\n✔ Adding '*.html', '*.R' to 'vignettes/.gitignore'\n✔ Writing 'vignettes/articles/my-article.Rmd'\n• Modify 'vignettes/articles/my-article.Rmd'\n✔ Adding '^vignettes/articles$' to '.Rbuildignore'\n```\n\n## Your turn\n\n1. Install your **animalsounds** package and restart R (Install button).\n2. Create a simple vignette, `animalsounds`, that shows how to use `animal_sounds()`.\n3. Fix the \"vignette title\" in the YAML header.\n4. Knit the vignette to preview it.\n5. Run `devtools::install(build_vignettes = TRUE)` to install the package with the vignettes. Call `browseVignettes(\"animalsounds\")` to open your vignette.\n6. Commit your changes to git.\n\n# NEWS {.inverse}\n\n## Track changes in a NEWS file\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_news_md()\n```\n:::\n\n\nAdd news for the latest version at the top.\n\nUse a top-level heading for each release version\n\n```r\n# animalsounds 1.0.0\n```\n\n\n## What news to include\n\nAdd each change in a bulleted list:\n\n- If you have many changes, split into subsections (e.g.\n `## Major changes`, `## Minor improvements`, `## Bug fixes`).\n- Wait until release to decide if subsections are necessary\n \nNote connections to GitHub:\n\n - If related to a GitHub issue, add the issue number, e.g. (`#10`). \n - If related to a pull request, add the pull request number and the author, e.g. (`#101, @hadley`). \n\n# Package website with pkgdown {.inverse}\n\n## A package website pkgdown\n\nThe **pkgdown** package is designed to make it quick and easy to build a website for your package:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\nusethis::use_pkgdown()\n```\n:::\n\n\nWhy have a website for your package?\n\n - Google-ability\n - Easy-to-read and browse documentation and package information in one place\n - Examples with output!\n \n## Build a website\n\n`pkgdown::build_site()` creates a package website based on the \nstandard documentation files.\n\n**Home page**: based on README\n\n**Reference**: \n\n - one page for each help file\n - generates an index page, with functions listed alphabetically by default \n \n::: {.notes}\nFor packages with lots of functions, it's good to organise them by theme or functionality.\n:::\n\n## Build a website (ctd)\n\n**Articles**: one page for each vignette\n\n**Get Started**: if you have a vignette with filename = package name\n\n**News**: based on NEWS.md\n\nPlus:\n\n - A link to your GitHub repo (if listed in the DESCRIPTION url field).\n - A link to the License\n - Any badges added to your README (e.g. from GitHub Actions)\n\n## Hosting your website\n\n- You can host your website directly from its GitHub repo (repo has to be public) \n- The recommended approach is to let GitHub build your page (instead of calling `pkgdown::build_site()` and committing and pushing the artifacts of the built website (i.e., html files) to GitHub\n- Add an action to your GitHub repo to be run automatically every time you push to it to rebuild the site:\n\n ```{.r}\n usethis::use_pkgdown_github_pages()\n ```\n\n- The URL will be https://USERNAME.github.io/animalsounds \n \n::: {.notes}\nuse_pkgdown_github_pages() will:\n\n - call `use_pkgdown()` - allow it to overwrite existing `_pkgdown.yml` (will add the url)\n - set up a `gh-pages` branch from which to deploy the site\n - add a `pkgdown.yml` to .github/workflows\n\nWill still need to call `pkgdown::build_site()` to preview locally\n:::\n\n## Customising your website\n\nYou can add more information to `_pkgdown.yml` to customise the package website:\n\n- curate the index for the Reference page - functions can be grouped and described in categories\n\n \n\n- customise the appearance\n\n \n\n## Your turn\n\n1. Run `usethis::use_pkgdown_github_pages()` -- this will ask you to install **pkgdown** if you don't already have it.\n2. Read through all the output in the console to see the many things that this function does. \n3. Look at the diffs in the Git pane. Commit and push all changes.\n4. Go to your GitHub repo of the package. Click on **Actions**. If there's a green tick next to \"pages build and deployment\" then your site is ready to view!\n5. Click on the link to the website under the **About** section of the repo.\n6. (Bonus) Change the appearance of the site with a Bootswatch theme: . \n\n# Minute cards {.inverse}\n\n- [Cohort 1](https://tally.so/r/VLbEYy)\n- [Cohort 2](https://tally.so/r/kdJa7e)\n\n# End matter {.inverse}\n\n## References\n\nWickham, H and Bryan, J, *R Packages* (2nd edn, in progress), .\n\nR Core Team, *Writing R Extensions*, \n\n## License\n\nLicensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License ([CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/){target=\"_blank\"}).\n\n# Aside notes {.inverse}\n\n## Aside: in case of error\n\nOn running `devtools::check()` you may get an error of the form\n\n``` \nUpdating animalsounds documentation \nError: The specified file is not readable: path-to\\animalsounds\\NAMESPACE \n```\n\nThis can happen if your repo is on a networked drive.\n\nThis is covered in this [Stackoverflow question](https://stackoverflow.com/questions/40530968/overwriting-namespace-and-rd-with-roxygen2) and can be fixed.\n\n## Aside: a fix for networked drives\n\n1. Save a copy of this file: [fix_for_networked_drives.R](../../R/fix_for_networked_drives.R)\n\n Save it somewhere other than the `animalsounds` directory\n\n2. Open the file from the `animalsounds` project session\n\n3. Run the whole file\n\nYou should now find that `devtools::check()` proceeds normally.\n", "supporting": [], "filters": [ "rmarkdown/pagebreak.lua" diff --git a/_freeze/slides/05-publication-maintenance/index/execute-results/html.json b/_freeze/slides/05-publication-maintenance/index/execute-results/html.json index 548f536..01e37e0 100644 --- a/_freeze/slides/05-publication-maintenance/index/execute-results/html.json +++ b/_freeze/slides/05-publication-maintenance/index/execute-results/html.json @@ -1,8 +1,8 @@ { - "hash": "4d851e48fb8a9efb3056db59240b3e73", + "hash": "d63dbb2a06452250cfb1d2c3d73914cc", "result": { "engine": "knitr", - "markdown": "---\ntitle: Packaging data
Publication and maintenance\nsubtitle: R package development workshop
Module 5\nauthor: Forwards teaching team\nformat: forwardspres-revealjs\n---\n\n## Overview\n\n- Publication\n - GitHub\n - R-Universe\n - CRAN\n- Promotion\n- Maintenance\n- *Workshop admin*\n\n::: {.notes}\nThe packaging data section would more naturally fit earlier in the course, but put here because of how the schedule has panned out. \n:::\n\n# Publication {.inverse}\n\n# GitHub {.inverse}\n\n## Your package is already on GitHub\n\nSince your package is already on GitHub, any R user can install it with\n\n```r\nremotes::install_github(\"USER/REPO\")\n```\n\nIf you want to tag it as a release, make sure there's a `NEWS.md` file then run\n\n```r\nusethis::use_version() # set the release version number\n```\n\nCheck the `NEWS.md` file is up-to-date (`use_version()` will modify it) then \n\n```\nusethis::use_github_release()\n```\n\nThis will bundle the source code as a `zip` and a `tar.gz` and make them available from the **Releases** section of the repo homepage.\n\n::: {.notes}\nSee previous session for details about NEWS.md and `usethis::use_news_md()`\n\nuse_github_release() is also part of the CRAN submission process, towards the end of the `use_release_issue()` to-do list.\n:::\n\n# R-Universe {.inverse}\n\n## R-Universe\n\nWith [R-Universe](https://r-universe.dev/), you can create a personal, CRAN-like repository.\n\nYou're in control of what's published in your R-Universe! \n\nIt is a good way to allow users to easily install packages without going through the rigour of the CRAN submission process.\n\nUseful resources:\n\n- Search the whole R-universe: \n- About the R-universe: \n- R-universe help-page: \n\n::: {.notes}\nA project from rOpenSci \n:::\n\n## Installing a package from an R-universe\n\nBinaries are built for Windows and MacOS, which a user can install using \n`install.packages()`, e.g.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n# Install 'warwickplots' from the 'warwick-stats-resources' universe\ninstall.packages('warwickplots', repos = c(\n WSR = 'https://warwick-stats-resources.r-universe.dev',\n CRAN = 'https://cloud.r-project.org')\n)\n```\n:::\n\n\n. . .\n\nAlternatively, you can first set `options(repos)` to enable favourite repositories by default:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\noptions(repos = c(\n WSR = 'https://warwick-stats-resources.r-universe.dev',\n CRAN = 'https://cloud.r-project.org')\n)\ninstall.packages(\"warwickplots\")\n```\n:::\n\n\n\n::: {.notes}\nGive an aside about the warwickplots package\n\nNote that we need to list CRAN as r-universe packages may have dependencies on CRAN packages.\n\nTo make options persist across sessions, add this code to your .RProfile with `usethis::edit_r_profile()`\n\nCRAN repo is listed because r-universe package may have dependencies on packages on CRAN.\n:::\n\n## Create your R-universe\n\nFollow this rOpenSci guide: [How to create your personal CRAN-like repository on R-universe](https://ropensci.org/blog/2021/06/22/setup-runiverse/). \n\n:::{.smaller80}\nIn a nutshell:\n\n1. Create a repository called `.r-universe.dev` on the GitHub account for `username`, e.g. . The repository must contain a file called [packages.json](https://github.com/maelle/maelle.r-universe.dev/blob/main/packages.json) in the standard format, defining at least the `package` name and git `url` for the packages you want to include, e.g.\n \n ```\n [\n {\n \"package\": \"warwickplots\",\n \"url\": \"https://github.com/Warwick-Stats-Resources/warwickplots\"\n }\n ]\n ```\n\n2. Install the [r-universe app](https://github.com/apps/r-universe/installations/new) on the GitHub account that you want to enable. Choose __enable for all repositories__ when asked.\n:::\n\n::: {.notes}\nStill using `maelle` in places as shorter that Warwick-Stats-Resources! \nAlso, nice to give a shout-out to her and her role in rOpenSci.\n\nCopying the example from the r-universe help page, with the example/links to Maelle, but using the example json from WSR to be consistent with previous slide. \n\nMight be worth pointing out that with actual username, don't need `< >`\n:::\n\n## What happens next\n\n- After a few minutes, your source universe will appear on: `https://github.com/r-universe/`\n\n- The universe automatically starts building the packages from your registry. Once finished, they will appear on `https://.r-universe.dev`\n\n- The universe automatically syncs and builds your package git repos once per hour.\n\n- If you encounter any issues, the actions tab in your source universe may show what is going on, for example: \n\n# CRAN {.inverse}\n\n## Why publish on CRAN?\n\n- Sign of quality\n\n - Code is ready to be used (not a beta version)\n - Basic standards: documented code, running examples, etc\n - Works with current version of R and other packages \n - Commitment of maintainer\n- Discoverability\n- Ease of installation\n- Bioconductor, rOpenSci: even higher standards, code review\n\n## It's an involved process\n\n- Read the official [Checklist for CRAN Submissions](https://cran.r-project.org/web/packages/submission_checklist.html) \nto check requirements beyond the automated checks.\n\n- Read the community-created [Prepare for CRAN](https://github.com/ThinkR-open/prepare-for-cran) checklist.\n\n- Useful functions for additional checks:\n - `goodpractice::gp()`\n - `spelling::spell_check_package()`\n\n## `usethis::use_release_issue()`\n\nThis function will first ask you to select the release version (major, minor, patch) then create and open a to-do list as an issue in the package GitHub repo.\n\nFor a first submission, there are around **22** tasks to complete, split into sections, to follow (more-or-less) in order:\n\n- First release (one-time only)\n- Prepare for release\n- Submit to CRAN\n- Wait for CRAN (things to do after package has been accepted)\n\nFor more details on each, see the [Releasing to CRAN](https://r-pkgs.org/release.html) chapter of the R Packages book.\n\n::: {.notes}\nNumber of tasks may vary depending on what you've already done (e.g. use_news_md will appear on list if you haven't already got a NEWS.md file, but won't if you do.)\n\nSome are clearly optional (e.g. drafting a blog post about the release) but most should be followed.\n\nSome relate to promoting the package, rather than the technicalities of releasing it.\n\nThere are some different items for packages already on CRAN - not covered here - see Release chapter of R Packages for more details\n\nWill catch many common 'gotchas', e.g. Title Case for Title, checking all exported functions have @returns and @examples\n:::\n\n## Run \"as CRAN\" checks\n\n[CRAN policies](https://cran.r-project.org/web/packages/policies.html) state that you must run `R CMD check --as-cran` _on the tarball to be uploaded_ with the current version of R-devel.\n\nFirst make sure the package passes check locally:\n```r\ndevtools::check()\n```\n\nThen allow some extra checks:\n```r\ndevtools::check(remote = TRUE, manual = TRUE)\n```\n\nThen send to CRAN's win-builder to check on R-devel\n```r\ndevtools::check_win_devel()\n```\n\nAlso check on Mac (M1)\n\n```r\ndevtools::check_mac_release()\n```\n\n::: {.notes}\nIt's possible that the way your libraries are set up can mask problems with `check()` on your local machine. For example, it's important that your System Library just comes with base and recommended packages and that all packages that you install go in your user library. Aside: Installing R through `rig` takes care of this for you. \n:::\n\n## R-hub v2\n\n- A new (since April 2024) check system.\n- Works best if the package is on GitHub\n- Works with GitHub Actions\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n# run once\ninstall.packages(\"rhub\") \nrhub::rhub_setup() # guides through set-up process\nrhub::rhub_doctor() # checks the set-up\n\n# run the checks\nrhub::rhub_check()\n```\n:::\n\n\n\nSee [the r-hub blog post](https://blog.r-hub.io/2024/04/11/rhub2/) for more details\n\n## `cran-comments.md`\n\nWrite submission notes, generating the `cran-comments.md` file with\n```r\nusethis::use_cran_comments()\n```\n```\n\n ## Test environments\n * local OS X install (R-release)\n * win-builder (R-release, R-devel) \n\n ## R CMD check results\n\n 0 errors | 0 warnings | 1 note\n\n * This is a new release.\n```\n\nThere’s always one note for a new submission.\n\n::: {.notes}\nuse_cran_comments() will populate cran-comments.md with the R CMD check results section. \n\nThe test environments section needs to be filled by hand\n:::\n\n## Submit to CRAN\n\n```{.r}\ndevtools::release()\n```\n\nThis asks you questions which you should carefully read and answer.\n\n::: {.notes}\nThe use_release_issue() function uses devtools::submit_cran() rather than devtools::release(), but the documentation page for submit_cran() recommends using release() instead as it performs more checks. \n:::\n\n## If your submission fails\n\nDo not despair! It happens to everyone, even R-core members.\n\nIf it’s from the CRAN robot, just fix the problem & resubmit.\n\nIf it’s from a human, do not respond to the email and do not argue. Instead update `cran-comments.md` & resubmit.\n\n## For resubmission\n\n```\n This is a resubmission. Compared to the last submission, I\n have:\n\n * First change.\n * Second change.\n * Third change.\n\n --\n\n ## Test environments\n * local OS X install, R 3.2.2\n * win-builder (devel and release)\n\n ## R CMD check results\n ...\n```\n\n## Subsequent submissions to CRAN\n\nProceed as before. If you have reverse dependencies you need to also run \n`R CMD check` on them, and notify CRAN if you have deliberately broken them.\n\nFortunately the **revdepcheck** package makes this fairly easy\n\n```r\nremotes::install_github(\"r-lib/revdepcheck\")\nusethis::use_revdep()\n\nrevdepcheck::revdep_check()\nrevdepcheck::revdep_report_cran() # get intermediate report for cran \n```\n\n# Promotion {.inverse}\n\n## Promoting your package\n\n- Some promotion will/may be done for you: [CRANberries](https://dirk.eddelbuettel.com/cranberries/), \nsearch engines (vignette/pkgdown site)\n- Some channels are obvious: personal website, blog, Mastodon (#RStats)\n- Publicize your new package via R Weekly \n - Add to the weekly news blog, see [CONTRIBUTING](https://github.com/rweekly/rweekly.org/blob/gh-pages/CONTRIBUTING.md), and example pull requests [new package](https://github.com/rweekly/rweekly.org/pull/279), [new version](https://github.com/rweekly/rweekly.org/pull/277).\n- Would your package fit in a CRAN Task View? \n - Check [GitHub organization](https://github.com/cran-task-views/ctv) for how to propose addition.\n \n::: {.notes}\nAnd Twitter, if you must.\n:::\n\n## Talks\n\n- Meetups: Warwick RUG, Coventry R-Ladies (or your local groups)\n- Conferences \n - **General**: useR!, posit::conf, satRdays\n - **Specific**: R/Finance, BioC, Psychoco\n - **Non R-specific**: Royal Statistical Society (RSS), ???\n- Conferences provide greater exposure, particular to people working in relevant\nfield(s).\n- Don't forget to share your slides! (Conference/personal website, LinkedIn, RPubs, Slideshare)\n\n## Paper\n\n:::{.smaller90}\n- A paper not only promotes your package but benefits from peer review\n - Paper can also overlap with vignette\n- Traditional journals:\n - **Open Source Software**: The R Journal, Journal of Statistical Software\n - **Computing**: Computational Statistics and Data Analysis, Journal of \n Computational and Graphical Statistics, SoftwareX\n - **Science**: Bioinformatics, PLOS ONE, Method in Ecology and Evolution\n- Alternative journals:\n - F1000research Bioconductor/R package gateway: publish, then open review\n - [Journal Open Source Software](https://joss.theoj.org/): open code review, short descriptive paper\n::: \n\n# Maintenance {.inverse}\n\n## `usethis::use_upkeep_issue()`\n\nThis is a new function in **usethis**. Like `usethis::use_release_issue()`, it opens a GitHub issue with an (opinionated) to-do list of tasks that should be ticked off for your package (at least) once a year.\n\nThe **tidyverse** team think of this like 'spring cleaning' for packages.\n\nBlog post: [Package spring cleaning](https://www.tidyverse.org/blog/2023/06/spring-cleaning-2023/)\n\n## Interacting with users\n\n- Bug reports/help requests\n - Can show where documentation/tests need improving\n - Help you find out who's using your package and what for\n - Can give ideas for new features\n - Can lead to collaborations\n- Avoid using email, so that other people can benefit\n - GitHub issues\n - Stackoverflow questions \n \n## Interacting with developers\n\n:::{.smaller80}\n* Write developer documentation -- remember you can add non-vignette articles with `usethis::use_article()`\n* Add a code of conduct, e.g. Contributor Covenant\n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n usethis::use_code_of_conduct()\n ```\n :::\n\n* Add a CONTRIBUTING.md to your GitHub repository\n - Do you have a style guide?\n - Reminders to run check/tests/add NEWS item to pull requests\n* Use tags to highlight issues: the following are promoted by GitHub, e.g. `help wanted`, `good first issue`\n* Add topics to your GitHub repo so potential contributors can find it \n:::\n\n## Consider the longer-term\n\n* Work on new features and bug fixes for the next release\n* Buddy-up\n - Review each other's code\n - Co-author each other's packages\n* Take advantage of events e.g. [Hacktoberfest](https://hacktoberfest.digitalocean.com/), Closember \n* Start work on your next package!\n\n## Congratulations 🎉 {.inverse .center .center-h}\n\nYou have written a package!\n\n## We value your feedback\n\nPlease take a few minutes to complete this anonymous [feedback form](https://docs.google.com/forms/d/e/1FAIpQLSenADnqVZfmWCTlz1VMMfuod1qFcqqWd0kaj6ekZ4Y6QFAzlQ/viewform?usp=dialog)\n\nThanks!\n\n# End matter {.inverse}\n\n## References\n\nWickham, H and Bryan, J, _R Packages_ (2nd edn, in progress), .\n\nR Core Team, _Writing R Extensions_, \n\nrOpenSci Packages: Development, Maintenance, and Peer Review \n\nrOpenSci Statistical Software Peer Review (especially Chapter 3: Guide for Authors) \n\n## License\n\nLicensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License ([CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/){target=\"_blank\"}).\n", + "markdown": "---\ntitle: Packaging data
Publication and maintenance\nsubtitle: R package development workshop
Module 5\nauthor: Forwards teaching team\nformat: forwardspres-revealjs\n---\n\n## Overview\n\n- Publication\n - GitHub\n - R-Universe\n - CRAN\n- Promotion\n- Maintenance\n- *Workshop admin*\n\n::: {.notes}\nThe packaging data section would more naturally fit earlier in the course, but put here because of how the schedule has panned out. \n:::\n\n# Publication {.inverse}\n\n# GitHub {.inverse}\n\n## Your package is already on GitHub\n\nSince your package is already on GitHub, any R user can install it with\n\n```r\nremotes::install_github(\"USER/REPO\")\n```\n\nIf you want to tag it as a release, make sure there's a `NEWS.md` file then run\n\n```r\nusethis::use_version() # set the release version number\n```\n\nCheck the `NEWS.md` file is up-to-date (`use_version()` will modify it) then \n\n```\nusethis::use_github_release()\n```\n\nThis will bundle the source code as a `zip` and a `tar.gz` and make them available from the **Releases** section of the repo homepage.\n\n::: {.notes}\nSee previous session for details about NEWS.md and `usethis::use_news_md()`\n\nuse_github_release() is also part of the CRAN submission process, towards the end of the `use_release_issue()` to-do list.\n:::\n\n# R-Universe {.inverse}\n\n## R-Universe\n\nWith [R-Universe](https://r-universe.dev/), you can create a personal, CRAN-like repository.\n\nYou're in control of what's published in your R-Universe! \n\nIt is a good way to allow users to easily install packages without going through the rigour of the CRAN submission process.\n\nUseful resources:\n\n- Search the whole R-universe: \n- About the R-universe: \n- R-universe help-page: \n\n::: {.notes}\nA project from rOpenSci \n:::\n\n## Installing a package from an R-universe\n\nBinaries are built for Windows and MacOS, which a user can install using \n`install.packages()`, e.g.\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n# Install 'warwickplots' from the 'warwick-stats-resources' universe\ninstall.packages('warwickplots', repos = c(\n WSR = 'https://warwick-stats-resources.r-universe.dev',\n CRAN = 'https://cloud.r-project.org')\n)\n```\n:::\n\n\n. . .\n\nAlternatively, you can first set `options(repos)` to enable favourite repositories by default:\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\noptions(repos = c(\n WSR = 'https://warwick-stats-resources.r-universe.dev',\n CRAN = 'https://cloud.r-project.org')\n)\ninstall.packages(\"warwickplots\")\n```\n:::\n\n\n\n::: {.notes}\nGive an aside about the warwickplots package\n\nNote that we need to list CRAN as r-universe packages may have dependencies on CRAN packages.\n\nTo make options persist across sessions, add this code to your .RProfile with `usethis::edit_r_profile()`\n\nCRAN repo is listed because r-universe package may have dependencies on packages on CRAN.\n:::\n\n## Create your R-universe\n\nFollow the official R-Universe: [Set up your own universe](https://docs.r-universe.dev/publish/set-up.html/). \n\n:::{.smaller80}\nIn a nutshell:\n\n1. Create a repository called `.r-universe.dev` on the GitHub account for `username`, e.g. . The repository must contain a file called [packages.json](https://github.com/maelle/maelle.r-universe.dev/blob/main/packages.json) in the standard format, defining at least the `package` name and git `url` for the packages you want to include, e.g.\n \n ```\n [\n {\n \"package\": \"warwickplots\",\n \"url\": \"https://github.com/Warwick-Stats-Resources/warwickplots\"\n }\n ]\n ```\n\n2. Install the [r-universe app](https://github.com/apps/r-universe/installations/new) on the GitHub account that you want to enable. Choose __enable for all repositories__ when asked.\n:::\n\n::: {.notes}\nStill using `maelle` in places as shorter that Warwick-Stats-Resources! \nAlso, nice to give a shout-out to her and her role in rOpenSci.\n\nCopying the example from the r-universe help page, with the example/links to Maelle, but using the example json from WSR to be consistent with previous slide. \n\nMight be worth pointing out that with actual username, don't need `< >`\n:::\n\n## What happens next\n\n- After a few minutes, your source universe will appear on: `https://github.com/r-universe/`\n\n- The universe automatically starts building the packages from your registry. Once finished, they will appear on `https://.r-universe.dev`\n\n- The universe automatically syncs and builds your package git repos once per hour.\n\n- If you encounter any issues, the actions tab in your source universe may show what is going on, for example: \n\n- Or check the [R-Universe documentation](https://docs.r-universe.dev/).\n\n# CRAN {.inverse}\n\n## Why publish on CRAN?\n\n- Sign of quality\n\n - Code is ready to be used (not a beta version)\n - Basic standards: documented code, running examples, etc\n - Works with current version of R and other packages \n - Commitment of maintainer\n- Discoverability\n- Ease of installation\n- Bioconductor, rOpenSci: even higher standards, code review\n\n## It's an involved process\n\n- Read the official [Checklist for CRAN Submissions](https://cran.r-project.org/web/packages/submission_checklist.html) \nto check requirements beyond the automated checks.\n\n- Read the community-created [Prepare for CRAN](https://github.com/ThinkR-open/prepare-for-cran) checklist.\n\n- Useful functions for additional checks:\n - `goodpractice::gp()`\n - `spelling::spell_check_package()`\n\n## `usethis::use_release_issue()`\n\nThis function will first ask you to select the release version (major, minor, patch) then create and open a to-do list as an issue in the package GitHub repo.\n\nFor a first submission, there are around **22** tasks to complete, split into sections, to follow (more-or-less) in order:\n\n- First release (one-time only)\n- Prepare for release\n- Submit to CRAN\n- Wait for CRAN (things to do after package has been accepted)\n\nFor more details on each, see the [Releasing to CRAN](https://r-pkgs.org/release.html) chapter of the R Packages book.\n\n::: {.notes}\nNumber of tasks may vary depending on what you've already done (e.g. use_news_md will appear on list if you haven't already got a NEWS.md file, but won't if you do.)\n\nSome are clearly optional (e.g. drafting a blog post about the release) but most should be followed.\n\nSome relate to promoting the package, rather than the technicalities of releasing it.\n\nThere are some different items for packages already on CRAN - not covered here - see Release chapter of R Packages for more details\n\nWill catch many common 'gotchas', e.g. Title Case for Title, checking all exported functions have @returns and @examples\n:::\n\n## Run \"as CRAN\" checks\n\n[CRAN policies](https://cran.r-project.org/web/packages/policies.html) state that you must run `R CMD check --as-cran` _on the tarball to be uploaded_ with the current version of R-devel.\n\nFirst make sure the package passes check locally:\n```r\ndevtools::check()\n```\n\nThen allow some extra checks:\n```r\ndevtools::check(remote = TRUE, manual = TRUE)\n```\n\nThen send to CRAN's win-builder to check on R-devel\n```r\ndevtools::check_win_devel()\n```\n\nAlso check on Mac (M1)\n\n```r\ndevtools::check_mac_release()\n```\n\n::: {.notes}\nIt's possible that the way your libraries are set up can mask problems with `check()` on your local machine. For example, it's important that your System Library just comes with base and recommended packages and that all packages that you install go in your user library. Aside: Installing R through `rig` takes care of this for you. \n:::\n\n## R-hub v2\n\n- A new (since April 2024) check system.\n- Works best if the package is on GitHub\n- Works with GitHub Actions\n\n\n::: {.cell layout-align=\"center\"}\n\n```{.r .cell-code}\n# run once\ninstall.packages(\"rhub\") \nrhub::rhub_setup() # guides through set-up process\nrhub::rhub_doctor() # checks the set-up\n\n# run the checks\nrhub::rhub_check()\n```\n:::\n\n\n\nSee [the r-hub blog post](https://blog.r-hub.io/2024/04/11/rhub2/) for more details\n\n## `cran-comments.md`\n\nWrite submission notes, generating the `cran-comments.md` file with\n```r\nusethis::use_cran_comments()\n```\n```\n\n ## Test environments\n * local OS X install (R-release)\n * win-builder (R-release, R-devel) \n\n ## R CMD check results\n\n 0 errors | 0 warnings | 1 note\n\n * This is a new release.\n```\n\nThere’s always one note for a new submission.\n\n::: {.notes}\nuse_cran_comments() will populate cran-comments.md with the R CMD check results section. \n\nThe test environments section needs to be filled by hand\n:::\n\n## Submit to CRAN\n\n```{.r}\ndevtools::release()\n```\n\nThis asks you questions which you should carefully read and answer.\n\n::: {.notes}\nThe use_release_issue() function uses devtools::submit_cran() rather than devtools::release(), but the documentation page for submit_cran() recommends using release() instead as it performs more checks. \n:::\n\n## If your submission fails\n\nDo not despair! It happens to everyone, even R-core members.\n\nIf it’s from the CRAN robot, just fix the problem & resubmit.\n\nIf it’s from a human, do not respond to the email and do not argue. Instead update `cran-comments.md` & resubmit.\n\n## For resubmission\n\n```\n This is a resubmission. Compared to the last submission, I\n have:\n\n * First change.\n * Second change.\n * Third change.\n\n --\n\n ## Test environments\n * local OS X install, R 3.2.2\n * win-builder (devel and release)\n\n ## R CMD check results\n ...\n```\n\n## Subsequent submissions to CRAN\n\nProceed as before. If you have reverse dependencies you need to also run \n`R CMD check` on them, and notify CRAN if you have deliberately broken them.\n\nFortunately the **revdepcheck** package makes this fairly easy\n\n```r\nremotes::install_github(\"r-lib/revdepcheck\")\nusethis::use_revdep()\n\nrevdepcheck::revdep_check()\nrevdepcheck::revdep_report_cran() # get intermediate report for cran \n```\n\n# Promotion {.inverse}\n\n## Promoting your package\n\n- Some promotion will/may be done for you: [CRANberries](https://dirk.eddelbuettel.com/cranberries/), \nsearch engines (vignette/pkgdown site)\n- Some channels are obvious: personal website, blog, Mastodon (#RStats)\n- Publicize your new package via R Weekly \n - Add to the weekly news blog, see [CONTRIBUTING](https://github.com/rweekly/rweekly.org/blob/gh-pages/CONTRIBUTING.md), and example pull requests [new package](https://github.com/rweekly/rweekly.org/pull/279), [new version](https://github.com/rweekly/rweekly.org/pull/277).\n- Would your package fit in a CRAN Task View? \n - Check [GitHub organization](https://github.com/cran-task-views/ctv) for how to propose addition.\n \n::: {.notes}\nAnd Twitter, if you must.\n:::\n\n## Talks\n\n- Meetups: Warwick RUG, Coventry R-Ladies (or your local groups)\n- Conferences \n - **General**: useR!, posit::conf, satRdays\n - **Specific**: R/Finance, BioC, Psychoco\n - **Non R-specific**: Royal Statistical Society (RSS), ???\n- Conferences provide greater exposure, particular to people working in relevant\nfield(s).\n- Don't forget to share your slides! (Conference/personal website, LinkedIn, RPubs, Slideshare)\n\n## Paper\n\n:::{.smaller90}\n- A paper not only promotes your package but benefits from peer review\n - Paper can also overlap with vignette\n- Traditional journals:\n - **Open Source Software**: The R Journal, Journal of Statistical Software\n - **Computing**: Computational Statistics and Data Analysis, Journal of \n Computational and Graphical Statistics, SoftwareX\n - **Science**: Bioinformatics, PLOS ONE, Method in Ecology and Evolution\n- Alternative journals:\n - F1000research Bioconductor/R package gateway: publish, then open review\n - [Journal Open Source Software](https://joss.theoj.org/): open code review, short descriptive paper\n::: \n\n# Maintenance {.inverse}\n\n## `usethis::use_upkeep_issue()`\n\nThis is a new function in **usethis**. Like `usethis::use_release_issue()`, it opens a GitHub issue with an (opinionated) to-do list of tasks that should be ticked off for your package (at least) once a year.\n\nThe **tidyverse** team think of this like 'spring cleaning' for packages.\n\nBlog post: [Package spring cleaning](https://www.tidyverse.org/blog/2023/06/spring-cleaning-2023/)\n\n## Interacting with users\n\n- Bug reports/help requests\n - Can show where documentation/tests need improving\n - Help you find out who's using your package and what for\n - Can give ideas for new features\n - Can lead to collaborations\n- Avoid using email, so that other people can benefit\n - GitHub issues\n - Stackoverflow questions \n \n## Interacting with developers\n\n:::{.smaller80}\n* Write developer documentation -- remember you can add non-vignette articles with `usethis::use_article()`\n* Add a code of conduct, e.g. Contributor Covenant\n\n ::: {.cell layout-align=\"center\"}\n \n ```{.r .cell-code}\n usethis::use_code_of_conduct()\n ```\n :::\n\n* Add a CONTRIBUTING.md to your GitHub repository\n - Do you have a style guide?\n - Reminders to run check/tests/add NEWS item to pull requests\n* Use tags to highlight issues: the following are promoted by GitHub, e.g. `help wanted`, `good first issue`\n* Add topics to your GitHub repo so potential contributors can find it \n:::\n\n## Consider the longer-term\n\n* Work on new features and bug fixes for the next release\n* Buddy-up\n - Review each other's code\n - Co-author each other's packages\n* Take advantage of events e.g. [Hacktoberfest](https://hacktoberfest.digitalocean.com/), Closember \n* Start work on your next package!\n\n## Congratulations 🎉 {.inverse .center .center-h}\n\nYou have written a package!\n\n## We value your feedback\n\nPlease take a few minutes to complete this anonymous [feedback form](https://docs.google.com/forms/d/e/1FAIpQLSenADnqVZfmWCTlz1VMMfuod1qFcqqWd0kaj6ekZ4Y6QFAzlQ/viewform?usp=dialog)\n\nThanks!\n\n# End matter {.inverse}\n\n## References\n\nWickham, H and Bryan, J, _R Packages_ (2nd edn, in progress), .\n\nR Core Team, _Writing R Extensions_, \n\nrOpenSci Packages: Development, Maintenance, and Peer Review \n\nrOpenSci Statistical Software Peer Review (especially Chapter 3: Guide for Authors) \n\n## License\n\nLicensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License ([CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/){target=\"_blank\"}).\n", "supporting": [], "filters": [ "rmarkdown/pagebreak.lua" diff --git a/prerequisites.qmd b/prerequisites.qmd index 2c3d129..68820f9 100644 --- a/prerequisites.qmd +++ b/prerequisites.qmd @@ -2,7 +2,7 @@ title: Prerequisites --- -*Last updated: May 11th, 2026* +*Last updated: May 12th, 2026* ## Prior knowledge @@ -15,15 +15,6 @@ Two of the modules from the Warwick-Stats-Resources R Foundations course covers - [Introduction to R and RStudio](https://warwick-stats-resources.github.io/r-foundations/content/01-intro/) - [Programming in R](https://warwick-stats-resources.github.io/r-foundations/content/03-programming/) -## git and GitHub - -Although there will be some time dedicated to this in the workshop, it would be advantageous if you already have: - -- `git` installed on your computer. See -- a [GitHub](https://github.com) account -- a GitHub PAT configured to work with RStudio -- the vignette from the usethis package on [Managing Git(Hub) Credentials](https://usethis.r-lib.org/articles/git-credentials.html) goes through this. - -[Happy Git and GitHub for the useR](https://happygitwithr.com/) is an excellent resource for troubleshooting git related issues and checking that your setup works. Note that while the git/GitHub setup is convenient for sharing packages, you can create a package locally without it. ## R and RStudio @@ -37,11 +28,11 @@ Download the pre-compiled binary for your OS from https://cloud.r-project.org/ a **For Windows** -Click "Download R for Windows", then "base", then "Download R-4.5.0 for Windows". This will download an `.exe` file; once downloaded, open to start the installation. +Click "Download R for Windows", then "base", then "Download R-4.6.0 for Windows". This will download an `.exe` file; once downloaded, open to start the installation. **For Mac** -Click "Download R for macOS", then "R-4.5.0-arm64.pkg" (the first option) to download the installer for Macs with **Apple Silicon** chips or "R-4.5.0-x86_64.pkg" (the second option) to download the installer for Macs with **Intel** chips. Run the installer to complete installation. +Click "Download R for macOS", then "R-4.6.0-arm64.pkg" (the first option) to download the installer for Macs with **Apple Silicon** chips or "R-4.6.0-x86_64.pkg" (the second option) to download the installer for Macs with **Intel** chips. Run the installer to complete installation. **For Linux** @@ -63,7 +54,7 @@ Download the relevant installer for your OS listed under "Installers for Support ### Development Tools -Some additional tools may be required to compile R packages from source. +Some additional tools are required to compile R packages from source, which is a necessary part of the package development process. These tools are not part of a typical R/RStudio setup so please follow the steps below to install them. **For Windows with no admin rights** @@ -96,3 +87,16 @@ If you installed `r-base-dev`, when installing R, you should have all you need to build packages from source. Otherwise return to the instructions for installing R for your distribution and follow the instructions there to install the additional tools. + +## git and GitHub + +Although there will be some time dedicated to this in the workshop, it would be advantageous if you already have: + +- `git` installed on your computer. See +- a [GitHub](https://github.com) account +- a GitHub PAT configured to work with RStudio -- the vignette from the usethis package on [Managing Git(Hub) Credentials](https://usethis.r-lib.org/articles/git-credentials.html) goes through this. + +[Happy Git and GitHub for the useR](https://happygitwithr.com/) is an excellent resource for troubleshooting git related issues and checking that your setup works. + +Whilst git/GitHub isn't essential for package development, it is best practice and we will be making use of GitHub features during the course, e.g. publishing a package documentation website using GitHub pages. However, you can create a package locally without it. + diff --git a/slides/01-package-foundations/index.qmd b/slides/01-package-foundations/index.qmd index 32a8354..cdac885 100644 --- a/slides/01-package-foundations/index.qmd +++ b/slides/01-package-foundations/index.qmd @@ -266,7 +266,9 @@ Option 2 We'll be using the following tools for package development: - RStudio: to manage and edit the package source code + - Feel free to use any IDE you're comfortable with - git + GitHub: to version control the package source code + - See if unfamiliar - **devtools** and **usethis** R packages: - **devtools** for functions supporting the development workflow - **usethis** for setup tasks @@ -344,6 +346,15 @@ It's also a good idea to vaccinate. This implements best practice by adding file usethis::git_vaccinate() ``` +## Default branch + +Set the default branch to "main" (globally) + +```{r} +#| eval: false +usethis::git_default_branch_configure(name = "main") +``` + ## Create a GitHub PAT The **usethis** package uses personal access tokens (PAT) to communicate with GitHub. @@ -536,10 +547,10 @@ usethis::use_github() ``` ```{r eval = FALSE} - ✔ Creating GitHub repository 'Warwick-Stats-Resources/animalsounds' - ✔ Setting remote 'origin' to 'https://github.com/Warwick-Stats-Resources/animalsounds.git' - ✔ Setting URL field in DESCRIPTION to 'https://github.com/Warwick-Stats-Resources/animalsounds' - ✔ Setting BugReports field in DESCRIPTION to 'https://github.com/Warwick-Stats-Resources/animalsounds/issues' + ✔ Creating GitHub repository 'forwards/animalsounds' + ✔ Setting remote 'origin' to 'https://github.com/forwards/animalsounds.git' + ✔ Setting URL field in DESCRIPTION to 'https://github.com/forwards/animalsounds' + ✔ Setting BugReports field in DESCRIPTION to 'https://github.com/forwards/animalsounds/issues' There is 1 uncommitted file: * 'DESCRIPTION' Is it ok to commit it? @@ -551,10 +562,12 @@ usethis::use_github() Choose the affirmative option! (The exact options may vary.) +Note that you will see `/animalsounds` instead of `forwards/animalsounds`. + ::: {.notes} Take a look at the repo on GitHub. There is no `/R` folder as that folder is empty at the moment! -During the demo, will need to run `use_github("Warwick-Stats-Resources")` as by default use_github goes to my personal repo +During the demo, will need to run `use_github("forwards")` as by default use_github goes to my personal repo ::: ## Adding functions @@ -648,6 +661,11 @@ To actually install the package: - from GitHub: `remotes::install_github("USERNAME/REPO")` or `pak::pak("USERNAME/REPO")` +# Minute cards {.inverse} + +- [Cohort 1](https://tally.so/r/ODdWrY) +- [Cohort 2](https://tally.so/r/Zj15YB) + # End matter {.inverse} ## References diff --git a/slides/02-documentation/index.qmd b/slides/02-documentation/index.qmd index cc5c993..490993f 100644 --- a/slides/02-documentation/index.qmd +++ b/slides/02-documentation/index.qmd @@ -9,13 +9,30 @@ format: forwardspres-revealjs - Documenting functions with roxygen2 - NAMESPACE: exporting functions +- Dependencies - NAMESPACE: importing functions # Function documentation with roxygen2 {.inverse} ## roxygen2 -The **roxygen2** package generates documentation from specially formatted comments, that we write above the function code, e.g. +The [**roxygen2**](https://roxygen2.r-lib.org/index.html) package generates documentation from specially formatted comments. + +comments -> `.Rd files` -> HTML + +This powers R's help system, e.g. `?function` and package websites. + +**roxygen2** 8.0.0 was released May 2026, with over 100 improvements and bug fixes. + +[This blog post](https://opensource.posit.co/blog/2026-05-01_roxygen2-8-0-0/) outlines the major changes. + +```{r} +packageVersion("roxygen2") +``` + +## roxygen2 comments + +We write comments above the function code, e.g. ```{r, eval = FALSE} #' @param x A numeric vector. @@ -179,7 +196,7 @@ Internal | External --------------------------- | ------------- Only for use within package | For use by others Documentation optional | Must be documented -Easily changed | Changing will break other people’s code +Easily changed | Changing could break other people’s code ## Default NAMESPACE @@ -213,8 +230,6 @@ NAMESPACE for each function that has an `#' @export` comment. export(fun1) ``` - - ## What to export Only export functions that you want your package users to use, i.e. those that are relevant to the purpose of the package. @@ -276,7 +291,7 @@ Most commonly used mark-up is easier with markdown (and can be mixed with `.Rd` * To a function in a different package: `[pkg::func()]` * With different link text, e.g. `[link text][func()]` -For more details, see the [(R)Markdown support](https://cran.r-project.org/web/packages/roxygen2/vignettes/rd-formatting.html) vignette. +For more details, see the [(R)Markdown support](https://roxygen2.r-lib.org/articles/rd-formatting.html) vignette. ::: {.notes} Seems you no longer need to specify `pkg::` - will find the documentation in any installed package. For functions in multiple packages, e.g. `select()` help pane will offer a choice. Probably still best practice to use `pkg::` for disambiguation, though still may not be necessary for base functions @@ -285,7 +300,7 @@ Seems you no longer need to specify `pkg::` - will find the documentation in any ## Your turn 1. Add some details to the help page for `animal_sounds()`, with a link to `paste0()` and some markdown syntax. -2. Add a link to a function from a package you don't have installed (perhaps `basemodels::dummy_classifier()`). +2. Add a link to a function from a package you don't have installed (perhaps `grumpy::read_npy()`). 3. Run `devtools::document()` and check the link in the help page. What happens? 4. Run `devtools::check()`. Does the link cause problems? 5. Delete the link to the package you don't have installed and run `devtools::document()` again. @@ -299,6 +314,15 @@ In 3, the link will still be created (and will work if you subsequently install # Dependencies {.inverse} +## No `library()` calls! + +We **cannot** use `library()` calls in R packages. + +We need another way to access functions in other packages, +and to ensure that users of our package have those other packages installed on their system too. + +To do this, we can take **dependencies** on other packages. + ## Dependencies Dependencies are other R packages that our package uses. There are three types of dependency: @@ -363,28 +387,56 @@ By default, packages will be added to "Imports". ```{r} #| eval: false -usethis::use_package("rlang") -usethis::use_package("glue", type = "Suggests") +usethis::use_package("cli") ``` -# NAMESPACE: imports {.inverse} - -## You might get tired of using `::` all the time - -Or you might want to use an infix function +``` +✔ Adding cli to Imports field in DESCRIPTION. +☐ Refer to functions with `cli::fun()`. +``` +. . . ```{r} #| eval: false -`%-=%` <- roperators::`%-=%` +usethis::use_package("withr", type = "Suggests") +``` -center <- function(df, select) { - for (nm in names(df[select])){ - # overwrite column after subtracting the mean - df[[nm]] %-=% mean(df[[nm]]) - } - df -} ``` +✔ Adding withr to Suggests field in DESCRIPTION. +☐ Use `requireNamespace("withr", quietly = TRUE)` to test if + withr is installed. +☐ Then directly refer to functions with `withr::fun()`. +``` + +# NAMESPACE: imports {.inverse} + +## Accessing functions in other packages + +Once we have taken a dependency on a *package*, +we have a choice about how to access the *functions* in that package: + +- directly, with `pkgname::function()` +- importing the function, then just calling `function()` + +## Advantages of `pkgname::function()` + +- Makes it really clear to you, and other readers of your code, where functions have come from. +- Requires no further setup +- Necessary to dissambiguate when have two functions with the same name in two different packages + +## Advantages of importing a function + +- Don't need to keep typing `pkgname::` - this can get tedious and make code much more noisy +- Code is slightly more efficient to run (though only by about 2 microseconds) + - Difference will only be noticeable if running 100,000+ calls in a tight loop +- Some packages already have function names that make it clear where they are from, e.g. **stringr** with `str_*()` +- Necessary for infix operators, e.g. `%>%` + - N.B. recommendation is now to use native pipe `|>` in packages instead. + +. . . + +Largely a matter of personal preference. A hybrid approach is also fine! + ## Aside: `%>%` vs `|>` @@ -402,7 +454,9 @@ Depends: - Use the methods outlined in [this tidyverse blog post](https://www.tidyverse.org/blog/2023/04/base-vs-magrittr-pipe/#using-the-native-pipe-in-packages) to ensure the package can still work with older versions of R. -## You can `import` functions into the package +See [slides 28-31 here](https://warwick-stats-resources.github.io/r-foundations-2025/slides/02-data-wrangling/index.html#/a-note-about-pipes-vs) for more about the pipes, and [this blog post](https://ivelasq.rbind.io/blog/understanding-the-r-pipe/). + +## Importing functions into the package manually ```{r} #| eval: false @@ -419,29 +473,28 @@ col_summary <- function(df, fun) { `devtools::document()` will add corresponding `import()` statements to the NAMESPACE, e.g. `import(purr, keep, modify)`. -Adding formal imports is slightly more efficient than using `::`. - Here, the `@importFrom` tag is placed above the function in which the imported function is used. -## Package-level import file +## Package-level import file, manually Imports belong to the package, not to individual functions, so alternatively you can recognise this by storing them in a central location, e.g. `R/animalsounds-package.R` ```r #' @importFrom purrr keep modify -#' @importFrom roperators %-=% +#' @importFrom magrittr %>% NULL ``` -::: {.notes} -This removes the possibility of multiple (redundant) imports of the same function. But harder to remember to remove import if function changes! It's a matter of personal taste. -::: +This removes the possibility of multiple (redundant) imports of the same function. +But harder to remember to remove import if function changes! It's a matter of personal taste. ## `usethis::use_import_from()` There can be several steps to importing a function. `usethis::use_import_from()` takes care of all of them. +This is a (preferred) alternative to writing `@importFrom` tags manually. + It will first create the package documentation file `R/animalsounds-package.R` (if it doesn't already exist -- you will also need to agree to this). ```{r} @@ -462,7 +515,9 @@ use_import_from() is opinionated in implementing package-level import (rather th May need to close and reopen R/animalsounds-package.R to see the changes. ::: -## It may be tempting to import a whole package... +## You *can* import all functions from a package... + +It may be tempting to import all the functions from a package: ```r #' @import purrr @@ -476,7 +531,9 @@ col_summary <- function(df, fun) { ``` -## ...but it is dangerous +## ... but don't! + +**It is dangerous:** ```r #' @import pkg1 @@ -484,13 +541,16 @@ col_summary <- function(df, fun) { fun <- function(x) { fun1(x) + fun2(x) } - ``` Works today... ... but next year, what if **pkg2** adds a `fun1` function? +**And not clear:** + +You lose direct evidence in your package of which functions come from which packages. + ## Metapackages It is bad practice to import "metapackages" (i.e. packages that are collections of packages), such as **tidyverse**. @@ -503,8 +563,8 @@ See the blog post Testing subtitle: R package development workshop
Module 3 author: Forwards teaching team format: forwardspres-revealjs @@ -13,6 +13,15 @@ format: forwardspres-revealjs # Packaging data {.inverse} +## Why package data + +- The purpose of the package is to make data available + - e.g. [nycflights13](https://github.com/tidyverse/nycflights13), [palmerpenguins](https://github.com/allisonhorst/palmerpenguins) +- To share (toy) data to use with the package functions + - e.g. [readr](https://github.com/tidyverse/readr) + - Useful for examples and vignettes +- To use internally, e.g. look-up tables + ## Including data There are 3 types of data we might want to include: @@ -140,23 +149,32 @@ system.file("extdata", "mtcars.csv", package = "readr") 5. Run `devtools::document()` to create the documentation for the `farm_animals` data. Preview the documentation to check it. 6. Commit all the changes to your repo. +# Unit testing with testthat {.inverse} +## You (almost certainly) already test your code -# Unit testing with testthat {.inverse} +You write code, run it, check outputs (maybe for a few different inputs). -## Why test? +Maybe you also use print statements for debugging. -We build new functions one bit at a time. +. . . -What if a new thing we add changes the existing functionality? +**Unit tests make your informal testing systematic so it scales with your package** -How can we check and be sure all the old functionality still works with New Fancy Feature? +## Testing beyond your own workflow -Unit Tests! +You are building something that others will use, in possibly unpredictable ways. -::: {.notes} -Gives confidence to package users as well -::: +- Future you (or contributors) will modify code and need to verify nothing broke +- Users will pass inputs you didn't anticipate +- Documentation alone won't prevent misuse + +## Practical benefits + +- Refactoring confidence: change internals or add new functionality without fear of breaking user-facing behavior +- Regression prevention: every bug fix becomes a test that ensures it stays fixed +- Living documentation: tests show how functions are actually meant to be used +- Faster debugging: when something breaks, tests help isolate exactly where ## Set up test infrastructure @@ -194,6 +212,13 @@ Test every individual task the function completes separately. Check both for successful situations and for expected failure situations. +::: {.callout-tip} +Look at the `"tests/testthat"` directory and some test files in the GitHub repos for some well-respected packages to see how their tests are organised, e.g. + +- +- +::: + ## Expectations Three expectations cover the vast majority of cases @@ -501,6 +526,11 @@ The [vdiffr](https://vdiffr.r-lib.org) package allows comparisons between SVG im ## When you stop work, leave a test failing. {.inverse .center .center-h} +# Minute cards {.inverse} + +- [Cohort 1](https://tally.so/r/LZa1Yp) +- [Cohort 2](https://tally.so/r/aQANEb) + # End matter {.inverse} ## References diff --git a/slides/04-check-package-documentation/index.qmd b/slides/04-check-package-documentation/index.qmd index 41dc0d4..3d7a44d 100644 --- a/slides/04-check-package-documentation/index.qmd +++ b/slides/04-check-package-documentation/index.qmd @@ -58,30 +58,71 @@ We haven't yet specified a license for our package. On running `check()` you may get an error if you are using a networked drive. There's a fix coming in a few slides. ::: -## Aside: in case of error - -On running `devtools::check()` you may get an error of the form - -``` -Updating animalsounds documentation -Error: The specified file is not readable: path-to\animalsounds\NAMESPACE +## A function that fails many checks + +```{r} +#| eval: false +#' Make an excited animal sound +#' +#' Repeat an animal sound in uppercase. +#' +#' @param sound A character string containing an animal sound. +#' @param times Number of times to repeat the sound. +#' +#' @return A character vector. +#' +#' @examples +#' excited_sound("moo", 3) +#' +#' @export +excited_sound <- function(animal, sound, times = 3) { + + sound <- str_to_upper(sound) + + paste("The", animal, "says", paste(rep(sound, times), collapse = " ")) +} ``` -This can happen if your repo is on a networked drive. - -This is covered in this [Stackoverflow question](https://stackoverflow.com/questions/40530968/overwriting-namespace-and-rd-with-roxygen2) and can be fixed. - -## Aside: a fix for networked drives - -1. Save a copy of this file: [fix_for_networked_drives.R](../../R/fix_for_networked_drives.R) - - Save it somewhere other than the `animalsounds` directory +## A function that fails many checks (cont.) -2. Open the file from the `animalsounds` project session - -3. Run the whole file - -You should now find that `devtools::check()` proceeds normally. +```r +❯ checking examples ... ERROR + Running examples in 'animalsounds-Ex.R' failed + The error most likely occurred in: + + > base::assign(".ptime", proc.time(), pos = "CheckExEnv") + > ### Name: excited_sound + > ### Title: Make an excited animal sound + > ### Aliases: excited_sound + > + > ### ** Examples + > + > excited_sound("moo", 3) + Error in str_to_upper(sound) : could not find function "str_to_upper" + Calls: excited_sound + Execution halted + +❯ checking Rd \usage sections ... WARNING + Undocumented arguments in Rd file 'excited_sound.Rd' + 'animal' + + Functions with \usage entries need to have the appropriate \alias + entries, and all their arguments documented. + The \usage entries must correspond to syntactically valid R code. + See chapter 'Writing R documentation files' in the 'Writing R + Extensions' manual. + +❯ checking R code for possible problems ... NOTE + excited_sound: no visible global function definition for 'str_to_upper' + Undefined global functions or variables: + str_to_upper + +1 error ✖ | 1 warning ✖ | 1 note ✖ +Error: R CMD check found ERRORs +Execution halted + +Exited with status 1. +``` ## Types of problem @@ -221,25 +262,6 @@ In LICENSE: Copyright 2023 ACME Ltd. All rights reserved. ``` -## Licensing considerations at universities - -:::{.callout-note appearance="simple"} -[This slide is specific for The University of Warwick, but similar considerations are likely to be true at other Universities.]{.smaller80} -::: - -:::{.smaller75} -- Software is defined as a creative output (unlike scholarly works, e.g. thesis, conference paper) -- The university owns the IP of any software created by Warwick PhD students and staff in the course of their work -- Before making code public or publishing software under any license, contact Brendan at [B.Spillane@warwick.ac.uk](mailto:B.Spillane@warwick.ac.uk) - - - Permission to publish under an open source license likely to be granted for research code - - Not necessary to obtain permission if open source software was part of grant proposal (as proposal will have already been checked by Research & Impact Services, who will have identified any IP issues). -::: - -::: {.notes} -Extra note from Brendan: "they don’t need to come to me with every single bit of code they open source. I’m more interested in the entirety of the project rather than approving 2000 bits of individual code!" -::: - ## Your turn 1. Open the DESCRIPTION file and add a title and description. @@ -568,6 +590,11 @@ You can add more information to `_pkgdown.yml` to customise the package website: 5. Click on the link to the website under the **About** section of the repo. 6. (Bonus) Change the appearance of the site with a Bootswatch theme: . +# Minute cards {.inverse} + +- [Cohort 1](https://tally.so/r/VLbEYy) +- [Cohort 2](https://tally.so/r/kdJa7e) + # End matter {.inverse} ## References @@ -580,3 +607,29 @@ R Core Team, *Writing R Extensions*, ` - If you encounter any issues, the actions tab in your source universe may show what is going on, for example: +- Or check the [R-Universe documentation](https://docs.r-universe.dev/). + # CRAN {.inverse} ## Why publish on CRAN? diff --git a/workshops/may-july-2026-cohort-2.qmd b/workshops/may-july-2026-cohort-2.qmd index e4f5dc2..d077349 100644 --- a/workshops/may-july-2026-cohort-2.qmd +++ b/workshops/may-july-2026-cohort-2.qmd @@ -35,7 +35,7 @@ and make sure you register for all five **cohort 2** sessions (noting that there ## Welcome and introduction -Here's a link to the [slides](may-july-2026-cohort-2.qmd){target="_blank"} in a new window. +Here's a link to the [slides](may-july-2026-cohort-2-intro.qmd){target="_blank"} in a new window. Here are the slides embedded: