Elo is a system of ratings/rankings (named after its creator, Arpad Elo) for pairwise matchups. In short, pairs of “teams” (“A” and “B”) begin a match with rankings \(R_A\) and \(R_B\). The result (“score”) of the game is coded as 0/0.5/1 for loss/tie/win, respectively. The prior expectation of this result can be expressed as \[P_A = \frac{1}{1 + 10^{(R_B - R_A) / 400}}\] \[P_B = \frac{1}{1 + 10^{(R_A - R_B) / 400}} = 1 - P_A\] where \[P_i\] is the prior probability that team \(i\) wins the match.
After each match, ratings are updated as follows: \[R^{new}_A = R_A + K(S_A - P_A)\] \[R^{new}_B = R_B + K(S_B - P_B) = R_B + K(1 - S_A - (1 - P_A)) = R_B - K(S_A - P_A)\] where \(S_i\) is the score of team \(i\) (0/0.5/1) and \(K\) is an update weight (commonly called the “k-factor”).
Therefore, we see that the system as a whole (all teams) retains (“conserves”) its total sum of Elo ratings; for every rating point team A gains/loses, team B loses/gains the same amount.
elo
PackageThe elo
package includes functions to address all kinds
of Elo calculations.
library(elo)
Most functions begin with the prefix “elo.”, for easy autocompletion.
Vectors or scalars of Elo scores are denoted elo.A
or elo.B
.
Vectors or scalars of wins by team A are denoted by
wins.A
.
Vectors or scalars of win probabilities are denoted by
p.A
.
Vectors of team names are denoted team.A
or
team.B
.
To calculate the probability team.A beats team.B, use
elo.prob()
:
<- c(1500, 1500)
elo.A <- c(1500, 1600)
elo.B elo.prob(elo.A, elo.B)
## [1] 0.500000 0.359935
To calculate the score update after the two teams play, use
elo.update()
:
<- c(1, 0)
wins.A elo.update(wins.A, elo.A, elo.B, k = 20)
## [1] 10.0000 -7.1987
To calculate the new Elo scores after the update, use
elo.calc()
:
elo.calc(wins.A, elo.A, elo.B, k = 20)
## elo.A elo.B
## 1 1510.000 1490.000
## 2 1492.801 1607.199
It may be helpful to calculate wins.A
from raw
scores:
<- c(4, 1)
points.A <- c(3, 3)
points.B elo.calc(score(points.A, points.B), elo.A, elo.B, k = 20)
## elo.A elo.B
## 1 1510.000 1490.000
## 2 1492.801 1607.199
All of the “basic” functions accept formulas as input:
<- data.frame(elo.A = c(1500, 1500), elo.B = c(1500, 1600),
dat wins.A = c(1, 0), k = 20)
<- wins.A ~ elo.A + elo.B + k(k)
form elo.prob(form, data = dat)
## [1] 0.500000 0.359935
elo.update(form, data = dat)
## [1] 10.0000 -7.1987
elo.calc(form, data = dat)
## elo.A elo.B
## 1 1510.000 1490.000
## 2 1492.801 1607.199
Note that for elo.prob()
, formula =
can be
more succinct:
elo.prob(~ elo.A + elo.B, data = dat)
## [1] 0.500000 0.359935
We can even adjust the Elos, for, e.g., home-field advantage.
elo.calc(wins.A ~ adjust(elo.A, 10) + elo.B + k(k), data = dat)
## elo.A elo.B
## 1 1509.712 1490.288
## 2 1492.534 1607.466
All of these functions assume that Elo scores are constant. The next vignette explores calculating “running” Elos.