cassionData Analysis

Lesson 4 of 8

Unit · The same answer twice

Four things that change the answer when nothing changed

The clock, the seed, the file order and the locale. Each produces a different result from identical code and identical data, each is invisible in a diff, and this platform hit three of the four in production.

PythonR135 minOECD DAC evaluation criteriaSMART survey

One: the clock

from datetime import date
report_date = date.today()          # different every day, by design

Anything that reads the current time makes every run differ. A generated-on footer, a filename with today’s date, a filter for “the last 30 days”, a random seed derived from the time.

The fix is to take the date from the data or from a parameter.

import pandas as pd

survey = pd.read_csv("data/raw/household-survey-2025.v1.csv")
as_of = pd.to_datetime(survey["interview_date"]).max()   # from the data
as_of <- max(as.Date(survey$interview_date))

This platform got this wrong and the failure is instructive. The course handouts and lesson decks originally took their generation date from the newest updated field across the entire content library. Deterministic — and still wrong, because publishing one new course restamped every existing handout and rewrote all ninety-six deck binaries.

The date is now scoped to the artefact. A handout’s date comes from its own course and lessons, a deck’s from its own lesson, a report’s from its own project. A deterministic wrong answer is still a wrong answer, and “the diff is enormous but correct” is how nobody reviews it.

Two: the seed

import numpy as np

rng = np.random.default_rng(20260729)      # stated, committed, reproducible
sample = rng.choice(households, size=200, replace=False)
set.seed(20260729)
sample(households, 200)

Anything random needs a seed and the seed belongs in the code. Bootstrap intervals, random sampling for verification, simulation, train/test splits, jittered scatter points.

Set it once, at the top, visibly. A seed buried three functions deep is a seed someone will move.

And say so in the output. The regression course’s bootstrap interval is reported as “400 resamples, seed 20260729” for exactly this reason — an interval nobody can reproduce is not an interval.

Every dataset on this platform is generated by a seeded script, which is what makes pnpm datasets:generate a verification step: it rewrites all twenty CSVs and git status reports nothing changed.

Three: the order files come back in

import glob
for path in glob.glob("data/raw/*.csv"):     # order is filesystem-dependent
    ...

glob and os.listdir return files in an order that differs between operating systems and sometimes between runs. If anything downstream depends on order — a concatenation, a first-wins deduplication, a row index — the result differs.

for path in sorted(glob.glob("data/raw/*.csv")):
    ...
for (path in sort(list.files("data/raw", full.names = TRUE))) { }

Sort it. Always. It costs six characters.

The same applies to dictionary and group order. groupby in pandas sorts by default and dplyr::group_by does not; a chart whose bar order came from an unsorted group is a chart whose order can change without the data changing.

Four: the locale

float("1,234")           # ValueError, or 1.234, depending on where you are

The decimal separator, the thousands separator, the date format and the sort order of accented characters are all locale-dependent, and this platform’s audience works across both conventions by definition.

survey = pd.read_csv(path, decimal=".", thousands=None)     # stated, not inferred
dates = pd.to_datetime(survey["date"], format="%Y-%m-%d")   # explicit
readr::read_csv(path, locale = locale(decimal_mark = ".", date_format = "%Y-%m-%d"))

State the format rather than letting the reader infer it. pd.to_datetime without a format is a function that guesses, and it guesses differently on 03/04/2025 depending on what else is in the column.

Sorting is the subtler one. sorted() on French commune names orders Étroit after Zone under one locale and before Fond under another, so a chart’s category order becomes machine-dependent.

The test that catches all four

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

Run it twice and diff. Anything that differs is one of the four, and the diff names the file.

Run it twice on different machines and you additionally catch the locale and the file ordering, which a single machine cannot.

Put it in CI, which is a clean machine every time, and the check runs whether or not anyone remembers.

The deterministic-output habit

For anything a build produces, byte-identical output on unchanged input is the goal, and it is achievable more often than people assume.

Source of churn Fix
Timestamps in file metadata SOURCE_DATE_EPOCH, or strip them
A generation date in a footer Take it from the content
Compression with a timestamp A zip whose entry times are pinned
Floating-point summation order Sort before reducing where it matters
An embedded random ID Derive it from a hash of the content

This platform pins SOURCE_DATE_EPOCH for both pdfTeX and pandoc, because a .pptx is a zip whose entry times and document properties would otherwise churn every deck on every rebuild.

The payoff is that a diff means something. When rebuilding produces no change, a change in the diff is a real change — and reviewing becomes possible.

Report it whole

Determinism

  All randomness is seeded: seed 20260729, set in src/config.py and reported
  with every bootstrap interval in this report.

  Dates are taken from the data (the latest interview date) rather than from
  the clock. No output contains a generation timestamp.

  File iteration is sorted. Group order is stated explicitly rather than
  inherited from the grouping library's default.

  CSV reading states the decimal mark and the date format rather than
  inferring them.

  Verified: running the pipeline twice produces byte-identical outputs, and
  CI runs it on a clean machine on every push.

The last line is the claim and the four above it are how it was achieved. A report that asserts determinism without saying which of the four it handled has probably handled the seed and none of the others.

What comes next

A reproducible pipeline that produces one report is worth having. The next lesson makes it produce twelve, from one template, without a copy anywhere.

Teach this lesson

The lesson as a slide deck, with the prose kept in the speaker notes rather than on the slide. Generated from this page, so it cannot fall out of step with it.

Start the slideshowRead the slides

The PDF needs no software and projects from any machine. The PowerPoint file is there to be edited — add your organisation's branding, cut a section for a shorter session, or merge two lessons into a workshop.