Esta práctica se basa en una de las aplicaciones de algoritmos de clasificación, desarrolladas con K-NN y árboles de decisión.
K-NN (o k-vecinos) es un sistema de clasificación basado en la comparación de instancias nuevas con instancias presentes en el juego de datos de entrenamiento. La construcción de grupos se realiza a partir del propio juego de datos de entrenamiento, tomando como referencia el parámetro k indicado por el investigador. El algoritmo de los K vecinos más cercanos es uno de los algoritmos más simples que existen que muestran la esencia del aprendizaje basado en instancias. Este algoritmo asume que todas las instancias corresponden a puntos que se encuentran en un espacio de dimensión n. El vecino más cercano de una instancia es definido en términos de la distancia Euclidiana estándar.
Los árboles de decisión son algoritmos que construyen modelos de decisión que forman estructuras similares a los diagramas de flujo donde los nodos internos suelen ser puntos de decisión sobre un atributo del juego de datos. Son muy dependientes del concepto de ganancia de la información ya que es el criterio que utilizan para construir las ramificaciones del árbol. A grandes rasgos existen dos tipos de árboles de decisión: * Árboles de decisión simples: el resultado se construye mediante un proceso de clasificación. * Árboles de decisión múltiples (random forest): el resultado se construye mediante el desarrollo iterativo de n procesos de clasificación.
Recursos en la web:
A Detailed Introduction to K-Nearest Neighbor (K-NN) Algorithm
Formamos parte de la Dirección Comercial de la Bodega de vinos Mureda y queremos analizar la actividad de nuestra red de ventas, formada por tres categorías de comerciales (A, B y C). Para ello, estamos interesados en conocer si existen diferencias en la actividad generada por cada uno de los comerciales y, en caso afirmativo, identificar cuáles son las variables que más contribuyen a dichas diferencias y si podemos predecir a qué categoría de comercial pertenece un nuevo empleado en función de su actividad.
Para ello, nuestro equipo de análisis dispone de un fichero con información de 150 clientes que recoge estadísticas de actividad de los tres grupos de Comerciales, a razón de 50 registros por grupo de comercial. El fichero contiene información de las siguientes variables:
Importe: Volumen de Facturación en el Cliente (vinculado a una categoría de Comercial)
Margen: Margen por Cliente (vinculado a una categoría de Comercial)
Km: Kilómetros recorridos para visitar al Cliente.
Visitas: Visitas realizadas al Cliente.
Comercial: Categoría de comercial asignada al cliente (Toma valores A, B ó C)
Una vez definidos los objetivos de nuestra investigación, nuestro departamento de análisis nos propone desarrollar un proceso de clasificación que aplique tres tipos de algoritmos complementarios: K-NN, Árboles de decisión simples y Árboles de clasificación múltiples (random forest) sobre nuestro fichero de datos.
El código R que utilizaremos en la práctica se divide en apartados según las tareas que iremos realizando:
Apartados práctica:
Carga de paquetes necesarios y fichero
Análisis univariable y bivariable del fichero K-NN
Descriptivos de las variables del fichero
Estudio de la relación entre variables
Comparación de las variables por tipo de Comercial
Clasificación de los clientes con K-NN
Construcción del Modelo de clasificación con K-NN
Validación del Modelo de clasificación con K-NN
Clasificación de los clientes con árboles de decisión simples
Construcción del Modelo de clasificación con el paquete rpart
Validación del Modelo de clasificación con el paquete rpart
Clasificación de los clientes con árboles de decisión múltiples (random forest)
Construcción del Modelo de clasificación con el paquete randomForest
Validación del Modelo de clasificación con el paquete randomForest
Empezaremos por cargar los packages R que necesitaremos tener en memoria.
Cargamos también los datos ubicados en el fichero PEC2.csv
#revisamos si es necesario instalar los paquetes necesarios para desarrollar la PEC 2
#install.packages("ggplot2")
#install.packages("rpart.plot")
#install.packages("useful")
#install.packages("randomForest")
#cargamos los paquetes necesarios para desarrollar la PEC 2
# Para representar gráficamente la relación entre variables
library("ggplot2")
# Para clasificar con K-NN
library("class")
# Para clasificar con rpart
library("rpart")
library("rpart.plot")
# Para clasificar con randomForest
library("useful")
library("randomForest")
#cargamos el fichero de datos que utilizamos para desarrollar la PEC 2
nombreruta_PEC2 <- paste(getwd(),"/PEC2.csv", sep = "")
Data_PEC2 <- read.csv(nombreruta_PEC2, encoding="UTF-8",
header=TRUE, sep=",", na.strings="NA", dec=".", strip.white=TRUE)
La primera fase del análisis consiste siempre en un análisis descriptivo de las variables incluidas en el fichero y de la relación existente entre ellas. Para ello, aplicamos la siguiente secuencia de cálculos y representaciones gráficas.
# 1.Calculamos los descriptivos univariables de las variables del fichero
summary(Data_PEC2) #Estadísticos descriptivos básicos de las variables
## Ingresos Margen Km Visitas Comercial
## Min. :4300 Min. :200.0 Min. :10.00 Min. : 1.00 A:50
## 1st Qu.:5100 1st Qu.:280.0 1st Qu.:16.00 1st Qu.: 3.00 B:50
## Median :5800 Median :300.0 Median :43.50 Median :13.00 C:50
## Mean :5843 Mean :305.4 Mean :37.59 Mean :11.99
## 3rd Qu.:6400 3rd Qu.:330.0 3rd Qu.:51.00 3rd Qu.:18.00
## Max. :7900 Max. :440.0 Max. :69.00 Max. :25.00
Las 5 variables presentan una distribución bastante homogénea:
Importe: Promedio de 5.843€, Máximo 7.900€, Mínimo 4.300€
Margen: Promedio de 305,4€, Máximo 440€, Mínimo 200€
Km: Promedio de 37,59€, Máximo 69€, Mínimo 10€
Visitas: Promedio de 11,99€, Máximo 25€, Mínimo 1€
Comercial: 50 observaciones por comercial
# 2.Representamos gráficamente las variables del fichero mediante histogramas
#Histograma Ingresos
f1 <- hist(Data_PEC2$Ingresos, main="Histograma Ingresos", col = "gray", labels = TRUE)
f1
## $breaks
## [1] 4000 4500 5000 5500 6000 6500 7000 7500 8000
##
## $counts
## [1] 5 27 27 30 31 18 6 6
##
## $density
## [1] 6.666667e-05 3.600000e-04 3.600000e-04 4.000000e-04 4.133333e-04
## [6] 2.400000e-04 8.000000e-05 8.000000e-05
##
## $mids
## [1] 4250 4750 5250 5750 6250 6750 7250 7750
##
## $xname
## [1] "Data_PEC2$Ingresos"
##
## $equidist
## [1] TRUE
##
## attr(,"class")
## [1] "histogram"
#Histograma Margen
f2 <- hist(Data_PEC2$Margen, main="Histograma Margen", col = "gray", labels = TRUE)
f2
## $breaks
## [1] 200 220 240 260 280 300 320 340 360 380 400 420 440
##
## $counts
## [1] 4 7 13 23 36 25 18 9 9 3 2 1
##
## $density
## [1] 0.0013333333 0.0023333333 0.0043333333 0.0076666667 0.0120000000
## [6] 0.0083333333 0.0060000000 0.0030000000 0.0030000000 0.0010000000
## [11] 0.0006666667 0.0003333333
##
## $mids
## [1] 210 230 250 270 290 310 330 350 370 390 410 430
##
## $xname
## [1] "Data_PEC2$Margen"
##
## $equidist
## [1] TRUE
##
## attr(,"class")
## [1] "histogram"
#Histograma Km
f3 <- hist(Data_PEC2$Km, main="Histograma Km", col = "gray", labels = TRUE)
f3
## $breaks
## [1] 10 15 20 25 30 35 40 45 50 55 60 65 70
##
## $counts
## [1] 37 13 0 1 4 11 21 21 17 16 5 4
##
## $density
## [1] 0.049333333 0.017333333 0.000000000 0.001333333 0.005333333
## [6] 0.014666667 0.028000000 0.028000000 0.022666667 0.021333333
## [11] 0.006666667 0.005333333
##
## $mids
## [1] 12.5 17.5 22.5 27.5 32.5 37.5 42.5 47.5 52.5 57.5 62.5 67.5
##
## $xname
## [1] "Data_PEC2$Km"
##
## $equidist
## [1] TRUE
##
## attr(,"class")
## [1] "histogram"
#Histograma Visitas
f4 <- hist(Data_PEC2$Visitas, main="Histograma Visitas", col = "gray", labels = TRUE)
f4
## $breaks
## [1] 0 2 4 6 8 10 12 14 16 18 20 22 24 26
##
## $counts
## [1] 34 14 2 0 7 8 21 16 14 11 9 11 3
##
## $density
## [1] 0.113333333 0.046666667 0.006666667 0.000000000 0.023333333
## [6] 0.026666667 0.070000000 0.053333333 0.046666667 0.036666667
## [11] 0.030000000 0.036666667 0.010000000
##
## $mids
## [1] 1 3 5 7 9 11 13 15 17 19 21 23 25
##
## $xname
## [1] "Data_PEC2$Visitas"
##
## $equidist
## [1] TRUE
##
## attr(,"class")
## [1] "histogram"
#Histograma Comercial
f5 <- plot(Data_PEC2$Comercial)
f5
## [,1]
## [1,] 0.7
## [2,] 1.9
## [3,] 3.1
Las variables cuantitativas presentan dos distribuciones diferenciadas:
Importe y Margen presentan una distribución similar a una campana de Gauss, algo más concentrada en el caso de Margen
Km y Visitas presentan una distribución muy similar. Con una alta concentración para valores bajos que desciende rápidamente para volver a crecer siguiendo una campana de Gauss a partir del tercer valor de la serie.
# 3.Estudiamos la relación existente entre las variables del fichero
# Estudiamos la relación entre variables mediante gráficos de dispersión
f6<- plot(Data_PEC2)
f6
## NULL
# Estudiamos la relación entre variables cuantitativas mediante correlaciones
cor(Data_PEC2[,c("Ingresos","Margen","Km","Visitas")], use="complete")
## Ingresos Margen Km Visitas
## Ingresos 1.0000000 -0.1093692 0.8717542 0.8179536
## Margen -0.1093692 1.0000000 -0.4205161 -0.3565441
## Km 0.8717542 -0.4205161 1.0000000 0.9627571
## Visitas 0.8179536 -0.3565441 0.9627571 1.0000000
Analizando los gráficos de dispersión, apuntamos una fuerte relación entre Visitas-Km, Ingresos-Km, Margen-Km e Ingresos-Visitas que podemos validar con el coeficiente de correlación, estadístico que toma valores entre -1 y 1 y que mide la fuerza con la que dos variables quedan interrelacionadas (próximo a 1 cuando la relación es fuertemente directa y próximo a -1 cuando la relación es fuertemente inversa)
Coeficiente de Correlación Visitas-Km -> (0,96)
Coeficiente de Correlación Ingresos-Km -> (0,87)
Coeficiente de Correlación Ingresos-Visitas -> (0,82)
Coeficiente de Correlación Margen-Km -> (-0,42)
# Estudiamos la relación entre variables Km y Visitas
f7<-ggplot(Data_PEC2, aes(x=Km, y=Visitas)) + geom_point()
f7
# Estudiamos la relación entre variables Km y Visitas con tamaño ingresos
f8<-ggplot(Data_PEC2, aes(x=Km, y=Visitas)) + geom_point(aes(size=Ingresos))
f8
# Relación entre variables Km y Visitas con tamaño margen
f9<-ggplot(Data_PEC2, aes(x=Km, y=Visitas)) + geom_point(aes(size=Margen))
f9
# Relación entre variables Km y Visitas con tamaño margen
fA<-ggplot(Data_PEC2, aes(x=Km, y=Margen)) + geom_point(aes(size=Ingresos))
fA
# 3.Estudiamos la existencia de diferencias por Comercial
# promedio variables por comercial
tapply(Data_PEC2$Ingresos,Data_PEC2$Comercial,mean)
## A B C
## 5006 5936 6588
tapply(Data_PEC2$Margen,Data_PEC2$Comercial,mean)
## A B C
## 341.8 277.0 297.4
tapply(Data_PEC2$Km,Data_PEC2$Comercial,mean)
## A B C
## 14.64 42.60 55.52
tapply(Data_PEC2$Visitas,Data_PEC2$Comercial,mean)
## A B C
## 2.44 13.26 20.26
Vemos que existen diferencias remarcables en el promedio de cada una de las variables para cada Comercial:
El Comercial C es el Comercial con un Importe promedio mayor, con una valor ligeramente superior al de B
El Comercial A es el Comercial con un Margen promedio mayor
El Comercial C es el Comercial que hace más Visitas en promedio
El Comercial C es el Comercial que hace más Km en promedio, con un valor que es prácticamente el doble que el del B
Graficamos a continuación las variables cuantitativas diferenciando por Comercial.
# Relación entre variables Km y Visitas con tamaño ingresos y Color según Comercial
f10<-ggplot(Data_PEC2, aes(x=Km, y=Visitas, color=Comercial)) + geom_point(aes(size=Ingresos))
f10
# Relación entre variables Km y Visitas con tamaño ingresos y Color según Comercial, línea tendencia y elipse
f11<-ggplot(Data_PEC2, aes(x=Km, y=Visitas, color=Comercial)) + geom_point(aes(size=Ingresos)) +
geom_smooth(method=lm, aes(fill=Comercial))+ stat_ellipse(type = "norm")
f11
Identificamos un comportamiento diferenciado donde Km y Visitas ya que son las variables que presentan una mayor capacidad de diferenciación.
Una vez analizado descriptivamente el fichero, consideramos necesario evaluar la capacidad predictiva de tres modelos predictivos:
K-Vecino próximo (K-NN)
Árboles de decisión simples
Árboles de decisión múltiples (random forest)
Con dicho objetivo, aplicaremos los algoritmos siguiendo la siguiente secuencia:
6 Clasificación de los clientes con K-NN
6.1 Construcción del Modelo de clasificación con _K-NN_
6.2 Validación del Modelo de clasificación con _K-NN_
7 Clasificación de los clientes con árboles de decisión simples
7.1 Construcción del Modelo de clasificación con el paquete _rpart_
7.2 Validación del Modelo de clasificación con el paquete _rpart_
8 Clasificación de los clientes con árboles de decisión múltiples (random forest)
8.1 Construcción del Modelo de clasificación con el paquete _randomForest_
8.2 Validación del Modelo de clasificación con el paquete _randomForest_
Construimos un juego de datos de entrenamiento con el 70% de registros para construir los modelos y un juego de datos de pruebas con el 30% de registros restantes para validar los modelos.
# Dividimos el fichero en 70% entreno y 30% validación #
set.seed(1234)
ind <- sample(2, nrow(Data_PEC2), replace=TRUE, prob=c(0.7, 0.3))
trainData <- Data_PEC2[ind==1,]
testData <- Data_PEC2[ind==2,]
Aplicamos el modelo K-NN, pasándole como parámetros la matriz de entrenamiento compuesta por las 4 variables cuantitativas : Importe, Margen, Km y Visitas. No le pasamos el campo Comercial porque precisamente es el campo que el algoritmo debe predecir.
Dado que el modelo K-NN permite replicar el modelo para n valores diferentes de k, repetimos el análisis para k=1,2,3 y 4.
# Aplicamos el algoritmo K-NN seleccionando 1 como k inicial
KnnTestPrediccion_k1 <- knn(trainData[,1:4],testData[,1:4], trainData$Comercial , k = 1, prob = TRUE )
# Visualizamos una matriz de confusión
table ( testData$Comercial , KnnTestPrediccion_k1 )
## KnnTestPrediccion_k1
## A B C
## A 10 0 0
## B 0 9 3
## C 0 6 10
# Calculamos el % de aciertos para k=1
sum(KnnTestPrediccion_k1 == testData$Comercial)/ length(testData$Comercial)*100
## [1] 76.31579
# Aplicamos el algoritmo K-NN seleccionando 2 como k inicial
KnnTestPrediccion_k2 <- knn(trainData[,1:4],testData[,1:4], trainData$Comercial , k = 2, prob = TRUE )
# Visualizamos una matriz de confusión
table ( testData$Comercial , KnnTestPrediccion_k2 )
## KnnTestPrediccion_k2
## A B C
## A 10 0 0
## B 2 8 2
## C 0 7 9
# Calculamos el % de aciertos para k=2
sum(KnnTestPrediccion_k2 == testData$Comercial)/ length(testData$Comercial)*100
## [1] 71.05263
# Aplicamos el algoritmo K-NN seleccionando 3 como k inicial
KnnTestPrediccion_k3 <- knn(trainData[,1:4],testData[,1:4], trainData$Comercial , k = 3, prob = TRUE )
# Visualizamos una matriz de confusión
table ( testData$Comercial , KnnTestPrediccion_k3 )
## KnnTestPrediccion_k3
## A B C
## A 10 0 0
## B 2 7 3
## C 0 8 8
# Calculamos el % de aciertos para k=3
sum(KnnTestPrediccion_k3 == testData$Comercial)/ length(testData$Comercial)*100
## [1] 65.78947
# Aplicamos el algoritmo K-NN seleccionando 4 como k inicial
KnnTestPrediccion_k4 <- knn(trainData[,1:4],testData[,1:4], trainData$Comercial , k = 4, prob = TRUE )
# Visualizamos una matriz de confusión
table ( testData$Comercial , KnnTestPrediccion_k4 )
## KnnTestPrediccion_k4
## A B C
## A 9 1 0
## B 2 7 3
## C 0 7 9
# Calculamos el % de aciertos para k=4
sum(KnnTestPrediccion_k4 == testData$Comercial)/ length(testData$Comercial)*100
## [1] 65.78947
Una vez aplicados el algoritmo para k=1,2,3 y 4. Mediante la matriz de confusión valoramos el nivel de acierto del modelo. Con dicho objetivo, estudiamos el % de acierto de cada uno de ellos con el objetivo de escoger el valor de k que permite obtener un % de clasificación correcta más alto:
para k=1 el porcentaje de aciertos es 76%
para k=2 el porcentaje de aciertos es 71%
para k=3 el porcentaje de aciertos es 66%
para k=4 el porcentaje de aciertos es 74%
En consecuencia tomamos el valor k=1 con un 76% de clasificación correcta.
Para construir un árbol de decisión es necesario definir una función que relaciona una variable categórica dependiente (factor) con n variables independientes que pueden ser categóricas o numéricas. En nuestro caso trabajaremos con:
1 variable factor dependiente -> Comercial
4 variables independientes -> Ingresos, Margen, Km y Visitas
El algoritmo de clasificación busca cuál es la variable que permite obtener una submuestra más diferenciada para la variable dependiente (Comercial en nuestro caso) e identifica también qué intervalos (si la variable es cuantitativa) ó agrupación de categorías de la/s variable/s independiente/s permitiría/n maximizar dicha división.
Una vez identificada la variable independiente que permite obtener la clasificación con una mayor capacidad de diferenciación, el proceso se repite reiterativamente en cada uno de los nodos obtenidos hasta que el algoritmo no encuentra diferencias significativas que le permitan seguir profundizando en los nodos.
Una vez obtenido una primera versión del árbol, existen algoritmos que permiten hacer un podado del árbol (prunning), eliminando aquellas ramas que no acaban de justificar su presencia de acuerdo con algunos parámetros preestablecidos.
En todos los casos seguiremos la siguiente secuencia de pasos para obtener los árboles de clasificación:
Definir la muestra de entrenamiento y la muestra de prueba
Definir la función que relaciona la variable dependiente con las variables independientes
Estimar el árbol de decisión
Representar gráficamente una primera versión del árbol
Estudiar la aplicación práctica del resultado obtenido
Podar el árbol (si el algoritmo admite podado)
Estudiamos a continuación la capacidad predictiva del árbol de decisión simple obtenido mediante el paquete rpart
# Dividimos el fichero en 70% entreno y 30% validación (parte recurrente en todo experimento)
set.seed(1234)
ind <- sample(2, nrow(Data_PEC2), replace=TRUE, prob=c(0.7, 0.3))
trainData <- Data_PEC2[ind==1,]
testData <- Data_PEC2[ind==2,]
#Declaramos función del árbol
ArbolRpart <- Comercial ~ Ingresos + Margen + Km + Visitas
#Aplicamos algoritmo
ArbolRpart_ctree <- rpart(ArbolRpart, method="class", data=trainData)
#Obtenemos la relación de reglas de asociación del árbol en formato listado
print(ArbolRpart_ctree) # estadísticas detalladas de cada nodo
## n= 112
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 112 72 A (0.3571429 0.3392857 0.3035714)
## 2) Km< 24.5 40 0 A (1.0000000 0.0000000 0.0000000) *
## 3) Km>=24.5 72 34 B (0.0000000 0.5277778 0.4722222)
## 6) Visitas< 17.5 40 3 B (0.0000000 0.9250000 0.0750000) *
## 7) Visitas>=17.5 32 1 C (0.0000000 0.0312500 0.9687500) *
#Obtenemos el árbol con un diseño gráfico cuidado
f13<-rpart.plot(ArbolRpart_ctree,extra=4) #visualizamos el árbol
f13
## $obj
## n= 112
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 112 72 A (0.3571429 0.3392857 0.3035714)
## 2) Km< 24.5 40 0 A (1.0000000 0.0000000 0.0000000) *
## 3) Km>=24.5 72 34 B (0.0000000 0.5277778 0.4722222)
## 6) Visitas< 17.5 40 3 B (0.0000000 0.9250000 0.0750000) *
## 7) Visitas>=17.5 32 1 C (0.0000000 0.0312500 0.9687500) *
##
## $snipped.nodes
## NULL
##
## $xlim
## [1] -0.2 1.2
##
## $ylim
## [1] -0.2 1.2
##
## $x
## [1] 0.39728386 0.07949514 0.71507258 0.50321344 0.92693173
##
## $y
## [1] 0.93468675 0.03376948 0.52517890 0.03376948 0.03376948
##
## $branch.x
## [,1] [,2] [,3] [,4] [,5]
## x 0.3972839 0.07949514 0.7150726 0.5032134 0.9269317
## NA 0.07949514 0.7150726 0.5032134 0.9269317
## NA 0.39728386 0.3972839 0.7150726 0.7150726
##
## $branch.y
## [,1] [,2] [,3] [,4] [,5]
## y 1.026378 0.1254608 0.6168702 0.1254608 0.1254608
## NA 0.8327290 0.8327290 0.4232212 0.4232212
## NA 0.8327290 0.8327290 0.4232212 0.4232212
##
## $labs
## [1] "A\n.36 .34 .30" "A\n1.00 .00 .00" "B\n.00 .53 .47"
## [4] "B\n.00 .92 .07" "C\n.00 .03 .97"
##
## $cex
## [1] 1
##
## $boxes
## $boxes$x1
## [1] 0.28054780 -0.04785837 0.59833652 0.38647737 0.81019567
##
## $boxes$y1
## [1] 0.88220993 -0.01870734 0.47270208 -0.01870734 -0.01870734
##
## $boxes$x2
## [1] 0.5140199 0.2068487 0.8318086 0.6199495 1.0436678
##
## $boxes$y2
## [1] 1.0263781 0.1254608 0.6168702 0.1254608 0.1254608
##
##
## $split.labs
## [1] ""
##
## $split.cex
## [1] 1 1 1 1 1
##
## $split.box
## $split.box$x1
## [1] 0.3076274 NA 0.5946265 NA NA
##
## $split.box$y1
## [1] 0.7935145 NA 0.3840067 NA NA
##
## $split.box$x2
## [1] 0.4869403 NA 0.8355187 NA NA
##
## $split.box$y2
## [1] 0.8719435 NA 0.4624357 NA NA
# Estudiamos la evolución del error a medida que el árbol va creciendo
summary(ArbolRpart_ctree) # estadísticas detalladas de cada nodo
## Call:
## rpart(formula = ArbolRpart, data = trainData, method = "class")
## n= 112
##
## CP nsplit rel error xerror xstd
## 1 0.5277778 0 1.00000000 1.15277778 0.06438675
## 2 0.4166667 1 0.47222222 0.55555556 0.07042952
## 3 0.0100000 2 0.05555556 0.08333333 0.03309688
##
## Variable importance
## Visitas Km Ingresos Margen
## 34 31 21 14
##
## Node number 1: 112 observations, complexity param=0.5277778
## predicted class=A expected loss=0.6428571 P(node) =1
## class counts: 40 38 34
## probabilities: 0.357 0.339 0.304
## left son=2 (40 obs) right son=3 (72 obs)
## Primary splits:
## Km < 24.5 to the left, improve=38.61111, (0 missing)
## Visitas < 8 to the left, improve=38.61111, (0 missing)
## Ingresos < 5450 to the left, improve=27.51111, (0 missing)
## Margen < 305 to the right, improve=16.75806, (0 missing)
## Surrogate splits:
## Visitas < 8 to the left, agree=1.000, adj=1.00, (0 split)
## Ingresos < 5450 to the left, agree=0.929, adj=0.80, (0 split)
## Margen < 335 to the right, agree=0.839, adj=0.55, (0 split)
##
## Node number 2: 40 observations
## predicted class=A expected loss=0 P(node) =0.3571429
## class counts: 40 0 0
## probabilities: 1.000 0.000 0.000
##
## Node number 3: 72 observations, complexity param=0.4166667
## predicted class=B expected loss=0.4722222 P(node) =0.6428571
## class counts: 0 38 34
## probabilities: 0.000 0.528 0.472
## left son=6 (40 obs) right son=7 (32 obs)
## Primary splits:
## Visitas < 17.5 to the left, improve=28.401390, (0 missing)
## Km < 47.5 to the left, improve=25.263500, (0 missing)
## Ingresos < 7050 to the left, improve= 6.469534, (0 missing)
## Margen < 295 to the left, improve= 2.688889, (0 missing)
## Surrogate splits:
## Km < 47.5 to the left, agree=0.917, adj=0.812, (0 split)
## Ingresos < 6350 to the left, agree=0.708, adj=0.344, (0 split)
## Margen < 295 to the left, agree=0.667, adj=0.250, (0 split)
##
## Node number 6: 40 observations
## predicted class=B expected loss=0.075 P(node) =0.3571429
## class counts: 0 37 3
## probabilities: 0.000 0.925 0.075
##
## Node number 7: 32 observations
## predicted class=C expected loss=0.03125 P(node) =0.2857143
## class counts: 0 1 31
## probabilities: 0.000 0.031 0.969
printcp(ArbolRpart_ctree) # estadísticas de resultados
##
## Classification tree:
## rpart(formula = ArbolRpart, data = trainData, method = "class")
##
## Variables actually used in tree construction:
## [1] Km Visitas
##
## Root node error: 72/112 = 0.64286
##
## n= 112
##
## CP nsplit rel error xerror xstd
## 1 0.52778 0 1.000000 1.152778 0.064387
## 2 0.41667 1 0.472222 0.555556 0.070430
## 3 0.01000 2 0.055556 0.083333 0.033097
plotcp(ArbolRpart_ctree) # evolución del error a medida que se incrementan los nodos
# Validamos la capacidad de predicción del árbol con el fichero de validación
testPredRpart <- predict(ArbolRpart_ctree, newdata = testData, type = "class")
# Visualizamos una matriz de confusión
table(testPredRpart, testData$Comercial)
##
## testPredRpart A B C
## A 10 0 0
## B 0 12 2
## C 0 0 14
# Calculamos el % de aciertos
sum(testPredRpart == testData$Comercial)/ length(testData$Comercial)*100
## [1] 94.73684
El árbol de decisión obtenido mediante el paquete rpart clasifica correctamente un 94,73% de los registros. Un resultado bastante alto y aceptable.
Una vez construida una primera versión del árbol, estudiamos la viabilidad de un podado de árbol.
# Podado del árbol
pArbolRpart_ctree<- prune(ArbolRpart_ctree, cp= ArbolRpart_ctree$cptable[which.min(ArbolRpart_ctree$cptable[,"xerror"]),"CP"])
pArbolRpart_ctree<- prune(ArbolRpart_ctree, cp= 0.02)
# Representación del árbol podado
f14<-rpart.plot(pArbolRpart_ctree,extra=4) #visualizamos el árbol
f14
## $obj
## n= 112
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 112 72 A (0.3571429 0.3392857 0.3035714)
## 2) Km< 24.5 40 0 A (1.0000000 0.0000000 0.0000000) *
## 3) Km>=24.5 72 34 B (0.0000000 0.5277778 0.4722222)
## 6) Visitas< 17.5 40 3 B (0.0000000 0.9250000 0.0750000) *
## 7) Visitas>=17.5 32 1 C (0.0000000 0.0312500 0.9687500) *
##
## $snipped.nodes
## NULL
##
## $xlim
## [1] -0.2 1.2
##
## $ylim
## [1] -0.2 1.2
##
## $x
## [1] 0.39728386 0.07949514 0.71507258 0.50321344 0.92693173
##
## $y
## [1] 0.93468675 0.03376948 0.52517890 0.03376948 0.03376948
##
## $branch.x
## [,1] [,2] [,3] [,4] [,5]
## x 0.3972839 0.07949514 0.7150726 0.5032134 0.9269317
## NA 0.07949514 0.7150726 0.5032134 0.9269317
## NA 0.39728386 0.3972839 0.7150726 0.7150726
##
## $branch.y
## [,1] [,2] [,3] [,4] [,5]
## y 1.026378 0.1254608 0.6168702 0.1254608 0.1254608
## NA 0.8327290 0.8327290 0.4232212 0.4232212
## NA 0.8327290 0.8327290 0.4232212 0.4232212
##
## $labs
## [1] "A\n.36 .34 .30" "A\n1.00 .00 .00" "B\n.00 .53 .47"
## [4] "B\n.00 .92 .07" "C\n.00 .03 .97"
##
## $cex
## [1] 1
##
## $boxes
## $boxes$x1
## [1] 0.28054780 -0.04785837 0.59833652 0.38647737 0.81019567
##
## $boxes$y1
## [1] 0.88220993 -0.01870734 0.47270208 -0.01870734 -0.01870734
##
## $boxes$x2
## [1] 0.5140199 0.2068487 0.8318086 0.6199495 1.0436678
##
## $boxes$y2
## [1] 1.0263781 0.1254608 0.6168702 0.1254608 0.1254608
##
##
## $split.labs
## [1] ""
##
## $split.cex
## [1] 1 1 1 1 1
##
## $split.box
## $split.box$x1
## [1] 0.3076274 NA 0.5946265 NA NA
##
## $split.box$y1
## [1] 0.7935145 NA 0.3840067 NA NA
##
## $split.box$x2
## [1] 0.4869403 NA 0.8355187 NA NA
##
## $split.box$y2
## [1] 0.8719435 NA 0.4624357 NA NA
Dado que el árbol original es muy simple. El podado no devuelve ninguna versión nueva reducida.
Una vez evaluada la capacidad predictiva del algoritmo K-NN, y los árboles de decisión simples obtenidos mediante el paquete rpart, estimamos el modelo que obtendríamos si ejecutásemos n árboles de decisión simultáneamente (para n=100 en nuestro caso) mediante el algoritmo randomForest.
El algoritmo randomForest es un método de estimación combinado, donde el resultado de la estimación se construye a partir de los resultados obtenidos mediante el cálculo de n árboles donde los predictores son incluidos al azar.
Es un método complejo con ventajas e inconvenientes respecto a los árboles de clasificación simples:
Ventajas
Es uno de los algoritmos de aprendizaje más precisos
Se ejecuta eficientemente en grandes bases de datos
Permite trabajar con cientos de variables independientes sin excluir ninguna
Determina la importancia en la clasificación de cada variable
Recupera eficazmente los valores perdidos de un dataset (missings)
Permite evaluar la ganancia en clasificación obtenida a medida que incrementamos el número de árboles generados en el modelo.
Inconvenientes
A diferencia de los árboles de decisión, la clasificación hecha por random forests es difícil de interpretar
Favorece las variables categóricas que tienen un mayor número de niveles por encima de aquéllas que tienen un número de categoría más reducido. Comprometiendo la fiabilidad del modelo para este tipo de datos.
Favorece los grupos más pequeños cuando las variables están correlacionadas
randomForest sobreajusta en ciertos grupos de datos con tareas de clasificación/regresión ruidosas
# Dividimos el fichero en 70% entreno y 30% validación (parte recurrente en todo experimento)
set.seed(1234)
ind <- sample(2, nrow(Data_PEC2), replace=TRUE, prob=c(0.7, 0.3))
trainData <- Data_PEC2[ind==1,]
testData <- Data_PEC2[ind==2,]
#Declaramos función del árbol
ArbolRF <- Comercial ~ Ingresos + Margen + Km + Visitas
#Aplicamos algoritmo
ArbolRF_ctree <- randomForest(ArbolRF, data=trainData, ntree=100,proximity=T) #indicamos el número de árboles mediante ntree=100
#Obtenemos la importancia de cada variable en el proceso de clasificación
importance(ArbolRF_ctree) #Importancia de las variables en formato text
## MeanDecreaseGini
## Ingresos 7.731585
## Margen 1.775483
## Km 28.376887
## Visitas 35.959795
f15<-varImpPlot(ArbolRF_ctree) #Importancia de las variables en formato gráfico
f15
## MeanDecreaseGini
## Ingresos 7.731585
## Margen 1.775483
## Km 28.376887
## Visitas 35.959795
#evolución del error según el número de árboles
f16<-plot(ArbolRF_ctree, main = "")
head(f16)
## OOB A B C
## [1,] 0.04347826 0 0.05882353 0.07142857
## [2,] 0.05714286 0 0.11111111 0.06250000
## [3,] 0.08333333 0 0.10344828 0.16000000
## [4,] 0.08510638 0 0.09375000 0.16666667
## [5,] 0.07920792 0 0.08571429 0.15625000
## [6,] 0.07619048 0 0.08333333 0.14705882
# Validamos la capacidad de predicción del árbol con el fichero de validación
testPredRF <- predict(ArbolRF_ctree, newdata = testData)
table(testPredRF, testData$Comercial)
##
## testPredRF A B C
## A 10 0 0
## B 0 12 2
## C 0 0 14
# Calculamos el % de aciertos
sum(testPredRF == testData$Comercial)/ length(testData$Comercial)*100
## [1] 94.73684
El árbol de decisión obtenido mediante el paquete randomForest clasifica correctamente un 94,73% de los registros. Un resultado bastante alto y aceptable.
Al aumentar el K la probabilidad se mantiene estable salvo en k=6 que sube ligereamente hasta un 73%. Tras k=6, el resto de porcentajes de acierto para k=7,8,9,10 desciende respecto a los valores anteriores.
Indica cómo podemos identificar las variables influyentes de cada modelo. Verifica si cada algoritmo (knn, rpart o randomForest) contiene información relativa a la relevancia de cada variable del juego de datos.
Mientras que el KNN clasifica y predice, el rprt nos da información sobre las variables significativas: km y visitas (en ese orden); por otro lado, randomForest nos ofrece también las variables significativas, que coinciden con las anteriores pero en orden inverso: visitas y km.
Interpreta el gráfico en términos de negocio e identifica oportunidades de mejora y riesgos.
En el gráfico apreciamos que los ingresos son más altos en aquellos clientes en los que se invierten más kilómetros. No obstante, el margen que deja cada cliente debería ser un dato a tener en cuenta de forma mas significativa. De esta forma, vemos que esta variable toma sus valores más altos en algunos clientes que no tienen tantos ingresos pero en los que no se invierten tantos kilómetros. Igual ocurre con el número de visitas, donde el margen toma sus valores más altos en algunos clientes que requieren menos visitas. Haciendo un par de gráficos adicionales, vemos cómo se clasifican estas variables en función del tipo de comerciales, por lo que:
# Relación entre variables Km y Margen con tamaño ingresos y Color según Comercial, línea tendencia y elipse
f20<-ggplot(Data_PEC2, aes(x=Km, y=Margen, color=Comercial)) + geom_point(aes(size=Ingresos)) + geom_smooth(method=lm, aes(fill=Comercial))+ stat_ellipse(type = "norm")
f20
# Relación entre variables Km y Visitas con tamaño ingresos y Color según Comercial, línea tendencia y elipse
f21<-ggplot(Data_PEC2, aes(x=Visitas, y=Margen, color=Comercial)) + geom_point(aes(size=Ingresos)) + geom_smooth(method=lm, aes(fill=Comercial))+ stat_ellipse(type = "norm")
f21
Así, vemos que el comercial A invierte menos en kilómetros y visitas, a pesar de tener unos ingresos menores, pero dejando un mayor margen en los clientes. El margen que obtiene el comercial C es más alto que el B, a pesar de invertir más kilómetros y visitas, pero consigue unos ingresos mayores. A nivel de negocio, parece ser que el proceso que lleva a cabo el comercial A es más eficiente que el resto, seguido por el comercial C y el B. Debería apostarte por seguir la dinámica del comercial A.
Elaborad vuestra respuesta considerando la facilidad de cálculo, la facilidad de interpretación de resultados, la capacidad predictiva de cada modelo y el principio de parsimonia (ante dos soluciones tomaremos siempre la más simple)
Según los resultados obtenidos, rpart y randomForest nos dan mejor porcentaje de acierto en la predicción de la clasificación. Entre estos dos, con random forest podemos distinguir el error en cada una de las clasificaciones.
En este caso rpart y rforest coinciden en el porcentaje de clasificación correcta y en la asignación de los mismos individuos a cada grupo de comerciales, pero no tiene por qué ser así en todos los casos, ya que podrían haber asignado erróneamente comerciales a diferentes grupos pero manteniendo el mismo porcentaje de acierto.
Viendo las matrices de confusión:
#Matriz confusión KNN=2
table(testData$Comercial , KnnTestPrediccion_k2 )
## KnnTestPrediccion_k2
## A B C
## A 10 0 0
## B 2 8 2
## C 0 7 9
#Matriz de confusión rpart
table(testData$Comercia, testPredRpart)
## testPredRpart
## A B C
## A 10 0 0
## B 0 12 0
## C 0 2 14
#Matriz de confusión randomForest
table(testData$Comercia , testPredRF)
## testPredRF
## A B C
## A 10 0 0
## B 0 12 0
## C 0 2 14
Podemos entonces apreciar las diferencias de clasificación entre los 3 algoritmos. El algoritmo KNN con K=2 predice 2 individuos en A que pertenecen a B, 2 en C que pertenecen a B y 7 en B que pertenecen a C.
Mediante el análisis de componentes principales (PCA) generaremos una variable nueva (el componente principal comp1), que podemos incluir como variable de nuestro juego de datos. Aplicando el algoritmo K-NN al nuevo juego de datos, verificaremos si se experimenta alguna mejora en la capacidad predictiva.
El análisis de componentes principales (PCA) trata de buscar un sistema de coordenadas más adiente para representar el juego da datos.
Estas coordenadas nuevas les llama componentes y lo que persigue el algoritmo es que estos componentes o coordenadas recojan la máxima variabilidad posible.
En nuestro caso variabilidad significa información y relevancia. Cuanta más variabilidad haya en un componente, más información acumula del juego de datos y en consecuencia más relevante será para realizar predicciones sobre el juego de datos.
En esta pregunta veremos como podemos usar PCA para mejorar las predicciones de otros algoritmos como los árboles de decisión y KNN.
Inicialmente generamos el juego de datos newData_PEC2 al que añadimos una nueva columna que contiene las proyecciones del juego de datos sobre el componente 1 del sistema de coordenadas óptimo que se ha determinado con el algoritmo PCA.
# Estandarizamos el juego de datos para que todas las variables estén expresadas en la misma escala.
scData_PEC2 <- as.data.frame(scale(Data_PEC2[,1:4]))
# Aplicamos el algoritmo de componentes principales
pcaPEC2 <- princomp(scData_PEC2[,1:4])
summary(pcaPEC2)
## Importance of components:
## Comp.1 Comp.2 Comp.3 Comp.4
## Standard deviation 1.7004154 0.9565979 0.38258453 0.143074535
## Proportion of Variance 0.7277045 0.2303052 0.03683832 0.005151927
## Cumulative Proportion 0.7277045 0.9580098 0.99484807 1.000000000
# Construimos un nuevo juego de datos, añadiendo
# el componente PCA que contiene más información: Comp1
newData_PEC2 = scData_PEC2[,1:4]
newData_PEC2$Comp1 = pcaPEC2$scores[,1]
newData_PEC2$Comercial = Data_PEC2[,5]
head(newData_PEC2)
## Ingresos Margen Km Visitas Comp1 Comercial
## 1 -0.8976739 1.0286113 -1.336794 -1.308593 -2.256981 A
## 2 -1.1392005 -0.1245404 -1.336794 -1.308593 -2.079459 A
## 3 -1.3807271 0.3367203 -1.393470 -1.308593 -2.360044 A
## 4 -1.5014904 0.1060900 -1.280118 -1.308593 -2.296504 A
## 5 -1.0184372 1.2592416 -1.336794 -1.308593 -2.380802 A
## 6 -0.5353840 1.9511326 -1.166767 -1.046525 -2.063623 A
Al añadir una variable adicional (componente 1) la predicción mejora pasando a un 94% para K=1, 97% para k=5 y 97% para k=10.