Base analizada: dataset rotacion del
paquete MODELOS
Propósito del informe: identificar los factores asociados a la rotación, cuantificar su efecto, evaluar la capacidad predictiva del modelo y proponer un criterio operativo de intervención.
La tasa de rotación observada fue de 16.1% (237 de 1,470 empleados). Se seleccionaron tres variables categóricas (Horas_Extra, Departamento y Estado_Civil) y tres cuantitativas (Edad, Ingreso_Mensual y Antigüedad_Cargo) por su relación teórica con carga laboral, contexto organizacional, estabilidad personal, compensación y permanencia en el rol.
En el análisis bivariado, todas las variables mostraron asociación estadísticamente significativa con la rotación. En el modelo logístico multivariado, las covariables con evidencia más sólida fueron Horas_Extra, DepartamentoVentas, Estado_CivilSoltero, Edad, Ingreso_Mensual y Antigüedad_Cargo.
El factor de mayor peso fue realizar horas extra (OR = 4.39). El modelo alcanzó un AUC = 0.772, valor que indica capacidad de discriminación aceptable. Para fines de gestión preventiva se propone un punto de corte de 0.25, ya que ofrece un mejor equilibrio entre sensibilidad y especificidad que el umbral tradicional de 0.50.
La rotación de personal es uno de los principales desafíos en la gestión del talento humano, ya que impacta directamente la estabilidad organizacional, los costos de contratación y la continuidad de los procesos. Comprender los factores asociados a este fenómeno permite a las organizaciones diseñar estrategias preventivas orientadas a la retención del talento clave.
En este estudio se analiza un conjunto de datos históricos de empleados que incluye variables demográficas, laborales y organizacionales. El objetivo principal es identificar los factores asociados a la rotación de personal y construir un modelo de regresión logística binaria que permita estimar la probabilidad de que un empleado presente rotación laboral.
A partir de este modelo, la organización podrá anticipar comportamientos de riesgo, identificar perfiles con alta probabilidad de rotación y tomar decisiones estratégicas basadas en evidencia, con el fin de mejorar la estabilidad laboral y la gestión del talento humano.
En esta sección se realiza la importación del conjunto de datos “rotacion” desde el paquete proporcionado, con el fin de iniciar el proceso de análisis exploratorio y modelamiento estadístico.
data("rotacion")
glimpse(rotacion)
## Rows: 1,470
## Columns: 24
## $ Rotación <chr> "Si", "No", "Si", "No", "No", "No", "No", …
## $ Edad <dbl> 41, 49, 37, 33, 27, 32, 59, 30, 38, 36, 35…
## $ `Viaje de Negocios` <chr> "Raramente", "Frecuentemente", "Raramente"…
## $ Departamento <chr> "Ventas", "IyD", "IyD", "IyD", "IyD", "IyD…
## $ Distancia_Casa <dbl> 1, 8, 2, 3, 2, 2, 3, 24, 23, 27, 16, 15, 2…
## $ Educación <dbl> 2, 1, 2, 4, 1, 2, 3, 1, 3, 3, 3, 2, 1, 2, …
## $ Campo_Educación <chr> "Ciencias", "Ciencias", "Otra", "Ciencias"…
## $ Satisfacción_Ambiental <dbl> 2, 3, 4, 4, 1, 4, 3, 4, 4, 3, 1, 4, 1, 2, …
## $ Genero <chr> "F", "M", "M", "F", "M", "M", "F", "M", "M…
## $ Cargo <chr> "Ejecutivo_Ventas", "Investigador_Cientifi…
## $ Satisfación_Laboral <dbl> 4, 2, 3, 3, 2, 4, 1, 3, 3, 3, 2, 3, 3, 4, …
## $ Estado_Civil <chr> "Soltero", "Casado", "Soltero", "Casado", …
## $ Ingreso_Mensual <dbl> 5993, 5130, 2090, 2909, 3468, 3068, 2670, …
## $ Trabajos_Anteriores <dbl> 8, 1, 6, 1, 9, 0, 4, 1, 0, 6, 0, 0, 1, 0, …
## $ Horas_Extra <chr> "Si", "No", "Si", "Si", "No", "No", "Si", …
## $ Porcentaje_aumento_salarial <dbl> 11, 23, 15, 11, 12, 13, 20, 22, 21, 13, 13…
## $ Rendimiento_Laboral <dbl> 3, 4, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, …
## $ Años_Experiencia <dbl> 8, 10, 7, 8, 6, 8, 12, 1, 10, 17, 6, 10, 5…
## $ Capacitaciones <dbl> 0, 3, 3, 3, 3, 2, 3, 2, 2, 3, 5, 3, 1, 2, …
## $ Equilibrio_Trabajo_Vida <dbl> 1, 3, 3, 3, 3, 2, 2, 3, 3, 2, 3, 3, 2, 3, …
## $ Antigüedad <dbl> 6, 10, 0, 8, 2, 7, 1, 1, 9, 7, 5, 9, 5, 2,…
## $ Antigüedad_Cargo <dbl> 4, 7, 0, 7, 2, 7, 0, 0, 7, 7, 4, 5, 2, 2, …
## $ Años_ultima_promoción <dbl> 0, 1, 0, 3, 2, 3, 0, 0, 1, 7, 0, 0, 4, 1, …
## $ Años_acargo_con_mismo_jefe <dbl> 5, 7, 0, 0, 2, 6, 0, 0, 8, 7, 3, 8, 3, 2, …
tabla <- data.frame(
Variable = c("Rendimiento_Laboral", "Distancia_Casa", "Educación",
"Satisfacción_Ambiental", "Satisfación_Laboral",
"Trabajos_Anteriores", "Equilibrio_Trabajo_Vida"),
Descripcion = c(
"1 = bajo; 2 = medio; 3 = alto; 4 = muy alto",
"Kilómetros desde la casa hasta el lugar de trabajo",
"1 = primaria; 2 = secundaria; 3 = técnico/tecnólogo; 4 = pregrado; 5 = posgrado",
"1 = muy insatisfecho; 2 = insatisfecho; 3 = satisfecho; 4 = muy satisfecho",
"1 = muy insatisfecho; 2 = insatisfecho; 3 = satisfecho; 4 = muy satisfecho",
"Número de trabajos antes de ingresar a la empresa",
"1 = muy bajo; 2 = bajo; 3 = medio; 4 = alto"
)
)
library(knitr)
library(kableExtra)
kable(tabla,
caption = "Descripción de variables",
booktabs = TRUE) %>%
kable_styling(
full_width = FALSE,
position = "center",
latex_options = "hold_position"
) %>%
column_spec(1, width = "5cm") %>%
column_spec(2, width = "8cm")
| Variable | Descripcion |
|---|---|
| Rendimiento_Laboral | 1 = bajo; 2 = medio; 3 = alto; 4 = muy alto |
| Distancia_Casa | Kilómetros desde la casa hasta el lugar de trabajo |
| Educación | 1 = primaria; 2 = secundaria; 3 = técnico/tecnólogo; 4 = pregrado; 5 = posgrado |
| Satisfacción_Ambiental | 1 = muy insatisfecho; 2 = insatisfecho; 3 = satisfecho; 4 = muy satisfecho |
| Satisfación_Laboral | 1 = muy insatisfecho; 2 = insatisfecho; 3 = satisfecho; 4 = muy satisfecho |
| Trabajos_Anteriores | Número de trabajos antes de ingresar a la empresa |
| Equilibrio_Trabajo_Vida | 1 = muy bajo; 2 = bajo; 3 = medio; 4 = alto |
Se escogieron seis covariables: tres categóricas y tres cuantitativas. La selección busca cubrir dimensiones complementarias del problema: sobrecarga laboral, área organizacional, estabilidad personal, experiencia, nivel salarial y consolidación en el cargo.
tabla_hipotesis <- data.frame(
Variable = c("Horas_Extra", "Departamento", "Estado_Civil",
"Edad", "Ingreso_Mensual", "Antigüedad_Cargo"),
Tipo = c("Categórica", "Categórica", "Categórica",
"Cuantitativa", "Cuantitativa", "Cuantitativa"),
Hipotesis = c(
"Los empleados con horas extra tendrán mayor probabilidad de rotación por mayor carga y desgaste laboral.",
"Se espera mayor rotación en áreas con mayor presión operativa o comercial, particularmente Ventas.",
"Se espera mayor rotación en empleados solteros, dado que suelen tener mayor flexibilidad para cambiar de empleo.",
"Los empleados más jóvenes tenderán a rotar más en búsqueda de mejores oportunidades o ajuste de carrera.",
"Un mayor ingreso mensual reducirá la probabilidad de rotación al mejorar la percepción de recompensa.",
"Una mayor permanencia en el cargo reflejará ajuste y estabilidad, por lo que debería disminuir la rotación."
),
Relacion = c("Positiva",
"Positiva en Ventas/RH frente a IYD",
"Positiva para solteros",
"Negativa",
"Negativa",
"Negativa")
)
library(knitr)
library(kableExtra)
kable(tabla_hipotesis,
caption = "Variables e hipótesis",
booktabs = TRUE) %>%
kable_styling(
full_width = FALSE,
position = "center",
latex_options = c("hold_position", "scale_down")
) %>%
column_spec(1, latex_column_spec = "p{3cm}") %>% # Variable
column_spec(2, latex_column_spec = "p{2.5cm}") %>% # Tipo
column_spec(3, latex_column_spec = "p{7cm}") %>% # Hipótesis (principal)
column_spec(4, latex_column_spec = "p{2.5cm}") # Relación
| Variable | Tipo | Hipotesis | Relacion |
|---|---|---|---|
| Horas_Extra | Categórica | Los empleados con horas extra tendrán mayor probabilidad de rotación por mayor carga y desgaste laboral. | Positiva |
| Departamento | Categórica | Se espera mayor rotación en áreas con mayor presión operativa o comercial, particularmente Ventas. | Positiva en Ventas/RH frente a IYD |
| Estado_Civil | Categórica | Se espera mayor rotación en empleados solteros, dado que suelen tener mayor flexibilidad para cambiar de empleo. | Positiva para solteros |
| Edad | Cuantitativa | Los empleados más jóvenes tenderán a rotar más en búsqueda de mejores oportunidades o ajuste de carrera. | Negativa |
| Ingreso_Mensual | Cuantitativa | Un mayor ingreso mensual reducirá la probabilidad de rotación al mejorar la percepción de recompensa. | Negativa |
| Antigüedad_Cargo | Cuantitativa | Una mayor permanencia en el cargo reflejará ajuste y estabilidad, por lo que debería disminuir la rotación. | Negativa |
El análisis univariado se realizó respetando el tipo de variable. Para la variable respuesta y las variables categóricas se reportaron frecuencias y porcentajes; para las cuantitativas se utilizaron medidas de tendencia central y dispersión. Este paso permite caracterizar la muestra antes de evaluar asociaciones.
Para las variables cualitativas se emplearon tablas de frecuencia absoluta y relativa, mientras que para las cuantitativas se reportaron medidas de tendencia central y dispersión. Esta caracterización permite describir la muestra y anticipar patrones que luego serán contrastados formalmente en el análisis bivariado y multivariado.
La base presenta 1,233 empleados sin rotación (83.9%) y 237 con rotación (16.1%). Esto implica que el evento de interés es minoritario, situación que debe considerarse al definir el punto de corte del modelo, ya que umbrales altos pueden reducir en exceso la sensibilidad.
library(scales)
rotacion$Rotación <- factor(rotacion$Rotación, levels = c("No", "Si"))
ggplot(rotacion, aes(x = Rotación, fill = Rotación)) +
geom_bar(width = 0.6, show.legend = FALSE) +
geom_text(
stat = "count",
aes(label = paste0(..count..,
"\n(",
scales::percent(..count../sum(..count..)),
")")),
vjust = -0.4,
size = 3.5
) +
scale_fill_manual(values = c("No" = "#2C3E50", "Si" = "#C0392B")) +
scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
labs(
x = "Rotación",
y = "Número de empleados"
) +
theme_minimal(base_size = 11) +
theme(
plot.title = element_blank(),
axis.title = element_text(face = "bold"),
panel.grid.major.x = element_blank()
)
Distribución de la rotación de cargo
tabla_cuant <- rotacion %>%
summarise(
Edad_media = mean(Edad, na.rm = TRUE),
Edad_mediana = median(Edad, na.rm = TRUE),
Edad_sd = sd(Edad, na.rm = TRUE),
Edad_min = min(Edad, na.rm = TRUE),
Edad_max = max(Edad, na.rm = TRUE),
Ingreso_media = mean(Ingreso_Mensual, na.rm = TRUE),
Ingreso_mediana = median(Ingreso_Mensual, na.rm = TRUE),
Ingreso_sd = sd(Ingreso_Mensual, na.rm = TRUE),
Ingreso_min = min(Ingreso_Mensual, na.rm = TRUE),
Ingreso_max = max(Ingreso_Mensual, na.rm = TRUE),
Antig_media = mean(Antigüedad_Cargo, na.rm = TRUE),
Antig_mediana = median(Antigüedad_Cargo, na.rm = TRUE),
Antig_sd = sd(Antigüedad_Cargo, na.rm = TRUE),
Antig_min = min(Antigüedad_Cargo, na.rm = TRUE),
Antig_max = max(Antigüedad_Cargo, na.rm = TRUE)
)
tabla_final <- data.frame(
Variable = c("Edad", "Ingreso_Mensual", "Antigüedad_Cargo"),
Media = c(tabla_cuant$Edad_media,
tabla_cuant$Ingreso_media,
tabla_cuant$Antig_media),
Mediana = c(tabla_cuant$Edad_mediana,
tabla_cuant$Ingreso_mediana,
tabla_cuant$Antig_mediana),
DE = c(tabla_cuant$Edad_sd,
tabla_cuant$Ingreso_sd,
tabla_cuant$Antig_sd),
Mínimo = c(tabla_cuant$Edad_min,
tabla_cuant$Ingreso_min,
tabla_cuant$Antig_min),
Máximo = c(tabla_cuant$Edad_max,
tabla_cuant$Ingreso_max,
tabla_cuant$Antig_max)
)
# Formato numérico bonito
tabla_final[, -1] <- lapply(tabla_final[, -1], function(x) round(x, 2))
kable(tabla_final,
caption = "Estadísticos descriptivos de variables cuantitativas",
align = "l",
booktabs = TRUE) %>%
kable_styling(
full_width = FALSE,
position = "center",
latex_options = c("striped", "hold_position")
) %>%
row_spec(0, bold = TRUE, align = "c")
| Variable | Media | Mediana | DE | Mínimo | Máximo |
|---|---|---|---|---|---|
| Edad | 36.92 | 36 | 9.14 | 18 | 60 |
| Ingreso_Mensual | 6502.93 | 4919 | 4707.96 | 1009 | 19999 |
| Antigüedad_Cargo | 4.23 | 3 | 3.62 | 0 | 18 |
La edad se concentra alrededor de los 37 años y presenta una dispersión moderada, suficiente para esperar diferencias entre grupos de empleados.
El ingreso mensual es la variable más dispersa; además, su media supera con amplitud a la mediana, lo que sugiere heterogeneidad salarial y posible asimetría hacia ingresos altos.
La antigüedad en el cargo muestra una mediana de 3 años, por lo que una fracción importante de la planta se encuentra todavía en etapas tempranas de consolidación en su rol actual.
vars_cat <- c("Horas_Extra", "Departamento", "Estado_Civil")
tabla_categoricas <- rotacion %>%
select(all_of(vars_cat)) %>%
tidyr::pivot_longer(
cols = everything(),
names_to = "Variable",
values_to = "Categoria"
) %>%
group_by(Variable, Categoria) %>%
summarise(Frecuencia = n(), .groups = "drop") %>%
group_by(Variable) %>%
mutate(Porcentaje = Frecuencia / sum(Frecuencia)) %>%
ungroup() %>%
mutate(Porcentaje = scales::percent(Porcentaje, accuracy = 0.1))
kable(tabla_categoricas,
caption = "Distribución de variables categóricas",
booktabs = TRUE) %>%
kable_styling(
full_width = FALSE,
position = "center",
latex_options = "hold_position"
) %>%
column_spec(1, width = "4cm") %>%
column_spec(2, width = "4cm") %>%
column_spec(3, width = "3cm") %>%
column_spec(4, width = "3cm")
| Variable | Categoria | Frecuencia | Porcentaje |
|---|---|---|---|
| Departamento | IyD | 961 | 65.4% |
| Departamento | RH | 63 | 4.3% |
| Departamento | Ventas | 446 | 30.3% |
| Estado_Civil | Casado | 673 | 45.8% |
| Estado_Civil | Divorciado | 327 | 22.2% |
| Estado_Civil | Soltero | 470 | 32.0% |
| Horas_Extra | No | 1054 | 71.7% |
| Horas_Extra | Si | 416 | 28.3% |
La mayoría de empleados no realiza horas extra, aunque 28.3% sí lo hace, proporción suficiente para analizar su asociación con la rotación.
IYD concentra casi dos tercios de la planta, mientras que Ventas representa cerca de una tercera parte; RH tiene un peso bajo dentro del total.
Predominan los empleados casados y solteros, lo que hace pertinente examinar si la estabilidad familiar se relaciona con decisiones de permanencia.
En el caso de las variables cuantitativas se utilizó la prueba t de Welch, ya que no exige igualdad de varianzas entre los grupos comparados y resulta apropiada en contextos con tamaños muestrales desbalanceados. Para las variables categóricas se aplicó la prueba Chi-cuadrado de independencia, con el fin de evaluar si la distribución de la rotación cambia entre categorías. La comparación se realizó siempre contra la variable respuesta y = 1 (rotación) frente a y = 0 (no rotación).
vars_num <- c("Edad", "Ingreso_Mensual", "Antigüedad_Cargo")
tabla_welch <- lapply(vars_num, function(v) {
test <- t.test(rotacion[[v]] ~ rotacion$Rotación)
data.frame(
Variable = v,
Media_No = mean(rotacion[[v]][rotacion$Rotación == "No"], na.rm = TRUE),
Media_Si = mean(rotacion[[v]][rotacion$Rotación == "Si"], na.rm = TRUE),
Diferencia = mean(rotacion[[v]][rotacion$Rotación == "No"], na.rm = TRUE) -
mean(rotacion[[v]][rotacion$Rotación == "Si"], na.rm = TRUE),
t_Welch = unname(test$statistic),
p_valor = test$p.value
)
}) %>%
dplyr::bind_rows()
tabla_welch_formato <- tabla_welch %>%
dplyr::mutate(
Media_No = round(Media_No, 2),
Media_Si = round(Media_Si, 2),
Diferencia = round(Diferencia, 2),
t_Welch = round(t_Welch, 2),
p_valor = ifelse(p_valor < 0.001, "<0.001", round(p_valor, 3))
)
kable(tabla_welch_formato,
caption = "Comparación de medias entre grupos (t de Welch)",
booktabs = TRUE) %>%
kable_styling(
full_width = FALSE,
position = "center",
latex_options = "hold_position"
)
| Variable | Media_No | Media_Si | Diferencia | t_Welch | p_valor |
|---|---|---|---|---|---|
| Edad | 37.56 | 33.61 | 3.95 | 5.83 | <0.001 |
| Ingreso_Mensual | 6832.74 | 4787.09 | 2045.65 | 7.48 | <0.001 |
| Antigüedad_Cargo | 4.48 | 2.90 | 1.58 | 6.85 | <0.001 |
Edad: los empleados que rotan son más jóvenes en promedio (33.61 años) que quienes permanecen (37.56 años). La evidencia respalda la hipótesis de relación negativa entre edad y rotación.
Ingreso mensual: quienes rotan presentan ingresos menores (4,787 frente a 6,833). Esto confirma la hipótesis de que una mejor compensación tiene un efecto protector.
Antigüedad en el cargo: los empleados que rotan acumulan menos tiempo en su rol (2.90 años frente a 4.48). La evidencia es consistente con una menor consolidación en el cargo.
vars_cat <- c("Horas_Extra", "Departamento", "Estado_Civil")
tabla_chi <- lapply(vars_cat, function(v) {
tab <- table(rotacion[[v]], rotacion$Rotación)
chi <- chisq.test(tab)
df <- as.data.frame(tab)
colnames(df) <- c("Categoria", "Rotación", "n")
# calcular proporción de rotación por categoría
df <- df %>%
group_by(Categoria) %>%
mutate(
total = sum(n),
pct_rotacion = n[Rotación == "Si"] / total
) %>%
ungroup() %>%
filter(Rotación == "Si") %>% # solo dejamos fila "Si"
mutate(
Variable = v,
Chi2 = chi$statistic,
p_valor = chi$p.value
) %>%
select(Variable, Categoria, n, pct_rotacion, Chi2, p_valor)
return(df)
}) %>% bind_rows()
tabla_chi_formato <- tabla_chi %>%
mutate(
pct_rotacion = percent(pct_rotacion, accuracy = 0.1),
Chi2 = round(Chi2, 2),
p_valor = ifelse(p_valor < 0.001, "<0.001", round(p_valor, 4))
)
if (knitr::is_latex_output()) {
kable(tabla_chi_formato,
col.names = c("Variable", "Categoría", "n", "% Rotación", "Chi²", "p-valor"),
caption = "Asociación entre variables categóricas y rotación (Chi-cuadrado)",
booktabs = TRUE) %>%
kable_styling(
latex_options = c("hold_position", "scale_down"),
font_size = 10
)
} else {
kable(tabla_chi_formato,
col.names = c("Variable", "Categoría", "n", "% Rotación", "Chi²", "p-valor"),
caption = "Asociación entre variables categóricas y rotación (Chi-cuadrado)",
booktabs = TRUE) %>%
kable_styling(
full_width = FALSE,
position = "center",
font_size = 12
)
}
| Variable | Categoría | n | % Rotación | Chi² | p-valor |
|---|---|---|---|---|---|
| Horas_Extra | No | 110 | 10.4% | 87.56 | <0.001 |
| Horas_Extra | Si | 127 | 30.5% | 87.56 | <0.001 |
| Departamento | IyD | 133 | 13.8% | 10.80 | 0.0045 |
| Departamento | RH | 12 | 19.0% | 10.80 | 0.0045 |
| Departamento | Ventas | 92 | 20.6% | 10.80 | 0.0045 |
| Estado_Civil | Casado | 84 | 12.5% | 46.16 | <0.001 |
| Estado_Civil | Divorciado | 33 | 10.1% | 46.16 | <0.001 |
| Estado_Civil | Soltero | 120 | 25.5% | 46.16 | <0.001 |
Horas extra es la asociación bivariada más fuerte: la rotación sube de 10.4% a 30.5% entre quienes sí realizan horas extra.
Por departamento, Ventas presenta la mayor tasa de rotación observada (20.6%), seguido por RH (19.0%), ambos por encima de IYD (13.8%).
En estado civil, los empleados solteros muestran la mayor rotación (25.5%), casi el doble de los casados (12.5%).
tabla_sintesis <- data.frame(
Variable = c("Horas_Extra", "Departamento", "Estado_Civil",
"Edad", "Ingreso_Mensual", "Antigüedad_Cargo"),
Direccion_esperada = c("Positiva", "Mayor en Ventas/RH", "Mayor en solteros",
"Negativa", "Negativa", "Negativa"),
Evidencia_observada = c(
"Mayor rotación en empleados con horas extra",
"Ventas y RH presentan mayor tasa de rotación",
"Solteros muestran mayor rotación",
"Rotan empleados más jóvenes",
"Rotan empleados con menor ingreso",
"Rotan empleados con menor antigüedad"
),
Conclusion = rep("Hipótesis respaldada", 6)
)
# WRAP
tabla_sintesis$Evidencia_observada <- str_wrap(tabla_sintesis$Evidencia_observada, 35)
tabla_sintesis$Direccion_esperada <- str_wrap(tabla_sintesis$Direccion_esperada, 20)
# TABLA
kable(tabla_sintesis,
caption = "Síntesis de hallazgos frente a las hipótesis",
booktabs = TRUE) %>%
kable_styling(
latex_options = c("hold_position", "scale_down"),
position = "center",
font_size = 11
) %>%
column_spec(1, width = "3.5cm") %>%
column_spec(2, width = "3cm") %>%
column_spec(3, width = "5cm") %>%
column_spec(4, width = "2.5cm")
| Variable | Direccion_esperada | Evidencia_observada | Conclusion |
|---|---|---|---|
| Horas_Extra | Positiva | Mayor rotación en empleados con horas extra | |Hipótesis respaldada |
| Departamento | Mayor en Ventas/RH | Ventas y RH presentan mayor tasa de rotación | |Hipótesis respaldada |
| Estado_Civil | Mayor en solteros | Solteros muestran mayor rotación | Hipótesis respaldada |
| Edad | Negativa | Rotan empleados más jóvenes | Hipótesis respaldada |
| Ingreso_Mensual | Negativa | Rotan empleados con menor ingreso | Hipótesis respaldada |
| Antigüedad_Cargo | Negativa | Rotan empleados con menor antigüedad | |Hipótesis respaldada |
En las variables categóricas la prueba Chi-cuadrado evidencia asociación, pero no produce un coeficiente con signo. Por ello, la dirección del efecto se interpreta a partir de las tasas observadas y se formaliza en el modelo logístico multivariado de la siguiente sección.
El modelo logístico binario estimado toma la forma general:
\[ \begin{aligned} \text{logit}\big(P(Y_i = 1)\big) &= \beta_0 \\ &+ \beta_1(\text{Horas\_Extra}) \\ &+ \beta_2(\text{Departamento}) \\ &+ \beta_3(\text{Estado\_Civil}) \\ &+ \beta_4(\text{Edad}) \\ &+ \beta_5(\text{Ingreso\_Mensual}) \\ &+ \beta_6(\text{Antigüedad\_Cargo}) \end{aligned} \]
donde \(p_i = P(Y_i = 1)\) representa la probabilidad de rotación del empleado \(i\). Este enfoque permite modelar una variable dependiente binaria y cuantificar el efecto de cada covariable sobre los log-odds de rotación.
Las categorías de referencia utilizadas fueron: No para Horas_Extra, I&D para Departamento y Casado para Estado_Civil.
La prueba global del modelo resultó estadísticamente significativa (razón de verosimilitud, \(p < 0.001\)) y el pseudo \(R^2\) de McFadden fue de 0.163, lo que indica que el conjunto de covariables aporta información relevante para explicar la rotación.
donde \(p_i = P(Y_i = 1)\) representa la probabilidad de rotación del empleado \(i\). Este enfoque permite modelar una variable dependiente binaria y cuantificar el efecto de cada covariable sobre los log-odds de rotación.
Las categorías de referencia utilizadas fueron: No para Horas_Extra, I&D para Departamento y Casado para Estado_Civil.
La prueba global del modelo resultó estadísticamente significativa (razón de verosimilitud, \(p < 0.001\)) y el pseudo \(R^2\) de McFadden fue de 0.163, lo que indica que el conjunto de covariables aporta información relevante para explicar la rotación.
rotacion$Rotación <- factor(rotacion$Rotación, levels = c("No", "Si"))
rotacion$Horas_Extra <- factor(rotacion$Horas_Extra)
rotacion$Departamento <- factor(rotacion$Departamento)
rotacion$Estado_Civil <- factor(rotacion$Estado_Civil)
modelo <- glm(
Rotación ~ Horas_Extra + Departamento + Estado_Civil +
Edad + Ingreso_Mensual + Antigüedad_Cargo,
data = rotacion,
family = binomial
)
tabla_logit <- tidy(modelo, conf.int = TRUE) %>%
filter(term != "(Intercept)") %>%
mutate(
OR = exp(estimate),
LI_95 = exp(conf.low),
LS_95 = exp(conf.high)
) %>%
select(term, estimate, OR, LI_95, LS_95, p.value)
tabla_logit <- tabla_logit %>%
mutate(
term = str_replace_all(term, "Horas_ExtraSi", "Horas extra: Sí vs No"),
term = str_replace_all(term, "DepartamentoRH", "Departamento: RH vs IYD"),
term = str_replace_all(term, "DepartamentoVentas", "Departamento: Ventas vs IYD"),
term = str_replace_all(term, "Estado_CivilDivorciado", "Estado civil: Divorciado vs Casado"),
term = str_replace_all(term, "Estado_CivilSoltero", "Estado civil: Soltero vs Casado"),
term = str_replace_all(term, "Edad", "Edad (por año)"),
term = str_replace_all(term, "Ingreso_Mensual", "Ingreso mensual (por 1 unidad)"),
term = str_replace_all(term, "Antigüedad_Cargo", "Antigüedad en el cargo (por año)")
)
tabla_logit <- tabla_logit %>%
mutate(
Coeficiente = round(estimate, 3),
OR = round(OR, 3),
LI_95 = round(LI_95, 3),
LS_95 = round(LS_95, 3),
p_valor = ifelse(p.value < 0.001, "<0.001", round(p.value, 4))
) %>%
select(
Variable = term,
Coeficiente,
OR,
LI_95,
LS_95,
p_valor
)
if (knitr::is_latex_output()) {
kable(tabla_logit,
caption = "Modelo de regresión logística: Odds Ratios para rotación",
booktabs = TRUE) %>%
kable_styling(
latex_options = c("scale_down", "hold_position"),
font_size = 10
)
} else {
kable(tabla_logit,
caption = "Modelo de regresión logística: Odds Ratios para rotación",
booktabs = TRUE) %>%
kable_styling(
full_width = FALSE,
position = "center",
font_size = 12
)
}
| Variable | Coeficiente | OR | LI_95 | LS_95 | p_valor |
|---|---|---|---|---|---|
| Horas extra: Sí vs No | 1.480 | 4.391 | 3.227 | 5.998 | <0.001 |
| Departamento: RH vs IYD | 0.641 | 1.899 | 0.908 | 3.717 | 0.0724 |
| Departamento: Ventas vs IYD | 0.619 | 1.858 | 1.338 | 2.578 | <0.001 |
| Estado civil: Divorciado vs Casado | -0.325 | 0.722 | 0.455 | 1.124 | 0.1573 |
| Estado civil: Soltero vs Casado | 0.796 | 2.216 | 1.588 | 3.103 | <0.001 |
| Edad (por año) | -0.025 | 0.976 | 0.956 | 0.995 | 0.0133 |
| Ingreso mensual (por 1 unidad) | 0.000 | 1.000 | 1.000 | 1.000 | <0.001 |
| Antigüedad en el cargo (por año) | -0.099 | 0.905 | 0.858 | 0.954 | <0.001 |
Horas_Extra es la covariable más influyente del modelo. Su coeficiente es positivo y altamente significativo; en términos de odds ratio, un empleado con horas extra multiplica por 4.39 sus odds de rotación frente a uno sin horas extra, manteniendo las demás variables constantes.
DepartamentoVentas presenta coeficiente positivo y significativo. Frente a IYD, pertenecer a Ventas aumenta los odds de rotación en aproximadamente 85.8%. RH va en la misma dirección, pero no alcanza significancia al 5%.
Estado_CivilSoltero también presenta coeficiente positivo y significativo. Un empleado soltero tiene 2.22 veces los odds de rotación de un empleado casado, a igualdad de las demás covariables. La categoría Divorciado no resultó significativa.
Edad tiene coeficiente negativo. Cada año adicional reduce ligeramente los odds de rotación; expresado en una escala más útil, 10 años adicionales llevan el OR a aproximadamente 0.78, es decir, alrededor de 22% menos odds de rotación.
Ingreso_Mensual tiene coeficiente negativo y significativo. El OR por una unidad parece 1.000 por un efecto de escala, pero al expresarlo por 1,000 unidades monetarias el OR es aproximadamente 0.916; esto equivale a una reducción cercana a 8.4% en los odds de rotación.
Antigüedad_Cargo también tiene signo negativo. Tres años adicionales en el cargo llevan el OR a aproximadamente 0.742, lo que representa una reducción cercana a 25.8% en los odds de rotación. En consecuencia, las covariables significativas al 5% fueron: Horas_Extra, DepartamentoVentas, Estado_CivilSoltero, Edad, Ingreso_Mensual y Antigüedad_Cargo. Los signos estimados del modelo fueron coherentes con las hipótesis formuladas en la etapa inicial.
El modelo alcanzó un área bajo la curva ROC de 0.772, lo que indica una capacidad de discriminación aceptable entre empleados con y sin rotación. En términos prácticos, esto significa que, al comparar aleatoriamente un empleado que rota con uno que no rota, el modelo asignará una probabilidad mayor al caso con rotación en aproximadamente 77.2% de las ocasiones. La Figura presenta la curva ROC correspondiente.
modelo <- glm(
Rotación ~ Horas_Extra + Departamento + Estado_Civil +
Edad + Ingreso_Mensual + Antigüedad_Cargo,
data = rotacion,
family = binomial
)
prob <- predict(modelo, type = "response")
roc_obj <- roc(
response = rotacion$Rotación,
predictor = prob,
levels = c("No", "Si"),
direction = "<"
)
# AUC
auc_val <- auc(roc_obj)
# Gráfico
plot(roc_obj,
col = "blue",
lwd = 2,
main = paste("Curva ROC (AUC =", round(auc_val, 3), ")"))
abline(a = 0, b = 1, lty = 2, col = "red")
Curva ROC del modelo de regresión logística
Dado que la rotación tiene una frecuencia relativamente baja (16.1%), no es conveniente usar de forma automática el punto de corte 0.50. Para gestión de talento suele ser más costoso no detectar a un empleado en riesgo que revisar preventivamente un caso adicional. Por ello se compararon varios umbrales.
prob <- predict(modelo, type = "response")
real <- ifelse(rotacion$Rotación == "Si", 1, 0)
cortes <- c(0.20, 0.25, 0.30, 0.40, 0.50)
calc_metricas <- function(corte) {
pred <- ifelse(prob >= corte, 1, 0)
TP <- sum(pred == 1 & real == 1)
TN <- sum(pred == 0 & real == 0)
FP <- sum(pred == 1 & real == 0)
FN <- sum(pred == 0 & real == 1)
data.frame(
Corte = corte,
Sensibilidad = TP / (TP + FN),
Especificidad = TN / (TN + FP),
Precision = TP / (TP + FP),
Exactitud = (TP + TN) / length(real),
Porcentaje_intervenir = mean(pred)
)
}
tabla_cutoff <- lapply(cortes, calc_metricas) %>%
bind_rows()
tabla_cutoff_formato <- tabla_cutoff %>%
mutate(
Sensibilidad = percent(Sensibilidad, accuracy = 0.1),
Especificidad = percent(Especificidad, accuracy = 0.1),
Precision = percent(Precision, accuracy = 0.1),
Exactitud = percent(Exactitud, accuracy = 0.1),
Porcentaje_intervenir = percent(Porcentaje_intervenir, accuracy = 0.1)
)
colnames(tabla_cutoff_formato) <- c(
"Corte", "Sens.", "Esp.", "Prec.", "Exact.", "% Interv."
)
if (knitr::is_latex_output()) {
kable(tabla_cutoff_formato,
caption = "Evaluación del modelo según distintos puntos de corte",
booktabs = TRUE) %>%
kable_styling(
latex_options = "scale_down",
position = "center",
font_size = 10
)
} else {
kable(tabla_cutoff_formato,
caption = "Evaluación del modelo según distintos puntos de corte",
booktabs = TRUE) %>%
kable_styling(
full_width = TRUE,
position = "center",
font_size = 14
)
}
| Corte | Sens. | Esp. | Prec. | Exact. | % Interv. |
|---|---|---|---|---|---|
| 0.20 | 65.0% | 78.6% | 36.8% | 76.4% | 28.4% |
| 0.25 | 57.8% | 86.1% | 44.3% | 81.5% | 21.0% |
| 0.30 | 46.0% | 90.5% | 48.2% | 83.3% | 15.4% |
| 0.40 | 30.4% | 95.8% | 58.1% | 85.2% | 8.4% |
| 0.50 | 17.7% | 98.1% | 64.6% | 85.2% | 4.4% |
El corte 0.50 ofrece alta especificidad, pero detecta solo 17.7% de los casos positivos; para intervención preventiva resulta demasiado conservador.
El corte 0.20 eleva la sensibilidad, pero amplía considerablemente la proporción de empleados a intervenir.
El corte 0.25 ofrece un balance más razonable: mantiene una sensibilidad de 57.8%, una especificidad de 86.1% y focaliza la intervención en aproximadamente 21% de la planta.
Por lo anterior, se propone 0.25 como umbral operativo para intervención temprana. No obstante, la empresa podría moverlo según su capacidad de seguimiento y el costo relativo entre falsos positivos y falsos negativos.
La selección del punto de corte no debe entenderse como una decisión exclusivamente estadística, sino también operativa. En la práctica, la empresa debe escoger el umbral considerando su capacidad de seguimiento, el costo de intervenir empleados que finalmente no rotan y el costo de no intervenir empleados que sí terminan rotando.
Se evaluó el siguiente perfil: empleado de 25 años, soltero, del área de Ventas, con horas extra, ingreso mensual de 3,000 y un año de antigüedad en el cargo. La probabilidad estimada de rotación fue 72.16%.
tabla_caso <- data.frame(
Elemento = c(
"Horas_Extra",
"Departamento",
"Estado_Civil",
"Edad",
"Ingreso_Mensual",
"Antigüedad_Cargo",
"Probabilidad estimada de rotación",
"Decisión con corte 0.25"
),
Valor = c(
"Sí",
"Ventas",
"Soltero",
"25 años",
"3,000",
"1 año",
"72.16%",
"Intervenir"
)
)
kable(tabla_caso,
caption = "Predicción individual de rotación y decisión de intervención",
booktabs = TRUE) %>%
kable_styling(
full_width = FALSE,
position = "center",
latex_options = c("hold_position", "scale_down")
) %>%
column_spec(1, width = "6cm") %>%
column_spec(2, width = "4cm")
| Elemento | Valor |
|---|---|
| Horas_Extra | Sí |
| Departamento | Ventas |
| Estado_Civil | Soltero |
| Edad | 25 años |
| Ingreso_Mensual | 3,000 |
| Antigüedad_Cargo | 1 año |
| Probabilidad estimada de rotación | 72.16% |
| Decisión con corte 0.25 | Intervenir |
El perfil combina varios factores de riesgo simultáneos: juventud, baja antigüedad, salario relativamente bajo, horas extra, soltería y pertenencia a Ventas.
Con un valor de 72.16%, la recomendación es intervenir de inmediato, tanto con el corte 0.25 como con umbrales más altos como 0.30 o 0.50.
Una intervención razonable incluiría entrevista de permanencia, revisión de carga laboral, revisión de compensación y un plan de desarrollo o carrera para el corto plazo.
Dado que la probabilidad estimada de rotación para este perfil es de 72.16%, el empleado sería clasificado como caso prioritario de intervención bajo el punto de corte propuesto de 0.25. Este resultado es consistente con la acumulación de factores de riesgo identificados en el modelo: horas extra, pertenencia al área de Ventas, estado civil soltero, baja antigüedad, menor edad e ingreso relativamente bajo.
La rotación observada en la muestra fue de 16.1%, por lo que el evento es minoritario pero suficientemente frecuente como para justificar un sistema de alerta temprana.
Las seis variables seleccionadas mostraron evidencia bivariada consistente con las hipótesis iniciales. En el modelo multivariado, las variables significativas fueron Horas_Extra, DepartamentoVentas, Estado_CivilSoltero, Edad, Ingreso_Mensual y Antigüedad_Cargo.
El principal disparador de rotación fue la sobrecarga laboral: trabajar horas extra multiplicó por 4.39 los odds de rotación. Este es el hallazgo más accionable para la gerencia.
Los resultados también sugieren que la estabilidad salarial y la consolidación temprana en el cargo cumplen un papel protector. Empleados jóvenes, con menor ingreso y baja antigüedad requieren monitoreo prioritario.
El modelo presentó AUC = 0.772, por lo que su capacidad predictiva es aceptable para apoyar decisiones.
Estrategia recomendada para disminuir la rotación: