Die Programmiersprache Python ist eine hervorragende Wahl für Data Science und Predictive Analytics. Sie bringt zahlreiche Pakete mit, die die meisten Anforderungen deiner Datenanalyse abdecken. Für Machine Learning in Python ist Scikit-Learn (sklearn) eine sehr gute Option. Es basiert auf NumPy, SciPy und Matplotlib (für N-dimensionale Arrays, wissenschaftliches Rechnen und Datenvisualisierung).
In diesem Tutorial lädst du Daten mit sqlite3 aus einer Datenbank, explorierst sie und verbesserst ihre Qualität mit pandas und matplotlib, und nutzt anschließend Scikit-Learn, um belastbare Erkenntnisse aus den Daten zu gewinnen.
In diesem Projekt testest du mehrere Machine-Learning-Modelle aus sklearn, um die Anzahl der Spiele vorherzusagen, die ein Major-League-Baseball-Team in einer Saison gewinnt – basierend auf Teamstatistiken und weiteren Variablen dieser Saison. Wäre ich ein Wettfreund (und das bin ich durchaus), könnte ich ein Modell mit historischen Daten aus früheren Saisons bauen, um die nächste Saison zu prognostizieren. Aufgrund der Zeitreihen-Natur der Daten ließen sich etwa Indikatoren wie durchschnittliche Siege der letzten fünf Jahre bilden, um das Modell weiter zu schärfen. Das liegt hier jedoch außerhalb des Umfangs: Du behandelst jede Zeile unabhängig. Jede Zeile repräsentiert ein bestimmtes Team in einem spezifischen Jahr.
Sean Lahman hat diese Daten auf seiner Website zusammengetragen; eine sqlite-Datenbank wurde daraus generiert.
Daten importieren
Du liest die Daten, indem du eine sqlite-Datenbank mit dem Paket sqlite3 abfragst und das Ergebnis mit pandas in ein DataFrame umwandelst. Die Daten werden so gefiltert, dass nur aktuell aktive, moderne Teams enthalten sind und nur Jahre, in denen das Team 150 oder mehr Spiele absolviert hat.
Lade zunächst die Datei „lahman2016.sqlite“ (hier) herunter. Lade dann Pandas und benenne es der Effizienz halber in pd um. Du erinnerst dich: pd ist der gängige Alias für Pandas. Lade schließlich sqlite3 und verbinde dich so mit der Datenbank:
# import`pandas` and `sqlite3`import pandas as pdimport sqlite3# Connecting to SQLite Databaseconn = sqlite3.connect('lahman2016.sqlite')Bereitgestellt von
Als Nächstes schreibst du eine Query, führst sie aus und holst die Ergebnisse ab.
# 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()Bereitgestellt von
Mit pandas wandelst du die Ergebnisse in ein DataFrame um und gibst mit der Methode head() die ersten 5 Zeilen aus:
# Convert `Teams` to DataFrame
teams_df = pd.DataFrame(_____)
# Print out first 5 rows
print(_______________)
Jede Spalte enthält Daten zu einem bestimmten Team und Jahr. Einige der wichtigsten Variablen sind unten aufgeführt. Eine vollständige Liste findest du hier.
yearID - Jahr
teamID - Team
franchID - Franchise (verweist auf die TeamsFranchise-Tabelle)
G - Spiele
W - Siege
LgWin - Ligameister (Y oder N)
WSWin - World-Series-Sieger (Y oder N)
R - Erzielt Punkte (Runs)
AB - At Bats (Schlagversuche)
H - Hits der Schlagleute
HR - Homeruns der Schlagleute
BB - Walks der Schlagleute
SO - Strikeouts der Schlagleute
SB - Stolen Bases
CS - Caught Stealing
HBP - Hit by Pitch
SF - Sacrifice Flies
RA - Gegners Runs
ER - Earned Runs zugelassen
ERA - Earned Run Average
CG - Complete Games
SHO - Shutouts
SV - Saves
IPOuts - Outs gepitcht (Innings × 3)
HA - Zugelassene Hits
HRA - Zugelassene Homeruns
BBA - Zugelassene Walks
SOA - Strikeouts der Pitcher
E - Errors
DP - Double Plays
FP - Fielding Percentage
name - Vollständiger Teamname
Falls du mit Baseball nicht so vertraut bist, hier eine kurze Erklärung des Spiels – inklusive einiger oben genannter Variablen.
Baseball wird zwischen zwei Teams mit jeweils neun Spielern gespielt (in den Daten findest du sie unter name oder teamID). Die Teams wechseln sich im Schlagrecht und in der Feldverteidigung ab. Das schlagende Team versucht, Runs zu erzielen, indem es den vom Pitcher des Feldteams geworfenen Ball schlägt und dann gegen den Uhrzeigersinn die vier Bases umläuft: First, Second, Third und Home Plate. Das Feldteam versucht, Runs zu verhindern, indem es Schlagmänner oder Baserunner auf verschiedene Arten out macht. Ein Run (R) wird erzielt, wenn ein Spieler alle Bases umläuft und zur Home Plate zurückkehrt. Wer sicher eine Base erreicht, versucht oft, bei den folgenden Schlägen weiter vorzurücken – z. B. durch einen Hit (H), eine Stolen Base (SB) oder andere Spielzüge.
Die Teams wechseln, sobald das Feldteam drei Outs erzielt. Je eine Schlagrunde beider Teams – beginnend mit dem Auswärtsteam – ergibt ein Inning. Ein Spiel besteht aus neun Innings; das Team mit mehr Runs gewinnt. Baseball hat keine Spieluhr: Endet das Spiel nach neun Innings unentschieden, geht es in Extra-Innings weiter, bis ein Team am Ende eines Extra-Innings führt.
Wie du oben siehst, hat das DataFrame keine Spaltenüberschriften. Du kannst Header hinzufügen, indem du dem Attribut columns von pandas eine Liste mit Spaltennamen übergibst.
# Adding column names to dataframe
cols = ['yearID','lgID','teamID','franchID','divID','Rank','G','Ghome','W','L','DivWin','WCWin','LgWin','WSWin','R','AB','H','2B','3B','HR','BB','SO','SB','CS','HBP','SF','RA','ER','ERA','CG','SHO','SV','IPouts','HA','HRA','BBA','SOA','E','DP','FP','name','park','attendance','BPF','PPF','teamIDBR','teamIDlahman45','teamIDretro','franchID','franchName','active','NAassoc']
teams_df.columns = ____
# Print the first rows of `teams_df`
print(______________)
# Print the length of `teams_df`
print(len(________))
Die Funktion len() zeigt dir, wie viele Zeilen vorliegen: 2.287 sind nicht allzu viele Datenpunkte – hoffentlich gibt es also nicht zu viele fehlende Werte.
Bevor du die Datenqualität bewertest, entferne zunächst Spalten, die unnötig sind oder direkt vom Ziel (Wins) abgeleitet werden. Hier zahlt sich Domänenwissen aus: Es ist egal, wie gut du in Code oder Statistik bist – ohne Verständnis für die Daten kommst du nicht weit. Mir hat meine lebenslange Baseball-Erfahrung hier sehr geholfen.
# Dropping your unnecesary column variables.
drop_cols = ['lgID','franchID','divID','Rank','Ghome','L','DivWin','WCWin','LgWin','WSWin','SF','name','park','attendance','BPF','PPF','teamIDBR','teamIDlahman45','teamIDretro','franchID','franchName','active','NAassoc']
df = teams_df.drop(drop_cols, axis=1)
# Print out first rows of `df`
print(_________)
Wie oben erwähnt, beeinträchtigen fehlende Werte die Datenqualität – und das kann wiederum zu Problemen bei ML-Algorithmen führen.
Deshalb entfernst du sie als Nächstes. Es gibt verschiedene Ansätze, aber sinnvoll ist, zunächst die Anzahl fehlender Werte pro Spalte auszugeben, um dann gezielt zu entscheiden.
# Print out null values of all columns of `df`
print(_________.sum(axis=0).tolist())
Hier zeigt sich der Trade-off: Du brauchst saubere Daten, aber die Datenmenge ist begrenzt. Zwei Spalten haben relativ wenige fehlende Werte: 110 in SO (Strikeouts) und 22 in DP (Double Plays). Zwei Spalten haben sehr viele: 419 in CS (Caught Stealing) und 1777 in HBP (Hit by Pitch).
Wenn du Zeilen mit wenigen fehlenden Werten entfernst, verlierst du etwas über fünf Prozent der Daten. Da du Siege vorhersagen willst, sind erzielte und zugelassene Runs stark mit dem Ziel korreliert – diese Spalten sollten möglichst akkurat sein.
Strikeouts (SO) und Double Plays (DP) sind weniger entscheidend.
Es ist sinnvoller, die Zeilen zu behalten und fehlende Werte spaltenweise mit dem Median zu füllen – mit fillna(). CS und HBP sind ebenfalls nicht sonderlich wichtig. Bei so vielen fehlenden Werten ist es am besten, diese Spalten komplett zu entfernen.
# Eliminating columns with null values
df = df.drop(['CS','HBP'], axis=1)
# Filling null values
df['SO'] = df['SO'].fillna(df['SO'].median())
df['DP'] = df['DP'].fillna(df['DP'].median())
# Print out null values of all columns of `df`
print(df.isnull().sum(axis=0).tolist())
Daten erkunden und visualisieren
Nachdem du die Daten bereinigt hast, kannst du sie explorieren. Ein paar einfache Visualisierungen geben dir schnell ein Gefühl für den Datensatz. matplotlib ist dafür bestens geeignet.
Importiere matplotlib.pyplot und benenne es in plt um. Wenn du mit einem Jupyter-Notebook arbeitest, nutze das Magic %matplotlib inline.
Starte mit einem Histogramm der Zielspalte, um die Verteilung der Siege zu sehen.
# 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()Bereitgestellt von
Hinweis: Wenn du kein Jupyter-Notebook verwendest, musst du plt.show() aufrufen, um die Plots anzuzeigen.
Gib die durchschnittlichen Siege (W) pro Jahr aus. Nutze dafür mean().
print(df['_'].______)
Beim Explorieren kann es hilfreich sein, Bins für die Zielspalte zu definieren. Wichtig: Solche aus dem Ziel abgeleiteten Features dürfen später nicht ins Training einfließen – das wäre, als würdest du dem Modell die Antworten vorab geben.
Für die Gewinn-Labels (win_bins) erstellst du eine Funktion assign_win_bins, die eine ganze Zahl (Siege) entgegennimmt und abhängig vom Wert eine Zahl von 1–5 zurückgibt.
Dann erzeugst du die neue Spalte win_bins, indem du apply() auf der Siegspalte aufrufst und die Funktion assign_win_bins() übergibst.
# Creating bins for the win column
def assign_win_bins(_):
if W < 50:
return 1
if W >= 50 and W <= 69:
return 2
if W >= 70 and W <= 89:
return 3
if W >= 90 and W <= 109:
return 4
if W >= 110:
return 5
# Apply `assign_win_bins` to `df['W']`
df['win_bins'] = df['_'].apply(______________)
Erstelle nun ein Scatter-Plot mit dem Jahr auf der x-Achse und den Siegen auf der y-Achse und hebe die win_bins-Spalte farblich hervor.
# 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()Bereitgestellt von
Wie du siehst, gibt es vor 1900 nur wenige Saisons, zudem war das Spiel damals deutlich anders. Es ergibt Sinn, diese Zeilen aus dem Datensatz zu entfernen.
# Filter for rows where 'yearID' is greater than 1900
df = df[___________________]
Bei stetigen Variablen in linearen Modellen können ganzzahlige Werte wie ein Jahr Probleme machen. Es ist unwahrscheinlich, dass „1950“ dieselbe Beziehung im Modell repräsentiert, wie man intuitiv annehmen würde.
Um das zu umgehen, kannst du neue Variablen erstellen, die Daten basierend auf yearID labeln.
Wer MLB verfolgt, weiß: Im Lauf der Zeit gab es Epochen mit deutlich unterschiedlichen durchschnittlichen Runs pro Spiel. Die „Dead-Ball“-Ära Anfang des 20. Jahrhunderts war z. B. niedrigpunktreich; die Steroid-Ära um die Jahrtausendwende hingegen sehr hochpunktreich.
Lass uns eine Grafik erstellen, die zeigt, wie viel in jedem Jahr gescort wurde.
Beginne mit den Dictionaries runs_per_year und games_per_year. Iteriere mit iterrows() über das DataFrame. Fülle runs_per_year mit Jahren als Keys und der Anzahl der in diesem Jahr erzielten Runs. Fülle games_per_year mit Jahren als Keys und der Anzahl der in diesem Jahr gespielten Spiele.
# Create runs per year and games per year dictionaries
runs_per_year = {}
games_per_year = {}
for i, row in df.iterrows():
year = row['yearID']
runs = row['R']
games = row['G']
if year in runs_per_year:
runs_per_year[year] = runs_per_year[year] + runs
games_per_year[year] = games_per_year[year] + games
else:
runs_per_year[year] = runs
games_per_year[year] = games
print(runs_per_year)
print(games_per_year)
Als Nächstes erstellst du das Dictionary mlb_runs_per_game. Iteriere mit items() über games_per_year. Befülle mlb_runs_per_game mit Jahren als Keys und der ligaweiten Zahl der Runs pro Spiel als Wert.
# Create MLB runs per game (per year) dictionary
mlb_runs_per_game = {}
for k, v in games_per_year.items():
year = k
games = v
runs = runs_per_year[year]
mlb_runs_per_game[year] = runs / games
print(mlb_runs_per_game)
Jetzt erstellst du den Plot aus mlb_runs_per_game, mit den Jahren auf der x-Achse und den Runs pro Spiel auf der y-Achse.
# 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()Bereitgestellt von
Neue Features hinzufügen
Mit dem besseren Verständnis der Scoring-Trends kannst du nun Variablen erstellen, die die jeweilige Ära jeder Zeile anhand von yearID kennzeichnen. Du gehst ähnlich vor wie bei der Spalte win_bins.
Diesmal erzeugst du jedoch Dummy-Spalten – eine neue Spalte pro Ära. Dafür eignet sich get_dummies().
# Creating "year_label" column, which will give your algorithm information about how certain years are related
# (Dead ball eras, Live ball/Steroid Eras)
def assign_label(____):
if year < 1920:
return 1
elif year >= 1920 and year <= 1941:
return 2
elif year >= 1942 and year <= 1945:
return 3
elif year >= 1946 and year <= 1962:
return 4
elif year >= 1963 and year <= 1976:
return 5
elif year >= 1977 and year <= 1992:
return 6
elif year >= 1993 and year <= 2009:
return 7
elif year >= 2010:
return 8
# Add `year_label` column to `df`
df['year_label'] = df['yearID'].apply(___________)
dummy_df = pd.get_dummies(df['year_label'], prefix='era')
# Concatenate `df` and `dummy_df`
df = pd.concat([df, dummy_df], axis=1)
print(df.head())
Da du bereits die Runs pro Spiel pro Jahr bestimmt hast, füge diese Daten dem Datensatz hinzu.
# Create column for MLB runs per game from the mlb_runs_per_game dictionary
def assign_mlb_rpg(year):
return mlb_runs_per_game[year]
df['mlb_rpg'] = df['yearID'].apply(assign_mlb_rpg)
Nun wandelst du die Jahre in Jahrzehnte um, indem du Dummy-Spalten pro Jahrzehnt erstellst. Danach kannst du nicht mehr benötigte Spalten droppen.
# Convert years into decade bins and creating dummy variables
def assign_decade(year):
if year < 1920:
return 1910
elif year >= 1920 and year <= 1929:
return 1920
elif year >= 1930 and year <= 1939:
return 1930
elif year >= 1940 and year <= 1949:
return 1940
elif year >= 1950 and year <= 1959:
return 1950
elif year >= 1960 and year <= 1969:
return 1960
elif year >= 1970 and year <= 1979:
return 1970
elif year >= 1980 and year <= 1989:
return 1980
elif year >= 1990 and year <= 1999:
return 1990
elif year >= 2000 and year <= 2009:
return 2000
elif year >= 2010:
return 2010
df['decade_label'] = df['yearID'].apply(assign_decade)
decade_df = pd.get_dummies(df['decade_label'], prefix='decade')
df = pd.concat([df, decade_df], axis=1)
# Drop unnecessary columns
df = df.drop(['yearID','year_label','decade_label'], axis=1)
Am Ende zählt im Baseball, wie viele Runs du erzielst und wie viele du zulässt. Du kannst die Modellgüte oft deutlich steigern, indem du Verhältnis-Features bildest. Runs pro Spiel und zugelassene Runs pro Spiel sind hervorragende Ergänzungen.
Mit Pandas ist das simpel: Erzeuge eine neue Spalte, indem du R durch G teilst – so entsteht R_per_game.
# Create new features for Runs per Game and Runs Allowed per Game
df['R_per_game'] = df['R'] / df['G']
df['RA_per_game'] = df['RA'] / df['G']
Untersuche nun in zwei Scatterplots, wie die beiden neuen Variablen mit den Siegen zusammenhängen: Ein Plot mit Runs pro Spiel auf der x-Achse, ein weiterer mit zugelassenen Runs pro Spiel. Auf der y-Achse jeweils W.
# Create scatter plots for runs per game vs. wins and runs allowed per game vs. winsfig = plt.figure(figsize=(12,6))ax1 = fig.add_subplot(1,2,1)ax2 = fig.add_subplot(1,2,2)ax1.scatter(df['R_per_game'], df['W'], c='blue')ax1.set_title('Runs per Game vs. Wins')ax1.set_ylabel('Wins')ax1.set_xlabel('Runs per Game')ax2.scatter(df['RA_per_game'], df['W'], c='red')ax2.set_title('Runs Allowed per Game vs. Wins')ax2.set_xlabel('Runs Allowed per Game')plt.show()Bereitgestellt von
Bevor du Modelle trainierst, lohnt ein Blick auf die Korrelationen der Features mit dem Ziel. Mit Pandas geht das einfach über corr().
df.corr()['W']
Ein weiteres Feature, das du hinzufügen kannst, sind Labels aus einem K-Means-Clustering mit sklearn. K-Means ist ein einfaches Clustering-Verfahren, das die Daten anhand der vorgegebenen Anzahl k von Zentroiden partitioniert. Jeder Datenpunkt wird dem Cluster mit der geringsten euklidischen Distanz zum Zentroid zugewiesen.
Erstelle zunächst ein DataFrame ohne die Zielvariable:
attributes = ['G','R','AB','H','2B','3B','HR','BB','SO','SB','RA','ER','ERA','CG',
'SHO','SV','IPouts','HA','HRA','BBA','SOA','E','DP','FP','era_1','era_2','era_3','era_4','era_5','era_6','era_7','era_8','decade_1910','decade_1920','decade_1930','decade_1940','decade_1950','decade_1960','decade_1970','decade_1980','decade_1990','decade_2000','decade_2010','R_per_game','RA_per_game','mlb_rpg']
data_attributes = df[attributes]
# Print the first rows of `df`
print(df.head())
Bei K-Means musst du die Anzahl der Cluster festlegen. Eine Orientierung bietet silhouette_score() aus sklearn. Die Funktion gibt den mittleren Silhouettenkoeffizienten über alle Stichproben zurück. Je höher, desto besser – der Wert sinkt meist, wenn du mehr Cluster hinzufügst.
# Import necessary modules from `sklearn`
from sklearn.cluster import KMeans
from sklearn import metrics
# Create silhouette score dictionary
s_score_dict = {}
for i in range(2,11):
km = KMeans(n_clusters=i, random_state=1)
l = km.fit_predict(data_attributes)
s_s = metrics.silhouette_score(data_attributes, l)
s_score_dict[i] = [s_s]
# Print out `s_score_dict`
print(s_score_dict)
Initialisiere nun das Modell. Setze die Clusteranzahl auf 6 und den Random State auf 1. Bestimme die euklidischen Distanzen jedes Datenpunkts mit fit_transform() und visualisiere die Cluster in einem Scatterplot.
# Create K-means model and determine euclidian distances for each data pointkmeans_model =KMeans(n_clusters=6, random_state=1)distances = kmeans_model.fit_transform(data_attributes)# Create scatter plot using labels fromK-means model as colorlabels = kmeans_model.labels_plt.scatter(distances[:,0], distances[:,1], c=labels)plt.title('Kmeans Clusters')plt.show()Bereitgestellt von
Füge nun die Cluster-Labels als neue Spalte in den Datensatz ein. Ergänze außerdem den String „labels“ in der Liste attributes für die spätere Nutzung.
# Add labels from K-means model to `df` DataFrame and attributes list
df['labels'] = labels
attributes.append('labels')
# Print the first rows of `df`
print(_________)
Bevor du ein Modell baust, teilst du die Daten in Trainings- und Test-Set. Trainierst und testest du auf denselben Daten, droht Overfitting: Das Modell „merkt“ sich die Daten statt zu generalisieren – und performt bei neuen Daten schlecht.
Keine Sorge: Es gibt verschiedene Möglichkeiten zur Kreuzvalidierung.
Hier ziehst du einfach eine Zufallsstichprobe von 75 Prozent als train und nutzt die restlichen 25 Prozent als test. Erstelle eine Liste numeric_cols mit allen Spalten, die dein Modell verwenden soll. Erzeuge dann aus df ein neues DataFrame data mit genau diesen Spalten. Anschließend erstellst du train und test, indem du data entsprechend samplest.
# Create new DataFrame using only variables to be included in models
numeric_cols = ['G','R','AB','H','2B','3B','HR','BB','SO','SB','RA','ER','ERA','CG','SHO','SV','IPouts','HA','HRA','BBA','SOA','E','DP','FP','era_1','era_2','era_3','era_4','era_5','era_6','era_7','era_8','decade_1910','decade_1920','decade_1930','decade_1940','decade_1950','decade_1960','decade_1970','decade_1980','decade_1990','decade_2000','decade_2010','R_per_game','RA_per_game','mlb_rpg','labels','W']
data = df[numeric_cols]
print(data.head())
# Split data DataFrame into train and test sets
train = data.sample(frac=0.75, random_state=1)
test = data.loc[~data.index.isin(train.index)]
x_train = train[attributes]
y_train = train['W']
x_test = test[attributes]
y_test = test['W']
Fehlermetrik und Modell auswählen
Als Metrik nutzt du den Mean Absolute Error (MAE), um die Genauigkeit deines Modells zu bewerten. Er misst, wie nah die Vorhersagen an den tatsächlichen Ergebnissen liegen. Konkret bedeutet das hier: Der MAE gibt den durchschnittlichen absoluten Abstand deiner Vorhersage zum wahren Wert in Siegen an.
Wenn deine Vorhersagen im Schnitt um 5 Siege danebenliegen, beträgt der Fehler 5.
Als erstes trainierst du ein lineares Regressionsmodell. Importiere LinearRegression bzw. mean_absolute_error aus sklearn.linear_model und sklearn.metrics, erstelle das Modell lr, fitte es, erzeuge Vorhersagen und bestimme den MAE.
# Import `LinearRegression` from `sklearn.linear_model`
from sklearn.linear_model import ___________________
# Import `mean_absolute_error` from `sklearn.metrics`
from sklearn.metrics import mean_absolute_error
# Create Linear Regression model, fit model, and make predictions
lr = LinearRegression(normalize=True)
lr.fit(x_train, y_train)
predictions = lr.predict(x_test)
# Determine mean absolute error
mae = mean_absolute_error(y_test, predictions)
# Print `mae`
print(___)
Zur Einordnung: Der Durchschnitt lag bei etwa 79 Siegen. Das Modell liegt im Schnitt nur um 2,687 Siege daneben.
Probiere jetzt Ridge-Regression. Importiere RidgeCV aus sklearn.linear_model und erstelle rrm. RidgeCV erlaubt dir, den Alpha-Parameter zu setzen, der die Komplexität steuert (Shrinkage, mehr dazu hier). Das Modell wählt per Cross-Validation den besten Alpha-Wert aus deinen Vorgaben.
Anschließend wieder: fitten, vorhersagen, MAE bestimmen.
# Import `RidgeCV` from `sklearn.linear_model`
from sklearn.linear_model import _______
# Create Ridge Linear Regression model, fit model, and make predictions
rrm = RidgeCV(alphas=(0.01, 0.1, 1.0, 10.0), normalize=True)
rrm.fit(x_train, y_train)
predictions_rrm = rrm.predict(x_test)
# Determine mean absolute error
mae_rrm = mean_absolute_error(y_test, predictions_rrm)
print(________)
Dieses Modell ist minimal besser und liegt im Schnitt um 2,673 Siege daneben.
Sports Analytics & Scikit-Learn
Damit endet der erste Teil dieser Tutorial-Reihe. Du hast gesehen, wie du mit scikit-learn Sportdaten analysieren kannst: Daten aus einer SQLite-Datenbank importieren, bereinigen, visuell untersuchen und neue Features konstruieren. Außerdem hast du ein K-Means-Clustering, zwei Lineare Regressionsmodelle trainiert und deine Prognosen mit dem Mean Absolute Error bewertet.
Im zweiten Teil lernst du, mit Klassifikationsmodellen vorherzusagen, welche Spieler in die MLB Hall of Fame aufgenommen werden.
Gewinne Vertrauen in die OLS-Regression, indem du ihre theoretischen Grundlagen beherrschst. Erforsche, wie du einfache Implementierungen in Excel, R und Python durchführst.
Erforsche Pythons match-case: eine Anleitung zu seiner Syntax, Anwendungen in Data Science und ML sowie eine vergleichende Analyse mit dem traditionellen switch-case.
Lerne, wie du Python-Listen mit Techniken wie Slicing, List Comprehensions und itertools aufteilen kannst. Finde heraus, wann du welche Methode für die beste Datenverarbeitung nutzen solltest.
Mach dich mit Python-Datenstrukturen vertraut: Lerne mehr über Datentypen und primitive sowie nicht-primitive Datenstrukturen wie Strings, Listen, Stapel usw.
Ein umfassendes Einführungs-Tutorial zu Python-Schleifen. Lerne und übe while- und for-Schleifen, verschachtelte Schleifen, die Schlüsselwörter break und continue, die Range-Funktion und vieles mehr!
Die Programmiersprache Python ist eine hervorragende Wahl für Data Science und Predictive Analytics. Sie bringt zahlreiche Pakete mit, die die meisten Anforderungen deiner Datenanalyse abdecken. Für Machine Learning in Python ist Scikit-Learn (
sklearn) eine sehr gute Option. Es basiert auf NumPy, SciPy und Matplotlib (für N-dimensionale Arrays, wissenschaftliches Rechnen und Datenvisualisierung).In diesem Tutorial lädst du Daten mit
sqlite3aus einer Datenbank, explorierst sie und verbesserst ihre Qualität mitpandasundmatplotlib, und nutzt anschließend Scikit-Learn, um belastbare Erkenntnisse aus den Daten zu gewinnen.Wenn du einen Kurs zu Machine Learning machen möchtest, schau dir den DataCamp-Kurs Supervised Learning with scikit-learn an.
Teil 1: MLB-Team-Siege pro Saison vorhersagen
In diesem Projekt testest du mehrere Machine-Learning-Modelle aus
sklearn, um die Anzahl der Spiele vorherzusagen, die ein Major-League-Baseball-Team in einer Saison gewinnt – basierend auf Teamstatistiken und weiteren Variablen dieser Saison. Wäre ich ein Wettfreund (und das bin ich durchaus), könnte ich ein Modell mit historischen Daten aus früheren Saisons bauen, um die nächste Saison zu prognostizieren. Aufgrund der Zeitreihen-Natur der Daten ließen sich etwa Indikatoren wie durchschnittliche Siege der letzten fünf Jahre bilden, um das Modell weiter zu schärfen. Das liegt hier jedoch außerhalb des Umfangs: Du behandelst jede Zeile unabhängig. Jede Zeile repräsentiert ein bestimmtes Team in einem spezifischen Jahr.Sean Lahman hat diese Daten auf seiner Website zusammengetragen; eine sqlite-Datenbank wurde daraus generiert.
Daten importieren
Du liest die Daten, indem du eine sqlite-Datenbank mit dem Paket
sqlite3abfragst und das Ergebnis mitpandasin ein DataFrame umwandelst. Die Daten werden so gefiltert, dass nur aktuell aktive, moderne Teams enthalten sind und nur Jahre, in denen das Team 150 oder mehr Spiele absolviert hat.Lade zunächst die Datei „lahman2016.sqlite“ (hier) herunter. Lade dann
Pandasund benenne es der Effizienz halber inpdum. Du erinnerst dich:pdist der gängige Alias für Pandas. Lade schließlichsqlite3und verbinde dich so mit der Datenbank:Als Nächstes schreibst du eine Query, führst sie aus und holst die Ergebnisse ab.
Tipp: Wenn du mehr über SQL mit Python lernen möchtest, probiere den DataCamp-Kurs Introduction to Databases in Python.
Mit
pandaswandelst du die Ergebnisse in ein DataFrame um und gibst mit der Methodehead()die ersten 5 Zeilen aus:Jede Spalte enthält Daten zu einem bestimmten Team und Jahr. Einige der wichtigsten Variablen sind unten aufgeführt. Eine vollständige Liste findest du hier.
yearID- JahrteamID- TeamfranchID- Franchise (verweist auf die TeamsFranchise-Tabelle)G- SpieleW- SiegeLgWin- Ligameister (Y oder N)WSWin- World-Series-Sieger (Y oder N)R- Erzielt Punkte (Runs)AB- At Bats (Schlagversuche)H- Hits der SchlagleuteHR- Homeruns der SchlagleuteBB- Walks der SchlagleuteSO- Strikeouts der SchlagleuteSB- Stolen BasesCS- Caught StealingHBP- Hit by PitchSF- Sacrifice FliesRA- Gegners RunsER- Earned Runs zugelassenERA- Earned Run AverageCG- Complete GamesSHO- ShutoutsSV- SavesIPOuts- Outs gepitcht (Innings × 3)HA- Zugelassene HitsHRA- Zugelassene HomerunsBBA- Zugelassene WalksSOA- Strikeouts der PitcherE- ErrorsDP- Double PlaysFP- Fielding Percentagename- Vollständiger TeamnameFalls du mit Baseball nicht so vertraut bist, hier eine kurze Erklärung des Spiels – inklusive einiger oben genannter Variablen.
Baseball wird zwischen zwei Teams mit jeweils neun Spielern gespielt (in den Daten findest du sie unter
nameoderteamID). Die Teams wechseln sich im Schlagrecht und in der Feldverteidigung ab. Das schlagende Team versucht, Runs zu erzielen, indem es den vom Pitcher des Feldteams geworfenen Ball schlägt und dann gegen den Uhrzeigersinn die vier Bases umläuft: First, Second, Third und Home Plate. Das Feldteam versucht, Runs zu verhindern, indem es Schlagmänner oder Baserunner auf verschiedene Arten out macht. Ein Run (R) wird erzielt, wenn ein Spieler alle Bases umläuft und zur Home Plate zurückkehrt. Wer sicher eine Base erreicht, versucht oft, bei den folgenden Schlägen weiter vorzurücken – z. B. durch einen Hit (H), eine Stolen Base (SB) oder andere Spielzüge.Die Teams wechseln, sobald das Feldteam drei Outs erzielt. Je eine Schlagrunde beider Teams – beginnend mit dem Auswärtsteam – ergibt ein Inning. Ein Spiel besteht aus neun Innings; das Team mit mehr Runs gewinnt. Baseball hat keine Spieluhr: Endet das Spiel nach neun Innings unentschieden, geht es in Extra-Innings weiter, bis ein Team am Ende eines Extra-Innings führt.
Eine ausführliche Erklärung liefert das offizielle Regelwerk der Major League Baseball.
Daten bereinigen und vorbereiten
Wie du oben siehst, hat das DataFrame keine Spaltenüberschriften. Du kannst Header hinzufügen, indem du dem Attribut
columnsvonpandaseine Liste mit Spaltennamen übergibst.Die Funktion
len()zeigt dir, wie viele Zeilen vorliegen: 2.287 sind nicht allzu viele Datenpunkte – hoffentlich gibt es also nicht zu viele fehlende Werte.Bevor du die Datenqualität bewertest, entferne zunächst Spalten, die unnötig sind oder direkt vom Ziel (
Wins) abgeleitet werden. Hier zahlt sich Domänenwissen aus: Es ist egal, wie gut du in Code oder Statistik bist – ohne Verständnis für die Daten kommst du nicht weit. Mir hat meine lebenslange Baseball-Erfahrung hier sehr geholfen.Wie oben erwähnt, beeinträchtigen fehlende Werte die Datenqualität – und das kann wiederum zu Problemen bei ML-Algorithmen führen.
Deshalb entfernst du sie als Nächstes. Es gibt verschiedene Ansätze, aber sinnvoll ist, zunächst die Anzahl fehlender Werte pro Spalte auszugeben, um dann gezielt zu entscheiden.
Hier zeigt sich der Trade-off: Du brauchst saubere Daten, aber die Datenmenge ist begrenzt. Zwei Spalten haben relativ wenige fehlende Werte: 110 in
SO(Strikeouts) und 22 inDP(Double Plays). Zwei Spalten haben sehr viele: 419 inCS(Caught Stealing) und 1777 inHBP(Hit by Pitch).Wenn du Zeilen mit wenigen fehlenden Werten entfernst, verlierst du etwas über fünf Prozent der Daten. Da du Siege vorhersagen willst, sind erzielte und zugelassene Runs stark mit dem Ziel korreliert – diese Spalten sollten möglichst akkurat sein.
Strikeouts (
SO) und Double Plays (DP) sind weniger entscheidend.Es ist sinnvoller, die Zeilen zu behalten und fehlende Werte spaltenweise mit dem Median zu füllen – mit
fillna().CSundHBPsind ebenfalls nicht sonderlich wichtig. Bei so vielen fehlenden Werten ist es am besten, diese Spalten komplett zu entfernen.Daten erkunden und visualisieren
Nachdem du die Daten bereinigt hast, kannst du sie explorieren. Ein paar einfache Visualisierungen geben dir schnell ein Gefühl für den Datensatz.
matplotlibist dafür bestens geeignet.Importiere
matplotlib.pyplotund benenne es inpltum. Wenn du mit einem Jupyter-Notebook arbeitest, nutze das Magic%matplotlib inline.Starte mit einem Histogramm der Zielspalte, um die Verteilung der Siege zu sehen.
Hinweis: Wenn du kein Jupyter-Notebook verwendest, musst du
plt.show()aufrufen, um die Plots anzuzeigen.Gib die durchschnittlichen Siege (
W) pro Jahr aus. Nutze dafürmean().Beim Explorieren kann es hilfreich sein, Bins für die Zielspalte zu definieren. Wichtig: Solche aus dem Ziel abgeleiteten Features dürfen später nicht ins Training einfließen – das wäre, als würdest du dem Modell die Antworten vorab geben.
Für die Gewinn-Labels (
win_bins) erstellst du eine Funktionassign_win_bins, die eine ganze Zahl (Siege) entgegennimmt und abhängig vom Wert eine Zahl von 1–5 zurückgibt.Dann erzeugst du die neue Spalte
win_bins, indem duapply()auf der Siegspalte aufrufst und die Funktionassign_win_bins()übergibst.Erstelle nun ein Scatter-Plot mit dem Jahr auf der x-Achse und den Siegen auf der y-Achse und hebe die
win_bins-Spalte farblich hervor.Wie du siehst, gibt es vor 1900 nur wenige Saisons, zudem war das Spiel damals deutlich anders. Es ergibt Sinn, diese Zeilen aus dem Datensatz zu entfernen.
Bei stetigen Variablen in linearen Modellen können ganzzahlige Werte wie ein Jahr Probleme machen. Es ist unwahrscheinlich, dass „1950“ dieselbe Beziehung im Modell repräsentiert, wie man intuitiv annehmen würde.
Um das zu umgehen, kannst du neue Variablen erstellen, die Daten basierend auf
yearIDlabeln.Wer MLB verfolgt, weiß: Im Lauf der Zeit gab es Epochen mit deutlich unterschiedlichen durchschnittlichen Runs pro Spiel. Die „Dead-Ball“-Ära Anfang des 20. Jahrhunderts war z. B. niedrigpunktreich; die Steroid-Ära um die Jahrtausendwende hingegen sehr hochpunktreich.
Lass uns eine Grafik erstellen, die zeigt, wie viel in jedem Jahr gescort wurde.
Beginne mit den Dictionaries
runs_per_yearundgames_per_year. Iteriere mititerrows()über das DataFrame. Fülleruns_per_yearmit Jahren als Keys und der Anzahl der in diesem Jahr erzielten Runs. Füllegames_per_yearmit Jahren als Keys und der Anzahl der in diesem Jahr gespielten Spiele.Als Nächstes erstellst du das Dictionary
mlb_runs_per_game. Iteriere mititems()übergames_per_year. Befüllemlb_runs_per_gamemit Jahren als Keys und der ligaweiten Zahl der Runs pro Spiel als Wert.Jetzt erstellst du den Plot aus
mlb_runs_per_game, mit den Jahren auf der x-Achse und den Runs pro Spiel auf der y-Achse.Neue Features hinzufügen
Mit dem besseren Verständnis der Scoring-Trends kannst du nun Variablen erstellen, die die jeweilige Ära jeder Zeile anhand von
yearIDkennzeichnen. Du gehst ähnlich vor wie bei der Spaltewin_bins.Diesmal erzeugst du jedoch Dummy-Spalten – eine neue Spalte pro Ära. Dafür eignet sich
get_dummies().Da du bereits die Runs pro Spiel pro Jahr bestimmt hast, füge diese Daten dem Datensatz hinzu.
Nun wandelst du die Jahre in Jahrzehnte um, indem du Dummy-Spalten pro Jahrzehnt erstellst. Danach kannst du nicht mehr benötigte Spalten droppen.
Am Ende zählt im Baseball, wie viele Runs du erzielst und wie viele du zulässt. Du kannst die Modellgüte oft deutlich steigern, indem du Verhältnis-Features bildest. Runs pro Spiel und zugelassene Runs pro Spiel sind hervorragende Ergänzungen.
Mit
Pandasist das simpel: Erzeuge eine neue Spalte, indem duRdurchGteilst – so entstehtR_per_game.Untersuche nun in zwei Scatterplots, wie die beiden neuen Variablen mit den Siegen zusammenhängen: Ein Plot mit Runs pro Spiel auf der x-Achse, ein weiterer mit zugelassenen Runs pro Spiel. Auf der y-Achse jeweils
W.Bevor du Modelle trainierst, lohnt ein Blick auf die Korrelationen der Features mit dem Ziel. Mit
Pandasgeht das einfach übercorr().Ein weiteres Feature, das du hinzufügen kannst, sind Labels aus einem K-Means-Clustering mit
sklearn. K-Means ist ein einfaches Clustering-Verfahren, das die Daten anhand der vorgegebenen Anzahl k von Zentroiden partitioniert. Jeder Datenpunkt wird dem Cluster mit der geringsten euklidischen Distanz zum Zentroid zugewiesen.Mehr über K-Means-Clustering erfährst du hier.
Erstelle zunächst ein DataFrame ohne die Zielvariable:
Bei K-Means musst du die Anzahl der Cluster festlegen. Eine Orientierung bietet
silhouette_score()aus sklearn. Die Funktion gibt den mittleren Silhouettenkoeffizienten über alle Stichproben zurück. Je höher, desto besser – der Wert sinkt meist, wenn du mehr Cluster hinzufügst.Initialisiere nun das Modell. Setze die Clusteranzahl auf 6 und den Random State auf
1. Bestimme die euklidischen Distanzen jedes Datenpunkts mitfit_transform()und visualisiere die Cluster in einem Scatterplot.Füge nun die Cluster-Labels als neue Spalte in den Datensatz ein. Ergänze außerdem den String „labels“ in der Liste
attributesfür die spätere Nutzung.Bevor du ein Modell baust, teilst du die Daten in Trainings- und Test-Set. Trainierst und testest du auf denselben Daten, droht Overfitting: Das Modell „merkt“ sich die Daten statt zu generalisieren – und performt bei neuen Daten schlecht.
Keine Sorge: Es gibt verschiedene Möglichkeiten zur Kreuzvalidierung.
Hier ziehst du einfach eine Zufallsstichprobe von 75 Prozent als
trainund nutzt die restlichen 25 Prozent alstest. Erstelle eine Listenumeric_colsmit allen Spalten, die dein Modell verwenden soll. Erzeuge dann ausdfein neues DataFramedatamit genau diesen Spalten. Anschließend erstellst dutrainundtest, indem dudataentsprechend samplest.Fehlermetrik und Modell auswählen
Als Metrik nutzt du den Mean Absolute Error (MAE), um die Genauigkeit deines Modells zu bewerten. Er misst, wie nah die Vorhersagen an den tatsächlichen Ergebnissen liegen. Konkret bedeutet das hier: Der MAE gibt den durchschnittlichen absoluten Abstand deiner Vorhersage zum wahren Wert in Siegen an.
Wenn deine Vorhersagen im Schnitt um 5 Siege danebenliegen, beträgt der Fehler 5.
Als erstes trainierst du ein lineares Regressionsmodell. Importiere
LinearRegressionbzw.mean_absolute_erroraussklearn.linear_modelundsklearn.metrics, erstelle das Modelllr, fitte es, erzeuge Vorhersagen und bestimme den MAE.Zur Einordnung: Der Durchschnitt lag bei etwa 79 Siegen. Das Modell liegt im Schnitt nur um 2,687 Siege daneben.
Probiere jetzt Ridge-Regression. Importiere
RidgeCVaussklearn.linear_modelund erstellerrm.RidgeCVerlaubt dir, den Alpha-Parameter zu setzen, der die Komplexität steuert (Shrinkage, mehr dazu hier). Das Modell wählt per Cross-Validation den besten Alpha-Wert aus deinen Vorgaben.Anschließend wieder: fitten, vorhersagen, MAE bestimmen.
Dieses Modell ist minimal besser und liegt im Schnitt um 2,673 Siege daneben.
Sports Analytics & Scikit-Learn
Damit endet der erste Teil dieser Tutorial-Reihe. Du hast gesehen, wie du mit scikit-learn Sportdaten analysieren kannst: Daten aus einer SQLite-Datenbank importieren, bereinigen, visuell untersuchen und neue Features konstruieren. Außerdem hast du ein K-Means-Clustering, zwei Lineare Regressionsmodelle trainiert und deine Prognosen mit dem Mean Absolute Error bewertet.
Im zweiten Teil lernst du, mit Klassifikationsmodellen vorherzusagen, welche Spieler in die MLB Hall of Fame aufgenommen werden.