Ekstraklasa – ranking Elo i symulacje

Kontynuujemy cykl związany z Ekstraklasą. Przyszła pora na zbudowanie bardzo prostego modelu symulującego rozgrywki.

Zgodnie z zapowiedzią:

  • w pierwszej przeprowadziliśmy prostą analizę historycznych rozgrywek
  • w niniejszej – drugiej – części spróbujemy znaleźć model, który pozwoli na symulację rozgrywek oraz sprawdzimy jego skuteczność. Będzie dużo teorii.
  • w ostatniej części przygotowanym modelem wytypujemy wyniki na sezon 2017/18

Do dzieła!

Model oprzemy na popularnym rankingu Elo. Tego typu model wykorzystywany jest często przy symulacji rozgrywek podczas futbolowych Mistrzostw Europy lub Świata, a także w ligach innych dziedzin sportowych – chociażby NBA, NFL czy MBL. Polecam przewidywania w serwisie FiveThirtyEight, w szczególności Elo w NBA oraz Elo w NFL.

Ranking ELO został wymyślony na potrzeby rozgrywek szachowych – miał umożliwić ocenę własnych postępów w porównaniu z innymi szachistami. Nazwa wbrew pozorom nie pochodzi od Elo, ziom! a od nazwiska Arpada Elo – fizyka węgierskiego pochodzenia, który opracował system oceny umiejętności szachistów na prośbę Federacji Szachowej Stanów Zjednoczonych. Ranking opracowany został w 1960 roku, a jego główną ideą jest nie tyle porównanie graczy po rozgrywce (bo to nie jest trudne), co przed nią razem z przewidywaniem zwycięzcy.

Porównanie przed turniejem rankingu zawodnika i jego przeciwników powinno określić oczekiwany rezultat tego zawodnika. Jeśli w turnieju uzyskał wynik lepszy od oczekiwanego to jego ranking powinien wzrosnąć, jeśli gorszy – zmaleć. Elo zaproponował potraktowanie oczekiwanego wyniku jako zmiennej losowej o rozkładzie normalnym.

Istnieje kilka modyfikacji rankingu, smaczku dodaje ciekawostka wspomniana w angielskojęzycznej Wikipedii (polecam zajrzeć – ładnie tam wszystko wyjaśnione). Otóż jest jedna scena w filmie The Social Network (taki film o powstaniu Facebooka), gdzie między wierszami przemycona jest informacja, że w Facemash (prototyp Facebooka) do oceny dziewczyn użyty został właśnie Elo. Ale ranking Elo, a nie Arpad Elo.

Jak to się liczy?

Liczymy dwie rzeczy: kto wygra rozgrywkę na podstawie wartości Elo przeciwników i nowe wartości Elo po rozgrywce. W szachach to wystarczy. W grach, gdzie zdobywa się punkty (na przykład piłka nożna) chcielibyśmy również przewidzieć wynik.

Kto wygra?

Na początek zdefinujemy sobie funkcję:

    \[ f(x) = \frac{1}{1 + 10^{\frac{x}{400}}} \]

Wartość x podstawiana do funkcji będzie kombinacją czterech parametrów:

  • aktualnych (przed rozgrywką) wartości Elo dla jednej (A – eloA) i drugiej (B – eloB) strony pojedynku
  • wartości elo_home, która określa przewagę gospodarza (lub tego, który rozpoczyna grę – np. w szachach) – zakładamy tutaj, że A gra u siebie
  • wartości elo_draw, która określa jak bardzo prawdopodobny jest remis

Funkcja f(x) pozwoli na określenie prawdopodobieństwa wygranych przez każdą ze stron oraz prawdopodobieństwa remisu. Liczy się to dość prosto:

  • P(WygrywaA) = f(eloB – eloA – elo_home + elo_draw)
  • P(WygrywaB) = f(eloA – eloB + elo_home + elo_draw)
  • P(Remis) = 1 – P(WygrywaA) – P(WygrywaB)

Porównując prawdopodobieństwa P(WygrywaA), P(WygrywaB) oraz P(Remis) możemy określić prawdopodobny wynik meczu (kto wygra, nie liczbę bramek).

Zmiana Elo

Po rozgrywce należy uwzględnić jej wynik i odpowiednio zaktualizować wartości punktowe Elo dla obu przeciwników.

Nową wartość określimy zgodnie z równaniem:

    \[ R_{n} = R_{o} + K * (W - W_{e}) \]

gdzie:

  • Rn to oczywiście nowy ranking, a Ro – stary (przed meczem)
  • K określa wagę spotkania i przyjmuje wartości:
    • 60 dla finałów rozgrywek mistrzostw świata
    • 50 dla finałów rozgrywek kontynentalnych
    • 40 dla dużych, międzynarodowych turniejów, w tym rund kwalifikacyjnych do mistrzostw świata czy kontynentu
    • 30 dla pozostałych rozgrywek turniejowych
    • 20 dla spotkań towarzystkich
  • zgodnie z powyższym w Ekstraklasie używamy K = 30
  • dodatkowo wartość K jest dostosowana na podstawie wyniku spotkania:
    • jest powiększona o połowę (pomnożona przez 1.5) jeśli mecz zakończył się różnicą 2 bramek
    • pomnożona przez \frac{3}{4} jeśli różnica wyniosła trzy bramki
    • pomnożona przez \frac{3}{4} + \frac{N-3}{8} jeśli mecz zakończył się różnicą co najmniej czterech punktów, przy czym N określa ową różnicę
  • W to wynik meczu z perspektywy drużyny dla której liczymy nową wartość Elo: 1 za zwycięstwo, 0.5 za remis, 0 za przegraną
  • We to oczekiwany rezultat, wyliczony według równania (dr to różnica rankingu Elo pomiędzy drużynami):

    \[ W_{e} = \frac{1}{10^{-\frac{dr}{400}} + 1} \]

Uff. Spora dawka teorii, ale prostej i łatwo implementowalnej w kodzie. Odpowiednie funkcje znajdziecie w pliku elo_functions.r w githubowym repozytorium.

Wszystko wydaje się proste, ale mamy jedną zagwozdkę – nie znamy wartości elo_draw i elo_home. Możemy jakieś przyjąć, ale czy dowolne dadzą dobry model? Jeśli będziemy mieć szczęście – pewnie tak.

Ja postanowiłem wyznaczyć te współczynniki eksperymentalnie. Metodą nieco czołgową – dla każdej z kombinacji (w ramach rozsądku dobierając przedziały) zasymulowałem wyniki (to nastąpi za chwilę) rozgrywek dla wszystkich kolejnych sezonów, mecz po meczu. Później sprawdziłem jak dobrze model sprawdził się w porównaniu z rzeczywistymi wynikami. I kombinacja elo_draw i elo_home, która dała nalepsze wyniki (najwięcej trafionych wskazań zwycięzcy lub remisów) została przyjęta do modelu końcowego.

Poszczególne kombinacje (i ich skuteczność) przedstawia poniższa heat-mapa:

Ostatecznie wybieramy elo_draw = 120 i elo_home = 165. Dodatkowo, żeby zwycięzca konkretnego meczu miał szansę być za każdym razem inny te wartości są losowane – wykorzystując rozkład normalny o średniej równej parametrowi i odchyleniu standardowym równym dwa. W przeciwnym przypadku za każdym razem zwycięzca byłby jednakowy i cała symulacja nie miałaby większego sensu.

Symulacja rozgrywek z użyciem ELO

Jak wyglądała symulacja? Założenia (i zarazem algorytm) są następujące:

  • drużyny startują ze swoim prawdziwym Elo sprzed rozgrywek
  • rozgrywamy po 1000 razy każdy mecz (wirtualnie)
  • wygrywa ten, kto wygrał najczęściej w tych wirtualnych meczach (dlatego potrzebna jest losowość przy elo_home i elo_draw; inaczej zawsze wygrywałby ten, kto ma większe Elo z uwzględnieniem “forów” dla gospodarza)
  • losujemy wynik, tak żeby wygrany rzeczywiście wygrał, biorąc pod uwagę historyczne spotania drużyn:
    • bramki wygranego to średnia liczba bramek wygranego w jego wygranych meczach (tych rzeczywistych)
    • bramki przegranego to średnia liczba bramek przegranego w jego rzeczywistych przegranych meczach
    • jeśli spotkania drużyn nie było do tej pory – losujemy jakąś liczbę bramek dla przegranego i dodajemy jeden dla wygranego (zamiast jedynki też możemy coś losować)
    • w przypadku remisu – wynik jest średnią z dotychczasowych remisów (lub losową liczbą jeśli nie było takiego spotkania)
  • po każdym wirtualnym meczu liczymy nowy ranking Elo, który użyty jest do kolejnych rozgrywek (i od razu zaczyna rozjeżdżać się on z rzeczywistością, symulacja podąża własną drogą)

Czas na najciekawsze, czyli

wyniki symulacji

Kto stracił, kto zyskał punkty ELO?

Początkowe wartości Elo pobrałem z bardzo fajnej strony ClubELO.com zgodnie ze stanem na początek sezonu 2008-2009 (na dzień 8 sierpnia 2008). Dla drużyn, których nie było w danych ze strony przypisałem wartości równe medianie (dokładnie 1254) istniejących wartości (to jedna z metod uzupełnienia wartości brakujących). Tabela początkowa wygląda następująco:

n Drużyna ELO
1 Wisła Kraków 1565
2 Legia Warszawa 1485
3 Lech Poznań 1426
4 Cracovia 1333
5 GKS Bełchatów 1326
6 Ruch Chorzów 1288
7 ŁKS Łódź 1285
8 Górnik Zabrze 1257
9 Korona Kielce 1254
10 Zagłębie Lubin 1254
11 Widzew Łódź 1254
12 Podbeskidzie Bielsko-Biała 1254
13 Pogoń Szczecin 1254
14 Zawisza Bydgoszcz 1254
15 Górnik Łęczna 1254
16 Termalica Bruk-Bet Nieciecza 1254
17 Wisła Płock 1254
18 Polonia Bytom 1252
19 Odra Wodzisław Śl. 1250
20 Arka Gdynia 1229
21 Polonia Warszawa 1229
22 Piast Gliwice 1229
23 Lechia Gdańsk 1229
24 Śląsk Wrocław 1229
25 Jagiellonia Białystok 1211

Zobaczmy jak zmieniła się wartość Elo dla poszczególnych drużyn po zakończeniu symulacji wszystkich sezonów, łącznie z 2016/17:

co daje końcową tabelę:

n Drużyna ELO
1 Legia Warszawa 2015
2 Lech Poznań 1927
3 Wisła Kraków 1795
4 Lechia Gdańsk 1292
5 Zagłębie Lubin 1253
6 Polonia Warszawa 1234
7 Śląsk Wrocław 1231
8 Pogoń Szczecin 1223
9 Odra Wodzisław Śl. 1221
10 Górnik Zabrze 1218
11 Polonia Bytom 1205
12 Wisła Płock 1201
13 Zawisza Bydgoszcz 1198
14 Jagiellonia Białystok 1194
15 Ruch Chorzów 1194
16 Arka Gdynia 1192
17 Podbeskidzie Bielsko-Biała 1191
18 ŁKS Łódź 1186
19 GKS Bełchatów 1186
20 Cracovia 1181
21 Korona Kielce 1180
22 Piast Gliwice 1170
23 Termalica Bruk-Bet Nieciecza 1162
24 Górnik Łęczna 1149
25 Widzew Łódź 1111

Jak widać Wisła Kraków z pierwszego miejsca spadła na trzecie, ustępując miejsca Legii i Lechowi. Cracovia straciła najwięcej i poleciała o 16 miejsc w dół. A Jagiellonia mimo, że straciła kilkanaście punktów awansowała w rankingu – wygrywała z drużynami słabszymi (które straciły więcej).

Drużyny z początku aktualnej (z ostatniego sezonu) tabeli zyskały najwięcej.

Aktualnych rankingów Elo dla drużyn nie podam – jeśli jesteście ciekawi to zajrzyjcie na przykład na ClubELO.com (wszystkich tam niestety nie ma).

Zobaczmy jeszcze na przebieg historii Elo dla poszczególnych drużyn (w ramach symulacji).

Generalnie: dobry jest coraz lepszy.

To wszystko symulacja, za każdym razem może wyjść odrobinę (albo i nawet więcej niż odrobinę) inaczej! Wystarczy że słaba (z małym Elo) drużyna zagra u siebie i wygra z mocną – wówczas “wymienią się” różnicą punktów Elo, co może skutkować niezłym późniejszym zamieszaniem. Tak byłoby gdybyśmy rozgrywali jeden wirtualny mecz. Przy próbie 1000 (przy setce jest już ok) losowań na rozgrywkę powinno być w miarę jednakowo.

To tak samo jak rzut kostką – im więcej rzutów (“symetryczną kostką sześcienną” – to wbrew pozorom ważne założenie) tym bardziej zbliżamy się do prawdopodobieństwa 1/6 trafienia dowolnej z liczb na kostce. Taka metoda nazywa się Monte Carlo i być może jeszcze o niej kiedyś napiszę.

Dobrze, mamy bardzo prosty model, mamy wyniki jego działania, mamy rzeczywistość. Z heatmapy wyżej można już wyczytać

sprawdzalność modelu

Jak bardzo trafiliśmy albo jak bardzo pomyliliśmy się?

Tabela niżej przedstawia szczegółowy rozkład kombinacji zwycięzca w rzeczywistości kontra zwycięzca w symulacji:

Wygrany w rzeczywistości Wygrany według symulacji p
gospodarz gospodarz 39.0%
gość gospodarz 19.2%
remis gospodarz 21.6%
gospodarz gość 4.8%
gość gość 7.0%
remis gość 4.2%
gospodarz remis 1.4%
gość remis 1.5%
remis remis 1.4%

Jak to czytać? W rzeczywistości wygrywa Gość, w symulacji wygrywa Gospodarz (czyli wynik symulacji jest niepoprawny) – takich przypadków mamy 19.2%. I tak dalej dla pozostałych par.

Sprawdzalność modelu to 47.36% (suma zgodnych par). To dobrze czy źle? A może raczej należy zadać pytanie dlaczego się nie udało?

Przede wszystkim z powodu braku kilku informacji:

  • nie uwzględniamy innych (poza Ekstraklasą) meczy pomiędzy drużynami – te same zespoły grają ze sobą chociażby w Pucharze Polski
  • nie ma meczy zespołów, które wypadły i wróciły do Ekstraklasy (ich Elo jest “zamrożone” w czasie nieobecności)
  • nie ma przede wszystkim meczy z innymi drużynami (ligii międzynarodowe)

Uzupełnienie o taką historię może dużo poprawić. Ale to nie wszystko. Model oparty na rankingu Elo to tylko liczby – dwie liczby określają dwie drużyny i to wszystko. Dobry model może uwzględniać inne czynniki:

  • pogodę
  • składach zespołów podczas spotkań, na przykład każdemu z zawodników można by przypisać jakąś wartość punktową, która zmienia się w zależności od aktywności zawodnika podczas meczu
  • zmęczeniu zawodników (występy w reprezentacji, występy zespołu w innych ligach)
  • kto był trenerem (punkty dla trenera analogiczne jak dla piłkarzy)
  • czy była publiczność na stadionie czy nie, albo – liczba kibiców każdej z drużyn
  • czy mecz był transmitowany w TV
  • i tak dalej, i tak dalej

Czekam na Wasze opinie i komentarze. Wpiszcie tam niżej.

Skrypty w R dostępne są na GitHubie. Te użyte w tej części to elo_sym.r oraz find_good_elo_params.r oraz elo_functions.r.

W kolejnej części spróbujemy przewidzieć wyniki sezonu 2017-2018.

Dodaj komentarz