In [1]:
# Change HTML style
from IPython.core.display import HTML
HTML("""
<style>
.output_png {
display: table-cell;
text-align: center;
vertical-align: middle;
}
</style>
""")
Out[1]:
In [2]:
# Imports and settings
import warnings
warnings.filterwarnings('ignore')
import utils
import os
import pandas as pd
pd.options.display.max_columns = 100
pd.options.display.max_rows = 100
Sul sito http://dati.comune.roma.it/, nella sezione incidenti stradali, abbiamo scaricato due file, relativi agli incidenti nel primo e secondo semestre dell'anno 2014. I dati sono per lo più di tipo categorico, con poche colonne di tipo numerico. Di conseguenza, si è scelto di lavorare con dati totalmente categorici, creando delle categorie dove ci fossero dei valori numerici. Per iniziare, fondiamo i due dataset per avere una tabella che rappresenti l'intero anno. L'obiettivo è quello di predire il tipo di lesione causato da un incidente stradale, a scopo puramente dimostrativo. Tuttavia, si possono pensare a molteplici applicazioni: capire cosa separa gli incidenti gravi da quelli non, capire che tipo di incidenti potrebbero verificarsi sotto certe condizioni, ecc.
In [3]:
# Load dataset
sem_path = r"0_Sem_2014"
sem1_path = r"1_Sem_2014.csv"
sem2_path = r"2_Sem_2014.csv"
if not os.path.isfile(sem_path):
utils.join_dataframes(sem_path, sem1_path, sem2_path)
dataset = pd.read_pickle(sem_path)
In [4]:
dataset.describe()
Out[4]:
In [5]:
dataset.shape
Out[5]:
Come possiamo vedere, i dati sono formattati male e ci sono molti valori mancanti. Ad esempio, i campi a valore numerico sono tuti con la virgola; alcuni campi hanno molti valori mancanti, come il campo airbag o i campi relativi alle strade. Il campo "DecedutoDopo" ha pochi valori solo perchè funge da ausilio al campo "Deceduto". I campi "Progressivo", "TipoVeicolo", "StatoVeicolo", "Marca" e "Modello" hanno dei campi mancanti quando il record fa riferimento ad un pedone. I campi che presentano veramente dei valori mancanti sono "Sesso", "AnnoNascita" e "Traffico", per cui provvederemo a gestirli in modo opportuno. Quello che faremo ora, è formattare le colonne in modo opportuno, rimuovere le colonne che non ci servono, e fixare la colonna "AnnoNascita". Infatti, da una prima ispezione visiva, risulta che alcuni record hanno come anno di nascita date superiori al 2014, il che è un assurdo. Per cui, rimuoveremo tali record dal dataset, rimuovendo anche i record corrispondenti allo stesso incidente, non potendoci fidare del modo in cui i dati sono stati inseriti.
In [6]:
# Assign correct values to columns
dataset = utils.assign_columns(dataset)
# Remove columns we don't need
dataset = utils.remove_columns(dataset)
# Fix broken values
dataset = utils.fix_columns(dataset)
In [7]:
dataset.describe()
Out[7]:
In [8]:
dataset.shape
Out[8]:
La colonna "DataOraIncidente" contiene tante informazioni, che possiamo sfruttare per avere una descrizione migliore dei dati, più ricca di informazione. Ad esempio, possiamo creare nuove colonne per il giorno, il mese e l'orario in cui l'incidente è avvenuto. Ancora meglio, possiamo creare nuove categorie; in questo caso, ci siamo concentrati solamente sulla fascia oraria (nel codice vengono anche trattati altri campi), e abbiamo creato 6 categorie.
In [9]:
# Create new features using DataOraIncidente column
dataset = utils.expand_DataOraIncidente(dataset)
Un'altra informazione che possiamo estrarre è la dimensione dell'incidente. Per calcolarla, abbiamo due modi: il primo è quello di utilizzare il campo "IDProtocollo", che è lo stesso per tutti i veicoli e pedoni facenti parte dello stesso incidente, mentre il secondo è quello di utilizzare le colonne relative al numero di feriti, illesi, deceduti e persone in prognosi riservata. Il secondo modo va chiaramente contro lo scopo dell'analisi, perchè il nostro obiettivo è predire il tipo di lesione, ma essendo più banale del primo da scrivere, abbiamo usato quello. Tuttavia, essendoci un modo "lecito" di ottenere la dimensione dell'incidente, possiamo ignorare questo aspetto.
In [10]:
# Create new features using NUM columns
dataset = utils.expand_NUM(dataset)
Utilizziamo ora la colonna "DecedutoDopo" per modificare il campo "TipoLesione": in particolare, consideriamo come decedute anche le persone morte non sul posto, ovvero in ospedale. Questa ci sembra una considerazione adatta, in quanto è più mirata a valutare la quantità di danni prodotta da un incidente piuttosto che condizioni fortuite che hanno permesso alla persona di non morire sul colpo, nonostante l'incidente fosse effettivamente brutto.
In [11]:
# Create new features using DecedutoDopo column
dataset = utils.expand_DecedutoDopo(dataset)
La colonna "NaturaIncidente" ha 22 categorie, molte delle quali sono ridondanti o addirittura contengono pochi record per la categoria. Provvediamo perciò a mapparle in 8 categorie.
In [12]:
# Adjust NaturaIncidente column
dataset = utils.adjust_NaturaIncidente(dataset)
#dataset['NaturaIncidente'].unique()
#dataset['NaturaIncidente'].value_counts()
La colonna "ParticolaritaStrade" ha 20 categorie, molte delle quali sono ridondanti o addirittura contengono pochi record per la categoria. Provvediamo perciò a mapparle in 7 categorie.
In [13]:
# Adjust ParticolaritaStrade column
dataset = utils.adjust_ParticolaritaStrade(dataset)
#dataset['ParticolaritaStrade'].unique()
#dataset['ParticolaritaStrade'].value_counts()
La colonna "FondoStradale" ha 10 categorie, ma la maggior parte dei valori cadono solo in due di queste. Per questo motivo, abbiamo scelto di utilizzare solamente 3 categorie, accorpando tutte le altre.
In [14]:
# Adjust FondoStradale column
dataset = utils.adjust_FondoStradale(dataset)
#dataset['FondoStradale'].unique()
#dataset['FondoStradale'].value_counts()
La colonna "Pavimentazione" ha 10 categorie, molte delle quali sono ridondanti o addirittura contengono pochi record per la categoria. Provvediamo perciò a mapparle in 3 categorie.
In [15]:
# Adjust Pavimentazione column
dataset = utils.adjust_Pavimentazione(dataset)
#dataset['Pavimentazione'].unique()
#dataset['Pavimentazione'].value_counts()
La colonna "CondizioneAtmosferica" ha 8 categorie, molte delle quali sono ridondanti o addirittura contengono pochi record per la categoria. Provvediamo perciò a mapparle in 3 categorie.
In [16]:
# Adjust CondizioneAtmosferica column
dataset = utils.adjust_CondizioneAtmosferica(dataset)
#dataset['CondizioneAtmosferica'].unique()
#dataset['CondizioneAtmosferica'].value_counts()
La colonna "Traffico" ha solo 3 categorie, ma presenta dei valori mancanti. Dato che la condizione di traffico regolare compare nella maggioranza dei record, scegliamo di rimpiazzare i valori mancanti utilizzando il valore più comune.
In [17]:
# Adjust Traffico column
dataset = utils.adjust_Traffico(dataset)
#dataset['Traffico'].unique()
#dataset['Traffico'].value_counts()
La colonna "TipoVeicolo" ha 41 categorie, molte delle quali sono ridondanti o addirittura contengono pochi record per la categoria. Provvediamo perciò a mapparle in 14 categorie. Le categorie ottenute non sono poche, e potevano sicuramente essere ridotte, a patto di utilizzare una mappatura dei veicoli che tenesse conto del loro peso. E' molto probabile, infatti, che sia il peso del veicolo ad influire sul danno.
In [18]:
# Adjust TipoVeicolo column
dataset = utils.adjust_TipoVeicolo(dataset)
#dataset['TipoVeicolo'].unique()
#dataset['TipoVeicolo'].value_counts()
La colonna "TipoPersona" ha 6 categorie, molte delle quali sono ridondanti. Provvediamo perciò a mapparle in 3 categorie.
In [19]:
# Adjust TipoPersona column
dataset = utils.adjust_TipoPersona(dataset)
#dataset['TipoPersona'].unique()
#dataset['TipoPersona'].value_counts()
Il valore mancante della colonna "AnnoNascita" è stato eliminato nella fase iniziale di lavorazione del dataset. Procediamo ora a creare delle nuove categorie, che rappresentano le fascie di età. Alla fine di questa procedura, otteniamo 6 categorie.
In [20]:
# Adjust AnnoNascita column
dataset = utils.adjust_AnnoNascita(dataset)
#dataset['AnnoNascita'].unique()
#dataset['AnnoNascita'].value_counts()
La colonna "Sesso" ha dei valori mancanti, che sono comunque pochi rispetto ai valori presenti. Per riempire i valori mancanti, si è scelto di campionare dalla distribuzione della colonna, il chè equivale a tirare una moneta dove le probabilità di testa e croce non sono 50/50 ma sono date dal numero di record già presenti.
In [21]:
# Adjust Sesso column
dataset = utils.adjust_Sesso(dataset)
#dataset['Sesso'].unique()
#dataset['Sesso'].value_counts()
La colonna "TipoLesione" ha 8 categorie, molte delle quali sono ridondanti. Provvediamo perciò a mapparle in 4 categorie.
In [22]:
# Adjust TipoLesione column
dataset = utils.adjust_TipoLesione(dataset)
#dataset['TipoLesione'].unique()
#dataset['TipoLesione'].value_counts()
Out[22]:
In [24]:
dataset.describe()
Out[24]:
In [25]:
dataset.shape
Out[25]:
Per concludere, serializziamo il dataset e salviamolo anche in formato csv. Siamo ora pronti a passare alla parte di machine learning, dove cercheremo di predire il tipo di lesione che viene riportato dalle persone coinvolte in un incidente, utilizzando appunto la colonna "TipoLesione".
In [26]:
save_path = r"0_CarIncident_2014"
save_path_csv = r"0_CarIncident_2014.csv"
dataset.to_pickle(save_path)
dataset.to_csv(save_path_csv)
In [ ]: