La rotación de cargos es un fenómeno clave en la gestión del talento humano. Su análisis permite a las empresas anticipar riesgos de pérdida de personal, identificar factores críticos asociados y diseñar políticas para retener talento clave. Este trabajo utiliza el conjunto de datos rotacion del paquete paqueteMODELOS y aplica un modelo de regresión logística binomial (logit) para estimar la probabilidad de que un empleado rote (sí/no).
# 1) Definir CRAN (imprescindible en knit no interactivo)
if (is.null(getOption("repos")) || getOption("repos")["CRAN"] %in% c("", "@CRAN@")) {
options(repos = c(CRAN = "https://cloud.r-project.org"))
}
# 2) Helper para instalar/cargar desde CRAN
install_if_missing <- function(pkgs) {
for (p in pkgs) {
if (!require(p, character.only = TRUE)) {
install.packages(p, dependencies = TRUE)
library(p, character.only = TRUE)
}
}
}
# 3) Si no tienes devtools, lo instala desde CRAN
if (!requireNamespace("devtools", quietly = TRUE)) {
install.packages("devtools", dependencies = TRUE)
}
# 4) Instalar paqueteMODELOS desde GitHub solo si falta
if (!requireNamespace("paqueteMODELOS", quietly = TRUE)) {
devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
library(paqueteMODELOS)
} else {
library(paqueteMODELOS)
}
# 5) Resto de paquetes necesarios (desde CRAN)
cran_pkgs <- c("tidyverse", "janitor", "skimr", "broom", "pROC", "caret", "kableExtra")
install_if_missing(cran_pkgs)
# 6) Comprobación opcional (ejecuta pero no muestra resultados en HTML)
invisible(sapply(c("paqueteMODELOS", cran_pkgs), require, character.only = TRUE))
# Librerías necesarias
library(paqueteMODELOS)
library(tidyverse)
library(janitor)
library(skimr)
library(broom)
library(pROC)
library(caret)
library(kableExtra)
A continuación se resumen los campos reportados por la documentación de paqueteMODELOS::rotacion y su mapeo tras janitor::clean_names() (snake_case). Esto asegura trazabilidad de nombres, tipos esperados y escalas antes del análisis :
| Nombre limpio (clean_names) | Nombre original | Descripción | Tipo/escala esperada |
|---|---|---|---|
| rotacion | Rotación | Indica si el trabajador ha sido cambiado de departamento (Sí/No). | Categórica binaria |
| edad | Edad | Edad del empleado. | Numérica (años) |
| viaje_de_negocios | Viaje de negocios | Frecuencia de viajes por negocios. | Ordinal (p. ej., No viaja/Raramente/Frecuentemente) |
| departamento | Departamento | Dependencia a la que pertenece. | Categórica nominal |
| distancia_casa | Distancia_Casa | Km desde casa hasta la oficina. | Numérica (km) |
| educacion | Educación | Nivel educativo. | Ordinal (niveles) |
| campo_educacion | Campo_Educación | Área de formación. | Categórica nominal |
| satisfaccion_ambiental | Satisfacción_Ambiental | Percepción del ambiente. | Ordinal (1–4/1–5)* |
| genero | Genero | Género. | Categórica nominal |
| cargo | Cargo | Cargo actual. | Categórica nominal |
| satisfacion_laboral | Satisfación_Laboral | Percepción laboral. | Ordinal (1–4/1–5)* |
| estado_civil | Estado_Civil | Estado civil. | Categórica nominal |
| ingreso_mensual | Ingreso_Mensual | Ingreso mensual (USD). | Numérica |
| trabajos_anteriores | Trabajos_Anteriores | Nº de empleos anteriores. | Numérica (conteo) |
| horas_extra | Horas_Extra | Indica si trabaja horas extra. | Categórica binaria |
| porcentaje_aumento_salarial | Porcentaje_aumento_salarial | % de aumento respecto al cargo anterior. | Numérica (%) |
| rendimiento_laboral | Rendimiento_Laboral | Puntaje de rendimiento. | Ordinal (1–4/1–5)* |
| anos_experiencia | Años_Experiencia | Años de experiencia. | Numérica (años) |
| capacitaciones | Capacitaciones | Nº de capacitaciones. | Numérica (conteo) |
| equilibrio_trabajo_vida | Equilibrio_Trabajo_Vida | Balance trabajo–vida. | Ordinal (1–4/1–5)* |
| antiguedad | Antigüedad | Años en la empresa. | Numérica (años) |
| antiguedad_cargo | Antigüedad_Cargo | Años en el cargo actual. | Numérica (años) |
| anos_ultima_promocion | Años_ultima_promoción | Años desde el último ascenso. | Numérica (años) |
| anos_acargo_con_mismo_jefe | Años_acargo_con_mismo_jefe | Años con el mismo jefe. | Numérica (años) |
data("rotacion")
rotacion <- rotacion %>% clean_names()
glimpse(rotacion)
## Rows: 1,470
## Columns: 24
## $ rotacion <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…
## $ educacion <dbl> 2, 1, 2, 4, 1, 2, 3, 1, 3, 3, 3, 2, 1, 2, …
## $ campo_educacion <chr> "Ciencias", "Ciencias", "Otra", "Ciencias"…
## $ satisfaccion_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…
## $ satisfacion_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, …
## $ anos_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, …
## $ antiguedad <dbl> 6, 10, 0, 8, 2, 7, 1, 1, 9, 7, 5, 9, 5, 2,…
## $ antiguedad_cargo <dbl> 4, 7, 0, 7, 2, 7, 0, 0, 7, 7, 4, 5, 2, 2, …
## $ anos_ultima_promocion <dbl> 0, 1, 0, 3, 2, 3, 0, 0, 1, 7, 0, 0, 4, 1, …
## $ anos_acargo_con_mismo_jefe <dbl> 5, 7, 0, 0, 2, 6, 0, 0, 8, 7, 3, 8, 3, 2, …
# Vista general
skimr::skim(rotacion)
| Name | rotacion |
| Number of rows | 1470 |
| Number of columns | 24 |
| _______________________ | |
| Column type frequency: | |
| character | 8 |
| numeric | 16 |
| ________________________ | |
| Group variables | None |
Variable type: character
| skim_variable | n_missing | complete_rate | min | max | empty | n_unique | whitespace |
|---|---|---|---|---|---|---|---|
| rotacion | 0 | 1 | 2 | 2 | 0 | 2 | 0 |
| viaje_de_negocios | 0 | 1 | 8 | 14 | 0 | 3 | 0 |
| departamento | 0 | 1 | 2 | 6 | 0 | 3 | 0 |
| campo_educacion | 0 | 1 | 4 | 11 | 0 | 6 | 0 |
| genero | 0 | 1 | 1 | 1 | 0 | 2 | 0 |
| cargo | 0 | 1 | 7 | 23 | 0 | 9 | 0 |
| estado_civil | 0 | 1 | 6 | 10 | 0 | 3 | 0 |
| horas_extra | 0 | 1 | 2 | 2 | 0 | 2 | 0 |
Variable type: numeric
| skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 | hist |
|---|---|---|---|---|---|---|---|---|---|---|
| edad | 0 | 1 | 36.92 | 9.14 | 18 | 30 | 36 | 43 | 60 | ▂▇▇▃▂ |
| distancia_casa | 0 | 1 | 9.19 | 8.11 | 1 | 2 | 7 | 14 | 29 | ▇▅▂▂▂ |
| educacion | 0 | 1 | 2.91 | 1.02 | 1 | 2 | 3 | 4 | 5 | ▂▃▇▆▁ |
| satisfaccion_ambiental | 0 | 1 | 2.72 | 1.09 | 1 | 2 | 3 | 4 | 4 | ▅▅▁▇▇ |
| satisfacion_laboral | 0 | 1 | 2.73 | 1.10 | 1 | 2 | 3 | 4 | 4 | ▅▅▁▇▇ |
| ingreso_mensual | 0 | 1 | 6502.93 | 4707.96 | 1009 | 2911 | 4919 | 8379 | 19999 | ▇▅▂▁▂ |
| trabajos_anteriores | 0 | 1 | 2.69 | 2.50 | 0 | 1 | 2 | 4 | 9 | ▇▃▂▂▁ |
| porcentaje_aumento_salarial | 0 | 1 | 15.21 | 3.66 | 11 | 12 | 14 | 18 | 25 | ▇▅▃▂▁ |
| rendimiento_laboral | 0 | 1 | 3.15 | 0.36 | 3 | 3 | 3 | 3 | 4 | ▇▁▁▁▂ |
| anos_experiencia | 0 | 1 | 11.28 | 7.78 | 0 | 6 | 10 | 15 | 40 | ▇▇▂▁▁ |
| capacitaciones | 0 | 1 | 2.80 | 1.29 | 0 | 2 | 3 | 3 | 6 | ▂▇▇▂▃ |
| equilibrio_trabajo_vida | 0 | 1 | 2.76 | 0.71 | 1 | 2 | 3 | 3 | 4 | ▁▃▁▇▂ |
| antiguedad | 0 | 1 | 7.01 | 6.13 | 0 | 3 | 5 | 9 | 40 | ▇▂▁▁▁ |
| antiguedad_cargo | 0 | 1 | 4.23 | 3.62 | 0 | 2 | 3 | 7 | 18 | ▇▃▂▁▁ |
| anos_ultima_promocion | 0 | 1 | 2.19 | 3.22 | 0 | 0 | 1 | 3 | 15 | ▇▁▁▁▁ |
| anos_acargo_con_mismo_jefe | 0 | 1 | 4.12 | 3.57 | 0 | 2 | 3 | 7 | 17 | ▇▂▅▁▁ |
El resumen inicial con skimr permite constatar:
Hay 1470 registros y 24 variables (8 categóricas, 16 numéricas).
La variable rotacion está desbalanceada: 84% No vs 16% Sí.
Las escalas ordinales de satisfacción y equilibrio van de 1 a 4, confirmando que valores más altos indican percepciones más positivas.
rendimiento_laboral se concentra en niveles altos (3–4), mostrando baja variabilidad.
edad media aproximadamente 37 años (18–60); antiguedad_cargo promedio aproximadamente 4.2 años (máx. 18).
ingreso_mensual presenta asimetría a la derecha, con valores hasta 20.000 USD.
A partir del resumen exploratorio:
Escalas ordinales confirmadas: satisfaccion_ambiental, satisfacion_laboral y equilibrio_trabajo_vida presentan valores de 1 a 4. Esto confirma que valores mayores reflejan percepciones más positivas, cumpliendo la instrucción de no asumir la dirección de la escala sin verificarla.
Desbalance de la respuesta: rotacion está fuertemente desbalanceada (~84% No vs ~16% Sí). Este patrón motiva reportar Precision, Recall, F1 además de ROC/AUC y usar el índice de Youden para fijar umbrales.
Asimetrías y dispersión: ingreso_mensual muestra fuerte asimetría a la derecha (máx. ~20.000), y varias variables de antigüedad/experiencia presentan colas largas; rendimiento_laboral está muy concentrada (predomina 3–4), lo que limita su poder discriminante.
Contexto de magnitudes: antiguedad_cargo promedio ~4.2 años (máx. 18), distancia_casa media ~9 km (máx. 29), edad media ~37 años (rango 18–60). Estas magnitudes guían expectativas razonables de signo y tamaño de efecto en el modelo.
Estas observaciones respaldan la selección de covariables, la atención al desbalance en la validación y la lectura cuidadosa de escalas antes de interpretar coeficientes.
Seleccionamos 3 variables categóricas y 3 cuantitativas relacionadas con la rotación, justificando hipótesis:
Categóricas:
horas_extra – Hipótesis: empleados con horas extra tienen mayor rotación por desgaste laboral.
viaje_de_negocios – Hipótesis: viajes frecuentes aumentan la rotación por carga y movilidad.
estado_civil – Hipótesis: solteros presentan más rotación que casados (mayor movilidad laboral).
Cuantitativas:
antiguedad_cargo – Hipótesis: menor antigüedad implica mayor rotación.
edad – Hipótesis: a mayor edad del empleado se reduce la rotación.
ingreso_mensual – Hipótesis: salarios más altos reducen rotación.
# Variables categoricas almacenadas en vars_cat
vars_cat <- c("horas_extra", "viaje_de_negocios", "estado_civil")
# Variables numericas almacenadas en vars_num
vars_num <- c("antiguedad_cargo", "edad", "ingreso_mensual")
# Variable respuesta binaria y; coerción de tipos y NA handling
datos <- rotacion %>%
mutate(
# creamos la variable respuesta binaria y
# Toma 1 si rotacion es "Si" y 0 en caso contrario
# El sufijo L fuerza el tipo integer
# ya revisamos que el texto debe coincidir exactamente con los valores de tu columna ("Si"/"No"). Si hay "Sí" con tilde, hay que ajustar.
y = if_else(rotacion == "Si", 1L, 0L),
across(all_of(vars_cat), as.factor),
#convierte todas las columnas listadas en vars_cat a factor (categóricas).
#la regresión logística necesita saber qué variables son categóricas para crear automáticamente las dummies
across(all_of(vars_num), as.numeric)
#asegura que esas columnas sean numéricas (por si vinieran como character).
) %>%
drop_na(y, all_of(vars_cat), all_of(vars_num))
# elimina filas con NA en la respuesta o en cualquiera de las variables seleccionadas.
# Chequeo de tamaños por clase (desbalance)
datos %>% count(y) %>% mutate(prop = round(100*n/sum(n),1)) %>% kbl(caption = "Distribución de la respuesta (desbalance)") %>% kable_styling(full_width = FALSE)
| y | n | prop |
|---|---|---|
| 0 | 1233 | 83.9 |
| 1 | 237 | 16.1 |
Antes de interpretar, confirmamos la codificación y dirección de escalas ordinales:
# Rango observado para variables ordinales relevantes
escalas_tbl <- tibble(
variable = c("satisfacion_laboral", "satisfaccion_ambiental", "equilibrio_trabajo_vida"),
min = c(min(rotacion$satisfacion_laboral, na.rm=TRUE),
min(rotacion$satisfaccion_ambiental, na.rm=TRUE),
min(rotacion$equilibrio_trabajo_vida, na.rm=TRUE)),
max = c(max(rotacion$satisfacion_laboral, na.rm=TRUE),
max(rotacion$satisfaccion_ambiental, na.rm=TRUE),
max(rotacion$equilibrio_trabajo_vida, na.rm=TRUE))
)
escalas_tbl %>% kbl(caption = "Rangos observados de escalas (1 = bajo, valores mayores = más alto)") %>% kable_styling(full_width = FALSE)
| variable | min | max |
|---|---|---|
| satisfacion_laboral | 1 | 4 |
| satisfaccion_ambiental | 1 | 4 |
| equilibrio_trabajo_vida | 1 | 4 |
Nota: Se confirma empíricamente que los valores van de 1 a 4, donde valores mayores indican mejor satisfacción/equilibrio.
datos %>%
ggplot(aes(x = factor(y, labels = c("No", "Sí")))) +
geom_bar(fill = "gray40") +
geom_text(stat = "count", aes(label = scales::percent(..count../sum(..count..), accuracy = 0.1)),
vjust = -0.5) +
labs(title = "Distribución de Rotación",
x = "Rotación",
y = "Frecuencia")
La variable respuesta rotación presenta un marcado desbalance en los datos: aproximadamente el 84% de los empleados no rota, mientras que solo un 16% sí lo hace. Esta distribución evidencia que la clase minoritaria es significativamente más pequeña, lo que implica un desafío para el modelado predictivo, ya que los algoritmos tienden a favorecer la clase mayoritaria.
for (v in vars_cat) {
print(
datos %>% ggplot(aes(x = .data[[v]])) +
geom_bar() +
labs(title = paste("Distribución de", v), x = v, y = "Frecuencia")
)
}
Horas extra: La mayoría de empleados no realiza horas extra, mientras que una minoría significativa sí lo hace. Esta diferencia es importante porque, según la hipótesis H1, quienes trabajan horas extra tienen mayor probabilidad de rotación, y aunque sean menos, podrían explicar buena parte de los casos de salida.
Viajes de negocios: La mayoría de empleados viaja raramente por trabajo. Un grupo más reducido lo hace frecuentemente, y aún menor es el de quienes no viajan. La distribución respalda la hipótesis H2: los viajes frecuentes, aunque menos comunes, pueden tener un impacto mayor en la probabilidad de rotación.
Estado civil: El grupo más numeroso es el de casados, seguido por solteros y en menor medida divorciados. Esto concuerda con la hipótesis H3: se espera que los solteros presenten mayor propensión a rotar, dado que representan un segmento intermedio en tamaño pero con potencial de movilidad laboral más alto.
datos %>%
pivot_longer(all_of(vars_num), names_to = "var", values_to = "val") %>%
ggplot(aes(x = val)) +
geom_histogram(bins = 30) +
facet_wrap(~var, scales = "free") +
labs(title = "Histogramas de variables numéricas")
Antigüedad en el cargo: La distribución se concentra en valores bajos (0 a 5 años), con algunos casos aislados hasta 18 años. Esto indica que muchos empleados llevan relativamente poco tiempo en su cargo actual, lo cual respalda la hipótesis H4: menor antigüedad podría asociarse a mayor rotación.
Ingreso mensual: La variable muestra una distribución asimétrica a la derecha. La mayoría gana entre 2.000 y 8.000 dólares, pero existen empleados con ingresos muy superiores (hasta 20.000). Esta dispersión refleja heterogeneidad en la estructura salarial, y según H6, los ingresos más altos deberían reducir la rotación.
edad (años del empleado) Distribución aproximadamente normal, con ligera asimetría a la derecha. Centro alrededor de los 36–37 años, que coincide con la media reportada. Rango: desde 18 hasta 60 años. Tiene suficiente variabilidad para captar un posible efecto protector (mayor edad → menor rotación).
# Barras apiladas por proporción ("stacked %")
plot_prop <- function(var){
datos %>%
ggplot(aes(x = .data[[var]], fill = factor(y, labels = c("No", "Sí")))) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent) +
labs(title = paste("Rotación por", var, "(proporción)"), x = var, y = "% dentro de categoría", fill = "Rotación")
}
# Barras de tasa (media de y) con etiquetas
plot_rate <- function(var){
datos %>%
group_by(.data[[var]]) %>%
summarise(rate = mean(y), n = n(), .groups = 'drop') %>%
ggplot(aes(x = .data[[var]], y = rate)) +
geom_col() +
scale_y_continuous(labels = scales::percent) +
geom_text(aes(label = scales::percent(rate, accuracy = 0.1)), vjust = -0.2, size = 3.4) +
labs(title = paste("Tasa de rotación por", var), x = var, y = "Pr(Rotación)")
}
plots <- c(vars_cat, vars_cat) %>%
map2(rep(c("prop","rate"), each = length(vars_cat)), ~ if(.y=="prop") plot_prop(.x) else plot_rate(.x))
# imprimir en orden
for(p in plots) print(p)
cat_rates <- map_dfr(
vars_cat,
~ datos %>%
group_by(.data[[.x]]) %>%
summarise(rate = mean(y), n = n(), .groups = 'drop') %>%
mutate(variable = .x,
categoria = as.character(.data[[.x]])) %>%
select(variable, categoria, n, rate) # reordenamos aquí
)
cat_rates %>%
arrange(variable, desc(rate)) %>%
kbl(digits = 3, caption = "Tasa de rotación por categoría") %>%
kable_styling(full_width = FALSE)
| variable | categoria | n | rate |
|---|---|---|---|
| estado_civil | Soltero | 470 | 0.255 |
| estado_civil | Casado | 673 | 0.125 |
| estado_civil | Divorciado | 327 | 0.101 |
| horas_extra | Si | 416 | 0.305 |
| horas_extra | No | 1054 | 0.104 |
| viaje_de_negocios | Frecuentemente | 277 | 0.249 |
| viaje_de_negocios | Raramente | 1043 | 0.150 |
| viaje_de_negocios | No_Viaja | 150 | 0.080 |
Horas extra: La tasa de rotación es 30.5% en quienes sí hacen horas extra, frente a 10.4% en quienes no. Esto respalda la hipótesis H1: trabajar horas extra se asocia con mayor desgaste y probabilidad de rotación.
Viajes de negocios: La mayor tasa de rotación aparece en empleados que viajan frecuentemente (24.9%), frente a quienes viajan raramente (15.0%) y los que no viajan (8.0%). Esto confirma H2: la movilidad frecuente incrementa la propensión a rotar.
Estado civil: Los solteros presentan la tasa de rotación más alta (25.5%), frente a casados (12.5%) y divorciados (10.1%). Esto coincide con H3: mayor movilidad laboral en personas solteras.
# Pruebas Chi²
chi_list <- lapply(vars_cat, function(v){
tab <- table(datos[[v]], datos$y)
broom::tidy(chisq.test(tab)) %>% mutate(variable = v)
})
chi_tbl <- bind_rows(chi_list)
chi_tbl %>% select(variable, statistic, parameter, p.value) %>%
kbl(digits = 4, caption = "Chi-cuadrado: asociación categoría ~ rotación") %>% kable_styling(full_width = FALSE)
| variable | statistic | parameter | p.value |
|---|---|---|---|
| horas_extra | 87.5643 | 1 | 0 |
| viaje_de_negocios | 24.1824 | 2 | 0 |
| estado_civil | 46.1637 | 2 | 0 |
Interpretación de Chi²
Horas extra (χ² = 87.56, gl = 1, p < 0.001): Existe una asociación altamente significativa entre trabajar horas extra y la rotación. Esto confirma estadísticamente la hipótesis H1 de que las horas extra elevan la probabilidad de rotar.
Viaje de negocios (χ² = 24.18, gl = 2, p < 0.001): Se detecta asociación significativa entre la frecuencia de viajes y la rotación. La hipótesis H2 se confirma: los viajes frecuentes incrementan la rotación.
Estado civil (χ² = 46.16, gl = 2, p < 0.001): El estado civil también está significativamente asociado a la rotación. En línea con la hipótesis H3, los solteros presentan mayor propensión a rotar que casados o divorciados.
Conclusión parcial: Las tres variables categóricas seleccionadas muestran asociación significativa con la rotación, lo que justifica su inclusión en el modelo logístico multivariado.
for (v in vars_num) {
print(
datos %>% ggplot(aes(x = factor(y, labels = c("No", "Sí")), y = .data[[v]])) +
geom_boxplot() +
labs(title = paste(v, "según rotación"), x = "Rotación", y = v)
)
}
Antigüedad en el cargo: Los empleados que rotan tienden a tener menor antigüedad (mediana más baja), mientras que los que permanecen suelen acumular más años en el cargo. Esto respalda la hipótesis H4: menor antigüedad incrementa la probabilidad de rotación.
Edad: Los empleados que rotan tienen una mediana de edad más baja (≈32 años) frente a los que permanecen en su cargo (≈37 años). Este hallazgo respalda la hipótesis planteada: a mayor edad, menor probabilidad de rotación. En términos gerenciales, los trabajadores jóvenes parecen más propensos a buscar nuevas oportunidades, mientras que los de mayor edad tienden a permanecer estables en sus cargos.
Ingreso mensual: Los empleados que no rotan perciben ingresos mensuales más altos en promedio y con mayor dispersión, en comparación con quienes rotan (mediana más baja y rango reducido). Esto coincide con la hipótesis H6: mayores ingresos reducen la probabilidad de rotación.
# Regresiones logísticas univariadas (odds ratios)
uni_glm <- map_dfr(vars_num, function(v){
f <- as.formula(paste("y ~", v))
mod <- glm(f, family = binomial, data = datos)
broom::tidy(mod, conf.int = TRUE, exponentiate = TRUE) %>% mutate(variable = v)
})
uni_glm %>% filter(term != "(Intercept)") %>%
select(variable, term, estimate, conf.low, conf.high, p.value) %>%
kbl(digits = 3, caption = "Univariado (OR) para variables numéricas") %>% kable_styling(full_width = FALSE)
| variable | term | estimate | conf.low | conf.high | p.value |
|---|---|---|---|---|---|
| antiguedad_cargo | antiguedad_cargo | 0.864 | 0.823 | 0.905 | 0 |
| edad | edad | 0.949 | 0.933 | 0.965 | 0 |
| ingreso_mensual | ingreso_mensual | 1.000 | 1.000 | 1.000 | 0 |
Antigüedad en el cargo (OR = 0.864, p < 0.001): Cada año adicional en el cargo reduce en ~14% la probabilidad de rotación (1 – 0.864). Esto confirma la hipótesis H4: la antigüedad actúa como factor protector.
Edad (edad) OR = 0.949, IC95% [0.933 – 0.965], p < 0.001. Cada año adicional de edad reduce en un 5.1% (1–0.949) la probabilidad de rotación. los empleados más jóvenes tienden a rotar más.
Ingreso mensual (OR ≈ 1.000, p < 0.001): El efecto es significativo pero de magnitud muy pequeña, debido a la escala en dólares (cambios unitarios son irrelevantes). Sin embargo, el signo respalda la hipótesis H6: a mayor ingreso, menor rotación. El efecto real debe evaluarse considerando incrementos más grandes (ej. por cada 1.000 USD).
form <- as.formula(paste("y ~", paste(c(vars_cat, vars_num), collapse = " + ")))
mod <- glm(form, family = binomial(link = "logit"), data = datos)
# Resumen
summary(mod)
##
## Call:
## glm(formula = form, family = binomial(link = "logit"), data = datos)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.013e-01 3.801e-01 -0.266 0.789882
## horas_extraSi 1.451e+00 1.582e-01 9.173 < 2e-16 ***
## viaje_de_negociosNo_Viaja -1.325e+00 3.530e-01 -3.753 0.000175 ***
## viaje_de_negociosRaramente -6.546e-01 1.811e-01 -3.614 0.000302 ***
## 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 ***
## antiguedad_cargo -1.041e-01 2.731e-02 -3.812 0.000138 ***
## edad -2.885e-02 1.009e-02 -2.860 0.004232 **
## ingreso_mensual -6.808e-05 2.518e-05 -2.703 0.006870 **
## ---
## 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
horas_extra (Sí vs No) β = 1.451, p < 0.001. OR ≈ exp(1.451) ≈ 4.27. Los empleados con horas extra tienen más de 4 veces la probabilidad de rotar.
viaje_de_negocios (No viaja vs Frecuente) β = -1.325, p < 0.001. OR ≈ 0.27. Los que no viajan tienen un 73% menos probabilidad de rotar que los que viajan frecuentemente.
viaje_de_negocios (Raramente vs Frecuente) β = -0.654, p < 0.001. OR ≈ 0.52. Viajar raramente reduce los odds de rotación casi a la mitad respecto a viajar frecuentemente.
estado_civil (Divorciado vs Casado) β = -0.291, p = 0.204 (no significativo). No hay evidencia de diferencia significativa entre divorciados y casados en la rotación.
estado_civil (Soltero vs Casado) β = 0.793, p < 0.001.OR ≈ 2.21. Los solteros tienen más del doble de probabilidad de rotar que los casados.
antiguedad_cargo (años) β = -0.104, p < 0.001. OR ≈ 0.90. Cada año adicional en el cargo reduce los odds de rotación un 10%.
edad (años) β = -0.289, p = 0.004. OR ≈ 0.75 (por cada 10 años, ya que el efecto anual es pequeño). A mayor edad, menor probabilidad de rotación. Hipótesis confirmada.
ingreso_mensual (USD) β = -0.000688, p = 0.006. OR ≈ 1.000 por dólar (muy cercano a 1). Re-escalar por 1.000 USD ayuda a interpretar: por cada 1.000 USD adicionales, los odds de rotación bajan alrededor de un 9–10%.
Ajuste global del modelo
Null deviance: 1298.6 (modelo sin predictores). Residual deviance: 1082.4 (modelo con predictores). AIC = 1100.4: usado para comparar con otros modelos.
El modelo multivariado muestra que los principales factores de riesgo de rotación son realizar horas extra (OR ≈ 4.3) y ser soltero (OR ≈ 2.2), mientras que la edad y la antigüedad en el cargo aparecen como factores protectores, reduciendo significativamente la probabilidad de rotación. Asimismo, la frecuencia de viajes de negocios tiene un impacto relevante: los viajes frecuentes incrementan la rotación, mientras que viajar raramente o no viajar reduce ese riesgo. El ingreso mensual, aunque estadísticamente significativo, presenta un efecto marginal a escala de dólar, pero su efecto se clarifica al re-escalarlo por miles de USD.
# OR con IC95%
ors <- tidy(mod, conf.int = TRUE, exponentiate = TRUE) %>%
select(term, estimate, conf.low, conf.high, p.value)
ors %>% kbl(digits = 3, caption = "Odds Ratios (multivariado) con IC95%") %>% kable_styling(full_width = FALSE)
| term | estimate | conf.low | conf.high | p.value |
|---|---|---|---|---|
| (Intercept) | 0.904 | 0.430 | 1.909 | 0.790 |
| horas_extraSi | 4.268 | 3.135 | 5.832 | 0.000 |
| viaje_de_negociosNo_Viaja | 0.266 | 0.128 | 0.515 | 0.000 |
| viaje_de_negociosRaramente | 0.520 | 0.365 | 0.744 | 0.000 |
| estado_civilDivorciado | 0.747 | 0.471 | 1.162 | 0.204 |
| estado_civilSoltero | 2.211 | 1.583 | 3.101 | 0.000 |
| antiguedad_cargo | 0.901 | 0.853 | 0.950 | 0.000 |
| edad | 0.972 | 0.952 | 0.991 | 0.004 |
| ingreso_mensual | 1.000 | 1.000 | 1.000 | 0.007 |
Horas extra (Sí vs No) OR = 4.27 (IC95%: 3.14 – 5.83), p < 0.001. Los empleados que trabajan horas extra tienen más de 4 veces la probabilidad de rotar comparados con quienes no lo hacen. Es el predictor de mayor peso en el modelo.
Viajes de negocios (No viaja vs Frecuente) OR = 0.27 (IC95%: 0.13 – 0.52), p < 0.001. No viajar reduce en un 73% los odds de rotación respecto a viajar frecuentemente.
Viajes de negocios (Raramente vs Frecuente) OR = 0.52 (IC95%: 0.37 – 0.74), p < 0.001. Viajar raramente también disminuye el riesgo de rotación, casi a la mitad comparado con viajar frecuentemente.
Estado civil (Divorciado vs Casado) OR = 0.75 (IC95%: 0.47 – 1.16), p = 0.204. No es significativo → no se puede concluir que los divorciados difieran de los casados en su propensión a rotar.
Estado civil (Soltero vs Casado) OR = 2.21 (IC95%: 1.58 – 3.10), p < 0.001. Los solteros tienen más del doble de probabilidad de rotar que los casados.
Antigüedad en cargo (años) OR = 0.90 (IC95%: 0.85 – 0.95), p < 0.001. Cada año adicional en el cargo reduce la probabilidad de rotación en 9.9%.
Edad (años) OR = 0.97 (IC95%: 0.95 – 0.99), p = 0.004. Cada año adicional de edad reduce el riesgo de rotación en 2.8%, confirmando que los jóvenes rotan más.
Ingreso mensual (USD) OR = 1.000 (IC95%: 1.000 – 1.000), p = 0.007. Aunque es significativo, el efecto es casi imperceptible en dólares. Re-escalado por 1.000 USD suele mostrar que un mayor salario reduce la probabilidad de rotación.
El modelo confirma que la rotación está fuertemente asociada a factores laborales y demográficos:
Mayor riesgo: trabajar horas extra y ser soltero.
Menor riesgo: mayor antigüedad, mayor edad, y viajar poco o no viajar. El ingreso es significativo, pero su interpretación requiere re-escalado.
# AIC y deviances
metricas_mod <- tibble(
AIC = AIC(mod),
Null_deviance = mod$null.deviance,
Residual_deviance = mod$deviance
)
metricas_mod %>% kbl(digits = 2, caption = "Criterios de selección del modelo") %>% kable_styling(full_width = FALSE)
| AIC | Null_deviance | Residual_deviance |
|---|---|---|
| 1100.38 | 1298.58 | 1082.38 |
El AIC (1100.38) indica la calidad relativa del modelo ajustado: mientras más bajo sea el valor, mejor es el equilibrio entre bondad de ajuste y complejidad. Aunque no puede interpretarse de forma aislada, este valor sirve para comparar diferentes modelos candidatos.
La null deviance (1298.58) representa el desajuste del modelo nulo (aquel que solo incluye la media de la variable respuesta).
La residual deviance (1082.38) corresponde al desajuste del modelo ajustado con todas las variables incluidas.
La reducción entre la null deviance y la residual deviance (≈ 216 puntos) evidencia que el modelo con predictores mejora sustancialmente la explicación de la rotación frente al modelo nulo.
En conclusión, el modelo presenta un ajuste razonable y aporta capacidad explicativa relevante para predecir la rotación de empleados.
# Crear variable re-escalada
if(!"ingreso_mensual_k" %in% names(datos)){
datos <- datos %>% mutate(ingreso_mensual_k = ingreso_mensual/1000)
}
form_k <- as.formula(paste("y ~", paste(c(vars_cat, "antiguedad_cargo",
"edad", "ingreso_mensual_k"), collapse = " + ")))
mod_k <- glm(form_k, family = binomial, data = datos)
broom::tidy(mod_k, conf.int = TRUE, exponentiate = TRUE) %>%
select(term, estimate, conf.low, conf.high, p.value) %>%
kableExtra::kbl(digits = 3, caption = "Odds Ratios con IC95% (ingreso por 1.000 USD)") %>%
kableExtra::kable_styling(full_width = FALSE)
| term | estimate | conf.low | conf.high | p.value |
|---|---|---|---|---|
| (Intercept) | 0.904 | 0.430 | 1.909 | 0.790 |
| horas_extraSi | 4.268 | 3.135 | 5.832 | 0.000 |
| viaje_de_negociosNo_Viaja | 0.266 | 0.128 | 0.515 | 0.000 |
| viaje_de_negociosRaramente | 0.520 | 0.365 | 0.744 | 0.000 |
| estado_civilDivorciado | 0.747 | 0.471 | 1.162 | 0.204 |
| estado_civilSoltero | 2.211 | 1.583 | 3.101 | 0.000 |
| antiguedad_cargo | 0.901 | 0.853 | 0.950 | 0.000 |
| edad | 0.972 | 0.952 | 0.991 | 0.004 |
| ingreso_mensual_k | 0.934 | 0.888 | 0.980 | 0.007 |
Ingreso mensual (por 1.000 USD): OR = 0.934 (IC95%: 0.888 – 0.980, p < 0.01). A mayor salario, menor probabilidad de rotación: cada 1.000 USD adicionales en salario reducen en ~6.6% la probabilidad de rotación.
Conclusión: Las variables más influyentes y significativas en la probabilidad de rotación son las horas extra, el estado civil, los viajes de negocios, la antigüedad en el cargo, la edad y el ingreso mensual reescalado.
modelo_para_plot <- if (exists("mod_k")) mod_k else mod
ors_plot <- tidy(modelo_para_plot, conf.int=TRUE, exponentiate=TRUE) %>%
filter(term != "(Intercept)", term != "estado_civilDivorciado") %>%
mutate(variable = dplyr::recode(term,
"horas_extraSi" = "Horas extra: Sí vs No",
"viaje_de_negociosNo_Viaja" = "Viajes: No viaja vs Frecuente",
"viaje_de_negociosRaramente" = "Viajes: Raramente vs Frecuente",
"estado_civilCasado" = "Estado civil: Casado vs Divorciado",
"estado_civilSoltero" = "Estado civil: Soltero vs Divorciado",
"antiguedad_cargo" = "Antigüedad en cargo (años)",
"edad" = "Edad (años)",
"ingreso_mensual_k" = "Ingreso mensual (por 1.000 USD)"
),
signif = ifelse(p.value < 0.05, "Significativo", "No significativo"),
dist_a_1 = abs(log(estimate))) %>% arrange(dist_a_1)
ggplot(ors_plot, aes(x=reorder(variable, dist_a_1), y=estimate, ymin=conf.low, ymax=conf.high, color=signif)) +
geom_pointrange(size=0.9) + geom_errorbar(width=0.15) + geom_hline(yintercept=1, linetype="dashed") +
coord_flip() + scale_y_log10() +
labs(title="Forest plot – Odds Ratios (IC95%)", x="Variables (comparadas con la referencia)", y="OR (escala log)", color="Significancia") +
theme_minimal(base_size=12)
El gráfico resume visualmente los efectos de cada variable sobre la probabilidad de rotación laboral.
La línea punteada en OR = 1 representa el punto de “no efecto”. Los OR mayores a 1 implican mayor riesgo de rotación, y menores a 1, menor riesgo.
Todas las variables presentadas resultan significativas (se muestran en rojo).
Horas extra (Sí vs No): Incrementa fuertemente la probabilidad de rotación (OR > 4).
Viajes de negocios (No viaja o Raramente vs Frecuente): Reducen significativamente la rotación (OR < 1).
Estado civil (Soltero vs Divorciado): Incrementa la probabilidad de rotación (OR ≈ 2.2).
Antigüedad en el cargo (años): A mayor antigüedad, menor riesgo de rotación (OR < 1).
Ingreso mensual (por 1.000 USD): Los salarios más altos se asocian con menor rotación (OR < 1).
Edad: Cada año adicional se asocia con una ligera reducción en la probabilidad de rotación (OR < 1).
En conjunto, el forest plot confirma que los factores laborales (horas extra, viajes, antigüedad, ingreso) y personales (estado civil, edad) influyen significativamente en la rotación de empleados, en la dirección esperada por la hipótesis: condiciones más estables (mayor edad, más antigüedad, mayores ingresos, menos viajes) disminuyen el riesgo de rotación.
set.seed(123)
i_train <- createDataPartition(datos$y, p=0.8, list=FALSE)
train <- datos[i_train,]
test <- datos[-i_train,]
mod_tr <- glm(form, family=binomial, data=train)
prob <- predict(mod_tr, newdata=test, type="response")
eval_df <- tibble(prob=as.numeric(prob), y=as.numeric(test$y)) %>% drop_na()
roc_obj <- pROC::roc(response=eval_df$y, predictor=eval_df$prob, quiet=TRUE)
auc_val <- pROC::auc(roc_obj); auc_val
## Area under the curve: 0.7583
El modelo logístico presenta un AUC de 0.7583 en el conjunto de prueba, lo cual refleja una capacidad predictiva adecuada para diferenciar entre empleados que rotan y los que no. Este valor respalda el uso del modelo como herramienta de apoyo a la toma de decisiones. Aunque el dataset está desbalanceado (83.9% “No rotan” vs 16.1% “Sí rotan”), el modelo logra un AUC > 0.75, mostrando un desempeño superior al azar (AUC = 0.5). Esto significa que, dado un par de empleados (uno que rota y otro que no), el modelo tiene un ~76% de probabilidad de asignar una mayor probabilidad de rotación al empleado que realmente rota. A pesar de ser un resultado prometedor, se debe tener precaución: el desbalance puede inflar algunas métricas como la exactitud (accuracy). Por eso conviene complementar con sensibilidad, especificidad, F1 y matrices de confusión en distintos umbrales (como ya venías haciendo con Youden y F1).
plot(roc_obj, print.auc=TRUE, main="Curva ROC – Modelo (test)")
El AUC = 0.758 confirma que el modelo tiene una capacidad de discriminación aceptable para distinguir entre empleados que rotan y los que no.
Una AUC de 0.758 significa que, si tomamos al azar un empleado que rotó y uno que no, el modelo tiene un 75.8% de probabilidad de asignar mayor probabilidad de rotación al empleado que realmente rotó.
La curva se encuentra por encima de la diagonal de referencia (línea gris), lo que demuestra que el modelo funciona mejor que el azar (AUC = 0.5).
El modelo es útil como herramienta predictiva, pero requiere complementar su evaluación con métricas adicionales (F1, sensibilidad, especificidad, matrices de confusión en distintos umbrales) para asegurar un balance entre falsos positivos y falsos negativos.
## 7.2 Selección del umbral de clasificación (Youden vs Máx F1)
# Umbral óptimo según índice de Youden
best <- pROC::coords(roc_obj, x="best", best.method="youden",
ret=c("threshold","sensitivity","specificity","accuracy"),
transpose=TRUE)
thr <- as.numeric(best["threshold"])
# Clasificación con este umbral
pred_cls <- ifelse(eval_df$prob >= thr, 1, 0)
cm <- caret::confusionMatrix(
factor(pred_cls, levels=c(0,1)),
factor(eval_df$y, levels=c(0,1)),
positive="1"
)
# Cálculo de precisión, recall y F1
TP <- cm$table[2,2]; FP <- cm$table[2,1]; FN <- cm$table[1,2]
precision <- TP/(TP+FP); recall <- TP/(TP+FN)
F1 <- 2*precision*recall/(precision+recall)
# Exploración de umbral que maximiza F1
ths <- seq(0,1,by=0.001)
get_f1 <- function(t){
pred <- ifelse(eval_df$prob >= t, 1, 0)
TP <- sum(pred==1 & eval_df$y==1)
FP <- sum(pred==1 & eval_df$y==0)
FN <- sum(pred==0 & eval_df$y==1)
if ((TP+FP)==0 || (TP+FN)==0) return(NA_real_)
prec <- TP/(TP+FP); rec <- TP/(TP+FN)
if ((prec+rec)==0) return(NA_real_)
2*prec*rec/(prec+rec)
}
f1s <- vapply(ths, get_f1, numeric(1))
thr_f1 <- ths[which.max(f1s)]
max_f1 <- max(f1s, na.rm=TRUE)
# Confusión con umbral de F1
pred_f1 <- ifelse(eval_df$prob >= thr_f1, 1, 0)
cm_f1 <- caret::confusionMatrix(
factor(pred_f1, levels=c(0,1)),
factor(eval_df$y, levels=c(0,1)),
positive="1"
)
# Tabla comparativa
tribble(~Umbral, ~AUC, ~F1, ~Accuracy, ~Sensibilidad, ~Especificidad,
thr, as.numeric(auc_val), F1, best["accuracy"], best["sensitivity"], best["specificity"],
thr_f1, as.numeric(auc_val), max_f1, cm_f1$overall["Accuracy"], cm_f1$byClass["Sensitivity"], cm_f1$byClass["Specificity"]) %>%
kbl(digits=3, caption="Comparación de umbrales: Youden vs Máx F1 (test)") %>%
kable_styling(full_width=FALSE)
| Umbral | AUC | F1 | Accuracy | Sensibilidad | Especificidad |
|---|---|---|---|---|---|
| 0.179 | 0.758 | 0.476 | 0.745 | 0.708 | 0.752 |
| 0.294 | 0.758 | 0.489 | 0.837 | 0.479 | 0.907 |
Logra un balance razonable entre sensibilidad (0.708) y especificidad (0.752).
Es más útil si se prioriza detectar la mayor proporción posible de empleados que sí rotan (evitar falsos negativos), aunque a costa de una menor exactitud global (Accuracy = 0.745).
Maximiza el F1 Score (0.489), mejorando la exactitud global (Accuracy = 0.837).
Sin embargo, sacrifica sensibilidad (0.479), lo que implica que casi la mitad de los empleados que rotan no son detectados por el modelo.
La especificidad sube a 0.907, lo que significa que clasifica muy bien a los empleados que no rotan.
En conclusión: El umbral de Youden es más adecuado si la prioridad es identificar casos de rotación (alto recall/sensibilidad), aunque se cometan más falsos positivos.
El umbral de Máx F1 favorece el equilibrio general del modelo y reduce falsos positivos, pero deja escapar muchos casos de rotación (falsos negativos).
install.packages("gt")
## package 'gt' successfully unpacked and MD5 sums checked
##
## The downloaded binary packages are in
## C:\Users\Lenovo\AppData\Local\Temp\RtmpecGiF1\downloaded_packages
library(gt)
# --- Confusión con umbral de Youden ---
pred_youden <- ifelse(eval_df$prob >= thr, 1, 0)
cm_youden <- caret::confusionMatrix(
factor(pred_youden, levels=c(0,1)),
factor(eval_df$y, levels=c(0,1)),
positive="1"
)
# --- Confusión con umbral de Máx F1 ---
pred_f1 <- ifelse(eval_df$prob >= thr_f1, 1, 0)
cm_f1 <- caret::confusionMatrix(
factor(pred_f1, levels=c(0,1)),
factor(eval_df$y, levels=c(0,1)),
positive="1"
)
cm_md <- function(cm, titulo = "Matriz de confusión") {
# OJO: cm$table tiene filas = Reference (Real), columnas = Prediction (Predicho)
TN <- cm$table[1, 1]
FP <- cm$table[1, 2]
FN <- cm$table[2, 1]
TP <- cm$table[2, 2]
tribble(
~`Real \\ Predicho`, ~`0 (No rotación)`, ~`1 (Rotación)`,
"0 (No)", paste0(TN, " (TN)"), paste0(FP, " (FP)"),
"1 (Sí)", paste0(FN, " (FN)"), paste0(TP, " (TP)")
) %>%
kableExtra::kbl(align = c("l", "c", "c"), caption = titulo) %>%
kableExtra::kable_styling(full_width = FALSE)
}
# Tablas para tus dos umbrales
cm_md(cm, sprintf("Matriz de confusión — Youden (%.3f)", thr))
| Real Predicho | 0 (No rotación) | 1 (Rotación) |
|---|---|---|
| 0 (No) | 185 (TN) | 14 (FP) |
| 1 (Sí) | 61 (FN) | 34 (TP) |
cm_md(cm_f1, sprintf("Matriz de confusión — Máx F1 (%.3f)", thr_f1))
| Real Predicho | 0 (No rotación) | 1 (Rotación) |
|---|---|---|
| 0 (No) | 223 (TN) | 25 (FP) |
| 1 (Sí) | 23 (FN) | 23 (TP) |
Matriz de confusión — Youden (Umbral 0.179)
TN = 185: empleados que no rotaron y fueron correctamente clasificados.
FP = 14: empleados que no rotaron pero el modelo predijo que sí (falsos positivos).
FN = 61: empleados que rotaron pero no fueron detectados (falsos negativos).
TP = 34: empleados que rotaron y el modelo lo predijo correctamente.
Interpretación:
El modelo es más sensible: logra detectar más rotaciones (34 TP) pero al mismo tiempo comete muchos falsos negativos (61 FN).
Es útil cuando la prioridad es detectar la mayor cantidad posible de empleados en riesgo de rotación, incluso si aumentan los errores al clasificar.
Matriz de confusión — Máx F1 (Umbral 0.294)
TN = 223: empleados que no rotaron y fueron correctamente clasificados.
FP = 25: empleados que no rotaron pero fueron clasificados como rotación.
FN = 23: empleados que rotaron pero no fueron detectados.
TP = 23: empleados que rotaron y fueron correctamente clasificados.
Interpretación:
El modelo es más preciso y específico: identifica muy bien a los que no rotan (223 TN, especificidad alta). Sin embargo, pierde sensibilidad: solo detecta 23 casos de rotación y deja escapar otros 23 (FN). Es más apropiado si el objetivo es minimizar los falsos positivos (evitar intervenir empleados que realmente no iban a rotar).
nuevo <- tibble(
horas_extra=factor("Si", levels=levels(datos$horas_extra)),
viaje_de_negocios=factor("Frecuentemente", levels=levels(datos$viaje_de_negocios)),
estado_civil=factor("Soltero", levels=levels(datos$estado_civil)),
antiguedad_cargo=1,
edad=22,
ingreso_mensual=median(datos$ingreso_mensual)
)
p_hat <- predict(mod_tr, newdata=nuevo, type="response")
decision <- ifelse(p_hat>=thr, "Intervenir (alto riesgo)", "Monitorear")
tribble(~prob_rotacion, ~umbral, ~decision,
p_hat, thr, decision) %>%
kbl(digits=3, caption="Predicción de probabilidad y regla de decisión") %>%
kable_styling(full_width=FALSE)
| prob_rotacion | umbral | decision |
|---|---|---|
| 0.746 | 0.179 | Intervenir (alto riesgo) |
Condiciones del empleado hipotético:
Horas extra: Sí.
Viajes de negocios: Frecuentemente.
Estado civil: Soltero.
Antigüedad en el cargo: 1 año.
Edad: 22 años (empleado joven).
Ingreso mensual: valor mediano de la empresa (~4919 USD).
Resultado de la predicción:
Probabilidad de rotación: 74.6% (0.746).
Umbral de decisión: 0.179 (criterio de Youden).
Clasificación: Intervenir (alto riesgo).
Interpretación:
El modelo indica que un empleado joven, soltero, con poca antigüedad,
que trabaja horas extra y viaja frecuentemente por negocios tiene un
riesgo muy alto de rotación.
Dado que su probabilidad de rotación (74.6%) supera ampliamente el
umbral (17.9%), la recomendación es aplicar estrategias de
retención inmediatas, como:
Programas de motivación y balance trabajo–vida.
Reducción de viajes excesivos.
Incentivos para permanencia y desarrollo profesional.
Síntesis: La rotación está asociada con condiciones de sobrecarga laboral, viajes frecuentes, menor edad y antigüedad.