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.

Wir 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 vom Fähigkeitswert der Person und von der Itemschwierigkeit abhängt. Ü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.38449022
F_alba-berlin                     1.28078764
F_fc-bayern-muenchen              0.81674418
F_bg-goettingen                   0.50103385
F_ewe-baskets-oldenburg           0.48547923
F_mhp-riesen-ludwigsburg          0.29131234
F_ratiopharm-ulm                  0.10878235
F_wuerzburg-baskets               0.08605295
F_niners-chemnitz                 0.08156395
F_rostock-seawolves              -0.11635679
F_brose-bamberg                  -0.30858316
F_syntainics-mbc                 -0.31747895
F_hakro-merlins-crailsheim       -0.33204017
F_mlp-academics-heidelberg       -0.43244842
F_fraport-skyliners              -0.55371067
F_towers-hamburg                 -0.73907082
F_basketball-loewen-braunschweig -0.96596989
F_medi-bayreuth                  -1.27102811

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 das Team mit der höheren Elo-Zahl eine niedrigere Elo-Zahl erhält. Das Konzept ist vor allem aus dem Schach bekannt.

# Beispiel zur Elo-Zahl
library(elo)
# Fall A - Berlin gewinnt
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, sind im Datensatz BBL_2 die Spiele nach Spieltag sortiert 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

Vorläufiges Fazit:

Wir haben nun drei Möglichkeiten kennengelernt, 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.9961500
Elo_Score   0.9910351 1.0000000 0.9922191
F_Score     0.9961500 0.9922191 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 Abschlusstabelle der Saison ist, bleibt eine empirische Frage und kann nach dem letzten Spieltag an dieser Stelle beantwortet werden.