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)
[ ]: