Premier League: team strengths, a title race and a fight with the bookmaker
Difficulty
★★★☆☆
Time
3-4 hours
Data
football-data.co.uk: every Premier League match of 2024/25 and 2025/26, with Bet365 odds
Skills
Poisson regression with crossed group effects · diagnosing non-identifiability · pm.ZeroSumNormal · hierarchical shrinkage · posterior predictive checks on quantities the model never saw · simulating a season from the posterior predictive · proper scoring rules against a market benchmark
You have joined the analytics desk of a sports media company. The editor wants two things:
Team strength estimates - attack and defence ratings with honest uncertainty, not a
league table with extra steps.
Probabilistic forecasts - "Arsenal have an X% chance of the title, West Ham a Y%
chance of going down" - that the desk can publish mid-season without embarrassing itself.
The head of the desk is a sceptic: "Bookmakers do this for a living. Show me your numbers
next to theirs on matches your model has not seen, then we talk."
You will build the model on the complete 2024/25 season, then deploy it on 2025/26 as it
stood on 13 February 2026 - 26 rounds played, 12 to go - and forecast the rest. We know
how the season ended, so we can mark your homework.
One row per match. The columns you need: HomeTeam, AwayTeam, full-time home and away
goals FTHG and FTAG, the full-time result FTR (H, D or A), and Bet365's
pre-match decimal odds B365H, B365D, B365A (a winning 1-unit bet at odds 2.5 pays
back 2.5). There are many more columns (shots, cards, other bookmakers); ignore them or
use them in "Going further".
epl_2425: All 380 match results with goals, shots and bookmaker odds.
source: football-data.co.uk, English Premier League 2024/25.
url: https://www.football-data.co.uk/mmz4281/2425/E0.csv
Date
HomeTeam
AwayTeam
FTHG
FTAG
FTR
B365H
B365D
B365A
0
2024-08-16
Man United
Fulham
1
0
H
1.60
4.20
5.25
1
2024-08-17
Ipswich
Liverpool
0
2
A
8.50
5.50
1.33
2
2024-08-17
Arsenal
Wolves
2
0
H
1.18
7.50
13.00
3
2024-08-17
Everton
Brighton
0
3
A
2.63
3.30
2.63
4
2024-08-17
Newcastle
Southampton
1
0
H
1.36
5.25
8.00
A helper for the bookkeeping, so you can spend your time on the modelling. Ties on points
are broken by goal difference, then goals scored.
defleague_table(df):"""League table (points, goals for/against, goal difference) from a frame of matches."""home=pd.DataFrame({"team":df.HomeTeam,"gf":df.FTHG,"ga":df.FTAG})away=pd.DataFrame({"team":df.AwayTeam,"gf":df.FTAG,"ga":df.FTHG})long=pd.concat([home,away])long["played"]=1long["pts"]=np.select([long.gf>long.ga,long.gf==long.ga],[3,1],0)table=long.groupby("team")[["played","pts","gf","ga"]].sum()table["gd"]=table.gf-table.gatable=table.sort_values(["pts","gd","gf"],ascending=False)table.insert(0,"pos",range(1,len(table)+1))returntableleague_table(epl25)
How large is home advantage - in goals and in results?
Are goals plausibly Poisson? Compare the observed distribution of home goals and of
away goals with a Poisson of the same mean, and compare means with variances.
One sentence: is a Poisson likelihood a defensible starting point?
with $\mu$ an intercept and $\eta$ the home advantage. A colleague suggests
"uninformative priors, let the data speak": independent Normal(0, 3) on $\mu$, on $\eta$
and on every $\text{att}$ and $\text{def}$.
Deliver
Fit exactly this model to 2024/25 with default sampler settings, using coords/dims
for teams.
Read the diagnostics. Something is wrong. Explain what, in terms of the model - not
in terms of the sampler - and produce one plot or table that demonstrates your explanation.
Which quantities does this posterior pin down well in spite of the problem, and which
not at all? Give one example of each, with numbers.
A reformulated model in which every parameter is identified by construction (not by
luck of the prior), and in which the spread of attacking and of defensive ability
across the league is learned from the data rather than fixed by you.
Priors you can defend, backed by a prior predictive check on a scale a football fan
would recognise.
Clean diagnostics, and a comparison with Task 1: what happened to the effective sample size?
A plot of attack and defence ratings with uncertainty. Then compare your attack ratings
with the naive estimate "log of goals scored relative to the league average". Which way
does the model move teams, and why?
Your turn Work this task in the notebook.
#Task 3 · Check the model against things it was not fitted to
The model was fitted to home goals and away goals, separately. Football is about
results. A model can match both goal distributions and still get the things people
care about wrong.
Deliver posterior predictive checks, each with a verdict, for
the share of draws in a season,
the frequency of the most common scorelines (0-0, 1-1, 1-0, ...),
one further statistic of your choice that the likelihood does not target directly.
Does anything fail badly enough to matter for forecasting results?
Now the live season. It is 13 February 2026: every team has played 26 of 38 matches.
played is what you may fit to; remaining holds the 120 fixtures still to come. (It
also holds their results and odds. Those are for marking - do not let the model see them.)
epl_2526: All 380 match results with goals, shots and bookmaker odds.
source: football-data.co.uk, English Premier League 2025/26.
url: https://www.football-data.co.uk/mmz4281/2526/E0.csv
260 matches played, 120 remaining
pos
played
pts
gf
ga
gd
team
Arsenal
1
26
57
50
18
32
Man City
2
26
53
54
24
30
Aston Villa
3
26
50
37
27
10
Man United
4
26
45
47
37
10
Chelsea
5
26
44
47
30
17
Liverpool
6
26
42
41
35
6
Brentford
7
26
40
40
35
5
Everton
8
26
37
29
30
-1
Bournemouth
9
26
37
43
45
-2
Newcastle
10
26
36
37
37
0
Sunderland
11
26
36
27
30
-3
Fulham
12
26
34
35
40
-5
Crystal Palace
13
26
32
28
32
-4
Brighton
14
26
31
34
34
0
Leeds
15
26
30
36
45
-9
Tottenham
16
26
29
36
37
-1
Nott'm Forest
17
26
27
25
38
-13
West Ham
18
26
24
32
49
-17
Burnley
19
26
18
28
51
-23
Wolves
20
26
9
16
48
-32
Deliver
Your Task 2 model fitted to played, with clean diagnostics.
Simulations of the 120 remaining fixtures from the posterior predictive (parameter
uncertainty and match-day randomness), turned into complete final league tables -
one table per posterior draw, with the usual tie-breakers.
For every team: expected final points with a 94% interval, P(title), P(top 4),
P(relegation) (bottom three).
The reckoning: compare with league_table(epl26). Were the favourites right? How many
of the 20 final points totals fall inside your 94% intervals, and how many should?
The sceptic's test. For each of the 120 held-out matches, the model and the bookmaker each
give three probabilities (home win, draw, away win), and then one of the three happened.
Two proper scoring rules, both "lower is better", averaged over matches, where $p_{ik}$ is
the probability given to outcome $k$ in match $i$ and $o_{ik}$ is 1 if it happened, else 0:
Model probabilities for H/D/A for every held-out match, from the Task 4 posterior.
Bookmaker probabilities from the Bet365 odds. Raw inverse odds do not sum to one; how
big is the bookmaker's margin (the "overround"), and what do you do about it?
A reference forecaster that knows nothing about teams.
A table of both scores for all three forecasters - and a statement about whether 120
matches are enough to separate them. Quantify it.
The head of desk reads your table and asks the natural follow-up: "Fine, the market is
better on average. But when your model disagrees with it - is there money in that? And
what does the market know that your model does not?"
Deliver
A back-test on the held-out matches: stake 1 unit on every outcome where your model
sees positive expected value at the Bet365 odds (the real odds, margin included). Number of
bets, profit, return on investment - with an uncertainty interval on the ROI.
A diagnosis of how the model's probabilities differ from the market's. Is the model
more or less opinionated? If you mix the two forecasts, w * model + (1 - w) * market,
which w scores best on the held-out matches, and what does that tell you?
A recommendation in three sentences. What is the model good for, what is it not good
for, and what would you add first?
Last season as a prior. Fit both seasons jointly, with a 2025/26 rating centred on
a fraction of the 2024/25 rating and a separate prior for promoted teams. Does it score
better on the held-out matches? (A quick attempt of ours scored 1.056 against 1.052 for
the single-season model at round 26 - no better - and its sampler diagnostics needed work.
Does it help at round 8, when the current season says little?)
Dependent scores. Task 3 found home and away goals more negatively correlated than
the model allows, and the run-in had more draws than either forecaster expected. Add the
low-score correction of Dixon & Coles (1997) with pm.Potential or pm.CustomDist, or a
match-level random effect shared by both sides with opposite signs. Does the log score improve?
Time-varying strength. Chelsea and Burnley broke the model in Task 4. Let
ratings follow a pm.GaussianRandomWalk over match rounds. Is there evidence that form
is real, or is it noise?
Better data. Shots on target (HST, AST) are about three times as plentiful as
goals. Model them jointly with goals and see whether ratings sharpen faster early in the
season.
h.progress()
C02: 0/20 hints used, 14/14 checks passed.
Reference solution
A complete, executed solution with commentary on every task — including the ways the obvious model fails. Try the hint ladder first.