Az ESPN Fantasy Football előrejelzéseinek ellenőrzése Python segítségével

Vasárnap reggel van, a játékoskeretek hamarosan lezárulnak a kora délutáni játéknapra, és két kilencedik szintű WR között kell döntened a WR3 helyért. Ez a 3. hét, mindkettő waiver wire kétségbeesett választás, amit még nem volt időd kutatni, és őszintén szólva, ma más dolgod is van. Íme: az ESPN előrejelzése az egyik srácnak 7, a másiknak 8. A 8-asra szavazol, és azt gondolod: “Ennek az előrejelzett pontszámnak jelentenie kell valamit, nem?”

tl;dr Ez az előrejelzett pontszám lényegében semmit sem jelent, és ezt meg is tudjuk mutatni, az ESPN Fantasy API (nem dokumentált) és egy kis Python segítségével.

Egy kis háttér

Mike Clay az ESPN fantasy előrejelzéseinek embere a függöny mögött. Esküszik rá, hogy egy “hosszadalmas folyamat”, amely “statisztikai számításokat és szubjektív inputokat” tartalmaz. Úgy értem, őt fizetik, én pedig blogbejegyzéseket írok, úgyhogy nem utálhatom túlságosan, bármi is legyen ez a rejtélyes statisztikai folyamat.

Ettől függetlenül számos elemzés készült már az ESPN előrejelzéseit más oldalakkal összehasonlítva, az intenzív reddit-posztoktól kezdve a NYT blogbejegyzésekig mindenhol.

A konszenzus úgy tűnik, hogy az ESPN előrejelzései nem túl jók, olyan mérőszámok alapján, mint a “pontosság” és az R-négyzet, amelyek egyetlen összefoglaló statisztikával próbálják számszerűsíteni az általános hibát.

De azt is észrevettem, hogy nagyon kevés információ van arról, hogyan lehet ezt saját magunknak ellenőrizni. Ez az oldal a footballanalytics.net-től néhány nagyszerű R szkriptre hivatkozik, de nem láttam, hogy bármelyik konkrétan az ESPN előrejelzéseit ragadja meg (bár lehet, hogy tévedek).

Exploring …

Az ESPN jelenleg egy történelmi szezon előrejelzéseit tartja fenn, így ragadjuk meg a 2018-as évet, és nézzük meg, mit találunk.

Kihasználjuk az ESPN Fantasy API-t, amelynek használatát itt ismertetem.

Egyszóval, dióhéjban, ugyanazt a GET-kérést fogjuk küldeni az ESPN-nek, amit a honlapja küld a saját szervereinek, amikor egy történelmi liga oldalára navigálunk. A Safari Web Developer eszközeivel vagy egy olyan proxy szolgáltatással, mint a Charles vagy a Fiddler, kihallgathatja, hogyan alakulnak ki ezek a kérések.

Mielőtt kódot írnánk az összes szükséges adat megszerzéséhez, vizsgáljunk meg egy kis darabot belőle:

import requestsswid = 'YOUR_SWID'espn_s2 = 'LONG_ESPN_S2_KEY'league_id = 123456season = 2018week = 5url = 'https://fantasy.espn.com/apis/v3/games/ffl/seasons/' + \ str(season) + '/segments/0/leagues/' + str(league_id) + \ '?view=mMatchup&view=mMatchupScore'r = requests.get(url, params={'scoringPeriodId': week}, cookies={"SWID": swid, "espn_s2": espn})d = r.json()

Ezt egy kicsit részletesebben kifejtettem az előző bejegyzésemben, de a lényege az, hogy az ESPN API-jának küldünk egy kérést egy adott ligára, hétre és szezonra vonatkozó konkrét nézetért, amely teljes matchup/boxscore információt ad nekünk, beleértve az előrejelzett pontokat is. A sütikre csak a privát ligákhoz van szükség, és ismét itt foglalkozom vele.

Ha a JSON struktúrában navigálsz, láthatod, hogy minden fantasy csapatnak van egy roster a játékosairól, és minden játékosnak van egy stats listája.

(Ismétlem, ebben a struktúrában való navigálás egy szép módja a Safari Web Developer eszközeivel: menj a fantázialigád klubházának egy érdekes oldalára, nyisd meg a Web Developer eszközeit, menj az erőforrásokra, majd keresd meg az XHR-ek között a liga azonosítóját tartalmazó objektumot. Ez lesz a JSON nyers szövege … cseréld ki a “Response” szót “JSON”-ra a kis fejlécben a felhasználóbarát explorer-stílusú felülethez.)

Grabbing all 2018 Projections

Egy kicsit rejtve van, de ebben az alstruktúrában vannak a tervezett és a tényleges fantasy pontok minden játékosnak az egyes játékoslistákon.

Észrevettem, hogy egy adott játékos stats listájában 5-6 bejegyzés van, amelyek közül az egyik mindig az előrevetített pontszám, a másik pedig a tényleges. A tervezett pontszámot a statSourceId=1, a ténylegeset a statSourceId=0 azonosítja.

Ezt a megfigyelést használjuk fel arra, hogy létrehozzunk egy olyan cikluskészletet, amely minden hétre vonatkozóan GET-kéréseket küld, majd kivonjuk az egyes játékosok tervezett/tényleges pontszámát az egyes játékoskeretekből.

import requestsimport pandas as pdleague_id = 123456season = 2018slotcodes = { 0 : 'QB', 2 : 'RB', 4 : 'WR', 6 : 'TE', 16: 'Def', 17: 'K', 20: 'Bench', 21: 'IR', 23: 'Flex'}url = 'https://fantasy.espn.com/apis/v3/games/ffl/seasons/' + \ str(season) + '/segments/0/leagues/' + str(league_id) + \ '?view=mMatchup&view=mMatchupScore'data = print('Week ', end='')for week in range(1, 17): print(week, end=' ') r = requests.get(url, params={'scoringPeriodId': week}, cookies={"SWID": swid, "espn_s2": espn}) d = r.json() for tm in d: tmid = tm for p in tm: name = p slot = p pos = slotcodes # injured status (need try/exc bc of D/ST) inj = 'NA' try: inj = p except: pass # projected/actual points proj, act = None, None for stat in p: if stat != week: continue if stat == 0: act = stat elif stat == 1: proj = stat data.append()print('\nComplete.')data = pd.DataFrame(data, columns=)
Week 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Complete.

Valami ilyesmit kapunk:

data.head()
 Week Team Player Slot Pos Status Proj Actual0 1 1 Leonard Fournette 2 RB QUESTIONABLE 13.891825 5.01 1 1 Christian McCaffrey 2 RB ACTIVE 11.067053 7.02 1 1 Derrick Henry 20 Bench ACTIVE 10.271163 2.03 1 1 Josh Gordon 4 WR OUT 6.153141 7.04 1 1 Philip Rivers 0 QB QUESTIONABLE 26.212294 42.0

Igen, igen, ez csak a játékoskeretben szereplő játékosokat tartalmazza, tehát nem rögzítjük a szabadügynököket … de legalább egyelőre érzékeltetni kell az ESPN előrejelzéseinek pontosságát.

Vetítsük össze a “Proj”-t az “Actual”-nal néhány különböző pozícióra, és szorítsunk …

fig, axs = plt.subplots(1,3, sharey=True, figsize=(12, 4))for i, pos in enumerate(): (data .query('Pos == @pos') .pipe((axs.scatter, 'data'), x='Proj', y='Actual', s=50, c='b', alpha=0.5) ) axs.plot(, , 'k--') axs.set(xlim=, ylim=, xlabel='Projected', title=pos)axs.set_ylabel('Actual')plt.tight_layout()plt.show()

Um, nem nagyszerű. Csinálhatnánk itt néhány statisztikai tesztet, de az én képzetlen szememnek úgy tűnik, hogy ezek a vetített pontok akár egy egyenletes eloszlásból is származhatnának.

Meglehet, hogy bizonyos heteken jobb? Talán a szezon későbbi szakaszában? Ábrázoljuk a teljes hibát, hetente, pozíciónként. Ez a kód egy kicsit haknissá válik 🙁 de én kész vagyok együtt élni vele:

fig, axs = plt.subplots(1,3, sharey=True, figsize=(13,4))data = data - datadata = pd.cut(data, bins=4, labels=)cols = sns.color_palette('Blues')# dummy plots for a legendfor k,cat in enumerate(): axs.plot(,, c=cols, label='Wk ' + cat)axs.legend()for i, pos in enumerate(): for cat in range(4): t = data.query('Pos == @pos & Cat == @cat') sns.kdeplot(t, color=cols, ax=axs, legend=False) axs.set(xlabel='Actual - Proj', title=pos) plt.show()

Még lehet, hogy a szezon későbbi szakaszában van egy tendencia a túlprojekciózásra, de összességében itt sem mondanék semmit. (Megjegyzendő, hogy ezzel elvesztettünk némi információt arról, hogy a hiba nagyobb vagy kisebb a magas vs. alacsony előrejelzéseknél.)

A játékosok pontjainak idősorát szeretném legközelebb megvizsgálni. A józan ész szerint a legjobb módja annak, hogy megtippeljük, hogy egy játékos milyen pontokat fog szerezni a következő héten, ha csak szemezünk az utolsó 4-5 hetével … mennyire megbízható stratégia ez?

Ebben a posztban megpróbálom ellenőrizni az ESPN előrejelzéseit a játékoskeret szintjén – lehet, hogy az egyéni előrejelzések nem lenyűgözőek, de összesítve varázslatos módon kezdenek segíteni? (Spoiler: nem igazán.)

Írta: Steven Morse, 2019. augusztus 5-én.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.