The Elo's system is an iterative method coined by the physics professor and excellent chess player Arpad Elo. Let be the score of team against team ; for instance, in chess a win is given a score of 1 and a draw a score of 1/2 (and a defeat a score of 0). Notice that . Let be the number of points that team is expected to score against team ; this is typically computed as a logistic function of the difference of ratings between the players, for instance,
Hence, beating a stronger player has a larger reward than beating a weaker one. Notice the intriguing similarity of Elo's update equation with equation defining temporal Massey's method. According to the movie The social network by David Fincher, it appears that the Elo's method formed the basis for rating people on Zuckerberg's Web site Facemash, which was the predecessor of Facebook.
What is the meaning of constants and ? As for parameter , it balances the difference between actual and expected scores against prior ratings. If is too large, then too much volatile are the ratings. On the other hand, if is too small, then too much stagnant are the ratings. As for parameter , notice that
Instead of building ratings based just on wins and loses, Elo's can use the number of points that scores against by defining the score that team makes against team as
Another interesting property of Elo's ratings is that the sum of all player ratings is always a constant. To show this let us compute the sum of ratings before and after a match between players and . Recall that and . It follows that
The following code plots the logistic function:
logistic = function(x, z=400) {1 / (1 + 10^(-x / z))}
curve(logistic, -1000, 1000, ylab="logistic")
abline(h = 0.5, lty=2)
abline(v = 0, lty=2)
The following user-defined function elo computes the Elo's method. If also computes the foresight prediction accuracy, possibly using a home-field advantage.
## Elo
# INPUT
# matches: matches data frame
# teams: team names
# zeta: logistic parameter
# k: update factor
# scores: use scores of matches?
# hf: home-field advantage
# OUTPUT
# r: rating vector
# wins: number of victories
# foreseen: number of foreseen victories
# accuracy: percentage of foreseen victories
elo = function(matches, teams, z = 400, k = 25, scores=FALSE, hf=0) {
# number of teams
n = length(teams)
# number of matches
m = dim(matches)[1]
# old rating vector
rold = rep(0, n)
# new rating vector
rnew = rep(0, n)
wins = 0
foreseen = 0
for (i in 1:m) {
# compute prediction accuracy
spread = matches[i,"score1"] - matches[i,"score2"]
if (spread > 0) {
wins = wins + 1
if ((rold[matches[i,"team1"]] + hf) > rold[matches[i,"team2"]]) {
foreseen = foreseen + 1
}
}
if (spread < 0) {
wins = wins + 1
if (rold[matches[i,"team2"]] > (rold[matches[i,"team1"]] + hf)) {
foreseen = foreseen + 1
}
}
# team 1
# compute score
if (scores == FALSE) {
spread = matches[i,"score1"] - matches[i,"score2"]
if (spread > 0) {
score = 1
}
if (spread == 0) {
score = 0.5
}
if (spread < 0) {
score = 0
}
} else {
score = (matches[i,"score1"] + 1) / (matches[i,"score1"] + matches[i,"score2"] + 2)
}
# compute update
delta = rold[matches[i,"team1"]] - rold[matches[i,"team2"]]
mu = 1 / (1 + 10^(-delta / z))
update = k * (score - mu)
# update rating
rnew[matches[i,"team1"]] = rold[matches[i,"team1"]] + update
# team 2
# compute score
if (scores == FALSE) {
spread = matches[i,"score2"] - matches[i,"score1"]
if (spread > 0) {
score = 1
}
if (spread == 0) {
score = 0.5
}
if (spread < 0) {
score = 0
}
} else {
score = (matches[i,"score2"] + 1) / (matches[i,"score1"] + matches[i,"score2"] + 2)
}
# compute update
delta = rold[matches[i,"team2"]] - rold[matches[i,"team1"]]
mu = 1 / (1 + 10^(-delta / z))
update = k * (score - mu)
# update new rating
rnew[matches[i,"team2"]] = rold[matches[i,"team2"]] + update
# update old ratings
rold[matches[i,"team1"]] = rnew[matches[i,"team1"]]
rold[matches[i,"team2"]] = rnew[matches[i,"team2"]]
}
return(list(r=rnew, foreseen = foreseen, wins = wins, accuracy = foreseen / wins))
}