Loading...

Loading...

Predictive Analytics with Machine Learning

Predictive Analytics with Machine Learning

Lopez Leonardo

Enunciado

El Ayuntamiento de Madrid ha publicado los datos de 2018 referentes a la flota de taxis de la ciudad, y cuenta con más de 15.000 vehículos. Cada licencia puede tener adscrito un único vehículo para la prestación del servicio. Este conjunto de datos se ofrece en un fichero junto con los datos históricos de la flota de taxi indicando para cada vehículo, su matrícula, marca, modelo, combustible, potencia, número de plazas, etc. El campo Fecha indica la fecha en que se realizó la extracción de la flota mensual.

Para este Ayuntamiento construyo un algoritmo en R que clasifique medioambientalmente dicha flota según su grado de contaminación. Para simplificar dicho trabajo, la contaminación se medirá a partir del tipo de combustible, y la Fecha de Matriculación (Año).

Fase 1: Planteamiento del problema

A modo introductorio para concientización, podemos reflexionar con el siguiente video de solo 5 minutos que explica los efectos graves en la salud:

Planteamiento del problema en forma visual:

Fase 2: Carga de datos

rm(list = ls()) # Comienzo limpiando la memoria

# Luego instalo paquetes y llamo las librerías necesarias
#------------------------------------------------------------------------------

#install.packages("caTools")
library(caTools) # Permite partir el conjunto de datos 
## Warning: package 'caTools' was built under R version 4.0.5
#install.packages("e1071")
library(e1071) # Cargo la librería que utiliza el SVM
## Warning: package 'e1071' was built under R version 4.0.5
#install.packages("dplyr")
library(dplyr)  #Proporciona "gramática" para manipulación y operaciones con data frames
## Warning: package 'dplyr' was built under R version 4.0.5
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
#install.packages("rpart")
library(rpart) # Libreria para el árbol de decision
## Warning: package 'rpart' was built under R version 4.0.5
#install.packages("rpart.plot")
library(rpart.plot) # Libreria para visualizar el árbol de decisión
## Warning: package 'rpart.plot' was built under R version 4.0.5
#install.packages("randomForest")
library(randomForest) # Librería para Random Forest
## Warning: package 'randomForest' was built under R version 4.0.5
## randomForest 4.6-14
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## The following object is masked from 'package:dplyr':
## 
##     combine
#install.packages("party")
library(party) # Librería para visualizar el árbol
## Warning: package 'party' was built under R version 4.0.5
## Loading required package: grid
## Loading required package: mvtnorm
## Loading required package: modeltools
## Loading required package: stats4
## Loading required package: strucchange
## Warning: package 'strucchange' was built under R version 4.0.5
## Loading required package: zoo
## Warning: package 'zoo' was built under R version 4.0.5
## 
## Attaching package: 'zoo'
## The following objects are masked from 'package:base':
## 
##     as.Date, as.Date.numeric
## Loading required package: sandwich
## Warning: package 'sandwich' was built under R version 4.0.5
# Datos de la Flota de Taxis de la ciudad de Madrid.

taxiFlota<-read.csv("http://www.diegocalvo.es/wp-content/uploads/2019/11/taxiFlota.csv",sep = ";",header=TRUE)
head(taxiFlota)  # Observamos la cabecera
##    Código Matrícula Fecha.Matriculación     Marca Modelo Tipo Variante
## 1 1198109   0800GZC          18/10/2010 CHEVROLET  EPICA KLAL  LV3/134
## 2 1196804   7686GYS          23/09/2010 CHEVROLET  EPICA KLAL  LV1/111
## 3 1182347   5440GTD          02/02/2010 CHEVROLET  EPICA KLAL  LV3/134
## 4 1189908   3259GWN          21/05/2010 CHEVROLET  EPICA KLAL  LV3/134
## 5 1180158   9107GSN          29/12/2009 CHEVROLET  EPICA KLAL  LV3/134
## 6 1194793   2463GYJ          18/08/2010 CHEVROLET  EPICA KLAL  LV3/134
##   Clasificación.medioambiental               Combustible Cilindrada Potencia
## 1                            B                    DIESEL       1991      110
## 2                            C GASOLINA TRANSFORMADO GLP       1993      105
## 3                            B                    DIESEL       1991      110
## 4                            B                    DIESEL       1991      110
## 5                            B                    DIESEL       1991      110
## 6                            B                    DIESEL       1991      110
##   Número.de.Plazas Fecha.inicio.de.prestación.del.servicio.de.taxi Eurotaxi
## 1                5                                      25/10/2010       NO
## 2                5                                      04/10/2010       NO
## 3                5                                      04/02/2010       NO
## 4                5                                      24/05/2010       NO
## 5                5                                      04/01/2010       NO
## 6                5                                      19/08/2010       NO
##   Régimen.Especial.de.Eurotaxi Fecha.inicio.Régimen.Especial.Eurotaxi
## 1                           NO                                       
## 2                           NO                                       
## 3                           NO                                       
## 4                           NO                                       
## 5                           NO                                       
## 6                           NO                                       
##   Fecha.fin.Régimen.Especial.Eurotaxi      Fecha
## 1                                     30/07/2018
## 2                                     30/07/2018
## 3                                     30/07/2018
## 4                                     30/07/2018
## 5                                     30/07/2018
## 6                                     30/07/2018

Fase 3: Estudio de datos

Preparación y eliminación de filas incompletas

#Utilizamos la funcion summary() para ver qué datos tenemos
summary(taxiFlota)
##      Código         Matrícula         Fecha.Matriculación    Marca          
##  Min.   : 550160   Length:46918       Length:46918        Length:46918      
##  1st Qu.:1288351   Class :character   Class :character    Class :character  
##  Median :1351791   Mode  :character   Mode  :character    Mode  :character  
##  Mean   :1356890                                                            
##  3rd Qu.:1438629                                                            
##  Max.   :1524506                                                            
##                                                                             
##     Modelo              Tipo             Variante        
##  Length:46918       Length:46918       Length:46918      
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
##                                                          
##                                                          
##  Clasificación.medioambiental Combustible          Cilindrada      Potencia  
##  Length:46918                 Length:46918       Min.   :   0   Min.   :  0  
##  Class :character             Class :character   1st Qu.:1598   1st Qu.: 73  
##  Mode  :character             Mode  :character   Median :1598   Median : 77  
##                                                  Mean   :1684   Mean   : 79  
##                                                  3rd Qu.:1798   3rd Qu.: 85  
##                                                  Max.   :3498   Max.   :193  
##                                                                 NA's   :5    
##  Número.de.Plazas   Fecha.inicio.de.prestación.del.servicio.de.taxi
##  Length:46918       Length:46918                                   
##  Class :character   Class :character                               
##  Mode  :character   Mode  :character                               
##                                                                    
##                                                                    
##                                                                    
##                                                                    
##    Eurotaxi         Régimen.Especial.de.Eurotaxi
##  Length:46918       Length:46918                
##  Class :character   Class :character            
##  Mode  :character   Mode  :character            
##                                                 
##                                                 
##                                                 
##                                                 
##  Fecha.inicio.Régimen.Especial.Eurotaxi Fecha.fin.Régimen.Especial.Eurotaxi
##  Length:46918                           Length:46918                       
##  Class :character                       Class :character                   
##  Mode  :character                       Mode  :character                   
##                                                                            
##                                                                            
##                                                                            
##                                                                            
##     Fecha          
##  Length:46918      
##  Class :character  
##  Mode  :character  
##                    
##                    
##                    
## 

Eliminación de nulos

#Contamos número de nulos por columna
sapply(taxiFlota, function(x) sum(is.na(x))) 
##                                          Código 
##                                               0 
##                                       Matrícula 
##                                               0 
##                             Fecha.Matriculación 
##                                               0 
##                                           Marca 
##                                               0 
##                                          Modelo 
##                                               0 
##                                            Tipo 
##                                               0 
##                                        Variante 
##                                               0 
##                    Clasificación.medioambiental 
##                                               0 
##                                     Combustible 
##                                               0 
##                                      Cilindrada 
##                                               0 
##                                        Potencia 
##                                               5 
##                                Número.de.Plazas 
##                                               0 
## Fecha.inicio.de.prestación.del.servicio.de.taxi 
##                                               0 
##                                        Eurotaxi 
##                                               0 
##                    Régimen.Especial.de.Eurotaxi 
##                                               0 
##          Fecha.inicio.Régimen.Especial.Eurotaxi 
##                                               0 
##             Fecha.fin.Régimen.Especial.Eurotaxi 
##                                               0 
##                                           Fecha 
##                                               0
#Borramos todas las filas con nulos
taxiFlota <- na.omit(taxiFlota)
#Corroboramos el número de nulos por columna
sapply(taxiFlota, function(x) sum(is.na(x))) 
##                                          Código 
##                                               0 
##                                       Matrícula 
##                                               0 
##                             Fecha.Matriculación 
##                                               0 
##                                           Marca 
##                                               0 
##                                          Modelo 
##                                               0 
##                                            Tipo 
##                                               0 
##                                        Variante 
##                                               0 
##                    Clasificación.medioambiental 
##                                               0 
##                                     Combustible 
##                                               0 
##                                      Cilindrada 
##                                               0 
##                                        Potencia 
##                                               0 
##                                Número.de.Plazas 
##                                               0 
## Fecha.inicio.de.prestación.del.servicio.de.taxi 
##                                               0 
##                                        Eurotaxi 
##                                               0 
##                    Régimen.Especial.de.Eurotaxi 
##                                               0 
##          Fecha.inicio.Régimen.Especial.Eurotaxi 
##                                               0 
##             Fecha.fin.Régimen.Especial.Eurotaxi 
##                                               0 
##                                           Fecha 
##                                               0

Preparación y eliminación de filas duplicadas

Hay duplicados?
#Contamos el número de filas
nrow(taxiFlota) 
## [1] 46913
#Eliminamos las filas con matrículas repetidas, y me quedo con la carga más actual
MaxFecha_Matricula <- taxiFlota %>% group_by(Matrícula) %>% summarise(Fecha=max(Fecha))
#Actualizamos los datos del conjunto sin duplicados
taxiFlota <- merge(x=taxiFlota, y=MaxFecha_Matricula, by=c('Matrícula','Fecha'))
#Contamos el nuevo número de filas
nrow(taxiFlota) 
## [1] 15967

Sección optativa de análisis exploratorio de los datos

Como aporte me resultó interesante complementar exploraciones en la herramienta de Rapidminer, donde podemos observar en el siguiente histograma una representación de las cantidades o frecuencias con que se matriculaban por fecha, los vehículos según su tipo de combustible diferenciado en colores:

Correlación entre variables mediante un mapa de calor

Pintando el mapa de calor en R
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.0.5
## 
## Attaching package: 'ggplot2'
## The following object is masked from 'package:randomForest':
## 
##     margin
ggplot(taxiFlota, aes(x=Clasificación.medioambiental, y=Marca) ) +
geom_bin2d(bins = 7) +
scale_fill_continuous(type = "viridis") +
theme_bw()
Pintando el mapa de calor en RapidMiner

Aquí pudimos obervar a simple vista algo imposible de ver en la planilla de miles de registros. Una distribución de las cantidades de marcas vehículos según su tipo de grado de contaminación, donde afortunadamente la mayoría son los de mayor temperatura en rojo son Toyota y relacionados al tipo ECO.

Distribución de unas variables con respecto a otras

# Distribución de la clasificación medioambiental respecto al combustible
library(ggplot2)
ggplot(taxiFlota, aes(x=Clasificación.medioambiental, y=Combustible) ) +
  geom_bin2d(bins = 7) +
  scale_fill_continuous(type = "viridis") +
  theme_bw()

Fase 4: Estudio y comparación de algoritmos de clasificación:

SVM (Support Vector Machine)

Las Máquinas de Vectores de Soporte son modelos capaces de generar clasificaciones o regresiones de datos no lineales a partir de la transformación de los datos de entrada a otros espacios de mayores dimensiones. En el caso de la clasificación, la SVM busca encontrar aquella curva que sea capaz de separar y clasificar los datos de entrenamiento garantizando que la separación entre ésta y ciertas observaciones del conjunto de entrenamiento (los vectores de soporte) sea la mayor posible.

# Preparo los datos
#------------------------------------------------------------------------------
# Fijo una semilla para que los datos aleatorios sean siempre los mismos
set.seed(1234)  

#Me quedo ahora con las variables predictoras planteadas por el enunciado

frame2 <- taxiFlota%>%dplyr::select("Clasificación.medioambiental","Combustible","Fecha.Matriculación")
anio <- as.Date(frame2$Fecha.Matriculación, format("%d/%m/%Y"))
frame2$Fecha.Matriculación <- format(anio,"%Y")
str(frame2)
## 'data.frame':    15967 obs. of  3 variables:
##  $ Clasificación.medioambiental: chr  "B" "B" "ECO" "B" ...
##  $ Combustible                 : chr  "DIESEL" "DIESEL" "GLP / GASOLINA" "DIESEL" ...
##  $ Fecha.Matriculación         : chr  "2011" "2013" "2017" "2011" ...
frame2$Clasificación.medioambiental <- as.factor(frame2$Clasificación.medioambiental)

str(frame2)
## 'data.frame':    15967 obs. of  3 variables:
##  $ Clasificación.medioambiental: Factor w/ 4 levels "0","B","C","ECO": 2 2 4 2 4 4 4 4 2 2 ...
##  $ Combustible                 : chr  "DIESEL" "DIESEL" "GLP / GASOLINA" "DIESEL" ...
##  $ Fecha.Matriculación         : chr  "2011" "2013" "2017" "2011" ...
# Partir el conjunto de datos en 2 partes
split <- sample.split(frame2$Clasificación.medioambiental, SplitRatio = 0.75)

# 1) Entrenamiento
training <- subset(frame2, split == TRUE)


# 2) Test
test <- subset(frame2, split == FALSE)


# EXPLORAR DATOS
#------------------------------------------------------------------------------

# Explorar en detalle los datos de entrenamiento
summary(training )
##  Clasificación.medioambiental Combustible        Fecha.Matriculación
##  0  :  11                     Length:11975       Length:11975       
##  B  :3813                     Class :character   Class :character   
##  C  :2793                     Mode  :character   Mode  :character   
##  ECO:5358
# Exprorar los datos de test
nrow(training)
## [1] 11975
nrow(test)
## [1] 3992
Definiendo el modelo
attach(training) # Permite usar de forma directa las variables

model_svm <- svm(training$Clasificación.medioambiental ~ ., 
             data = training, # Se indica el conjunto de datos a utilizar
             type = 'C-classification',  # Indica el tipo de clasificador 
             scale = FALSE,
             cross = 10,
             kernel = 'radial') # Indica el tipo de núcleo utilizado 


summary(model_svm)  # Muestro un resumen del modelo
## 
## Call:
## svm(formula = training$Clasificación.medioambiental ~ ., data = training, 
##     type = "C-classification", cross = 10, kernel = "radial", scale = FALSE)
## 
## 
## Parameters:
##    SVM-Type:  C-classification 
##  SVM-Kernel:  radial 
##        cost:  1 
## 
## Number of Support Vectors:  589
## 
##  ( 259 50 269 11 )
## 
## 
## Number of Classes:  4 
## 
## Levels: 
##  0 B C ECO
## 
## 10-fold cross-validation on training data:
## 
## Total Accuracy: 98.3382 
## Single Accuracies:
##  98.24561 98.58097 98.57978 98.08013 98.74687 98.33055 98.49624 97.66277 98.24561 98.41402
Predicción del modelo
# Realizar la prediccion
prediction_svm <- predict(model_svm, newdata = test)

# Muestra las predicciones de los test por el identificador del elemento
head(prediction_svm)
##   3  10  11  25  32  33 
## ECO   B   C ECO ECO ECO 
## Levels: 0 B C ECO
Evaluación del modelo con Matriz de confusión
# Definir la matriz de confusión
confusionMatrix_svm <- table(test$Clasificación.medioambiental, prediction_svm)
confusionMatrix_svm
##      prediction_svm
##          0    B    C  ECO
##   0      4    0    0    0
##   B      0 1232   39    0
##   C      0   28  903    0
##   ECO    0    0    0 1786
# Analizar porcentaje de acierto. Dividir la diagonal entre el total de datos de test
(correctos_svm <- sum(diag(confusionMatrix_svm)) / nrow(test) *100)
## [1] 98.32164

Conclusión: el algoritmo predice con un alto porcentate del 98% lógicamente dado por la gran relación que existe entre las variables predictoras con la que deseamos estimar ya que por ejemplo está estrechamente relacionado el tipo de combustible con el grado de contaminación.

ARBOLES DE DECISION

Predicción del modelo
model_tree<- rpart(training$Clasificación.medioambiental ~ ., # Se indican las variables  
             data = training) # Se indica el conjunto de datos a utilizar

# Muestra un resumen del modelo
summary(model_tree)
## Call:
## rpart(formula = training$Clasificación.medioambiental ~ ., data = training)
##   n= 11975 
## 
##          CP nsplit  rel error     xerror        xstd
## 1 0.5759408      0 1.00000000 1.00000000 0.008223052
## 2 0.3858244      1 0.42405924 0.42405924 0.007004964
## 3 0.0100000      2 0.03823485 0.03823485 0.002378276
## 
## Variable importance
##         Combustible Fecha.Matriculación 
##                  53                  47 
## 
## Node number 1: 11975 observations,    complexity param=0.5759408
##   predicted class=ECO  expected loss=0.5525678  P(node) =1
##     class counts:    11  3813  2793  5358
##    probabilities: 0.001 0.318 0.233 0.447 
##   left son=2 (6619 obs) right son=3 (5356 obs)
##   Primary splits:
##       Combustible         splits as  LLLRRLR,      improve=4468.234, (0 missing)
##       Fecha.Matriculación splits as  LLLLLLLLRRRR, improve=1612.100, (0 missing)
##   Surrogate splits:
##       Fecha.Matriculación splits as  LLLLRRLLRLLR, agree=0.678, adj=0.281, (0 split)
## 
## Node number 2: 6619 observations,    complexity param=0.3858244
##   predicted class=B    expected loss=0.4239311  P(node) =0.5527349
##     class counts:    11  3813  2793     2
##    probabilities: 0.002 0.576 0.422 0.000 
##   left son=4 (3853 obs) right son=5 (2766 obs)
##   Primary splits:
##       Fecha.Matriculación splits as  LLLLLLLLRRRR, improve=2756.32700, (0 missing)
##       Combustible         splits as  LLR--R-,      improve=  31.38274, (0 missing)
##   Surrogate splits:
##       Combustible splits as  LRR--L-, agree=0.585, adj=0.006, (0 split)
## 
## Node number 3: 5356 observations
##   predicted class=ECO  expected loss=0  P(node) =0.4472651
##     class counts:     0     0     0  5356
##    probabilities: 0.000 0.000 0.000 1.000 
## 
## Node number 4: 3853 observations
##   predicted class=B    expected loss=0.03633532  P(node) =0.3217537
##     class counts:     0  3713   140     0
##    probabilities: 0.000 0.964 0.036 0.000 
## 
## Node number 5: 2766 observations
##   predicted class=C    expected loss=0.04085322  P(node) =0.2309812
##     class counts:    11   100  2653     2
##    probabilities: 0.004 0.036 0.959 0.001
# Visualiza el árbol de decision
rpart.plot(model_tree)
Predicción del modelo
# Realizar la prediccion
prediction_tree <- predict(model_tree, newdata = test, type = "class")

# Muestra las predicciones de los test por el identificador del elemento
head(prediction_tree)
##   3  10  11  25  32  33 
## ECO   B   C ECO ECO ECO 
## Levels: 0 B C ECO
Evaluación del modelo con Matriz de confusión
# Definir la matriz de confusión
confusionMatrix_tree <- table(test$Clasificación.medioambiental, prediction_tree)
confusionMatrix_tree
##      prediction_tree
##          0    B    C  ECO
##   0      0    1    3    0
##   B      0 1232   39    0
##   C      0   37  894    0
##   ECO    0    0    0 1786
# Analizar porcentaje de acierto. Dividir la diagonal entre el total de datos de test
(correctos_tree <- sum(diag(confusionMatrix_tree)) / nrow(test) *100)
## [1] 97.99599

BOSQUES ALEATORIOS (Random Forest)

Predicción del modelo
model_ran<- randomForest(training$Clasificación.medioambiental ~ ., # Las variables  
             data = training,
             ntree = 50) # Número de árboles

# Muestra un resumen del modelo
summary(model_ran)
##                 Length Class  Mode     
## call                4  -none- call     
## type                1  -none- character
## predicted       11975  factor numeric  
## err.rate          250  -none- numeric  
## confusion          20  -none- numeric  
## votes           47900  matrix numeric  
## oob.times       11975  -none- numeric  
## classes             4  -none- character
## importance          2  -none- numeric  
## importanceSD        0  -none- NULL     
## localImportance     0  -none- NULL     
## proximity           0  -none- NULL     
## ntree               1  -none- numeric  
## mtry                1  -none- numeric  
## forest             14  -none- list     
## y               11975  factor numeric  
## test                0  -none- NULL     
## inbag               0  -none- NULL     
## terms               3  terms  call
Predicción del modelo
# Realizar la prediccion
prediction_ran <- predict(model_ran, newdata = test, type = "class")

# Muestra las predicciones de los test por el identificador del elemento
head(prediction_ran)
##   3  10  11  25  32  33 
## ECO   B   C ECO ECO ECO 
## Levels: 0 B C ECO
Evaluación del modelo con Matriz de confusión
# Definir la matriz de confusión
confusionMatrix_ran <- table(test$Clasificación.medioambiental, prediction_ran)
confusionMatrix_ran
##      prediction_ran
##          0    B    C  ECO
##   0      3    1    0    0
##   B      0 1232   39    0
##   C      0   28  902    1
##   ECO    0    0    0 1786
# Analizar porcentaje de acierto. Dividir la diagonal entre el total de datos de test
(correctos_ran <- sum(diag(confusionMatrix_ran)) / nrow(test) *100)
## [1] 98.27154

CUANTIFICADOR BAYESIANO INGENUO

Predicción del modelo
model_bay<- naiveBayes(training$Clasificación.medioambiental ~ ., # Las variables  
             data = training,
             laplace = 0) # Deshabilito el suavizado de Laplace

# Muestra un resumen del modelo
summary(model_bay)
##           Length Class  Mode     
## apriori   4      table  numeric  
## tables    2      -none- list     
## levels    4      -none- character
## isnumeric 2      -none- logical  
## call      4      -none- call
Predicción del modelo
# Realizar la prediccion
prediction_bay <- predict(model_bay, newdata = test, type = "class")

# Muestra las predicciones de los test por el identificador del elemento
head(prediction_bay)
## [1] ECO B   C   ECO ECO ECO
## Levels: 0 B C ECO
Evaluación del modelo con Matriz de confusión
# Definir la matriz de confusión
confusionMatrix_bay <- table(test$Clasificación.medioambiental, prediction_bay)
confusionMatrix_bay
##      prediction_bay
##          0    B    C  ECO
##   0      1    1    0    2
##   B      0 1232   39    0
##   C      0   30  901    0
##   ECO    0    0    0 1786
# Analizar porcentaje de acierto. Dividir la diagonal entre el total de datos de test
(correctos_bay <- sum(diag(confusionMatrix_bay)) / nrow(test) *100)
## [1] 98.19639

Fase 5: Análisis de resultados obtenidos

Existen varias métricas a observar al momento de optar por un modelo, pero para nuestro caso que no son dicotómicas tampoco analizamos la curva ROC, sino que determinaremos el algoritmo de mejor estimación al target en base a la Matriz de confusión. En el apartado previo ya la calculamos para cada caso, y obtuvimos su porcentaje de aciertos.

Desde mi opinión no observé grandes diferencias muy relevantes para discriminar cuál será el mejor algoritmo, por lo que me interesó investigar un poco más y utiliar el aporte de otra herramienta más para comprobar mis calculos: Alteryx

Con la extención de Alterix Predictive, pude modelar los 4 algoritmos cuyos resultados ingresan al procesamiento del bloque comparador del que obtuve el siguiente reporte:

Como conclusiones finales pude comprobar los mismos resultados en los que nuevamente el de mejor performance resultó Support Vector Machine, con un alto acierto del 98,32% con la lógica esperable ya que las variables predictoras como el tipo de combustible están estrechamente relacionadas con el grado de contaminación.