Este documento busca comprender y prever los factores que influyen en la rotación de empleados entre distintos cargos. Esto se hará mediante los datos históricos sobre el empleo de sus trabajadores, incluyendo variables como la antigüedad en el cargo actual, el nivel de satisfacción laboral, el salario actual, edad y otros factores relevantes.

Desde la gerencia se planea desarrollar un modelo de regresión logística que permita estimar la probabilidad de que un empleado cambie de cargo en el próximo período y determinar cuales factores indicen en mayor proporción a estos cambios.

Con esta información, la empresa podrá tomar medidas proactivas para retener a su talento clave, identificar áreas de mejora en la gestión de recursos humanos y fomentar un ambiente laboral más estable y tranquilo.

Literatura

Según Marroquín y Cruz (2020), la rotación de personal en las empresas colombianas está influenciada por diversos factores, como la insatisfacción personal (35.3%), selección incorrecta (35.3%), baja remuneración (35.3%), condiciones laborales (41.2%), motivación (41.2%), bajas por motivos personales (47.1%), entre otros.

Cadena, N., Posada, N., Arana, M., & Torres, S. (2022) trataron el tema de las renuncias voluntarias en empresas de produccion y encontraron que los empleados tienden a buscar mejores oportunidades, tambien la existencia de bajas remuneraciones, calidad de vida y el horario laboral son las causas y consecuencias de estas empresas en la ciudad de Bogotá.

Tambien Poveda Madrid, E. C., Díaz Álvarez, M. I., & Puentes Vargas, M. P. (2025) definieron un grupo etario (23 - 30 años) e identificaron los problemas que provocan la rotacion a este grupo, como los malos procesos de reclutamiento, condiciones laborales (horarios, ubicacion fisica, tipo de contrato, instalaciones), ambiente laboral, reconocimentos, entre otros son los mayores causantes de la desercion laboral

Y para finalizar Herrity, J. (2025) del portal Indeed, nos indica que 9 causas de la rotacion de personal, como overwork (trabajo excesivo), estilo de gestion inconsistente, falta de reconocimiento, pocas oportunidades de crecimiento, bajos salarios y bajos aumentos, entre otros.

Variables del modelo e hipótesis

Teniendo en cuenta la informacion recogida de la literatura, decidimos trabajar el modelo bajo las siguentes variables:

Dependiente

Rotación: Variable dicotomica que indica si hay o no rotacion de personal

  • SI <- 1
  • NO <- 0

Categoricas

Satisfación labora: Medida que indica cuan satisfecho esta el empleado en la empresa

  • 1 <- Muy baja
  • 2 <- Baja
  • 3 <- Alta
  • 4 <- Muy alta

Hipótesis: Se espera un signo positivo, que a mayor satisfacion, menor es la rotación de los empleados.

Equilibrio trabajo - vida: Nos indica el nivel de satisfacion de los empleados entre su vida personal y la laboral.

  • 1 <- Muy baja
  • 2 <- Baja
  • 3 <- Alta
  • 4 <- Muy alta

Hipótesis: Se espera un signo positivo, a mayor equilibrio entre trabajo y vida, menor será la rotación de los empleados.

Horas_Extra: Cantidad de horas hechas despues de la jornada laboral.

  • SI <- 1
  • NO <- 0

Hipótesis: Se espera un signo negativo, si la persona realiza horas extras aumentará la rotación.

Numéricas

Ingreso_Mensual: Salario percibido por el empleado.

Hipótesis: Se espera un signo negativo, a mayor ingreso menor rotación.

Distancia a casa: Kilometros que hay desde la casa del empleado hasta el sitio de trabajo.

Hipótesis: Se espera un signo positivo, entre mayor sea la distancia entre la casa y el trabajo, aumentará la rotación.

Edad: Edad del empleado.

Hipótesis: Se espera un signo negativo, ente mayor edad menor sera la rotacion (la poblacion joven presentará mayor rotación.)

Análisis de las variables

df1 <- rotacion[, c("Rotación", 
                     "Satisfación_Laboral", 
                     "Equilibrio_Trabajo_Vida", 
                     "Horas_Extra", 
                     "Ingreso_Mensual", 
                     "Distancia_Casa", 
                      "Edad")]

A continuacion realizaremos un analisis univariado y bivariado de las variables que hemos seleccionado.

Analisis univariado

Variables categoricas

Rotación

plot_ly(df1, labels = ~Rotación, type = "pie",
        marker = list(colors = c("purple", "gold")),
        textinfo = "label+percent",
        insidetextorientation = "radial") %>%
  layout(title = "Distribución de la Rotación")

Este grafico nos indica que la empresa cuenta relativamente con una alta tasa de retencion de personal (83.9%), sin embargo existe un grupo de empleados (16.1%) que rota, lo que puede significar un problema asociados a los costos de reclutacion, entrenamiento y la perdida de la memoria y conocimiento institucional.

Satisfacción laboral

plot_ly(df1,
        labels = ~Satisfación_Laboral,
        type = "pie",
        marker = list(colors = c("purple", "green", "gold", "blue")),
        textinfo = "label+percent",
        title = "Distribución de Satisfacción Laboral")

Este grafico nos indica los 4 niveles de la satisfacion laboral:

  • 1 <- Muy baja: representa el 19.7% de los empleados que no se sienten para nada comodos en su trabajo, presentando un riesgo alto de rotación.
  • 2 <- Baja: representa el 19% de los empleados que se sienten iinsastifescho, pero exixste la posbilidad de mejora, presentando un riesgo moderado de rotar.
  • 3 (Alta) y 4 (Muy alta) presenta la mayor concentracion con 61.3% de los empleados, que son el grupo no presenta rotación en la emrpresa.

Equilibrio entre trabajo y vida

# Equilibrio trabajo - vida
plot_ly(df1, labels = ~Equilibrio_Trabajo_Vida, type = "pie",
        marker = list(colors = c("purple", "green", "gold", "blue")),
        textinfo = "label+percent") %>%
  layout(title = "Distribución del Equilibrio trabajo - vida")

Podemos observar que el 71,1% de los empleados perciben un buen equilibrio entre el trabajo y la vida personal, que contribuye a una alta permanencia de los empleados. Por otro lado, el 28.8% de los empleados perciben un regular o mal balance entre la vida personal y el trabajo, este grupo tiende a tener mas estres y desmotivación lo que los hace mas propensos a rotar.

Horas extras

plot_ly(df1, labels = ~Horas_Extra, type = "pie",
        marker = list(colors = c("purple", "gold")),
        textinfo = "label+percent") %>%
  layout(title = "Distribución de las Horas Extras")

Obsrvamos que el 71.7% de los empleados no trabaja horas extras, lo que se relaciona cn el equilibrio entre la vida personal y el trabajo, esto evita el sobrecarga general, estres y la rotacion. Mintras que un parte de los empleados 28.3, trabaja horas extras que puede ser por la alta demanda de trabajo o la falta de personal lo que hace que algunos trabajadores se les aumenta la carga y no tenga una equilibrio trabajo y vida personal lo que genera un incremento de la rotación.

Variables numericas

Ingreso laboral

summary(df1$Ingreso_Mensual)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    1009    2911    4919    6503    8379   19999
plot_ly(df1, x = ~Ingreso_Mensual,
        type = "histogram", nbinsx = 30,
        marker = list(color = 'purple')) %>%
  layout(title = "Distribución del Ingreso Mensual",
         xaxis = list(title = "Ingreso Mensual"),
         yaxis = list(title = "Frecuencia"))

Podemos observar que hay una concentacion entre salarios medio - bajos, donde el 50% de los empleados gana menos de 4.919, pero el promedio de ingresos de la empresa es de 6.503, nos indica la presencia de salarios bastante altos.

Distancia a casa

summary(df1$Distancia_Casa)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   1.000   2.000   7.000   9.193  14.000  29.000
# Distancia a casa
plot_ly(df1, x = ~Distancia_Casa,
        type = "histogram", nbinsx = 20,
        marker = list(color = 'gold')) %>%
  layout(title = "Distribución de la Antigüedad",
         xaxis = list(title = "Distancia a casa (km"),
         yaxis = list(title = "Frecuencia"))

Aqui podemos identificar que la mayoria de empleados vivien relativamente cerca del lugar del trabajo (1 a 5 km), este grupo de empleados tiende a tener una menor probabilidad de rotar, mientras que el extremo contrario distancias mayores a 15 km tiende a generar mayor rotacion debido a los costos y tiempos de transporte que afectan el equilibrio entre trabajo y vida personal.

Edad

summary(df1$Edad)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   18.00   30.00   36.00   36.92   43.00   60.00
plot_ly(df1, x = ~Edad,
        type = "histogram", nbinsx = 20,
        marker = list(color = 'green')) %>%
  layout(title = "Distribución de la Edad",
         xaxis = list(title = "Edad"),
         yaxis = list(title = "Frecuencia"))

Podemos presenciar que hay una fuerza joven adulta, donde gran parte de sus empleados tienen entre 30 y 40 años y para este grupo etario existe una menor rotacion a diferencia de las personas mas jovenes 18 a 28 y de 50 a 64 que tienden a tener una rotacion mucho mas alta.

Análisis Bivariado

Variables categoricas

Ahora realizaremos una mira de las variables explicativas, contra la variable a explicar

Rotacion vs satisfaccion laboral

### 1. Satisfacción Laboral vs Rotación
df_prop1 <- df1 %>%
  count(Satisfación_Laboral, y) %>%
  group_by(Satisfación_Laboral) %>%
  mutate(prop = n / sum(n),
         label = scales::percent(prop, accuracy = 1))

g1 <- ggplot(df_prop1, aes(x = factor(Satisfación_Laboral), 
                           y = prop, fill = factor(y))) +
  geom_bar(stat = "identity", position = "fill") +
  geom_text(aes(label = label), 
            position = position_fill(vjust = 0.5), 
            color = "white", size = 4) +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Rotación según Satisfacción Laboral",
       x = "Nivel de Satisfacción",
       y = "Proporción",
       fill = "Rotación (1=Sí, 0=No)")
g1

El análisis muestra que la satisfacción laboral tiene una relación directa con la permanencia de los empleados en la organización. A medida que aumenta el nivel de satisfacción, la proporción de rotación disminuye. Observamos que en los niveles más bajos de satisfacción (1 y 2), se presenta una rotación del 23% y 16% respectivamente, en los niveles más altos de satisfacción (3 y 4) se reducen los valores de rotación al 17% y 11% respectivamente. Esto muestra la evidencia que los empleados más satisfechos tienen menor probabilidad de abandonar la empresa.

Rotacion vs equilibrio entre trabajo y vida

df_prop2 <- df1 %>%
  count(Equilibrio_Trabajo_Vida, y) %>%
  group_by(Equilibrio_Trabajo_Vida) %>%
  mutate(prop = n / sum(n),
         label = scales::percent(prop, accuracy = 1))

g2 <- ggplot(df_prop2, aes(x = factor(Equilibrio_Trabajo_Vida), 
                           y = prop, fill = factor(y))) +
  geom_bar(stat = "identity", position = "fill") +
  geom_text(aes(label = label), 
            position = position_fill(vjust = 0.5), 
            color = "white", size = 4) +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Rotación según Equilibrio entre Trabajo y Vida",
       x = "Nivel de Equilibrio",
       y = "Proporción",
       fill = "Rotación (1=Sí, 0=No)")
g2

Este gráfico muestra la relación entre el equilibrio entre trabajo y vida personal y la rotación de empleados. Se observa que cuando los trabajadores perciben un bajo equilibrio (nivel 1), la rotación es más alta (31%), lo que sustenta que entre mas complejo sea para el empleado balancear su vida, mas alta es la probabilidad de que los empleados abandonen la empresa. Por otro lado, a medida que mejora el equilibrio (niveles 2, 3 y 4), la rotación disminuye a un rango entre el 18%, 17% y 14% respectivamente.

Rotación vs Horas extras

### 3. Horas Extra vs Rotación
df_prop3 <- df1 %>%
  count(Horas_Extra, y) %>%
  group_by(Horas_Extra) %>%
  mutate(prop = n / sum(n),
         label = scales::percent(prop, accuracy = 1))

g3 <- ggplot(df_prop3, aes(x = factor(Horas_Extra), 
                           y = prop, fill = factor(y))) +
  geom_bar(stat = "identity", position = "fill") +
  geom_text(aes(label = label), 
            position = position_fill(vjust = 0.5), 
            color = "white", size = 4) +
  scale_y_continuous(labels = scales::percent) +
  labs(title = "Rotación según Horas Extra",
       x = "Horas Extra",
       y = "Proporción",
       fill = "Rotación (1=Sí, 0=No)")
g3

Este gráfico muestra cómo las horas extra encuentran una correspondencia con la rotación, los empleados que realizan horas extras el 31% abandona la empresa, mientras que el grupo que no hacen horas extra, solo un 10% rota. Esto nos hace deducir que el trabajo extra genera desgaste, afectando el equilibrio, por enden aumentando la probabilidad de rotación.

Variables numericas

Rotación vs ingreso laboral

ggplot(df1, aes(x = Rango_Ingreso, fill = factor(y))) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = percent) +
  labs(title = "Rotación según Ingreso Mensual",
       x = "Rango de Ingreso (intervalos en pesos)",
       y = "Proporción",
       fill = "Rotación (1=Sí, 0=No)") +
  geom_text(stat = "count", 
            aes(label = scales::percent(..count../tapply(..count..,..x..,sum)[..x..])),
            position = position_fill(vjust = 0.8))

A medida que el ingreso mensual aumenta, la rotación de empleados disminuye de forma clara y progresiva. Esto sugiere que el salario es un factor determinante de la permanencia, y que los empleados con menores ingresos son los más propensos a dejar la organización.

Rotación vs distancia a casa

ggplot(df1, aes(x = Rango_Distancia, fill = factor(y))) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = percent) +
  labs(title = "Rotación según Distancia a Casa",
       x = "Intervalos de Distancia",
       y = "Proporción",
       fill = "Rotación (1=Sí, 0=No)") +
  geom_text(stat = "count",
            aes(label = scales::percent(..count../tapply(..count..,..x..,sum)[..x..])),
            position = position_fill(vjust = 0.8))

Existe una relación positiva entre la distancia a casa y la rotación laboral, los empleados que viven más lejos de la empresa presentan una mayor probabilidad de renunciar. Esto sugiere que la ubicación geográfica y el tiempo de desplazamiento podrían ser factores importantes a considerar en las estrategias de retención.

Rotación vs edad

ggplot(df1, aes(x = Rango_Edad, fill = factor(y))) +
  geom_bar(position = "fill") +
  scale_y_continuous(labels = percent) +
  labs(title = "Rotación según Edad",
       x = "Intervalos de Edad",
       y = "Proporción",
       fill = "Rotación (1=Sí, 0=No)") +
  geom_text(stat = "count",
            aes(label = scales::percent(..count../tapply(..count..,..x..,sum)[..x..])),
            position = position_fill(vjust = 0.8))

La rotación está fuertemente concentrada en los empleados más jóvenes, quienes son más propensos a dejar la empresa. En cambio, los trabajadores de mediana edad (39-50 años) muestran la mayor estabilidad. Esto sugiere que las estrategias de retención deberían enfocarse especialmente en los empleados jóvenes.

Correlaciones

Variables numericas

##                 Ingreso_Mensual Distancia_Casa         Edad
## Ingreso_Mensual      1.00000000   -0.017014445  0.497753202
## Distancia_Casa      -0.01701444    1.000000000 -0.001559109
## Edad                 0.49775320   -0.001559109  1.000000000
corrplot(cor_matrix, method = "color", type = "upper",
         tl.col = "black", tl.srt = 45, addCoef.col = "black")

Aqui podemos notar que la unica asociasion relevante es la edad y el ingreso, y es que los empleados mas viejos tienden a tener mayores salarios debido a su experiencia.

Variables categoricas

# Correlacion variables categoricas

# Chi-cuadrado entre Satisfacción y Equilibrio
chisq.test(table(df1$Satisfación_Laboral, df1$Equilibrio_Trabajo_Vida))
## 
##  Pearson's Chi-squared test
## 
## data:  table(df1$Satisfación_Laboral, df1$Equilibrio_Trabajo_Vida)
## X-squared = 6.5765, df = 9, p-value = 0.6811
# Chi-cuadrado entre Satisfacción y Horas Extra
chisq.test(table(df1$Satisfación_Laboral, df1$Horas_Extra))
## 
##  Pearson's Chi-squared test
## 
## data:  table(df1$Satisfación_Laboral, df1$Horas_Extra)
## X-squared = 3.6881, df = 3, p-value = 0.2972
# Chi-cuadrado entre Equilibrio y Horas Extra
chisq.test(table(df1$Equilibrio_Trabajo_Vida, df1$Horas_Extra))
## 
##  Pearson's Chi-squared test
## 
## data:  table(df1$Equilibrio_Trabajo_Vida, df1$Horas_Extra)
## X-squared = 2.3835, df = 3, p-value = 0.4967

Podemos observar que ninguna de las variables categoricas presenta relacionan entre si, ya que todos sus coeficientes p, son mayores al nivel de 0.05 (5%).

Modelo

Para responder la pregunta de cuales son los factores que mas influyen en la rotacion, se realiza un modelo logit (logistico) para determinar si las hipotesis son correctas y el modelo puedo predecir la rotación.

Modelo 1 - particion y undersampling parcial

El primer modelo se realizara teniendo en cuenta la particion del dataset en training (70) y test (30) y realizando undersampling parcial, para no perder demasiada informacion y evitar que el modelo solo aprenda de la clase mayoritaria

table(train$Rotación)
## 
##   0   1 
## 864 166

Aqui vemos que sin haber aplicado la técnica, la clase mayoritaria “no rotan” (0) tiene 864 datos.

table(train_down_partial$Rotación)
## 
##   0   1 
## 300 166

Despues de haber ejecutado el undersampling vemos que ahora sigeu existiendo la clase mayoritaria, pero no tiene ese gran desbalance.

modelo <- glm(Rotación ~ Satisfación_Laboral + Equilibrio_Trabajo_Vida + Horas_Extra +
                Ingreso_Mensual + Distancia_Casa + Edad,
              data = train_down_partial, family = binomial)

summary(modelo)
## 
## Call:
## glm(formula = Rotación ~ Satisfación_Laboral + Equilibrio_Trabajo_Vida + 
##     Horas_Extra + Ingreso_Mensual + Distancia_Casa + Edad, family = binomial, 
##     data = train_down_partial)
## 
## Coefficients:
##                            Estimate Std. Error z value Pr(>|z|)    
## (Intercept)               1.637e+00  6.560e-01   2.495 0.012594 *  
## Satisfación_Laboral2     -7.181e-01  3.281e-01  -2.188 0.028650 *  
## Satisfación_Laboral3     -4.034e-01  2.910e-01  -1.386 0.165635    
## Satisfación_Laboral4     -1.031e+00  3.015e-01  -3.420 0.000627 ***
## Equilibrio_Trabajo_Vida2 -2.117e-01  4.665e-01  -0.454 0.650038    
## Equilibrio_Trabajo_Vida3 -7.955e-01  4.355e-01  -1.827 0.067747 .  
## Equilibrio_Trabajo_Vida4 -1.880e-01  5.229e-01  -0.360 0.719152    
## Horas_Extra               1.221e+00  2.221e-01   5.496 3.88e-08 ***
## Ingreso_Mensual          -1.016e-04  3.169e-05  -3.206 0.001347 ** 
## Distancia_Casa            2.843e-02  1.286e-02   2.211 0.027060 *  
## Edad                     -3.643e-02  1.366e-02  -2.667 0.007658 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 606.93  on 465  degrees of freedom
## Residual deviance: 516.91  on 455  degrees of freedom
## AIC: 538.91
## 
## Number of Fisher Scoring iterations: 4

Metricas de evaluacion

VIF

El factor de inflación de varianza, nos indica en el modelo logit realizado, si existe multicolinealidad en el modelo, esto se da cuando los valores de VIF estan por encima de 10, y hacemos referencia que cuando esta entre 5 y 10 el modelo presenta una multicolinealidad moderada y por debajo, significa que las variables del modelo no presentan relaciones entre si.

vif(modelo)
##                             GVIF Df GVIF^(1/(2*Df))
## Satisfación_Laboral     1.054592  3        1.008898
## Equilibrio_Trabajo_Vida 1.058099  3        1.009457
## Horas_Extra             1.037625  1        1.018639
## Ingreso_Mensual         1.272611  1        1.128101
## Distancia_Casa          1.026734  1        1.013279
## Edad                    1.300465  1        1.140379

Esto indica que no existe multicolinealidad significativa en el modelo.

Matriz de confusion

Una vez tenemos claro que el modelo esta especificado de manera correcta, con el dataset de test, realizamos las predicciones y calculamos la matriz de confusion.

# Predecir probabilidades (por defecto glm devuelve log-odds, usamos type="response")
pred_probs <- predict(modelo, newdata = test, type = "response")

pred_class <- ifelse(pred_probs >= 0.5, 1, 0)

confusionMatrix(as.factor(pred_class), as.factor(test$Rotación), positive = "1")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0 312  31
##          1  57  40
##                                           
##                Accuracy : 0.8             
##                  95% CI : (0.7595, 0.8364)
##     No Information Rate : 0.8386          
##     P-Value [Acc > NIR] : 0.986583        
##                                           
##                   Kappa : 0.3562          
##                                           
##  Mcnemar's Test P-Value : 0.007699        
##                                           
##             Sensitivity : 0.56338         
##             Specificity : 0.84553         
##          Pos Pred Value : 0.41237         
##          Neg Pred Value : 0.90962         
##              Prevalence : 0.16136         
##          Detection Rate : 0.09091         
##    Detection Prevalence : 0.22045         
##       Balanced Accuracy : 0.70445         
##                                           
##        'Positive' Class : 1               
## 

La matriz de confusion nos indica si los datos fueron correctamente clasificados o no, a continuacion describiremos lo que nos muestra la matriz.

  • 312 casos estan clasificados correctamente en la clase 0 (no rotan)

  • 57 casos son falsos positivos, osea, clase 0 (no rotan) fueron clasificados como 1 (si rotan)

  • 31 casos son falsos negativos, osea, clase 1 (si rotan) fueron clasificados como 0 (no rotan)

  • 40 casos estan clasificados de manera correcta en la clase 1 (si rotan)

Métricas clave

Accuracy = 0.80 (80%)

El modelo acierta 8 de cada 10 casos.

Kappa = 0.3562

Esto indica que el modelo es justo y es mejor con respecto a adivinar

Sensibilidad (Recall) = 0.563

El modelo es capaz de detectar el 56% de los positivos (clase 1, si rotan).

Especificidad = 0.846

Detecta bien a la clase 0 (negativos).

El modelo es capaz de detectar el 84% de los negativos (clase 0, no rotan).

Precisión (PPV) = 0.412

Esto nos indica que solo el 41% de los casos que el modelo predice como verdaderos, son realmente verdaderos.

Balanced Accuracy = 0.704

Promedia sensibilidad y especificidad → da una medida más justa en contextos desbalanceados.

Curva de ROC

roc_obj <- roc(test$Rotación, pred_probs)
auc(roc_obj)  # Área bajo la curva
## Area under the curve: 0.787

Con una area bajo la curva de 0.787 el modelo tiene un buen poder de discriminacion.

plot(roc_obj, col = "blue", lwd = 2, main = "Curva ROC - Logistic Regression")

Aqui podemos observar la grafica de la curva de ROC, con un corte predeterminado en 0.5

F1 score

Es una medida que permite identificar correctamente los casos positivos (empleados qu eno rotan), entre mas cercano a 1, el modelo tiene una muy buena precision.

F1_Score(y_true = as.factor(test$Rotación),
         y_pred = as.factor(pred_class),
         positive = "1")
## [1] 0.4761905

Curva de precision

# Calcular la curva Precision-Recall
pr <- pr.curve(scores.class0 = pred_probs[test$Rotación == 1],
               scores.class1 = pred_probs[test$Rotación == 0],
               curve = TRUE)

# Mostrar el valor del AUC bajo la curva PR
print(paste("AUC (PR curve):", round(pr$auc.integral, 4)))
## [1] "AUC (PR curve): 0.4354"
# Graficar la curva PR
plot(pr, main = "Curva Precision-Recall", col = "darkred", lwd = 2)

Modelo 2 - Dataset completo

En este modelo, no se realiza ninguna particion a los datos y se realiza con el dataset completo, lo que no nos permitiria realizar validacion cruzada, para evaluar el modelo, pero si las otras metricas.

df3 <- rotacion[, c("Rotación", 
                    "Satisfación_Laboral", 
                    "Equilibrio_Trabajo_Vida", 
                    "Horas_Extra", 
                    "Ingreso_Mensual", 
                    "Distancia_Casa", 
                    "Edad")]

# Variable dependiente
df3$Rotación <- ifelse(df1$Rotación == "Si", 1, 0)

# Variables predictoras
df3$Satisfación_Laboral <- as.factor(df3$Satisfación_Laboral)
df3$Equilibrio_Trabajo_Vida <- as.factor(df3$Equilibrio_Trabajo_Vida)
df3$Horas_Extra <- ifelse(df3$Horas_Extra == "Si", 1, 0)
modelo1 <- glm(Rotación ~ Satisfación_Laboral + Equilibrio_Trabajo_Vida + Horas_Extra +
                Ingreso_Mensual + Distancia_Casa + Edad,
              data = df3, family = binomial)

summary(modelo1)
## 
## Call:
## glm(formula = Rotación ~ Satisfación_Laboral + Equilibrio_Trabajo_Vida + 
##     Horas_Extra + Ingreso_Mensual + Distancia_Casa + Edad, family = binomial, 
##     data = df3)
## 
## Coefficients:
##                            Estimate Std. Error z value Pr(>|z|)    
## (Intercept)               9.673e-01  4.500e-01   2.149  0.03161 *  
## Satisfación_Laboral2     -4.429e-01  2.310e-01  -1.917  0.05520 .  
## Satisfación_Laboral3     -4.578e-01  2.044e-01  -2.240  0.02507 *  
## Satisfación_Laboral4     -1.060e+00  2.204e-01  -4.810 1.51e-06 ***
## Equilibrio_Trabajo_Vida2 -9.075e-01  3.051e-01  -2.974  0.00294 ** 
## Equilibrio_Trabajo_Vida3 -1.161e+00  2.829e-01  -4.105 4.05e-05 ***
## Equilibrio_Trabajo_Vida4 -7.508e-01  3.497e-01  -2.147  0.03179 *  
## Horas_Extra               1.504e+00  1.570e-01   9.577  < 2e-16 ***
## Ingreso_Mensual          -9.793e-05  2.511e-05  -3.900 9.63e-05 ***
## Distancia_Casa            2.787e-02  9.200e-03   3.030  0.00245 ** 
## Edad                     -4.001e-02  1.016e-02  -3.939 8.18e-05 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 1298.6  on 1469  degrees of freedom
## Residual deviance: 1105.4  on 1459  degrees of freedom
## AIC: 1127.4
## 
## Number of Fisher Scoring iterations: 5

Metricas de evaluacion

VIF

El factor de inflación de varianza, nos indica en el modelo logit realizado, si existe multicolinealidad en el modelo, esto se da cuando los valores de VIF estan por encima de 10, y hacemos referencia que cuando esta entre 5 y 10 el modelo presenta una multicolinealidad moderada y por debajo, significa que las variables del modelo no presentan relaciones entre si.

vif(modelo1)
##                             GVIF Df GVIF^(1/(2*Df))
## Satisfación_Laboral     1.032089  3        1.005278
## Equilibrio_Trabajo_Vida 1.026824  3        1.004422
## Horas_Extra             1.042001  1        1.020785
## Ingreso_Mensual         1.233572  1        1.110663
## Distancia_Casa          1.009816  1        1.004896
## Edad                    1.242080  1        1.114486

Esto indica que no existe multicolinealidad significativa en el modelo.

Matriz de confusion

Una vez tenemos claro que el modelo esta especificado de manera correcta, con el dataset de test, realizamos las predicciones y calculamos la matriz de confusion.

probabilidades <- predict(modelo1, type = "response")

pred_clases <- ifelse(probabilidades > 0.5, 1, 0)

# 2. Convertir a factor con mismos niveles
pred_clases_factor <- factor(pred_clases, levels = c(0, 1))
real_factor <- factor(df3$Rotación, levels = c(0, 1))

# 3. Matriz de confusión
cm <- confusionMatrix(pred_clases_factor, real_factor, positive = "1")
print(cm)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction    0    1
##          0 1213  201
##          1   20   36
##                                           
##                Accuracy : 0.8497          
##                  95% CI : (0.8304, 0.8676)
##     No Information Rate : 0.8388          
##     P-Value [Acc > NIR] : 0.1354          
##                                           
##                   Kappa : 0.1962          
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 0.15190         
##             Specificity : 0.98378         
##          Pos Pred Value : 0.64286         
##          Neg Pred Value : 0.85785         
##              Prevalence : 0.16122         
##          Detection Rate : 0.02449         
##    Detection Prevalence : 0.03810         
##       Balanced Accuracy : 0.56784         
##                                           
##        'Positive' Class : 1               
## 

La matriz de confusion nos indica si los datos fueron correctamente clasificados o no, a continuacion describiremos lo que nos muestra la matriz.

  • 1213 casos estan clasificados correctamente en la clase 0 (no rotan)

  • 20 casos son falsos positivos, osea, clase 0 (no rotan) fueron clasificados como 1 (si rotan)

  • 201 casos son falsos negativos, osea, clase 1 (si rotan) fueron clasificados como 0 (no rotan)

  • 36 casos estan clasificados de manera correcta en la clase 1 (si rotan)

Métricas clave

Accuracy = 0.8497 (84.97%)

El modelo acierta casi 9 de cada 10 casos.

Kappa = 0.1962

Esto indica que el modelo tiene una baja capacidad de discriminacion.

Sensibilidad (Recall) = 0.1519

El modelo casi no detecta los casos positivos (clase 1, si rotan).

Especificidad = 0.9837

Detecta bien a la clase 0 (negativos).

El modelo es capaz de detectar el 98.37% de los negativos (clase 0, no rotan).

Precisión (PPV) = 0.6428

Esto nos indica que solo el 64.28% de los casos que el modelo predice como verdaderos, son realmente verdaderos.

Balanced Accuracy = 0.5678

Promedia sensibilidad y especificidad → da una medida más justa en contextos desbalanceados y en este contesto el modelo esta desbalanceado, ya que predice de manera clara quien no rota, pero no de manera clara para los que si rotan.

Curva de ROC

# Generar objeto ROC
roc_obj1 <- roc(df3$Rotación, probabilidades)

print(roc_obj1)
## 
## Call:
## roc.default(response = df3$Rotación, predictor = probabilidades)
## 
## Data: probabilidades in 1233 controls (df3$Rotación 0) < 237 cases (df3$Rotación 1).
## Area under the curve: 0.7673

Con una area bajo la curva de 0.7673 (76.73%) el modelo tiene un buen poder de discriminacion.

# Graficar la curva ROC
plot(roc_obj1, col = "blue", lwd = 2, main = "Curva ROC - Modelo Logit")

Aqui podemos observar la grafica de la curva de ROC, con un corte predeterminado en 0.5

F1 score

Es una medida que permite identificar correctamente los casos positivos (empleados qu eno rotan), entre mas cercano a 1, el modelo tiene una muy buena precision.

# Calcular F1 Score
f1 <- F1_Score(y_true = df3$Rotación,
               y_pred = pred_clases,
               positive = "1")
print(f1)
## [1] 0.2457338

El modelo no logra un equilibrio entre identificar los casos positivos (Recall) y no equivocarse demasiado (Precision).

Curva de precision

# Calcular la curva Precision-Recall
pr1 <- pr.curve(scores.class0 = probabilidades[df3$Rotación == 1],
               scores.class1 = probabilidades[df3$Rotación == 0],
               curve = TRUE)

# Mostrar el valor del AUC bajo la curva PR
print(paste("AUC (PR curve):", round(pr1$auc.integral, 4)))
## [1] "AUC (PR curve): 0.4431"
plot(pr1, main = "Curva Precision-Recall", col = "darkred", lwd = 2)

El modelo tiene buen Accuracy global, debido a que predice bien la clase mayoritaria (no rotación), sain embargo la curva PR muestra que es muy débil en la clase minoritaria (rotación = 1).

Conclusión

El mejor modelo es en el cual se realiza la particion de los datos y esto lo elegimos con el criterio de informacion de Akaike (AIC), que permite seleccionar el mejor modelo al encontrar un equilibrio entre la complejidad del modelo y la bonda de ajuste, ademas se tuvieron en cuenta los criterios Cox & Snell, Nagelkerke y McFadden.

Modelo de partición

LogRegR2(modelo)
## Chi2                 90.02204 
## Df                   10 
## Sig.                 5.329071e-15 
## Cox and Snell Index  0.1756667 
## Nagelkerke Index     0.2412576 
## McFadden's R2        0.1483232

Modelo sin particion

LogRegR2(modelo1)
## Chi2                 193.1868 
## Df                   10 
## Sig.                 0 
## Cox and Snell Index  0.1231502 
## Nagelkerke Index     0.2099316 
## McFadden's R2        0.1487674

Ambos modelos son significativos (p < 0.001). El Chi cuadrado es mayor en el segundo modelo (193 vs. 89), lo que indica mayor mejora si no hay ningun modelo Sin embargo, el Nagelkerke R² es superior en el primero (0.239 vs. 0.210), lo que muestra mayor poder explicativo. El McFadden R² es muy similar en ambos (0.15), indicando ajuste aceptable. En conclusión, aunque el segundo modelo mejora más respecto al nulo, el primer modelo es ligeramente mejor por su mayor capacidad explicativa.

Efectos marginales

Los efectos marginales, permiten realizar una interpretacion mas clara del modelo, analizando el impacto de las variables explicativas frente a la variable target, permitiendo conocer, como los pequeños cambios de una variable independiente afectan la variable dependiente.

Efectos marginales promedio (AME)
factor AME SE z p lower upper
Distancia_Casa 0.0053 0.0024 2.2504 0.0244 0.0007 0.0100
Edad -0.0068 0.0025 -2.7417 0.0061 -0.0117 -0.0019
Equilibrio_Trabajo_Vida2 -0.0427 0.0943 -0.4524 0.6510 -0.2275 0.1422
Equilibrio_Trabajo_Vida3 -0.1540 0.0870 -1.7708 0.0766 -0.3245 0.0165
Equilibrio_Trabajo_Vida4 -0.0379 0.1055 -0.3594 0.7193 -0.2448 0.1689
Horas_Extra 0.2291 0.0364 6.2980 0.0000 0.1578 0.3004
Ingreso_Mensual 0.0000 0.0000 -3.3189 0.0009 0.0000 0.0000
Satisfación_Laboral2 -0.1408 0.0629 -2.2392 0.0251 -0.2639 -0.0176
Satisfación_Laboral3 -0.0811 0.0583 -1.3906 0.1643 -0.1954 0.0332
Satisfación_Laboral4 -0.1955 0.0557 -3.5125 0.0004 -0.3046 -0.0864
  • Distancia al trabajo: Por cada kilómetro adicional, incrementa la probabilidad de rotación en 0.37%, aunque no es estadísticamente significativo (p = 0.12).

  • Edad: Por cada año adicional reduce la probabilidad de rotación en 0.39% puntos porcentuales, aunque no es estadísticamente significativo (p = 0.12).

  • Equilibrio Trabajo-Vida baja (2): Con respecto a un Equilibrio Trabajo-Vida muy baja (1), el incremento a un Equilibrio Trabajo-Vida baja (2), reduce la probabilidad de rotar en 25.6% (p < 0.05).

  • Equilibrio Trabajo-Vida alta (3): Con respecto a un Equilibrio Trabajo-Vida muy baja (1), el incremento a un Equilibrio Trabajo-Vida alto (3), reduce la probabilidad de rotar en 33.76% (p < 0.001).

  • Equilibrio Trabajo-Vida muy alta (4): Con respecto a un Equilibrio Trabajo-Vida muy baja (1), el incremento a un Equilibrio Trabajo-Vida muy alta (4), reduce la probabilidad de rotar en 24.23% (p < 0.05).

  • Horas Extra: Por cada hora adicional aumenta la probabilidad de rotación en 25.83%, siendo altamente significativo (p < 0.001).

  • Ingreso Mensual: El incremento de 1 peso al ingreso del empleado, reduce la probabilidad de rotacion, aunque tiene un efecto positivo muy pequeño es estadísticamente significativo (p < 0.01).

  • Satisfacción Laboral baja (2): Con respecto a la Satisfacción Laboral muy baja (1), el incremento a una Satisfacción Laboral baja (2), reduce la probabilidad de rotación en 12.18%, aunque con significancia marginal (p = 0.059).

  • Satisfacción Laboral alto (3): Con respecto a la Satisfacción Laboral muy baja (1), el incremento a una Satisfacción Laboral alta (3), reduce la probabilidad en 11.7% (p < 0.05).

  • Satisfacción Laboral muy alto (4): Con respecto a la Satisfacción Laboral muy baja (1), el incremento a una Satisfacción Laboral muy alta (4), reduce la probabilidad en 17.54% (p < 0.01).

Predicciones

Realizamos la prediccion de rotacion o no, para un empleado con un nivel de satisfaccion laboral bajo (2), equilibrio entre trabajo y vida personal bajo (2). Este trabajador realiza horas extras, tiene un ingreso de 10.000, la distancia del trabajo a la casa son de 10 km y tiene 28 años de edad.

empleado_nuevo <- data.frame(
  Satisfación_Laboral = factor(2, 
                               levels = levels(test$Satisfación_Laboral)),  
  Equilibrio_Trabajo_Vida = factor(2, 
                                   levels = levels(test$Equilibrio_Trabajo_Vida)), 
  Horas_Extra = 1,               # Sí hace horas extra
  Ingreso_Mensual = 10000,        # Ingreso mensual
  Distancia_Casa = 10,           # Vive a 15 km
  Edad = 28                      # Tiene 25 años
)

# Probabilidad de rotación
prob_rotacion <- predict(modelo, newdata = empleado_nuevo, type = "response")
print(paste("Probabilidad de rotación:", round(prob_rotacion, 3)))
## [1] "Probabilidad de rotación: 0.544"
clasificacion <- ifelse(prob_rotacion >= 0.5, "En riesgo de rotación", "No en riesgo")
print(paste("Clasificación:",clasificacion))
## [1] "Clasificación: En riesgo de rotación"

Este empleado tiene una probabilidad de rotar

Bibliografía

  1. Marroquín, A. Y., & Cruz, F. N. (2020). Hechos que provocan la rotación de personal en las empresas colombianas a partir del año 2020.

  2. Cadena, N., Posada, N., Arana, M., & Torres, S. (2022). Causas y consecuencias de la renuncia voluntaria en las empresas de producción en Bogotá.

  3. Poveda Madrid, E. C., Díaz Álvarez, M. I., & Puentes Vargas, M. P. (2025). Estrategias de retención de empleados entre 23 y 30 años debido a la alta rotación laboral en Bogotá para estas edades

  4. Herrity, J. (2025, marzo 26). 9 causes of high employee turnover and how to prevent it. Indeed Career Guide. https://www.indeed.com/career-advice/career-development/high-employee-turnover

  5. https://www.icesi.edu.co/editorial/intro-clasificacion-web/EvLogit.html