← the method, in plain English  ·  the index

The annex · for people who read appendices · figures from the 15 October 2026 build (calc 1.3.0)

The nerd annex

The method page says what every number means. This page says exactly how each one is computed: the model specifications, the fitted coefficients, the shrinkage constants, the censoring rules and the validation that chose each model over its rivals. Nothing here changes a score — it is the arithmetic the scores already use, written down. Every formula is pinned by the automated test suite, so this page cannot quietly drift from the code.

A dating convention, stated once: the loyalty slope, the jump terms, the shrinkage constants and every validation statistic below are quoted from the build named above. The pipeline refits these weekly as results and attendances arrive, so the third decimal place moves; the specifications and the editorial constants do not.

01Percentiles and ties

Every component score is a percentile: the share of the comparison pool a club is worse than, with ties split down the middle and the club itself counted among the ties.

P(v) = 100 · ( #{x < v} + ½ · #{x = v} ) / n

where n is the size of the pool (not n−1). Consequences worth knowing: the range is open — a unique worst value in a pool of n scores 100·(n−½)/n, never 100; a one-club pool scores 50; and because several inputs are rounded before ranking (matches-per-manager to 1 dp, damped loyalty to 1 dp, away pull to 2 dp), ties are more common than raw floats would give. The pool is the current 92 for most components, restricted where a component is unavailable (§03–§08); “higher = worse” is arranged by negating the good-direction statistics (loyalty, away pull, matches-per-manager) before ranking.

02Composites and renormalisation

The published weights, and the exact rule when a component is unavailable:

sins w = (impatience 1, indiscipline 1, lawlessness 1, waste 1, arsehole 0.5) Σw = 4.5 fans v = (stay 7, travel 2, behave 1) Σv = 10 ShitClub = Σ wⁱpⁱ / Σ wⁱ over AVAILABLE sins (pⁱ = component percentiles) ShitFans = Σ vₖqₖ / Σ vₖ over AVAILABLE fan parts Combined = (ShitClub + ShitFans) / 2 each rounded to 1 dp

A missing component is dropped from both numerator and denominator — its weight is redistributed pro rata across what remains, which is all “renormalisation” means. In practice only two components can be missing: waste (insufficient wage disclosure, 27 clubs) and stay (no observed transition yet, 2 clubs); the rest always have a value, so the denominators never empty. Effective shares when everything is present: 22.2% per structural sin and 11.1% for the arsehole factor within the club score; 70/20/10 within the fans score; the two sides split the combined score evenly. The site’s interactive recalculations run the same weighted mean, ported to JavaScript and held to the Python build by a parity test suite (compensated summation, same round-half-to-even behaviour), always from the unrounded component percentiles — never the rounded headlines.

03Waste: the expectation regression

The unit of observation is a disclosed club-season: total staff costs from the club’s statutory accounts (financial years 2023–25, each mapped to the league season whose May falls inside it), paired with league points per match that season, deductions not applied. Seasons outside the top four divisions never enter. The expectation model is a single pooled OLS with a division-season intercept and one slope on log relative wages:

ppmₖ = αdiv(c,s),s + b · ln( wagesₖ / mediandiv-season(wages) ) + εₖ b = +0.657 points per match per unit of log relative wages ⇒ doubling your division's typical outlay ≈ 0.657 · ln 2 · 46 ≈ +21 points over 46 games wasteₖ = fittedₖ − actualₖ (positive = underperforming the money) waste(club) = mean over its disclosed seasons; requires ≥ 2 scored seasons

Fitted on n = 194 club-seasons from 67 clubs across 12 division-seasons (the intercepts are 12 dummies; the median is taken over disclosing clubs only, which runs thin in League Two — four to six clubs per season, said plainly on the method page). 65 clubs clear the two-season bar; 27 show waste as unavailable and renormalise.

The spec was chosen by out-of-sample bake-off against four rivals — leave-one-season-out (LOSO: hold out a whole season, carry the division’s mean training intercept) and leave-one-club-out (LOCO: hold out all of a club’s seasons), RMSE in points per match:

modelin-sample R²LOSO RMSELOCO RMSE
division-season mean (straw man)0.0310.4010.414
pooled slope on log relative wages (adopted)0.5600.2900.282
division-specific slopes0.5710.2990.287
within-division wage rank0.4360.3150.323
raw log wages, no intercepts0.56011.430.282

Division-specific slopes fit marginally better in sample and transfer worse out of sample — four slopes from 15–71 observations each is how overfitting starts — so the pooled slope stays. Raw log wages without intercepts collapses under LOSO (11.4) because wage levels don’t transfer across seasons; relative wages do.

04Loyalty: the transition model

One observation per club per summer: dp = percentage change in season-average home gate, dr = places climbed on the 92-place pyramid (division offset 0/20/44/68 plus final position; points → goal difference → goals → name; deductions not applied), plus promotion and relegation indicators. Every variable — including the indicators — is demeaned within its transition-year cohort, so league-wide tides, the partial current season and era effects cancel by construction. The fit is OLS through the origin (both sides demeaned, so no intercept) over the 355 usable transitions:

dp̃ = β·dr̃ + γₚ·promoted̃ + γ₠·relegated̃ + res β (same-division gate response) = +0.282 pp of gate per place γₚ (promotion surge, one-off) = +7.9 pp γ₠ (relegation slump, one-off) = −1.8 pp raw loyalty(club) = mean of its residuals; n(club) = its transition count

The promotion surge is strongly determined (39 promotions in the fit); the relegation slump is small relative to its sampling noise (40 relegations) and is retained for symmetry rather than as a firm estimate. No clamping or winsorising anywhere — the only exclusions are five stadium/capacity events, listed exactly (and shipped in the data payload as loyalty_exclusions): Everton 24/25→25/26 (new stadium), Wrexham 24/25→25/26 and 25/26→26/27 (rebuild), Manchester City 25/26→26/27 (expansion), Aston Villa 25/26→26/27 (capacity cut to 36,887).

Model selection, leave-one-cohort-out (four folds of whole transition-years) and leave-one-club-out, RMSE in pp of demeaned gate change, recomputed on the current inputs:

modelparamsLOCO-cohortLOCO-club
pooled slope only18.398.30
division-specific slopes48.358.27
pooled slope + promotion/relegation jumps (adopted)38.308.10
rise/fall asymmetric slopes28.398.32
capacity-aware response18.468.35

The jump model wins both folds, and on the 79 promoted/relegated transitions specifically cuts in-sample RMSE from 11.1 to 10.3 — the surge really is a separate crowd event, not a steeper slope. Without the jump terms the slope inflates to ≈0.44 because promotions drag it upward; that inflation was being credited to promoted clubs’ fans as loyalty before calc 1.2.0.

05The shrinkage

A club’s raw loyalty rests on one to four residuals. Tested directly (leave-one-transition-out: predict each held-out residual from the club’s other transitions), the raw club mean predicts worse than assuming zero — RMSE 9.45 raw against 8.13 for zero, with the shrunk estimate between at 8.36, and adjacent-summer residuals correlate at only +0.05. So the published score is an empirical-Bayes compromise, shrunk toward zero (the demeaned national norm), with the variance components estimated from the data by method of moments — not hand-tuned:

σ² = mean over clubs with n ≥ 2 of their sample residual variance = 60.5 τ² = Var(club raw means) − σ² · mean(1/n) = 10.6 w(n) = τ² / ( τ² + σ²/n ) published loyalty = w · raw w(1) = 0.15 w(2) = 0.26 w(3) = 0.34 w(4) = 0.41

So a one-transition club keeps about a seventh of its raw deviation and a four-transition club about two-fifths — the method page’s words, with the arithmetic attached. Worked extremes from the current build: Barnet −28.0 raw on one transition damps to −4.2; Bromley +19.8 on two damps to +5.1 — which is why a huge thin-evidence score can legitimately out-rank a modest well-evidenced one in the published XIs (both inputs are printed beside every list). The shrunk score is then percentiled (§01) into the stay component; one observed transition is enough to be scored, because the damping now does the caution a hard two-transition minimum used to.

06Away support: the two-way fixed-effects model

No turnstile counts exist, so the away score is declared an estimate and modelled. Each of ~8,500 match crowds (after the calc-1.3.0 row-by-row verification: 34 crowds corrected to sourced figures, 2 excluded as unverifiable, 12 suspect rows outside 0.4–2.5× the host-season median dropped, and zero crowds above their dated per-season capacity) is decomposed as:

attendanceₘ = Hhost(m),season(m) + Vvisitor(m) + β · ln(1 + milesₘ) + eₘ H: 460 host-season effects (absorb ground size, form, pricing, season) V: 99 visitor effects, recentred so the match-weighted mean is 0 β ≈ −400 attendees per log-mile on the censored-excluded fit (R² ≈ 0.993) away pull(club) = 100 · V(club) / own average home gate (the published %)

The log distance form beat linear miles on fit; the headline visitor effects come from the censored-excluded fit, which drops the matches where the away end physically couldn’t respond: sell-outs (≥98% of the host’s dated per-season capacity, ~1,000 matches) and allocation-bound matches (crowd lift ≥80% of the estimated away allocation — 3,000 in the Premier League, 10% of host capacity in the EFL; ~1,100 matches). A club whose away matches are allocation-bound ≥25% of the time is flagged ≥ on its page: its true pull is at least the published number (right-censoring), which is why Premier League away scores are floors. The percentage-of-own-gate form and the distance adjustment are the two editorial fairness choices, stated as such on the method page; the visitor effect necessarily bundles travelling support with box-office pull.

Validation against the only independent ground truth found — fan-compiled 2023/24 away-attendance tables covering all 72 EFL clubs — Spearman rank correlation:

cohortρ, published statistic (% of own gate)ρ, visitor effect in bodies
all 72 EFL clubs0.9410.924
Championship (24)0.8450.865
League One (24)0.9320.919
League Two (24)0.9370.911

The Championship is the weakest cohort on the published scale — 0.85, not the “roughly 0.9” the plain-English page rounds to — and the Premier League publishes nothing to check against at all. The model is fitted once over the whole window by hand, not refit weekly, and is held constant in the season history (§09).

07Sanction arithmetic: the arsehole factor and the deductions pillar

Both pillars share one shape: clean is 0, offenders start at 50 and climb by rank among offenders. The 50 floor is an editorial statement (punished is punished), not an estimate; everything above it is the percentile formula of §01 applied within the offender pool:

score = 0 if clean score = 50 + Poffenders(raw) / 2 otherwise (one offender alone ⇒ 75) arsehole raw = Σ over rows [ ban_matches / (2 if wholly suspended else 1) + fine_£ / 10,000 ] person rows follow the MANAGER (scored to his current employer, wherever earned); club rows stay with the club forever. Fines always count in full. deductions raw = Σ points actually deducted (status = imposed, post-appeal) guilty with sanction pending, nothing imposed ⇒ exactly the 50 floor

The £10,000-per-ban-match conversion and the half-weight for wholly-suspended bans are the register’s two editorial constants — which is precisely why the whole factor carries half the weight of a structural sin (11.1% of the club score). Every row traces to a governing-body announcement; part-suspended bans count only their immediate part.

08Crowd behaviour: verdicts and arrests

Two halves, averaged, each ≈5% of the fans score. The FA half scores verdicts (sanctioned or admitted only — charges pending and not-proven rows are ignored) as a lexicographic pair: discriminatory cases count double in the verdict count, and fines break ties in the count rather than being priced in — the FA fines by club resources, so pounds would just rank turnover. Clean crowds are 0; sanctioned crowds get the 50-floor shape of §07. The arrests half is a shrunk rate over the last three published Home Office seasons (they lag about a year):

A = Σ domestic arrests (total police actions minus European-competition rows) G = Σ season average home gate / 10,000 ("gate-units" of exposure) rate* = ( A + 2 · pooled rate ) / ( G + 2 ) pooled rate ≈ 10.3 per 10k gate-season

— i.e. every club’s raw rate is blended with 20,000 gate-seasons’ worth of the league-wide rate, so a small ground’s handful of arrests can’t swing its score. The shrunk rate is percentiled over the 92; a club with no published arrest season yet is held at a neutral 50 on this half, presumed neither clean nor guilty. An arrest is a police statistic, not a conviction, and policing intensity varies by fixture — both said on the method page, both the reason this half carries the smallest weight on the site.

09The history, twice

Recomputed track. Each club’s season-by-season scores re-run the whole index as of each past season-end under today’s rules: matches, cards, deductions, sanctions and completed transitions known by then; the waste regression refitted on the accounts covering the seasons played by then (accounts are matched to the season they cover — the filing lag is deliberately ignored, because the question is “how wasteful were they being?”); percentiles retaken against the clubs in the league at that point. Two components behave differently by construction: the away model is fitted once over the whole window and held constant, and the first history point has no scoreable loyalty at all (one cohort of transitions leaves the shrinkage variance components inestimable, so stay is unavailable and the fans score renormalises). A build-time assertion requires the final history point to equal the published headline exactly.

As-published track. Separately, every build appends the day’s published scores to an append-only ledger (one row per club per UTC date; same-day reruns replace that day’s rows; earlier dates are never rewritten). Where the two tracks disagree, the gap is the method changing, not the club — which is the point of keeping both. Each build also ships its full audit block: calculation version, weights, exclusion lists, match count, and a content hash of every input file.

10The constants, in one place

constantvalue
Window / history points5 seasons (2022/23–2026/27) / 4 as-of points
Sin weights (imp/disc/ded/waste/ars)1 / 1 / 1 / 1 / 0.5
Fan weights (stay/travel/behave)7 / 2 / 1
Club/fans balance50 / 50
Impatience minimum evidence46 league matches in window, else neutral 50
Red card3 yellows
Offender floor (deductions, arsehole, FA half)50 + offender-percentile/2
Fine conversion / suspended bans£10,000 = 1 ban-match; wholly suspended = ×½ (bans only)
Discriminatory crowd verdictscount double; fines break ties
Arrest shrinkage prior2 gate-units (20,000 gate-seasons) at the pooled rate
Arrest seasons scoredlatest three published (2023/24–2025/26)
Wage seasons scoredFY2023–25 → seasons 2022/23–2024/25; ≥2 disclosed seasons required
Waste slope+0.657 ppm per unit log relative wages (≈21 points per doubling)
Loyalty fit (this build)β +0.282 pp/place; jumps +7.9 / −1.8 pp; 355 transitions; 5 exclusions
Shrinkage (this build)τ² 10.6, σ² 60.5; w = 0.15 / 0.26 / 0.34 / 0.41 for n = 1–4
Loyalty minimum evidence1 observed transition (damping does the caution)
Away allocationsPL 3,000; EFL 10% of host capacity; bound if lift ≥ 80% of it
Away sell-out / suspect crowd≥98% of dated capacity / outside 0.4–2.5× host-season median
Away floor flag (≥)allocation-bound in ≥25% of away matches
Away validationρ 0.941 vs fan-compiled 2023/24 EFL tables (72 clubs)

Reproducibility: the pipeline is a deterministic build from versioned input files (every one content-hashed into the payload), the regression and shrinkage code is plain least squares with no tuning parameters beyond the constants above, and the test suite pins the percentile formula, the renormalisation rule, the weights, the exclusion lists and a full fixture of expected scores. If this page and the code ever disagree, a test fails before the site builds.

← back to the plain-English method  ·  the index