Corso
Il linguaggio di programmazione Python è un'ottima scelta per data science e analisi predittiva, perché offre diversi pacchetti che coprono la maggior parte delle esigenze di analisi dei dati. Per il machine learning in Python, Scikit-learn (sklearn) è un'ottima opzione ed è costruito su NumPy, SciPy e Matplotlib (rispettivamente array N-dimensionali, calcolo scientifico e visualizzazione dei dati).
In questo tutorial vedrai come caricare facilmente i dati da un database con sqlite3, come esplorare i dati e migliorarne la qualità con pandas e matplotlib, e come utilizzare poi il pacchetto Scikit-Learn per ricavare insight validi dai tuoi dati.
Se vuoi seguire un corso di machine learning, dai un'occhiata al corso di DataCamp Supervised Learning with scikit-learn.
Parte 1: prevedere le vittorie per stagione delle squadre MLB
In questo progetto testerai diversi modelli di machine learning di sklearn per prevedere il numero di partite vinte in una stagione da una squadra della Major League Baseball, in base alle statistiche delle squadre e ad altre variabili di quella stagione. Se fossi un giocatore d'azzardo (e lo sono senz'altro), potrei creare un modello usando i dati storici delle stagioni precedenti per prevedere quella successiva. Dati la natura di serie temporale dei dati, potresti generare indicatori come la media di vittorie per anno negli ultimi cinque anni, e altri fattori simili, per ottenere un modello molto accurato. Questo però è fuori dallo scopo di questo tutorial, quindi tratterai ogni riga come indipendente. Ogni riga dei nostri dati corrisponderà a una singola squadra in uno specifico anno.
Sean Lahman ha raccolto questi dati sul suo sito web e sono stati trasformati in un database sqlite qui.
Importazione dei dati
Leggerai i dati interrogando un database sqlite usando il pacchetto sqlite3 e convertendo poi il risultato in un DataFrame con pandas. I dati verranno filtrati per includere solo le squadre moderne attualmente attive e solo gli anni in cui la squadra ha giocato 150 o più partite.
Per prima cosa, scarica il file "lahman2016.sqlite" (qui). Poi carica Pandas e rinominalo in pd per praticità. Forse ricordi che pd è l'alias comune per Pandas. Infine, carica sqlite3 e connettiti al database, così:
# import `pandas` and `sqlite3`import pandas as pdimport sqlite3# Connecting to SQLite Databaseconn = sqlite3.connect('lahman2016.sqlite')Successivamente, scrivi una query, eseguila e recupera i risultati.
# 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()Consiglio: se vuoi saperne di più sull'uso di SQL con Python, valuta di seguire il corso di DataCamp Introduction to Databases in Python.
Usando pandas, converti quindi i risultati in un DataFrame e stampa le prime 5 righe con il metodo head():
Ognuna delle colonne contiene dati relativi a una specifica squadra e a un anno. Di seguito trovi alcune delle variabili più importanti. L'elenco completo delle variabili è disponibile qui.
yearID- AnnoteamID- SquadrafranchID- Franchigia (collega alla tabella TeamsFranchise)G- Partite giocateW- VittorieLgWin- Campione di lega (Y o N)WSWin- Vincitore delle World Series (Y o N)R- Punti segnatiAB- Turni alla battutaH- Valide dei battitoriHR- Fuoricampo dei battitoriBB- Basi su ball dei battitoriSO- Strikeout dei battitoriSB- Basi rubateCS- Eliminazioni in rubataHBP- Battitori colpiti dal lancioSF- Volate di sacrificioRA- Punti subitiER- Punti guadagnati concessiERA- Media PGL (Earned Run Average)CG- Partite completeSHO- ShutoutSV- SalvezzeIPOuts- Out lanciati (inning lanciati x 3)HA- Valide concesseHRA- Fuoricampo concessiBBA- Basi su ball concesseSOA- Strikeout dei lanciatoriE- ErroriDP- Doppie eliminazioniFP- Percentuale di difesaname- Nome completo della squadra
Per chi non ha molta familiarità con il baseball, ecco una breve spiegazione del funzionamento del gioco, con alcune variabili incluse.
Il baseball si gioca tra due squadre (che ritroverai nei dati con name o teamID) di nove giocatori ciascuna. Le due squadre si alternano in attacco e in difesa. La squadra in attacco cerca di segnare punti battendo a turno una palla lanciata dal lanciatore della squadra in difesa, per poi correre in senso antiorario attorno a quattro basi: prima, seconda, terza e casa base. La squadra in difesa cerca di impedire i punti eliminando i battitori o i corridori in base in vari modi e un punto (R) viene segnato quando un giocatore avanza sulle basi e torna a casa base. Un giocatore della squadra in attacco che raggiunge la base in sicurezza cercherà di avanzare alle basi successive durante i turni di battuta dei compagni, ad esempio su una valida (H), una base rubata (SB) o in altri modi.

Le squadre si scambiano tra attacco e difesa ogni volta che la squadra in difesa registra tre out. Un turno di battuta per entrambe le squadre, iniziando da quella in trasferta, costituisce un inning. Una partita è composta da nove inning e la squadra con più punti alla fine vince. Il baseball non ha un cronometro e, sebbene la maggior parte delle partite termini al nono inning, se dopo nove inning c'è parità si va agli extra inning e si continua a oltranza finché una squadra non chiude un inning supplementare in vantaggio.
Per una spiegazione dettagliata del gioco del baseball, consulta le regole ufficiali della Major League Baseball.
Pulizia e preparazione dei dati
Come vedi sopra, il DataFrame non ha intestazioni di colonna. Puoi aggiungerle passando un elenco di intestazioni all'attributo columns di pandas.
La funzione len() ti dirà con quante righe hai a che fare: 2.287 non è un numero enorme di punti dati, quindi speriamo che non ci siano troppi valori nulli.
Prima di valutare la qualità dei dati, eliminiamo le colonne non necessarie o derivate dalla colonna target (Wins). Qui la conoscenza dei dati con cui lavori inizia a diventare molto preziosa. Non importa quanto tu sappia di coding o di statistica, se non conosci i dati con cui stai lavorando. Essere un fan del baseball da una vita mi ha sicuramente aiutato in questo progetto.
Come hai letto sopra, i valori nulli influiscono sulla qualità dei dati e questo, a sua volta, può creare problemi con gli algoritmi di machine learning.
Per questo li rimuoverai adesso. Ci sono vari modi per eliminare i valori nulli, ma potrebbe essere meglio prima visualizzare il conteggio dei null per ogni colonna, così puoi decidere come gestirli al meglio.
E qui vedrai un compromesso: ti servono dati puliti, ma non hai un grande volume di dati da sacrificare. Due colonne hanno un numero relativamente piccolo di valori nulli. Ci sono 110 valori nulli nella colonna SO (Strikeout) e 22 nella colonna DP (Doppia eliminazione). Due colonne ne hanno invece un numero relativamente alto. Ci sono 419 valori nulli nella colonna CS (Eliminazioni in rubata) e 1777 nella colonna HBP (Battitori colpiti dal lancio).
Se elimini le righe in cui le colonne hanno un piccolo numero di valori nulli, perdi poco più del cinque percento dei dati. Poiché stai cercando di prevedere le vittorie, i punti segnati e i punti subiti sono fortemente correlati con il target. Vuoi che i dati in quelle colonne siano molto accurati.
Gli strikeout (SO) e le doppie eliminazioni (DP) non sono altrettanto importanti.
Secondo me è meglio tenere le righe e riempire i valori nulli con il valore mediano di ciascuna colonna usando il metodo fillna(). Anche le eliminazioni in rubata (CS) e i battitori colpiti dal lancio (HBP) non sono variabili molto importanti. Con così tanti valori nulli in queste colonne, è meglio eliminarle del tutto.
Esplorazione e visualizzazione dei dati
Ora che hai ripulito i dati, puoi fare un po' di esplorazione. Puoi farti un'idea migliore del dataset con alcune semplici visualizzazioni. matplotlib è un'ottima libreria per la visualizzazione dei dati.
Importi matplotlib.pyplot e lo rinomini in plt per comodità. Se lavori con un notebook Jupyter, devi usare la magic %matplotlib inline.
Inizia tracciando un istogramma della colonna target per vedere la distribuzione delle vittorie.
# 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()
Nota che se non stai usando un notebook Jupyter, devi utilizzare plt.show() per visualizzare i grafici.
Stampa la media delle vittorie (W) per anno. Puoi usare il metodo mean() per farlo.
Può essere utile creare dei bin per la colonna target durante l'esplorazione dei dati, ma devi assicurarti di non includere nessuna caratteristica generata dalla colonna target quando alleni il modello. Includere nel training set una colonna di etichette generata dalla colonna target sarebbe come dare al modello le risposte del test.
Per creare le etichette delle vittorie, creerai una funzione chiamata assign_win_bins che prenderà in input un valore intero (vittorie) e restituirà un intero da 1 a 5 a seconda del valore di input.
Poi creerai una nuova colonna win_bins usando il metodo apply() sulla colonna delle vittorie e passando la funzione assign_win_bins().
Ora creiamo un grafico a dispersione con l'anno sull'asse x e le vittorie sull'asse y, evidenziando la colonna win_bins con colori.
# 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()
Come puoi vedere dallo scatter plot qui sopra, ci sono pochissime stagioni prima del 1900 e il gioco era molto diverso allora. Per questo ha senso eliminare quelle righe dal dataset.
Quando lavori con dati continui e crei modelli lineari, valori interi come un anno possono creare problemi. È improbabile che il numero 1950 abbia con il resto dei dati la stessa relazione che il modello andrà a inferire.
Puoi evitare questi problemi creando nuove variabili che etichettino i dati in base al valore di yearID.
Chi segue il baseball sa che, con l'evolversi della Major League Baseball (MLB), sono emerse diverse ere in cui il numero di punti per partita è aumentato o diminuito in modo significativo. La dead ball era dei primi del Novecento è un esempio di era a basso punteggio, mentre l'era degli steroidi a cavallo del XXI secolo è un esempio di era ad alto punteggio.
Creiamo qui sotto un grafico che indichi quanto si segnava per ciascun anno.
Inizia creando i dizionari runs_per_year e games_per_year. Esegui un loop sul dataframe usando il metodo iterrows(). Popola il dizionario runs_per_year con gli anni come chiavi e con quanti punti sono stati segnati in quell'anno come valore. Popola il dizionario games_per_year con gli anni come chiavi e con quante partite sono state giocate in quell'anno come valore.
Poi crea un dizionario chiamato mlb_runs_per_game. Itera sul dizionario games_per_year con il metodo items(). Popola il dizionario mlb_runs_per_game con gli anni come chiavi e con il numero di punti segnati per partita, a livello di lega, come valore.
Infine, crea il grafico a partire dal dizionario mlb_runs_per_game mettendo gli anni sull'asse x e i punti per partita sull'asse y.
# 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()
Aggiungere nuove feature
Ora che hai un’idea più chiara delle tendenze nel punteggio, puoi creare nuove variabili che indicano l’epoca specifica a cui appartiene ogni riga di dati in base a yearID. Seguirai lo stesso procedimento usato sopra quando hai creato la colonna win_bins.
Questa volta, però, creerai colonne dummy: una nuova colonna per ogni epoca. Per farlo puoi usare il metodo get_dummies().
Dal momento che hai già calcolato i punti MLB per partita per ogni anno, aggiungi quel dato al dataset.
Ora convertirai gli anni in decenni creando colonne dummy per ogni decennio. Poi potrai eliminare le colonne che non ti servono più.
In fin dei conti, nel baseball contano quanti punti segni e quanti ne concedi. Puoi aumentare sensibilmente l’accuratezza del modello creando colonne che siano rapporti di altre colonne. Punti per partita e punti concessi per partita saranno ottime feature da aggiungere al nostro dataset.
Pandas lo rende molto semplice: crei una nuova colonna dividendo la colonna R per la colonna G per ottenere la colonna R_per_game.
Ora osserva come ciascuna delle due nuove variabili è correlata alla colonna target delle vittorie creando un paio di grafici a dispersione. Metti i punti per partita sull’asse x di un grafico e i punti concessi per partita sull’asse x dell’altro. Metti la colonna W su ciascun asse y.
# 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()
Prima di passare a qualsiasi modello di machine learning, può essere utile vedere come ciascuna variabile è correlata con la variabile target. Pandas semplifica molto questa operazione con il metodo corr().
Un’altra feature che puoi aggiungere al dataset sono etichette derivate da un algoritmo di clustering K-means fornito da sklearn. K-means è un semplice algoritmo di clustering che partiziona i dati in base al numero di centroidi k che indichi. A ogni punto dati viene assegnato un cluster in base al centrodide con la minore distanza euclidea dal punto.
Puoi approfondire il clustering K-means qui.
Per prima cosa, crea un DataFrame che escluda la variabile target:
Un aspetto del clustering K-means che devi stabilire prima di usare il modello è quanti cluster vuoi. Puoi farti un’idea più precisa del numero ideale di cluster usando la funzione silhouette_score() di sklearn. Questa funzione restituisce il coefficiente medio di silhouette su tutti i campioni. Vuoi un punteggio di silhouette più alto, e il punteggio diminuisce man mano che aggiungi più cluster.
Ora puoi inizializzare il modello. Imposta il numero di cluster a 6 e il random state a 1. Determina le distanze euclidee per ogni punto dati usando il metodo fit_transform() e poi visualizza i cluster con un grafico a dispersione.
# 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 from K-means model as colorlabels = kmeans_model.labels_plt.scatter(distances[:,0], distances[:,1], c=labels)plt.title('Kmeans Clusters')plt.show()
Ora aggiungi le etichette dei tuoi cluster al dataset come nuova colonna. Aggiungi anche la stringa “labels” alla lista attributes, per usarla più avanti.
Prima di costruire il modello, devi suddividere i dati in train e test set. Questo perché, se decidi di addestrare il modello sugli stessi dati su cui lo testi, rischi di overfittare: il modello finirebbe per memorizzare i dati invece di apprendere, risultando eccessivamente complesso per i tuoi dati. Questo spiega anche perché un modello overfittato si comporta molto male quando provi a fare previsioni su nuovi dati.
Ma non preoccuparti: esistono diversi modi per fare la cross-validation del modello.
Questa volta prenderai semplicemente un campione casuale del 75% dei nostri dati per il set di dati di train e userai il restante 25% per il set di dati di test. Crea una lista numeric_cols con tutte le colonne che userai nel modello. Poi crea un nuovo DataFrame data dal DataFrame df con le colonne nella lista numeric_cols. Quindi crea anche i tuoi set di dati di train e test campionando il DataFrame data.
Scelta della metrica di errore e del modello
La Mean Absolute Error (MAE) è la metrica che userai per valutare l’accuratezza del modello. Misura quanto le previsioni siano vicine ai risultati effettivi. In particolare, per questi dati, significa che questa metrica ti restituisce il valore assoluto medio di quanto la tua previsione ha mancato il bersaglio.
Questo vuol dire che se, in media, le tue previsioni sbagliano di 5 vittorie rispetto all’obiettivo, la tua metrica di errore sarà 5.
Il primo modello che allenerai sarà una regressione lineare. Puoi importare LinearRegression e mean_absolute_error rispettivamente da sklearn.linear_model e sklearn.metrics, quindi creare un modello lr. Poi, effettuerai il fit del modello, genererai le previsioni e calcolerai la mean absolute error del modello.
Se ricordi quanto visto sopra, il numero medio di vittorie era circa 79. In media, il modello sbaglia di sole 2,687 vittorie.
Ora prova un modello di regressione Ridge. Importa RidgeCV da sklearn.linear_model e crea il modello rrm. Il modello RidgeCV ti permette di impostare il parametro alpha, che è un parametro di complessità che controlla l’ammontare di shrinkage (approfondisci qui). Il modello userà la cross-validation per determinare quale tra i valori di alpha che fornisci è ideale.
Di nuovo, effettua il fit del modello, fai le previsioni e calcola la mean absolute error.
Questo modello ha avuto prestazioni leggermente migliori, con un errore medio di 2,673 vittorie.
Sports Analytics & Scikit-Learn
Si conclude così la prima parte di questa serie di tutorial in cui hai visto come usare scikit-learn per analizzare dati sportivi. Hai importato i dati da un database SQLite, li hai ripuliti, ne hai esplorato alcuni aspetti in modo visivo e hai creato diverse nuove feature. Hai imparato a creare un modello di clustering K-means, un paio di modelli di regressione lineare e a testare le tue previsioni con la metrica della mean absolute error.
Nella seconda parte vedrai come usare modelli di classificazione per prevedere quali giocatori entreranno nella MLB Hall of Fame.

