Methodology
This page documents every formula, source, and editorial choice on the site. Read it before you cite us. If you find an error, mail [email protected] and a correction will be published with date, diff, and impact.
Editorial frame
Every page on this site is structured to make the voting method the subject of the observation, not the individual elected on it. We name candidates because the public election record names them; we make no claim about any individual candidate's conduct, fitness for office, or character. The factual claim — "won X% of valid ballots" — is verifiable from the source data linked on /data.
We deliberately avoid the words "majority" and "minority" as system labels. They imply that First-Past-the-Post and bloc vote have a numerical threshold a winner must clear — which they do not. A candidate wins under these methods by being top of the poll, full stop, regardless of share. Instead we report each elected candidate's share against the proportional quota — the share that would be needed to be guaranteed that seat under a voting method that allocates seats in proportion to votes — and call out the gap.
Where seats are allocated proportionally to votes, results like the ones on this site do not occur: the proportional quota is the floor that any common proportional method requires. The contrast between First-Past-the-Post / bloc vote and a proportional alternative is the rhetorical hook of the project, and the editorial frame throughout.
What counts as "proportional"? The quota threshold applies to any method whose overall seat allocation tracks the vote distribution — STV, list PR (D'Hondt, Sainte-Laguë, and similar), proportional approval, and the regional-list portion of mixed-member systems like AMS. Mixed-member systems are partly First-Past-the-Post in their constituency component, but their compensatory list seats restore overall proportionality, so the quota framing is still the right benchmark for the system as a whole. We deliberately don't single out one preferred method elsewhere on the site — the editorial argument is against the distortion, not for one particular replacement.
Picking the right replacement is exactly what a National Commission on Electoral Representation would be for. The data on this site documents the case for one.
Winning percentage
For a single-member ward, winning_pct = winner_votes / valid_ballots.
For a multi-member ward (electing N councillors at once under bloc vote),
each elected candidate has their own candidate_votes / valid_ballots. We headline the most-marginal of the elected candidates — the seat-holder
with the thinnest mandate in that ward — because that figure shows how
little support the weakest seat-winner actually attracted. Per-candidate
shares are also visible in the candidate table on every council page.
valid_ballots = ballots_cast − invalid_votes. This matches
the House of Commons Library "Valid vote turnout (HoC method)" definition
in the source workbook.
A note on multi-member wards
In a multi-member ward (electing N councillors at once under bloc vote),
each voter may cast up to N votes — so a candidate's candidate_votes / valid_ballots ratio understates the
share of voters who supported them, sometimes by a lot. We report the
ratio anyway, with the same denominator as single-member wards,
because (a) it is the figure used by the source workbook and
consistently comparable across ward types, and (b) any other choice
would require speculating about how voters distributed their second
and third votes. Readers interpreting multi-member percentages should
treat them as a lower bound on candidate support, not an upper one.
The proportional quota framing (above) is calibrated to this same
denominator, so the comparison stays apples-to-apples.
Proportional quota and "under par"
The proportional quota is the share of valid
ballots a candidate would need to be guaranteed a seat under any
common proportional voting method: quota = 1 seats + 1. For a 1-seat
ward this collapses to 50% (a true majority); for 2 seats it is
33.3%; for 3 seats, 25%. (Technical name for the curious: the Droop quota, after H. R. Droop's 1869 paper on
proportional election methods. The figure is used, in the same or
near-identical form, by every preferential and party-list system
we are aware of.)
For each race we compute under_par = quota − winning_pct.
A positive value means the marginal elected candidate won less of
the valid ballots than the quota — the seat would not have
been guaranteed under a proportional count. We surface this as "X.X points below quota" and treat it as the editorial
indictment of the voting method, not the candidate. Above-quota
results are mathematically clean and pass without comment.
This is a deliberate departure from the older "minority winner" framing (winning_pct < 50%). The 50% threshold is not a rule of First-Past-the-Post or bloc vote — calling someone a "minority winner" implies they fell short of a standard the system never required of them. The quota framing is the standard a proportional system does require, and the gap is the loss attributable to the method.
"If votes were counted by party"
On every council page we show how the seats would be distributed if the party vote totals were allocated proportionally rather than via First-Past-the-Post. The arithmetic uses the D'Hondt method — the most widely-used party-list proportional algorithm worldwide (used by the European Parliament, the Scottish Parliament regional list, the Welsh Senedd regional list, and the London Assembly). It allocates seats one at a time to the party with the highest quotient party votes (seats already won) + 1, repeating until all seats are filled.
Caveat — bloc vote inflation. In a multi-member ward (electing N councillors at once under bloc vote), each voter may cast up to N votes — so parties that ran a full slate get up to N× the votes of parties that ran a single candidate. Aggregating "candidate votes by party" therefore over-counts parties with full slates and under-counts parties with partial ones. Treat the proportional column as a directional proxy for what a list-PR system would deliver, not a strict counterfactual. A preferential proportional method that counts voter rankings would avoid this distortion, but the LEH data does not record voter rankings so we cannot reproduce that allocation here.
Cycle-over-cycle comparison
Each council overview page (/[council]) compares the
council across every cycle in our data. Three views:
- Council composition. A grid of one square per seat, summed across the cycles within two years of the latest cycle. That window captures a full term for by-thirds councils (3 consecutive years) and the latest cycle for all-out councils (whose prior cycle is 4 years earlier and represents a separate set of councillors). The result is approximate and labelled as such — boundary reviews mid-term, partial-cycle elections, and councils on irregular schedules can shift the count by a few seats.
- Party-control flips. For each consecutive cycle pair (cycle N, cycle N+1) where the largest party (by seats won this cycle) changed, we record the flip and compute the incoming party's vote-share shift and seat-share shift between the two cycles, both as percentage points. Flips are ranked by the formula seat shift (pp) max(vote shift (pp), 1 pp) — a big seat shift on a small vote shift scores high. The vote-shift denominator is floored at 1 percentage point so a flip on near-zero vote movement (e.g. Wealden 2021→2023, 0.6 pp) doesn't divide by zero. The rich per-flip visualisation shows a bar comparison of votes vs seats for both cycles.
- Ward-by-ward grid. Rows are wards (matched by name across cycles); columns are cycles, oldest on the left, latest on the right. Each cell shows the top-of-poll candidate's party and their share of valid ballots. Empty cells mean the ward did not poll that cycle. Caveat: ward names are matched as strings — boundary reviews can mean a ward of the same name is a slightly different geographical area in a later cycle.
Council reorganisations
Eleven councils in our window were created or abolished by the 2019–2023 wave of UK local-government reorganisation. Where that applies, the council overview page carries an explicit warning banner explaining what happened and when, and listing the predecessor or successor councils. The list is hand-curated from the Wikipedia record of the LGR wave, cross-checked against the Commons Library briefing CBP-9056. The next wave (Surrey 2026, Essex 2027/28) will be added when those cycles enter the dataset.
System anomalies
Two anomaly lenses are surfaced as their own pages:
- /below-quota · every elected seat anywhere in the data where the marginal winner's share fell below the proportional quota for that ward. Sortable, filterable by year, party, and council.
- /flips · every council that flipped plurality party between consecutive cycles, ranked by seat-shift ÷ vote-shift.
Other classical FPTP anomalies — smallest absolute winning vote count, party second in vote share winning the most seats per council, vote-share-to-seat-share inversions, multi-member wards electing a third-placed party's candidate, high-turnout wards with low-mandate winners — are computable from the SQLite database but not yet surfaced as their own lenses on the site.
Sources
All cycles below are ingested from the Local Election Handbook (LEH) for the relevant year,
published by the House of Commons Library under the Open Parliament
Licence. Workbooks live in the repo at docs/LEH-{2021..2025}-results-HoC*.xlsx;
the per-year ETL adapter normalises sheet-name and column-name drift
across years.
- 1 May 2025 · 115 councils, 1400 races, 1640 seats elected.
- 2 May 2024 · 107 councils, 1903 races, 2659 seats elected.
- 4 May 2023 · 230 councils, 4797 races, 8031 seats elected.
- 5 May 2022 · 168 councils, 3533 races, 5549 seats elected.
- 6 May 2021 · 227 councils, 3863 races, 4729 seats elected.
For the 2026-05-07 cycle, the eventual Commons Library Local Election Handbook for that year will be the canonical source.
Reproducibility
Every number on the published site is derivable from the SQLite database on /data using standard SQL. The schema documentation on that page lists every table and column. The site build is deterministic: the same git commit and the same source data produce the same published bytes.
What is not here yet
- An in-page SQL console (datasette-lite) — deferred.
- DuckDB-WASM widgets for live-querying tables in the browser — deferred.
- The full system-anomalies lens beyond /below-quota and /flips — smallest absolute winning vote count, vote-share-to-seat-share inversions, third-place-by-party-share-wins-seat, and high-turnout wards with low-mandate winners are all computable but don't yet have their own pages.
- An exact council-composition figure that matches each council's live members list — see the "Council composition" caveat above.
- Per-candidate pages and biographies — explicitly excluded by editorial design (race-as-noun discipline).
Errata
No errata recorded yet. When a methodological correction is required, an entry will be published here with a date, a one-paragraph explanation, the diff (what number changed, by how much), and the affected pages.