Bivariate Weibull Count Plus Copula Model#
The Bivariate Weibull Count Plus Copula Model is a sophisticated statistical framework designed for predicting football match outcomes by jointly modeling the number of goals scored by both teams.
It extends traditional Poisson-based approaches by incorporating Weibull-distributed goal counts, which allow for greater flexibility in capturing goal-scoring patterns, particularly when goal distributions exhibit overdispersion.
Additionally, the copula component explicitly models the dependence between home and away team goal counts, improving prediction accuracy for correct scores, match outcomes, and betting markets like total goals and Asian handicaps.
This advanced model is particularly valuable for football analytics when capturing nuanced relationships between teams’ performances is crucial for better forecasting.
[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.WeibullCopulaGoalsModel(
df["goals_home"], df["goals_away"], df["team_home"], df["team_away"]
)
clf.fit()
The model’s parameters#
[4]:
clf
[4]:
Module: Penaltyblog
Model: Bivariate Weibull Count + Copula
Number of parameters: 43
Log Likelihood: -1049.502
AIC: 2185.004
Team Attack Defence
------------------------------------------------------------
Arsenal 1.163 -0.828
Aston Villa 0.81 -0.466
Bournemouth 0.783 -0.504
Brighton 0.741 -0.705
Burnley 0.825 -0.786
Chelsea 1.344 -0.674
Crystal Palace 0.475 -0.791
Everton 0.853 -0.649
Leicester 1.317 -0.994
Liverpool 1.559 -1.179
Man City 1.709 -1.132
Man United 1.292 -1.156
Newcastle 0.715 -0.608
Norwich 0.338 -0.355
Sheffield United 0.716 -1.067
Southampton 1.024 -0.583
Tottenham 1.245 -0.835
Watford 0.7 -0.665
West Ham 1.017 -0.566
Wolves 1.034 -1.029
------------------------------------------------------------
Home Advantage: 0.237
Weibull Shape: 1.191
Kappa: -1.124
[5]:
clf.get_params()
[5]:
{'attack_Arsenal': np.float64(1.16259374410796),
'attack_Aston Villa': np.float64(0.8095022336740563),
'attack_Bournemouth': np.float64(0.7828356246312403),
'attack_Brighton': np.float64(0.7411382844943274),
'attack_Burnley': np.float64(0.8245955288804583),
'attack_Chelsea': np.float64(1.3435746341571566),
'attack_Crystal Palace': np.float64(0.47529592595775816),
'attack_Everton': np.float64(0.8527277199218508),
'attack_Leicester': np.float64(1.3171425299831485),
'attack_Liverpool': np.float64(1.5585738201391832),
'attack_Man City': np.float64(1.7093313894060342),
'attack_Man United': np.float64(1.2918401696521093),
'attack_Newcastle': np.float64(0.7147581606014619),
'attack_Norwich': np.float64(0.33772678732379247),
'attack_Sheffield United': np.float64(0.7159069282535363),
'attack_Southampton': np.float64(1.023930037749901),
'attack_Tottenham': np.float64(1.2449775431354324),
'attack_Watford': np.float64(0.699657704066482),
'attack_West Ham': np.float64(1.0172106325524315),
'attack_Wolves': np.float64(1.033576507630148),
'defense_Arsenal': np.float64(-0.8281738204952019),
'defense_Aston Villa': np.float64(-0.46550368389038743),
'defense_Bournemouth': np.float64(-0.5042771094115648),
'defense_Brighton': np.float64(-0.7054618304504835),
'defense_Burnley': np.float64(-0.7855322875689625),
'defense_Chelsea': np.float64(-0.6738309989097833),
'defense_Crystal Palace': np.float64(-0.7906810455804004),
'defense_Everton': np.float64(-0.6491088057506019),
'defense_Leicester': np.float64(-0.9943647106590421),
'defense_Liverpool': np.float64(-1.1793125500974093),
'defense_Man City': np.float64(-1.1320386579970085),
'defense_Man United': np.float64(-1.1559852251791218),
'defense_Newcastle': np.float64(-0.6081339959788542),
'defense_Norwich': np.float64(-0.3553524059816674),
'defense_Sheffield United': np.float64(-1.0673273897843734),
'defense_Southampton': np.float64(-0.5825445597267889),
'defense_Tottenham': np.float64(-0.8354532263823686),
'defense_Watford': np.float64(-0.6653714381676077),
'defense_West Ham': np.float64(-0.5660042508403601),
'defense_Wolves': np.float64(-1.0294936203432046),
'home_advantage': np.float64(0.23662029387143488),
'shape': np.float64(1.1914362553954994),
'kappa': np.float64(-1.1239649112332324)}
Predict Match Outcomes#
[6]:
probs = clf.predict("Liverpool", "Wolves")
probs
[6]:
Module: Penaltyblog
Class: FootballProbabilityGrid
Home Goal Expectation: [2.15050026]
Away Goal Expectation: [0.86438583]
Home Win: 0.6321733110211908
Draw: 0.2082763227463461
Away Win: 0.15955036614422274
1x2 Probabilities#
[7]:
probs.home_draw_away
[7]:
[np.float64(0.6321733110211908),
np.float64(0.2082763227463461),
np.float64(0.15955036614422274)]
[8]:
probs.home_win
[8]:
np.float64(0.6321733110211908)
[9]:
probs.draw
[9]:
np.float64(0.2082763227463461)
[10]:
probs.away_win
[10]:
np.float64(0.15955036614422274)
Probablity of Total Goals >1.5#
[11]:
probs.total_goals("over", 1.5)
[11]:
np.float64(0.8056653102057293)
Probability of Asian Handicap 1.5#
[12]:
probs.asian_handicap("home", 1.5)
[12]:
np.float64(0.38394326568912934)
Probability of both teams scoring#
[13]:
probs.both_teams_to_score
[13]:
np.float64(0.4978154468351224)
Train the model with more recent data weighted to be more important#
[14]:
weights = pb.models.dixon_coles_weights(df["date"], 0.001)
clf = pb.models.WeibullCopulaGoalsModel(
df["goals_home"], df["goals_away"], df["team_home"], df["team_away"], weights
)
clf.fit()
[15]:
clf
[15]:
Module: Penaltyblog
Model: Bivariate Weibull Count + Copula
Number of parameters: 43
Log Likelihood: -873.221
AIC: 1832.443
Team Attack Defence
------------------------------------------------------------
Arsenal 1.136 -0.816
Aston Villa 0.746 -0.455
Bournemouth 0.757 -0.47
Brighton 0.692 -0.669
Burnley 0.781 -0.772
Chelsea 1.313 -0.639
Crystal Palace 0.43 -0.739
Everton 0.809 -0.628
Leicester 1.252 -0.932
Liverpool 1.516 -1.117
Man City 1.674 -1.142
Man United 1.283 -1.146
Newcastle 0.704 -0.56
Norwich 0.229 -0.32
Sheffield United 0.676 -1.016
Southampton 1.01 -0.577
Tottenham 1.207 -0.822
Watford 0.689 -0.634
West Ham 0.998 -0.546
Wolves 0.985 -1.022
------------------------------------------------------------
Home Advantage: 0.244
Weibull Shape: 1.189
Kappa: -1.151
[ ]: