Basketball - Liga-Rating

Was ist die Spielstärke eines Teams

Theorie
R
Sport
Author

Markus Burkhardt

Published

March 8, 2023

Lernziele in R

Beispiel für:

  • \(F-Score\) (IRT)

  • \(Elo\)- Zahl

im Kontext von Basketball

Spielstärke im Basketball:

Die Spielstärke eines Basketballteams hängt von den technischen, physischen und mentalen Fähigkeiten ab. Hinzu kommt eine gute Portion Erfahrung und Spieltaktik. Am Ende eines Spiels steht jedoch einfach die Frage, ob ein Spiel gewonnen oder verloren wurde. Die Qualität bzw. Spielstärke eines Teams wird gemeinhin durch Anzahl gewonnener Spiele bestimmt, was sich im Punktwert in der Tabelle niederschlägt.

Wie wollen hier neben der Anzahl gewonnener Spiele zwei weitere Möglichkeiten betrachten, die Spielstärke zu ermitteln: der \(F-Score\) als Fähigkeitsparameter eines Teams und die \(Elo\)-Zahl als Ausdruck der Gewinnwahrscheinlichkeit.

Der \(Sieg\)-Score

Im Ligabetrieb der Basketballbundesliga (BBL) erhalten Mannschaften für ein gewonnenes Spiel 2 Punkte und für ein verlorenes Spiel 0 Punkte. Bei einem Unentschieden nach der regulären Spielzeit gibt es solange Verlängerung, bis ein Sieger feststeht. Die Spielstärke eines Teams wird also allein durch die Anzahl gewonnener Spiele ermittelt:

# Einlesen der Daten aus der Hinrunde.
# Zeilenweise sind die Ergebnisse der Mannschaften kodiert. 
# 1... Sieg
# 0... Niederlage 
# Eine Zeile repräsentiert die Leistung / Fähigkeit einer Mannschaft.
# Spaltenweise kann später die Schwierigkeiten der Gegner ermittelt werden.
BBL <- read.csv2(
  "https://www-user.tu-chemnitz.de/~burma/blog_data/IRT_Tab_BBL.csv"
  )
rownames(BBL) <-  BBL$X 
BBL <-  BBL[, -1]
# exemplarisch die ersten 6 Teams und die letzten 2 "Items"
BBL[1:6, 17:18]
                                 Items_towers.hamburg Items_wuerzburg.baskets
F_alba-berlin                                       1                       1
F_basketball-loewen-braunschweig                    1                       0
F_bg-goettingen                                     1                       1
F_brose-bamberg                                     0                       1
F_ewe-baskets-oldenburg                             1                       0
F_fc-bayern-muenchen                                0                       1

Nun ermitteln wir über die Zeilensumme die Anzahl der gewonnen Spiele.

Sieg_Score <- sort(rowSums(BBL, na.rm = T), decreasing = TRUE)
data.frame(Sieg_Score)
                                 Sieg_Score
F_telekom-baskets-bonn                   15
F_alba-berlin                            14
F_fc-bayern-muenchen                     12
F_bg-goettingen                          11
F_ewe-baskets-oldenburg                  11
F_mhp-riesen-ludwigsburg                 10
F_niners-chemnitz                         9
F_ratiopharm-ulm                          9
F_wuerzburg-baskets                       9
F_rostock-seawolves                       8
F_brose-bamberg                           7
F_hakro-merlins-crailsheim                7
F_syntainics-mbc                          7
F_mlp-academics-heidelberg                6
F_fraport-skyliners                       5
F_towers-hamburg                          5
F_basketball-loewen-braunschweig          4
F_medi-bayreuth                           2

Der \(F-Score\)

Für den F-Score nutzen wir die Item-Response-Theorie (IRT) als Analogie. Die IRT geht davon aus, dass die Beantwortung eines Items zur Messung einer latenten Variable aus einem Fähigkeitswert der Person und der Itemschwierigkeit zusammen. Übersetzt in die Basketballlogik bedeutet dieses Konzept, dass der Sieg in einem Spiel von der Fähigkeit der eigenen Mannschaft und der Schwierigkeit des Gegners abhängt.

Obwohl einige Voraussetzungen der IRT in unserem Anwendungsbeispiel nicht erfüllt sind, wollen wir trotzdem wagen den \(F-Score\) der BBL-Teams zu ermitteln. Die Blogbeiträge von Hao ( 2022) und Masur (2022) bieten eine hervorragende Einführung in die IRT mit R. Wir betrachten hier lediglich die Ergebnisse.

library(mirt) # Paket zur IRT
BBL_irt_model1 <- mirt(BBL, itemtype = "Rasch", verbose = F)
BBL_FScore <- fscores(BBL_irt_model1)
names(BBL_FScore) <- rownames(BBL)
F_Score <- sort(BBL_FScore, decreasing = T)
data.frame(F_Score)
                                     F_Score
F_telekom-baskets-bonn            1.37705069
F_alba-berlin                     1.27387004
F_fc-bayern-muenchen              0.81274049
F_bg-goettingen                   0.49885445
F_ewe-baskets-oldenburg           0.48339031
F_mhp-riesen-ludwigsburg          0.29013237
F_ratiopharm-ulm                  0.10845063
F_wuerzburg-baskets               0.08579881
F_niners-chemnitz                 0.08134249
F_rostock-seawolves              -0.11572568
F_brose-bamberg                  -0.30712137
F_syntainics-mbc                 -0.31597374
F_hakro-merlins-crailsheim       -0.33045815
F_mlp-academics-heidelberg       -0.43033612
F_fraport-skyliners              -0.55107069
F_towers-hamburg                 -0.73562524
F_basketball-loewen-braunschweig -0.96135334
F_medi-bayreuth                  -1.26429427

Die \(Elo\)-Zahl

Die Elo-Zahl basiert auf der Annahme, dass die relative Spielstärke zweier Teams proportional zur Wahrscheinlichkeit ist, dass das stärkere Team gewinnt. Wenn ein Team mit einer höheren Elo-Zahl (z. B. Alba Berlin) gegen ein Team mit einer niedrigeren Elo-Zahl (z. B. Chemnitz 99) antritt, wird erwartet, dass das Team mit der höheren Elo-Zahl das Spiel gewinnt. Wenn jedoch die Mannschaft mit der niedrigeren Elo-Zahl überraschend gewinnt, erhält sie eine höhere Elo-Zahl, während der Spieler mit der höheren Elozahl eine niedrigere Elo-Zahl erhält. Das Konzept ist vor allem aus dem Schach bekannt.

# Beispiel zur Elo-Zahl
library(elo)
# Fall A - Berlin geinnt
Fall_A <- elo.calc(wins.A = 1, elo.A = 1100, elo.B = 1000, k = 20)
Fall_B <- elo.calc(wins.A = 0, elo.A = 1100, elo.B = 1000, k = 20)
#
Fall_A; Fall_B
     elo.A    elo.B
1 1107.199 992.8013
     elo.A    elo.B
1 1087.199 1012.801

Während Chemnitz bei einer Niederlage (Fall_A) lediglich 7 Elo-Punkte verliert, würde das Team bei einem Sieg (Fall_B) 13 Elo-Punkte gewinnen.

Ermitteln wir den Verlauf der Elo-Zahlen über die Saison. Da die Elo-Zahl nicht unabhängig von der Reihenfolge der Spiele ist, haben wir im Datensatz BBL_2 die Spiele hinsichtlich der Reihenfolge (sortiert nach Spieltag hinterlegt). Anschließend ermitteln wir nach jedem Spiel die neuen Elo-Zahlen.

# Einlesen der Daten
BBL_2 <- read.csv2(
  "https://www-user.tu-chemnitz.de/~burma/blog_data/BBL_2.csv"
  )
Teams <- sort(unique(BBL_2$V2))
i = 2
Elo_Score <- rep(1000, 18)
names(Elo_Score) <- sort(unique(BBL_2$V2))
for (i in 1: nrow(BBL_2)) {
  h1 <- BBL_2[i,] 
  result <- ifelse(h1$V1 > h1$V3, 1, 0)
  new_elo <- elo.calc(wins.A = result,
                      elo.A = unlist(Elo_Score[h1$V2]),
                      elo.B = unlist(Elo_Score[h1$V4]),
                      k = 20)
  Elo_Score[h1$V2] <- unlist(new_elo[1])
  Elo_Score[h1$V4] <- unlist(new_elo[2])
}
Elo_Score <- sort(Elo_Score, decreasing = TRUE)
data.frame(Elo_Score)
                               Elo_Score
telekom-baskets-bonn           1130.0306
alba-berlin                    1124.8563
fc-bayern-muenchen             1064.7000
ewe-baskets-oldenburg          1046.6310
bg-goettingen                  1044.5056
ratiopharm-ulm                 1035.9338
mhp-riesen-ludwigsburg         1025.5762
brose-bamberg                  1008.9465
wuerzburg-baskets               998.0369
niners-chemnitz                 987.9494
syntainics-mbc                  984.8858
rostock-seawolves               979.8147
hakro-merlins-crailsheim        958.4813
towers-hamburg                  946.8142
mlp-academics-heidelberg        945.8847
basketball-loewen-braunschweig  929.9745
fraport-skyliners               918.1736
medi-bayreuth                   878.8048

Vorlaufiges Fazit:

Wir haben nun 3 Möglichkeiten kennen gelernt die Spielstärke eines Basketballteams zu ermitteln. Alle 3 ermittelten Maßzahlen unterscheiden sich nur geringfügig:

cor(data.frame(Sieg_Score, Elo_Score, F_Score),
    use = "pairwise.complete.obs")
           Sieg_Score Elo_Score   F_Score
Sieg_Score  1.0000000 0.9910351 0.9961607
Elo_Score   0.9910351 1.0000000 0.9922154
F_Score     0.9961607 0.9922154 1.0000000
plot(data.frame(Sieg_Score, Elo_Score, F_Score))

\(Elo\)-Score und \(F\)-Score sind etwas differenzierter in ihrer Abstufung. Dagegen werden Mannschaften beim Sieg-Score als gleichstark klassifiziert. Der \(Elo\)-Score hat den Nachteil, dass er mehr Informationen benötigt (die Reihenfolge der Spiele. Vereinfachend könnte diese Information aber auch unberücksichtigt bleiben.)
Welcher Score der beste Prädiktor für die Abschluss-Tabelle der Saison ist, bleibt eine empirische Frage und kann nach dem letzten Spieltag an dieser Stelle beantworten wollen.