# Personal notes of Barbara in preparing R Packaging To be used for additions to the [current workshop materials](https://carpentries-incubator.github.io/lesson-R-packaging/). ## Week 4 - Vignettes Let's take a look at existing vignettes: `browseVignettes()` Or specifically one from knitr: `browseVignettes("knitr")` - Confirm whether people know Rmarkdown - Let's make our report `usethis::use_vignette("final_report")` The knit button gives a preview. To actually document: `devtools::build_rmd("vignettes/final-report.Rmd")` Mention that usethis also knows `usethis::use_article("")`. ## Week 3 - dependencies & data ### Dependencies - We sometimes use functions from other packages - Best practice: with `::` - These packages are required to be installed, otherwise your package does not work. - They are mentioned in the `DESCRIPTION` - This is how to put them there: `usethis::use_package("dplyr")` - There is also 'suggests' Q Can you think of a reason that a dependency would not be imported but only suggested? A If it is only used for one function (you do need to check for the presence of that package before you run the function); if it is used to run the test suite; when they are used in an example or in your vignette... - This is how to use Suggests: `usethis::use_package("tidyr", type="suggests")` - You can also specify a minimal version: `usethis::use_package("magrittr", min_version="1.0.0")` #### Do Exercise #### Q It is probably possible to do this automatically. Can you think of a reason why it is good to keep this a manual step? A Dependencies should be kept to a minimum. You should know what dependencies you introduce. THAT SAID: the "check" will check whether you have undeclared dependencies or if your dependencies don't actually show up in your code. Note about versions: "Generally, it’s always better to specify the version and to be conservative about which version to require. Unless you know otherwise, always require a version greater than or equal to the version you’re currently using."" Check your version with: `packageVersion("dplyr")` Automatically update them with `usethis::use_latest_dependencies()` ### Data ```r= sample_names <- c("Luke", "Darth Vader", "Leia", "Chewbacca", "Han Solo", "R2D2") usethis::use_data(sample_names) ``` Best done after documentation with roxygen, then document: ``` #' Example names #' #' An example data set containing six names from the Star Wars universe #' #' @format A vector of strings #' @source Star Wars "example_names" ``` - No need to add the @export tag, in fact, it will break your package. - The object you create will be available to the user. - It is not in NAMESPACE, that is OK. Save raw data in `inst/extdata`. When using the data, this is how you refer to the file path: `system.file("extdata", "names.csv", package = "mysterycoffee")` so load it with: ```r= filepath <- system.file("extdata", "names.csv", package = "mysterycoffee") names <- read.csv(filepath) ``` ### Exercise: add data to your package ```mermaid flowchart LR id1(Does the user need access?) --Yes--> id6(Store it in data/) id3(Is the data in .Rda format?)--Yes--> id1 id1 --No, but tests do--> id5(Store it in tests/) id1 --No, but functions do--> id4(Store it in R/sysdata.Rda*) id3 --No--> id8(But can it be?) id8 --Yes, with some work --> id9(Document the process in data-raw/**) id8 --No, it shouldn't--> id7(Store it in inst/extdata) ``` `*`) `R/sysdata.Rda` is a file dedicated to (larger) data needed by your functions. [Read more about it here](https://r-pkgs.org/Data.html#sec-data-sysdata). `**`) `data-raw/` is a folder dedicated to the origin and cleanup of your data. [Read more about it here](https://r-pkgs.org/Data.html#sec-data-data-raw). Add data to your package: - Do you need raw data as part of your package? - Create a folder `inst/extdata`, and save the files here. Note that a user will be able to access this data. - When loading the data, do not describe the path as you usually would. Instead, use something like: ```r filepath <- system.file("extdata", "names.csv", package = "mysterycoffee") names <- read.csv(filepath) ``` - Do you need data you can store in your package as an .Rda file? - Create the object - Store it with `usethis::use_data(object_name)` - Verify the object is now stored in the `data/` folder - Create a new R file called `data.R`: `usethis::use_r("data")` (`data/R` is an example; you may call this whatever you want) - In this file, document the data object, using this example: ``` #' Title #' #' A short description. #' #' @format What format is the data in? #' @source Where did it come from? \url{https://google.com} "object_name" ``` - Don't forget to call `devtools::document()` to generate the documentation file and add this data to the package namespace. ## Week 2 - testing - Check if `testthat` is installed. Tests are formalized checks that you would do anyway. ### Set up testthat `usethis::use_testthat()` This creates tests folder and proper structure inside. ### Set up a test ```r usethis::use_test("name-of-function") ``` ### Run a test Run tests options: - Cmd/Ctrl + Shift + T - `devtools::test()` - Visual (with button); both individual test files and all tests - As part of 'check' ### Workflow Write test, run tests. Update code, run tests. No need to build in between :) library is loaded in testing env! ### Test strategy - Use setup for a test, use it once - But decouple testing environments as much as possible (so don't daisy-chain tests, especially if you change things in setup) - "Expected" should be as clean as possible; a fixed value, not itself subject to function use (unless that is warranted) - But test your functions, not R functions (so don't verify things inside your test suite) ### Integration tests vs unit tests - unit tests are about single functions - integration will test larger workflow - both have value (can you think of some?) ### Tests written ```r test_that("even number of people", { load("testdata.Rda") starwars <- sample(starwars, 6) groups <- make_groups(starwars) expect_equal(ncol(groups),2) expect_equal(nrow(groups),3) }) test_that("dataframe", { df <- data.frame(1:10, 11:20) expect_error(make_groups(df), "Requires vector input.") }) skip("uneven") test_that("uneven number of people", { load("testdata.Rda") starwars <- sample(starwars, 7) groups <- make_groups(starwars) expect_equal(ncol(groups),2) }) ``` ``` starwars <- c("R2D2", "C3PO", "Luke", "Leia", "Darth Vader", "Amidala", "Han Solo", "Chewie", "JarJar Binks") save(starwars, file="tests/testthat/testdata.Rda") #rm(starwars) #load(file="testdata.Rda") ``` ### Add data to test - Make data object `starwars <- c("R2D2", "C3PO", "Luke", "Leia", "Darth Vader", "Amidala", "Han Solo", "Chewie", "JarJar Binks")` - Save it in `/tests` `save(starwars, file="tests/testthat/testdata.Rda")` - When loading: `load(file="testdata.Rda")` This puts the object `testdata` in the environment. - Show that it needs to be loaded in every `testthat` call, because the environment is recreated. ### Quiz (T/F) - When you update/refactor a function, you have to update your tests. - Tests can only be written on functions available to the user, not on 'helper functions'. - Unit tests test small parts of the code, like single functions. - You can add tests to a script, but they only work on functions. - ### Test-driven development Turns around the process: instead of writing functions, and thinking of edge-cases, you first formalize what you want your function to do, then write the function. RED Step 1: Write test(s) Step 2: Run test(s) - make sure they fail [this means test adds value] GREEN Step 3: Write code so that test passes Step 4: Ensure that all tests, including older tests, pass REFACTOR Step 5: Refactor and improve code This also ensures high coverage: you only write code which fixes your tests, and only write tests that break your code, ensuring that your code is covered by tests. PRO - forces you to think about requirements first, define feature well - saves time in the long run, bugs are detected early - code will be easily testable by design, easier maintenance PITFALLS - takes time initially, can be a lot of overhead - unit tests can accumulate and make you feel like everything is covered, you don't spend time on integration tests - if you write tests AND code, you can have blind spots and miss testing opportunities Use v abuse tests - check that the use matches expected behavior: use - expect_true, expect_false, expect_equal - check that faulty behavior generates error: abuse - expect_error ![](https://i.imgur.com/5TuNlnG.png) [source: Medium](https://medium.com/@l.ogrady/test-driven-development-the-red-green-refactor-cycle-e36ee7e6f1b4) #### example - uneven number. - setup test (can be in a function!) - write ncol/nrow test - KEEP ONLY BREAKING TESTS (ideally you do this from beginning) - fix (by ensuring even number in function) - tests now work - you want to report person removed - Write test `expect_warning` - Does not work, so now write warning - Now add a dataframe to the function ### Skipping tests https://testthat.r-lib.org/reference/skip.html Add `skip("Message")` to the top of the file (or in the middle). Everything following this will be skipped. ## Week 1 - licenses ```r tools::CRAN_package_db() %>% group_by(License) %>% summarize(count = n()) %>% arrange(desc(count)) ```