Bradley–Terry Model¶
Overview¶
The Bradley–Terry model is a probability model for predicting the outcome of pairwise comparisons between items, teams, or objects. Given two items i and j with positive strength parameters p_i and p_j, it estimates the probability that i beats j as:
$$P(i > j) = \frac{p_i}{p_i + p_j}$$
The model was presented by Ralph A. Bradley and Milton E. Terry in 1952. It is foundational to the elo-rating-system — ELO is a specific scaled case of Bradley–Terry with a scale of 400 and logistic function. The model can be used to predict outcomes given strength parameters, or — more commonly — to infer strength parameters from observed outcomes via maximum likelihood estimation.
Why It Matters¶
Bradley–Terry provides the theoretical justification for ELO-style rating systems and is used directly in sports betting models when:
1. Head-to-head sports: Tennis, chess, and other two-player games where only pairwise comparisons matter
2. Team strength estimation: Bradley–Terry can estimate team strength from win/loss records when match scores (margins) aren't available
3. Covariate-adjusted modeling: Adding covariates (home advantage, player availability) to Bradley–Terry produces richer models
For football, Poisson-based models are more common because they model goal counts, but Bradley–Terry provides a useful alternative when only win/loss data is available.
Key Formula¶
Core Bradley–Terry probability:
$$P(i > j) = \frac{p_i}{p_i + p_j}$$
Logit form:
$$\log\frac{P(i > j)}{P(j > i)} = \log(p_i) - \log(p_j) = \alpha_i - \alpha_j$$
Log-likelihood:
$$\ell(p) = \sum_{i,j} w_{ij} [\log(p_i) - \log(p_i + p_j)]$$
Where w_ij = number of times i beat j.
Zermelo iteration (parameter update):
$$p_i^{(new)} = \frac{\sum_j w_{ij}}{\sum_j \frac{w_{ij} + w_{ji}}{p_i + p_j}}$$
Worked Example¶
Three teams with observed results:
- Team A beat B: 3 times, lost to B: 2 times
- Team A beat C: 1 time, lost to C: 0 times
- Team B beat C: 5 times, lost to C: 0 times
Estimating via MLE gives strength parameters approximately:
- Team A: p_A ≈ 1.2
- Team B: p_B ≈ 1.0
- Team C: p_C ≈ 0.4
Predicted probabilities:
- P(A > B) = 1.2 / (1.2 + 1.0) = 0.545
- P(A > C) = 1.2 / (1.2 + 0.4) = 0.75
- P(B > C) = 1.0 / (1.0 + 0.4) = 0.714
Code Snippet¶
import numpy as np
from scipy.optimize import minimize
def bradley_terry_mle(outcomes):
"""
outcomes: dict of {(team_i, team_j): wins_ij}
Returns: dict of {team: strength parameter}
"""
teams = sorted(set([i for (i, j) in outcomes.keys()] + [j for (i, j) in outcomes.keys()]))
n = len(teams)
team_idx = {t: i for i, t in enumerate(teams)}
def neg_log_likelihood(log_p):
p = np.exp(log_p)
ll = 0
for (i, j), w_ij in outcomes.items():
w_ji = outcomes.get((j, i), 0)
ll += w_ij * (np.log(p[i]) - np.log(p[i] + p[j]))
ll += w_ji * (np.log(p[j]) - np.log(p[i] + p[j]))
return -ll
result = minimize(neg_log_likelihood, np.zeros(n), method='L-BFGS-B')
p = np.exp(result.x)
p = p / np.mean(p) # normalize
return {t: p[i] for i, t in enumerate(teams)}
# Example
outcomes = {('A', 'B'): 3, ('B', 'A'): 2, ('A', 'C'): 1, ('C', 'A'): 0, ('B', 'C'): 5, ('C', 'B'): 0}
strengths = bradley_terry_mle(outcomes)
print(strengths)
# P(A > B) = strengths['A'] / (strengths['A'] + strengths['B'])
Pitfalls¶
- Transitivity assumption: Bradley–Terry assumes transitivity (if A > B and B > C, then A > C), which may not hold in real sports.
- No margin information: Standard Bradley–Terry ignores score margins. For football, Poisson using goal counts is more informative.
- Parameter interpretation: Strength parameters are relative and scale-dependent — only ratios matter, not absolute values.
- Small samples: With few head-to-head games, MLE estimates are noisy. Use Bayesian priors for small-sample situations.
See Also¶
- elo-rating-system — ELO is a specific scaled case of Bradley–Terry
- glicko-2 — extends Bradley–Terry with uncertainty quantification
- massey-ratings — alternative approach using linear systems
- bayesian-inference-sports — Bayesian Bradley–Terry for small-sample estimation