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 felix.sargent@gmail.com — I'll publish a correction with date, diff, and impact.

Editorial frame

Every page on this site makes the voting method the subject — 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 /councils/data.

We deliberately don't call winners "majority" or "minority" winners. Those terms imply First-Past-the-Post and bloc vote have a numerical threshold a winner must clear — they don't. 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 they'd need to clinch the seat under a system that allocates seats in proportion to votes — and call out the gap.

Under proportional systems, results like the ones on this site simply don't 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 editorial frame throughout the site.

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 on this site — the editorial argument is against the distortion, not for one particular replacement. For comparisons between proportional methods, see the sister site proportional.uk.

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 every elected candidate we report candidate_votes / valid_ballots, where valid_ballots is the number of voters who cast a valid ballot — not the sum of all candidate votes. Reporting on a per-voter basis keeps the percentage comparable across single-member and multi-member wards (under bloc vote a voter casts up to N votes for an N-seat ward, so summing candidate votes over-counts voters by ~N×).

For a multi-member ward we headline the elected candidate with the smallest share of voters — the seat-holder at the margin in that ward — because that figure shows how little voter support a seat actually needed.

A note on the bloc-vote denominator

valid_ballots is sourced one of two ways:

The approximation has one knowable bias: voters who plump (cast fewer than N votes) make the real ballot count higher than votes / N, so the marginal winner's voter share is slightly lower than the figure we publish. Treat any multi-member percentage from a non-LEH-2025 cycle as an upper bound; the cleanest figures on the site are the 2025 LEH cycle where Ballots − Invalid votes is the actual count.

Why the candidate table shows two share columns. For multi-seat wards (2+ seats) the per-candidate row carries both share of votescandidate_votes / total_votes_cast, the figure the council publishes — and share of voters (est.)candidate_votes / valid_ballots, equivalent to the raw share multiplied by the seat count when we are approximating. The first reconciles with the official source; the second is the figure that is comparable across single- and multi-seat wards and the one the proportional-quota indictment is measured against. Both are correct; they answer different questions, and people coming from the council's results page should see both side by side.

Proportional quota and "below quota"

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 the signed gap under_par = winning_pct − quota. A negative 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. Positive (above-quota) results are mathematically clean and pass without comment. The value is a signed gap: below-quota results read as a negative number, matching the Drift from quota column on per-council pages.

"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 as a proxy for proportionality — a measurement tool, not a recommendation. D'Hondt is one well-defined way to allocate seats to vote shares, which makes it useful as a consistent benchmark; the column shows how distorted the actual FPTP allocation is against any proportional standard, not against D'Hondt specifically. 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.

This site doesn't take a stand on which voting method is best. Picking the right replacement for First-Past-the-Post is exactly what a National Commission on Electoral Representation would be for. For more on the trade-offs between proportional methods (STV, list PR, mixed-member, approval, and others), see the sister site proportional.uk.

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 any list-PR system would deliver, not an exact prediction. Methods that count voter rankings (preferential / ranked) don't have this aggregation issue at all because they read each voter's full preference order rather than summing candidate totals; the LEH data doesn't record rankings, so we can't reproduce that allocation here.

Gallagher disproportionality index

Above the “If votes were counted by party” table on every council page we show a single number called the Gallagher index (sometimes LSq, for least-squares). It is the academic standard summary of how proportionally an election translated votes into seats — one number, computed across every party that stood, that you can compare from council to council and cycle to cycle.

The formula, from Michael Gallagher (1991):

Gallagher = √( ½ · Σ (Vi − Si)2 )

where Vi and Si are party i’s vote share and seat share, both expressed as percentages (0–100). Squaring the differences before summing means a single very large gap (e.g. one party getting 60% of the seats on 35% of the vote) contributes much more than several small ones — the index weights big distortions heavily, which is the editorial point. Halving the sum and taking the square root puts the result back in the same percentage-point units as the inputs, so the number is directly readable.

How to read the number

Higher means more disproportional. Gallagher's own rough bands, widely used in the literature:

Worked example

Take a council where the cycle delivered: Conservative 35.1% votes → 59.3% seats; Labour 30.2% → 40.7%; Green 17.7% → 0%; Reform 10.8% → 0%; Lib Dem 6.2% → 0%. Squaring each (V−S) gap and halving the sum gives ≈583, whose square root is ~24.1. That is Gallagher saying: across all parties, this election was severely disproportional — not just because the Conservative bonus was 24 points, but because the Green, Reform and Lib Dem shutouts compound on top.

Gallagher vs. the per-party Δ table

The two views are complementary. The per-party Δ column tells you who the system over- and under-represented in this specific cycle — which party got an unearned bonus and which votes counted for nothing. Gallagher tells you how bad the overall mismatch was, in a single number you can rank councils by or watch over time. Use Δ to name the affected parties; use Gallagher to compare elections.

A note on what Gallagher is not: it is purely descriptive. It scores a particular vote-to-seat translation; it does not prescribe what the seats should have been (we use D'Hondt on the same page as a separate proxy for that). High Gallagher just means “these vote shares and these seat shares don't match well.”

A caveat carries over from the “If votes were counted by party” table: in multi-member bloc-vote wards each voter casts multiple votes, so candidate totals over-count parties that ran a full slate and under-count those that didn't. The vote shares feeding Gallagher inherit that bias; treat the absolute number as directional for councils with many multi-member wards.

Cycle-over-cycle comparison

Each council overview page (/[council]) compares the council across every cycle in our data. Three views:

FPTP per-election distortion

Distinct from council-control changes (the rare event where a council's largest party actually flipped) is the per-election FPTP distortion story — the systemic phenomenon where, in any single election, the seats First-Past-the-Post allocates don't match how a proportional system would have allocated them. Even in a stable council where no party flips, every cycle's election distorts the vote-to-seat relationship; that's the whole point of having an electoral-reform audit.

For every (council, year) cycle we already compute an If votes were counted by party view (the per-council page's "If votes were counted by party" table) which gives the FPTP allocation alongside the D'Hondt proportional allocation. We rank cycles by the seats reallocated measure: sum(|fptp_seats − dhondt_seats|) / 2, summed over parties in that cycle. The division by two is because every seat FPTP gives a party that proportional wouldn't is matched by a seat FPTP withholds from a party proportional would — so the raw sum double-counts. A perfectly proportional cycle has all-zero deltas; a cycle where FPTP gave one party 2 extra seats has total reallocation = 2.

The /councils/distortion lens ranks cycles by reallocated share (reallocated seats ÷ total seats elected that cycle), so a small council with 4 of 12 seats reallocated (33%) ranks above a big council with 4 of 60 (7%). The metric is per-cycle and apples-to-apples — it describes a single election in isolation, with no by-thirds caveat needed. Caveat: a boundary-review all-out cycle puts every seat up at once, which can mechanically produce big reallocation counts if the new ward map favours one party; the fact of reallocation is real distortion, but the magnitude is partly a function of "more seats were up to begin with."

Council reorganisations

Eleven councils in our window were created or abolished by the 2019–2023 wave of UK local-government reorganisation (LGR). 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

Three anomaly lenses are surfaced as their own pages:

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

Election results (2021–2025) — ingested from the Local Election Handbook (LEH) for the relevant year, published by the House of Commons Library under the Open Parliament Licence. The per-year ETL adapter normalises sheet-name and column-name drift across years.

Scottish STV (2022). Council elections in Scotland use Single Transferable Vote. Per-candidate first preferences, round-by-round transfers, per-ward electorate / valid poll / quota are sourced from indylive radio's aggregated CSV (their redistribution of council eCount exports + Democracy Club candidate metadata) under CC BY-SA 4.0. The raw CSV is mirrored at /data/stv/scotland-2022.csv so sister projects (notably stv.vote) can pull the round-by-round data directly. The “unfairly awarded” measure (party first-pref share vs party seat share) uses the same arithmetic as the FPTP map; the contrast on the homepage is the same metric, different counting rule. Wales's 2022 elections were FPTP — the Local Government and Elections (Wales) Act 2021 enables councils to opt in to STV from 2027; no Welsh council has yet adopted it.

Election results (2026, preliminary) — sourced from Democracy Club's candidates dataset, reused here under CC BY 4.0 (party logos and candidate photos, which Democracy Club excludes from that licence, are not used on this site — we only consume the structured results data). Polling-night data is updated as wards report; until a ward's full count is in we drop the race entirely (rather than surface partial vote totals that would assign “elected” to the wrong candidates). The 2026 LEH replaces this feed as the canonical source when it ships.

Council composition snapshots (2016–2025) — annual per-council per-party seat counts (used for the running-composition block on each council page and the council-control flip definition above) come from opencouncildata, a hand-maintained register of UK council membership going back to 1973, published under CC BY-SA 4.0. Snapshot file (source-data/council/history2016-2025.csv) is committed in the repo; refresh by re-downloading from oncd. Slug aliases for the small number of councils that LEH and oncd name slightly differently (Bristol, County Durham, King's Lynn and West Norfolk, Kingston upon Hull) are explicit in scripts/etl.mjs; the ETL prints any unmatched councils on each run.

Council composition snapshots (2026, synthesised) — opencouncildata publishes its annual snapshot months after polling day, so the 2026 row doesn't yet exist. To run the flip detector against the just-finished cycle we synthesise a 2026 snapshot per council from the 2025 oncd per-councillor roster plus the 2026 election results: retain every 2025 incumbent whose Next Election field is not 2026, then add the 2026 election winners for the seats that were up. Synthesised snapshots are flagged (synthesised: true) in the data and surfaced as such in the UI; they are replaced by the real oncd row on the next ETL run after oncd publishes 2026.

Reproducibility

Every number on the published site is derivable from the SQLite database on /councils/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

Errata

2026-05-13 — county-council elections mis-attributed in 2021 and 2025

The LEH workbooks for county-election years (2021, 2025) record the upper tier (e.g. Lancashire) as the elected body and the lower tier (e.g. Rossendale) as the geographic district that holds each division. Our ETL was filing every race under the lower tier, so Reform UK's 2025 Lancashire County Council sweep showed up as phantom races on Rossendale, Burnley, Pendle, Preston, and West Lancashire borough pages, while /councils/lancashire appeared to have no data after 2017. The fix re-attributes shire-county rows (2021 Type=SC, 2025 Local authority type=CC) to the actual county council. Affected: every district that overlaps a two-tier county lost its phantom 2021/2025 cycle; 21 county councils gained their 2021 cycle (Norfolk, Lancashire, Surrey, Hertfordshire, Hampshire, Kent, Essex, Lincolnshire, West Sussex, Suffolk, Derbyshire, Oxfordshire, Staffordshire, Cambridgeshire, Devon, Warwickshire, Nottinghamshire, Gloucestershire, Leicestershire, Worcestershire, East Sussex), 14 of those gained their 2025 cycle (the others had their 2025 elections postponed for local-government reorganisation). 18 new county-council flips surface in the data — most notably the 2021→2025 Conservative-collapse rows (Reform UK or Liberal Democrats taking 13 of the 14 county councils that polled). Total flip count 218 → 220.

2026-05-13 — opencouncildata "stale tail" rows hidden

opencouncildata publishes the year-N composition row in early year N+1; until it lands they leave a placeholder identical to the previous year. The composition history on each council page was surfacing those placeholders as if they were real snapshots — most visibly on Rossendale, whose 2025 row was a byte-identical copy of its 2024 row because the borough didn't poll in 2025 and oncd hadn't refreshed yet. The ETL now flags such tail rows staleCopy: true and the data layer hides them from public timelines and from the headline "council composition as of {year}" callout. The rows remain in the underlying file so they can still serve as a 2025 baseline for the 2026 synthesis step. 109 rows are currently flagged.