My son’s friends began a spikeball tournament and wanted to determine who would be the best spikeball team. The traditional choice would be a round robin format and/or a tournament. With a few teams, neither would take very long, but my son wanted to know if they could use the chess rating Elo system to determine what team was the best.
First we had to learn about how the Elo system works. Basically it assigns each team a baseline rating then as teams play each other, the teams that win more get more points and the teams that lose more have fewer points. It’s similar to a computerized power ranking system in football or other sports.
Here I load the dataset then check to make sure there are no teams that are mistyped. In this table the score corresponds to whether team1 won (score = 1) or team2 won (score = 0).
games <- read.csv("../datasets/spikeball_results.csv")
head(games)
## game_ID team1 team2 score
## 1 1 jackson_isaac landon_isaiah 1
## 2 2 jackson_isaac reef_evan 0
## 3 3 conner_carter micah_conrad 1
## 4 4 laine_cole reef_evan 0
## 5 5 ryden_trenton kekoa_cedric 0
## 6 6 andyn_josh laine_cole 0
team_names <- sort(unique(c(games$team1, games$team2)))
team_names
## [1] "andyn_josh" "ari_justin" "cole_isaiah" "conner_carter"
## [5] "conrad_evan" "jackson_andyn" "jackson_isaac" "josh_carter"
## [9] "kekoa_cedric" "laine_cole" "laine_conner" "landon_conrad"
## [13] "landon_isaiah" "micah_conrad" "micah_isaiah" "micah_landon"
## [17] "reef_evan" "reef_isaac" "ryden_kekoa" "ryden_trenton"
## [21] "trenton_cedric"
Next step was to create a stand in data table to hold the ratings and assign each team a starting rating of 1500.
rating_table <- data.frame(
team = team_names,
rating = rep(1500, length(team_names))
)
rating_table
## team rating
## 1 andyn_josh 1500
## 2 ari_justin 1500
## 3 cole_isaiah 1500
## 4 conner_carter 1500
## 5 conrad_evan 1500
## 6 jackson_andyn 1500
## 7 jackson_isaac 1500
## 8 josh_carter 1500
## 9 kekoa_cedric 1500
## 10 laine_cole 1500
## 11 laine_conner 1500
## 12 landon_conrad 1500
## 13 landon_isaiah 1500
## 14 micah_conrad 1500
## 15 micah_isaiah 1500
## 16 micah_landon 1500
## 17 reef_evan 1500
## 18 reef_isaac 1500
## 19 ryden_kekoa 1500
## 20 ryden_trenton 1500
## 21 trenton_cedric 1500
I constructed the Elo function where expected score is between 0 and 1 and corresponds to the probability of one team beating another as a function of the teams’ ratings.
expected_score <- function(rat_a, rat_b) {
1 / (1 + 10^((rat_b-rat_a)/400))
}
The hard part was figuring out how to update the rating table using the result of a game, where score is 1 or 0 depending on whether team A won or team B won.
rating_update <- function(team_a, team_b, score, ratings) {
# extract rating from table
rat_a <- ratings$rating[which(ratings$team == team_a)]
rat_b <- ratings$rating[which(ratings$team == team_b)]
# calculate new rating
rat_a <- rat_a + 20 * (score-expected_score(rat_a, rat_b))
rat_b <- rat_b + 20 * (-score+expected_score(rat_a, rat_b))
# replace rating in table
ratings$rating[which(ratings$team == team_a)] <- rat_a
ratings$rating[which(ratings$team == team_b)] <- rat_b
return(ratings)
}
Finally I went through the games and updated the table from each game. There’s probably a nifty vector based way to do this but I used a for loop for simplicity.
for(i in 1:nrow(games)) {
rating_table <- rating_update(games$team1[i], games$team2[i], games$score[i], rating_table)
}
library(dplyr)
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
rating_table %>% arrange(-rating)
## team rating
## 1 conner_carter 1538.572
## 2 trenton_cedric 1529.417
## 3 reef_evan 1527.265
## 4 conrad_evan 1510.550
## 5 reef_isaac 1509.976
## 6 micah_landon 1509.186
## 7 josh_carter 1508.643
## 8 ryden_trenton 1500.813
## 9 jackson_isaac 1499.465
## 10 kekoa_cedric 1499.449
## 11 cole_isaiah 1499.425
## 12 laine_cole 1499.139
## 13 landon_conrad 1490.837
## 14 ryden_kekoa 1490.296
## 15 landon_isaiah 1490.288
## 16 micah_conrad 1490.288
## 17 laine_conner 1490.000
## 18 micah_isaiah 1482.445
## 19 andyn_josh 1480.565
## 20 jackson_andyn 1480.008
## 21 ari_justin 1471.381
Et voila! Elo ratings for all the teams.