Dixon and Coles#
The Dixon-Coles model extends the basic Poisson framework by accounting for dependencies between the home and away team scores in football matches.
Originally proposed by Dixon and Coles (1997), it incorporates adjustments for low-scoring matches and correlations between outcomes, providing more accurate predictions of match results, correct scores, and betting markets like goal totals and Asian handicaps.
Due to its enhanced predictive accuracy and flexibility, the Dixon-Coles model has become a widely-adopted approach in professional football analytics and betting strategies.
[1]:
import penaltyblog as pb
Get data from football-data.co.uk#
[2]:
fb = pb.scrapers.FootballData("ENG Premier League", "2019-2020")
df = fb.get_fixtures()
df.head()
[2]:
date | datetime | season | competition | div | time | team_home | team_away | fthg | ftag | ... | b365_cahh | b365_caha | pcahh | pcaha | max_cahh | max_caha | avg_cahh | avg_caha | goals_home | goals_away | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||||||||||
1565308800---liverpool---norwich | 2019-08-09 | 2019-08-09 20:00:00 | 2019-2020 | ENG Premier League | E0 | 20:00 | Liverpool | Norwich | 4 | 1 | ... | 1.91 | 1.99 | 1.94 | 1.98 | 1.99 | 2.07 | 1.90 | 1.99 | 4 | 1 |
1565395200---bournemouth---sheffield_united | 2019-08-10 | 2019-08-10 15:00:00 | 2019-2020 | ENG Premier League | E0 | 15:00 | Bournemouth | Sheffield United | 1 | 1 | ... | 1.95 | 1.95 | 1.98 | 1.95 | 2.00 | 1.96 | 1.96 | 1.92 | 1 | 1 |
1565395200---burnley---southampton | 2019-08-10 | 2019-08-10 15:00:00 | 2019-2020 | ENG Premier League | E0 | 15:00 | Burnley | Southampton | 3 | 0 | ... | 1.87 | 2.03 | 1.89 | 2.03 | 1.90 | 2.07 | 1.86 | 2.02 | 3 | 0 |
1565395200---crystal_palace---everton | 2019-08-10 | 2019-08-10 15:00:00 | 2019-2020 | ENG Premier League | E0 | 15:00 | Crystal Palace | Everton | 0 | 0 | ... | 1.82 | 2.08 | 1.97 | 1.96 | 2.03 | 2.08 | 1.96 | 1.93 | 0 | 0 |
1565395200---tottenham---aston_villa | 2019-08-10 | 2019-08-10 17:30:00 | 2019-2020 | ENG Premier League | E0 | 17:30 | Tottenham | Aston Villa | 3 | 1 | ... | 2.10 | 1.70 | 2.18 | 1.77 | 2.21 | 1.87 | 2.08 | 1.80 | 3 | 1 |
5 rows × 111 columns
Train the model#
[3]:
clf = pb.models.DixonColesGoalModel(
df["goals_home"], df["goals_away"], df["team_home"], df["team_away"]
)
clf.fit()
The model’s parameters#
[4]:
clf
[4]:
Module: Penaltyblog
Model: Dixon and Coles
Number of parameters: 42
Log Likelihood: -1057.16
AIC: 2198.319
Team Attack Defence
------------------------------------------------------------
Arsenal 1.134 -0.939
Aston Villa 0.843 -0.619
Bournemouth 0.812 -0.652
Brighton 0.781 -0.832
Burnley 0.874 -0.917
Chelsea 1.341 -0.81
Crystal Palace 0.54 -0.928
Everton 0.904 -0.798
Leicester 1.306 -1.077
Liverpool 1.539 -1.288
Man City 1.72 -1.213
Man United 1.283 -1.217
Newcastle 0.756 -0.773
Norwich 0.386 -0.524
Sheffield United 0.757 -1.171
Southampton 1.046 -0.729
Tottenham 1.216 -0.951
Watford 0.712 -0.664
West Ham 1.012 -0.693
Wolves 1.037 -1.12
------------------------------------------------------------
Home Advantage: 0.231
Rho: -0.079
[5]:
clf.get_params()
[5]:
{'attack_Arsenal': np.float64(1.1342332512814666),
'attack_Aston Villa': np.float64(0.8428614200812865),
'attack_Bournemouth': np.float64(0.8120871220574839),
'attack_Brighton': np.float64(0.7808917582956009),
'attack_Burnley': np.float64(0.8735858001950194),
'attack_Chelsea': np.float64(1.3406405229341805),
'attack_Crystal Palace': np.float64(0.54027401448345),
'attack_Everton': np.float64(0.90405407569214),
'attack_Leicester': np.float64(1.3058030084854737),
'attack_Liverpool': np.float64(1.5388197328408173),
'attack_Man City': np.float64(1.719736412293199),
'attack_Man United': np.float64(1.2833664185175406),
'attack_Newcastle': np.float64(0.7560522647598563),
'attack_Norwich': np.float64(0.38627656860167087),
'attack_Sheffield United': np.float64(0.7571750870728832),
'attack_Southampton': np.float64(1.0463066006069428),
'attack_Tottenham': np.float64(1.2164199869178027),
'attack_Watford': np.float64(0.7121950398114016),
'attack_West Ham': np.float64(1.011821528056154),
'attack_Wolves': np.float64(1.0373993870113039),
'defence_Arsenal': np.float64(-0.9388607870877208),
'defence_Aston Villa': np.float64(-0.6189739728624671),
'defence_Bournemouth': np.float64(-0.6519243441185367),
'defence_Brighton': np.float64(-0.831967850468479),
'defence_Burnley': np.float64(-0.9169450014938234),
'defence_Chelsea': np.float64(-0.8096059010087857),
'defence_Crystal Palace': np.float64(-0.9276415128403426),
'defence_Everton': np.float64(-0.7978054456239708),
'defence_Leicester': np.float64(-1.0774446379382292),
'defence_Liverpool': np.float64(-1.2883064861537878),
'defence_Man City': np.float64(-1.2127587839326763),
'defence_Man United': np.float64(-1.2167171606719684),
'defence_Newcastle': np.float64(-0.7733099150404236),
'defence_Norwich': np.float64(-0.5236341128015811),
'defence_Sheffield United': np.float64(-1.171235209200169),
'defence_Southampton': np.float64(-0.7286072073062362),
'defence_Tottenham': np.float64(-0.9506668078158086),
'defence_Watford': np.float64(-0.6643983432945821),
'defence_West Ham': np.float64(-0.6928216817677401),
'defence_Wolves': np.float64(-1.1199754294549622),
'home_advantage': np.float64(0.23111657421057807),
'rho': np.float64(-0.07857474584693216)}
Predict Match Outcomes#
[6]:
probs = clf.predict("Liverpool", "Wolves")
probs
[6]:
Module: Penaltyblog
Class: FootballProbabilityGrid
Home Goal Expectation: [1.91546589]
Away Goal Expectation: [0.77809465]
Home Win: 0.6343621791727205
Draw: 0.22869163320053712
Away Win: 0.13694618543564444
1x2 Probabilities#
[7]:
probs.home_draw_away
[7]:
[np.float64(0.6343621791727205),
np.float64(0.22869163320053712),
np.float64(0.13694618543564444)]
[8]:
probs.home_win
[8]:
np.float64(0.6343621791727205)
[9]:
probs.draw
[9]:
np.float64(0.22869163320053712)
[10]:
probs.away_win
[10]:
np.float64(0.13694618543564444)
Probablity of Total Goals >1.5#
[11]:
probs.total_goals("over", 1.5)
[11]:
np.float64(0.7580899659472173)
Probability of Asian Handicap 1.5#
[12]:
probs.asian_handicap("home", 1.5)
[12]:
np.float64(0.3889763491819654)
Probability of both teams scoring#
[13]:
probs.both_teams_to_score
[13]:
np.float64(0.4690074122745808)
[ ]: