Package Development

Misc

  • Packages
    • {goodpractices} - Give advice about good practices when building R packages. Advice includes functions and syntax to avoid, package structure, code complexity, code formatting, etc
  • Version Number Syntax
    • 0.0.0.9000 = the “.9000” means intital experimental or in development version (.9001 would be the second experimental version)
    • 0.1.0 = the “1” means this is your first “minor” version
    • 1.0.0 = the “1” means this is your first “major” version
  • In function docs, always use @examples and NOT @example
  • If you have internal functions (e.g. little helper functions) that you don’t want your users to have the regular access to then:
    • Don’t include @export for that function’s script
    • Documentation of the function isn’t required
  • Add a file from another repo using usethis::use_standalone
    • If another package has a set of functions that you’d like to include in your package but don’t want to import their entire package, this can be used.
    • It always overwrites an existing standalone file of the same name, making it easy to update previously imported code.
  • Using “Starting a Package” (below) takes some time when you want to test newly written functions.
    • Alternative: load_all() - Quickly makes function available for interactive testing, (ctrl + shift + L)
  • Change package name (if not on CRAN): changer::changer("current_pkg_name", "new_pkg_name")
    • Shouldn’t have the package project open when running this. I set the working dir to the root of my “Projects” directory locally (e.g Projects >> current_proj_directory, then set working directory to Projects)
    • This changes EVERYTHING automagically, but be sure and make a copy of the directory before running just in case.
    • Need to change repo name in the github repo’s settings before pushing changes
  • Create Citation file for package: usethis::use_citation()
    • Only execute after you’ve gone through putting your package on zenodo and have a generated citation to populate the fields of the CITATION file.
    • See indianacovid19data project for example
  • If planning to release on CRAN, use usethis::use_release_issue, which creates a checklist of things to fix/do before submitting to CRAN
    • CRAN requires your pkg run without issues on all platforms. {covr} tracks test coverage for your R package and view reports locally or (optionally) upload the results to codecov or coveralls
  • Developing Internal Packages

Starting a Package

Check package name, Create pkg, DESCRIPTION, Set-Up Git

  1. Check if package name already taken on CRAN:

    available::available("package_name", browse = FALSE)
    pak::pkg_name_check("pkg_name")
    • Asks you stuff about using “urban dictionary” to see if your package name might be offensive. Just say yes. (or it keeps asking)
    • You want it to say that your package name is available on CRAN
    • Shows sentiment analysis of your package name according to different dictionaries
  2. Create directory, a project, a basic package skeleton in that directory

    setwd("~/R/Projects")
    usethis::create_package("package_name")
  3. Open project and fill out some of DESCRIPTION

    • Title, Authors, Description
  4. Go to Build pane in RStudio \(\rightarrow\) more \(\rightarrow\) Configure Build Tools \(\rightarrow\) Make sure the “Generate documentation with Roxygen” box is ticked (tick it and click ok)

  5. Set-Up Git: usethis::use_git()

    • It will ask to commit the package skeleton to github \(\rightarrow\) Choose yes to commit locally
    • It’ll ask you to restart RStudio to activate the git pane \(\rightarrow\) Choose yes
    • Set-up of GH Auth token, if you don’t have one
      • Then use usethis::create_github_token() and follow steps
      • Refresh session once you’ve updated .Renviron
    • For private repo: usethis::use_github(private = TRUE)
    • For public repo: usethis::use_github(private = FALSE, protocol = "ssh")
      • Choose 1 to use ssh key
      • “Are title and description okay?” \(\rightarrow\) choose 3. Yes

Basic Set-up

  1. Use Markdown for documentation: usethis::use_roxygen_md()
  2. Add license: usethis::use_mit_license(copyright_holder = "Eric Book")
  3. Add Readme.Rmd: usethis::use_readme_rmd()
    • You’ll still need to render/knit README.Rmd regularly.
    • To keep README.md up-to-date, devtools::build_readme() is handy.
    • You could also use GitHub Actions to re-render README.Rmd every time you push. An example workflow can be found here: https://github.com/r-lib/actions/tree/master/examples.
  4. Add News/Changlog file: usethis::use_news_md()
  5. Add Article/Vignette: usethis::use_vignette
  6. Document, Install, and Check package
    • Build pane \(\rightarrow\)
      • more \(\rightarrow\) Run Document
      • Run Install and Restart
      • Run Check
  7. Commit files and Push

Development

  • Add common imported functions to DESCRIPTION

    usethis::use_tidy_eval()
    usethis::use_tibble()
  • Add data

    # read data into environment
    pkg_dat <- readr::read_rds("../path/to/data.rds")
    usethis::use_data(pkg_dat, overwrite = TRUE)
    • Creates data directory and adds data as an .rda file
    • Document the data: usethis::use_r("data")
    • If you want to store binary data and make it available to the user, put it in data/. This is the best place to put example datasets. If you want to store parsed data, but NOT make it available to the user, put it in R/sysdata.rda. This is the best place to put data that your functions need.
    • If you want to store raw data, put it in inst/extdata.
  • Add Functions

    • Create R file: usethis::use_r("name-function-file")
    • Write function
      • Use pkg::function format for every external package function
    • Add documentation (e.g. @description, @params, @returns, @details, @references, @export, and @examples
      • When pasting code into examples, use the RStudio multiple cursors feature (ctrl + alt + up/down) to add “#>” to all the lines of code at once
    • Add packages used in function to DESCRIPTION: usethis::use_package("package_name")
    • Make sure hardcoded variable names are in global variables file
    • Make sure any data used in @examples is added and documentedf
    • If submitting to CRAN: need examples and they should be wrapped in \dontrun{} and \donttest{}.
    • Test
      • Build pane \(\rightarrow\) more \(\rightarrow\) Run Document (Ctrl+Shift+D) \(\rightarrow\) Run Install and Restart (ctrl+shift+B) \(\rightarrow\) Run Check (ctrl+shift+E)
    • After writing a function or two, set-up {pkgdown}, and return here.
    • Add new function to _pkgdown.yml
    • Add new function to any additional pages of the site (e.g. vignettes, Readme.Rmd)
      • Remember to knit
    • Run pkgdown::build_site()
      • Knits html pages for reference function pages
    • Commit and Push
    • Rinse and Repeat for each function

pkgdown

  • Docs
  • Set-Up: usethis::use_pkgdown()
    • Creates yaml file: _pkgdown.yml
    • Fill out yaml
  • Publish website and set-up Github Actions: usethis::use_pkgdown_github_pages()
  • Customize site
    • Name css file, “extra.css”, and place in folder called, “pkgdown” (also extra.js, extra.scss)
    • When testing out themes, css features, etc., iterating is much faster when using build_home_index(); init_site() instead of build_site(). Then, refresh html page in browser.
    • See docs for bslib variables that can be used in _pkgdown.yml (requires using bootsrap 5).

Dependencies

  • Imports and Depends

    • Imports just loads the package
      • Unless there is a good reason otherwise, you should always list packages in Imports not Depends. That’s because a good package is self-contained, and minimises changes to the global environment (including the search path)
    • Depends attaches it.
  • Loading and Attaching

    • Loading
      • The package is available in memory, but because it’s not in the search path (path that R searches for functions), you won’t be able to access its components without using ::.
    • Attaching
      • Puts the package in the search path. You can’t attach a package without first loading it
      • Both library() (throws error when pkg not installed )or require() (just returns false when pkg not installed) load then attach the package

Testing

  • Packages
    • {testthat} explicitly evaluates the outputs of your function but you can add a test that makes sure the checks on inputs within the function are working
    • {doctext} - Generate tests from examples using {roxygen} and {testthat}
  • Run tests (Ctrl+Shift+T): test()
    • Also ran when using, check()
  • Set-up - Run usethis::use_testthat within the project directory
    • Creates:
      • A tests folder in your working directory
      • A testthat folder in the tests folder where your tests will live
      • A testthat.R file in the tests folder which organizes things for running. Don’t modify this manually.
  • Names of your testing files must begin with ‘test’
    • e.g. testing file, ‘test-my-function.R’, which lives in testthat folder
  • Writing Tests
    • Write tests using the test_that function, and with each test, there’s an “expectation.”

    • You can have one or more tests in each file and one or more expectations within each test.

    • Example

      test_that("description of the test", {
              test_output1 <- dat %>%
                  your_package_function()
      
              expect_equal(nrow(test_output1), 6)
              # etc...
      })
    • Example

      • Where sse is the function you’re testing
    • Example expect_error(compute_corr(data = faithful, var1 = erruptions, var2 = waiting)

      • erruptions isn’t a column in the dataset and should throw an error because of a check inside the function
      • Also expect_warning() available
    • Example: Compare optional outputs of a nested function to the individual functions within that nested function

      • error is the nested function with optional outputs of sse error or mape error
      • Second chunk should say “mape calculations work”
      • 1st chunk checks if error with sse option output is the same as sse output
      • 2nd chunk checks if error with mape option output is the same as mape output
  • Run Tests
    • Use the test_file function
    • Use the test_dir function: e.g. test_dir(wd$test_that) for running all tests.
    • Press the Run Tests button in R Studio if you open the test file.
    • Highlight and run the code.