Machine Learning, uno de los términos más utilizados dentro de la Ciencia de Datos, hace referencia a modelos que aprenden patrones de diversos features de los datos, lo cual les permite realizar predicciones/clasificaciones.
Tal como se ve en la imágen, Machine Learning (ML) es una rama de la Inteligencia Artificial; y dentro de ML también existe Deep Learning (DL), pero este último no lo veremos aquí ya que es un tema más complejo y avanzado.
Por ahora, concentrémonos en ML! Así como vimos regresión lineal para hablar de modelos estadísticos, a continuación veremos árboles de decisión o decision tree y bosques aleatorios random forest para hablar de modelos de aprendizaje automático.
Cabe destacar que, para evaluar los modelos de ML es necesario separar los datos en 2 conjuntos llamados:
Set de Entrenamiento o Train, donde se selecciona aleatoriamente entre un 70 y 80% de la muestra.
Set de Prueba o Test, donde se selecciona aleatoriamente entre un 20 y 30% de la muestra.
Tal como sus nombres lo indican, lo ideal es entrenar el modelo con el Set de Entrenamiento y luego calcular todas las métricas a partir del Set de Prueba que nunca fue visto por el modelo.
Los árboles de decisión son uno de los algoritmos o modelos supervisados más populares dentro del Aprendizaje Automático o Machine Learning. Son llamados “árboles” porque inician con un nodo raíz (que representa a la variable/dato que más influye en el modelo) y luego a partir de decisiones, se van ramificando en diferentes resultados, que a su vez generan nuevos nodos que vuelven a ramificarse en otros resultados. Estos nodos se particionan de acuerdo al valor del atributo, y esto es conocido como recursive partitioning o “partición recursiva”.
Cabe destacar que, mientras más variables incluyamos en estos modelos, más preciso será el resultado, ya que el modelo entiende mejor el tema estudiado.
Según la variable a predecir (target) existen 2 tipos de árbol:
Árbol de Clasificación: La variable target es una categoría.
Árbol de Regresión: La variable target es numérica.
Pero como dijimos más arriba, estos árboles también pueden agruparse y generar Bosques Aleatorios o Random Forest que también veremos a continuación.
Como siempre, comencemos instalando y activando las librerías que vamos a utilizar. En este caso se trata de la ya conocida tidyverse
y de 3 nuevas librerías que aprenderemos hoy para armar modelos basados en árboles de decisión y bosques aleatorios: rpart
, rpart.plot
y randomForest
#install.packages("tidyverse")
library(tidyverse)
#install.packages("rpart")
library(rpart)
#install.packages("rpart.plot")
library(rpart.plot)
#install.packages("randomforest")
library(randomForest)
Ahora si arranquemos con un ejemplo práctico utilizando datos provenientes de la Encuesta Permanente de Hogares (EPH) realizada el 3er Trimestre de 2020. Cabe destacar que esta Encuesta se raliza trimestralmente como parte de un programa nacional de indicadores sociales llevado a cabo por el Instituto Nacional de Estadística y Censos (INDEC) con el objetivo de conocer y analizar las condiciones sociodemográficas y socioeconómicas de la población de los diferentes aglomerados urbanos del país.
La fuente oficial de donde se obtuvo la información es https://www.indec.gob.ar/indec/web/Institucional-Indec-BasesDeDatos; y pueden descargar el dataset procesado y “pulido” desde https://data.world/angie-scetta/eph-individual-3t2020
Recomendación: Al descargarlo, moverlo de la carpeta “Descargas” a una nueva carpeta llamada “data” dentro de la carpeta del Proyecto donde estén trabajando.
Ya estamos listos para cargar nuestro set de datos asegurándonos que los string se carguen como factor ya que los modelos no trabajan con datos de tipo character:
eph <- read.csv("data/3T2020_eph_individual.csv",
stringsAsFactors = TRUE)
Y como siempre, el primer paso es explorar de que se trata su contenido:
dim(eph)
## [1] 9722 11
El dataset contiene 9722 registros/observaciones y 11 columnas. Veamos el contenido!
summary(eph)
## REGION AGLOMERADO MAS_500HAB
## Cuyo :1349 Partidos del GBA : 643 NO:6400
## Gran Buenos Aires: 886 Gran Salta : 528 SI:3322
## NEA :1325 Gran Mendoza : 516
## NOA :2445 Jujuy-Palpalá : 452
## Pampeana :2560 Santiago del Estero - La Banda: 448
## Patagonia :1157 La Rioja : 441
## (Other) :6694
## SEXO EDAD NIVEL_EDUCATIVO TIPO_TRABAJO OBRA_SOCIAL
## HOMBRE:5248 Min. :15.00 PRIMARIO :1371 ESTATAL:3610 NO:2601
## MUJER :4474 1st Qu.:31.00 SECUNDARIO :4366 OTRO : 112 SI:7121
## Median :40.00 UNIVERSITARIO:3985 PRIVADO:6000
## Mean :40.41
## 3rd Qu.:49.00
## Max. :82.00
##
## SUBSIDIO INGRESOS INGRESOS_28K
## NO:8684 Min. : 150 MAYOR:4998
## SI:1038 1st Qu.: 16000 MENOR:4724
## Median : 28000
## Mean : 31401
## 3rd Qu.: 40000
## Max. :390000
##
A partir del resumen estadístico podemos ver que el dataset contiene las encuestas realizadas en todos los aglomerados urbanos y que las 11 columnas refieren a: REGIÓN, AGLOMERADO, HABITANTES DEL AGLOMERADO, SEXO DEL ENTREVISTADO, EDAD, NIVEL EDUCATIVO MÁXIMO, TIPO_TRABAJO, OBRA SOCIAL, SUBSIDIOS e INGRESOS.
Pero antes de continuar tenemos que definir cuál el problema a resolver a partir de un modelo de ML: Con nuestra base de datos podríamos hacer varios modelos, pero hoy vamos a trabajar con la variable INGRESOS_28K que indica quienes de la base de datos cobran más de 28000 pesos y quienes menos. Por lo tanto, el objetivo será entrenar un modelo que aprenda que características (máximo nivel educativo alcanzado, edad, lugar de residencia, etc) tienen en común (o mejor dicho, “qué requisitos cumplen”) las personas que cobran menos de 28000 a diferencia de las que cobran un monto mayor.
Ya estamos en condiciones de armar nuestros set de train y test utilizando un 80% y un 20% de la muestra respectivamente. Pero antes que nada, al tratarse de una selección aleatoria, debemos asegurarnos de definir una “semilla” para obtener resultados reproducibles. Utilicemos set.seed()
:
set.seed(123)
# seleccionemos aleatoriamente las posiciones de un 80% de la muestra
set_sample <- sample(1:nrow(eph), size = nrow(eph) * 0.8)
# seleccionemos el set de train a partir de la muestra aleatoria del 80%
set_train <- eph %>%
filter(row_number() %in% set_sample)
# seleccionemos el set de test a partir del opuesto (!) de la muestra del 80%
set_test <- eph %>%
filter(!(row_number() %in% set_sample))
Revisemos la dimensión de ambos set:
dim(set_train)
## [1] 7777 11
dim(set_test)
## [1] 1945 11
Perfecto, tal como esperabamos tenemos 7777 (80%) registros en el train y 1945 (20%) en el test.
Hagamos nuestro primer árbol de decisión en R con la función rpart()
para entender qué variables de la base de EPH que estamos analizando tienen peso en la variable INGRESOS_28K. Como la variable mencionada es de tipo categórica y tiene 2 valores niveles (“MAYOR” o “MENOR”), el árbol a entrenar con el set de train será de clasificación:
arbol_clasificacion <- rpart(formula = INGRESOS_28K ~ ., #Variable a predecir
data = set_train, #Utilizamos set de entrenamiento
method = "class") #En este caso el método es de clasificación
Veamos el resultado:
arbol_clasificacion
## n= 7777
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 7777 3790 MAYOR (0.5126656 0.4873344)
## 2) INGRESOS>=27950 3987 0 MAYOR (1.0000000 0.0000000) *
## 3) INGRESOS< 27950 3790 0 MENOR (0.0000000 1.0000000) *
El modelo solo usó una variable explicativa (INGRESOS). Grafiquemos esto para entenderlo mejor:
rpart.plot(arbol_clasificacion, digits = -1)
Como vemos en el gráfico, lógicamente la variable INGRESOS explica el 100% de la variable INGRESOS_28K, por lo tanto se solapan y no nos da información extra para nuestro modelo así que probemos reentrenar pero eliminándola del set de entrenamiento:
arbol_clasificacion <- rpart(formula= INGRESOS_28K ~.,
data = set_train %>% select(-INGRESOS),
method = "class")
Y ahora veamos el nuevo resultado:
arbol_clasificacion
## n= 7777
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 7777 3790 MAYOR (0.51266555 0.48733445)
## 2) OBRA_SOCIAL=SI 5698 1896 MAYOR (0.66725167 0.33274833)
## 4) AGLOMERADO=Bahía Blanca - Cerri,Ciudad Autónoma de Buenos Aires,Comodoro Rivadavia - Rada Tilly,Gran Rosario,Gran Santa Fé,Neuquén – Plottier,Rawson – Trelew,Río Gallegos,San Nicolás – Villa Constitución,Santa Rosa – Toay,Viedma – Carmen de Patagones 1683 302 MAYOR (0.82055853 0.17944147) *
## 5) AGLOMERADO=Concordia,Corrientes,Formosa,Gran Catamarca,Gran Córdoba,Gran La Plata,Gran Mendoza,Gran Paraná,Gran Resistencia,Gran Salta,Gran San Juan,Gran San Luis,Gran Tucumán - Tafí Viejo,Jujuy-Palpalá,La Rioja,Mar del Plata,Partidos del GBA,Posadas,Río Cuarto,Santiago del Estero - La Banda 4015 1594 MAYOR (0.60298879 0.39701121)
## 10) SUBSIDIO=NO 3875 1469 MAYOR (0.62090323 0.37909677)
## 20) AGLOMERADO=Concordia,Corrientes,Formosa,Gran Catamarca,Gran Córdoba,Gran La Plata,Gran Mendoza,Gran Paraná,Gran Salta,Gran San Juan,Gran San Luis,Gran Tucumán - Tafí Viejo,Jujuy-Palpalá,La Rioja,Mar del Plata,Partidos del GBA,Posadas,Río Cuarto 3494 1246 MAYOR (0.64338867 0.35661133) *
## 21) AGLOMERADO=Gran Resistencia,Santiago del Estero - La Banda 381 158 MENOR (0.41469816 0.58530184) *
## 11) SUBSIDIO=SI 140 15 MENOR (0.10714286 0.89285714) *
## 3) OBRA_SOCIAL=NO 2079 185 MENOR (0.08898509 0.91101491) *
Ahora si aparecen más variables explicativas en el resultado de nuestro árbol de clasificación, donde cada fila hace referencia a un nodo junto a la regla que le corresponde. Se marca con un "*" a los nodos terminales u hojas del árbol.
Volvamos al recurso visual que siempre ayuda a la hora de interpretar resultados. Hagamos un gráfico con rpart.plot()
:
rpart.plot(arbol_clasificacion)
Como se puede ver en el gráfico, los árboles de decisión están compuestos por nodos con “preguntas” o “reglas” que según la respuesta, seguirán un camino u el otro. Por ejemplo en el primer nodo de nuestro modelo, que dice OBRA_SOCIAL, si la respuesta es NO, el modelo predice con una probabilidad del 91% que los ingresos son menores a 28000. Sin embargo, si es SI, el modelo pasa a otra pregunta que es AGLOMERADO, y así sucesivamente.
¿Cómo podemos interpretar los números dentro de cada nodo y hoja del árbol de clasificación?
El primer dato hace referencia a la categoría producto de la predicción (MAYOR o MENOR).
El segundo dato contiene la probabilidad de que la predicción sea MENOR. Es decir que, indica que proporción de los casos de ese nodo pertenecen a la categoría MENOR.
Por último, el tercer dato muestra la proporción de los casos totales del set de train que han sido agrupados en el nodo u hoja.
Cabe destacar que, de las 9 variables del dataset de EPH, el árbol solo consideró importante a 3 (OBRA_SOCIAL, AGLOMERADO, SUBSIDIO), dejando de lado al resto de variables que no resultaron relevantes en la predicción.
Otra forma de ver las reglas que generó nuestro árbol, es utilizando rpart.rules()
:
rpart.rules(arbol_clasificacion)
## INGRESOS_28K
## 0.18 when OBRA_SOCIAL is SI & AGLOMERADO is Bahía Blanca - Cerri or Ciudad Autónoma de Buenos Aires or Comodoro Rivadavia - Rada Tilly or Gran Rosario or Gran Santa Fé or Neuquén – Plottier or Rawson – Trelew or Río Gallegos or San Nicolás – Villa Constitución or Santa Rosa – Toay or Viedma – Carmen de Patagones
## 0.36 when OBRA_SOCIAL is SI & AGLOMERADO is Concordia or Corrientes or Formosa or Gran Catamarca or Gran Córdoba or Gran La Plata or Gran Mendoza or Gran Paraná or Gran Salta or Gran San Juan or Gran San Luis or Gran Tucumán - Tafí Viejo or Jujuy-Palpalá or La Rioja or Mar del Plata or Partidos del GBA or Posadas or Río Cuarto & SUBSIDIO is NO
## 0.59 when OBRA_SOCIAL is SI & AGLOMERADO is Gran Resistencia or Santiago del Estero - La Banda & SUBSIDIO is NO
## 0.89 when OBRA_SOCIAL is SI & AGLOMERADO is Concordia or Corrientes or Formosa or Gran Catamarca or Gran Córdoba or Gran La Plata or Gran Mendoza or Gran Paraná or Gran Resistencia or Gran Salta or Gran San Juan or Gran San Luis or Gran Tucumán - Tafí Viejo or Jujuy-Palpalá or La Rioja or Mar del Plata or Partidos del GBA or Posadas or Río Cuarto or Santiago del Estero - La Banda & SUBSIDIO is SI
## 0.91 when OBRA_SOCIAL is NO
Pero esto no termina acá, todavía nos queda la parte más importante que es la de testear nuestro modelo con el set de test para poder entender qué tan bien hace sus predicciones.
¿Qué métricas utilizamos para medir la performance del modelo realizado?
Para medir el rendimiento de los modelos de ML de tipo clasificación se suele utilizar la Matriz de Confusión, que divide en 4 celdas los resultados obtenidos y luce así:
Los valores más importantes son los de la diagonal que desciende de izquierda a derecha ya que son los datos que nuestro modelo acertó en la predicción. Estos valores son llamados “Verdaderos Negativos (VN)” y “Verdaderos Positivos (VP)”.
Pero para poder ver nuestra matriz de confusión, antes debemos hacer las predicciones con el set de test usando la función predict()
. En este caso la vamos a calcular dentro de una columna del set así es más sencillo realizar luego la matriz:
set_test <- set_test %>%
mutate(PREDICCION_CLASS = predict(arbol_clasificacion, #nombre del modelo entrenado
newdata = set_test, #set de test
type="class")) #tipo de modelo
Utilicemos head()
para revisar que se haya generado la nueva columna llamada PREDICCION_CLASS:
head(set_test)
## REGION AGLOMERADO MAS_500HAB SEXO EDAD NIVEL_EDUCATIVO TIPO_TRABAJO
## 1 NOA Jujuy-Palpalá NO MUJER 44 UNIVERSITARIO ESTATAL
## 2 Patagonia Río Gallegos NO HOMBRE 63 UNIVERSITARIO PRIVADO
## 3 NOA Gran Catamarca NO HOMBRE 23 UNIVERSITARIO ESTATAL
## 4 NOA Gran Catamarca NO MUJER 49 SECUNDARIO ESTATAL
## 5 NOA Gran Catamarca NO HOMBRE 52 SECUNDARIO ESTATAL
## 6 NOA Gran Catamarca NO HOMBRE 31 SECUNDARIO ESTATAL
## OBRA_SOCIAL SUBSIDIO INGRESOS INGRESOS_28K PREDICCION_CLASS
## 1 SI NO 25000 MENOR MAYOR
## 2 SI NO 70000 MAYOR MAYOR
## 3 SI NO 35000 MAYOR MAYOR
## 4 SI NO 25000 MENOR MAYOR
## 5 SI NO 30000 MAYOR MAYOR
## 6 SI NO 50000 MAYOR MAYOR
Y ahora si, ya estamos en condiciones de hacer nuestra matriz de confusión con las columnas INGRESO_35K y PREDICCION_CLASS para ver que tan bien funciona el modelo:
table(set_test$PREDICCION_CLASS, set_test$INGRESOS_28K)
##
## MAYOR MENOR
## MAYOR 920 389
## MENOR 91 545
A simple vista los resultados parecen buenos, ¿No? El modelo tiene un error del 25% (Falsos Positivos + Falsos Negativos) y una exactitud o accuracy del 75%, es decir que del total de 1945 registros, nuestro árbol de decisión encontró un patrón en la predicción de 1465 (1465/1945).
Pero, ¿Cómo podríamos saber si un 75% de exactitud es bueno o malo? Bueno para esto es necesario que entendamos la distribución inicial de los datos, es decir, que porcentaje tenía un sueldo mayor a 28000 y que porcentaje tenía un sueldo menor.
table(eph$INGRESOS_28K)/nrow(eph)
##
## MAYOR MENOR
## 0.5140918 0.4859082
Podemos ver que el 51% de los encuestados tiene un sueldo mayor a 28000, por lo tanto, si categorizamos al 100% de nuestros datos como “mayores de 28000”, vamos a tener un error del 49%. Sin embargo el árbol que creamos lo redujo a 25%. Nada mal, ¿No?
Veamos si en el set de train estos resultados eran los mismos:
table(predict(arbol_clasificacion, type="class"), set_train$INGRESOS_28K)
##
## MAYOR MENOR
## MAYOR 3629 1548
## MENOR 358 2242
Efectivamente, hubo una exactitud del 75% ya que el árbol encontró un patrón en 5871 de 7777 registros totales.
Pero como dijimos antes, existe una versión “evolucionada” de los árboles de decisión, y son los random forest que combinan varios árboles. Así que probemos este mismo caso de estudio pero con un RF.
El concepto de Random Forest o Bosque Aleatorio hace referencia a una técnica de aprendizaje automático supervisado muy popular ya que presenta una gran capacidad de generalización a la hora de resolver varios problemas. Un Random Forest es una combinación o ensamble de árboles de decisión. La ventaja del bosque frente a los árboles, es que ningúno de los árboles que lo componen ven la totalidad de datos de train. Es decir, cada uno ve distintas porciones/partes de los datos. Por lo tanto, cada árbol se entrena con distintas muestras para el mismo problema, al combinar resultados, se “compensan” los errores y obtenemos predicciones más exactas ya que el modelo generaliza mejor.
Es importante destacar que, los árboles de decisión tienen tendencia al overfitting ya que para entrenar ven la totalidad de los datos, entonces los aprenden muy bien. Así que justamente, el plus que pueden llegar a tener los RF son que mejora la capacidad de generalización del modelo.
Sigamos trabajando con los mismos set de train y test pero ahora utilicemos la función randomForest()
:
modelo_RF <- randomForest(INGRESOS_28K ~ .,
data = set_train %>% select(-INGRESOS),
ntree = 500) #Por default este parámetro es siempre 500
Y veamos los resultados del set de train:
modelo_RF
##
## Call:
## randomForest(formula = INGRESOS_28K ~ ., data = set_train %>% select(-INGRESOS), ntree = 500)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 3
##
## OOB estimate of error rate: 22.35%
## Confusion matrix:
## MAYOR MENOR class.error
## MAYOR 3406 581 0.1457236
## MENOR 1157 2633 0.3052770
Volvimos a encontrarnos con la matriz de confusión, donde podemos ver que en el entrenamiento logró separar correctamente 6049 registros de los 7777, es decir que la exactitud aumentó respecto al árbol. Pasó de ser 75% a 77%.
Probemos con el set de test a ver que pasa:
set_test <- set_test %>%
mutate(PREDICCION_RF = predict(modelo_RF,
newdata = set_test))
Y veamos la matriz de confusión:
table(set_test$PREDICCION_RF, set_test$INGRESOS_28K)
##
## MAYOR MENOR
## MAYOR 848 275
## MENOR 163 659
Se mantiene el mismo accuracy que en el entrenamiento (77%) ya que el modelo predice correctamente 1505 registros de un total de 1945.
Por lo tanto, como ambas performance son similares podríamos asegurar que nuestro modelo no sufre de overfitting, es decir que puede generaliza bien porque no aprendió demasiado las especificidades de la muestra con la que se lo entrenó.
Por último, hagamos un scatter plot que nos permita visualizar los resultados:
ggplot(set_test) +
geom_jitter(aes(x = PREDICCION_RF, y = INGRESOS_28K, color=INGRESOS_28K))
Pero como dijimos al principio, los árboles y bosques también pueden ser utilizados para problemas del tipo “regresión”, es decir que nuestra variable a predecir sea numérica.
Ahora les toca a uds hacer otro ejemplo para practicar!
Elegir una variable categórica a predecir y realizar un modelo predictivo: 1. Modelo Árbol de Clasificación. Interpretar y describir resultados de ambos set (Accuracy o Exactitud). 2. Modelo Random Forest. Interpretar y describir resultados de ambos set (Accuracy o Exactitud).
Aclaración: En cualquiera de los 2 casos deberán separar un 80% de la muestra para entrenar y un 20% para testear.