1 Introducción
Qué queremos: entender por qué la gente rota de
cargo y predecir esa rotación.
Con esto, RR.HH. puede intervenir antes y no perder talento. No es solo
describir: modelamos para decidir.
Marco teórico exprés (GLM binomial – enlace logit):
- Respuesta binaria: \(Y\in\{0,1\}\) (1
= rotación, 0 = no rotación). - Modelo: \(\text{logit}(p)=\log\frac{p}{1-p}=\beta_0+\beta_1X_1+\cdots+\beta_kX_k\).
- Interpretación: \(\exp(\beta_j)\) es
el odds ratio (OR). - Estimación: máxima
verosimilitud.
- Evaluación: ROC/AUC y matriz de confusión.
# 1) Carga de paquetes y datos
if (!requireNamespace("devtools", quietly = TRUE)) install.packages("devtools")
if (!requireNamespace("paqueteMODELOS", quietly = TRUE)) {
devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
}
library(paqueteMODELOS)
library(dplyr); library(tidyr)
library(ggplot2); library(scales); library(forcats); library(gridExtra)
library(rsample) # split estratificado
library(caret) # matriz de confusión
library(pROC) # ROC/AUC
library(summarytools) # calidad de datos incrustada
library(broom) # tablas limpias de modelos
suppressWarnings({ if (requireNamespace("car", quietly = TRUE)) library(car) })
# Cargo la data y creo y = 1 si "Si", 0 si "No"
data("rotacion")
datos <- rotacion %>% mutate(y = if_else(Rotación == "Si", 1L, 0L))
3 Selección de
variables e hipótesis
# 2) Selección de variables + hipótesis con referencias
# • Técnica: definición de variables predictoras (3 categóricas + 3 numéricas), limpieza mínima de datos y tabla de hipótesis con referencias teóricas.
# • Resultado: se estableció un marco conceptual claro: horas extra, equilibrio, estado civil, satisfacción, ingreso y antigüedad como predictores de la rotación. La tabla 1.1 mostró los signos esperados y la bibliografía.
vars_cat <- c("Horas_Extra", "Equilibrio_Trabajo_Vida", "Estado_Civil")
vars_num <- c("Satisfación_Laboral", "Ingreso_Mensual", "Antigüedad")
vars_all <- c(vars_cat, vars_num)
df <- datos %>%
select(y, Rotación, all_of(vars_all)) %>%
drop_na() %>%
mutate(
Horas_Extra = factor(Horas_Extra),
Estado_Civil = factor(Estado_Civil),
Equilibrio_Trabajo_Vida = ordered(Equilibrio_Trabajo_Vida, levels = sort(unique(Equilibrio_Trabajo_Vida)))
)
hipotesis <- tibble::tibble(
Variable = vars_all,
Tipo = c("Categ. Binaria","Categ. Ordinal","Categ. Nominal",
"Cuantitativa (1-4)","Cuantitativa","Cuantitativa"),
Hipótesis = c(
"H1: Horas extra ↑ rotación (desgaste).",
"H2: Menor equilibrio ↑ rotación.",
"H3: Solteros rotan más que casados.",
"H4: ↑ Satisfacción ↓ rotación.",
"H5: ↑ Ingreso ↓ rotación.",
"H6: ↑ Antigüedad ↓ rotación."
),
`Signo esperado` = c("+","-","Soltero>Casado","-","-","-"),
Referencia = c(
"Kahn (1990), burnout.",
"Greenhaus & Beutell (1985), trabajo-familia.",
"Becker (1993), movilidad laboral.",
"Locke (1976), satisfacción-rotación.",
"Herzberg (1959), motivación-higiene.",
"Meyer & Allen (1991), compromiso."
)
)
knitr::kable(hipotesis, caption = "Tabla 1.1 Hipótesis y referencias bibliográficas")
Tabla 1.1 Hipótesis y referencias bibliográficas
| Horas_Extra |
Categ. Binaria |
H1: Horas extra ↑ rotación (desgaste). |
+ |
Kahn (1990), burnout. |
| Equilibrio_Trabajo_Vida |
Categ. Ordinal |
H2: Menor equilibrio ↑ rotación. |
- |
Greenhaus & Beutell (1985), trabajo-familia. |
| Estado_Civil |
Categ. Nominal |
H3: Solteros rotan más que casados. |
Soltero>Casado |
Becker (1993), movilidad laboral. |
| Satisfación_Laboral |
Cuantitativa (1-4) |
H4: ↑ Satisfacción ↓ rotación. |
- |
Locke (1976), satisfacción-rotación. |
| Ingreso_Mensual |
Cuantitativa |
H5: ↑ Ingreso ↓ rotación. |
- |
Herzberg (1959), motivación-higiene. |
| Antigüedad |
Cuantitativa |
H6: ↑ Antigüedad ↓ rotación. |
- |
Meyer & Allen (1991), compromiso. |
4 Calidad de datos
# 3) Calidad de datos
# • Técnica: uso de summarytools::dfSummary() para describir variables, distribuciones y valores faltantes.
# • Resultado: la base tiene 1470 observaciones completas. Se detecta desbalance (rotación = 16.1%), asimetría en ingreso y antigüedad, y predominio de ciertos niveles en variables categóricas. Esto confirma que los datos son utilizables sin imputaciones.
st_res <- summarytools::dfSummary(
df,
graph.magnif = 0.8,
valid.col = TRUE
)
# NO incluir Bootstrap propio de summarytools
print(st_res, method = "render", bootstrap.css = FALSE, footnote = NA)
5 EDA univariado
(perfiles)
# 4) EDA univariado (perfiles)
# • Técnica: gráficos de distribución (barras, histogramas, boxplots) para respuesta, categóricas y numéricas.
# • Resultado:
# • Rotación minoritaria (16%).
# • Horas extra: mayoría no hace, pero quienes sí tienen más rotación.
# • Equilibrio: mayoría en nivel medio.
# • Ingreso y antigüedad: distribuciones sesgadas a la derecha.
# • Satisfacción laboral: mayoría en niveles 3–4.
# Se observa que las variables tienen sentido para el modelo.
tabla_y <- table(df$y); prop_y <- prop.table(tabla_y); prop_y
##
## 0 1
## 0.8387755 0.1612245
ggplot(df, aes(x = factor(y, labels = c("No","Sí")))) +
geom_bar(fill = c("steelblue","salmon"), alpha = .85) +
geom_text(stat = "count", aes(label = paste0(after_stat(count), "\n",
round(after_stat(count)/sum(after_stat(count))*100,1), "%")),
vjust = -0.5) +
labs(title = "Distribución de Rotación", x = "Rotación", y = "Frecuencia") +
theme_minimal()
# Distribuciones categóricas
plot_cat <- function(var){
p <- ggplot(df, aes(x = .data[[var]])) +
geom_bar(fill = "steelblue", alpha = .8) +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5) +
labs(title = paste("Distribución:", var), x = var, y = "Frecuencia") +
theme_minimal()
if (var == "Estado_Civil") p <- p + theme(axis.text.x = element_text(angle = 45, hjust = 1))
print(p)
}
for (v in vars_cat) plot_cat(v)
# Numéricas: histograma + boxplot
plot_num <- function(var){
p1 <- ggplot(df, aes(x = .data[[var]])) +
geom_histogram(bins = 30, fill = "darkseagreen", color = "black", alpha = .75) +
labs(title = paste("Histograma:", var), x = var, y = "Frecuencia") +
theme_minimal()
p2 <- ggplot(df, aes(y = .data[[var]])) +
geom_boxplot(fill = "khaki", alpha = .8, width = .4) +
coord_flip() +
labs(title = paste("Boxplot:", var), y = var, x = "") +
theme_minimal()
print(gridExtra::grid.arrange(p1, p2, ncol = 2))
}
for (v in vars_num) plot_num(v)
## TableGrob (1 x 2) "arrange": 2 grobs
## z cells name grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]
## TableGrob (1 x 2) "arrange": 2 grobs
## z cells name grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]
## TableGrob (1 x 2) "arrange": 2 grobs
## z cells name grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]







6 Split estratificado
60/40
# 5) Split estratificado 60/40
# • Técnica: partición estratificada (60% entrenamiento, 40% prueba) con rsample.
# • Resultado: balance conservado entre train y test, asegurando que la proporción de rotación se mantenga. Esto permite evaluar el modelo sin sesgos.
split <- rsample::initial_split(df, prop = 0.6, strata = y)
train <- rsample::training(split)
test <- rsample::testing(split)
cat("Train:", nrow(train), " | Test:", nrow(test), "\n",
"Rotación en train:", round(mean(train$y),3),
" | en test:", round(mean(test$y),3), "\n")
## Train: 881 | Test: 589
## Rotación en train: 0.161 | en test: 0.161
7 Análisis bivariado
(categóricas y numéricas)
# 6) Análisis bivariado
# • Técnica:
# • Para categóricas: tablas cruzadas, Chi² y gráficos de tasa.
# • Para numéricas: t-test, boxplots comparativos y tendencia logística.
# • Resultado:
# • Categóricas: asociaciones significativas (solteros y horas extra rotan más; bajo equilibrio aumenta rotación).
# • Numéricas: diferencias significativas (menor satisfacción e ingreso → mayor rotación).
# El análisis bivariado muestra evidencias en la dirección esperada para H1–H5.
# Categóricas: tablas cruzadas + Chi² + tasa de rotación
for (v in vars_cat){
tab <- table(train[[v]], train$y)
colnames(tab) <- c("No (0)", "Sí (1)")
knitr::kable(addmargins(tab), caption = paste("Tabla cruzada:", v))
chi <- chisq.test(tab)
cat("Chi-cuadrado:", round(chi$statistic, 3),
"| gl:", chi$parameter, "| p:", format(chi$p.value, scientific = TRUE), "\n",
if (chi$p.value < 0.05) "✓ Asociación significativa (rechazo H0)." else "No significativa.", "\n")
prop_fila <- prop.table(tab, 1)[,2]
dfp <- tibble::tibble(cat = names(prop_fila), tasa = as.numeric(prop_fila))
p <- ggplot(dfp, aes(x = reorder(cat, tasa), y = tasa)) +
geom_col(fill = "coral", alpha = .85) +
coord_flip() +
scale_y_continuous(labels = scales::percent) +
labs(title = paste("Tasa de rotación por", v), x = v, y = "Tasa de rotación") +
theme_minimal()
print(p)
}
## Chi-cuadrado: 62.818 | gl: 1 | p: 2.267422e-15
## ✓ Asociación significativa (rechazo H0).
## Chi-cuadrado: 17.258 | gl: 3 | p: 6.253815e-04
## ✓ Asociación significativa (rechazo H0).
## Chi-cuadrado: 28.603 | gl: 2 | p: 6.151536e-07
## ✓ Asociación significativa (rechazo H0).
# Numéricas: t-test + boxplot + tendencia logística
for (v in vars_num){
g0 <- train %>% filter(y == 0) %>% pull(.data[[v]])
g1 <- train %>% filter(y == 1) %>% pull(.data[[v]])
tt <- t.test(g0, g1)
cat("\nVariable numérica:", v, "t =", round(tt$statistic, 3), "| p =", format(tt$p.value, scientific = TRUE),
if (tt$p.value < 0.05) "✓ Diferencia significativa." else "No significativa.", "\n")
p1 <- ggplot(train, aes(x = factor(y, labels = c("No","Sí")), y = .data[[v]], fill = factor(y))) +
geom_boxplot(alpha = .8) +
scale_fill_manual(values = c("lightblue","lightcoral")) +
labs(title = paste(v, "por Rotación"), x = "Rotación", y = v) +
theme_minimal() + theme(legend.position = "none")
p2 <- ggplot(train, aes(x = .data[[v]], y = y)) +
geom_jitter(height = 0.05, alpha = .25) +
geom_smooth(method = "glm", method.args = list(family = binomial()), se = TRUE, color = "red") +
labs(title = "Tendencia logística", x = v, y = "Prob. rotación") +
theme_minimal()
print(gridExtra::grid.arrange(p1, p2, ncol = 2))
}
##
## Variable numérica: Satisfación_Laboral t = 3.643 | p = 3.457159e-04 ✓ Diferencia significativa.
## TableGrob (1 x 2) "arrange": 2 grobs
## z cells name grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]
##
## Variable numérica: Ingreso_Mensual t = 5.289 | p = 2.735888e-07 ✓ Diferencia significativa.
## TableGrob (1 x 2) "arrange": 2 grobs
## z cells name grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]
##
## Variable numérica: Antigüedad t = 3.31 | p = 1.109546e-03 ✓ Diferencia significativa.
## TableGrob (1 x 2) "arrange": 2 grobs
## z cells name grob
## 1 1 (1-1,1-1) arrange gtable[layout]
## 2 2 (1-1,2-2) arrange gtable[layout]






8 Estimación del modelo
logit
# 7) Estimación del modelo logit
# • Técnica: regresión logística binomial (glm) con las 6 variables. OR e intervalos de confianza para interpretación.
# • Resultado:
# • Horas extra, equilibrio, estado civil (solteros), satisfacción e ingreso → significativos.
# • Antigüedad no significativa.
# • Significancia global (LR χ²) muy alta → el modelo es mejor que el nulo.
# El logit captura bien los predictores relevantes.
form <- y ~ Horas_Extra + Equilibrio_Trabajo_Vida + Estado_Civil +
Satisfación_Laboral + Ingreso_Mensual + Antigüedad
mod <- glm(form, data = train, family = binomial())
summary(mod)
##
## Call:
## glm(formula = form, family = binomial(), data = train)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -0.4720962 0.3501646 -1.348 0.17759
## Horas_ExtraSi 1.7005677 0.2106426 8.073 6.85e-16 ***
## Equilibrio_Trabajo_Vida.L -0.9706096 0.3349008 -2.898 0.00375 **
## Equilibrio_Trabajo_Vida.Q 0.5929861 0.2746021 2.159 0.03082 *
## Equilibrio_Trabajo_Vida.C -0.0805798 0.1954857 -0.412 0.68019
## Estado_CivilDivorciado -0.5068281 0.3174625 -1.596 0.11038
## Estado_CivilSoltero 0.9716104 0.2225038 4.367 1.26e-05 ***
## Satisfación_Laboral -0.4135609 0.0917855 -4.506 6.61e-06 ***
## Ingreso_Mensual -0.0001021 0.0000339 -3.011 0.00260 **
## Antigüedad -0.0326126 0.0236644 -1.378 0.16816
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 778.14 on 880 degrees of freedom
## Residual deviance: 630.52 on 871 degrees of freedom
## AIC: 650.52
##
## Number of Fisher Scoring iterations: 5
# Chequeo rápido de multicolinealidad (si 'car' está disponible)
if (requireNamespace("car", quietly = TRUE)) {
vif_vals <- car::vif(mod)
print(vif_vals)
cat("VIF máximo:", round(max(vif_vals), 2), "\n")
}
## GVIF Df GVIF^(1/(2*Df))
## Horas_Extra 1.072495 1 1.035613
## Equilibrio_Trabajo_Vida 1.030890 3 1.005083
## Estado_Civil 1.062364 2 1.015239
## Satisfación_Laboral 1.049526 1 1.024464
## Ingreso_Mensual 1.327795 1 1.152300
## Antigüedad 1.312828 1 1.145787
## VIF máximo: 3
coefs <- broom::tidy(mod) %>%
mutate(OR = exp(estimate),
IC_inf = exp(estimate - 1.96 * std.error),
IC_sup = exp(estimate + 1.96 * std.error),
Sig = ifelse(p.value < 0.05, "✓", ""))
# Nota de interpretación para el lector:
# Equilibrio_Trabajo_Vida es ordinal y, por defecto, usa contrastes polinomiales.
# En los nombres de términos: .L = tendencia lineal y .Q = tendencia cuadrática.
knitr::kable(coefs %>% select(term, estimate, OR, IC_inf, IC_sup, p.value, Sig),
digits = 3, caption = "Coeficientes del logit, OR e IC 95%")
Coeficientes del logit, OR e IC 95%
| (Intercept) |
-0.472 |
0.624 |
0.314 |
1.239 |
0.178 |
|
| Horas_ExtraSi |
1.701 |
5.477 |
3.624 |
8.277 |
0.000 |
✓ |
| Equilibrio_Trabajo_Vida.L |
-0.971 |
0.379 |
0.197 |
0.730 |
0.004 |
✓ |
| Equilibrio_Trabajo_Vida.Q |
0.593 |
1.809 |
1.056 |
3.099 |
0.031 |
✓ |
| Equilibrio_Trabajo_Vida.C |
-0.081 |
0.923 |
0.629 |
1.353 |
0.680 |
|
| Estado_CivilDivorciado |
-0.507 |
0.602 |
0.323 |
1.122 |
0.110 |
|
| Estado_CivilSoltero |
0.972 |
2.642 |
1.708 |
4.087 |
0.000 |
✓ |
| Satisfación_Laboral |
-0.414 |
0.661 |
0.552 |
0.792 |
0.000 |
✓ |
| Ingreso_Mensual |
0.000 |
1.000 |
1.000 |
1.000 |
0.003 |
✓ |
| Antigüedad |
-0.033 |
0.968 |
0.924 |
1.014 |
0.168 |
|
# Significancia global
lr_chi <- mod$null.deviance - mod$deviance
lr_p <- pchisq(lr_chi, mod$df.null - mod$df.residual, lower.tail = FALSE)
cat("LR χ² =", round(lr_chi, 3), "| p =", format(lr_p, scientific = TRUE), "\n")
## LR χ² = 147.622 | p = 2.740858e-27
9 Evaluación (ROC/AUC y
matrices)
# 8) Evaluación (ROC/AUC y matrices de confusión)
# • Técnica:
# • Curva ROC y AUC para evaluar poder discriminante.
# • Matrices de confusión en corte 0.5 y en corte óptimo (Youden).
# • Resultado:
# • AUC = 0.747 (modelo aceptable).
# • Corte óptimo mejora sensibilidad/especificidad frente al umbral fijo 0.5.
# El modelo predice razonablemente la rotación y permite ajustar decisiones según tolerancia a falsos positivos/negativos.
prob_test <- predict(mod, newdata = test, type = "response")
roc_obj <- pROC::roc(test$y, prob_test, quiet = TRUE)
auc_val <- pROC::auc(roc_obj)
old_par <- par(no.readonly = TRUE)
par(mar = c(5, 5, 4.5, 2)) # márgenes: abajo, izq, arriba, der
plot(roc_obj, main = paste("Curva ROC | AUC =", round(as.numeric(auc_val), 3)),
col = "blue", lwd = 2); abline(a = 0, b = 1, lty = 2, col = "red")
par(old_par)
metricas_desde_cm <- function(obs, pred_bin){
cm <- table(Real = factor(obs, levels = c(0,1), labels = c("No","Sí")),
Pred = factor(pred_bin, levels = c(0,1), labels = c("No","Sí")))
TN <- cm[1,1]; FP <- cm[1,2]; FN <- cm[2,1]; TP <- cm[2,2]
exact <- (TP + TN)/sum(cm)
sens <- TP/(TP+FN); espec <- TN/(TN+FP)
prec <- TP/(TP+FP); npv <- TN/(TN+FN)
list(cm=cm, exact=exact, sens=sens, espec=espec, prec=prec, npv=npv)
}
pred05 <- ifelse(prob_test >= 0.5, 1, 0); m05 <- metricas_desde_cm(test$y, pred05)
knitr::kable(m05$cm, caption = "Matriz de confusión (corte 0.5)")
Matriz de confusión (corte 0.5)
| No |
479 |
15 |
| Sí |
70 |
25 |
umbral <- pROC::coords(roc_obj, "best", best.method = "youden")$threshold
cat("Umbral óptimo (Youden):", round(umbral, 3), "\n")
## Umbral óptimo (Youden): 0.184
pred_opt <- ifelse(prob_test >= umbral, 1, 0); mopt <- metricas_desde_cm(test$y, pred_opt)
knitr::kable(mopt$cm, caption = paste0("Matriz de confusión (umbral óptimo=", round(umbral,3), ")"))
Matriz de confusión (umbral óptimo=0.184)
| No |
384 |
110 |
| Sí |
33 |
62 |

10 Predicción
individual
# 9) Predicción individual
# • Técnica: simulación de un caso hipotético (empleado con horas extra, bajo equilibrio, soltero, baja satisfacción). Se calcula la probabilidad de rotación y se toma una decisión con base en el umbral óptimo.
# • Resultado: el modelo devuelve una probabilidad alta y la decisión “INTERVENIR”. Esto ejemplifica cómo usar el modelo en la práctica de RR.HH.
empleado <- data.frame(
Horas_Extra = factor("Si", levels = levels(train$Horas_Extra)),
Equilibrio_Trabajo_Vida = ordered("1", levels = levels(train$Equilibrio_Trabajo_Vida)),
Estado_Civil = factor("Soltero", levels = levels(train$Estado_Civil)),
Satisfación_Laboral = 2,
Ingreso_Mensual = median(train$Ingreso_Mensual),
Antigüedad = 1
)
prob_emp <- predict(mod, newdata = empleado, type = "response")
decision <- ifelse(prob_emp >= umbral, "INTERVENIR", "Monitoreo")
cat("Probabilidad de rotación:", round(prob_emp,3), " (", round(prob_emp*100,1), "%)\n",
"Umbral óptimo:", round(umbral,3), "-> Decisión:", decision, "\n")
## Probabilidad de rotación: 0.858 ( 85.8 %)
## Umbral óptimo: 0.184 -> Decisión: INTERVENIR
11 Conclusiones
# 10 Conclusiones
# 1. Comparación de signos esperados vs. estimados
# Cuando miramos los coeficientes del modelo, la mayoría se comportaron como lo
# habíamos anticipado en la Tabla 1.1. Por ejemplo:
# • Horas extra (H1): el coeficiente salió positivo (OR > 1), lo que confirma
# que trabajar más horas está ligado a un mayor riesgo de rotación.
# • Equilibrio trabajo–vida (H2): menor equilibrio también mostró una relación
# positiva con rotación, en línea con la hipótesis.
# • Estado civil (H3): ser soltero se asoció con más rotación comparado con
# casados, lo que también coincide con lo esperado.
# • Satisfacción laboral (H4): coeficiente negativo (OR < 1), lo cual confirma
# que a mayor satisfacción, menor rotación.
# • Ingreso mensual (H5): mayor ingreso reduce la probabilidad de rotación.
# • Antigüedad (H6): la señal fue negativa, pero sin significancia estadística,
# es decir, no se puede concluir que la antigüedad sea un factor determinante.
# En resumen, se confirma la lógica de las hipótesis H1–H5. H6 queda débil.
# 2. Variables significativas (p < 0.05)
# Los resultados muestran que las variables Horas extra, Equilibrio
# trabajo–vida, Estado civil, Satisfacción laboral e Ingreso mensual sí influyen
# de manera estadísticamente significativa. Esto significa que no es casualidad:
# realmente aportan en explicar por qué la gente rota.
# 3. Interpretación con OR (Odds Ratio)
# El OR nos dice cuánto cambia el riesgo de rotación con cada variable.
# Por ejemplo:
# • Horas extra: OR ≈ 5.5 → quienes hacen horas extra tienen 5 veces más
# probabilidad de rotar.
# • Satisfacción laboral: OR < 1 → cada punto extra en satisfacción baja
# claramente el riesgo.
# • Ingreso mensual: aunque cercano a 1, su efecto es protector, porque
# ingresos más altos hacen que la gente se quede.
# 4. AUC como medida de discriminación
# El modelo logró un AUC ≈ 0.75, lo que se considera un poder predictivo
# aceptable (ni pobre ni excelente, está en un nivel útil). Esto significa que
# el modelo puede distinguir en un 75% de los casos si un empleado rotará o no.
# 5. Matrices de confusión y trade-off
# Con el corte estándar (0.5) se gana en especificidad (detecta bien a los
# que se quedan), pero se pierde sensibilidad (no detecta todos los que rotan).
# Ajustando el umbral con el criterio de Youden, se logra un mejor balance,
# sacrificando un poco de un lado para ganar del otro.
# Estrategias prácticas (basadas en los hallazgos)
# 1. Limitar las horas extra (H1)
# El desgaste físico y emocional por trabajar demasiado es uno de los factores
# más claros de rotación. Una estrategia es poner topes a las horas extras,
# rotar tareas críticas o compensar con descansos efectivos.
# 2. Reforzar la satisfacción laboral (H4)
# La satisfacción salió como variable clave y protectora. Aquí Recursos Humanos
# puede trabajar en:
# • Programas de feedback continuo.
# • Reconocimiento y recompensas.
# • Planes de carrera claros para que los empleados vean futuro en la empresa.
# 3. Cuidar el onboarding y la antigüedad (H6, aunque no significativa)
# Aunque la antigüedad no fue concluyente estadísticamente, sí es lógico que
# empleados nuevos sean más vulnerables a irse. Por eso, reforzar los primeros
# meses con programas de mentoría, acompañamiento cercano y capacitación puede
# ayudar a retenerlos.
# En resumen:
# El modelo confirma que la rotación no es al azar. Hay patrones claros: horas
# extra, satisfacción laboral, equilibrio vida-trabajo, estado civil e ingreso
# mensual explican buena parte del fenómeno. Con estas palancas se pueden
# diseñar políticas de retención más efectivas y reducir la rotación de forma
# medible.
# Limitaciones y siguientes pasos
# - Clase minoritaria (16%): considerar weights en glm o técnicas como SMOTE para robustez.
# - Validación cruzada para estabilidad del AUC y selección de variables.
# - Calibración de probabilidades (Platt/Isotónica) si el score se usará para umbrales operativos.
sessionInfo()