Universidad de Puerto Rico · Recinto de Río Piedras
Facultad de Administración de Empresas · Instituto de Estadística y SICI
Curso: ESTA 55044 · Profesor: Dr. Jairo Ayala
En este trabajo se realizó un análisis de clasificación supervisada
utilizando el conjunto de datos Employee_IBM.csv, con el
propósito de predecir el nivel de satisfacción de los empleados a partir
de distintas variables en relación al ámbito laboral. La variable
dependiente del análisis fue Satisfaction, la cual
originalmente estaba ordenada en cuatro niveles. No obstante, al
realizar los tres modelos de clasificación, estos daban tasas de
exactitud muy bajas y constantemente se equivocaban, clasificando la
mayoría de las observaciones en el nivel 3. Para solucionar este
problema, esta variable se transformó en una variable binaria con dos
categorías: Low y High. De esta forma,
los niveles 1 y 2 de satisfacción se clasificaron como satisfacción
baja, mientras que los niveles 3 y 4 se clasificaron como satisfacción
alta.
El análisis comenzó cargando los datos y observando su estructura. Este conjunto de datos contiene 1,470 observaciones y 16 variables. La variable de respuesta fue Satisfaction, que originalmente se encontraba en una escala de 1 a 4. Las demás variables estaban relacionadas con características laborales y personales del empleado. Luego de cargar los datos, se hizo una revisión de datos faltantes y se pudo concluir que no había. Por tal razón, no hubo necesidad de imputar o eliminar datos.
Por otra parte, se corroboró las categorías de los datos y se realizó una limpieza de los nombres de algunas variables para que quedaran más adecuadas para su análisis.
# ── Carga del dataset ──────────────────────────────────────
datos <- read_csv("Employee-IBM.csv")
gg_miss_var(datos) # Verificar valores faltantes## spc_tbl_ [1,470 × 16] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
## $ Satisfaction : num [1:1470] 1 4 2 3 4 3 1 2 2 2 ...
## $ Age : num [1:1470] 41 49 37 33 27 32 59 30 38 36 ...
## $ Gender : chr [1:1470] "Female" "Male" "Male" "Female" ...
## $ HourlyRate : num [1:1470] 94 61 92 56 40 79 81 67 44 94 ...
## $ JobInvolvement : num [1:1470] 3 2 2 3 3 3 4 3 2 3 ...
## $ MonthlyIncome : num [1:1470] 5993 5130 2090 2909 3468 ...
## $ NumCompaniesWorked : num [1:1470] 8 1 6 1 9 0 4 1 0 6 ...
## $ PercentSalaryHike : num [1:1470] 11 23 15 11 12 13 20 22 21 13 ...
## $ StockOptionLevel : num [1:1470] 0 1 0 0 1 0 3 1 0 2 ...
## $ TotalWorkingYears : num [1:1470] 8 10 7 8 6 8 12 1 10 17 ...
## $ TrainingTimesLastYear : num [1:1470] 0 3 3 3 3 2 3 2 2 3 ...
## $ WorkLifeBalance : num [1:1470] 1 3 3 3 3 2 2 3 3 2 ...
## $ YearsAtCompany : num [1:1470] 6 10 0 8 2 7 1 1 9 7 ...
## $ YearsInCurrentRole : num [1:1470] 4 7 0 7 2 7 0 0 7 7 ...
## $ YearsSinceLastPromotion: num [1:1470] 0 1 0 3 2 3 0 0 1 7 ...
## $ YearsWithCurrManager : num [1:1470] 5 7 0 0 2 6 0 0 8 7 ...
## - attr(*, "spec")=
## .. cols(
## .. Satisfaction = col_double(),
## .. Age = col_double(),
## .. Gender = col_character(),
## .. HourlyRate = col_double(),
## .. JobInvolvement = col_double(),
## .. MonthlyIncome = col_double(),
## .. NumCompaniesWorked = col_double(),
## .. PercentSalaryHike = col_double(),
## .. StockOptionLevel = col_double(),
## .. TotalWorkingYears = col_double(),
## .. TrainingTimesLastYear = col_double(),
## .. WorkLifeBalance = col_double(),
## .. YearsAtCompany = col_double(),
## .. YearsInCurrentRole = col_double(),
## .. YearsSinceLastPromotion = col_double(),
## .. YearsWithCurrManager = col_double()
## .. )
## - attr(*, "problems")=<externalptr>
# ── Renombrar variables ────────────────────────────────────
datos <- datos %>%
dplyr::rename(
Hourly_Rate = HourlyRate,
Job_Involvement = JobInvolvement,
Monthly_Income = MonthlyIncome,
Num_Companies_Worked = NumCompaniesWorked,
Percent_Salary_Hike = PercentSalaryHike,
Stock_Option_Level = StockOptionLevel,
Total_Working_Years = TotalWorkingYears,
Training_Times_Last_Year = TrainingTimesLastYear,
Work_Life_Balance = WorkLifeBalance,
Years_At_Company = YearsAtCompany,
Years_In_Current_Role = YearsInCurrentRole,
Years_Since_Last_Promotion = YearsSinceLastPromotion,
Years_With_Curr_Manager = YearsWithCurrManager)Variables renombradas durante la limpieza:
| Variable Original | Nombre Utilizado en el Analisis |
|---|---|
| HourlyRate | Hourly_Rate |
| JobInvolvement | Job_Involvement |
| MonthlyIncome | Monthly_Income |
| NumCompaniesWorked | Num_Companies_Worked |
| PercentSalaryHike | Percent_Salary_Hike |
| StockOptionLevel | Stock_Option_Level |
| TotalWorkingYears | Total_Working_Years |
| TrainingTimesLastYear | Training_Times_Last_Year |
| WorkLifeBalance | Work_Life_Balance |
| YearsAtCompany | Years_At_Company |
| YearsInCurrentRole | Years_In_Current_Role |
| YearsSinceLastPromotion | Years_Since_Last_Promotion |
| YearsWithCurrManager | Years_With_Curr_Manager |
La exploración de las variables numéricas se realizó a través de histogramas y boxplots. Este proceso permitió revisar la forma en que los datos estaban distribuidos, observar el sesgo e identificar valores extremos. En ambos gráficos, se pueden observar distintas variables con valores atípicos, pero la que más llamó la atención fue la variable Monthly_Income, puesto que presentaba una distribución asimétrica hacia la derecha. En otras palabras, gran parte de los empleados presentaban ingresos bajos o intermedios, pero había empleados con niveles de ingresos muy superiores que podrían inflar el ingreso promedio.
Al calcular el promedio y la mediana de esta variable, confirmamos que estos valores la estaban inflando. La mediana de Monthly_Income fue de 4,919.00, mientras que el promedio fue de 6,502.93. Para corregir este sesgo, se creó una versión del conjunto de datos con la variable Monthly_Income transformada en logaritmo natural. Al revisar los boxplots nuevamente, pudimos confirmar que la transformación produjo una distribución más estable de los datos, lo cual podría ayudar al entrenamiento de los modelos.
# ── Histogramas ────────────────────────────────────────────
Var_Num <- datos %>% select(-c(Satisfaction, Gender))
Var_Num_Wide <- Var_Num %>%
pivot_longer(
cols = everything(),
names_to = "Variable",
values_to = "Valor")
ggplot(Var_Num_Wide, aes(x = Valor)) +
geom_histogram(bins = 30) +
facet_wrap(~ Variable, scales = "free") +
theme_minimal()Histogramas de variables numéricas
# ── Boxplots y detección de valores extremos ───────────────
ggplot(Var_Num_Wide, aes(x = "", y = Valor)) +
geom_boxplot() +
facet_wrap(~ Variable, scales = "free_y") +
theme_minimal() +
labs(x = "", y = "Valor")Boxplots de variables numéricas
# ── Transformación logarítmica de Monthly_Income ────────────
datos_log_exploracion <- datos %>%
mutate(Monthly_Income = log(Monthly_Income)) %>%
rename(Log_Monthly_Income = Monthly_Income)
Var_Num_Log <- datos_log_exploracion %>% select(-c(Satisfaction, Gender))
Var_Num_Log_Wide <- Var_Num_Log %>%
pivot_longer(
cols = everything(),
names_to = "Variable",
values_to = "Valor")
ggplot(Var_Num_Log_Wide, aes(x = "", y = Valor)) +
geom_boxplot() +
facet_wrap(~ Variable, scales = "free_y") +
theme_minimal() +
labs(x = "", y = "Valor")La variable Satisfaction tenía cuatro categorías: los niveles 1 y 2 representan niveles más bajos, mientras que los niveles 3 y 4 representan niveles más altos de satisfacción del empleado. Antes de convertir la variable a binaria, se hizo una revisión de su estructura para entender su comportamiento con los datos.
Distribución original de Satisfaction:
##
## 1 2 3 4
## 276 303 459 432
##
## 1 2 3 4
## 0.19 0.21 0.31 0.29
La mayoría de las observaciones se concentraron en los niveles 3 y 4. En particular, el nivel 3 representó el 31.22% de los datos y el nivel 4 el 29.39%. Esto significa que, aun antes de crear la variable binaria, ya se observaba una tendencia alta hacia los niveles más altos de satisfacción.
Para entrenar los modelos de clasificación, la variable Satisfaction se transformó en dos categorías: Low para los niveles 1 y 2, y High para los niveles 3 y 4. Luego de la modificación, observamos nuevamente la distribución de la variable con respecto a los datos y encontramos que la clase High representa un 60.61% de los datos, mientras que la clase Low se quedó con el 39.39%. Este desbalance en los datos no es extremo, pero sí se tiene que tener en cuenta cuando se evalúen los modelos de clasificación.
Distribución binaria de Satisfaction:
##
## Low High
## 579 891
##
## Low High
## 0.39 0.61
Luego de limpiar los datos y realizar la exploración inicial, se realizó la división de los datos en los conjuntos de entrenamiento y prueba con una partición 80/20. El conjunto de entrenamiento quedó con 1,177 observaciones y el conjunto de prueba con 293. En la prueba, había 178 casos High y 115 casos Low. Se debe tener en cuenta esta proporción, puesto que un modelo que prediga muchas observaciones como High puede tener una exactitud alta, aun si falla en la predicción de la clase Low.
Distribución de la división entrenamiento-prueba:
# ── ───────────
datos_AAD <- datos %>%
mutate(
Gender = as.factor(Gender))
datos_nb <- datos_AAD %>%
mutate(Monthly_Income = log(Monthly_Income))
datos_red <- datos_AAD %>%
mutate(
Gender = ifelse(Gender == "Male", 1, 0),
sat_Low = ifelse(Satisfaction == "Low", 1, 0),
sat_High = ifelse(Satisfaction == "High", 1, 0)
) %>%
select(-Satisfaction)
set.seed(123)
idx <- createDataPartition(datos$Satisfaction, p = 0.80, list = FALSE)
entrenamiento_AAD <- datos_AAD[ idx, ]
prueba_AAD <- datos_AAD[-idx, ]
entrenamiento_nb <- datos_nb[ idx, ]
prueba_nb <- datos_nb[-idx, ]
entrenamiento_red <- datos_red[ idx, ]
prueba_red <- datos_red[-idx, ]
entrenamiento_labels <- datos$Satisfaction[idx]
prueba_labels <- datos$Satisfaction[-idx]
set.seed(4002)
train_control <- trainControl(
method = "cv",
number = 10,
savePredictions = "final",
classProbs = TRUE,
sampling = "down" )| Conjunto | Observaciones | High | Low |
|---|---|---|---|
| Entrenamiento | 1177 | 713 | 464 |
| Prueba | 293 | 178 | 115 |
El primer modelo de clasificación supervisada que se realizó fue el Clasificador Bayesiano Naive sin utilizar los datos con la transformación logarítmica. Este modelo obtuvo una exactitud de 56.67% y una tasa de error de 43.33%. Clasificó correctamente 17 casos Low y 149 casos High. Sin embargo, cometió errores al clasificar incorrectamente 98 casos Low como High y 29 casos High como Low. Su sensibilidad para High fue de 83.71% y su especificidad para Low fue de 14.78%. Esto significa que el modelo sí tuvo en cuenta clasificar algunas observaciones como Low, pero tuvo mucha dificultad para hacerlo correctamente.
# ── Entrenamiento del Clasificador Bayesiano Naive (sin log) ─
modelo_CBN <- naiveBayes(Satisfaction ~ ., data = entrenamiento_AAD)
pred_CBN <- predict(modelo_CBN, prueba_AAD)
table(pred_CBN, prueba_AAD$Satisfaction)##
## pred_CBN Low High
## Low 17 29
## High 98 149
# ── Predicción, matriz de confusión y métricas ──────────────
confusion_CBN <- confusionMatrix(
pred_CBN,
prueba_labels,
positive = "High")
confusion_CBN## Confusion Matrix and Statistics
##
## Reference
## Prediction Low High
## Low 17 29
## High 98 149
##
## Accuracy : 0.5666
## 95% CI : (0.5077, 0.6241)
## No Information Rate : 0.6075
## P-Value [Acc > NIR] : 0.932
##
## Kappa : -0.0169
##
## Mcnemar's Test P-Value : 1.599e-09
##
## Sensitivity : 0.8371
## Specificity : 0.1478
## Pos Pred Value : 0.6032
## Neg Pred Value : 0.3696
## Prevalence : 0.6075
## Detection Rate : 0.5085
## Detection Prevalence : 0.8430
## Balanced Accuracy : 0.4925
##
## 'Positive' Class : High
##
Posteriormente, se entrenó una versión del Clasificador Bayesiano usando el conjunto de datos con la transformación logarítmica de la variable Monthly_Income. El propósito de este modelo era evaluar si la transformación logarítmica ayudaba al cálculo de probabilidades del modelo. En la matriz de confusión, el modelo clasificó correctamente 17 casos Low y 154 casos High. En comparación con el modelo de CBN sin log, identificó correctamente más casos “High” , pero se equivocó en más casos que anteriormente había clasificado correctamente en la clase Low. Este modelo obtuvo una tasa de exactitud de 58.36% y una tasa de error de 41.64%. Su sensibilidad para High fue 86.52%, mientras que su especificidad para Low fue 14.78%. Aunque la transformación logarítmica logró que la variable Monthly_Income tuviera una distribución más estable, no aportó mucho al desempeño del modelo.
# ── Entrenamiento del Clasificador Bayesiano Naive (con log) ─
modelo_CBN2 <- naiveBayes(Satisfaction ~ ., data = entrenamiento_nb)
pred_CBN2 <- predict(modelo_CBN2, prueba_nb %>% select(-Satisfaction))
table(pred_CBN2, prueba_nb$Satisfaction)##
## pred_CBN2 Low High
## Low 17 24
## High 98 154
# ── Predicción, matriz de confusión y métricas ──────────────
confusion_CBN2 <- confusionMatrix(
pred_CBN2,
as.factor(prueba_nb$Satisfaction),
positive = "High")
confusion_CBN2## Confusion Matrix and Statistics
##
## Reference
## Prediction Low High
## Low 17 24
## High 98 154
##
## Accuracy : 0.5836
## 95% CI : (0.5249, 0.6407)
## No Information Rate : 0.6075
## P-Value [Acc > NIR] : 0.8154
##
## Kappa : 0.0147
##
## Mcnemar's Test P-Value : 3.866e-11
##
## Sensitivity : 0.8652
## Specificity : 0.1478
## Pos Pred Value : 0.6111
## Neg Pred Value : 0.4146
## Prevalence : 0.6075
## Detection Rate : 0.5256
## Detection Prevalence : 0.8601
## Balanced Accuracy : 0.5065
##
## 'Positive' Class : High
##
El tercer modelo fue el árbol de decisión sin la transformación
logarítmica de la variable Monthly_Income. Una ventaja de este
modelo es que no se ve afectado por valores atípicos. Por esta razón, no
se hizo una comparación del modelo con la transformación logarítmica.
Además, en el código, el árbol se controló con parámetros como
cp, minsplit, minbucket y
maxdepth, con el fin de limitar su complejidad. Este modelo
obtuvo una tasa de exactitud de 59.39%. Sin embargo,
este modelo clasificó 164 casos High correctamente, pero solo 10 casos
Low. Su sensibilidad 92.14%. Esto significa que el
árbol tuvo una tendencia a clasificar hacia la clase High y casi no
logró identificar empleados con satisfacción baja.
# ── Entrenamiento del Árbol de Decisión ─────────────────────
set.seed(2448)
modelo_rpart <- rpart(
Satisfaction ~ .,
data = entrenamiento_AAD,
method = "class",
control = rpart.control(
cp = 0.001,
minsplit = 10,
minbucket = 5,
maxdepth = 4))# ── Visualización del árbol con rpart.plot() ────────────────
rpart.plot(
modelo_rpart,
type = 2,
extra = 104,
fallen.leaves = TRUE,
cex = 0.7)Árbol de decisión — visualización
# ── Predicción, matriz de confusión y métricas ──────────────
pred_rpart <- predict(modelo_rpart, newdata = prueba_AAD, type = "class")
confusion_rpart <- confusionMatrix(
pred_rpart,
prueba_AAD$Satisfaction,
positive = "High")
confusion_rpart## Confusion Matrix and Statistics
##
## Reference
## Prediction Low High
## Low 10 14
## High 105 164
##
## Accuracy : 0.5939
## 95% CI : (0.5352, 0.6506)
## No Information Rate : 0.6075
## P-Value [Acc > NIR] : 0.7059
##
## Kappa : 0.0097
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 0.92135
## Specificity : 0.08696
## Pos Pred Value : 0.60967
## Neg Pred Value : 0.41667
## Prevalence : 0.60751
## Detection Rate : 0.55973
## Detection Prevalence : 0.91809
## Balanced Accuracy : 0.50415
##
## 'Positive' Class : High
##
## Monthly_Income Age
## 11.5429355 5.2396142
## Hourly_Rate Years_In_Current_Role
## 4.8391411 4.2975457
## Total_Working_Years Years_Since_Last_Promotion
## 3.9917712 1.8779331
## Years_With_Curr_Manager Years_At_Company
## 1.2568219 1.2128797
## Num_Companies_Worked Work_Life_Balance
## 0.8804348 0.8078039
## Percent_Salary_Hike Job_Involvement
## 0.6852751 0.2450577
## Stock_Option_Level
## 0.2347222
Para el modelo de árbol de decisión, se identificó que las variables con mayor poder predictivo son, en orden de relevancia: el Ingreso Mensual, la Edad, la Tarifa por Hora, los Años en la Posición Actual y el Total de Años Laborados. Por el contrario, factores como el Porcentaje de Aumento Salarial, el Equilibrio Vida-Trabajo , el Involucramiento Laboral y el Nivel de Opciones de Acciones resultaron ser las variables de menor importancia estructural para este algoritmo.
El último modelo que se entrenó fue la red neuronal con la
transformación logarítmica de la variable Monthly_Income.
Además, se transformó la variable Gender a variable numérica usando 1
para Male y 0 para Female. Luego se creó el conjunto de entrenamiento
con variables indicadoras para Satisfaction: sat_Low y
sat_High. La red neuronal obtuvo una tasa de exactitud de
60.75% y una tasa de error de 39.25%.
Clasificó correctamente los 178 casos High y ninguno de los casos Low.
Su sensibilidad para High fue de 100%, pero su
especificidad para Low solamente fue de 0. Esto indica que, aunque fue
el modelo con mejor exactitud, su clasificación también tenía una
tendencia a predecir la clase High.
# ── Preparación de datos y entrenamiento de la Red Neuronal ──
variables_predictoras <- setdiff(
names(entrenamiento_red),
c("sat_Low", "sat_High"))
formula_rna <- as.formula(
paste(
"sat_Low + sat_High ~",
paste(variables_predictoras, collapse = " + ")))
set.seed(4058)
RNA1 <- neuralnet(
formula_rna,
data = entrenamiento_red,
hidden = 5,
linear.output = FALSE,
err.fct = "ce",
stepmax = 1e7)Arquitectura de la Red Neuronal
## Capas ocultas: 1
## Neuronas por capa: 5 (Capa Oculta)
## Función de activación: logistic
## Iteraciones (stepmax): 42
La red neuronal presenta 15 variables de entrada (desde Edad hasta Años con el Manager actual), una capa oculta con 5 neuronas y una capa de salida con dos clases: Satisfacción Baja (sat_Low) y Alta (sat_High). El modelo incorpora dos nodos de sesgo (bias), uno para la capa oculta y otro para la de salida. La función de activación utilizada es logística (sigmoide).
Al analizar los pesos , se observa que el Ingreso Mensual es una de las variables con mayor peso positivo hacia las neuronas finales. No obstante, la variable Hourly Rate presenta pesos negativos cuyo peso negativo sugiere un efecto inhibidor considerablemente fuerte sobre la activación, confirmando una influencia significativa pero inversa en la predicción de la satisfacción
# ── Predicción, matriz de confusión y métricas ──────────────
output_test <- compute(RNA1, prueba_red[, variables_predictoras])
predicciones_prob <- output_test$net.result
pred_labels <- factor(
ifelse(apply(predicciones_prob, 1, which.max) == 1, "Low", "High"),
levels = c("Low", "High")
)
confusion_RNA <- confusionMatrix(
data = pred_labels,
reference = prueba_AAD$Satisfaction,
positive = "High"
)
confusion_RNA## Confusion Matrix and Statistics
##
## Reference
## Prediction Low High
## Low 0 0
## High 115 178
##
## Accuracy : 0.6075
## 95% CI : (0.549, 0.6638)
## No Information Rate : 0.6075
## P-Value [Acc > NIR] : 0.5255
##
## Kappa : 0
##
## Mcnemar's Test P-Value : <2e-16
##
## Sensitivity : 1.0000
## Specificity : 0.0000
## Pos Pred Value : 0.6075
## Neg Pred Value : NaN
## Prevalence : 0.6075
## Detection Rate : 0.6075
## Detection Prevalence : 1.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : High
##
Luego de evaluar cada modelo, se aplicó validación cruzada a cada uno para obtener una estimación más estable del desempeño de los modelos. Para los modelos del Clasificador Bayesiano se utilizó una validación cruzada de 20 folds, mientras que para el árbol de decisión y la red neuronal se utilizó una validación cruzada de 10 folds.
El modelo con mayor exactitud promedio en la validación cruzada fue el modelo de redes neurales con 54.29%.Le siguió el Clasificador Bayesiano sin transformación logarítmica, con 51.23%, luego, el Clasificador Bayesiano con log con 51.06% y finalmente, el modelo de árbol de decisión con 50.55%
No obstante, todos los modelos obtuvieron sensibilidades altas para High, pero especificidades muy bajas para Low. Esto indica que los modelos continuaron presentando esa tendencia hacia la clase High.
# Validación cruzada — Clasificador Bayesiano sin log (20 folds) ───
set.seed(4002)
NBC_cv <- train(
Satisfaction ~ .,
data = entrenamiento_AAD,
method = "naive_bayes",
trControl = train_control)
NBC_cv## Naive Bayes
##
## 1177 samples
## 15 predictor
## 2 classes: 'Low', 'High'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 1060, 1058, 1060, 1060, 1058, 1060, ...
## Addtional sampling using down-sampling
##
## Resampling results across tuning parameters:
##
## usekernel Accuracy Kappa
## FALSE 0.4681429 -0.001915518
## TRUE 0.5121700 0.017678769
##
## Tuning parameter 'laplace' was held constant at a value of 0
## Tuning
## parameter 'adjust' was held constant at a value of 1
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were laplace = 0, usekernel = TRUE
## and adjust = 1.
confusion_NBC_cv <- confusionMatrix(
NBC_cv$pred$pred,
NBC_cv$pred$obs,
positive = "High")
confusion_NBC_cv## Confusion Matrix and Statistics
##
## Reference
## Prediction Low High
## Low 228 338
## High 236 375
##
## Accuracy : 0.5123
## 95% CI : (0.4833, 0.5412)
## No Information Rate : 0.6058
## P-Value [Acc > NIR] : 1
##
## Kappa : 0.0167
##
## Mcnemar's Test P-Value : 2.491e-05
##
## Sensitivity : 0.5259
## Specificity : 0.4914
## Pos Pred Value : 0.6137
## Neg Pred Value : 0.4028
## Prevalence : 0.6058
## Detection Rate : 0.3186
## Detection Prevalence : 0.5191
## Balanced Accuracy : 0.5087
##
## 'Positive' Class : High
##
# Validación cruzada — Clasificador Bayesiano con log (20 folds) ───
set.seed(4002)
modelo_CBN2 = NBC_cv <- train(
Satisfaction ~ .,
data = entrenamiento_nb,
method = "naive_bayes",
trControl = train_control)
modelo_CBN2## Naive Bayes
##
## 1177 samples
## 15 predictor
## 2 classes: 'Low', 'High'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 1060, 1058, 1060, 1060, 1058, 1060, ...
## Addtional sampling using down-sampling
##
## Resampling results across tuning parameters:
##
## usekernel Accuracy Kappa
## FALSE 0.4809057 0.01373041
## TRUE 0.5103960 0.00851119
##
## Tuning parameter 'laplace' was held constant at a value of 0
## Tuning
## parameter 'adjust' was held constant at a value of 1
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were laplace = 0, usekernel = TRUE
## and adjust = 1.
confusion_CBN2_cv <- confusionMatrix(
modelo_CBN2$pred$pred,
modelo_CBN2$pred$obs,
positive = "High")
confusion_CBN2_cv## Confusion Matrix and Statistics
##
## Reference
## Prediction Low High
## Low 220 332
## High 244 381
##
## Accuracy : 0.5106
## 95% CI : (0.4816, 0.5395)
## No Information Rate : 0.6058
## P-Value [Acc > NIR] : 1.000000
##
## Kappa : 0.0082
##
## Mcnemar's Test P-Value : 0.000289
##
## Sensitivity : 0.5344
## Specificity : 0.4741
## Pos Pred Value : 0.6096
## Neg Pred Value : 0.3986
## Prevalence : 0.6058
## Detection Rate : 0.3237
## Detection Prevalence : 0.5310
## Balanced Accuracy : 0.5042
##
## 'Positive' Class : High
##
# ── Validación cruzada — Árbol de Decisión (10 folds) ────────
set.seed(4002)
ADD_cv <- train(
Satisfaction ~ .,
data = entrenamiento_AAD,
method = "rpart",
trControl = train_control,
tuneLength = 20)
ADD_cv## CART
##
## 1177 samples
## 15 predictor
## 2 classes: 'Low', 'High'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 1060, 1058, 1060, 1060, 1058, 1060, ...
## Addtional sampling using down-sampling
##
## Resampling results across tuning parameters:
##
## cp Accuracy Kappa
## 0.0000000000 0.5048363 0.0091827585
## 0.0005671506 0.5048363 0.0091827585
## 0.0011343013 0.5048363 0.0084333663
## 0.0017014519 0.5006347 -0.0019996725
## 0.0022686025 0.5006347 -0.0019996725
## 0.0028357532 0.4972447 -0.0095781441
## 0.0034029038 0.4964116 -0.0097760825
## 0.0039700544 0.4989181 -0.0034936923
## 0.0045372051 0.4929352 -0.0121677633
## 0.0051043557 0.5013886 -0.0003133551
## 0.0056715064 0.5022576 0.0018195633
## 0.0062386570 0.4988245 -0.0042624146
## 0.0068058076 0.4971296 -0.0100910218
## 0.0073729583 0.5055760 0.0031875500
## 0.0079401089 0.5055760 0.0031875500
## 0.0085072595 0.5004417 0.0040346227
## 0.0090744102 0.5030058 0.0064416970
## 0.0096415608 0.5013251 0.0107856773
## 0.0102087114 0.5038461 0.0214037116
## 0.0107758621 0.5047295 0.0260382600
##
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was cp = 0.007940109.
confusion_ADD_cv <- confusionMatrix(
ADD_cv$pred$pred,
ADD_cv$pred$obs,
positive = "High")
confusion_ADD_cv## Confusion Matrix and Statistics
##
## Reference
## Prediction Low High
## Low 221 339
## High 243 374
##
## Accuracy : 0.5055
## 95% CI : (0.4766, 0.5345)
## No Information Rate : 0.6058
## P-Value [Acc > NIR] : 1
##
## Kappa : 8e-04
##
## Mcnemar's Test P-Value : 8.221e-05
##
## Sensitivity : 0.5245
## Specificity : 0.4763
## Pos Pred Value : 0.6062
## Neg Pred Value : 0.3946
## Prevalence : 0.6058
## Detection Rate : 0.3178
## Detection Prevalence : 0.5242
## Balanced Accuracy : 0.5004
##
## 'Positive' Class : High
##
# ── Validación cruzada — Red Neuronal (10 folds) ─────────────
set.seed(4002)
RNA_cv <- train(
Satisfaction ~ .,
data = entrenamiento_AAD,
method = "nnet",
trControl = train_control,
tuneGrid = expand.grid(
size = c(3, 5, 7),
decay = c(0, 0.01, 0.1)),
maxit = 1000,
trace = FALSE)
RNA_cv## Neural Network
##
## 1177 samples
## 15 predictor
## 2 classes: 'Low', 'High'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 1060, 1058, 1060, 1060, 1058, 1060, ...
## Addtional sampling using down-sampling
##
## Resampling results across tuning parameters:
##
## size decay Accuracy Kappa
## 3 0.00 0.5020441 -0.0001985851
## 3 0.01 0.5226992 0.0298036419
## 3 0.10 0.4928147 0.0247165793
## 5 0.00 0.5283660 -0.0003042035
## 5 0.01 0.5181456 0.0464323878
## 5 0.10 0.4774947 0.0006714270
## 7 0.00 0.5427106 0.0000000000
## 7 0.01 0.4781912 -0.0090272182
## 7 0.10 0.5261114 0.0581212473
##
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were size = 7 and decay = 0.
confusion_RNA_cv <- confusionMatrix(
RNA_cv$pred$pred,
RNA_cv$pred$obs,
positive = "High")
confusion_RNA_cv## Confusion Matrix and Statistics
##
## Reference
## Prediction Low High
## Low 139 213
## High 325 500
##
## Accuracy : 0.5429
## 95% CI : (0.5139, 0.5717)
## No Information Rate : 0.6058
## P-Value [Acc > NIR] : 1
##
## Kappa : 9e-04
##
## Mcnemar's Test P-Value : 1.705e-06
##
## Sensitivity : 0.7013
## Specificity : 0.2996
## Pos Pred Value : 0.6061
## Neg Pred Value : 0.3949
## Prevalence : 0.6058
## Detection Rate : 0.4248
## Detection Prevalence : 0.7009
## Balanced Accuracy : 0.5004
##
## 'Positive' Class : High
##
| Modelo | Exactitud (prueba) | Sensibilidad High | Especificidad Low | Exactitud CV | Folds CV |
|---|---|---|---|---|---|
| Naive Bayes sin log | 56.66% | 52.59% | 49.14% | 51.23% | 20 |
| Naive Bayes con log | 58.36% | 53.44% | 47.41% | 51.06% | 20 |
| Arbol de Decision | 59.39% | 52.45% | 47.63% | 50.55% | 10 |
| Red Neuronal (con log) | 60.75% | 70.13% | 29.96% | 54.29% | 10 |
# ── Curvas ROC y valores AUC ────────────────────────────────
set.seed(4002)
# 1. Red Neuronal
prob_rna <- predict(RNA_cv, entrenamiento_AAD, type = "prob")$High
# 2. Árbol de Decisión
prob_rpart <- predict(ADD_cv, entrenamiento_AAD, type = "prob")$High
# 3. Naive Bayes (Sin Log)
prob_nb <- predict(NBC_cv, entrenamiento_AAD, type = "prob")$High
# 4. Naive Bayes (Con Log) - Usando el set que tiene los logs aplicados
prob_nb_log <- predict(modelo_CBN2, entrenamiento_nb, type = "prob")$High
roc_rna <- roc(entrenamiento_AAD$Satisfaction, prob_rna)
roc_rpart <- roc(entrenamiento_AAD$Satisfaction, prob_rpart)
roc_nb <- roc(entrenamiento_AAD$Satisfaction, prob_nb)
roc_nb_log <- roc(entrenamiento_nb$Satisfaction, prob_nb_log)
# Graficar la primera curva para establecer el lienzo
plot(roc_rna, col = "royalblue", lwd = 2, main = "Comparacion de Modelos: Curva ROC")
plot(roc_rpart, col = "forestgreen", lwd = 2, add = TRUE)
plot(roc_nb, col = "orange", lwd = 2, add = TRUE)
plot(roc_nb_log, col = "red", lwd = 2, add = TRUE)
abline(a = 0, b = 1, lty = 2, col = "grey")
# Leyenda con el AUC de cada uno
legend("bottomright",
legend = c(paste("Red Neuronal (AUC =", round(auc(roc_rna), 2), ")"),
paste("Arbol (AUC =", round(auc(roc_rpart), 2), ")"),
paste("Bayesiano (AUC =", round(auc(roc_nb), 2), ")"),
paste("Bayesiano Log (AUC =", round(auc(roc_nb_log), 2), ")")),
col = c("royalblue", "forestgreen", "orange", "red"), lwd = 2, bty = "n")Curvas ROC — comparación de los cuatro modelos
Basado en la comparativa de las curvas ROC, el modelo de Redes Neuronales se identifica como inefectivo para este conjunto de datos. Al presentar una métrica de AUC = 0.5, el modelo no logra distinguir entre clases, proporcionando una tasa de falsos positivos igual a sus clasificaciones correctas. A pesar de que su tasa de exactitud global sea alta, el modelo carece de poder predictivo real y presenta mayores limitaciones para capturar la complejidad de los factores de satisfacción en comparación con los demás modelos.
Por otro lado, el Modelo de Árbol de Decisión y los Modelos Bayesianos obtuvieron un rendimiento idéntico de AUC = 0.63. No obstante, el árbol de decisión demuestra una estabilidad superior en la relación entre sensibilidad y especificidad. Por esta razón, este modelo servirá como base para nuestras recomendaciones finales; aunque su tasa de aciertos no sea competitiva, su estructura permite una interpretación más robusta. Cabe destacar que, durante la validación cruzada, la tasa de aciertos entre el Árbol de Decisión y el clasificador Bayesiano resultó ser muy similar.
Finalmente, para el Clasificador Bayesiano, la transformación logarítmica de la variable “Ingreso Mensual” no provocó un cambio significativo en la capacidad predictiva del modelo. Ambas variantes obtuvieron la misma puntuación de AUC (0.63), lo que sugiere que la distribución original de los ingresos no era el factor limitante para este algoritmo en particular
# ── Análisis de importancia de variables ─────────────────────
importancia_df <- enframe(modelo_rpart$variable.importance, name = "Variable", value = "Importancia")
ggplot(importancia_df, aes(x = reorder(Variable, Importancia), y = Importancia)) +
geom_col(fill = "lightgreen") +
coord_flip() +
labs(title = "Importancia de las Variables",
x = "Variables",
y = "Puntaje de Importancia") +
theme_minimal()Importancia de variables — comparación entre modelos
Con base en las variables que resultaron más predictivas de la satisfacción laboral, se proponen las siguientes acciones:
- Implementación de planes de carrera: Dado que los años en el rol actual resultaron ser un factor de mayor peso predictivo, se recomienda invertir en programas de alineación profesional que permitan transformar la permanencia en oportunidades de crecimiento interno.
-Ajuste de competitividad salarial: Debido a que el ingreso salarial (tanto por hora como mensual) fue un factor decisivo para el modelo, es imperativo ofrecer esquemas de compensación competitivos para asegurar la retención del talento.
-Reconocimiento a la trayectoria: Debido a la alta importancia de la edad y los años empleados para el modelo de Árbol de Decisión, se sugiere establecer programas de incentivos vinculados a la experiencia de los empleados
-Priorización de incentivos económicos: Basado en el puntaje de importancia, los factores financieros y de rol jerárquico superan en relevancia a variables como el balance vida-trabajo para este grupo de empleados
En conclusión, tras la comparativa directa, el Árbol de Decisión se posiciona como el modelo más equilibrado. Este algoritmo logró la mayor estabilidad en la curva ROC sin presentar una disminución significativa en su tasa de aciertos en relación con los demás modelos evaluados.
Sin embargo, estos resultados deben interpretarse con cautela. El modelo alcanzó una sensibilidad del 52.45% para identificar niveles de satisfacción altos, pero su especificidad fue de apenas un 47.63% para los niveles bajos. Esto indica que, si bien el modelo es capaz de detectar a los empleados satisfechos, presenta una alta proporción de falsos positivos al intentar identificar a aquellos con baja satisfacción.
Esta tendencia se repitió de manera consistente en todos los modelos analizados, los cuales mostraron un sesgo de clasificación hacia la clase “High”. Asimismo, se observó que la transformación logarítmica de la variable Monthly_Income no produjo mejoras perceptibles en el rendimiento, lo que sugiere que la capacidad predictiva está limitada por la naturaleza de los datos y no por la escala de las variables. Por lo tanto, aunque el Árbol de Decisión es la opción más robusta entre las evaluadas, su implementación debe complementarse con un monitoreo constante del error de clasificación .
Informe preparado con R Markdown — May 2026