1 En pocas palabras

De los 1,470 empleados de la base, 237 (16%) cambiaron de cargo. Queríamos saber qué los diferencia de quienes se quedaron y, sobre todo, poder anticipar quién será el próximo.

Para eso usamos un modelo logit (regresión logística binomial) con seis variables. Encontramos que las seis influyen en la rotación, y tres de ellas pesan mucho más que el resto:

  • Quien trabaja horas extra tiene 4.3 veces más chances de rotar.
  • Quien viaja con frecuencia por trabajo tiene 3.8 veces más chances que quien no viaja.
  • Un empleado soltero tiene 2.2 veces más chances que uno casado.

En cambio, la edad, un mejor salario y los años en el cargo funcionan como “anclas” que reducen el riesgo.

El modelo acierta razonablemente: con datos que nunca había visto, logra un AUC de 0.75. Para usarlo en la práctica recomendamos marcar como “en riesgo” a todo empleado con probabilidad de 22% o más, y no de 50%, que es el corte habitual. La razón se explica en la sección 7.

2 El problema y la herramienta

La empresa quiere dejar de enterarse de la rotación cuando ya ocurrió y empezar a anticiparla. La pregunta es de sí o no (¿esta persona rotará?), así que la variable respuesta solo toma dos valores:

\[y_i = \begin{cases} 1 & \text{si el empleado rota} \\ 0 & \text{si no rota} \end{cases} \qquad y_i \sim \text{Bernoulli}(\pi_i)\]

Una regresión lineal podría dar “probabilidades” de 130% o de −20%. El modelo logit, también llamado regresión logística binaria o binomial, resuelve ese problema: no modela la probabilidad directamente, sino su logaritmo de chances (odds), y así garantiza un resultado siempre entre 0 y 1:

\[\ln\left(\frac{\pi_i}{1-\pi_i}\right) = \beta_0 + \beta_1 x_{1i} + \dots + \beta_k x_{ki}\]

Los coeficientes se estiman por máxima verosimilitud. Para leerlos usamos \(e^{\beta}\), la razón de odds (OR): un OR de 2 significa que las chances de rotar se duplican, y un OR de 0.9, que caen un 10%.

3 Qué variables elegimos y por qué

La base no tiene datos faltantes ni registros duplicados, así que pudimos trabajarla directamente. De sus 24 variables elegimos seis, cada una con una hipótesis concreta:

Variable Tipo Lo que esperamos encontrar
Horas extra Categórica Trabajar de más desgasta y quita tiempo personal. Quien hace horas extra rotará más.
Estado civil Categórica Un soltero suele tener menos ataduras para moverse. Los solteros rotarán más que los casados.
Viajes de negocio Categórica Viajar seguido cansa y altera la vida familiar. Quien viaja frecuentemente rotará más.
Edad Cuantitativa Los más jóvenes están explorando su carrera. A mayor edad, menos rotación.
Ingreso mensual Cuantitativa Un buen salario quita motivos para buscar otra cosa. A mayor ingreso, menos rotación.
Antigüedad en el cargo Cuantitativa Los primeros años en un cargo son los más inestables. A más años en el cargo, menos rotación.

Preferimos “antigüedad en el cargo” a “años de experiencia” porque esta última está muy correlacionada con la edad y el ingreso (r ≈ 0.7). Incluir las tres habría causado multicolinealidad desde el principio.

d <- rotacion %>%
  transmute(
    y            = as.integer(Rotación == "Si"),
    Rotacion     = Rotación,
    Horas_Extra  = factor(Horas_Extra, levels = c("No", "Si")),
    Estado_Civil = factor(Estado_Civil, levels = c("Casado", "Divorciado", "Soltero")),
    Viaje        = factor(`Viaje de Negocios`, levels = c("No_Viaja", "Raramente", "Frecuentemente")),
    Edad, Ingreso_Mensual, Antigüedad_Cargo
  )

4 Mirando cada variable por separado

4.1 La rotación

ggplot(d, aes(Rotacion, fill = Rotacion)) +
  geom_bar(width = 0.5) +
  geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.4) +
  scale_fill_manual(values = c(No = azul, Si = rojo), guide = "none") +
  labs(x = "¿Rotó?", y = "Empleados") + theme_minimal()

Uno de cada seis empleados rotó (16.1%). Es una cifra alta para cualquier área de talento humano, pero sigue siendo la minoría, y eso tiene una consecuencia importante: un “modelo” que simplemente dijera nadie se va acertaría el 84% de las veces sin haber aprendido nada. Por eso, más adelante no juzgaremos al modelo solo por su porcentaje de aciertos.

4.2 Las variables categóricas

porc <- function(x) round(100 * prop.table(table(x)), 1)
data.frame(
  Variable = c("Horas extra", "", "Estado civil", "", "", "Viajes", "", ""),
  Categoría = c(levels(d$Horas_Extra), levels(d$Estado_Civil), levels(d$Viaje)),
  `% de empleados` = c(porc(d$Horas_Extra), porc(d$Estado_Civil), porc(d$Viaje)),
  check.names = FALSE
) %>% tabla(1)
Variable Categoría % de empleados
No Horas extra No 71.7
Si Si 28.3
Casado Estado civil Casado 45.8
Divorciado Divorciado 22.2
Soltero Soltero 32.0
No_Viaja Viajes No_Viaja 10.2
Raramente Raramente 71.0
Frecuentemente Frecuentemente 18.8

Poco más de una cuarta parte de la plantilla trabaja horas extra (28%). La mayoría está casada (46%) y casi todos viajan “raramente” (71%). El grupo que no viaja es el más pequeño (10%, unas 150 personas), pero alcanza para usarlo como punto de comparación.

4.3 Las variables cuantitativas

resumen <- function(x) c(Media = mean(x), Mediana = median(x), `Desv. est.` = sd(x),
                         Mín = min(x), Máx = max(x))
rbind(Edad = resumen(d$Edad),
      `Ingreso mensual` = resumen(d$Ingreso_Mensual),
      `Antigüedad en el cargo` = resumen(d$Antigüedad_Cargo)) %>% tabla(1)
Media Mediana Desv. est. Mín Máx
Edad 36.9 36 9.1 18 60
Ingreso mensual 6,502.9 4,919 4,708.0 1,009 19,999
Antigüedad en el cargo 4.2 3 3.6 0 18
d %>%
  tidyr::pivot_longer(c(Edad, Ingreso_Mensual, Antigüedad_Cargo)) %>%
  ggplot(aes(value)) +
  geom_histogram(bins = 25, fill = "grey55", color = "white") +
  facet_wrap(~name, scales = "free") +
  labs(x = NULL, y = "Empleados") + theme_minimal()

  • Edad: distribución bastante simétrica, alrededor de los 36 años. Es una plantilla joven-adulta.
  • Ingreso: muy disparejo. La media ($6,503) está muy por encima de la mediana ($4,919) porque unos pocos cargos directivos ganan cerca de $20,000. No son errores, así que los conservamos.
  • Antigüedad en el cargo: la mitad lleva 3 años o menos en su puesto actual.

El modelo logit no exige que estas variables tengan una distribución normal, así que el sesgo del ingreso no es un problema.

5 Cruzando cada variable con la rotación

Ahora comparamos a quienes rotaron con quienes no, una variable a la vez. Para las categóricas usamos la prueba χ², que contrasta si la tasa de rotación cambia según la categoría. Para las cuantitativas usamos la prueba t y la de Wilcoxon, esta última porque el ingreso y la antigüedad tienen distribuciones sesgadas.

5.1 Categóricas: ¿cambia la tasa de rotación según el grupo?

tasa <- function(var) {
  d %>% group_by(Grupo = .data[[var]]) %>% summarise(tasa = 100 * mean(y)) %>% mutate(Variable = var)
}
bind_rows(tasa("Horas_Extra"), tasa("Estado_Civil"), tasa("Viaje")) %>%
  mutate(Grupo = as.character(Grupo)) %>%
  ggplot(aes(Grupo, tasa)) +
  geom_col(fill = rojo, width = 0.6) +
  geom_text(aes(label = paste0(round(tasa, 1), "%")), vjust = -0.4, size = 3.2) +
  geom_hline(yintercept = 16.1, linetype = "dashed") +
  facet_wrap(~Variable, scales = "free_x") +
  labs(x = NULL, y = "% que rota") + ylim(0, 35) + theme_minimal()

chi <- lapply(c("Horas_Extra", "Estado_Civil", "Viaje"), function(v) chisq.test(table(d[[v]], d$y)))
data.frame(Variable = c("Horas extra", "Estado civil", "Viajes"),
           `χ²` = sapply(chi, `[[`, "statistic"),
           `valor p` = format.pval(sapply(chi, `[[`, "p.value"), digits = 2),
           check.names = FALSE) %>% tabla(1)
Variable χ² valor p
Horas extra 87.6 < 2e-16
Estado civil 46.2 9.5e-11
Viajes 24.2 5.6e-06

La línea punteada del gráfico marca la tasa general (16%). Las diferencias son claras, y las tres pruebas χ² dan p < 0.001:

  • Horas extra: rota el 30.5% de quienes las trabajan, contra el 10.4% de quienes no. Casi el triple.
  • Estado civil: los solteros rotan el 25.5%, el doble que casados y divorciados, que están prácticamente igual entre sí.
  • Viajes: la rotación sube escalón por escalón: 8% entre quienes no viajan, 15% entre quienes viajan raramente y 25% entre quienes viajan frecuentemente.

5.2 Cuantitativas: ¿son distintos quienes rotan?

d %>%
  tidyr::pivot_longer(c(Edad, Ingreso_Mensual, Antigüedad_Cargo)) %>%
  ggplot(aes(Rotacion, value, fill = Rotacion)) +
  geom_boxplot(alpha = 0.85, outlier.alpha = 0.3) +
  scale_fill_manual(values = c(No = azul, Si = rojo), guide = "none") +
  facet_wrap(~name, scales = "free") +
  labs(x = "¿Rotó?", y = NULL) + theme_minimal()

comparar <- function(v) {
  c(`Promedio (no rota)` = mean(d[[v]][d$y == 0]),
    `Promedio (rota)`    = mean(d[[v]][d$y == 1]),
    `p prueba t`         = t.test(d[[v]] ~ d$y)$p.value,
    `p Wilcoxon`         = wilcox.test(d[[v]] ~ d$y)$p.value)
}
rbind(Edad = comparar("Edad"), Ingreso = comparar("Ingreso_Mensual"),
      `Antigüedad en el cargo` = comparar("Antigüedad_Cargo")) %>% signif(3) %>% tabla()
Promedio (no rota) Promedio (rota) p prueba t p Wilcoxon
Edad 37.60 33.6 0 0
Ingreso 6,830.00 4,790.0 0 0
Antigüedad en el cargo 4.48 2.9 0 0

El perfil de quien rota es claro: en promedio es 4 años más joven, gana $2,000 menos al mes y lleva año y medio menos en su cargo. Las dos pruebas son significativas en las tres variables (p < 0.001).

5.3 ¿Se cumplieron nuestras hipótesis?

Para cerrar el análisis bivariado, ajustamos una regresión logit con cada variable sola. Lo que nos interesa es el signo de su coeficiente:

simple <- function(v) {
  m <- glm(reformulate(v, "y"), data = d, family = binomial)
  broom::tidy(m)[-1, ] %>% transmute(Variable = term, β = estimate, OR = exp(estimate), `valor p` = p.value)
}
bind_rows(lapply(c("Horas_Extra", "Estado_Civil", "Viaje", "Edad", "Ingreso_Mensual", "Antigüedad_Cargo"), simple)) %>%
  mutate(`valor p` = format.pval(`valor p`, digits = 2, eps = 0.001)) %>% tabla(4)
Variable β OR valor p
Horas_ExtraSi 1.3274 3.7712 <0.001
Estado_CivilDivorciado -0.2395 0.7871 0.271
Estado_CivilSoltero 0.8772 2.4041 <0.001
ViajeRaramente 0.7044 2.0225 0.025
ViajeFrecuentemente 1.3389 3.8149 <0.001
Edad -0.0523 0.9491 <0.001
Ingreso_Mensual -0.0001 0.9999 <0.001
Antigüedad_Cargo -0.1463 0.8639 <0.001

Las seis hipótesis se cumplen. Horas extra, soltería y viajes tienen signo positivo, es decir, aumentan el riesgo. Edad, ingreso y antigüedad tienen signo negativo, es decir, lo reducen. El único matiz es el de los divorciados: no rotan más que los casados (p = 0.27). Lo que aumenta el riesgo no es “no estar casado”, sino ser soltero.

6 El modelo logit completo

Hasta ahora miramos cada variable por separado. Pero varias están relacionadas entre sí: los más jóvenes también suelen ganar menos y llevar menos tiempo en el cargo. Para separar el efecto propio de cada una, las metemos todas en un mismo modelo.

modelo <- glm(y ~ Horas_Extra + Estado_Civil + Viaje + Edad + Ingreso_Mensual + Antigüedad_Cargo,
              data = d, family = binomial(link = "logit"))

broom::tidy(modelo) %>%
  transmute(Variable = term, β = estimate, OR = exp(estimate),
            `OR IC 95%` = sprintf("%.3f – %.3f", exp(estimate - 1.96 * std.error), exp(estimate + 1.96 * std.error)),
            `valor p` = format.pval(p.value, digits = 2, eps = 0.001)) %>%
  tabla(4)
Variable β OR OR IC 95% valor p
(Intercept) -1.4259 0.2403 0.097 – 0.593 0.0020
Horas_ExtraSi 1.4512 4.2682 3.130 – 5.820 <0.001
Estado_CivilDivorciado -0.2913 0.7473 0.477 – 1.172 0.2043
Estado_CivilSoltero 0.7935 2.2111 1.580 – 3.094 <0.001
ViajeRaramente 0.6700 1.9543 1.022 – 3.736 0.0427
ViajeFrecuentemente 1.3246 3.7607 1.883 – 7.511 <0.001
Edad -0.0289 0.9716 0.953 – 0.991 0.0042
Ingreso_Mensual -0.0001 0.9999 1.000 – 1.000 0.0069
Antigüedad_Cargo -0.1041 0.9011 0.854 – 0.951 <0.001

6.1 Cómo leer los coeficientes

Cada efecto se interpreta comparando dos personas idénticas en todo lo demás:

  • Horas extra (OR = 4.27): es el factor más fuerte de todos. Hacer horas extra multiplica por 4.3 las chances de rotar.
  • Viajes: viajar raramente casi duplica las chances (OR = 1.95) frente a no viajar, y viajar frecuentemente las multiplica por 3.8 (OR = 3.76).
  • Soltero (OR = 2.21): duplica las chances frente a un casado. Ser divorciado no tiene un efecto significativo (p = 0.20).
  • Edad (OR = 0.972): cada año de más reduce las chances un 3%. Diez años de diferencia equivalen a un 25% menos.
  • Ingreso (OR = 0.99993 por cada $1): el número parece insignificante solo porque la unidad es un peso. Por cada $1,000 más de salario, las chances bajan un 7%.
  • Antigüedad en el cargo (OR = 0.901): cada año más en el puesto reduce las chances un 10%.

Comparados con el análisis bivariado, los efectos de edad, ingreso y antigüedad se reducen un poco, porque en parte se solapaban entre sí. Aun así, los tres conservan un efecto propio.

6.2 ¿Son significativas? Prueba de razón de verosimilitud

La prueba de razón de verosimilitud compara qué tan bien explica los datos el modelo con una variable y sin ella. Si al quitarla el ajuste empeora mucho, esa variable importa.

nulo <- glm(y ~ 1, data = d, family = binomial)
global <- anova(nulo, modelo, test = "LRT")
cat("Prueba global: G² =", round(global$Deviance[2], 1), "con", global$Df[2],
    "gl, p", format.pval(global$`Pr(>Chi)`[2], eps = 0.001), "\n")
## Prueba global: G² = 216.2 con 8 gl, p < 0.001
drop1(modelo, test = "LRT")[-1, c("Df", "LRT", "Pr(>Chi)")] %>%
  arrange(desc(LRT)) %>%
  setNames(c("gl", "G² al quitarla", "valor p")) %>% tabla(4)
gl G² al quitarla valor p
Horas_Extra 1 85.4125 0.0000
Estado_Civil 2 32.8408 0.0000
Viaje 2 20.1808 0.0000
Antigüedad_Cargo 1 15.5692 0.0001
Edad 1 8.5536 0.0034
Ingreso_Mensual 1 7.9060 0.0049
  • Prueba global: el modelo completo explica la rotación muchísimo mejor que no usar ninguna variable (G² = 216, p < 0.001).
  • Por variable: las seis son significativas y ninguna sobra. Ordenadas por importancia, quedan horas extra (de lejos la primera), estado civil, viajes, antigüedad en el cargo, edad e ingreso.

6.3 Revisión de supuestos

vif(modelo)[, 1] %>% round(2)
##      Horas_Extra     Estado_Civil            Viaje             Edad 
##             1.03             1.03             1.01             1.27 
##  Ingreso_Mensual Antigüedad_Cargo 
##             1.33             1.10
  • Distribución Bernoulli: se cumple por construcción, porque la respuesta solo puede ser rota o no rota.
  • Independencia: cada fila es un empleado distinto, observado una sola vez.
  • Multicolinealidad: todos los VIF están cerca de 1, muy por debajo del umbral de alerta (5). Las variables no se pisan entre sí.
  • Tamaño de muestra: tenemos 237 casos de rotación para 8 coeficientes, unos 30 por coeficiente. La regla práctica pide al menos 10.
  • Linealidad en el logit: se cumple bien para el ingreso. Para la edad y la antigüedad, la prueba de Box-Tidwell indica que su efecto protector es más fuerte en los primeros años y luego se aplana. No cambia ninguna conclusión, pero significa que los OR de esas dos variables deben leerse como un efecto promedio.

7 ¿Qué tan bien predice el modelo?

Evaluar un modelo con los mismos datos con los que se construyó es como calificar a un estudiante con el examen que ya resolvió. Por eso separamos la base: con el 70% estimamos el modelo y con el 30% restante (442 empleados que el modelo nunca vio) medimos su desempeño.

set.seed(123)
idx <- unlist(lapply(split(seq_len(nrow(d)), d$y), function(i) sample(i, floor(0.7 * length(i)))))
train <- d[idx, ]
test  <- d[-idx, ]

modelo_train <- glm(formula(modelo), data = train, family = binomial)
prob_test <- predict(modelo_train, test, type = "response")
roc_test  <- roc(test$y, prob_test, quiet = TRUE)
plot(roc_test, col = rojo, lwd = 2, legacy.axes = TRUE, print.auc = TRUE,
     xlab = "1 - Especificidad", ylab = "Sensibilidad", main = "Curva ROC (datos de validación)")

La curva ROC muestra cuántos de los que rotan detecta el modelo (sensibilidad) frente a cuántas falsas alarmas genera, para todos los puntos de corte posibles. El área bajo la curva, el AUC, resume todo en un número:

  • AUC = 0.75 (IC 95%: 0.69 – 0.82).
  • En palabras simples: si tomamos al azar a alguien que rotó y a alguien que no, el modelo le asigna más riesgo al que rotó en 3 de cada 4 casos.
  • Es una capacidad de discriminación aceptable (el rango 0.7–0.8 se considera así), y claramente mejor que el azar (0.5).
  • Con los datos de entrenamiento el AUC fue 0.79; la caída en validación es pequeña, así que el modelo no está sobreajustado.

8 ¿A partir de qué probabilidad intervenir?

El modelo entrega una probabilidad. Para actuar necesitamos una regla: si la probabilidad supera el corte, intervenimos. Ese corte define el equilibrio entre dos errores:

  • Corte alto: pocas falsas alarmas, pero se escapan muchos de los que sí se van.
  • Corte bajo: detectamos a casi todos, pero también “molestamos” a gente que no pensaba irse.
metricas <- function(corte) {
  pred <- prob_test >= corte
  c(Corte = corte,
    Exactitud     = mean(pred == test$y),
    Sensibilidad  = mean(pred[test$y == 1]),
    Especificidad = mean(!pred[test$y == 0]),
    `% alertados` = mean(pred))
}
sens <- as.data.frame(t(sapply(seq(0.05, 0.6, by = 0.01), metricas)))

youden <- coords(roc(train$y, fitted(modelo_train), quiet = TRUE), "best", ret = "threshold")$threshold
sens %>%
  tidyr::pivot_longer(c(Exactitud, Sensibilidad, Especificidad)) %>%
  ggplot(aes(Corte, value, color = name)) +
  geom_line(linewidth = 1) +
  geom_vline(xintercept = c(youden, 0.5), linetype = "dashed", color = c("black", "grey60")) +
  annotate("text", x = youden + 0.01, y = 0.05, label = "corte recomendado", hjust = 0, size = 3) +
  scale_color_manual(values = c(Exactitud = "grey40", Sensibilidad = rojo, Especificidad = azul)) +
  scale_y_continuous(labels = scales::percent) +
  labs(x = "Punto de corte", y = NULL, color = NULL) +
  theme_minimal() + theme(legend.position = "bottom")

as.data.frame(t(sapply(c(0.10, 0.15, 0.20, youden, 0.30, 0.40, 0.50), metricas))) %>%
  mutate(across(-Corte, ~ round(100 * .x, 1)), Corte = round(Corte, 2)) %>%
  tabla(2) %>% row_spec(4, bold = TRUE, background = "#FBEDE9")
Corte Exactitud Sensibilidad Especificidad % alertados
0.10 57.2 84.7 51.9 54.1
0.15 68.1 72.2 67.3 39.1
0.20 74.0 62.5 76.2 30.1
0.22 76.9 61.1 80.0 26.7
0.30 80.5 43.1 87.8 17.2
0.40 82.8 27.8 93.5 10.0
0.50 83.5 12.5 97.3 4.3

Todas las métricas en porcentaje, sobre los 442 empleados de validación.

El corte de 0.5 no sirve aquí. Con él, el modelo acierta el 83.5% de las veces, una cifra que suena bien hasta que se recuerda que decir “nadie se va” acierta el 83.7%. Detrás de ese porcentaje, el modelo solo detecta a 1 de cada 8 personas que realmente se van. Para retener talento, eso no sirve.

Recomendamos un corte de 0.22. Es el que mejor equilibra sensibilidad y especificidad (índice de Youden) y lo elegimos con los datos de entrenamiento. Con él:

  • Detectamos al 61% de quienes rotan, cinco veces más que con 0.5.
  • Acertamos con el 80% de quienes se quedan.
  • Alertamos a cerca de una de cada cuatro personas de la plantilla.

El corte puede ajustarse según el costo de intervenir. Si la intervención es barata, como una conversación con el jefe, conviene bajarlo a 0.15 para cubrir a más gente. Si es costosa, como un aumento de sueldo, conviene subirlo a 0.30 para concentrarse en los casos más claros.

9 Un caso concreto

Imaginemos a Andrés: 28 años, soltero, hace horas extra, viaja con frecuencia, gana $3,000 y lleva un año en su cargo.

andres <- data.frame(Horas_Extra = "Si", Estado_Civil = "Soltero", Viaje = "Frecuentemente",
                     Edad = 28, Ingreso_Mensual = 3000, Antigüedad_Cargo = 1)

escenarios <- andres[rep(1, 4), ]
escenarios$Horas_Extra[2:4] <- "No"
escenarios$Viaje[3:4] <- "Raramente"
escenarios$Ingreso_Mensual[4] <- 4500

data.frame(Escenario = c("Andrés hoy", "Sin horas extra", "Sin horas extra y viajando menos",
                         "Lo anterior + $1,500 más de salario"),
           `Probabilidad de rotar` = paste0(round(100 * predict(modelo, escenarios, type = "response")), "%"),
           check.names = FALSE) %>% tabla()
Escenario Probabilidad de rotar
Andrés hoy 74%
Sin horas extra 40%
Sin horas extra y viajando menos 25%
Lo anterior + $1,500 más de salario 23%

Hoy, Andrés tiene un 74% de probabilidad de rotar, muy por encima del corte de 22%. Hay que intervenir.

La tabla muestra el efecto de las medidas que sí dependen de la empresa:

  • Quitarle las horas extra reduce el riesgo casi a la mitad, al 40%.
  • Si además viaja menos, baja al 25%.
  • Con un aumento de $1,500, queda en 23%.

Ninguna medida basta por sí sola: el riesgo de Andrés se acumula por varios frentes, y la solución también tiene que llegar por varios. Lo que la empresa no puede cambiar, como su edad o su estado civil, sirve para saber a quién mirar primero.

10 Qué haríamos para reducir la rotación

Con base en las variables significativas, proponemos:

  1. Repartir mejor las horas extra. Es la medida con mayor impacto. Contratar apoyo para los picos de trabajo, rotar los turnos extra entre más personas y poner un tope mensual por empleado.
  2. Viajar menos, y viajar mejor. Reemplazar por reuniones virtuales los viajes que no son indispensables, repartir los que sí lo son y compensarlos con descanso o flexibilidad.
  3. Revisar los salarios de entrada. Quienes se van ganan en promedio $2,000 menos. Hay que comparar las bandas salariales más bajas con las del mercado.
  4. Acompañar el primer año en el cargo. El riesgo es mayor al principio: inducción, mentoría y un plan de carrera visible desde el inicio.
  5. Ofrecer movilidad interna a los perfiles jóvenes y solteros. Si van a buscar nuevos retos, que los encuentren dentro de la empresa: proyectos nuevos o rotación planeada entre áreas.
  6. Usar el modelo cada trimestre. Calcular la probabilidad de cada empleado, conversar primero con quienes superen el 22% y actualizar el modelo una vez al año.

Una advertencia final. El modelo muestra asociaciones, no causas. Es razonable pensar que las horas extra empujan a la gente a irse, pero también podría ocurrir que quien ya decidió irse acepte más horas extra para ahorrar. Por eso conviene probar estas medidas primero con un grupo piloto. Además, las probabilidades sirven mejor para ordenar a los empleados por riesgo que como un pronóstico exacto: la prueba de Hosmer-Lemeshow sugiere que la calibración no es perfecta. Usado así, como una forma de saber a quién escuchar primero, el modelo es una herramienta útil.


11 Anexo: salida completa del modelo

summary(modelo)
## 
## Call:
## glm(formula = y ~ Horas_Extra + Estado_Civil + Viaje + Edad + 
##     Ingreso_Mensual + Antigüedad_Cargo, family = binomial(link = "logit"), 
##     data = d)
## 
## Coefficients:
##                          Estimate Std. Error z value Pr(>|z|)    
## (Intercept)            -1.426e+00  4.605e-01  -3.096 0.001961 ** 
## Horas_ExtraSi           1.451e+00  1.582e-01   9.173  < 2e-16 ***
## Estado_CivilDivorciado -2.913e-01  2.295e-01  -1.269 0.204340    
## Estado_CivilSoltero     7.935e-01  1.714e-01   4.631 3.64e-06 ***
## ViajeRaramente          6.700e-01  3.306e-01   2.027 0.042676 *  
## ViajeFrecuentemente     1.325e+00  3.530e-01   3.753 0.000175 ***
## Edad                   -2.885e-02  1.009e-02  -2.860 0.004232 ** 
## Ingreso_Mensual        -6.808e-05  2.518e-05  -2.703 0.006870 ** 
## Antigüedad_Cargo       -1.041e-01  2.731e-02  -3.812 0.000138 ***
## ---
## 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: 1082.4  on 1461  degrees of freedom
## AIC: 1100.4
## 
## Number of Fisher Scoring iterations: 5