diff --git a/_extensions/EllaKaye/localtime/_extension.yml b/_extensions/EllaKaye/localtime/_extension.yml new file mode 100644 index 0000000..e63045a --- /dev/null +++ b/_extensions/EllaKaye/localtime/_extension.yml @@ -0,0 +1,7 @@ +title: localtime +author: Ella Kaye +version: 0.3.0 +quarto-required: ">=1.2.0" +contributes: + shortcodes: + - localtime.lua diff --git a/_extensions/EllaKaye/localtime/_schema.yml b/_extensions/EllaKaye/localtime/_schema.yml new file mode 100644 index 0000000..989b08d --- /dev/null +++ b/_extensions/EllaKaye/localtime/_schema.yml @@ -0,0 +1,33 @@ +$schema: https://m.canouil.dev/quarto-wizard/assets/schema/v1/extension-schema.json + +shortcodes: + localtime: + description: Display a source date and time converted to the reader's local timezone using Luxon. + arguments: + - name: date + type: string + required: true + pattern: \d{4}-\d{2}-\d{2} + pattern-exact: true + description: Source date in YYYY-MM-DD format. + - name: time + type: string + required: true + pattern: \d{1,2}:\d{2}([AaPp][Mm])? + pattern-exact: true + description: Source time in 24-hour H:MM or HH:MM format, or 12-hour format with am/pm suffix (e.g. 1:00pm, 1:00 PM); a space before am/pm is optional. + - name: timezone + type: string + default: UTC + description: Source timezone as an abbreviation (e.g. UTC, EST, CET), IANA name (e.g. America/New_York, Europe/Paris), or UTC offset (e.g. +05:30, UTC+8, -08:00); DST is handled automatically in the browser. + completion: + type: freeform + placeholder: UTC | EST | America/New_York | +05:30 + attributes: + format: + type: string + default: datetime + description: Output format preset or a custom strftime-like token string; supported tokens are %Y, %m, %-m, %d, %-d, %H, %-H, %I, %-I, %M, %-M, %p, %P, %A, %a, %B, %b, and %Z. + completion: + type: enum + values: [datetime, date, time, time12, datetime12, full, full12] diff --git a/_extensions/EllaKaye/localtime/_snippets.json b/_extensions/EllaKaye/localtime/_snippets.json new file mode 100644 index 0000000..7be53e0 --- /dev/null +++ b/_extensions/EllaKaye/localtime/_snippets.json @@ -0,0 +1,16 @@ +{ + "Local time shortcode": { + "prefix": "time", + "body": [ + "{{< localtime ${1:2026-01-30} ${2:13:00} ${3:UTC} >}}" + ], + "description": "Insert a localtime shortcode with date, time, and source timezone." + }, + "Local time with format": { + "prefix": "time-format", + "body": [ + "{{< localtime ${1:2026-01-30} ${2:13:00} ${3:UTC} format=\"${4:full}\" >}}" + ], + "description": "Insert a localtime shortcode with a preset or custom format value." + } +} diff --git a/_extensions/EllaKaye/localtime/localtime.lua b/_extensions/EllaKaye/localtime/localtime.lua new file mode 100644 index 0000000..68414d6 --- /dev/null +++ b/_extensions/EllaKaye/localtime/localtime.lua @@ -0,0 +1,261 @@ +-- localtime.lua +-- Quarto shortcode extension to display times in the reader's local timezone. +-- Usage: {{< localtime YYYY-MM-DD HH:MM TZ >}} + +local counter = 0 +local luxon_script_injected = false + +-- Timezone abbreviations mapped to either: +-- a string → IANA timezone name (browser Intl handles DST automatically) +-- a number → fixed UTC offset in minutes (positive = east of UTC) +-- Where an abbreviation is ambiguous, the most widely-used interpretation is chosen. +local TZ_ZONES = { + -- Universal + UTC = "UTC", GMT = "UTC", + + -- North America (DST-aware → IANA) + NST = "America/St_Johns", NDT = "America/St_Johns", + AST = "America/Halifax", ADT = "America/Halifax", + EST = "America/New_York", EDT = "America/New_York", + CST = "America/Chicago", CDT = "America/Chicago", + MST = "America/Denver", MDT = "America/Denver", + PST = "America/Los_Angeles", PDT = "America/Los_Angeles", + AKST = "America/Anchorage", AKDT = "America/Anchorage", + HST = -600, HDT = -570, + + -- South America (fixed offsets, minutes) + VET = -240, BOT = -240, PYT = -240, CLT = -240, + AMT = -240, GYT = -240, + COT = -300, PET = -300, ECT = -300, + BRT = -180, ART = -180, UYT = -180, SRT = -180, + PYST = -180, CLST = -180, + BRST = -120, + + -- Europe (DST-aware → IANA) + WET = "Europe/Lisbon", WEST = "Europe/Lisbon", + BST = "Europe/London", + CET = "Europe/Paris", CEST = "Europe/Paris", + EET = "Europe/Helsinki", EEST = "Europe/Helsinki", + MSK = 180, TRT = 180, + + -- Africa (fixed offsets) + WAT = 60, CAT = 120, SAST = 120, EAT = 180, + + -- Middle East + IDT = 180, + IRST = 210, IRDT = 270, + + -- Asia (fixed offsets) + GST = 240, AZT = 240, + AFT = 270, + PKT = 300, UZT = 300, + IST = 330, SLST = 330, + NPT = 345, + BDT = 360, BTT = 360, + MMT = 390, + ICT = 420, WIB = 420, HOVT = 420, + HKT = 480, SGT = 480, MYT = 480, + PHT = 480, WITA = 480, AWST = 480, + JST = 540, KST = 540, WIT = 540, TLT = 540, + + -- Australia & Pacific (DST-aware → IANA, others fixed) + ACST = "Australia/Adelaide", ACDT = "Australia/Adelaide", + AEST = "Australia/Sydney", AEDT = "Australia/Sydney", + LHST = 630, LHDT = 660, + SBT = 660, NCT = 660, NFT = 660, + NZST = "Pacific/Auckland", NZDT = "Pacific/Auckland", + FJT = 720, TOT = 780, LINT = 840, + SST = -660, WST = -660, + MART = -570, GAMT = -540, +} + +-- Parse a timezone string. +-- Returns a string (IANA name) or number (offset in minutes) for Luxon, or nil if unrecognised. +local function parse_tz(tz_str) + if not tz_str or tz_str == "" then return "UTC" end + + local upper = tz_str:upper() + + -- Direct abbreviation lookup + local zone = TZ_ZONES[upper] + if zone ~= nil then return zone end + + -- Already an IANA name (contains "/") + if tz_str:find("/", 1, true) then + return tz_str + end + + -- Convert h/m strings + sign to total minutes + local function to_minutes(sign, h, m) + local mins = tonumber(h) * 60 + (tonumber(m) or 0) + return sign == "+" and mins or -mins + end + + -- Handle UTC+X or GMT+X (e.g. UTC+5, UTC+5:30, GMT-8) + local after_prefix = upper:match("^UTC(.+)$") or upper:match("^GMT(.+)$") + if after_prefix then + local sign, h, m = after_prefix:match("^([%+%-])(%d+):?(%d*)$") + if sign and h then + return to_minutes(sign, h, m ~= "" and m or "0") + end + end + + -- Handle bare ±HH:MM or ±HHMM + local sign2, h2, m2 = tz_str:match("^([%+%-])(%d%d):?(%d%d)$") + if sign2 and h2 and m2 then + return to_minutes(sign2, h2, m2) + end + + -- Handle bare ±H or ±HH + local sign3, h3 = tz_str:match("^([%+%-])(%d+)$") + if sign3 and h3 then + return to_minutes(sign3, h3, "0") + end + + return nil +end + +return { + ["localtime"] = function(args, kwargs, meta, raw_args) + -- Collect positional args as strings + local parts = {} + for _, arg in ipairs(args) do + table.insert(parts, pandoc.utils.stringify(arg)) + end + + if #parts < 2 then + io.stderr:write("[localtime] Error: expected at least date and time arguments\n") + return pandoc.RawInline("html", "[localtime: invalid args]") + end + + local date_str = parts[1] -- e.g. "2026-01-30" + local time_str = parts[2] -- e.g. "13:00" or "1:00" + local tz_str + local next_idx = 3 + + -- Handle space-separated AM/PM: "1:00 PM EST" → parts = ["1:00", "PM", "EST"] + if parts[3] and (parts[3]:upper() == "AM" or parts[3]:upper() == "PM") then + time_str = parts[2] .. " " .. parts[3] + next_idx = 4 + end + tz_str = parts[next_idx] + + -- Parse date + local year, month, day = date_str:match("^(%d%d%d%d)-(%d%d)-(%d%d)$") + if not year then + io.stderr:write("[localtime] Error: invalid date format '" .. date_str .. "' (expected YYYY-MM-DD)\n") + return pandoc.RawInline("html", "[localtime: bad date]") + end + year, month, day = tonumber(year), tonumber(month), tonumber(day) + + -- Parse time (12-hour or 24-hour) + local time_str_lower = time_str:lower() + local hour, minute, ampm = time_str_lower:match("^(%d%d?):(%d%d)%s*([ap]m)$") + if hour then + hour, minute = tonumber(hour), tonumber(minute) + if hour < 1 or hour > 12 then + io.stderr:write("[localtime] Error: 12-hour clock hour " .. hour .. " is out of range (1-12)\n") + return pandoc.RawInline("html", "[localtime: bad time]") + end + if ampm == "am" then + if hour == 12 then hour = 0 end + else + if hour ~= 12 then hour = hour + 12 end + end + else + local h24, m24 = time_str:match("^(%d%d?):(%d%d)$") + if not h24 then + io.stderr:write("[localtime] Error: invalid time format '" .. time_str .. "' (expected HH:MM or H:MMam/pm)\n") + return pandoc.RawInline("html", "[localtime: bad time]") + end + hour, minute = tonumber(h24), tonumber(m24) + end + + -- Validate ranges + if month < 1 or month > 12 then + io.stderr:write("[localtime] Warning: month " .. month .. " is out of range (1-12)\n") + end + if day < 1 or day > 31 then + io.stderr:write("[localtime] Warning: day " .. day .. " is out of range (1-31)\n") + end + if hour < 0 or hour > 23 then + io.stderr:write("[localtime] Warning: hour " .. hour .. " is out of range (0-23)\n") + end + if minute < 0 or minute > 59 then + io.stderr:write("[localtime] Warning: minute " .. minute .. " is out of range (0-59)\n") + end + + -- Parse timezone + local zone = parse_tz(tz_str or "UTC") + if zone == nil then + io.stderr:write("[localtime] Warning: unrecognised timezone '" .. (tz_str or "") .. "', assuming UTC\n") + zone = "UTC" + tz_str = "UTC" + end + + -- zone is either a string (IANA name) or a number (offset in minutes). + -- JS detects which via isNaN(Number(tz)). + local zone_attr = type(zone) == "number" and tostring(zone) or zone + + -- ISO datetime string without timezone (Luxon applies zone separately) + local datetime_iso = string.format("%04d-%02d-%02dT%02d:%02d", year, month, day, hour, minute) + + -- Fallback text (shown when JS is disabled) + local fallback = string.format("%s %s %s", date_str, time_str, tz_str or "UTC") + + -- Optional format kwarg (empty → JS uses its default) + local fmt_attr = "" + if kwargs["format"] then + fmt_attr = pandoc.utils.stringify(kwargs["format"]) + end + + -- Unique element ID + counter = counter + 1 + local id = "localtime-" .. counter + + -- Inject Luxon CDN script once, before the first localtime element + local luxon_tag = "" + if not luxon_script_injected then + luxon_tag = '' + luxon_script_injected = true + end + + -- Inline JS: reads data attributes, converts timezone, formats with Luxon. + -- Zone attribute is either an IANA name (string) or offset in minutes (number). + -- Format string uses strftime-style tokens (%Y, %m, %H, %-H, etc.) substituted + -- directly — avoiding Luxon's toFormat() for the full string, which would + -- misinterpret literal text containing Luxon token letters (e.g. "at" → a=AM/PM, t=time). + local js = [[(function(){var el=document.getElementById(']] .. id .. [['); +if(typeof luxon==='undefined'){return;} +var tz=el.getAttribute('data-tz'); +var zone=isNaN(Number(tz))?tz:Number(tz); +var dt=luxon.DateTime.fromISO(el.getAttribute('data-datetime'),{zone:zone}).toLocal(); +if(!dt.isValid){return;} +var fmt=el.getAttribute('data-format')||'%Y-%m-%d %H:%M'; +var P={datetime:'%Y-%m-%d %H:%M',date:'%Y-%m-%d',time:'%H:%M',time12:'%-I:%M%P',datetime12:'%Y-%m-%d %-I:%M%P',full:'%A, %-d %B %Y at %H:%M %Z',full12:'%A, %-d %B %Y at %-I:%M%P %Z'}; +if(P[fmt])fmt=P[fmt]; +var pad=function(n){return String(n).padStart(2,'0');}; +var h=dt.hour,mi=dt.minute; +el.textContent=fmt + .replace(/%Y/g,String(dt.year)) + .replace(/%-m/g,String(dt.month)).replace(/%m/g,pad(dt.month)) + .replace(/%-d/g,String(dt.day)).replace(/%d/g,pad(dt.day)) + .replace(/%-H/g,String(h)).replace(/%H/g,pad(h)) + .replace(/%-I/g,String(h%12||12)).replace(/%I/g,pad(h%12||12)) + .replace(/%-M/g,String(mi)).replace(/%M/g,pad(mi)) + .replace(/%P/g,h<12?'am':'pm').replace(/%p/g,h<12?'AM':'PM') + .replace(/%A/g,dt.toFormat('EEEE')).replace(/%a/g,dt.toFormat('EEE')) + .replace(/%B/g,dt.toFormat('MMMM')).replace(/%b/g,dt.toFormat('MMM')) + .replace(/%Z/g,(Intl.DateTimeFormat(undefined,{timeZoneName:'short'}).formatToParts(dt.toJSDate()).find(function(p){return p.type==='timeZoneName';})||{value:''}).value);})();]] + + local html = luxon_tag .. + '' .. + fallback .. '' .. + '' + + return pandoc.RawInline("html", html) + end +} diff --git a/_freeze/slides/03-data-testing/index/execute-results/html.json b/_freeze/slides/03-data-testing/index/execute-results/html.json new file mode 100644 index 0000000..ee27b5e --- /dev/null +++ b/_freeze/slides/03-data-testing/index/execute-results/html.json @@ -0,0 +1,19 @@ +{ + "hash": "45d045b0a44aaf0f26f99af87efd1d2c", + "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", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": { + "include-after-body": [ + "\n\n\n" + ] + }, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/slides/05-publication-maintenance/index/execute-results/html.json b/_freeze/slides/05-publication-maintenance/index/execute-results/html.json new file mode 100644 index 0000000..548f536 --- /dev/null +++ b/_freeze/slides/05-publication-maintenance/index/execute-results/html.json @@ -0,0 +1,19 @@ +{ + "hash": "4d851e48fb8a9efb3056db59240b3e73", + "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", + "supporting": [], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": { + "include-after-body": [ + "\n\n\n" + ] + }, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/images/instructors/jesiformoso.jpg b/images/instructors/jesiformoso.jpg new file mode 100644 index 0000000..916a45a Binary files /dev/null and b/images/instructors/jesiformoso.jpg differ diff --git a/modules/03-testing/index.qmd b/modules/03-data-testing/index.qmd similarity index 67% rename from modules/03-testing/index.qmd rename to modules/03-data-testing/index.qmd index f98e31f..735f7bc 100644 --- a/modules/03-testing/index.qmd +++ b/modules/03-data-testing/index.qmd @@ -1,5 +1,5 @@ --- -title: Testing +title: Packaging data; Testing description: Module 3 author: Forwards teaching team draft: false @@ -7,11 +7,11 @@ draft: false ## Slides -Here's a link to the [slides](/slides/03-testing/index.qmd){target="_blank"} in a new window. +Here's a link to the [slides](/slides/03-data-testing/index.qmd){target="_blank"} in a new window. Here are the slides embedded: - ## Resources diff --git a/modules/05-data-publication-maintenance/index.qmd b/modules/05-publication-maintenance/index.qmd similarity index 93% rename from modules/05-data-publication-maintenance/index.qmd rename to modules/05-publication-maintenance/index.qmd index e37d12c..59807a8 100644 --- a/modules/05-data-publication-maintenance/index.qmd +++ b/modules/05-publication-maintenance/index.qmd @@ -1,5 +1,5 @@ --- -title: Packaging data; Publication and maintenance +title: Publication and maintenance description: Module 5 author: Forwards teaching team draft: false diff --git a/slides/03-testing/images/dev_cycle_before_testing.png b/slides/03-data-testing/images/dev_cycle_before_testing.png similarity index 100% rename from slides/03-testing/images/dev_cycle_before_testing.png rename to slides/03-data-testing/images/dev_cycle_before_testing.png diff --git a/slides/03-testing/images/dev_cycle_with_testing.png b/slides/03-data-testing/images/dev_cycle_with_testing.png similarity index 100% rename from slides/03-testing/images/dev_cycle_with_testing.png rename to slides/03-data-testing/images/dev_cycle_with_testing.png diff --git a/slides/03-testing/images/test_organization.png b/slides/03-data-testing/images/test_organization.png similarity index 100% rename from slides/03-testing/images/test_organization.png rename to slides/03-data-testing/images/test_organization.png diff --git a/slides/03-testing/index.qmd b/slides/03-data-testing/index.qmd similarity index 72% rename from slides/03-testing/index.qmd rename to slides/03-data-testing/index.qmd index f6e41b7..ca019ec 100644 --- a/slides/03-testing/index.qmd +++ b/slides/03-data-testing/index.qmd @@ -7,9 +7,141 @@ format: forwardspres-revealjs ## Overview +- Packaging data - Unit testing with **testthat** - Test driven development +# Packaging data {.inverse} + +## Including data + +There are 3 types of data we might want to include: + +- Exported data for the user to access: put in `/data` +- Internal data for functions to access: put in `/R/sysdata.rda` +- Raw data: put in `/inst/extdata` + +## Exported data + +The data should be saved in `/data` as an `.rda` (or `.RData`) file. + +`usethis::use_data()` will do this for you, as well as a few other necessary steps: + +```{r, eval = FALSE} +letter_indices <- data.frame(letter = letters, index = seq_along(letters)) +usethis::use_data(letter_indices) +``` + +``` +✔ Adding 'R' to Depends field in DESCRIPTION +✔ Creating 'data/' +✔ Setting LazyData to 'true' in 'DESCRIPTION' +✔ Saving 'letter_indices' to 'data/letter_indices.rda' +• Document your data (see 'https://r-pkgs.org/data.html') +``` + +. . . + +:::{.callout-note} +For larger datasets, you can try changing the `compress` argument to get the best compression. +::: + +## Provenance + +Often 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. + +It's a good idea to put the R script and any corresponding raw data in `/data-raw`. + +`usethis::use_data_raw("dataname")` will set this up: + + - Create `/data-raw` + - Add `/data-raw/dataname.R` for you to add the code needed to create the data + - Add `^data-raw$` to `.Rbuildignore` as it does not need to be included in the actual package. + +You should add any raw data files (e.g. `.csv` files) to `/data-raw`. + +## Documenting Data + +Datasets in `/data` are always exported, so **must** be documented. + +To document a dataset, we must have an `.R` script in `/R` that contains a Roxygen block above the name of the dataset. + +As 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. + +## Example: letter_indices + +``` +#' Letters of the Roman Alphabet with Indices +#' +#' A dataset of lower-case letters of the Roman alphabet and their +#' numeric index from a = 1 to z = 26. +#' +#' @format A data frame with 26 rows and 2 variables: +#' \describe{ +#' \item{letter}{The letter as a character string.} +#' \item{index}{The corresponding numeric index.} +#' } +"letter_indices" +``` + +`#' @ examples` can be used here too. + +## Data source + +For collected data, the (original) source should be documented with `#' @source`. + +This should either be a url, e.g. +```{r} +#| eval: false +#' @source \url{http://www.diamondse.info/} +``` +(alternatively `\href{DiamondSearchEngine}{http://www.diamondse.info/}`), or a reference, e.g. + +```{r, eval = FALSE} +#' @source Henderson and Velleman (1981), Building multiple +#' regression models interactively. *Biometrics*, **37**, 391–411. +``` + +## Internal data + +Sometimes functions need access to reference data, e.g. constants or look-up tables, that don't need to be shared with users. + +These objects should be saved in a single `R/sysdata.rda` file. + +This can be done with `use_data(..., internal = TRUE)`, e.g. + +```{r, eval = FALSE} +x <- sample(1000) +usethis::use_data(x, mtcars, internal = TRUE) +``` + +The generating code and any raw data can be put in `/data-raw`. + +As the objects are not exported, they don't need to be documented. + +## Raw data + +Sometimes you want to include raw data, to use in examples or vignettes. + +These files can be any format and should be added directly into `/inst/extdata`. + +When the package is installed, these files will be copied to the `extdata` directory and their path on your system can be found as follows: + +```{r} +system.file("extdata", "mtcars.csv", package = "readr") +``` + +## Your turn + +1. Run `usethis::use_data_raw("farm_animals")`. +2. 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. +3. Run all the code (including the already-present call to `usethis::use_data()`) to create the data and save it in `/data`. +4. Add an `R/farm_animals.R` script and add some roxygen comments to document the data. +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} ## Why test? diff --git a/slides/05-data-publication-maintenance/index.qmd b/slides/05-publication-maintenance/index.qmd similarity index 77% rename from slides/05-data-publication-maintenance/index.qmd rename to slides/05-publication-maintenance/index.qmd index e582759..4b409a7 100644 --- a/slides/05-data-publication-maintenance/index.qmd +++ b/slides/05-publication-maintenance/index.qmd @@ -7,7 +7,6 @@ format: forwardspres-revealjs ## Overview -- Packaging data - Publication - GitHub - R-Universe @@ -20,135 +19,6 @@ format: forwardspres-revealjs The packaging data section would more naturally fit earlier in the course, but put here because of how the schedule has panned out. ::: -# Packaging data {.inverse} - -## Including data - -There are 3 types of data we might want to include: - -- Exported data for the user to access: put in `/data` -- Internal data for functions to access: put in `/R/sysdata.rda` -- Raw data: put in `/inst/extdata` - -## Exported data - -The data should be saved in `/data` as an `.rda` (or `.RData`) file. - -`usethis::use_data()` will do this for you, as well as a few other necessary steps: - -```{r, eval = FALSE} -letter_indices <- data.frame(letter = letters, index = seq_along(letters)) -usethis::use_data(letter_indices) -``` - -``` -✔ Adding 'R' to Depends field in DESCRIPTION -✔ Creating 'data/' -✔ Setting LazyData to 'true' in 'DESCRIPTION' -✔ Saving 'letter_indices' to 'data/letter_indices.rda' -• Document your data (see 'https://r-pkgs.org/data.html') -``` - -. . . - -:::{.callout-note} -For larger datasets, you can try changing the `compress` argument to get the best compression. -::: - -## Provenance - -Often 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. - -It's a good idea to put the R script and any corresponding raw data in `/data-raw`. - -`usethis::use_data_raw("dataname")` will set this up: - - - Create `/data-raw` - - Add `/data-raw/dataname.R` for you to add the code needed to create the data - - Add `^data-raw$` to `.Rbuildignore` as it does not need to be included in the actual package. - -You should add any raw data files (e.g. `.csv` files) to `/data-raw`. - -## Documenting Data - -Datasets in `/data` are always exported, so **must** be documented. - -To document a dataset, we must have an `.R` script in `/R` that contains a Roxygen block above the name of the dataset. - -As 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. - -## Example: letter_indices - -``` -#' Letters of the Roman Alphabet with Indices -#' -#' A dataset of lower-case letters of the Roman alphabet and their -#' numeric index from a = 1 to z = 26. -#' -#' @format A data frame with 26 rows and 2 variables: -#' \describe{ -#' \item{letter}{The letter as a character string.} -#' \item{index}{The corresponding numeric index.} -#' } -"letter_indices" -``` - -`#' @ examples` can be used here too. - -## Data source - -For collected data, the (original) source should be documented with `#' @source`. - -This should either be a url, e.g. -```{r} -#| eval: false -#' @source \url{http://www.diamondse.info/} -``` -(alternatively `\href{DiamondSearchEngine}{http://www.diamondse.info/}`), or a reference, e.g. - -```{r, eval = FALSE} -#' @source Henderson and Velleman (1981), Building multiple -#' regression models interactively. *Biometrics*, **37**, 391–411. -``` - -## Internal data - -Sometimes functions need access to reference data, e.g. constants or look-up tables, that don't need to be shared with users. - -These objects should be saved in a single `R/sysdata.rda` file. - -This can be done with `use_data(..., internal = TRUE)`, e.g. - -```{r, eval = FALSE} -x <- sample(1000) -usethis::use_data(x, mtcars, internal = TRUE) -``` - -The generating code and any raw data can be put in `/data-raw`. - -As the objects are not exported, they don't need to be documented. - -## Raw data - -Sometimes you want to include raw data, to use in examples or vignettes. - -These files can be any format and should be added directly into `/inst/extdata`. - -When the package is installed, these files will be copied to the `extdata` directory and their path on your system can be found as follows: - -```{r} -system.file("extdata", "mtcars.csv", package = "readr") -``` - -## Your turn - -1. Run `usethis::use_data_raw("farm_animals")`. -2. 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. -3. Run all the code (including the already-present call to `usethis::use_data()`) to create the data and save it in `/data`. -4. Add an `R/farm_animals.R` script and add some roxygen comments to document the data. -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. - # Publication {.inverse} # GitHub {.inverse} diff --git a/workshops.qmd b/workshops.qmd index c9850a8..1e8e0fc 100644 --- a/workshops.qmd +++ b/workshops.qmd @@ -6,5 +6,10 @@ title: Workshops Click on the links below for details on how to join, the schedule, and the instructors. -- [Summer 2025, cohort 1, Zoom](workshops/summer-2025-cohort-1.qmd) -- [Summer 2025, cohort 2, Zoom](workshops/summer-2025-cohort-2.qmd) \ No newline at end of file +- [May-July 2026, cohort 1, Zoom](workshops/may-july-2026-cohort-1.qmd) +- [May-July 2026, cohort 2, Zoom](workshops/may-july-2026-cohort-2.qmd) + +## Past workshops + +- [June-July 2025, cohort 1, Zoom](workshops/summer-2025-cohort-1.qmd) +- [June-July 2025, cohort 2, Zoom](workshops/summer-2025-cohort-2.qmd) \ No newline at end of file diff --git a/workshops/may-july-2026-cohort-1-intro.qmd b/workshops/may-july-2026-cohort-1-intro.qmd new file mode 100644 index 0000000..c4e842b --- /dev/null +++ b/workshops/may-july-2026-cohort-1-intro.qmd @@ -0,0 +1,88 @@ +--- +title: Welcome and introduction +subtitle: R package development workshop +author: Ella Kaye, Pao Corrales, Elio Campitelli +format: forwardspres-revealjs +--- + +# Hello! {.inverse .center-h} + +## Instructors + +:::: {.columns} + +::: {.column width="33%"} +### [Ella Kaye](https://ellakaye.co.uk) + +![](../images/instructors/ellakaye.jpeg){width=60%} + +:::{.smaller80} +Senior Research Software Engineer + +University of Birmingham +::: +::: + +::: {.column width="33%"} +### [Pao Corrales](https://paocorrales.github.io/) + +![](../images/instructors/paocorrales.png){width=60%} + +:::{.smaller80} +Research Software Engineer + +21st Century Weather Centre +::: +::: + +::: {.column width="33%"} +### [Elio Campitelli](https://eliocamp.github.io) + +![](../images/instructors/eliocampitelli.jpg){width=60%} + +:::{.smaller80} +Atmospheric Scientist + +Monash University +::: + +::: + +:::: + +## Course material + +- Website and slides + + + +- Website and slides repo + + + +- Course text: R Packages (2nd edition), Hadley Wickham and Jennifer Bryan + + + +## Schedule + +Mondays, 10:00-12:00 UTC + +| Module | Date | Lead | Facilitator | +|----|----|----|----| +| [Package foundations](../modules/01-package-foundations/index.qmd) | May 18th | Ella | Pao | +| [Function documentation and dependencies](../modules/02-documentation/index.qmd) | June 1st | Ella | Elio | +| [Packaging data; Testing](../modules/03-data-testing/index.qmd) | June 15th | Ella | Pao | +| [Package check and documentation](../modules/04-check-package-documentation/index.qmd) | June 29th | Pao | Elio | +| [Publication and maintenance](../modules/05-publication-maintenance/index.qmd) | July 13th | Pao | Elio | + +## Pieces of a puzzle + +* In each module we will cover different elements of an R package + * Think of this elements as pieces of a puzzle, you don't want to miss one! + * The course builds cumulatively + +If you can't attend to a session, please follow the material and do the exersises to catch up before the next one. + +# Let's begin... {.inverse .center-h} + diff --git a/workshops/may-july-2026-cohort-1.qmd b/workshops/may-july-2026-cohort-1.qmd new file mode 100644 index 0000000..9f98561 --- /dev/null +++ b/workshops/may-july-2026-cohort-1.qmd @@ -0,0 +1,45 @@ +--- +title: May-July 2026, cohort 1 +--- + +## Join + +::: {.callout-note} +Please note this workshop consists of a series of **five** sessions, which build on each other. +Luma does not offer the ability to schedule a series of recurring events. +To sign up for the series, please click on the 'Register on Luma' button below, which will take you to the calendar, +and make sure you register for all five **cohort 1** sessions (noting that there are also five sessions in the calendar for [cohort 2](may-july-2026-cohort-2.qmd)). +::: + +[Register on Luma](https://luma.com/Forwards_Package_Development){.btn .btn-primary target="_blank"} + +## Instructors + +{{< fa user >}}   [Ella Kaye](https://ellakaye.co.uk), [Pao Corrales](https://paocorrales.github.io/), and [Elio Campitelli](https://eliocamp.github.io). + +{{< fa envelope >}}   [e.kaye.1\@bham.ac.uk](mailto:e.kaye.1@bham.ac.uk) + +## Schedule + +{{< fa calendar >}}   Every other **Monday**, starting May 18th + +{{< fa clock >}}   {{< localtime 2026-05-18 10:00 UTC format="%H:%M" >}}-{{< localtime 2026-05-18 12:00 UTC format="%H:%M %Z" >}} (in your local timezone). + + +| Module | Date | Lead | Facilitator | +|----|----|----|----| +| [Package foundations](../modules/01-package-foundations/index.qmd) | May 18th | Ella | Pao | +| [Function documentation and dependencies](../modules/02-documentation/index.qmd) | June 1st | Ella | Elio | +| [Packaging data; Testing](../modules/03-data-testing/index.qmd) | June 15th | Ella | Pao | +| [Package check and documentation](../modules/04-check-package-documentation/index.qmd) | June 29th | Pao | Elio | +| [Publication and maintenance](../modules/05-publication-maintenance/index.qmd) | July 13th | Pao | Elio | + +## Welcome and introduction + +Here's a link to the [slides](may-july-2026-cohort-1-intro.qmd){target="_blank"} in a new window. + +Here are the slides embedded: + + + diff --git a/workshops/may-july-2026-cohort-2-intro.qmd b/workshops/may-july-2026-cohort-2-intro.qmd new file mode 100644 index 0000000..d30a4a9 --- /dev/null +++ b/workshops/may-july-2026-cohort-2-intro.qmd @@ -0,0 +1,91 @@ +--- +title: Welcome and introduction +subtitle: R package development workshop +author: Joyce Robbins, Jesi Formoso, Heather Turner +format: forwardspres-revealjs +--- + +# Hello! {.inverse .center-h} + +## Instructors + +:::: {.columns} + +::: {.column width="33%"} +### Joyce Robbins + +![](../images/instructors/joycerobbins.jpg){width=60%} + +:::{.smaller80} +Senior Lecturer in Statistics + +Columbia University +::: +::: + +::: {.column width="33%"} +### Jesi Formoso + +![](../images/instructors/jesiformoso.jpg){width=60%} + +:::{.smaller80} +Associate Researcher + +University of Buenos Aires +::: +::: + +::: {.column width="33%"} +### Heather Turner + +![](../images/instructors/heatherturner.jpeg){width=60%} + +:::{.smaller80} +Principal RSE + +University of Birmingham +::: + +::: + +:::: + + +## Course material + +- Website and slides + + + +- Website and slides repo + + + +- Course text: R Packages (2nd edition), Hadley Wickham and Jennifer Bryan + + + +## Schedule + +Tuesdays, 14:30-16:00 UTC + +:::{.smaller90} +| Module | Date | +|----|----| +| [Package foundations](../modules/01-package-foundations/index.qmd) | May 20th | +| [Function documentation and dependencies](../modules/02-documentation/index.qmd) | June 3rd | +| [Packaging data; Testing](../modules/03-data-testing/index.qmd) | June 17th | +| [Package check and documentation](../modules/04-check-package-documentation/index.qmd) | July 1st | +| [Publication and maintenance](../modules/05-publication-maintenance/index.qmd) | July 15th | +::: + +## Pieces of a puzzle + +* In each module we will cover different elements of an R package + * Think of this elements as pieces of a puzzle, you don't want to miss one! + * The course builds cumulatively + +If you can't attend to a session, please follow the material and do the exersises to catch up before the next one. + +# Let's begin... {.inverse .center-h} + diff --git a/workshops/may-july-2026-cohort-2.qmd b/workshops/may-july-2026-cohort-2.qmd new file mode 100644 index 0000000..e4f5dc2 --- /dev/null +++ b/workshops/may-july-2026-cohort-2.qmd @@ -0,0 +1,44 @@ +--- +title: May-July 2026, cohort 2 +--- + +## Join + +::: {.callout-note} +Please note this workshop consists of a series of **five** sessions, which build on each other. +Luma does not offer the ability to schedule a series of recurring events. +To sign up for the series, please click on the 'Register on Luma' button below, which will take you to the calendar, +and make sure you register for all five **cohort 2** sessions (noting that there are also five sessions in the calendar for [cohort 1](may-july-2026-cohort-1.qmd)). +::: + +[Register on Luma](https://luma.com/Forwards_Package_Development){.btn .btn-primary target="_blank"} + +## Instructors + +{{< fa user >}}   [Joyce Robbins](https://datascience.columbia.edu/people/joyce-robbins/), [Jesi Formoso](https://jformoso.github.io/) and [Heather Turner](https://heatherturner.net). + +{{< fa envelope >}}   [h.l.turner\@bham.ac.uk](mailto:h.l.turner@bham.ac.uk) + +## Schedule + +{{< fa calendar >}}   Every other **Wednesday**, starting May 20th + +{{< fa clock >}}   {{< localtime 2026-05-20 14:00 UTC format="%H:%M" >}}-{{< localtime 2026-05-20 16:00 UTC format="%H:%M %Z" >}} (in your local timezone). + +| Module | Date | +|----|----| +| [Package foundations](../modules/01-package-foundations/index.qmd) | May 20th | +| [Function documentation and dependencies](../modules/02-documentation/index.qmd) | June 3rd | +| [Packaging data; Testing](../modules/03-data-testing/index.qmd) | June 17th | +| [Package check and documentation](../modules/04-check-package-documentation/index.qmd) | July 1st | +| [Publication and maintenance](../modules/05-publication-maintenance/index.qmd) | July 15th | + +## Welcome and introduction + +Here's a link to the [slides](may-july-2026-cohort-2.qmd){target="_blank"} in a new window. + +Here are the slides embedded: + + + diff --git a/workshops/summer-2025-cohort-1-intro.qmd b/workshops/summer-2025-cohort-1-intro.qmd index 628a065..e82a1da 100644 --- a/workshops/summer-2025-cohort-1-intro.qmd +++ b/workshops/summer-2025-cohort-1-intro.qmd @@ -74,9 +74,9 @@ Mondays, 09:00-10:30 UTC |----|----|----|----| | [Package foundations](../modules/01-package-foundations/index.qmd) | June 2nd | Pao | Ella | | [Function documentation and dependencies](../modules/02-documentation/index.qmd) | June 16th | Ella | Pao | -| [Testing](../modules/03-testing/index.qmd) | June 30th | Pao | Elio | +| [Testing](../modules/03-data-testing/index.qmd) | June 30th | Pao | Elio | | [Package check and documentation](../modules/04-check-package-documentation/index.qmd) | July 14th | Ella | Pao | -| [Packaging data; Publication and maintenance](../modules/05-data-publication-maintenance/index.qmd) | July 28th | Pao | Elio | +| [Packaging data; Publication and maintenance](../modules/05-publication-maintenance/index.qmd) | July 28th | Pao | Elio | ## Pieces of a puzzle diff --git a/workshops/summer-2025-cohort-1.qmd b/workshops/summer-2025-cohort-1.qmd index fe44e34..2cab92a 100644 --- a/workshops/summer-2025-cohort-1.qmd +++ b/workshops/summer-2025-cohort-1.qmd @@ -22,9 +22,9 @@ title: Summer 2025, cohort 1 |----|----|----|----| | [Package foundations](../modules/01-package-foundations/index.qmd) | June 2nd | Pao | Ella | | [Function documentation and dependencies](../modules/02-documentation/index.qmd) | June 16th | Ella | Pao | -| [Testing](../modules/03-testing/index.qmd) | June 30th | Pao | Elio | +| [Testing](../modules/03-data-testing/index.qmd) | June 30th | Pao | Elio | | [Package check and documentation](../modules/04-check-package-documentation/index.qmd) | July 14th | Ella | Pao | -| [Packaging data; Publication and maintenance](../modules/05-data-publication-maintenance/index.qmd) | July 28th | Pao | Elio | +| [Packaging data; Publication and maintenance](../modules/05-publication-maintenance/index.qmd) | July 28th | Pao | Elio | ## Welcome and introduction diff --git a/workshops/summer-2025-cohort-2-intro.qmd b/workshops/summer-2025-cohort-2-intro.qmd index 8c5d86a..6fc3729 100644 --- a/workshops/summer-2025-cohort-2-intro.qmd +++ b/workshops/summer-2025-cohort-2-intro.qmd @@ -74,9 +74,9 @@ Tuesdays, 14:30-16:00 UTC |----|----|----|----| | [Package foundations](../modules/01-package-foundations/index.qmd) | June 3rd | Emma | Joyce and Heather | | [Function documentation and dependencies](../modules/02-documentation/index.qmd) | June 17th | Joyce | Emma and Heather | -| [Testing](../modules/03-testing/index.qmd) | July 1st | Heather | Joyce | +| [Testing](../modules/03-data-testing/index.qmd) | July 1st | Heather | Joyce | | [Package check and documentation](../modules/04-check-package-documentation/index.qmd) | July 15th | Joyce | Heather | -| [Packaging data; Publication and maintenance](../modules/05-data-publication-maintenance/index.qmd) | July 29th | Heather | Joyce | +| [Packaging data; Publication and maintenance](../modules/05-publication-maintenance/index.qmd) | July 29th | Heather | Joyce | ::: # Let's begin... {.inverse .center-h} diff --git a/workshops/summer-2025-cohort-2.qmd b/workshops/summer-2025-cohort-2.qmd index 8114c69..f8c0093 100644 --- a/workshops/summer-2025-cohort-2.qmd +++ b/workshops/summer-2025-cohort-2.qmd @@ -22,9 +22,9 @@ title: Summer 2025, cohort 2 |----|----|----|----| | [Package foundations](../modules/01-package-foundations/index.qmd) | June 3rd | Emma | Joyce and Heather | | [Function documentation and dependencies](../modules/02-documentation/index.qmd) | June 17th | Joyce | Emma and Heather | -| [Testing](../modules/03-testing/index.qmd) | July 1st | Heather | Joyce | +| [Testing](../modules/03-data-testing/index.qmd) | July 1st | Heather | Joyce | | [Package check and documentation](../modules/04-check-package-documentation/index.qmd) | July 15th | Joyce | Heather | -| [Packaging data; Publication and maintenance](../modules/05-data-publication-maintenance/index.qmd) | July 29th | Heather | Joyce | +| [Packaging data; Publication and maintenance](../modules/05-publication-maintenance/index.qmd) | July 29th | Heather | Joyce | ## Welcome and introduction