Author: Ethan Heinzen

# Introduction to Elo Rankings and the ‘elo’ Package

#### Ethan Heinzen

# 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.