Applicazioni pratiche di machine learning/Previsioni su scommesse sportive

Indice del libro

CALCIO modifica

Caricamento librerie modifica

 library(dplyr)
 library(caret)

Parte 1: Dati modifica

Dal seguente link: https://www.football-data.co.uk/italym.php è possibile scaricare le probabilità di vincita, pareggio o vittoria in trasferta di vari bookmakers per le partite di calcio di serie A in Italia . In particolare si utilizzano per la previsione nelle scommesse sportive gli anni 2017,2018,2019,2020 .

Caricamento dati:

 I1_17_18 <- read.csv("I1_17_18.csv")
 I1_18_19 <- read.csv("I1_18_19.csv")
 I1_19_20 <- read.csv("I1_19_20.csv")

Creazione colonna "risultato", contenente gli 1,X,2 risultati della corrispondente partita, da aggiungere al dataframe degli anni 2017 e 2018:

 risultato <- rep(NA,nrow(I1_17_18))
 risultato[which(I1_17_18$FTR=="A")]="2"
 risultato[which(I1_17_18$FTR=="D")]="X"
 risultato[which(I1_17_18$FTR=="H")]="1"

 df1<-I1_17_18[,which(colnames(I1_17_18) %in% colnames(I1_19_20))]
 df1 <- cbind(df1,risultato)


Creazione colonna "risultato", contenente gli 1,X,2 risultati della corrispondente partita, da aggiungere al dataframe degli anni 2018 e 2019:

 risultato <- rep(NA,nrow(I1_18_19))
 risultato[which(I1_18_19$FTR=="A")]="2"
 risultato[which(I1_18_19$FTR=="D")]="X"
 risultato[which(I1_18_19$FTR=="H")]="1"

 df2<-I1_18_19[,which(colnames(I1_18_19) %in% colnames(I1_19_20))]
 df2 <- cbind(df2,risultato)


Creazione colonna "risultato", contenente gli 1,X,2 risultati della corrispondente partita, da aggiungere al dataframe degli anni 2019 e 2020:

 risultato <- rep(NA,nrow(I1_19_20))
 risultato[which(I1_19_20$FTR=="A")]="2"
 risultato[which(I1_19_20$FTR=="D")]="X"
 risultato[which(I1_19_20$FTR=="H")]="1"

 df3<-I1_19_20[,which(colnames(I1_19_20) %in% colnames(I1_18_19))]
 df3 <- cbind(df3,risultato)

Si scelgono per la previsione le seguenti variabili contenenti le probabilità di scommessa 1,X,2 sulle partite fatte dai bookmakers:

  • B365H = Bet365 probabilità di vittoria in casa
  • B365D = Bet365 probabilità di pareggio
  • B365A = Bet365 probabilità di vittoria in trasferta
  • BSH = probabilità di vittoria in casa Blue Square
  • BSD = probabilità di pareggio Blue Square
  • BSA = probabilità di vittoria in trasferta Blue Square
  • BWH = Bet&Win vittoria in casa
  • BWD = Bet&Win pareggio
  • BWA = Bet&Win probabilità di vincere in trasferta
  • GBH = probabilità di vittoria in casa di Gamebookers
  • GBD = Gamebookers probabilità di pareggio
  • GBA = Le probabilità di vincere in trasferta di Gamebookers
  • IWH = probabilità di vittoria in casa Interwetten
  • IWD = probabilità di pareggio Interwetten
  • IWA = Interwetten in trasferta
  • LBH = Probabilità di vittoria in casa di Ladbrokes
  • LBD = Ladbrokes probabilità di pareggio
  • LBA = Ladbrokes probabilità di vittoria in trasferta
  • PSH e PH = probabilità di vittoria in casa Pinnacle
  • PSD e PD = probabilità di pareggio di Pinnacle
  • PSA e PA = Pinnacle probabilità di vittoria in trasferta
  • SOH = probabilità di vittoria in casa Sporting Odds
  • SOD = probabilità di pareggio Sporting Odds
  • SOA = probabilità di vittoria in trasferta Sporting Odds
  • SBH = probabilità di vittoria in casa di Sportingbet
  • SBD = probabilità di pareggio di Sportingbet
  • SBA = probabilità di vittoria in trasferta Sportingbet
  • SJH = probabilità di vittoria in casa di Stan James
  • SJD = probabilità di pareggio di Stan James
  • SJA = probabilità di vittoria in trasferta di Stan James
  • SYH = probabilità di vittoria in casa di Stanleybet
  • SYD = probabilità di pareggio di Stanleybet
  • SYA = Probabilità di vittoria in trasferta di Stanleybet
  • VCH = VC probabilità di vincita in casa
  • VCD = VC probabilità di pareggio
  • VCA = VC probabilità di vittoria in trasferta
  • WHH = probabilità di vittoria in casa di William Hill
  • WHD = probabilità di pareggio di William Hill
  • WHA = Probabilità di vittoria in trasferta di William Hill


...e si uniscono i 3 dataframes contenenti le probabilità di vincita in casa, pareggio o trasferta negli anni 2017,2018,2019,2020 :

 df1<-rbind(df1,df2)
 df1<-rbind(df1,df3)

 df1 <- df1[,which(colnames(df1) %in%  c("Date","HomeTeam","AwayTeam","B365H","B365D","B365A","BWH","BWD","BWA","IWH","IWD","IWA","PSH","PSD","PSA","WHH","WHD","WHA","VCH","VCD","VCA","risultato" ))]

 head(df1)

Si eliminano dal dataframe df1 le righe contenenti gli eventuali valori mancanti nelle probabilità...

 df1 <- df1[-which(rowSums(is.na(df1))>0),]


Parte 2 : Domanda di ricerca modifica

Di prescelte partite future di serie A in Italia il risultato sarà 1,X o 2 tramite l'intelligenza artificiale?

Parte 3 : Modellizzazione e Previsione modifica

Si suddivide il dataset df1 in un training set costituito dal 90% delle partite e in un testing set costituito dal rimanente 10% di partite e come predictors si scelgono le probabilità indicate dai vari bookmakers escludendo data della partita e nomi delle squadre in casa e trasferta. La variabile da predire è "risultato" .

 n <-nrow(df1)
 training <- df1[1:round(n*0.9),c(4:22)]
 testing <- df1[round(n*0.9):n,c(4:22)]
    
 model <-  train( risultato ~ .,data=training, method="rf", verbose=FALSE )

 p1 <- predict(model,newdata = training)
 print(confusionMatrix(p1,training$risultato)) 

 p1 <- predict(model,newdata = testing)
 print(confusionMatrix(p1,testing$risultato))
Confusion Matrix and Statistics of training set:
         Reference
Prediction   1   2   X
        1  370   0   0
        2    0 272   0
        X    0   0 211
Overall Statistics
                                    
              Accuracy : 1          
                95% CI : (0.9957, 1)
   No Information Rate : 0.4338     
   P-Value [Acc > NIR] : < 2.2e-16  
                                    
                 Kappa : 1          
                                    
Mcnemar's Test P-Value : NA         
Statistics by Class:
                        Class: 1 Class: 2 Class: X
Sensitivity            1.0000   1.0000   1.0000
Specificity            1.0000   1.0000   1.0000
Pos Pred Value         1.0000   1.0000   1.0000
Neg Pred Value         1.0000   1.0000   1.0000
Prevalence             0.4338   0.3189   0.2474
Detection Rate         0.4338   0.3189   0.2474
Detection Prevalence   0.4338   0.3189   0.2474
Balanced Accuracy      1.0000   1.0000   1.0000


Confusion Matrix and Statistics of the testing set:
         Reference
Prediction  1  2  X
        1  27 12  9
        2   5 18  6
        X   5  7  7
Overall Statistics
                                         
              Accuracy : 0.5417          
                95% CI : (0.4369, 0.6438)
   No Information Rate : 0.3854          
   P-Value [Acc > NIR] : 0.001359        
                                         
                 Kappa : 0.29            
                                         
Mcnemar's Test P-Value : 0.250645        
Statistics by Class:
                    Class: 1 Class: 2 Class: X
Sensitivity            0.7297   0.4865  0.31818
Specificity            0.6441   0.8136  0.83784
Pos Pred Value         0.5625   0.6207  0.36842
Neg Pred Value         0.7917   0.7164  0.80519
Prevalence             0.3854   0.3854  0.22917
Detection Rate         0.2812   0.1875  0.07292
Detection Prevalence   0.5000   0.3021  0.19792
Balanced Accuracy      0.6869   0.6500  0.57801

Parte 4: Conclusioni modifica

Utilizzando quindi l'algoritmo Random Forest l'accuratezza della previsione sul training set è del 100%, mentre sul testing set è solo del 54,17% e quindi in pratica partecipare alle scommesse sportive utilizzando il machine learning è approssimativamente come giocare sul rosso e il nero alla Roulette...

TENNIS modifica

Caricamento librerie modifica

library(dplyr)
library(ggplot2)
library(h2o)
library(caret)
library(stringr)

Parte 1 : Dati modifica

I dati scaricabili da qui : https://github.com/JeffSackmann/tennis_atp contengono variabili di tutte le partite di tennis ATP giocate dal 1968 in poi . Le variabili che si prendono in considerazione per la previsione del vincitore o del perdente sono :

  • best_of - il numero massimo di set giocati
  • draw_size - la dimensione del disegno
  • match_num - numero partita in un determinato torneo
  • round: il round del torneo a cui appartiene una partita
  • surface - superficie in cui si gioca la partita
  • tourney_id - id del torneo
  • tourney_level - livello del torneo

'G' = Grande Slam 'M' = Master 1000s 'A' = altri eventi a livello di tour 'C' = Sfidanti 'S' = Satelliti/ITF 'F' = finali del tour e altri eventi di fine stagione 'D' = Coppa Davis

  • loser_age - età del giocatore perdente
  • loser_entry - Come è entrato il giocatore perdente nei tornei?

WC - Carattere jolly D - Qualificazione LL - Perdente fortunato PR - Classifica protetta SE - Esenzione speciale ALT - Giocatore alternativo

  • loser_hand - mano del giocatore perdente, destra o sinistra
  • loser_ht - l'altezza del giocatore perdente
  • loser_id - id del giocatore perdente
  • loser_ioc - il paese di origine del giocatore perdente
  • loser_rank - ranking del giocatore perdente
  • loser_seed - semi del giocatore perdente
  • winner_age - età del giocatore vincente
  • winner_entry - Come è entrato il giocatore vincente nei tornei?

WC - Carattere jolly D - Qualificazione LL - Perdente fortunato PR - Classifica protetta SE - Esenzione speciale ALT - Giocatore alternativo

  • winner_hand - mano del giocatore vincente, destra o sinistra
  • winner_ht - l'altezza del giocatore vincente
  • winner_id - id del giocatore vincente
  • winner_ioc - il paese di origine del giocatore vincente
  • winner_rank - ranking del giocatore vincente
  • winner_seed - semi del giocatore vincente


Caricamento dati:

df <- read.csv("ATP.csv")

Parte 2 : Domanda di ricerca modifica

Chi sarà il vincitore o il perdente in una partita di tennis tramite l'intelligenza artificiale ?

Parte 3 : Modellizzazione e Previsione modifica

Si creano 2 nuove variabili: mese e anno:

df$year<- as.integer(str_sub(df$tourney_date, start = 1,end = 4))
df$month <-as.integer(str_sub(df$tourney_date, start = 5,end = 6))

Si scelgono le variabili dal dataset sui cui fare l'addestramento del modello:

df <- df[, c(
  'best_of',
  'draw_size',
  'loser_age',
  'loser_entry',
  'loser_hand',
  'loser_ht',
  'loser_id',
  'loser_ioc',
  'loser_rank',
  'loser_seed',
  'match_num',
  'round',
  'surface',
  'tourney_id',
  'tourney_level',
  'winner_age',
  'winner_entry',
  'winner_hand',
  'winner_ht',
  'winner_id',
  'winner_ioc',
  'winner_rank',
  'winner_seed',
  'year',
  'month'
)]

Si rinominano le variabili relative a loser e winner:

colnames(df)[3:10] <- c(
  "first_age",
  "first_entry",
  "first_hand",
  "first_ht",
  "first_id",
  "first_ioc",
  "first_rank",
  "first_seed"
)

colnames(df)[16:23] <- c(
  "second_age",
  "second_entry",
  "second_hand",
  "second_ht",
  "second_id",
  "second_ioc",
  "second_rank",
  "second_seed"
)

Si crea una copia del dataset df in df1 e si scambia il primo con il secondo giocatore:

df1 <- df
df1[, c(
  "first_age",
  "first_entry",
  "first_hand",
  "first_ht",
  "first_id",
  "first_ioc",
  "first_rank",
  "first_seed",
  "second_age",
  "second_entry",
  "second_hand",
  "second_ht",
  "second_id",
  "second_ioc",
  "second_rank",
  "second_seed"
)] <- df1[, c(
  "second_age",
  "second_entry",
  "second_hand",
  "second_ht",
  "second_id",
  "second_ioc",
  "second_rank",
  "second_seed",
  "first_age",
  "first_entry",
  "first_hand",
  "first_ht",
  "first_id",
  "first_ioc",
  "first_rank",
  "first_seed"
)]

Si crea la variabile label da predire . label assume il valore 0 nel dataset df in cui second player vince e assume il valore 1 nel dataset df1 in cui first player vince e si concatenano i 2 due dataset:

df$label=0
df1$label=1
df <- rbind(df,df1)

Si divide il dataset df in un training set fatto dal 75% delle osservazioni e su cui si addestra il modello ed il rimanente 25% costituisce il testing set su cui verrà testato il modello :

df$label <- as.factor(df$label)
trainIndex <- createDataPartition(df$label,p=0.75, list = FALSE)
training <- df[trainIndex,]
testing <- df[-trainIndex,]

Si inizializza la libreria h2o necessaria per automatizzare il machine learning:

h2o.init()
Connection successful!
R is connected to the H2O cluster: 
   H2O cluster uptime:         1 hours 20 minutes 
   H2O cluster timezone:       Europe/Rome 
   H2O data parsing timezone:  UTC 
   H2O cluster version:        3.38.0.1 
   H2O cluster version age:    1 month and 1 day  
   H2O cluster name:           H2O_started_from_R_gian_yty236 
   H2O cluster total nodes:    1 
   H2O cluster total memory:   1.06 GB 
   H2O cluster total cores:    2 
   H2O cluster allowed cores:  2 
   H2O cluster healthy:        TRUE 
   H2O Connection ip:          localhost 
   H2O Connection port:        54321 
   H2O Connection proxy:       NA 
   H2O Internal Security:      FALSE 
   R Version:                  R version 4.2.1 (2022-06-23) 


Si addestra il modello :

df_hf <- as.h2o(training)
y <- "label"
x <- names(training)[1:25]

aml <- h2o.automl(x = x, y = y,
                  training_frame = df_hf,
                  max_runtime_secs =300)


Si ottengono i seguenti modelli per cui il migliore risulta : StackedEnsemble_BestOfFamily_4_AutoML_3_20221020_193936 con un auc=74,21%

lb <- aml@leaderboard
lb
model_id       auc   logloss     aucpr mean_per_class_error      rmse       mse
1 StackedEnsemble_BestOfFamily_4_AutoML_3_20221020_193936 0.7421346 0.5959886 0.7396699            0.3577591 0.4533024 0.2054831
2    StackedEnsemble_AllModels_2_AutoML_3_20221020_193936 0.7420115 0.5958908 0.7398776            0.3588214 0.4532841 0.2054665
3    StackedEnsemble_AllModels_1_AutoML_3_20221020_193936 0.7419540 0.5958249 0.7400070            0.3604438 0.4532571 0.2054420
4 StackedEnsemble_BestOfFamily_2_AutoML_3_20221020_193936 0.7411001 0.5967041 0.7383371            0.3551206 0.4536377 0.2057871
5 StackedEnsemble_BestOfFamily_3_AutoML_3_20221020_193936 0.7407722 0.5971972 0.7378754            0.3517726 0.4538422 0.2059727
6                          GBM_2_AutoML_3_20221020_193936 0.7405898 0.5974683 0.7373835            0.3475135 0.4539269 0.2060496


Si prova il modello sul testing set ottenendo la matrice di confusione e un'accuracy del 64,07% :

test <- as.h2o(testing)
model <- aml@leader
p1 = h2o.predict(model, newdata=test)
confusionMatrix(df2$predict,testing$label)
Confusion Matrix and Statistics
          Reference
Prediction     0     1
         0 17099  5159
         1 25323 37263
                                        
              Accuracy : 0.6407         
                95% CI : (0.6375, 0.644)
   No Information Rate : 0.5            
   P-Value [Acc > NIR] : < 2.2e-16      
                 Kappa : 0.2815         
Mcnemar's Test P-Value : < 2.2e-16      
           Sensitivity : 0.4031         
           Specificity : 0.8784         
        Pos Pred Value : 0.7682         
        Neg Pred Value : 0.5954         
            Prevalence : 0.5000         
        Detection Rate : 0.2015         
  Detection Prevalence : 0.2623         
     Balanced Accuracy : 0.6407         

Parte 4 : Previsioni con Python modifica

Di seguito si utilizza il codice contenuto in questo Notebook : https://www.kaggle.com/code/alexisbcook/pipelines rilasciato con licenza Apache 2.0 .

import pandas as pd
from sklearn.model_selection import train_test_split

# Caricamento dei dati
data = pd.read_csv('tennis.csv')

# Separo la variabile da predire label dai predictors
y = data.label
X = data.drop(['label'], axis=1)

# Divido i dati in training e validation subsets, il primo con il 75% delle partite e il secondo con il 25%
X_train_full, X_valid_full, y_train, y_valid = train_test_split(X, y, train_size=0.75, test_size=0.25,random_state=0)

# Seleziono le colonne con valori categoriali in numero minore di 10
categorical_cols = [cname for cname in X_train_full.columns if X_train_full[cname].nunique() < 10 and 
                        X_train_full[cname].dtype == "object"]

# Seleziono le colonne numeriche
numerical_cols = [cname for cname in X_train_full.columns if X_train_full[cname].dtype in ['int64', 'float64']]

# Mantengo soltanto le colonne selezionate
my_cols = categorical_cols + numerical_cols
X_train = X_train_full[my_cols].copy()
X_valid = X_valid_full[my_cols].copy()

Le colonne numeriche e categoriche nel dataset che hanno valori mancanti NA vengono trasformate con valori approssimativi nei NA tramite SimpleImputer, mentre le variabili categoriche vengono trasformate in numeriche tramite OneHotEncoder :

from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import OneHotEncoder

# Preprocessing per i dati numerici
numerical_transformer = SimpleImputer(strategy='constant')

# Preprocessing per i dati categoriali
categorical_transformer = Pipeline(steps=[
    ('imputer', SimpleImputer(strategy='most_frequent')),
    ('onehot', OneHotEncoder(handle_unknown='ignore'))
])


preprocessor = ColumnTransformer(
    transformers=[
        ('num', numerical_transformer, numerical_cols),
        ('cat', categorical_transformer, categorical_cols)
    ])

Per la previsione utilizzo l'algoritmo XGBoost (Extreme Gradient Boosting) e addestro il modello sul training set dopo avere preprocessato i dati con il preprocessor precedentemente creato. Infine prevedo i dati sul validation set.

from xgboost import XGBClassifier

model = XGBClassifier(learning_rate=0.05)

my_pipeline = Pipeline(steps=[('preprocessor', preprocessor),
                              ('model', model)
                             ])

# addestro il modello 
my_pipeline.fit(X_train, y_train)

# ottengo le previsioni sul validation set
preds = my_pipeline.predict(X_valid)

Sul validation set ottengo un'accuracy del 68% :

from sklearn.metrics import classification_report
 
print(classification_report(y_valid, preds))
             precision    recall  f1-score   support
           0       0.68      0.67      0.68     42670
           1       0.67      0.68      0.67     42175
    accuracy                           0.68     84845
   macro avg       0.68      0.68      0.68     84845
weighted avg       0.68      0.68      0.68     84845

Parte 5: Conclusioni modifica

Rispetto al calcio, nel tennis si ottiene un'accuracy maggiore e quindi una previsione migliore ma non sufficiente per considerare le previsioni su scommesse sportive tramite machine learning un buon investimento.

FORMULA 1 (Gran Premi) modifica

Parte 1: Dati modifica

Dal seguente link: http://ergast.com/mrd/ è possibile scaricare i dataset dei Gran Premi di Formula 1 con dati dal 1950 al 2023 e predire tramite algoritmi di machine learning il pilota vincente con un'Accuracy del 99% sul testing set.

Caricamento dati:

import pandas as pd

# Tabella riassuntiva dei risultati:
result_df = pd.read_csv('results.csv')
# Stato del pilota:
stats_df = pd.read_csv('status.csv')
# Piloti :
drivers_df = pd.read_csv('drivers.csv')
# Gran Premi:
races_df = pd.read_csv('races.csv')
# Case automobilistiche :
constructor_df = pd.read_csv('constructors.csv')
driver_standings_df = pd.read_csv('driver_standings.csv')
# unisco i datasets in un singolo dataset df :

con1 = pd.merge(result_df, races_df, on ='raceId')
con2 = pd.merge(con1, drivers_df, on = 'driverId')
con3 = pd.merge(con2, driver_standings_df, on = 'driverId')
con4 = pd.merge(con3, constructor_df, on ='constructorId')
df = pd.merge(con4, stats_df, on ='statusId')
df.head()
# Le variabili sono le seguenti: 
df.info()

Come si vede , ci sono 3.475.793 righe e 37 colonne nel dataset df :

<class 'pandas.core.frame.DataFrame'>
Int64Index: 3475793 entries, 0 to 3475792
Data columns (total 37 columns):
 #   Column             Dtype  
---  ------             -----  
0   resultId           int64  
1   raceId_x           int64  
2   driverId           int64  
3   constructorId      int64  
4   number_x           object 
5   grid               int64  
6   position_x         object 
7   positionText_x     object 
8   positionOrder      int64  
9   points_x           float64
10  laps               int64  
11  time               object 
12  milliseconds       object 
13  fastestLap         object 
14  rank               object 
15  fastestLapTime     object 
16  fastestLapSpeed    object 
17  statusId           int64  
18  driverRef          object 
19  number_y           object 
20  code               object 
21  forename           object 
22  surname            object 
23  dob                object 
24  nationality_x      object 
25  url_x              object 
26  driverStandingsId  int64  
27  raceId_y           int64  
28  points_y           float64
29  position_y         int64  
30  positionText_y     object 
31  wins               int64  
32  constructorRef     object 
33  name               object 
34  nationality_y      object 
35  url_y              object 
36  status             object 
dtypes: float64(2), int64(12), object(23)
memory usage: 1007.7+ MB
# cancello alcune variabili : 

df = df.drop(['url_y','url_x','position_x','fastestLapTime','positionText_x','driverRef',
              'constructorRef','nationality_y','positionText_y','raceId_y','points_y'],1)
              
# Creo la variabile driver_name col nome e cognome del pilota, che verrà prevista dall'algoritmo:

df['driver_name'] = df['forename']+' '+df['surname']
df = df.drop(['forename','surname'],1)

# converto alcune variabili di tipo object in variabili numeriche :
l = ['number_x','milliseconds','fastestLap','rank','fastestLapSpeed','number_y']
for i in l:
   df[i] = pd.to_numeric(df[i],errors='coerce')

Considero soltanto i piloti in stato finished, cioè quelli che hanno percorso almeno il 90% dei giri del Gran Premio, quindi su 60 giri ne hanno fatti almeno 54. In tal modo riduco il dataset a 1.320.987 righe:

df = df[df['status'] == 'Finished']
df.shape
(1320987, 42)

Parte 2: Previsioni modifica

from sklearn.model_selection import train_test_split

# Separo la variabile da predire driver_name dai predictors
y = df.driver_name
X = df.drop(['driver_name'], axis=1)

# Divido i dati in training e validation subsets, il primo con il 70% delle partite e il secondo con il 30%

X_train_full, X_valid_full, y_train, y_valid = train_test_split(X, y, test_size=0.3,
                                                                random_state=0)

# Seleziono le colonne con valori categoriali in numero minore di 10
categorical_cols = [cname for cname in X_train_full.columns if X_train_full[cname].nunique() < 10 and 
                        X_train_full[cname].dtype == "object"]

# Seleziono le colonne numeriche
numerical_cols = [cname for cname in X_train_full.columns if X_train_full[cname].dtype in ['int64', 'float64']]

# Mantengo soltanto le colonne selezionate
my_cols = categorical_cols + numerical_cols
X_train = X_train_full[my_cols].copy()
X_valid = X_valid_full[my_cols].copy()

Le colonne numeriche e categoriche nel dataset che hanno valori mancanti NA vengono trasformate con valori approssimativi nei NA tramite SimpleImputer, mentre le variabili categoriche vengono trasformate in numeriche tramite LabelEncoder :

from sklearn.preprocessing import LabelEncoder
from sklearn.impute import SimpleImputer

label_encoder = LabelEncoder()
for i in categorical_cols:
    X_train[i]=label_encoder.fit_transform(X_train[i])
    X_valid[i] = label_encoder.fit_transform(X_valid[i])

my_imputer = SimpleImputer()
X_train[numerical_cols] = my_imputer.fit_transform(X_train[numerical_cols])
X_valid[numerical_cols] = my_imputer.transform(X_valid[numerical_cols])

Per la previsione utilizzo l'algoritmo KNeighbors (il più semplice degli algoritmi di machine learning) e addestro il modello sul training set. Infine prevedo i dati sul validation set:

from sklearn.neighbors import KNeighborsClassifier

model= KNeighborsClassifier()

model.fit(X_train,y_train)
ypred = model.predict(X_valid)

Sul validation set ottengo un'accuracy del 99% :

from sklearn.metrics import classification_report
 
print(classification_report(y_valid, preds))
     	      accuracy                           0.99    396297
             macro avg       0.99      0.98      0.98    396297
          weighted avg       0.99      0.99      0.99    396297

Parte 3: Conclusioni modifica

Sebbene l'accuracy previsionale sia del 99%, il modello non prevede eventuali incidenti, guasti o cattive condizioni climatiche quindi in realtà l'accuratezza è minore...