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]:
IDProtocollo Gruppo DataOraIncidente Localizzazione1 STRADA1 Localizzazione2 STRADA2 Strada02 Chilometrica DaSpecificare NaturaIncidente ParticolaritaStrade TipoStrada FondoStradale Pavimentazione Segnaletica CondizioneAtmosferica Traffico Visibilità Illuminazione NUM_FERITI NUM_RISERVATA NUM_MORTI NUM_ILLESI LONGITUDINE LATITUDINE Confermato Progressivo TipoVeicolo StatoVeicolo Marca Modello TipoPersona AnnoNascita Sesso TipoLesione Deceduto DecedutoDopo CinturaCascoUtilizzato Airbag
count 70718 70718 70718 70718 70718 70718 27526 54373 26847 16345 70718 70718 70718 70718 70718 70718 70718 70696 70718 70718 70718 70718 70718 70718 67915 67915 70718 68533 68533 68533 68533 68533 70718 70717 70645 70718 70718 16038 62491 45596
unique 29053 21 22537 10 4395 5 4072 4080 2474 6587 22 20 5 10 10 5 8 3 3 4 12 4 4 13 15835 13325 2 9 41 3 332 2612 6 108 2 8 2 13 4 3
top 2197524,000000 1,000000 12/11/2014 06:30 Strada Urbana VIA CRISTOFORO COLOMBO all'intersezione con VIALE PALMIRO TOGLIATTI del civico n. 1 . Scontro laterale fra veicoli in marcia Rettilineo Una carreggiata a doppio senso Asciutto Asfaltata Verticale ed orizzontale Sereno Normale Buona Ore Diurne ,000000 ,000000 ,000000 2,000000 12,43204 41,7931 true 1,000000 Autovettura privata In marcia / fermata / arresto Fiat Punto Conducente 1973,000000 M Illeso false NON DECEDUTO Non accertato Inesploso
freq 22 6596 22 64500 1692 20255 187 22525 355 86 17273 36680 33796 57434 67515 49803 48316 42628 58248 50563 39195 70149 70391 20557 250 401 70713 39001 51630 68323 11984 3194 50647 1580 45008 54574 70612 15994 53425 36110

In [5]:
dataset.shape


Out[5]:
(70718, 40)

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]:
IDProtocollo DataOraIncidente NaturaIncidente ParticolaritaStrade TipoStrada FondoStradale Pavimentazione Segnaletica CondizioneAtmosferica Traffico Visibilità Illuminazione NUM_FERITI NUM_RISERVATA NUM_MORTI NUM_ILLESI Progressivo TipoVeicolo TipoPersona AnnoNascita Sesso TipoLesione Deceduto DecedutoDopo
count 70074 70074 70074 70074 70074 70074 70074 70074 70074 70052 70074 70074 70074 70074 70074 70074 67910 67910 70074 70074 70013 70074 70074 15904
unique 28891 22449 22 20 5 10 10 5 8 3 3 4 12 4 4 13 8 41 4 99 2 8 2 13
top 2197524 17/07/2014 18:00 Scontro laterale fra veicoli in marcia Rettilineo Una carreggiata a doppio senso Asciutto Asfaltata Verticale ed orizzontale Sereno Normale Buona Ore Diurne 0 0 0 2 1 Autovettura privata Conducente 1973 M Illeso false NON DECEDUTO
freq 22 22 17178 36362 33481 56912 66911 49314 47876 42246 57757 50130 38843 69512 69751 20463 38664 51144 50353 1570 44644 54066 69970 15860

In [8]:
dataset.shape


Out[8]:
(70074, 24)

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]:
Illeso        54066
Rimandato     14325
Ricoverato     1535
Deceduto        148
Name: TipoLesione, dtype: int64

In [24]:
dataset.describe()


Out[24]:
IDProtocollo NaturaIncidente ParticolaritaStrade TipoStrada FondoStradale Pavimentazione Segnaletica CondizioneAtmosferica Traffico Visibilità Illuminazione Progressivo TipoVeicolo TipoPersona Sesso TipoLesione FaseGiorno DimensioneIncidente FasciaEta
count 70074 70074 70074 70074 70074 70074 70074 70074 70074 70074 70074 67910 70074 70074 70074 70074 70074 70074 70074
unique 28891 8 7 5 3 3 5 3 3 3 4 8 14 3 2 4 6 3 6
top 2197524 MarciaVsMarcia Rettilineo Una carreggiata a doppio senso Asciutto Regolare Verticale ed orizzontale Sereno Normale Buona Ore Diurne 1 Autovettura Conducente M Illeso PomeriggioTardi Piccolo Adulto
freq 22 38664 36543 33481 56912 66915 49314 47909 42268 57757 50130 38664 53091 50353 44677 54066 19427 49315 28562

In [25]:
dataset.shape


Out[25]:
(70074, 19)

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 [ ]: