Cómo esta clase tiene más contenido teórico, recomendamos también ver la presentación que está en el archivo Clase_3_Análisis_Predictivos.html.
Además, como siempre, recuerden que tienen el canal #auxilio en Slack para hacer todas las consultas que necesitan.
El ejemplo de hoy, lo vamos a usar reciclando un caso de estudio que desarrollamos en Data 4HR en Python, y lo vamos a replicar en R. El caso original lo pueden ver en este link y este es el repositorio original (aprovechen para ver las diferencias en las sintaxis entre R y Python si pueden).
Existen varios tipos de análisis predictivos. En particular, hoy vamos a utilizar un modelo llamado de clasificación porque lo que queremos predecir es si una persona va a pertenecer a una clase (renuncia) o a otra clase (no renuncia).
Este tipo de algoritmo, nos va a brindar como resultado, una probabilidad, es decir un valor que nos va a indicar con qué grado de certeza (o menor margen de incertidumbre) podemos afirmar que algo va a pasar o no.
El objetivo de los análisis predictivos es detectar patrones, en nuestro caso, patrones en los comportamientos, en las características, y en los datos de los empleados para poder detectar quién tiene más probabilidad de renunciar por ejemplo.
Hoy, sin ningún análisis hecho, lo que sabemos de cada empleado es que tiene tanta probabilidad de renunciar, como de no renunciar. O sea que la cosa esta “fifty-fifty”, o como para ir nerdeando la cosa, con una probabilidad de 0.5.
Lo que buscamos con un análisis predictivo es mejorar esa probabilidad para reducir la incertidumbre sobre lo que puede o no ocurrir en el futuro.
Hacer un análisis no implica acertar el 100% de los casos, sino que es un intento de tener una idea de quiénes tienen más probabilidad de irse. ¿Esto quiere decir que alguien que tiene alta probabilidad de renunciar y no lo hace (o viceversa) el modelo está mal?
No. Al menos no necesariamente. ¿Qué es la probabilidad?
La probabilidad es toda una rama de la estadística en sí misma. Se enfoca en intentar descubrir la certeza (o incertidumbre) de que ocurran las cosas. El resultado de una probabilidad siempre va a dar entre 0 y 1.
¿Qué pasa si ocurre algo improbable, o no ocurre algo con alta probabilidad? Es parte del margen de error inherente a la estadística, y por eso se asume que va a haber errores. El punto es, si repetimos el experimento 100 veces, ¿cuánto acierta el modelo, y con qué precisión?
Hay varias formas de hacer análisis predictivos, y siempre vamos a tener que tomar decisiones entre modelos computacionalmente eficientes y simples, o computacionalmente más costosos y precisos; o entre modelos más transparentes en el sentido que podemos explicar la relación entre las variables y la predicción, o entre modelos que son una caja negra en la que sabemos el resultado pero no podemos explicar el por qué.
En este sentido, la regresión logística es un algoritmo computacionalmente eficiente, simple, y transparente para explicar la relación entre la probabilidad y la influencia de las variables.
Para hacer análisis predictivos vamos a trabajar con datos del pasado. Tradicionalmente se usa una el 70% de los datos para entrenar el modelo, y el 30% de los datos, se los usa para testear el modelo. A estos datasets los vamos a llamar training o de entrenamiento y dataset de testing o de validación respectivamente.
La selección de los datos se hace al azar. Así que hay que asegurarse que la proporción de renuncias sea similar en ambos datasets.
Vamos a utilizar los siguientes paquetes:
install.packages("caret") # Es EL paquete para correr modelos
install.packages("car") # Companion to Applied Regression
install.packages("pROC") # Para realizar Curvas ROC
install.packages("funModeling") # Análisis Exploratorios y mucho másAhora activemos todos los paquetes que vamos a necesitar.
library(caret)
library(car)
library(pROC)
library(funModeling)
library(tidyverse)La fuente de datos que vamos a utilizar lo podemos cargar directamente desde el repositorio de GitHub del caso de estudio:
# Leer los datos de GitHub
datos <- read_csv("https://raw.githubusercontent.com/mlambolla/Analytics_HR_Attrition/master/HR_comma_sep.csv")
# Ver el dataset con glimpse()
glimpse(datos)## Rows: 14,999
## Columns: 10
## $ satisfaction_level <dbl> 0.38, 0.80, 0.11, 0.72, 0.37, 0.41, 0.10, 0.92, …
## $ last_evaluation <dbl> 0.53, 0.86, 0.88, 0.87, 0.52, 0.50, 0.77, 0.85, …
## $ number_project <dbl> 2, 5, 7, 5, 2, 2, 6, 5, 5, 2, 2, 6, 4, 2, 2, 2, …
## $ average_montly_hours <dbl> 157, 262, 272, 223, 159, 153, 247, 259, 224, 142…
## $ time_spend_company <dbl> 3, 6, 4, 5, 3, 3, 4, 5, 5, 3, 3, 4, 5, 3, 3, 3, …
## $ Work_accident <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ left <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ promotion_last_5years <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ sales <chr> "sales", "sales", "sales", "sales", "sales", "sa…
## $ salary <chr> "low", "medium", "medium", "low", "low", "low", …
Un paso que necesitamos hacer es convertir una variable
character a numeric. Además eliminamos la
variable sales (que en realidad es la variable de sectores,
pero tiene muchos valores posibles).
# Elminamos la variable 'sales' y cambiemos los valores de 'salary' a numéricos.
datos<- datos %>%
select(-sales) %>%
mutate(salary = as.numeric(case_when(
salary == 'low' ~ 0,
salary == 'medium' ~ 1,
salary == 'high' ~ 2
)))En los ciclos de vida de los proyectos de data mining en general, está establecida como metodología, la metodología CRISP-DM (Cross Industry Standard Process for Data Mining). En donde:
Entre la etapa de Modelado y la Puesta en producción (deployment) hay una etapa de evaluación. Mientras diseñamos el modelo, trabajamos con datos históricos, en la etapa de Evaluación vamos probando la precisión del modelo con datos nuevos, y si todo va bien, se poné en producción.
Los pasos básicos de un análisis predictivo son:
Por un tema de tiempos, nos vamos a saltear el paso 3. Si necesitan ayuda para realizar el análisis exploratorio de datos por su cuenta, pueden revisar la sesión de R4HR Club de R para RRHH o el capítulo 7 de R para Ciencia de Datos.
targetUsualmente, en los modelos predictivos, tenemos una variable objetivo a la que llamaremos target.
Por lo general usamos o una variable numérica o lógica
(TRUE o FALSE) codificada con 1 o
0. Es una práctica común usar el número 1 para lo que nos
interesa saber, en nuestro caso, si la persona de nuestra base de datos,
se fue de la compañía.
Veamos el data frame con la función View().
# Ver la tabla de datos con View()
View(datos)En nuestro caso, la variable target es la columna
left. Veamos en los datos, cuantos empleados se fueron, y
cuántos aún permanecen en la compañía.
# Contar cantidad de empleados que se van y que se quedan
datos %>% # Ctrl + Shift + M
count(left)## # A tibble: 2 × 2
## left n
## <dbl> <int>
## 1 0 11428
## 2 1 3571
# Qué porcentaje de empleados se fueron?
datos %>% # Ctrl + Shift + M
count(left) %>%
mutate(prop = n/sum(n))## # A tibble: 2 × 3
## left n prop
## <dbl> <int> <dbl>
## 1 0 11428 0.762
## 2 1 3571 0.238
El paso siguiente es separar el data frame en dos partes, una que llamaremos training, y otra que llamaremos testing.
El data frame de training o entrenamiento sirve para entrenar el modelo, es decir que con esta porción de los datos vamos a crear la fórmula que luego utilizaremos para estimar las probabilidades de nuestro modelo predictivo.
El data frame de testing o validación nos sirve para verificar que la fórmula que creamos en el paso anterior sea efectiva con datos nuevos. Es una forma de simular que son nuevos datos y que nos permite evaluar qué tan efectiva es la predicción.
El método para dividir el dataset en dos es similar al que haríamos con un listado final de invitados a una boda. De todas las personas posibles sólo “invitamos” a un grupo.
De la misma manera vamos a crear un índice, el cual nos va a indicar las filas del data frame original que formarán el data frame de training y el de testing.
La selección de los datos se hace aleatoriamente, o sea que si no hacemos nada, cada vez que hagamos la selección R elegirá filas diferentes. Con lo cual, el primer paso es definir una semilla.
# Definir una semilla con set.seed()
set.seed(42)Luego vamos a crear nuestro índice de filas para el dataset de
entrenamiento (nuestra lista de invitados) con la función
createDataPartition() del paquete caret cuyos
parámetros son:
y: que indica nuestra variable target con el
formato data_frame$variable_target.p: que indica la proporción de datos que queremos para
el índice.list = FALSE: Con eso le indicamos a la función que no
queremos que almacene el resultado como una lista.# Usar la función createDataPartition() y guardar los resultados en un objeto llamado indice
indice <- createDataPartition(y = datos$left,
p = 0.7,
list = FALSE)
# Ver el contenido de las primeras 20 filas
head(indice, 20)## Resample1
## [1,] 1
## [2,] 2
## [3,] 3
## [4,] 4
## [5,] 6
## [6,] 7
## [7,] 8
## [8,] 9
## [9,] 13
## [10,] 14
## [11,] 15
## [12,] 16
## [13,] 18
## [14,] 19
## [15,] 22
## [16,] 24
## [17,] 25
## [18,] 26
## [19,] 27
## [20,] 29
Ahora, con ese índice vamos a crear los dos data frames que
necesitamos. Vamos a usar la lógica
nombre_dataframe[filas, columnas], indicando en este caso
que para el data frame de entrenamiento vamos a seleccionar las filas
que estén en el indice.
Para crear el data frame de validación, vamos a poner todas las filas que no estén en el índice.
El primer data frame lo almacenaremos en un objeto llamado
df_train y el data frame de validación lo
guardaremos en un objeto llamado df_test.
#Armo el dataframe de training data_frame[fila, columna]
df_train <- datos[indice, ]
# Con el signo - (menos), creamos el dataset de testing, con todas las filas 'que no estén en indice'
df_test <- datos[-indice, ]Revisemos ambos data frames con la función
glimpse().
# Revisar el data frame df_train
glimpse(df_train)## Rows: 10,500
## Columns: 9
## $ satisfaction_level <dbl> 0.38, 0.80, 0.11, 0.72, 0.41, 0.10, 0.92, 0.89, …
## $ last_evaluation <dbl> 0.53, 0.86, 0.88, 0.87, 0.50, 0.77, 0.85, 1.00, …
## $ number_project <dbl> 2, 5, 7, 5, 2, 6, 5, 5, 4, 2, 2, 2, 4, 2, 2, 2, …
## $ average_montly_hours <dbl> 157, 262, 272, 223, 153, 247, 259, 224, 234, 148…
## $ time_spend_company <dbl> 3, 6, 4, 5, 3, 4, 5, 5, 5, 3, 3, 3, 6, 3, 3, 3, …
## $ Work_accident <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, …
## $ left <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ promotion_last_5years <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, …
## $ salary <dbl> 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
# Revisar el data frame df_test()
glimpse(df_test)## Rows: 4,499
## Columns: 9
## $ satisfaction_level <dbl> 0.37, 0.42, 0.45, 0.11, 0.45, 0.76, 0.11, 0.09, …
## $ last_evaluation <dbl> 0.52, 0.53, 0.54, 0.81, 0.47, 0.89, 0.83, 0.95, …
## $ number_project <dbl> 2, 2, 2, 6, 2, 5, 6, 6, 2, 2, 6, 2, 6, 5, 2, 6, …
## $ average_montly_hours <dbl> 159, 142, 135, 305, 160, 262, 282, 304, 135, 132…
## $ time_spend_company <dbl> 3, 3, 3, 4, 3, 5, 4, 4, 3, 3, 4, 3, 4, 5, 3, 4, …
## $ Work_accident <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ left <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ promotion_last_5years <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ salary <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
Recuerden que por un tema de tiempos omitiremos este paso. Pueden ver los pasos desarrollados en el caso de estudio en el repositorio de GitHub.
En este paso lo que vamos a hacer es verificar que la proporción de casos en ambos dataframes sea pareja. Esto nos permitirá asumir que cuando hagamos la validación de los datos tengamos una cantidad de casos relevante para analizar.
Dado que la variable left se compone de 0 y
de 1 podemos calcular fácilmente que el porcentaje en ambos
data frames sea similar calculando el promedio de la columna
left en ambos data frames.
# Calcular el promedio de la columna left en df_train
mean(df_train$left)## [1] 0.2395238
# Calcular el promedio de la columna left en df_test
mean(df_test$left)## [1] 0.2347188
Como las proporciones son similares podemos avanzar.
El primer paso es generar un modelo predictivo con los datos de
training.left es la variable objetivo, y los
símbolos ~ . indican contra qué variables vamos a entrenar
el modelo. Esto implica que el resto del dataset son las variables
explicatorias.
# Calculamos un modelo de entrenamiento, sacando department de los cálculos.
modelo <- glm(left ~. ,
family = "binomial",
data = df_train)
# Veamos un resumen del resultado
summary(modelo)##
## Call:
## glm(formula = left ~ ., family = "binomial", data = df_train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.1472 -0.6627 -0.4160 -0.1336 3.0646
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 0.5069962 0.1418949 3.573 0.000353 ***
## satisfaction_level -4.1310403 0.1165079 -35.457 < 2e-16 ***
## last_evaluation 0.6773515 0.1767800 3.832 0.000127 ***
## number_project -0.3027174 0.0251443 -12.039 < 2e-16 ***
## average_montly_hours 0.0044670 0.0006097 7.327 2.35e-13 ***
## time_spend_company 0.2547823 0.0180718 14.098 < 2e-16 ***
## Work_accident -1.4787005 0.1051336 -14.065 < 2e-16 ***
## promotion_last_5years -1.2253470 0.2775988 -4.414 1.01e-05 ***
## salary -0.6866613 0.0445736 -15.405 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 11561.1 on 10499 degrees of freedom
## Residual deviance: 9134.7 on 10491 degrees of freedom
## AIC: 9152.7
##
## Number of Fisher Scoring iterations: 5
Un control que podemos hacer en este punto es analizar los resultados para analizar que no haya multicolinealidad.
La multicolinealidad es un efecto no deseado entre los datos, porque corremos el riesgo de encontrar relaciones sospechosamente fuertes entre dos o más variables cuando en realidad son la misma cosa expresada de diferente manera, como por ejemplo la antigüedad de una persona expresada en años y su bono de antigüeadd, o cuando una variable surge del cálculo de otra, como el Índice de Masa Muscular y el peso.
Entonces, lo que buscamos es que la relación entre las variables sea
lo más sana posible. Una forma de detectar si hay
multcolinealidad o no es calculando el VIF (Variance
Inflation Factor) con la función vif() del paquete
car.
# Usar la función vif() en df_train
vif(modelo)## satisfaction_level last_evaluation number_project
## 1.153786 1.429789 1.769583
## average_montly_hours time_spend_company Work_accident
## 1.519144 1.098879 1.009792
## promotion_last_5years salary
## 1.010921 1.025109
¿Cómo interpretamos los resultados? De la siguiente manera:
| VIF | Interpretación |
|---|---|
| 1 | No hay multicolinealidad |
| Entre 1 y 5 | Multicolinealidad moderada |
| 5 o más | Alta Multicolinealidad |
Ahora lo que tenemos es cuáles son las variables significativas para el modelo vamos a convertir los resultados en probabilidades. Así que ahora vamos a realizar tres pasos:
score.prediccion) donde si
score es mayor que 0.5 escriba un
1, y de lo contrario, 0.Realicemos el primer paso.
# Cacular las probabilidades de df_train
pred_train <- predict(modelo, # Fórmulas del modelo
newdata = df_train, # Fuente de datos
type = "response")
# Calcular las probabilidades de df_test
pred_test <- predict(modelo, newdata = df_test, type = "response")Lo que obtenemos en el paso anterior es es un gran listado de
probabilidades (asegurarse que los resultados estén entre 0 y 1). Veamos
los primeros 20 resultados de pred_test.
# Ver los primeros 20 resultados
pred_test[1:20]## 1 2 3 4 5 6 7 8
## 0.5498075 0.4810531 0.4441712 0.7625090 0.4600874 0.2499117 0.7459811 0.7924024
## 9 10 11 12 13 14 15 16
## 0.4871114 0.5061066 0.7448388 0.4513688 0.7450726 0.1781397 0.4595508 0.7638225
## 17 18 19 20
## 0.4676785 0.4560720 0.5168010 0.6452583
# Controlar que todos los resultados estén entre 0 y 1.
summary(pred_test)## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.00223 0.07697 0.17133 0.24082 0.34962 0.88983
Ahora tenemos que asignar esos resultados que son un vector a una
columna que llamaremos score. Una forma de hacerlo es la
siguiente:
# Crear la columna score en df_train
df_train$score <- pred_train
# Repetir con df_test
df_test$score <- pred_test
# Ver el contenido de df_test con glimpse()
glimpse(df_test)## Rows: 4,499
## Columns: 10
## $ satisfaction_level <dbl> 0.37, 0.42, 0.45, 0.11, 0.45, 0.76, 0.11, 0.09, …
## $ last_evaluation <dbl> 0.52, 0.53, 0.54, 0.81, 0.47, 0.89, 0.83, 0.95, …
## $ number_project <dbl> 2, 2, 2, 6, 2, 5, 6, 6, 2, 2, 6, 2, 6, 5, 2, 6, …
## $ average_montly_hours <dbl> 159, 142, 135, 305, 160, 262, 282, 304, 135, 132…
## $ time_spend_company <dbl> 3, 3, 3, 4, 3, 5, 4, 4, 3, 3, 4, 3, 4, 5, 3, 4, …
## $ Work_accident <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ left <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ promotion_last_5years <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ salary <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ score <dbl> 0.5498075, 0.4810531, 0.4441712, 0.7625090, 0.46…
Por último para simplificar los resultados vamos a crear una columna
que se llamará prediccion en la cual pondremos un 1 si el
valor de la celda de la columna score es mayor a 0.5, y un
0 cuando el valor de score sea menor a 0.5.
# Crear una columna llamada prediccion en df_train
df_train <- df_train %>%
mutate(prediccion = if_else(score > 0.5, 1, 0))
# Repetir para df_test
df_test <- df_test %>%
mutate(prediccion = if_else(score > 0.5, 1, 0))
# Usemos la función View() con df_test
View(df_test)Ahora podemos controlar nuestras predicciones contra la variable target original y revisar qué tan bien nos fue.
Para controlar los resultados vamos a hacer tres cosas. Calcular la Matriz de Confusión, la Curva ROC y por último el gain y el lift.
La matriz de confusión es una tabla de doble entrada en donde lo que hacemos es contrastar los aciertos del modelo, contra los fallos.
Matriz de Confusión
En principio nos vamos a enfocar únicamente en la diagonal. Los aciertos positivos y los aciertos negativos, lo que nos va a dar el Accuracy del modelo. Todo lo que vemos en los márgenes de la matriz son distintas métricas que podemos calcular con esta tabla.
El Accuracy lo que nos indica es el total de aciertos (verdadores y negativos, TP y TN respectivamente) sobre el total de casos. Ese porcentaje de aciertos es una de las cosas que nos interesa analizar.
Para crear una matriz de confusión tenemos que crear una tabla de
doble entrada, en donde pongamos por un lado los valores de la columna
left y por otro lado los valores de la columna
prediccion y veamos las coincidencias.
# Crear una matriz con los datos de df_train
matrix_conf_train <- table(df_train$left, df_train$prediccion)
# Repetir con df_test
matrix_conf_test <- table(df_test$left, df_test$prediccion)
# Ver la tabla
matrix_conf_test##
## 0 1
## 0 3190 253
## 1 666 390
El paquete caret tiene una función que nos brinda toda
la información que necesitamos, y es la función
confusionMatrix().
# Ver las métricas de matrix_conf_train
confusionMatrix(matrix_conf_train)## Confusion Matrix and Statistics
##
##
## 0 1
## 0 7402 583
## 1 1609 906
##
## Accuracy : 0.7912
## 95% CI : (0.7833, 0.799)
## No Information Rate : 0.8582
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.3339
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.8214
## Specificity : 0.6085
## Pos Pred Value : 0.9270
## Neg Pred Value : 0.3602
## Prevalence : 0.8582
## Detection Rate : 0.7050
## Detection Prevalence : 0.7605
## Balanced Accuracy : 0.7150
##
## 'Positive' Class : 0
##
Luego repetimos este paso con matrix_conf_test.
# Ver las métricas para matrix_conf_test
confusionMatrix(matrix_conf_test)## Confusion Matrix and Statistics
##
##
## 0 1
## 0 3190 253
## 1 666 390
##
## Accuracy : 0.7957
## 95% CI : (0.7837, 0.8074)
## No Information Rate : 0.8571
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.3422
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.8273
## Specificity : 0.6065
## Pos Pred Value : 0.9265
## Neg Pred Value : 0.3693
## Prevalence : 0.8571
## Detection Rate : 0.7090
## Detection Prevalence : 0.7653
## Balanced Accuracy : 0.7169
##
## 'Positive' Class : 0
##
La razón por la que hacemos este paso para ambos data frames es para tener un control de la calidad del modelo. Lo esperable es que el accuracy del modelo de entrenamiento sea un poco más alto que en el data frame de validación porque el modelo se construyó sobre esos datos.
Si hubieran diferencias significativas, como una amplia diferencia entre ambas accuracies, o que los resultados sean mucho mejores en testing que en training eso puede reflejar que el modelo tenga problemas como el overfitting o que necesitemos otro algoritmo para obtener mejores resultados.
Ahora probaremos otro control sobre los resultados que es la Curva ROC.
La Curva ROC, es una forma visual de calcular el AUC (Area Under the Curve, el área bajo la curva). Internamente lo que hace este gráfico es ordenar las probabilidades de mayor a menor, y a medida que tenemos un positivo verdadero (el empleado se fue y nosotros predecimos que se iba) la curva se mueve hacia arriba. Con cada falso positivo, la curva se va moviendo a la derecha.
Para esto vamos a usar la librería pROC.
Video: cómo se construye la curva ROC: https://youtu.be/OjWew7W4KnY.
Para simplificar vamos a realizar este proceso únicamente con los datos de testing:
# Calcular la curva ROC
pROC_obj <- roc(df_test$left, df_test$score,
smoothed = FALSE,
# argumentos del intervalo de confianza
ci=TRUE, ci.alpha=0.9, stratified=FALSE,
# argumentos del gráfico
plot=TRUE, auc.polygon=TRUE, max.auc.polygon=TRUE, grid=TRUE,
print.auc=TRUE, show.thres=TRUE)El valor que muestra dentro del gráfico, representa el porcentaje de la superficie del gráfico está cubierto por la Curva ROC del modelo. Mientras mayor sea ese porcentaje, mejor.
Cuando graficamos la curva ROC, R internamente ordena las predicciones de mayor probabilidad a menor. Para calcular el lift y el gain hace lo mismo, y divide al dataset en deciles (o sea que lo divide en 10 partes iguales).
El gain, (o ganancia en español), lo que mide es qué proporción de aciertos acumuladas para cada decil de las predicciones.
El lift, (o lift en español), lo que mide es cuántas veces mejora las predicciones el modelo, respecto de no tener ningún modelo.
Usaremos una función del paquete funModeling
desarrollado por Pablo Casas. La función gain_lift() nos
hace los gráficos de gain y de lift y nos genera una
tabla con los resultados.
# Carlcular el gain y el lift de df_test
gain_lift(data = df_test, score = "score", target = "left")## Population Gain Lift Score.Point
## 1 10 23.58 2.36 0.550951519
## 2 20 52.37 2.62 0.425579762
## 3 30 66.86 2.23 0.307962335
## 4 40 78.22 1.96 0.229118634
## 5 50 86.36 1.73 0.171325182
## 6 60 92.99 1.55 0.128518793
## 7 70 97.54 1.39 0.090582093
## 8 80 98.86 1.24 0.061068259
## 9 90 99.34 1.10 0.033568197
## 10 100 100.00 1.00 0.002230154
¿Qué hay de fondo en estas métricas?
Tanto con la curva ROC, con el gain, y con el lift, lo que buscan medir, es qué tanto mejoran las probabilidades los modelos respecto de no hacer ningún cálculo.
Desde el punto de vista de la teoría de la probabilidad, en su forma más simple, un empleado tiene 50% chances de irse, y 50% de chances de quedarse en la empresa. Esta falta de modelo, es de alguna manera un modelo aleatorio porque dejamos al azar la ocurrencia de renuncias.
Por ejemplo el lift nos dice en cada decil, cuántas veces mejor es la predicción respecto del modelo aleatorio.
En particular este modelo nos arrojó buenos resultados. Pero puede ocurrir que tengamos un modelo que no tenga tan buena performance pero que nos puede ser útil de todas maneras.
Algo interesante que surge del análisis exploratorio, son los tres grupos notorios que tenemos entre los empleados que se van.
Tenemos un grupo llamativo, que representan a los empleados de alto desempeño y de alto nivel de satisfacción.
ggplot(datos, aes(x = last_evaluation, y = satisfaction_level, color = factor(left)))+
geom_point(alpha = 0.7)+
scale_color_manual(values = c("#BFC9CA","#2874A6")) # Asigno manualmente los colores a los puntosVamos a dividir a los empleados del archivo en 3 grupos, para determinar qué tan bueno es el modelo con los top de lo top, o sea con las personas de alto desempeño y alto nivel de satisfacción.
Para ello, haremos un análisis de clusters, con un algoritmo que se
llama k-means, que es uno de los más sencillos para
encontrar grupos.
library(ggthemes)
# Seleccionamos las variables para elegir los clusters
variables_cluster <- df_test %>%
select(last_evaluation, satisfaction_level)
# Preparo los datos para hacer el cálculo
vc <- scale(variables_cluster)
# Defino una semilla para repetir resultados
set.seed(87)
# Corro el algoritmo de clustering
fit_vc <- kmeans(vc, 3)
# Agrego los clusters ajustados (calculados) al dataset
df_test$cluster <- fit_vc$cluster
# Visualizo los resultados
ggplot(df_test, aes(x = last_evaluation,
y = satisfaction_level,
color = factor(cluster))) +
geom_point(alpha = 0.7) Ahora, vamos a filtrar los resultados del cluster 1 que son las personas de alto nivel de desempeño y de alto nivel de satisfacción.
# Filtramos los datos del cluster 1
modelo_c1 <- df_test %>%
filter(cluster == 1)
conf_matrix_c1 <- table(modelo_c1$left, modelo_c1$prediccion)
# Veamos todas las métricas de la matriz con esta función del paquete caret
confusionMatrix(conf_matrix_c1)## Confusion Matrix and Statistics
##
##
## 0 1
## 0 1587 16
## 1 284 1
##
## Accuracy : 0.8411
## 95% CI : (0.8238, 0.8573)
## No Information Rate : 0.991
## P-Value [Acc > NIR] : 1
##
## Kappa : -0.0106
##
## Mcnemar's Test P-Value : <0.0000000000000002
##
## Sensitivity : 0.848210
## Specificity : 0.058824
## Pos Pred Value : 0.990019
## Neg Pred Value : 0.003509
## Prevalence : 0.990996
## Detection Rate : 0.840572
## Detection Prevalence : 0.849047
## Balanced Accuracy : 0.453517
##
## 'Positive' Class : 0
##
En este caso vemos que el modelo es aún mejor con este grupo de interés.
Si quieren conocer otros modelos predictivos pueden consultar las carpetas de Junio y de Julio 2020 que entre las sesiones 6 a la 10 realizamos distintas sesiones sobre modelos predictivos.
Si quieren revisar las fuentes de consulta para armar esta clase les recomiendo que vean:
Max Kuhn, The caret package
Pablo Casas, Libro Vivo de Ciencia de Datos
Keith McNulty, The Handbook of Regression Modeling in People Analytics
https://www.listendata.com/2015/06/r-function-gain-and-lift-table.html https://www.listendata.com/2014/08/excel-template-gain-and-lift-charts.html