School of Biological Sciences, Waipapa Taumata Rau
2026-07-02
Everything today comes from one free online chapter:
If you want the written version to work through later, start there. This talk is a live run at the same workflow.
Two books cover the full picture:
The first is the reference for everything package-related. The second is the one to reach for the moment Git misbehaves.



.csv on Google Drive, Dropbox
A lot of data ends up shared as a .csv over email, or a Supplementary File on a journal site, or a path that only works on one machine.
A data package does better. Once it exists, anyone gets the data in three lines:
You get:
?df_somethingtibble, fast to readFour folders, four jobs:
data-raw/ the raw fish: source CSVs and the scripts that clean themdata/ the sushi roll: one tidy .rda, ready to serveR/ the menu: what each column meansman/ the chef at your table: the help page a user actually reads
Illustration: rstudio4edu, CC BY-NC
Every stage from here maps onto one of these four folders.
Illustration: rstudio4edu, CC BY-NC
Each function moves the data one folder to the right. That is the whole talk.
| Folder | Purpose | In our toy mothsurvey.data |
|---|---|---|
data-raw/ |
Raw CSVs and cleaning scripts (not shipped) | 1 source CSV, 1 cleaning script |
data/ |
Cleaned .rda files (shipped) |
df_moths.rda |
R/ |
Roxygen-documented .R files |
df_moths.R, mothsurvey.data-package.R |
man/ |
Auto-generated .Rd help files |
df_moths.Rd, mothsurvey.data-package.Rd |
Plus the root: DESCRIPTION, NAMESPACE, LICENSE, README.md.
Raw data → cleaned object → documented object → installable, citable package.
.R script and a .qmd or .rmdTip
If anything above is shaky, the two books from the start of the talk (Happy Git with R and R Packages 2e) are the places to go.
We’ll build a small package live: a toy called mothsurvey.data, five rows of light-trap moth counts.
.csv into data-raw/ with a cleaning script.rda in data/man/DESCRIPTION, choose a licenseinstall_github()At the end we’ll look at the same workflow at real scale, in my own package anagotus.data.
Empty repo → package scaffold
Three workhorses:
usethis: scaffolds files and foldersdevtools: builds, checks, documents, installsroxygen2: turns #' comments into .Rd help filesNote
Everything that follows should work similarly in Positron. References to the “Build pane” map to Positron’s package commands.
Recommended: GitHub first
.gitignore)Choose No when asked about overwriting.
A second IDE session opens with package-building tools enabled. Use that one.
The package name is the repo name. Rules:
. only.Our demo package is mothsurvey.data, using the <topic>.data convention: a clear signal that it’s a data-only package. Other patterns you’ll see in the wild:
nzcarabidae, pollinatortraits: short, no separatorlepidata, mothdata.nz: topic-firstweevilSpecimens: camelCase (works but less common)Hyphens and underscores are not allowed in package names. Dots are.
🎣 data-raw/ → 🍣 data/
data-raw/This creates:
data-raw/ (added to .Rbuildignore, so it doesn’t ship)data-raw/DATASET.Rdata-raw/ is the messy workspace. Cleaning here can use tidyverse, sf, janitor, lubridate, anything: those packages do not become dependencies of your shipped package.
Our toy source, data-raw/moth_counts.csv, five nights of light-trap catches:
site,date,species,count
Cascade Kauri,2025-01-12,Declana floccosa,3
Cascade Kauri,2025-01-12,Epiphryne verriculata ,1
Waharau,2025-01-19,declana floccosa,2
Waharau,2025-01-19,Epiphryne verriculata,5
Cascade Kauri,2025-02-02,Declana floccosa,4
Deliberately messy: a trailing space on one species, an inconsistent capital, and dates stored as text. Real data always arrives like this.
data-raw/moth_counts.R:
One read, three small fixes. That’s the whole cleaning step for the toy. The point is that data-raw/ is where arbitrary tidying lives.
At the end of the script:
Writes data/df_moths.rda, the shipped artefact.
.rda and not .csv?<chr>, <dbl>, <date>, <fct>) exactlyThe cost: it’s binary. But you keep the source CSV in data-raw/ for diff-friendliness, and the cleaning script is the audit trail.
Does R agree this is a package?
First-time output usually includes:
❯ checking for missing documentation entries ... WARNING
Undocumented data sets: 'df_moths'
❯ checking data for ASCII and uncompressed saves ... WARNING
package needs dependence on R (>= 2.10)
Both are expected. The dataset isn’t documented yet, and the dependency line is missing from DESCRIPTION. Fixed next.
🍣 data/ → 📋 the menu (R/ and man/)
Creates R/df_moths.R. Add the documentation above the dataset name:
#' Light-trap moth counts from two Auckland sites
#'
#' A small toy dataset of nightly light-trap catches, used to
#' demonstrate how to build an R data package.
#'
#' @format A tibble with 5 rows and 4 variables:
#' \describe{
#' \item{site}{chr Survey site name.}
#' \item{date}{date Night of the survey.}
#' \item{species}{chr Moth species (Lepidoptera: Geometridae).}
#' \item{count}{dbl Number of individuals caught.}
#' }
#' @source Made up for teaching.
"df_moths"\describe{} block mattersIt’s how a user knows what each column means without opening your code. Document every variable:
site: where the trap was rundate: the night of the survey (a real date, not text)species: the moth, with its family in bracketscount: how many came to the lightFour variables here. In a real dataset it might be fifteen, and this block is where future-you (and reviewers) will thank present-you.
Walks every #' block in R/ and writes matching .Rd files into man/.
Tip
Re-run document() every time you edit Roxygen comments. The man/ folder is generated, never edit it directly.
DESCRIPTION, license, package-level docs
DESCRIPTIONThe toy’s DESCRIPTION:
Package: mothsurvey.data
Title: Toy Light-Trap Moth Counts
Version: 0.1.0
Authors@R:
person("Neil", "Birrell", email = "nbir012@aucklanduni.ac.nz",
role = c("aut", "cre"),
comment = c(ORCID = "0000-0002-7961-1626"))
Description: A tiny toy dataset of light-trap moth counts, used to
demonstrate how to build an R data package.
License: MIT + file LICENSE
Encoding: UTF-8
LazyData: true
Depends: R (>= 3.5)
Imports: tibble
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.3.3Authors@R: with an ORCID, you’re citableURL: points to both the repo and the pkgdown site (add these once it’s on GitHub)BugReports: wires bug.report() to GitHub issuesDepends: R (>= 3.5): silences the ASCII/compression warningLazyData: true: datasets are available on attach, no data() neededImports: tibble: lets datasets print as tibblesRoxygen: list(markdown = TRUE): use markdown in roxygen, not Rd syntaxThe simplest defensible choice for shared research data is MIT:
Writes both LICENSE and LICENSE.md, and updates DESCRIPTION.
For data specifically you may prefer CC-BY or CC0. The principle is the same: pick one, name it in DESCRIPTION, ship the file.
Note
Unlicensed data is, by default, not freely usable. Choose something.
Creates R/mothsurvey.data-package.R. With it open, run:
The file ends up as:
Then devtools::document() one more time. Now ?mothsurvey.data shows the package landing page.
📋 → 🧑🍳 the finished package
Build pane → Install and Restart, or:
A fresh R session loads. Then:
Note
Run devtools::check(document = FALSE) one final time. Aim for 0 errors, 0 warnings, 0 notes before pushing.
Push, tag, share
Commit, push, tag a release on GitHub. From there, anyone installs it in one line:
Tagged releases also trigger a Zenodo deposit (once you’ve connected the repo), giving you a versioned DOI for every release.
When raw data or cleaning logic changes:
devtools::document()devtools::check(document = FALSE), aim for cleanVersion in DESCRIPTION, commit, push, tag releasePut these steps in a comment at the bottom of your cleaning script. They live in the repo, not in someone’s head.
# 1. Empty repo on GitHub → clone → open in IDE
usethis::create_package(getwd())
# 2. Raw CSV plus cleaning script go in data-raw/
usethis::use_data_raw()
# (drop moth_counts.csv and moth_counts.R there)
# 3. At the end of the cleaning script:
usethis::use_data(df_moths, overwrite = TRUE)
# 4. Document the dataset
usethis::use_r("df_moths") # add roxygen comments
# 5. Document the package
usethis::use_package_doc()
usethis::use_tibble()
usethis::use_mit_license("Neil Birrell")
# 6. Build
devtools::document()
devtools::check(document = FALSE)
devtools::install()
# 7. Push to GitHub, tag a release. Done.anagotus.dataFrom five rows to two thousand
anagotus.dataLabel data for Anagotus spp. specimens (large flightless weevils, endemic to NZ) housed in museum collections.
Same four folders, more of everything:
anagotus.data/
├── DESCRIPTION
├── NAMESPACE
├── LICENSE / LICENSE.md
├── README.md
├── _pkgdown.yml # pkgdown site config
├── renv.lock # pinned dependencies
├── R/
│ ├── df_anagotus.R # roxygen for the dataset
│ └── anagotus.data-package.R # package-level docs
├── data/
│ └── df_anagotus.rda
├── data-raw/
│ ├── run_me.R # orchestrator
│ ├── 1_anagotus_data_load.R # load and clean all sources
│ ├── 2_create_rda.R # export to data/
│ └── *.csv # 8 raw specimen CSVs
├── man/ # auto-generated .Rd files
└── inst/
└── CITATION
The toy used one tidy CSV. The real package starts from eight, each with different column names, coordinate systems, and date formats.
data-raw/
├── 1_anagotus_data_load.R # load and harmonise every source
├── 2_create_rda.R # use_data() to save to data/
└── run_me.R # source the numbered scripts in order
Same folder, same use_data() at the end. The extra work is a run_me.R orchestrator and some sf coordinate wrangling. The pattern does not change, only the volume.
anagotus.data carries a few things the toy skips:
inst/CITATION: defines what citation("anagotus.data") returns_pkgdown.yml: config for the docs site at https://nbir012.github.io/anagotus.data/renv.lock: pins the exact package versions used to build the data, for reproducibility.github/: GitHub Actions for automated R CMD check and pkgdown rebuild on pushinst/CITATION fileLives at inst/CITATION (not data-raw/, not the root). The actual file:
bibentry(
bibtype = "Manual",
title = "anagotus.data: Label Data for Anagotus Specimens",
author = person("Neil", "Birrell",
email = "nbir012@aucklanduni.ac.nz",
role = c("aut", "cre"),
comment = c(ORCID = "0000-0002-7961-1626")),
year = "2025",
note = "R package version 0.1.0",
url = "https://nbir012.github.io/anagotus.data/"
)Users get a properly formatted citation:
Same one line as the toy. That’s the whole point: a collaborator, student, or reviewer gets the data with a single command, documented and citable.
usethis: https://usethis.r-lib.orgdevtools: https://devtools.r-lib.organagotus.data: https://github.com/nbir012/anagotus.datagit annex: https://git-annex.branchable.comwith:
dataLad: https://www.datalad.org/