cassionData Analysis

Back to the lessonLesson 4 of 8The same answer twice

Four things that change the answer when nothing changed

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.

Slides · PDFSlides · PowerPoint

  1. Slide 1 / 27

    What this lesson covers

    • One: the clock
    • Two: the seed
    • Three: the order files come back in
    • Four: the locale
    • The test that catches all four
    • The deterministic-output habit
    • Report it whole
    • What comes next
    Speaker notes
    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.
  2. Slide 2 / 27

    One: the clock — In Python

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

    One: the clock

    • Anything that reads the current time makes every run differ — A generated-on footer, a filename with today's date, a…
    • The fix is to take the date from the data or from a parameter
    Speaker notes
    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.
  4. Slide 4 / 27

    One: the clock — In Python

    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
  5. Slide 5 / 27

    One: the clock — In R

    as_of <- max(as.Date(survey$interview_date))
  6. Slide 6 / 27

    One: the clock

    • This platform got this wrong and the failure is instructive — The course handouts and lesson decks originally took…
    • 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…
    Speaker notes
    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.
  7. Slide 7 / 27

    Two: the seed — In Python

    import numpy as np
    
    rng = np.random.default_rng(20260729)      # stated, committed, reproducible
    sample = rng.choice(households, size=200, replace=False)
  8. Slide 8 / 27

    Two: the seed — In R

    set.seed(20260729)
    sample(households, 200)
  9. Slide 9 / 27

    Two: the seed

    • Anything random needs a seed and the seed belongs in the code — Bootstrap intervals, random sampling for verification,…
    • 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…
    • Every dataset on this platform is generated by a seeded script — which is what makes pnpm datasets:generate a…
    Speaker notes
    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.
  10. Slide 10 / 27

    Three: the order files come back in — In Python

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

    Three: the order files come back in

    • glob and os.listdir return files in an order that differs between operating systems and sometimes between runs — If…
    Speaker notes
    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.
  12. Slide 12 / 27

    Three: the order files come back in — In Python

    for path in sorted(glob.glob("data/raw/*.csv")):
        ...
  13. Slide 13 / 27

    Three: the order files come back in — In R

    for (path in sort(list.files("data/raw", full.names = TRUE))) { }
  14. Slide 14 / 27

    Three: the order files come back in

    • 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…
    Speaker notes
    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.
  15. Slide 15 / 27

    Four: the locale — In Python

    float("1,234")           # ValueError, or 1.234, depending on where you are
  16. Slide 16 / 27

    Four: the locale

    • The decimal separator, the thousands separator, the date format and the sort order of accented characters are all…
    Speaker notes
    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.
  17. Slide 17 / 27

    Four: the locale — In Python

    survey = pd.read_csv(path, decimal=".", thousands=None)     # stated, not inferred
    dates = pd.to_datetime(survey["date"], format="%Y-%m-%d")   # explicit
  18. Slide 18 / 27

    Four: the locale — In R

    readr::read_csv(path, locale = locale(decimal_mark = ".", date_format = "%Y-%m-%d"))
  19. Slide 19 / 27

    Four: the locale

    • State the format rather than letting the reader infer it — pd.to_datetime without a format is a function that…
    • Sorting is the subtler one — sorted() on French commune names orders Étroit after Zone under one locale and…
    Speaker notes
    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.
  20. Slide 20 / 27

    The test that catches all four — Shell

    python run.py && cp -r outputs outputs-first
    python run.py && diff -r outputs outputs-first
  21. Slide 21 / 27

    The test that catches all four

    • 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…
    • Put it in CI — which is a clean machine every time, and the check runs whether or not anyone remembers
    Speaker notes
    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.
  22. Slide 22 / 27

    The deterministic-output habit

    Source of churnFix
    Timestamps in file metadataSOURCE_DATE_EPOCH, or strip them
    A generation date in a footerTake it from the content
    Compression with a timestampA zip whose entry times are pinned
    Floating-point summation orderSort before reducing where it matters
    An embedded random IDDerive it from a hash of the content
    Speaker notes
    For anything a build produces, byte-identical output on unchanged input is the goal, and it is achievable more often than people assume.
  23. Slide 23 / 27

    The deterministic-output habit

    • This platform pins SOURCE_DATE_EPOCH for both pdfTeX and pandoc — because a .pptx is a zip whose entry times and…
    • The payoff is that a diff means something — When rebuilding produces no change, a change in the diff is a real change —…
    Speaker notes
    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.
  24. Slide 24 / 27

    Report it whole — Example

    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.
  25. Slide 25 / 27

    Report it whole

    • The last line is the claim and the four above it are how it was achieved — A report that asserts determinism without…
    Speaker notes
    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.
  26. Slide 26 / 27

    What comes next

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

    Where this goes next

    Read the full lesson, with runnable code Back to the lesson