Implementing Elo Ratings for Spikeball

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.