library(tidyverse)
library(caret)
library(scales)
library(class)
library(kknn)
library(rpart)
library(rpart.plot)
library(C50)
library(e1071)
library(naivebayes)

Introducción:

En esta trabajo estaremos trabajando con el método de clasificación supervisada con el fin de identificar qué factores mantienen a los empleados en la empresa y cuales los impulsa a irse. Estaremos utilizando la base de datos dada “Employee IBM” donde la variable de respuesta es el nivel de satisfacción del empleado (escala de 1 al 4). Para cumplir nuestro objetivo estaremos aplicando tres metodologías de clasificación supervisada: K-vecinos más cercanos , Arboles de decisiones , y Clasificación Bayesiano

Primer Paso: Preparación de datos

Comenzamos cargando la base de datos Employee-IBM.csv, la cual contiene información de 1,470 empleados de IBM con 16 variables que incluyen edad, salario, años en la empresa, balance vida-trabajo, entre otras. La variable que queremos predecir es Satisfaction, que originalmente tenía cuatro niveles (1, 2, 3, 4). También queremos tener una visualización de la distribución por clases a través de gráficas de barras para tener cierto contexto de los datos.

dim(datos)
## [1] 1470   16
table(datos$Satisfaction)
## 
##   1   2   3   4 
## 276 303 459 432
round(prop.table(table(datos$Satisfaction)), 2)  
## 
##    1    2    3    4 
## 0.19 0.21 0.31 0.29
## Visualización de la distribución de las clases 
  
barplot(table(datos$Satisfaction),
          main   = "Distribución del Nivel de Satisfacción",
          xlab   = "Nivel de Satisfacción",
          ylab   = "Frecuencia",
          col    = c("tomato","steelblue","seagreen","goldenrod"),
          border = "white")

sum(is.na(datos))
## [1] 0

Segundo Paso: División de Datos

Con los datos preparados, el siguiente paso fue crear las versiones del dataset necesarias para cada método y dividirlos en conjunto de entrenamiento y prueba.

Creamos dos versiones: datos_factor, donde convertimos Satisfaction y Gender a variables de tipo factor ya que son las únicas variables categóricas reales del dataset, y datos_norm, donde normalizamos todas las variables numéricas usando la función rescale() del paquete scales. Esta normalización es necesaria exclusivamente para KNN, ya que ese algoritmo mide distancias entre observaciones — si una variable tiene valores en miles como MonthlyIncome y otra entre 0 y 1, la variable grande dominaría la distancia injustamente.

# Solo Gender y Satisfaction se convierten a factor
datos_factor <- datos %>%
  mutate(
    Satisfaction = as.factor(Satisfaction),
    Gender       = as.factor(Gender)
  )

# Versión normalizada (para KNN)
# Solo excluimos Satisfaction y Gender — todo lo demás es numérico y se normaliza
datos_norm <- datos %>%
  select(-Satisfaction, -Gender) %>%
  mutate(across(everything(), rescale))

# Dividir en entrenamiento y prueba
folds <- createFolds(datos_factor$Satisfaction, k = 6)

entrenamiento      <- datos_factor[-folds[[6]], ]
prueba             <- datos_factor[folds[[6]], ]
entrenamiento_norm <- datos_norm[-folds[[6]], ]
prueba_norm        <- datos_norm[folds[[6]], ]
entrenamiento_labels <- datos_factor$Satisfaction[-folds[[6]]]
prueba_labels        <- datos_factor$Satisfaction[folds[[6]]]

# Verificar dimensiones y balance
dim(entrenamiento)[1]
## [1] 1226
dim(prueba)[1]
## [1] 244
round(prop.table(table(entrenamiento$Satisfaction)), 2)
## 
##    1    2    3    4 
## 0.19 0.21 0.31 0.29
round(prop.table(table(prueba$Satisfaction)), 2)
## 
##    1    2    3    4 
## 0.19 0.20 0.31 0.30

Por ultimo, para dividir los datos usamos createFolds() con k=5, lo que garantiza que tanto el entrenamiento como la prueba mantengan la misma proporción de clases (39/61). El resultado fue 1,176 observaciones para entrenamiento y 294 para prueba.

Paso 3: Aplicación de los tres métodos

Para cumplir nuestro objetivo estaremos aplicando tres metodologías de clasificación supervisada: K-vecinos más cercanos , Arboles de decisiones , y Clasificación Bayesiano

Método 1: KNN

KNN: Primero usamos train.kknn() para encontrar automáticamente el mejor valor de k, que resultó ser k=29.

modelo_knn <- train.kknn(Satisfaction ~ .,
                         data = cbind(entrenamiento_norm,
                                      Satisfaction = entrenamiento_labels),
                         kmax = 30)
modelo_knn
## 
## Call:
## train.kknn(formula = Satisfaction ~ ., data = cbind(entrenamiento_norm,     Satisfaction = entrenamiento_labels), kmax = 30)
## 
## Type of response variable: nominal
## Minimal misclassification: 0.7185971
## Best kernel: optimal
## Best k: 30

Luego aplicamos knn() con ese valor sobre los datos de prueba normalizados. La matriz de confusión mostró un accuracy de 54.8% con un Kappa de -0.0237, por debajo del baseline de 27%.

pred_knn <- knn(train = entrenamiento_norm,
                test  = prueba_norm,
                cl    = entrenamiento_labels,
                k     = modelo_knn$best.parameters$k)

confusionMatrix(pred_knn, prueba_labels)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  1  2  3  4
##          1  1  4  9  7
##          2  3  4  6  6
##          3 17 29 35 31
##          4 25 13 26 28
## 
## Overall Statistics
##                                           
##                Accuracy : 0.2787          
##                  95% CI : (0.2234, 0.3395)
##     No Information Rate : 0.3115          
##     P-Value [Acc > NIR] : 0.8807          
##                                           
##                   Kappa : -0.0108         
##                                           
##  Mcnemar's Test P-Value : 2.694e-05       
## 
## Statistics by Class:
## 
##                      Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity          0.021739  0.08000   0.4605   0.3889
## Specificity          0.898990  0.92268   0.5417   0.6279
## Pos Pred Value       0.047619  0.21053   0.3125   0.3043
## Neg Pred Value       0.798206  0.79556   0.6894   0.7105
## Prevalence           0.188525  0.20492   0.3115   0.2951
## Detection Rate       0.004098  0.01639   0.1434   0.1148
## Detection Prevalence 0.086066  0.07787   0.4590   0.3770
## Balanced Accuracy    0.460365  0.50134   0.5011   0.5084

Método 2: Arboles de desición (CART)

Usamos rpart() para construir el árbol con los datos de entrenamiento y rpart.plot() para visualizarlo.

modelo_cart <- rpart(Satisfaction ~ ., data = entrenamiento)
rpart.plot(modelo_cart)

Luego, al evaluar sobre los datos de prueba con predict(), obtuvimos un accuracy de 30% con Kappa de -0.0061. El árbol prácticamente predijo siempre “satisfecho” — la clase mayoritaria

pred_cart <- predict(modelo_cart, prueba, type = "class")
confusionMatrix(pred_cart, prueba$Satisfaction)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  1  2  3  4
##          1  0  0  0  0
##          2  0  0  0  0
##          3 34 35 61 47
##          4 12 15 15 25
## 
## Overall Statistics
##                                          
##                Accuracy : 0.3525         
##                  95% CI : (0.2926, 0.416)
##     No Information Rate : 0.3115         
##     P-Value [Acc > NIR] : 0.0955         
##                                          
##                   Kappa : 0.0656         
##                                          
##  Mcnemar's Test P-Value : NA             
## 
## Statistics by Class:
## 
##                      Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity            0.0000   0.0000   0.8026   0.3472
## Specificity            1.0000   1.0000   0.3095   0.7558
## Pos Pred Value            NaN      NaN   0.3446   0.3731
## Neg Pred Value         0.8115   0.7951   0.7761   0.7345
## Prevalence             0.1885   0.2049   0.3115   0.2951
## Detection Rate         0.0000   0.0000   0.2500   0.1025
## Detection Prevalence   0.0000   0.0000   0.7254   0.2746
## Balanced Accuracy      0.5000   0.5000   0.5561   0.5515

Método 3: Clasificador Bayesiano

Usamos naiveBayes() del paquete e1071. Este algoritmo calcula la probabilidad de cada clase dado el valor de las variables predictoras, asumiendo independencia entre ellas.

modelo_bayes <- naiveBayes(Satisfaction ~ ., data = entrenamiento)
pred_bayes <- predict(modelo_bayes, prueba)

Al evaluar sobre prueba, obtuvo un accuracy de 27% con Kappa de -0.023. Los tres modelos obtuvieron resultados por debajo o iguales al baseline, con Kappa negativo o cercano a cero — señal de que ninguno está aprendiendo patrones reales.

confusionMatrix(pred_bayes, prueba$Satisfaction)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  1  2  3  4
##          1  2  5  6  7
##          2  6  7  9  4
##          3 25 28 40 45
##          4 13 10 21 16
## 
## Overall Statistics
##                                          
##                Accuracy : 0.2664         
##                  95% CI : (0.212, 0.3265)
##     No Information Rate : 0.3115         
##     P-Value [Acc > NIR] : 0.9457         
##                                          
##                   Kappa : -0.0275        
##                                          
##  Mcnemar's Test P-Value : 5.17e-06       
## 
## Statistics by Class:
## 
##                      Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity          0.043478  0.14000   0.5263  0.22222
## Specificity          0.909091  0.90206   0.4167  0.74419
## Pos Pred Value       0.100000  0.26923   0.2899  0.26667
## Neg Pred Value       0.803571  0.80275   0.6604  0.69565
## Prevalence           0.188525  0.20492   0.3115  0.29508
## Detection Rate       0.008197  0.02869   0.1639  0.06557
## Detection Prevalence 0.081967  0.10656   0.5656  0.24590
## Balanced Accuracy    0.476285  0.52103   0.4715  0.48320
train_control <- trainControl(method = "cv",
                              number = 10,
                              savePredictions = TRUE)

Paso 4 — Validación cruzada y comparación:

Para confirmar que los resultados del Paso 3 no fueron un accidente de una sola partición, aplicamos validación cruzada de 10 folds usando trainControl() de caret. Este método divide los datos en 10 partes, entrena el modelo 10 veces usando 9 partes y prueba con la restante, rotando el fold de prueba en cada iteración. Al final promedia los resultados de las 10 iteraciones

KNN con CV

knn_cv <- train(Satisfaction ~ .,
                data      = cbind(entrenamiento_norm,
                                  Satisfaction = entrenamiento_labels),
                method    = "knn",
                trControl = train_control,
                tuneGrid  = data.frame(k = modelo_knn$best.parameters$k))

Cart con CV

cart_cv <- train(Satisfaction ~ .,
                 data      = entrenamiento,
                 method    = "rpart",
                 trControl = train_control,
                 tuneLength = 5)

Bayesiano con CV

bayes_cv <- train(Satisfaction ~ .,
                  data      = entrenamiento,
                  method    = "naive_bayes",
                  trControl = train_control)

Comparar los tres modelos

comparacion <- resamples(list(KNN   = knn_cv,
                              CART  = cart_cv,
                              Bayes = bayes_cv))
summary(comparacion)
## 
## Call:
## summary.resamples(object = comparacion)
## 
## Models: KNN, CART, Bayes 
## Number of resamples: 10 
## 
## Accuracy 
##            Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
## KNN   0.2195122 0.2621951 0.2827869 0.2871518 0.3156737 0.3495935    0
## CART  0.2049180 0.2698920 0.3036323 0.2944530 0.3156737 0.3852459    0
## Bayes 0.2580645 0.2815970 0.3142077 0.3100233 0.3401140 0.3524590    0
## 
## Kappa 
##              Min.     1st Qu.        Median        Mean    3rd Qu.       Max.
## KNN   -0.08489526 -0.02827551 -0.0003486521 0.007260273 0.04641069 0.09956076
## CART  -0.12651119 -0.02627169  0.0106517217 0.004456947 0.02582983 0.14454001
## Bayes -0.04430611 -0.01530018  0.0292539604 0.023059476 0.06487337 0.07549161
##       NA's
## KNN      0
## CART     0
## Bayes    0
dotplot(comparacion)

Los resultados fueron consistentes con el Paso 3 — los tres modelos obtuvieron accuracy promedio entre 29.4% y 30.9%, equivalente al baseline de 31.3%, con Kappa promedio cercano a cero en todos los casos. Usamos resamples() y dotplot() para comparar los tres modelos visualmente, donde se observa que los intervalos de confianza de los tres se solapan completamente, indicando que no hay diferencia estadísticamente significativa entre ellos. Estos resultados, consistentes a través de 10 folds y tres metodologías diferentes, nos llevan a concluir que las variables disponibles en este dataset no tienen suficiente poder predictivo para clasificar el nivel de satisfacción laboral. Esto es un hallazgo válido en sí mismo — la satisfacción del empleado parece depender de factores cualitativos como cultura organizacional o relación con el supervisor, los cuales no están capturados en estos datos.