# Introduction to Elo Rankings

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.

# The elo Package

The elo package includes functions to address all kinds of Elo calculations.

library(elo)

## Naming Schema

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.

# Basic Functions

To calculate the probability team.A beats team.B, use elo.prob():

elo.A <- c(1500, 1500)
elo.B <- c(1500, 1600)
elo.prob(elo.A, elo.B)
## [1] 0.500000 0.359935

To calculate the score update after the two teams play, use elo.update():

wins.A <- c(1, 0)
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:

points.A <- c(4, 1)
points.B <- c(3, 3)
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

# Formula Interface

All of the “basic” functions accept formulas as input:

dat <- data.frame(elo.A = c(1500, 1500), elo.B = c(1500, 1600),
wins.A = c(1, 0), k = 20)
form <- wins.A ~ elo.A + elo.B + k(k)
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

# Final Thoughts

All of these functions assume that Elo scores are constant. The next vignette explores calculating “running” Elos.