Instructor Guide
2026-10-15 10:59 PDT
This guide is for course instructors. It contains grading keys for Exercise 7 in each chapter, the cohort-based, autogradable exercise. Solutions for all other exercises, including checks and practice exercises, appear in the chapter itself.
Exercise 7 in every chapter asks the student to apply the chapter’s core tool or concept to the running cohort compendium (analysis/data/raw_data/cohort_raw.csv, generated by analysis/scripts/simulate_cohort.R). Each solution below gives the expected values a grading script can compare against, a brief interpretation rubric, and the common errors to watch for.
The cohort has a fixed seed (set.seed(20260908)) and a deterministic generator, so the autogradable values below are exact. A student whose values differ has either an edited generator, an edited extract, or a different version of clean_cohort() than the one shipped with the book; each of these deviations is diagnostically useful and worth discussing.
Chapter 2: The Reproducibility Crisis
raw <- load_cohort('analysis/data/raw_data/cohort_raw.csv')
class(raw$sex)
#> [1] "character"
legacy <- utils::read.csv(
'analysis/data/raw_data/cohort_raw.csv',
stringsAsFactors = TRUE)
class(legacy$sex)
#> [1] "factor"Autogradable values: class(raw$sex) = "character"; class(legacy$sex) = "factor".
Rubric. A correct answer names the two class strings and explains that the difference demonstrates the R-runtime layer: stringsAsFactors is an R default, not a package default or a property of the data, and its value flipped between R releases. load_cohort() closes the exposure at the analysis-code layer by stating the choice explicitly.
Common errors. Confusing the R-runtime layer with the package-versions layer; omitting the explanation of which determinant-stack layer the difference occupies.
Chapter 3: Levels of Reproducibility
cohort <- clean_cohort(
load_cohort('analysis/data/raw_data/cohort_raw.csv'))
nrow(cohort)
#> [1] 337Autogradable value: nrow(cohort) = 337.
Rubric. A correct answer reports 337 rows and places the compendium at L0 (locatable). The missing artifact for the next rung, L1, is a lockfile (renv.lock) recording the exact version of tibble, utils, and every other package the analysis calls.
Common errors. Claiming L1 because the code is under version control (version control is L0); confusing ‘pinned’ with ‘committed’.
Chapter 4: Version Control
raw <- load_cohort('analysis/data/raw_data/cohort_raw.csv')
nrow(raw)
#> [1] 338
intersect(c('mrn', 'patient_name', 'dob'), names(raw))
#> [1] "mrn" "patient_name" "dob"Autogradable values: nrow(raw) = 338; all three identifier columns present.
Rubric. A correct answer reports 338 rows (the raw extract before cleaning, including the planted duplicate), names all three identifier-shaped columns, and explains that in a real project any one of them would require gitignoring the raw directory. The extract is synthetic, so the current gitignore entry is correct practice, not a violation.
Common errors. Using nrow(cohort) (337) instead of nrow(raw) (338); not distinguishing the raw from the cleaned extract.
Chapter 5: The Research Compendium
raw <- load_cohort('analysis/data/raw_data/cohort_raw.csv')
cohort <- clean_cohort(raw)
nrow(raw)
#> [1] 338
nrow(cohort)
#> [1] 337
pillars <- c(
file.exists('Dockerfile'),
file.exists('renv.lock'),
file.exists('.Rprofile')
)
sum(pillars)
#> [1] 1Autogradable values: nrow(raw) = 338; nrow(cohort) = 337; sum(pillars) = 1.
Rubric. A correct answer reports all three numbers and identifies .Rprofile as the one directly-checked pillar present. Adding the two pillars the script does not check (source code under version control and the committed generator) brings the total to three of five, with Dockerfile and renv.lock absent.
Common errors. Claiming the compendium is at L1 because three pillars are present; not distinguishing which pillars the script checks from the full five-pillar count.
Chapter 6: Dependencies
deps <- renv::dependencies(
c('R/cohort.R', 'analysis/scripts/simulate_cohort.R')
)
unique(deps$Package)
#> [1] "stats" "tibble" "utils"Autogradable values: unique(deps$Package) = c("stats", "tibble", "utils"); contributed packages requiring a lockfile entry = "tibble" only.
Rubric. A correct answer identifies three referenced packages, explains that stats and utils are base packages excluded from the lockfile, and concludes that tibble is the only contributed package. simulate_cohort.R contributes zero rows because every function it calls is a base-package function or a primitive.
Common errors. Listing all three packages as needing lockfile entries; not checking the generator script separately.
Chapter 7: Containers
raw <- load_cohort('analysis/data/raw_data/cohort_raw.csv')
cohort <- clean_cohort(raw)
nrow(cohort)
#> [1] 337
length(unique(cohort$sex))
#> [1] 2
file.exists('Dockerfile')
#> [1] FALSEAutogradable values: nrow(cohort) = 337; length(unique(cohort$sex)) = 2; file.exists('Dockerfile') = FALSE.
Rubric. A correct answer reports all three values. The Dockerfile is absent because the compendium has not yet reached L2. The ENV line needed for thread-count reproducibility is OMP_NUM_THREADS=1 together with OPENBLAS_NUM_THREADS=1.
Common errors. Reporting unique(cohort$sex) (two strings) rather than length(unique(cohort$sex)) (the integer 2); not identifying the thread-count ENV line as the missing Dockerfile addition.
Chapter 8: Backends and Runtimes
repo_root <- '.'
c(
renv.lock = file.exists(file.path(repo_root, 'renv.lock')),
Dockerfile = file.exists(file.path(repo_root, 'Dockerfile')),
default.nix = file.exists(file.path(repo_root, 'default.nix')),
devcontainer = file.exists(
file.path(repo_root, '.devcontainer', 'devcontainer.json')))Expected result: FALSE, FALSE, FALSE, TRUE (only the devcontainer file present at the time of writing).
Rubric. A qualitative answer is expected here. A correct answer identifies Nix via rix (axis two) as the backend that could lift the compendium to L2 today without a Docker image, because axis one needs an image to swap the runtime on, and there is none yet, and axis three’s only existing artifact pins a generic environment.
Common errors. Treating the devcontainer as evidence of L2 (it points at a generic image, not a compendium-specific one); not explaining why axis two is the available path.
Chapter 9: Literate Programming
cohort <- clean_cohort(
load_cohort('analysis/data/raw_data/cohort_raw.csv'))
n_cohort <- nrow(cohort)
pct_missing <- round(100 * mean(is.na(cohort$incident_dm)), 1)Autogradable values: n_cohort = 337; pct_missing = 9.5.
Rubric. A correct answer inserts both values as inline R expressions (r n_cohort and r pct_missing) rather than typing them after a console check. The sealed sentence reads: ‘The cleaned cohort holds 337 records, of which 9.5 percent carry no recorded outcome.’
Common errors. Typing the numbers directly in prose rather than using inline expressions; using nrow(raw) (338) instead of nrow(cohort) (337).
Chapter 10: Automating the Analysis
cohort <- clean_cohort(
load_cohort('analysis/data/raw_data/cohort_raw.csv'))
nrow(cohort)
#> [1] 337Autogradable value: nrow(cohort) = 337.
_targets.R fragment:
list(
tar_target(
raw_file,
'analysis/data/raw_data/cohort_raw.csv',
format = 'file'
),
tar_target(raw, load_cohort(raw_file)),
tar_target(cleaned, clean_cohort(raw))
)Rubric. A correct answer reports 337 rows and includes a _targets.R fragment with a format = 'file' target for the CSV. After the first tar_make(), a second run with no file changed should report both targets as already up to date, confirmed by tar_outdated() returning an empty result.
Common errors. Omitting the format = 'file' target for the CSV (meaning file-content changes would not re-trigger downstream targets); placing the compendium at L3 because the pipeline runs green (a pipeline pins no determinant, so the compendium stays at L0).
Chapter 11: Testing and Code Quality
raw <- load_cohort('analysis/data/raw_data/cohort_raw.csv')
cohort <- clean_cohort(raw)
expect_true(all(c('mrn', 'age_at_enroll', 'sex', 'incident_dm')
%in% names(raw)))
expect_true(all(cohort$age_at_enroll >= 18 &
cohort$age_at_enroll <= 105, na.rm = TRUE))
expect_true(all(cohort$sex %in% c('F', 'M')))
expect_true(mean(is.na(cohort$incident_dm)) < 0.15)Autogradable values: nrow(raw) = 338; nrow(cohort) = 337; sum(duplicated(raw$mrn)) = 1.
Rubric. A correct answer reports all three counts, identifies one duplicate removed by clean_cohort(), and shows four passing expect_true() assertions covering required columns, age range, sex coding, and missing-outcome rate. The assertions should pass against the committed extract.
Common errors. Reporting nrow(cohort) = 338 (forgetting that clean_cohort() removes one duplicate); writing assertions that pass trivially (e.g., expect_true(TRUE)).
Chapter 12: Continuous Integration
used <- unique(renv::dependencies('R/cohort.R')$Package)
base_pkgs <- rownames(installed.packages(priority = 'base'))
missing <- setdiff(used, c('tibble', 'ggplot2', 'stats',
'utils', base_pkgs))
length(missing) # 0Autogradable values: length(missing) = 0; after appending library(sandwich), missing2 = "sandwich".
Rubric. A correct answer reports zero undeclared packages against the real R/cohort.R, and "sandwich" as the single undeclared package after the synthetic addition. The exercise demonstrates that a dependency-manifest gate can catch an undeclared import before CI runs a render.
Common errors. Running renv::dependencies() on the whole project rather than on R/cohort.R alone; not testing the planted library(sandwich) scenario.
Chapter 13: Archiving and Sharing
file.exists('LICENSE')
#> [1] TRUE
file.exists('DESCRIPTION')
#> [1] TRUE
any(grepl('raw_data', readLines('.gitignore')))
#> [1] TRUEAutogradable values: all three booleans = TRUE.
Rubric. A correct answer reports all three TRUE and explains that none of the three file-system findings would need to change for real patient-level data. LICENSE and DESCRIPTION are needed regardless. The .gitignore entry is already correct practice: the CSV is generated from a committed script rather than tracked directly, matching the pattern the chapter prescribes for data with identifier-shaped columns. What changes for real data is the deposit itself (a data-availability statement and a synthetic stand-in, rather than the real extract), not the file-system checks.
Common errors. Stating that the third check would need to change (it already demonstrates correct practice); confusing the file-system audit with the deposit decision.