Back to the lesson·Lesson 1 of 8·A project that reopens
An analysis that reopens a year later
The same deck as the downloads, rendered as a page. Start the slideshow to present it full screen — arrow keys or a click advance one slide, Escape leaves.
What this lesson covers
- The failure this lesson prevents
- The three habits to drop
- The project
here()rather than relative paths- renv: the package versions, recorded
- Loading packages
- Verifying the environment before trusting it
- What to hand to a colleague
- What comes next
Speaker notes
RStudio projects, here(), renv and the two habits — setwd() and a saved workspace — that make an R analysis run on exactly one machine on exactly one day.The failure this lesson prevents
- Someone asks you to explain last quarter's figure.
Speaker notes
Someone asks you to explain last quarter's figure. You open the analysis, run it, and get a different number — or it does not run at all, because the package that produced it has moved on two versions. That is not a bug in the analysis. It is the environment, and in R there are three specific habits that cause it.The three habits to drop
rm(list = ls())at the top- Restart R instead — Ctrl/Cmd + Shift + F10 in RStudio
- A saved workspace
Speaker notes
This runs on exactly one computer. It is the single most common reason a colleague's script fails on your machine, and the fix is not to edit the path — it is to stop having one. It looks like starting clean and is not. It removes the objects in your workspace and leaves everything else: attached packages, options, the working directory, anything loaded from a.Rprofile. A script that needs it is a script that is not reproducible, and running it in a fresh session is the only real test. Restart R instead — Ctrl/Cmd + Shift + F10 in RStudio. That is the clean startrm(list = ls())pretends to be. RStudio offers to save.RDatawhen you quit and to reload it when you start. Turn both off:The three habits to drop — Example
Tools -> Global Options -> General Restore .RData into workspace at startup [ ] Save workspace to .RData on exit NeverSpeaker notes
A reloaded workspace means your session contains objects whose code you cannot see. The analysis appears to work because something from last week is still in memory, and it stops working the moment someone else runs it.The project — Example
muac-analysis/ muac-analysis.Rproj data/ raw/ the export exactly as it arrived, never edited interim/ outputs/ tables/ figures/ R/ read_register.R indicator_table.R renv.lock README.md .gitignoreSpeaker notes
An RStudio project is a directory with a.Rprojfile in it. Opening the project sets the working directory to that directory, and that is the whole mechanism — but it is enough to make every path in your code relative to the project instead of to your home folder.The project
data/rawis read-only — The export as it arrived is the only thing you cannot reproduce; everything else is…
Speaker notes
data/rawis read-only. The export as it arrived is the only thing you cannot reproduce; everything else is regenerated by running the code. So it is never edited, never sorted in Excel "just to look", and a correction lands beside the original as a new versioned filename rather than replacing it.here()rather than relative paths — In Rlibrary(here) muac <- readr::read_csv(here("data", "raw", "muac-screening-artibonite-2024.v1.csv"))Speaker notes
A relative path like"data/raw/muac.csv"works from the project root and breaks in a notebook, in an R Markdown document knitted from a subdirectory, or when someone runs one line at a time from a different location.here()rather than relative paths — In Rhere() #> [1] "/home/marie/muac-analysis"Speaker notes
here()finds the project root — the directory containing the.Rprojfile, or a.herefile, or a.gitdirectory — and builds the path from there. The result is the same wherever the code is run from. Callhere()once at the top of a script and use it everywhere. A path built bypaste0()with a/in it will not work on Windows;here()handles the separator.renv: the package versions, recorded — In R
install.packages("renv") renv::init() # once per projectSpeaker notes
here()fixes paths. It does nothing about the packages, and the packages are where the number changes.renv: the package versions, recorded — In R
renv::snapshot() # after installing or upgrading anything renv::restore() # on another machine, or a year laterSpeaker notes
renv::init()gives the project its own library and writesrenv.lock, a record of every package and its exact version. From then on:renv: the package versions, recorded
- Commit
renv.lock— It is the difference between "install the tidyverse" and "install the versions that produced this… - Installing where there is no internet
Speaker notes
Commitrenv.lock. It is the difference between "install the tidyverse" and "install the versions that produced this table". The part most tutorials skip, and the part that matters on a deployment. On a connected machine, download the sources once:- Commit
renv: the package versions, recorded — In R
renv::init() renv::snapshot() # Fill a local cache with everything the lockfile names. renv::install() renv::isolate()renv: the package versions, recorded — In R
# On the connected machine dir.create("pkgs") download.packages(c("dplyr", "readr", "haven"), destdir = "pkgs", type = "source") # On the offline machine install.packages( c("dplyr", "readr", "haven"), repos = NULL, type = "source", contriburl = paste0("file://", normalizePath("pkgs")) )Speaker notes
Then copy the project directory — includingrenv/library— to the field laptop.renv::restore()will use what is already there rather than reaching for a repository it cannot see. For a machine that will need packages you have not yet installed, the general mechanism is a local repository: Source packages need a compiler for anything with C or C++ in it, which is common. If the field machines are Windows, download the binaries instead (type = "win.binary") on a Windows machine of the same R version.Loading packages
- Do not use
require()in a script — It returnsFALSEand continues when the package is missing, so the script fails… - Do not call
install.packages()from a script — A script that installs software as a side effect of being run is a…
Speaker notes
Two things not to do: Do not userequire()in a script. It returnsFALSEand continues when the package is missing, so the script fails later with a confusing error about an object that does not exist.library()stops there and tells you which package. Do not callinstall.packages()from a script. A script that installs software as a side effect of being run is a script nobody can safely run twice. Installation isrenv::restore(), once, deliberately. Where two packages export the same name —dplyr::filter()andstats::filter(),dplyr::lag()andstats::lag()— say which you mean:- Do not use
Loading packages — In R
muac |> dplyr::filter(muac_mm < 125)Speaker notes
The::form is worth the characters in a script that will outlive your memory of what was attached.Verifying the environment before trusting it — In R
stopifnot(getRversion() >= "4.2.0") stopifnot(requireNamespace("dplyr", quietly = TRUE))Speaker notes
For an analysis whose numbers matter, assert rather than inspect:|>, the native pipe used throughout this course, needs R 4.1 or later. If your team is on an older R,%>%from magrittr does the same job and the code in these lessons works unchanged with it.What to hand to a colleague
Commit Do not commit R/,.Rprojrenv/library/renv.lockdata/raw/— see belowREADME.mdoutputs/.gitignore.Rhistory,.RDataWhat to hand to a colleague — Example
.Rproj.user/ .Rhistory .RData renv/library/ data/raw/ outputs/What to hand to a colleague
- Never commit raw beneficiary data — identified or pseudonymised
Speaker notes
Never commit raw beneficiary data, identified or pseudonymised. Git keeps every version of every file forever, and a repository that was internal on Monday can be shared on Friday. Credentials — a DHIS2 token, a KoboToolbox key — never appear in a script:What to hand to a colleague — In R
token <- Sys.getenv("DHIS2_TOKEN") stopifnot(nzchar(token))Speaker notes
Keep the value in a.Renvironfile that.gitignorecovers, and document the variable's name — not its value — in the README.What comes next
- The project reopens and the packages are pinned.
Speaker notes
The project reopens and the packages are pinned. The next lesson reads an export into it:readrfor CSV,readxlfor Excel, and the column specification that stops a facility code from becoming a number.