DEV Community

Cover image for ⚽ Can We Predict the Next Premier League Champion with Binomial Probability?
Naomi Jepkorir
Naomi Jepkorir

Posted on

⚽ Can We Predict the Next Premier League Champion with Binomial Probability?

What are the chances your favorite EPL team wins the league next season? Time to let math do the talking! 🎲


🧠 Idea Behind the Madness

Every football fan has asked it:

"Can my team win the league next season?"

Instead of relying on blind hope, I decided to use binomial probability to calculate each team's chances of taking the crown in the next Premier League season, based entirely on how they performed last time.

We’ll:

  1. Fetch last season’s final standings from an API.
  2. Use binomial distribution to simulate two things:
    • The probability of a team repeating its exact win total.
    • The probability of a team reaching the typical championship threshold which is ≈27.6 so 28 wins.
  3. Rank them accordingly.

🛰️ Step 1: Fetching EPL Data Using an API

I used the football-data.org API to pull the standings. You’ll need a free API token, save it in a .env file like this:

API_TOKEN=your_football_data_token
Enter fullscreen mode Exit fullscreen mode

Now fetch the standings:

import requests
import os
from dotenv import load_dotenv

load_dotenv()

def fetch_epl_standings():
    token = os.getenv("API_TOKEN")
    if not token:
        raise ValueError("API_TOKEN not found in env")

    uri = "http://api.football-data.org/v4/competitions/PL/standings?season=2024"
    headers = { 'X-Auth-Token': token }

    response = requests.get(uri, headers=headers)

    if response.status_code != 200:
        raise Exception(f"API request failed with status code {response.status_code}: {response.text}")

    data = response.json()
    return data["standings"][0]["table"]

standings = fetch_epl_standings()
Enter fullscreen mode Exit fullscreen mode

Convert to DataFrame:

import pandas as pd

data_rows = []
for team in standings:
    data_rows.append({
        "Pos": team["position"],
        "Team": team["team"]["name"],
        "Matches": team["playedGames"],
        "Wins": team["won"],
        "Draws": team["draw"],
        "Losses": team["lost"],
        "Points": team["points"],
        "+/-": team["goalDifference"],
        "Goals": f'{team["goalsFor"]}:{team["goalsAgainst"]}'
    })

df = pd.DataFrame(data_rows)
df.to_csv('epl_standings.csv', index=False)
Enter fullscreen mode Exit fullscreen mode

🎯 Step 2: Binomial Probability of Exact Win Count

Now let's calculate the probability of each team repeating the exact number of wins they had last season.

import math

# Loop through each row and calculate binomial probability
for index, row in df.iterrows():
    team = row['Team']
    n = int(row['Matches'])  # total games
    k = int(row['Wins'])     # wins
    p = k / n                # estimated win probability

    try:
        binom_prob = math.comb(n, k) * (p**k) * ((1 - p)**(n - k))
    except OverflowError:
        binom_prob = 0.0

    print(f"{team}: P( {k} wins)  = {binom_prob:.6f}")
Enter fullscreen mode Exit fullscreen mode

Sample Output:

Liverpool FC: P( 25 wins)  = 0.135388
Arsenal FC: P( 20 wins)  = 0.128761
Ipswich Town FC: P( 4 wins)  = 0.206486
Southampton FC: P( 2 wins)  = 0.278054
Enter fullscreen mode Exit fullscreen mode

📉 What These Results Tell Us

  • Top teams like Liverpool have lower exact probabilities, there's more room for variation when you're near the top.

  • Lower-table teams tend to have higher repeat chances, but don't celebrate just yet...

🏆 Step 3: Probability of Title-Winning Season (≥ 28 Wins)

Next, we model the probability of each team reaching 28 or more wins, a common threshold to win the league.

We'll use the cumulative binomial distribution:

from scipy.stats import binom

def title_probability(wins, matches=38, threshold=28):
    p = wins / matches
    return 1 - binom.cdf(threshold - 1, matches, p)

for index, row in df.iterrows():
    team = row['Team']
    wins = int(row['Wins'])
    prob = title_probability(wins, threshold=28)
    print(f"{team}: P(Wins ≥ 28) = {prob:.6f}")
Enter fullscreen mode Exit fullscreen mode

Sample Output:

Team P(Wins ≥ 28)
Liverpool FC 19.78%
Manchester City FC 1.54%
Arsenal FC 0.66%
Chelsea FC 0.66%
Newcastle United 0.66%
Manchester United FC 0.00%

📊 Interpretation

Liverpool is most likely to hit 28+ wins based on current form.

City, Chelsea and the others trail behind, possibly due to more draws or inconsistent performances.

Man United? Their chance rounds to zero. Ouch 😬.

🫣 United fans, this model says your 11-win season gives you a statistically negligible shot at the title. You might want to pray harder than you code.

⚠️ Limitations

Let’s be honest, binomial probability isn’t a crystal ball. Here's why:

  1. It ignores real-world dynamics: transfers, injuries, managerial changes.
  2. It assumes independent, identically distributed matches (which football is not).
  3. Based on one season, not a large enough sample for deep insight.

But hey, it’s fun and statistically grounded!

🧪 Want to Take This Further?

Here’s how you can level up the model:

  • Use Poisson regression to simulate goals per match.

  • Integrate Elo ratings or other power metrics.

  • Run full Monte Carlo simulations of future fixtures.

  • Track the model live across the season for dynamic probabilities.

💭 Final Thoughts

While this model won’t help you win your fantasy league, it does give a math-driven glimpse into who’s statistically positioned to succeed. Liverpool fans? You have reason to dream. Southampton? Maybe next year...

Football is unpredictable, and that's what makes it beautiful. But every now and then, it's fun to let the math have a shot at calling the game. ⚽📊

Top comments (0)