Cursus
De programmeertaal Python is een uitstekende optie voor data science en voorspellende analyses, omdat er meerdere pakketten beschikbaar zijn die het grootste deel van je data-analysebehoeften afdekken. Voor machine learning in Python is Scikit-learn (sklearn) een sterke keuze; het is gebouwd op NumPy, SciPy en Matplotlib (respectievelijk N-dimensionale arrays, wetenschappelijk rekenen en datavisualisatie).
In deze tutorial zie je hoe je eenvoudig data uit een database laadt met sqlite3, hoe je je data verkent en de kwaliteit verbetert met pandas en matplotlib, en hoe je daarna met het Scikit-Learn-pakket geldige inzichten uit je data haalt.
Wil je een cursus machine learning volgen? Bekijk dan DataCamp’s cursus Supervised Learning with scikit-learn.
Deel 1: MLB-teamovertuigingen per seizoen voorspellen
In dit project test je verschillende machinelearningmodellen uit sklearn om het aantal wedstrijden te voorspellen dat een Major League Baseball-team in een seizoen wint, op basis van teamstatistieken en andere variabelen uit dat seizoen. Als ik een gokker was (en dat ben ik zeker), zou ik een model kunnen bouwen met historische data uit voorgaande seizoenen om het volgende seizoen te voorspellen. Gezien de tijdreeksstructuur van de data zou je indicatoren kunnen genereren zoals gemiddelde overwinningen per jaar over de afgelopen vijf jaar, en andere factoren, om een zeer nauwkeurig model te maken. Dat valt echter buiten de scope van deze tutorial; je behandelt elke rij als onafhankelijk. Elke rij in onze data staat voor één team in een specifiek jaar.
Sean Lahman verzamelde deze data op zijn website en deze is omgezet naar een sqlite-database.
Data importeren
Je leest de data in door een sqlite-database te bevragen met het pakket sqlite3 en zet die met pandas om naar een DataFrame. Je data wordt gefilterd op alleen momenteel actieve, moderne teams en alleen jaren waarin het team 150 of meer wedstrijden speelde.
Download eerst het bestand “lahman2016.sqlite” (hier). Laad daarna Pandas en hernoem dit naar pd voor het gemak. Je herinnert je misschien dat pd de gangbare alias is voor Pandas. Laad tot slot sqlite3 en maak verbinding met de database, zoals dit:
# import `pandas` and `sqlite3`import pandas as pdimport sqlite3# Connecting to SQLite Databaseconn = sqlite3.connect('lahman2016.sqlite')Vervolgens schrijf je een query, voer je die uit en haal je de resultaten op.
# Querying Database for all seasons where a team played 150 or more games and is still active today. query = '''select * from Teams inner join TeamsFranchiseson Teams.franchID == TeamsFranchises.franchIDwhere Teams.G >= 150 and TeamsFranchises.active == 'Y';'''# Creating dataframe from query.Teams = conn.execute(query).fetchall()Tip: Wil je meer leren over het gebruik van SQL met Python, overweeg dan DataCamp’s cursus Introduction to Databases in Python.
Met pandas zet je de resultaten vervolgens om naar een DataFrame en print je de eerste 5 rijen met de head()-methode:
Elke kolom bevat data over een specifiek team en jaar. Enkele van de belangrijkere variabelen staan hieronder. Een volledige lijst vind je hier.
yearID- JaarteamID- TeamfranchID- Franchise (linkt naar de tabel TeamsFranchise)G- Gespeelde wedstrijdenW- OverwinningenLgWin- League-kampioen (Y of N)WSWin- World Series-winnaar (Y of N)R- Gemaakte runsAB- Slagbeurten (at bats)H- Hits door slagmensenHR- Homeruns door slagmensenBB- Vier wijd door slagmensenSO- Drie slag door slagmensenSB- Gestolen honkenCS- Uitgegleden bij stelen (caught stealing)HBP- Raak gegooid (hit by pitch)SF- Sacrifice fliesRA- Tegenstanders: gescoorde runsER- Earned runs tegenERA- Earned run averageCG- Complete gamesSHO- ShutoutsSV- SavesIPOuts- Outs gegooid (innings pitched x 3)HA- Toegestane hitsHRA- Toegestane homerunsBBA- Toegestane vier wijdSOA- Drie slag door pitchersE- FoutenDP- DubbelspelenFP- Fielding-percentagename- Volledige teamnaam
Voor wie minder bekend is met honkbal volgt hier kort hoe het spel werkt, met enkele variabelen erbij.
Honkbal wordt gespeeld tussen twee teams (in de data terug te vinden als name of teamID) van elk negen spelers. De teams wisselen af tussen slaan en fielden. Het slagteam probeert runs te scoren door om beurten een door de pitcher van het fieldende team geworpen bal te slaan, en vervolgens tegen de klok in langs vier honken te rennen: eerste, tweede, derde en thuisplaat. Het fieldende team probeert runs te voorkomen door slagmensen of honklopers op verschillende manieren uit te maken; een run (R) wordt gescoord wanneer een speler alle honken passeert en terugkeert bij thuisplaat. Een speler van het slagteam die veilig een honk bereikt, probeert tijdens de beurten van teamgenoten verder op te schuiven, bijvoorbeeld op een hit (H), gestolen honk (SB) of op andere manieren.

De teams wisselen van slag- en veldbeurt zodra het fieldende team drie nullen heeft gemaakt. Eén slagbeurt voor beide teams, te beginnen met het uitspelende team, vormt een inning. Een wedstrijd bestaat uit negen innings, en het team met de meeste runs aan het einde wint. Er is geen spelklok; meestal eindigen wedstrijden in de negende inning, maar bij een gelijke stand na negen innings volgen extra innings en gaat het door totdat een team na een extra inning vóór staat.
Voor een uitgebreide uitleg van honkbal, bekijk de officiële regels van Major League Baseball.
De data opschonen en voorbereiden
Zoals je hierboven ziet, heeft het DataFrame geen kolomkoppen. Je kunt koppen toevoegen door een lijst met koppen door te geven aan het attribuut columns van pandas.
De functie len() laat zien met hoeveel rijen je werkt: 2.287 is niet heel veel, hopelijk zijn er dus niet te veel null-waarden.
Voordat je de datakwaliteit beoordeelt, halen we eerst kolommen weg die niet nodig zijn of die zijn afgeleid van de doelkolom (Wins). Kennis van de data waar je mee werkt wordt hier heel waardevol. Het maakt weinig uit hoeveel je weet van coderen of statistiek als je niets weet over je data. Een leven lang honkbalfan zijn hielp me zeker bij dit project.
Zoals je hierboven las, beïnvloeden null-waarden de datakwaliteit, wat op zijn beurt problemen kan geven voor machinelearningalgoritmen.
Daarom haal je die nu weg. Er zijn meerdere manieren om null-waarden te verwijderen, maar het is beter om eerst het aantal null-waarden per kolom te tonen, zodat je kunt beslissen hoe je ze het best behandelt.
Hier zie je de afweging: je wilt schone data, maar je hebt ook niet overdreven veel data te verliezen. Twee kolommen hebben relatief weinig null-waarden. Er zijn 110 null-waarden in de kolom SO (strikeouts) en 22 in DP (double play). Twee kolommen hebben er juist veel. Er zijn 419 null-waarden in CS (caught stealing) en 1777 in HBP (hit by pitch).
Als je de rijen weglaat waarin kolommen met weinig null-waarden voorkomen, verlies je iets meer dan vijf procent van onze data. Omdat je overwinningen wilt voorspellen, zijn gemaakte runs en toegestane runs sterk gecorreleerd met het doel. Je wilt dat de data in die kolommen zeer nauwkeurig is.
Strikeouts (SO) en double plays (DP) zijn minder belangrijk.
Ik denk dat je beter de rijen behoudt en de null-waarden opvult met de mediaan van elke kolom met de methode fillna(). Caught stealing (CS) en hit by pitch (HBP) zijn ook niet erg belangrijke variabelen. Met zoveel null-waarden in deze kolommen kun je ze het best geheel verwijderen.
De data verkennen en visualiseren
Nu je data is opgeschoond, kun je verkennen. Met een paar eenvoudige visualisaties krijg je gevoel voor de dataset. matplotlib is een uitstekende bibliotheek voor datavisualisatie.
Je importeert matplotlib.pyplot en hernoemt dit naar plt voor het gemak. Werk je in een Jupyter-notebook, gebruik dan de magic %matplotlib inline.
Begin met een histogram van de doelkolom zodat je de verdeling van overwinningen ziet.
# import the pyplot module from matplotlibimport matplotlib.pyplot as plt# matplotlib plots inline %matplotlib inline# Plotting distribution of winsplt.hist(df['W'])plt.xlabel('Wins')plt.title('Distribution of Wins')plt.show()
Let op: als je geen Jupyter-notebook gebruikt, moet je plt.show() aanroepen om de plots te tonen.
Print het gemiddelde aantal overwinningen (W) per jaar. Gebruik hiervoor de methode mean().
Het kan nuttig zijn om tijdens de verkenning bins te maken voor je doelkolom, maar zorg dat je geen kenmerk dat je uit de doelkolom afleidt, meeneemt in het trainen van je model. Zo’n kolom labels uit de doelkolom opnemen in je trainingsset is alsof je je model de antwoorden geeft.
Voor de winlabels maak je een functie assign_win_bins die een geheel getal (wins) inneemt en een integer van 1-5 teruggeeft, afhankelijk van de invoerwaarde.
Vervolgens maak je een nieuwe kolom win_bins door de apply()-methode op de wins-kolom te gebruiken en de functie assign_win_bins() door te geven.
Maak nu een scatterplot met het jaar op de x-as en overwinningen op de y-as en kleur de win_bins-kolom.
# Plotting scatter graph of Year vs. Winsplt.scatter(df['yearID'], df['W'], c=df['win_bins'])plt.title('Wins Scatter Plot')plt.xlabel('Year')plt.ylabel('Wins')plt.show()
Zoals je in de scatterplot ziet, zijn er heel weinig seizoenen van vóór 1900 en toen was het spel ook heel anders. Daarom is het logisch om die rijen uit de dataset te halen.
Bij continue data en lineaire modellen kunnen integerwaarden zoals een jaar voor problemen zorgen. Het is onwaarschijnlijk dat het getal 1950 dezelfde relatie met de rest van de data heeft als het model veronderstelt.
Dat kun je vermijden door nieuwe variabelen te maken die de data labelen op basis van de waarde yearID.
Iedereen die MLB volgt, weet dat er door de jaren heen verschillende tijdperken (era’s) waren met veel of juist weinig runs per wedstrijd. De dead-ball era begin 1900 is een laag-scorende periode; de steroid era rond de eeuwwisseling een hoog-scorende.
Laten we hieronder een grafiek maken die laat zien hoeveel er per jaar gescoord werd.
Begin met de dictionaries runs_per_year en games_per_year. Loop door het dataframe met de methode iterrows(). Vul runs_per_year met jaren als sleutel en het aantal gescoorde runs in dat jaar als waarde. Vul games_per_year met jaren als sleutel en het aantal gespeelde wedstrijden in dat jaar als waarde.
Maak vervolgens de dictionary mlb_runs_per_game. Itereer door games_per_year met de methode items(). Vul mlb_runs_per_game met jaren als sleutel en het aantal runs per wedstrijd, voor de hele league, als waarde.
Maak tot slot je plot uit de dictionary mlb_runs_per_game met jaren op de x-as en runs per wedstrijd op de y-as.
# Create lists from mlb_runs_per_game dictionarylists = sorted(mlb_runs_per_game.items())x, y = zip(*lists)# Create line plot of Year vs. MLB runs per Gameplt.plot(x, y)plt.title('MLB Yearly Runs per Game')plt.xlabel('Year')plt.ylabel('MLB Runs per Game')plt.show()
Nieuwe features toevoegen
Nu je beter zicht hebt op scoretrends, kun je nieuwe variabelen maken die de era aangeven waarin elke rij op basis van yearID valt. Je volgt hetzelfde proces als bij de kolom win_bins.
Dit keer maak je echter dummy-kolommen; één nieuwe kolom per era. Gebruik hiervoor de methode get_dummies().
Omdat je al hebt bepaald hoeveel MLB-runs per wedstrijd er per jaar waren, voeg je die data toe aan de dataset.
Nu zet je de jaren om naar decennia door dummy-kolommen per decennium te maken. Daarna kun je kolommen die je niet meer nodig hebt, droppen.
