FairDraw

The draw-position test

One question, answerable from a site's own results page: when a competition is drawn, does the winning ticket land anywhere among the tickets sold with equal chance?

What you need

For each completed competition: the number of tickets sold, the maximum number of tickets, and the winning ticket number (or numbers). Most UK competition sites publish all three on a results or completed-competitions page. Record them as you find them, with the date you looked.

The test

  1. For each winning ticket, work out its position: the winning number divided by the number of tickets sold. A position of 0.02 means the ticket sat 2% of the way into the tickets sold.
  2. If the draw is a fair pick over the tickets sold, positions are spread evenly between 0 and 1. About 2% of winners fall below 0.02, about 10% below 0.10, and the median sits near 0.5.
  3. Count how many winning tickets fall below 0.02 and below 0.10, and compare with what a fair draw would produce. The chance of seeing at least the observed count by luck is a binomial tail probability (code below).
  4. Group competitions by size (for example under 15,000 tickets sold, 15,000 to 50,000, over 50,000). A defect that only considers an early, fixed-size portion of entries shows up as a bias that grows with competition size, while small competitions look normal.

Checks before you conclude anything

What a result does and does not show

A failed test shows that the published winning tickets are not consistent with a random pick over the tickets sold. It does not show why. A draw procedure that reads only the first page of entries from a database, a spreadsheet sorted the wrong way, a numbering scheme the results page doesn't explain, or a choice by someone: all leave the same mark. Only the operator can say which, so every FairDraw audit asks them before it is published and prints their answer.

Reproduce it

Save the results as a CSV with the columns competition, tickets_sold, max_tickets, winning_ticket and run:

import csv
from math import comb

rows = list(csv.DictReader(open("draws.csv")))
def tail(n, k, p):  # chance of at least k hits in n tries at probability p
    return sum(comb(n, i) * p**i * (1 - p)**(n - i) for i in range(k, n + 1))

for label, lo, hi in [("under 15k", 0, 15000), ("15k to 50k", 15000, 50000), ("50k+", 50000, 10**12)]:
    pos = sorted(int(r["winning_ticket"]) / int(r["tickets_sold"])
                 for r in rows if lo <= int(r["tickets_sold"]) < hi)
    if not pos:
        continue
    n = len(pos); low2 = sum(p < 0.02 for p in pos); low10 = sum(p < 0.10 for p in pos)
    print(f"{label}: {n} winners, {low2} in first 2% (expected {0.02*n:.1f}, chance {tail(n, low2, 0.02):.1e}), "
          f"{low10} in first 10% (expected {0.10*n:.1f}, chance {tail(n, low10, 0.10):.1e}), median position {pos[n//2]:.3f}")

The CSV for each audit is linked from its page, so the figures can be checked line by line.

Reading the probability

Winning the UK Lotto jackpot is about 1 in 45 million (107.7). Choosing one specific atom from the observable universe is about 1 in 1080. Results in the 10-10 range are not bad luck; results in the 10-50 range are not luck at all.