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 laboral: 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, entre mayor edad menor sera la rotacion (la poblacion joven presentará mayor rotación.)

Análisis de las variables

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 insatisfecho, pero existe la posibilidad 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 empresa.

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")

Observamos que el 71.7% de los empleados no trabaja horas extras, lo que se relaciona con el equilibrio entre la vida personal y el trabajo, esto evita el sobrecarga general, estres y la rotacion. Mientras 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

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

# Separo las variables numericas del dataset
num_vars <- df1[, c("Ingreso_Mensual", "Distancia_Casa", "Edad")]

# Matriz de correlación
cor_matrix <- cor(num_vars, use = "complete.obs", method = "pearson")
print(cor_matrix)
##                 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 <- 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)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
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 que no 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)

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