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