cassionData Analysis

Lab · Intermediate

Make it run on someone else's laptop

You are handed a working analysis in a single notebook with hard-coded paths, no environment file and a chart exported by hand. Turn it into something a colleague can clone and run — and prove it by running it somewhere it has never been.

PythonYour own machine180 min

The analysis works. It produces a GAM estimate by commune, a chart, and a paragraph for the cluster report, and it has been doing so for four months.

It is one notebook. The path to the data is /Users/aline/Downloads/muac.csv, the chart was exported by right-clicking it, the seed is wherever numpy’s default left it, and the person who wrote it is leaving in three weeks.

Your job is to make it survive that.

The file

muac-screening-artibonite-2024.v1.csv — 4,218 screenings across twelve communes. Treat it as the monthly export: it arrives, it is read-only, and next month’s will be the same shape with different rows.

Part one: the layout

Build the structure from lesson 1 and move the analysis into it. Nothing in src/ may contain an absolute path, and nothing may be produced by hand.

project/
  data/raw/          the CSV, read-only, with CHECKSUMS
  src/               one function per step
  outputs/           everything regenerated
  tests/
  run.py             the whole pipeline, one command
  .gitignore         written first
  README.md

Commit .gitignore before anything else, and make sure data/raw/* is excluded with !data/raw/CHECKSUMS letting the fingerprint back in.

Part two: the environment

Initialise uv, add what the analysis imports, commit pyproject.toml and uv.lock. Pin the Python version as a range.

Then delete your virtual environment and run uv sync. If anything is missing, the analysis was importing something it never declared.

Part three: the four sources of difference

The original notebook has at least three of the four from lesson 4. Find them and fix them:

  • the clock — anything using today’s date;
  • the seed — the analysis bootstraps a confidence interval;
  • file order — if it globs anything;
  • the locale — the CSV read and any date parsing.

Then prove it. Run the pipeline twice into different directories and diff them. The diff must be empty.

python run.py && cp -r outputs outputs-first
rm -rf outputs && python run.py && diff -r outputs outputs-first

Part four: the checks

Add the loader checks from lesson 6. At minimum: required columns present, row count in range, commune values within the known set, muac_mm within a plausible range, and an assertion that the summary’s denominators sum to the number of screenings.

Then break the data on purpose — a copy with a renamed column, a copy truncated to 500 rows, a copy with Anse Rouge instead of Anse-Rouge — and confirm each one stops the pipeline with a message naming the problem.

Part five: prove it somewhere else

git clone . /tmp/handover-test && cd /tmp/handover-test
uv sync
python run.py
diff -r outputs/ ~/project/outputs/

A fresh clone in a temporary directory is the minimum. A colleague’s laptop is better. If you have CI available, add a workflow that does exactly this on every push.

Part six: the handover

Write HANDOVER.md using the seven sections from lesson 7. Sections 4 and 6 are the graded ones:

Section 4 must record every decision your pipeline makes that the code does not explain — which communes are normalised and why, what happens to screenings with no age, whether oedema cases are counted, and what the case definition is.

Section 6 must name the finding that will be challenged. You already know what it is if you did the visualisation course: the commune ranking.

Check your numbers

Expected
Screenings 4,218
Communes 12
Screenings, smallest and largest commune 189 and 768
GAM, whole district (MUAC or oedema) 10.3%
GAM, MUAC below 125 mm only 9.3%
Highest and lowest commune 15.3% and 5.6%
Communes whose interval clears the district median 3
Second pipeline run byte-identical to the first
Fresh-clone run byte-identical to both

If your two runs differ only in one figure file, you have found the timestamp that lesson 4 is about — SVG and PNG writers both embed one unless told not to.

The questions to answer in prose

Three sentences each.

1. Name the sources of run-to-run difference you found and how you found each. For any of the four you did not find, say why the analysis was already immune.

2. You broke the data three ways and each stopped the pipeline. For the truncated file, say what the analysis would have reported had the check not been there, and how plausible that number would have looked.

3. Your handover section 4 records decisions. Pick the one a successor is most likely to change without realising it matters, and say what the consequence would be.

What to hand in

  • the restructured project, running from a fresh clone with two commands
  • uv.lock, committed
  • tests/ with the loader checks, and evidence that each catches its defect
  • HANDOVER.md, seven sections
  • the output of the two-run diff and the fresh-clone diff, both empty
  • the three prose answers

How to know you are done

Give it to someone else and watch them run it without helping. Every question they ask is a line missing from the README or the handover, and every failure is a dependency you did not declare.

Failing that: delete your local checkout entirely, clone from the remote into a new directory, and run it. That catches everything except what is installed globally on your machine — which is the one thing only another machine finds.

What this lab is not

It is not a nutrition analysis. The GAM estimate is one the nutrition course already produced, and reproducing it is the point rather than the finding.

It is also not about tooling preference. uv and renv are the tools this course uses; conda, poetry, pip-tools and packrat all satisfy the same requirement, which is that a lock file exists and is committed. The requirement is the lesson; the tool is a detail that will change.