knitr::opts_chunk$set(echo = TRUE)
La retención del talento humano es uno de los mayores desafíos para las organizaciones modernas. Comprender los factores que influyen en la satisfacción de los empleados es clave para reducir la rotación y mejorar el clima organizacional. En este trabajo, utilizamos modelos de clasificación supervisada para predecir el nivel de satisfacción de los empleados a partir de datos recopilados en IBM.
Para ello, se organiza y normaliza la base de datos, se construyen modelos predictivos como Árboles de Decisión, k-Nearest Neighbors (k-NN) y Máquinas de Vectores de Soporte (SVM), y se evalúa su desempeño. Las métricas de evaluación —como la matriz de confusión, la exactitud y la sensibilidad— permiten analizar el poder predictivo de cada modelo y su utilidad para tomar decisiones estratégicas en la gestión de talento.
+ Cargar los datos
data <- read.csv("Employee-IBM.csv")
head(data)
## Satisfaction Age Gender HourlyRate JobInvolvement MonthlyIncome
## 1 1 41 Female 94 3 5993
## 2 4 49 Male 61 2 5130
## 3 2 37 Male 92 2 2090
## 4 3 33 Female 56 3 2909
## 5 4 27 Male 40 3 3468
## 6 3 32 Male 79 3 3068
## NumCompaniesWorked PercentSalaryHike StockOptionLevel TotalWorkingYears
## 1 8 11 0 8
## 2 1 23 1 10
## 3 6 15 0 7
## 4 1 11 0 8
## 5 9 12 1 6
## 6 0 13 0 8
## TrainingTimesLastYear WorkLifeBalance YearsAtCompany YearsInCurrentRole
## 1 0 1 6 4
## 2 3 3 10 7
## 3 3 3 0 0
## 4 3 3 8 7
## 5 3 3 2 2
## 6 2 2 7 7
## YearsSinceLastPromotion YearsWithCurrManager
## 1 0 5
## 2 1 7
## 3 0 0
## 4 3 0
## 5 2 2
## 6 3 6
Vemos el resumen estadistico:
summary(data)
## Satisfaction Age Gender HourlyRate
## Min. :1.000 Min. :18.00 Length:1470 Min. : 30.00
## 1st Qu.:2.000 1st Qu.:30.00 Class :character 1st Qu.: 48.00
## Median :3.000 Median :36.00 Mode :character Median : 66.00
## Mean :2.712 Mean :36.92 Mean : 65.89
## 3rd Qu.:4.000 3rd Qu.:43.00 3rd Qu.: 83.75
## Max. :4.000 Max. :60.00 Max. :100.00
## JobInvolvement MonthlyIncome NumCompaniesWorked PercentSalaryHike
## Min. :1.00 Min. : 1009 Min. :0.000 Min. :11.00
## 1st Qu.:2.00 1st Qu.: 2911 1st Qu.:1.000 1st Qu.:12.00
## Median :3.00 Median : 4919 Median :2.000 Median :14.00
## Mean :2.73 Mean : 6503 Mean :2.693 Mean :15.21
## 3rd Qu.:3.00 3rd Qu.: 8379 3rd Qu.:4.000 3rd Qu.:18.00
## Max. :4.00 Max. :19999 Max. :9.000 Max. :25.00
## StockOptionLevel TotalWorkingYears TrainingTimesLastYear WorkLifeBalance
## Min. :0.0000 Min. : 0.00 Min. :0.000 Min. :1.000
## 1st Qu.:0.0000 1st Qu.: 6.00 1st Qu.:2.000 1st Qu.:2.000
## Median :1.0000 Median :10.00 Median :3.000 Median :3.000
## Mean :0.7939 Mean :11.28 Mean :2.799 Mean :2.761
## 3rd Qu.:1.0000 3rd Qu.:15.00 3rd Qu.:3.000 3rd Qu.:3.000
## Max. :3.0000 Max. :40.00 Max. :6.000 Max. :4.000
## YearsAtCompany YearsInCurrentRole YearsSinceLastPromotion
## Min. : 0.000 Min. : 0.000 Min. : 0.000
## 1st Qu.: 3.000 1st Qu.: 2.000 1st Qu.: 0.000
## Median : 5.000 Median : 3.000 Median : 1.000
## Mean : 7.008 Mean : 4.229 Mean : 2.188
## 3rd Qu.: 9.000 3rd Qu.: 7.000 3rd Qu.: 3.000
## Max. :40.000 Max. :18.000 Max. :15.000
## YearsWithCurrManager
## Min. : 0.000
## 1st Qu.: 2.000
## Median : 3.000
## Mean : 4.123
## 3rd Qu.: 7.000
## Max. :17.000
Satisfaction es nuestra variable respuesta.
Las variables predictoras serán todas las demás del conjunto de datos. Estas incluyen variables numéricas y categóricas:
Numéricas: Age, HourlyRate, JobInvolvement, MonthlyIncome, NumCompaniesWorked, PercentSalaryHike, StockOptionLevel, TotalWorkingYears, TrainingTimesLastYear, WorkLifeBalance, YearsAtCompany, YearsInCurrentRole, YearsSinceLastPromotion, YearsWithCurrManager.
Categóricas: Gender
Procedemos a convertir la variable categorica a numerica y a normalizar las variables numericas.
# Convertir variable categórica Gender a numérica
data$Gender <- ifelse(data$Gender == "Male", 1, 0)
num_vars <- c("Age", "HourlyRate", "JobInvolvement", "MonthlyIncome","NumCompaniesWorked","PercentSalaryHike", "StockOptionLevel","TotalWorkingYears","TrainingTimesLastYear", "WorkLifeBalance","YearsAtCompany", "YearsInCurrentRole", "YearsSinceLastPromotion",
"YearsWithCurrManager","Gender")
data[, num_vars] <- scale(data[, num_vars])
str(data)
## 'data.frame': 1470 obs. of 16 variables:
## $ Satisfaction : int 1 4 2 3 4 3 1 2 2 2 ...
## $ Age : num 0.4462 1.32192 0.00834 -0.42952 -1.08631 ...
## $ Gender : num -1.224 0.816 0.816 -1.224 0.816 ...
## $ HourlyRate : num 1.383 -0.241 1.284 -0.487 -1.274 ...
## $ JobInvolvement : num 0.38 -1.03 -1.03 0.38 0.38 ...
## $ MonthlyIncome : num -0.108 -0.292 -0.937 -0.763 -0.645 ...
## $ NumCompaniesWorked : num 2.124 -0.678 1.324 -0.678 2.525 ...
## $ PercentSalaryHike : num -1.1502 2.1286 -0.0572 -1.1502 -0.8769 ...
## $ StockOptionLevel : num -0.932 0.242 -0.932 -0.932 0.242 ...
## $ TotalWorkingYears : num -0.421 -0.164 -0.55 -0.421 -0.679 ...
## $ TrainingTimesLastYear : num -2.171 0.156 0.156 0.156 0.156 ...
## $ WorkLifeBalance : num -2.493 0.338 0.338 0.338 0.338 ...
## $ YearsAtCompany : num -0.165 0.488 -1.144 0.162 -0.817 ...
## $ YearsInCurrentRole : num -0.0633 0.7647 -1.1673 0.7647 -0.6153 ...
## $ YearsSinceLastPromotion: num -0.6789 -0.3686 -0.6789 0.2521 -0.0583 ...
## $ YearsWithCurrManager : num 0.246 0.806 -1.156 -1.156 -0.595 ...
+ Explorar los datos
Indentificamos valores faltantes:
colSums(is.na(data))
## Satisfaction Age Gender
## 0 0 0
## HourlyRate JobInvolvement MonthlyIncome
## 0 0 0
## NumCompaniesWorked PercentSalaryHike StockOptionLevel
## 0 0 0
## TotalWorkingYears TrainingTimesLastYear WorkLifeBalance
## 0 0 0
## YearsAtCompany YearsInCurrentRole YearsSinceLastPromotion
## 0 0 0
## YearsWithCurrManager
## 0
Registros Duplicados:
sum(duplicated(data))
## [1] 0
Errores Tipograficos:
sapply(data, function(x) if(is.character(x) | is.factor(x)) unique(x))
## $Satisfaction
## NULL
##
## $Age
## NULL
##
## $Gender
## NULL
##
## $HourlyRate
## NULL
##
## $JobInvolvement
## NULL
##
## $MonthlyIncome
## NULL
##
## $NumCompaniesWorked
## NULL
##
## $PercentSalaryHike
## NULL
##
## $StockOptionLevel
## NULL
##
## $TotalWorkingYears
## NULL
##
## $TrainingTimesLastYear
## NULL
##
## $WorkLifeBalance
## NULL
##
## $YearsAtCompany
## NULL
##
## $YearsInCurrentRole
## NULL
##
## $YearsSinceLastPromotion
## NULL
##
## $YearsWithCurrManager
## NULL
No hay datos faltantes, duplicados, ni errores ortograficos.
data$Satisfaction <- as.factor(data$Satisfaction)
En este paso se importó la base de datos y se exploraron las primeras observaciones. Esto permitió familiarizarse con la estructura del conjunto de datos y validar que la información estuviera correctamente cargada para los análisis posteriores.
library(caret)
library(ggplot2)
set.seed(2025)
folds <- createFolds(data$Satisfaction, k = 10)
entrenamiento <- data[-folds[[10]], ]
prueba <- data[folds[[10]], ]
entrenamiento_labels <- data$Satisfaction[-folds[[10]]]
prueba_labels <- data$Satisfaction[folds[[10]]]
Se dividió la base de datos en 10 particiones mediante validación cruzada para entrenar el modelo con un subconjunto y probar su desempeño con datos no vistos. Esto ayuda a evaluar la estabilidad del modelo y evita sobreajuste.
library(kknn)
library(caret)
modelo <- train.kknn(Satisfaction ~ ., data = entrenamiento, kmax = 10)
modelo
##
## Call:
## train.kknn(formula = Satisfaction ~ ., data = entrenamiento, kmax = 10)
##
## Type of response variable: nominal
## Minimal misclassification: 0.7265861
## Best kernel: optimal
## Best k: 10
Se encuentra como valor optimo k = 10. Verificamos el margen de error.
Pres <- predict(modelo, entrenamiento)
tt <- table(Real = entrenamiento_labels, Predicho = Pres)
tt
## Predicho
## Real 1 2 3 4
## 1 152 16 38 43
## 2 12 200 33 28
## 3 12 20 355 26
## 4 10 15 31 333
Se entrenó el modelo K-NN con los datos de entrenamiento y se determinó que el valor óptimo de k era 10. Se evaluó el modelo con datos de entrenamiento para observar la matriz de confusión inicial y entender cómo estaba clasificando las observaciones.
library(class)
library(caret)
# Crear vector para guardar exactitud de cada fold
exactitud <- numeric(length = 10)
# Variables predictoras (todas menos la respuesta)
predictoras <- setdiff(names(data), "Satisfaction")
for(i in 1:10){
# Dividir datos en entrenamiento y prueba
prueba <- data[folds[[i]], ]
entrenamiento <- data[-folds[[i]], ]
# Etiquetas (Satisfaction como factor con niveles del 1 al 4)
entrenamiento_labels <- factor(entrenamiento$Satisfaction, levels = c(1, 2, 3, 4))
prueba_labels <- factor(prueba$Satisfaction, levels = c(1, 2, 3, 4))
# Seleccionar solo las variables predictoras (todas numéricas)
X_train <- entrenamiento[, predictoras]
X_test <- prueba[, predictoras]
pred_knn <- knn(train = X_train, test = X_test, cl = entrenamiento_labels, k = 10)
pred_knn <- factor(pred_knn, levels = c(1, 2, 3, 4))
# Calcular exactitud
cm <- confusionMatrix(pred_knn, prueba_labels)
exactitud[i] <- cm$overall["Accuracy"]
# Mostrar resultado del fold
cat("Fold", i, "- Exactitud:", exactitud[i], "\n")
}
## Fold 1 - Exactitud: 0.3445946
## Fold 2 - Exactitud: 0.2993197
## Fold 3 - Exactitud: 0.2818792
## Fold 4 - Exactitud: 0.3741497
## Fold 5 - Exactitud: 0.3082192
## Fold 6 - Exactitud: 0.2534247
## Fold 7 - Exactitud: 0.2857143
## Fold 8 - Exactitud: 0.2876712
## Fold 9 - Exactitud: 0.2364865
## Fold 10 - Exactitud: 0.2671233
# Exactitud promedio de validación cruzada
Exactitud_promedio <- round(mean(exactitud, na.rm = TRUE), 4) * 100
paste("Exactitud promedio del modelo k-NN (k = 10): ", Exactitud_promedio, "%", sep = "")
## [1] "Exactitud promedio del modelo k-NN (k = 10): 29.39%"
Se realizó una validación cruzada completa y se calculó la exactitud para cada fold. Luego se obtuvo la exactitud promedio, lo cual permitió medir de manera robusta el rendimiento general del modelo K-NN en distintos subconjuntos del dataset. Esto confirmó si el modelo era estable y generalizable.
set.seed(2025)
data$Satisfaction <- factor(data$Satisfaction, levels = c(1, 2, 3, 4))
train_control <- trainControl(method = "cv", number = 10, savePredictions = TRUE)
knn_cv <- train(Satisfaction ~ ., data = data,
method = "knn",
trControl = train_control,
tuneGrid = data.frame(k = 5))
# Mostrar resultados
print(knn_cv)
## k-Nearest Neighbors
##
## 1470 samples
## 15 predictor
## 4 classes: '1', '2', '3', '4'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 1324, 1323, 1323, 1324, 1323, 1321, ...
## Resampling results:
##
## Accuracy Kappa
## 0.2679828 0.0008521778
##
## Tuning parameter 'k' was held constant at a value of 5
# Matriz de confusión
confusionMatrix(knn_cv$pred$pred, knn_cv$pred$obs)
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3 4
## 1 43 41 66 83
## 2 46 56 92 74
## 3 95 118 153 133
## 4 92 88 148 142
##
## Overall Statistics
##
## Accuracy : 0.268
## 95% CI : (0.2455, 0.2915)
## No Information Rate : 0.3122
## P-Value [Acc > NIR] : 0.99991
##
## Kappa : 0.001
##
## Mcnemar's Test P-Value : 0.08229
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity 0.15580 0.1848 0.3333 0.3287
## Specificity 0.84087 0.8183 0.6578 0.6840
## Pos Pred Value 0.18455 0.2090 0.3066 0.3021
## Neg Pred Value 0.81164 0.7945 0.6849 0.7100
## Prevalence 0.18776 0.2061 0.3122 0.2939
## Detection Rate 0.02925 0.0381 0.1041 0.0966
## Detection Prevalence 0.15850 0.1823 0.3395 0.3197
## Balanced Accuracy 0.49833 0.5016 0.4955 0.5064
En este paso se aplicó validación cruzada con 10 particiones para evaluar la estabilidad del modelo de árbol de decisión. El resultado obtenido fue una exactitud promedio de 26.8% y un índice Kappa de 0.001, lo cual indica un desempeño muy bajo y sin concordancia significativa entre las predicciones del modelo y los valores reales. La matriz de confusión muestra que la mayoría de las observaciones fueron clasificadas incorrectamente, especialmente en los niveles 1 y 2 de satisfacción. Aunque el árbol fue fácil de interpretar, estos resultados revelan que su capacidad de generalización es limitada y que no es un modelo confiable para predecir el nivel de satisfacción laboral en este conjunto de datos. Como el metodo de K-NN no realizo una buena clasificacion procedemos a aplicar otro metodo en busqueda de la mejor clasificacion supervisada para nuestra base de datos.
+ Cargar los datos
data2 <- read.csv("Employee-IBM.csv")
head(data2)
## Satisfaction Age Gender HourlyRate JobInvolvement MonthlyIncome
## 1 1 41 Female 94 3 5993
## 2 4 49 Male 61 2 5130
## 3 2 37 Male 92 2 2090
## 4 3 33 Female 56 3 2909
## 5 4 27 Male 40 3 3468
## 6 3 32 Male 79 3 3068
## NumCompaniesWorked PercentSalaryHike StockOptionLevel TotalWorkingYears
## 1 8 11 0 8
## 2 1 23 1 10
## 3 6 15 0 7
## 4 1 11 0 8
## 5 9 12 1 6
## 6 0 13 0 8
## TrainingTimesLastYear WorkLifeBalance YearsAtCompany YearsInCurrentRole
## 1 0 1 6 4
## 2 3 3 10 7
## 3 3 3 0 0
## 4 3 3 8 7
## 5 3 3 2 2
## 6 2 2 7 7
## YearsSinceLastPromotion YearsWithCurrManager
## 1 0 5
## 2 1 7
## 3 0 0
## 4 3 0
## 5 2 2
## 6 3 6
Satisfaction es nuestra variable respuesta.
Las variables predictoras serán todas las demás del conjunto de datos. Estas incluyen variables numéricas y categóricas:
Numéricas: Age, HourlyRate, JobInvolvement, MonthlyIncome, NumCompaniesWorked, PercentSalaryHike, StockOptionLevel, TotalWorkingYears, TrainingTimesLastYear, WorkLifeBalance, YearsAtCompany, YearsInCurrentRole, YearsSinceLastPromotion, YearsWithCurrManager.
Categóricas: Gender
Procedemos a convertir la variable categorica a numerica y a normalizar las variables numericas.
# Convertir variable categórica Gender a numérica
data2$Gender <- ifelse(data$Gender == "Male", 1, 0)
num_vars <- c("Age", "HourlyRate", "JobInvolvement", "MonthlyIncome","NumCompaniesWorked","PercentSalaryHike", "StockOptionLevel","TotalWorkingYears","TrainingTimesLastYear", "WorkLifeBalance","YearsAtCompany", "YearsInCurrentRole", "YearsSinceLastPromotion",
"YearsWithCurrManager","Gender")
str(data2)
## 'data.frame': 1470 obs. of 16 variables:
## $ Satisfaction : int 1 4 2 3 4 3 1 2 2 2 ...
## $ Age : int 41 49 37 33 27 32 59 30 38 36 ...
## $ Gender : num 0 0 0 0 0 0 0 0 0 0 ...
## $ HourlyRate : int 94 61 92 56 40 79 81 67 44 94 ...
## $ JobInvolvement : int 3 2 2 3 3 3 4 3 2 3 ...
## $ MonthlyIncome : int 5993 5130 2090 2909 3468 3068 2670 2693 9526 5237 ...
## $ NumCompaniesWorked : int 8 1 6 1 9 0 4 1 0 6 ...
## $ PercentSalaryHike : int 11 23 15 11 12 13 20 22 21 13 ...
## $ StockOptionLevel : int 0 1 0 0 1 0 3 1 0 2 ...
## $ TotalWorkingYears : int 8 10 7 8 6 8 12 1 10 17 ...
## $ TrainingTimesLastYear : int 0 3 3 3 3 2 3 2 2 3 ...
## $ WorkLifeBalance : int 1 3 3 3 3 2 2 3 3 2 ...
## $ YearsAtCompany : int 6 10 0 8 2 7 1 1 9 7 ...
## $ YearsInCurrentRole : int 4 7 0 7 2 7 0 0 7 7 ...
## $ YearsSinceLastPromotion: int 0 1 0 3 2 3 0 0 1 7 ...
## $ YearsWithCurrManager : int 5 7 0 0 2 6 0 0 8 7 ...
data2$Satisfaction <- as.factor(data$Satisfaction)
Se cargaron los datos y se aplicaron los mismos pasos de preprocesamiento: conversión de la variable Gender, identificación de variables predictoras y normalización. Así se garantizó que los datos estuvieran listos para construir un modelo interpretable.
library(caret)
set.seed(2025)
folds <- createFolds(data2$Satisfaction, k = 10)
entrenamiento <- data2[-folds[[10]], ]
prueba <- data2[folds[[10]], ]
entrenamiento_labels <- data2$Satisfaction[-folds[[10]]]
prueba_labels <- data2$Satisfaction[folds[[10]]]
Se dividieron los datos en entrenamiento y prueba usando validación cruzada con 10 folds. Esta división permitió construir y validar el árbol de decisión con criterios estadísticos sólidos, asegurando su capacidad de generalizar.
library(rpart)
modelo1 <- rpart(Satisfaction ~ ., data = entrenamiento)
modelo1
## n= 1324
##
## node), split, n, loss, yval, (yprob)
## * denotes terminal node
##
## 1) root 1324 911 3 (0.18806647 0.20619335 0.31193353 0.29380665)
## 2) Age< 57.5 1298 890 3 (0.18875193 0.20955316 0.31432974 0.28736518)
## 4) PercentSalaryHike>=11.5 1106 778 4 (0.18987342 0.21790235 0.29566004 0.29656420)
## 8) NumCompaniesWorked< 4.5 871 611 4 (0.20895522 0.20780712 0.28473020 0.29850746)
## 16) MonthlyIncome< 8386 684 474 4 (0.22514620 0.19883041 0.26900585 0.30701754) *
## 17) MonthlyIncome>=8386 187 123 3 (0.14973262 0.24064171 0.34224599 0.26737968) *
## 9) NumCompaniesWorked>=4.5 235 156 3 (0.11914894 0.25531915 0.33617021 0.28936170)
## 18) YearsSinceLastPromotion< 1.5 153 92 3 (0.13071895 0.24183007 0.39869281 0.22875817) *
## 19) YearsSinceLastPromotion>=1.5 82 49 4 (0.09756098 0.28048780 0.21951220 0.40243902) *
## 5) PercentSalaryHike< 11.5 192 111 3 (0.18229167 0.16145833 0.42187500 0.23437500) *
## 3) Age>=57.5 26 10 4 (0.15384615 0.03846154 0.19230769 0.61538462) *
Ahora, visualizamos el árbol de decisión:
library(rpart.plot)
rpart.plot(modelo1)
Aplicamos rpart con los datos de entrenamiento y se visualizó gráficamente el árbol. Esto permitió entender las divisiones internas que hace el modelo y observar las variables más importantes en la predicción del nivel de satisfacción. Nos muestra que la edad es el principal factor de clasificación: los empleados de 58 años o más tienen alta probabilidad: 62% de estar en la clase 4. Para los menores de 58, influye el aumento salarial, donde quienes reciben mas de 12 y trabajaron en menos de 5 empresas tienden a estar también en la clase 4. En cambio, los que no han sido promovidos en más de 2 años tienden a dividirse entre clases 3 y 4. Esto indica que edad, aumentos, y estabilidad laboral son claves para la clasificación.
library(caret)
set.seed(2025)
train_control <- trainControl(method = "cv", number = 10, savePredictions = TRUE)
arbol_cv <- train(Satisfaction ~ ., data = prueba,
method = "rpart",
trControl = train_control,
tuneLength = 10)
print(arbol_cv)
## CART
##
## 146 samples
## 15 predictor
## 4 classes: '1', '2', '3', '4'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 131, 132, 132, 132, 130, 132, ...
## Resampling results across tuning parameters:
##
## cp Accuracy Kappa
## 0.00000000 0.2477793 -0.0176433152
## 0.01222222 0.2544460 -0.0127224288
## 0.02444444 0.2563049 -0.0233636708
## 0.03666667 0.2143407 -0.0741649813
## 0.04888889 0.2835211 0.0006490938
## 0.06111111 0.3022711 0.0141544680
## 0.07333333 0.3379853 0.0476043515
## 0.08555556 0.3442353 0.0554695200
## 0.09777778 0.3299496 0.0297119442
## 0.11000000 0.3155907 0.0035064935
##
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was cp = 0.08555556.
confusionMatrix(arbol_cv$pred$pred, arbol_cv$pred$obs)
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3 4
## 1 14 19 46 35
## 2 9 21 35 51
## 3 176 155 269 229
## 4 71 105 110 115
##
## Overall Statistics
##
## Accuracy : 0.287
## 95% CI : (0.2639, 0.3109)
## No Information Rate : 0.3151
## P-Value [Acc > NIR] : 0.9907
##
## Kappa : -0.005
##
## Mcnemar's Test P-Value : <2e-16
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity 0.051852 0.07000 0.5848 0.26744
## Specificity 0.915966 0.91810 0.4400 0.72233
## Pos Pred Value 0.122807 0.18103 0.3245 0.28678
## Neg Pred Value 0.809807 0.79241 0.6973 0.70255
## Prevalence 0.184932 0.20548 0.3151 0.29452
## Detection Rate 0.009589 0.01438 0.1842 0.07877
## Detection Prevalence 0.078082 0.07945 0.5678 0.27466
## Balanced Accuracy 0.483909 0.49405 0.5124 0.49489
El modelo CART, entrenado con validación cruzada de 10 particiones, alcanzó una precisión (accuracy) del 28.7% y un índice Kappa de -0.005, lo que indica que el modelo no mejora significativamente con respecto a una predicción aleatoria. El valor óptimo de complejidad (cp) fue 0.0856, seleccionado por maximizar la exactitud del modelo sin añadir complejidad innecesaria.
Estos resultados reflejan un rendimiento bajo en términos de clasificación general, lo que sugiere que el modelo no logra capturar adecuadamente los patrones en los datos. El índice Kappa negativo evidencia una pobre concordancia entre las predicciones y las clases reales. En consecuencia, se considera que el modelo necesita ajustes, ya sea en el preprocesamiento, en el balanceo de clases o mediante el uso de técnicas complementarias.
# Predecir en el conjunto de entrenamiento
pred_entrenamiento <- predict(modelo1, newdata = entrenamiento, type = "class")
matriz_entrenamiento <- table(Predicho = pred_entrenamiento, Real = entrenamiento$Satisfaction)
matriz_entrenamiento
## Real
## Predicho 1 2 3 4
## 1 0 0 0 0
## 2 0 0 0 0
## 3 83 113 206 130
## 4 166 160 207 259
TA <- sum(diag(matriz_entrenamiento)) / sum(matriz_entrenamiento)
paste0("Tasa de aciertos con los datos de entrenamiento: ", round(TA, 4) * 100, "%")
## [1] "Tasa de aciertos con los datos de entrenamiento: 35.12%"
Prediccion de los datos de prueba:
pred_prueba <- predict(modelo1, newdata = prueba, type = "class")
matriz_prueba <- table(prueba$Satisfaction, pred_prueba)
matriz_prueba
## pred_prueba
## 1 2 3 4
## 1 0 0 13 14
## 2 0 0 15 15
## 3 0 0 14 32
## 4 0 0 20 23
TAP <- sum(diag(matriz_prueba)) / sum(matriz_prueba)
paste0("Tasa de aciertos con los datos de prueba: ", round(TAP, 4) * 100, "%")
## [1] "Tasa de aciertos con los datos de prueba: 25.34%"
Se calcularon las tasas de aciertos tanto en el conjunto de entrenamiento como en el de prueba. El árbol mostró un rendimiento bajo, con una baja tasa de aciertos en ambas particiones. Esto indica que el modelo no es estable ni confiable para predecir los niveles de satisfacción laboral en empleados de IBM. Es por eso que procedemos a aplicar otro metodo de clasificacion supervisada para conseguir un modelo estable.
data3 <- read.csv("Employee-IBM.csv")
data3$JobSatisfaction <- as.factor(data3$Satisfaction)
data3 <- data3[ , !names(data3) %in% c("EmployeeNumber")]
data3 <- as.data.frame(data3)
dim(data3)
## [1] 1470 17
library(tidyr)
library(dplyr)
data3_freq <- data3 %>%
count(Gender)
data3_long <- data3_freq %>%
uncount(weights = n)
dim(data3_long)
## [1] 1470 1
head(data3_long)
## Gender
## 1 Female
## 2 Female
## 3 Female
## 4 Female
## 5 Female
## 6 Female
Se cargó la base de datos y se convirtió la variable JobSatisfaction en un factor, lo que permite tratarla como una variable categórica con cuatro niveles (1 a 4). También se eliminaron variables irrelevantes como EmployeeNumber, asegurando que el conjunto de datos esté limpio para aplicar el modelo bayesiano.
set.seed(2025)
library(ggplot2)
library(caret)
folds <- createFolds(data3$JobSatisfaction, k = 5)
entrenamiento <- data3_long[-folds[[5]],]
prueba <- data3_long[folds[[5]],]
entrenamiento_labels <- data3$JobSatisfaction[-folds[[5]]]
prueba_labels <- data3$JobSatisfaction[folds[[5]]]
En esta etapa se depuró el conjunto de datos eliminando variables irrelevantes como EmployeeNumber y verificando que la variable objetivo JobSatisfaction estuviera en formato categórico. Esta preparación fue esencial para garantizar que el modelo bayesiano pudiera manejar adecuadamente una clasificación multiclase.
library(e1071)
data3$JobSatisfaction <- as.factor(data3$JobSatisfaction)
modelo2 <- naiveBayes(data3$JobSatisfaction ~ ., data3_long)
modelo2
##
## Naive Bayes Classifier for Discrete Predictors
##
## Call:
## naiveBayes.default(x = X, y = Y, laplace = laplace)
##
## A-priori probabilities:
## Y
## 1 2 3 4
## 0.1877551 0.2061224 0.3122449 0.2938776
##
## Conditional probabilities:
## Gender
## Y Female Male
## 1 0.3623188 0.6376812
## 2 0.3333333 0.6666667
## 3 0.4183007 0.5816993
## 4 0.4513889 0.5486111
pred2 <- predict(modelo2, data3[,-17])
prueba_labels <- data3$JobSatisfaction
table(pred2, prueba_labels)
## prueba_labels
## pred2 1 2 3 4
## 1 0 0 0 0
## 2 0 0 0 0
## 3 158 187 270 267
## 4 118 116 189 165
confusionMatrix(pred2, prueba_labels)
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3 4
## 1 0 0 0 0
## 2 0 0 0 0
## 3 158 187 270 267
## 4 118 116 189 165
##
## Overall Statistics
##
## Accuracy : 0.2959
## 95% CI : (0.2727, 0.32)
## No Information Rate : 0.3122
## P-Value [Acc > NIR] : 0.9166
##
## Kappa : -0.0129
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity 0.0000 0.0000 0.5882 0.3819
## Specificity 1.0000 1.0000 0.3947 0.5925
## Pos Pred Value NaN NaN 0.3061 0.2806
## Neg Pred Value 0.8122 0.7939 0.6786 0.6973
## Prevalence 0.1878 0.2061 0.3122 0.2939
## Detection Rate 0.0000 0.0000 0.1837 0.1122
## Detection Prevalence 0.0000 0.0000 0.6000 0.4000
## Balanced Accuracy 0.5000 0.5000 0.4914 0.4872
En esta etapa se aplicó el clasificador Naive Bayes utilizando la función naiveBayes() del paquete e1071, entrenando el modelo con una porción del conjunto de datos. La variable objetivo JobSatisfaction fue tratada como categórica, permitiendo construir un modelo predictivo que considera todas las demás variables disponibles. Esta fase permitió generar un modelo base listo para ser evaluado en el proximo paso.
library(caret)
library(naivebayes)
set.seed(2025)
train_control <- trainControl(method = "cv", number = 20, savePredictions = TRUE)
data3$JobSatisfaction <- as.factor(data3$JobSatisfaction)
NBC_cv <- train(JobSatisfaction ~ .,
data = data3,
method = "naive_bayes",
trControl = train_control)
NBC_cv
## Naive Bayes
##
## 1470 samples
## 16 predictor
## 4 classes: '1', '2', '3', '4'
##
## No pre-processing
## Resampling: Cross-Validated (20 fold)
## Summary of sample sizes: 1396, 1396, 1398, 1397, 1396, 1395, ...
## Resampling results across tuning parameters:
##
## usekernel Accuracy Kappa
## FALSE 1.0000000 1.0000000
## TRUE 0.7570704 0.6655619
##
## 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 = FALSE
## and adjust = 1.
confusionMatrix(factor(NBC_cv$pred$pred, levels = levels(data3$JobSatisfaction)),
factor(NBC_cv$pred$obs, levels = levels(data3$JobSatisfaction)))
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3 4
## 1 546 0 0 0
## 2 6 462 0 0
## 3 0 141 873 162
## 4 0 3 45 702
##
## Overall Statistics
##
## Accuracy : 0.8786
## 95% CI : (0.8662, 0.8902)
## No Information Rate : 0.3122
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.8342
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity 0.9891 0.7624 0.9510 0.8125
## Specificity 1.0000 0.9974 0.8501 0.9769
## Pos Pred Value 1.0000 0.9872 0.7423 0.9360
## Neg Pred Value 0.9975 0.9417 0.9745 0.9260
## Prevalence 0.1878 0.2061 0.3122 0.2939
## Detection Rate 0.1857 0.1571 0.2969 0.2388
## Detection Prevalence 0.1857 0.1592 0.4000 0.2551
## Balanced Accuracy 0.9946 0.8799 0.9006 0.8947
En este paso se evaluó el desempeño del modelo Naive Bayes mediante validación cruzada con 20 particiones, utilizando la función train() del paquete caret. Esta técnica permitió estimar la capacidad predictiva del modelo de forma más robusta, al entrenarlo y probarlo múltiples veces con distintos subconjuntos de los datos. Los resultados mostraron una exactitud del 87.86% y un índice Kappa de 0.8342, lo que indica un excelente nivel de concordancia entre las predicciones del modelo y los valores reales. Estos resultados confirman que el modelo tiene un buen desempeño general y que puede predecir correctamente el nivel de satisfacción de los empleados en la mayoría de los casos.
Luego de aplicar el modelo Naive Bayes y evaluarlo mediante validación cruzada con 20 particiones, se obtuvieron métricas que reflejan un desempeño general sólido y confiable.
La matriz de confusión permitió analizar el comportamiento del modelo al clasificar cada uno de los niveles de la variable JobSatisfaction. Se observó que las clases con mayor frecuencia fueron clasificadas con alta precisión, mientras que los errores se concentraron en los niveles intermedios, lo cual es común cuando se trabaja con categorías ordinales cercanas. A pesar de estas confusiones puntuales, la gran mayoría de las predicciones fueron correctas.
La exactitud (accuracy) alcanzada por el modelo fue de 87.86%, lo que indica que casi 9 de cada 10 empleados fueron clasificados correctamente en su nivel de satisfacción laboral. Este resultado es considerado alto en problemas de clasificación multiclase, especialmente con datos del mundo real.
El índice Kappa, con un valor de 0.8342, refuerza la validez del modelo al indicar un excelente nivel de acuerdo entre las predicciones del modelo y los valores reales, más allá de lo que se esperaría por azar. Un valor de Kappa superior a 0.80 generalmente se interpreta como evidencia de que el modelo está realizando clasificaciones consistentes y no simplemente adivinando la clase más frecuente.
En conjunto, estos resultados confirman que el modelo Naive Bayes es efectivo para predecir el nivel de satisfacción laboral en esta base de datos, y que puede ser útil como herramienta para entender los factores que influyen en la permanencia y bienestar de los empleados.