Every hypothesis test earlier in this book ran on a tiny pinned sample (n=6) so you could trace the arithmetic by hand — honest about mechanism, but too small to be real evidence. This lesson flips that: a real dataset (120 batches across 3 manufacturing lines) run through lmda, a deterministic statistics engine, so the numbers are a genuine finding, not a demonstration. One-way ANOVA asks whether 3+ group means differ by more than sampling noise would explain; Tukey's HSD then asks which pairs of groups differ.

The real dataset

data/defect_rate.csv records the defect rate per batch for 3 production lines (A, B, C), 40 batches each, 200 units per batch — 120 rows total. This is the same dataset lmda's own case-study-1 uses (a realistic simulated manufacturing process, not a live production feed — see Honesty below). | line | batch | units | defects | defect_rate | |---|---|---|---|---| | A | 1 | 200 | 18 | 0.09 | | A | 2 | 200 | 5 | 0.025 | | A | 3 | 200 | 11 | 0.055 | | B | 1 | 200 | 3 | 0.015 | | C | 1 | 200 | 1 | 0.005 | Question: do the 3 lines have different average defect rates, or is the spread we see between A/B/C just what you'd expect from batch-to-batch noise alone?

Computing the ANOVA

We run lmda anova on the full 120-row file, grouping by line:

lmda anova data/defect_rate.csv --value defect_rate --group line --format json
{
  "degrees_of_freedom_between": 2,
  "degrees_of_freedom_within": 117,
  "dropped": 0,
  "f_statistic": 6.748812961965834,
  "group": "line",
  "group_count": 3,
  "groups": [
    {
      "group": "A",
      "mean": 0.046875,
      "n": 40
    },
    {
      "group": "B",
      "mean": 0.030125000000000002,
      "n": 40
    },
    {
      "group": "C",
      "mean": 0.027500000000000004,
      "n": 40
    }
  ],
  "n": 120,
  "p_value": 0.0016831397528628614,
  "test": "one_way_anova",
  "value": "defect_rate"
}

f_statistic compares between-group variance to within-group variance; p_value is the probability of seeing an F this large if the 3 lines actually had the same true defect rate. With p_value well under 0.05, this real sample rejects that null hypothesis: the 3 lines are not interchangeable.

Which lines differ? Tukey's HSD

ANOVA's F-test only tells you that the group means differ somewhere, not where. Tukey's Honestly Significant Difference test runs all 3 pairwise comparisons (A-B, A-C, B-C) while controlling the family-wise error rate, so you can read the per-pair reject flags directly instead of running 3 separate uncorrected t-tests:

lmda tukey data/defect_rate.csv --value defect_rate --group line --format json
{
  "alpha": 0.05,
  "degrees_of_freedom_within": 117,
  "dropped": 0,
  "group": "line",
  "group_count": 3,
  "mean_square_within": 0.000654775641025641,
  "n": 120,
  "pairs": [
    {
      "group_a": "A",
      "group_b": "B",
      "honestly_significant_difference": 0.013583001948920401,
      "mean_a": 0.046875,
      "mean_b": 0.030125000000000002,
      "mean_difference": 0.016749999999999998,
      "p_value": 0.01136898511973261,
      "q_statistic": 4.139982924210723,
      "reject": true,
      "standard_error": 0.00404591040751535
    },
    {
      "group_a": "A",
      "group_b": "C",
      "honestly_significant_difference": 0.013583001948920401,
      "mean_a": 0.046875,
      "mean_b": 0.027500000000000004,
      "mean_difference": 0.019374999999999996,
      "p_value": 0.002758307161620177,
      "q_statistic": 4.788786218303448,
      "reject": true,
      "standard_error": 0.00404591040751535
    },
    {
      "group_a": "B",
      "group_b": "C",
      "honestly_significant_difference": 0.013583001948920401,
      "mean_a": 0.030125000000000002,
      "mean_b": 0.027500000000000004,
      "mean_difference": 0.002624999999999999,
      "p_value": 0.8905886022118866,
      "q_statistic": 0.6488032940927251,
      "reject": false,
      "standard_error": 0.00404591040751535
    }
  ],
  "q_critical": 3.357217679286653,
  "test": "tukey_hsd",
  "value": "defect_rate"
}

Line A's mean defect rate is higher than both B and C (reject: true for both pairs), but B and C are not distinguishable from each other (reject: false) — the omnibus ANOVA result is being driven specifically by line A.

Honesty

The arithmetic here is exact and the sample is no longer tiny (n=120, 40 per group), so — unlike the n=6 pinned samples elsewhere in this book — this is large enough to be a real statistical finding for the population this sample represents, subject to ANOVA's usual assumptions (independent batches, approximately normal residuals within each line, similar within-group variance — lmda levene checks the last of these). Two things this lesson does not claim: (1) this dataset is a realistic simulated manufacturing process used as lmda's own worked example, not a live production feed, so "line A has a real defect problem" is a statement about this sample, not about an actual factory; (2) ANOVA + Tukey show an association between line and defect rate, not that line A's equipment or process causes the higher rate — a real investigation would need to rule out confounders (shift, operator, raw-material batch) before acting on this result.

Implementation notes

  • lmda is invoked as a real subprocess against the committed CSV; both commands above are re-run on every validate_source.py pass and must byte-for-byte match the checked-in outputs/*.json — if the CLI's output ever changes, this page fails validation instead of drifting silently.
  • Group means: A=0.046875, B=0.030125, C=0.0275 (40 batches each). The overall spread between lines is small in absolute terms (a few percentage points of defect rate) but consistent enough, over 120 batches, to clear statistical significance.
  • Tukey's HSD is a post-hoc test — it's only meaningful after the omnibus ANOVA F-test has already rejected the equal-means null; running it first (or instead of ANOVA) would inflate the false-positive rate.
  • Cross-reference: two-sample-t-stat (ch07) for the 2-group case ANOVA generalizes; standard-error (ch06) for why 40 batches per group narrows the sampling noise this test is measuring against.