← Lab Blog

The fairness audit — do some numbers travel together?

fairnesspairstriplesco-occurrencefair nullbiastzokereurojackpot

The fairness audit: do some numbers travel together?

Responsible play note: hobby stats and code. Lotteries are luck-first, and nothing here changes that.

Why bother — the one bias we hadn't ruled out

Across the earlier posts we showed the draw has no memory: numbers aren't "due," the overlap columns are coin-flips, the diagonals go nowhere. But all of that tested the process across time. There's a different way a real machine can be crooked — not over time, but in space: maybe two balls sit together, maybe a chamber under-mixes, and certain pairs or triples of numbers ride along together more often than chance. Singles can look perfectly uniform while a subtle pairing bias hides underneath. So before we ever call this lottery "fair," we owe it one last audit — the co-occurrence audit.

The question, stated exactly

For a fair 5-of-N draw, a specific pair {a,b} lands together with probability

P(pair) = C(N-2, 3) / C(N, 5)

— 0.0101 for Joker (5/45), 0.0067 for Euro (5/50). Over n draws each pair is therefore a Binomial(n, P(pair)): expected 33.6 co-occurrences per pair in Joker, 8.0 in Euro. The question is whether the observed pair counts scatter the way independent binomials should, or whether some pairs are pushed high (travel together) or low (avoid each other). Same logic one level up for triples.

Method (and the traps we avoid)

  1. Every pair, as a z-score. For all C(N,2) pairs (990 in Joker, 1,225 in Euro) we take z = (observed − expected) / sd. Under a fair machine these z's should look like a standard normal: centred on 0, spread 1, ~4.6% beyond ±2, ~0.3% beyond ±3.
  2. The multiple-testing trap. With ~1,000 pairs, someone will look extreme by luck — you can't just point at the biggest one. So we judge the max |z| against a fair-null: simulate hundreds of fully random draw-histories of the same size and record how big the most-extreme pair gets by chance. Only a real bias beats that.
  3. Triples get the same treatment, compared to their Poisson expectation.
  4. A folklore bonus: consecutive numbers (i, i+1) — do they really "never come up"?

What we found

Singles — the baseline. Joker counts run 338–403 (expected 370), Euro 78–115 (expected 98); uniformity χ² p = 0.96 / 0.95. Fair.

Pairs — the heart of it:

possible pairs exp / pair z mean z sd |z|>2 |z|>3 max |z|
Joker 990 33.6 −0.00 0.97 4.3% 0.0% 2.84
Euro 1,225 8.0 −0.00 0.95 4.2% 0.2% 3.54
fair machine (theory) 0.00 1.00 4.6% 0.3% —

Put every pair's z-score on a Q–Q plot against a standard normal and they land right on the fair-machine diagonal — Joker smooth, Euro in discrete steps (its counts are small integers) but hugging the line all the way out to the tails:

Then the decisive test — the single most-extreme pair against the fair-null. Its max |z| of 2.84 (Joker) / 3.54 (Euro) is smaller than what random draw-histories throw up (null max ≈ 3.5 / 3.8; fair-null p = 0.997 / 0.84). In plain terms: the real lottery's "most suspicious pair" is less suspicious than a computer's random draws produce.

Triples — expected 2.34 per triple (Joker) / 0.50 (Euro); the busiest triple appeared 10 / 6 times, exactly the maximum you'd expect from that many Poisson counts. Nothing stands out.

The consecutive-numbers myth — observed vs expected co-occurrence of (i, i+1): 1,450 vs 1,479 (Joker, ratio 0.98) and 380 vs 393 (Euro, ratio 0.97). Consecutive numbers come up at the chance rate — the belief that "they never draw 23-24 together" is simply false; it happens as often as the maths says it should.

Verdict

The machine is honest in space as well as in time. Numbers don't cluster, pairs don't conspire, triples are Poisson, consecutives behave. Combined with the earlier time-domain results, we can now say it cleanly: this is a fair, memoryless, unbiased draw. That's not a defeat — it's the licence for everything else the lab does. Because the process is provably fair, the honest levers left are structure (the shape of a combination) and construction (coverage guarantees) — never prediction. This audit is what lets us say that with a straight face.

➡️ Reproduce every number and chart: fairness_audit.py (needs each game's hist_df.csv with st1..st5).