1 Contexto

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

2 Carga de datos y librerías

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

3 Diccionario de variables

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)
Data summary
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.

4 Interpretación exploratoria de skimr

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.

5 Selección de variables e hipótesis

Seleccionamos 3 variables categóricas y 3 cuantitativas relacionadas con la rotación, justificando hipótesis:

Categóricas:

  1. horas_extra – Hipótesis: empleados con horas extra tienen mayor rotación por desgaste laboral.

  2. viaje_de_negocios – Hipótesis: viajes frecuentes aumentan la rotación por carga y movilidad.

  3. estado_civil – Hipótesis: solteros presentan más rotación que casados (mayor movilidad laboral).

Cuantitativas:

  1. antiguedad_cargo – Hipótesis: menor antigüedad implica mayor rotación.

  2. edad – Hipótesis: a mayor edad del empleado se reduce la rotación.

  3. 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)
Distribución de la respuesta (desbalance)
y n prop
0 1233 83.9
1 237 16.1

5.1 Verificación de escalas de medición

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)
Rangos observados de escalas (1 = bajo, valores mayores = más alto)
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.

6 Análisis univariado

6.1 Variable respuesta y (rotación)

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.

6.2 Varibles categóricas seleccionadas

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.

6.3 Variables numéricas seleccionadas

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

7 Análisis bivariado + pruebas estadísticas

7.1 Categóricas vs y (tasas y Chi-cuadrado)

# 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)
Tasa de rotación por categoría
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)
Chi-cuadrado: asociación categoría ~ rotación
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.

7.2 Numéricas vs y (boxplots y regresiones univariadas)

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)
Univariado (OR) para variables numéricas
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).

8 Modelo de regresión logístico

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)
Odds Ratios (multivariado) con IC95%
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)
Criterios de selección del modelo
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.

8.1 Re-escalar ingreso para interpretar mejor (por 1.000 USD)

# 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)
Odds Ratios con IC95% (ingreso por 1.000 USD)
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.

8.2 Forest plot (OR con IC95%) con etiquetas legibles

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.

9 Evaluación predictiva con atención al desbalance

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)
Comparación de umbrales: Youden vs Máx F1 (test)
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
  • Umbral de Youden (0.179):

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

  • Umbral de Máx F1 (0.294):

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

9.1 Tablas de confusión para umbrales de Youden y Máx F1

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))
Matriz de confusión — Youden (0.179)
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))
Matriz de confusión — Máx F1 (0.294)
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).

10 Predicción de caso hipotético

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)
Predicción de probabilidad y regla de decisión
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.

11 Conclusiones y recomendaciones gerenciales

11.1 Conclusiones

  • Factores determinantes de la rotación:
    • Trabajar horas extra y estar soltero aumentan significativamente la probabilidad de rotación.
    • Los empleados que viajan frecuentemente presentan mayor riesgo de rotación, mientras que quienes no viajan muestran menor propensión.
    • Una menor antigüedad en el cargo y menor edad incrementan el riesgo de rotación.
    • El ingreso mensual (re-escalado por 1.000 USD) actúa como factor protector: a mayor ingreso, menor probabilidad de rotación.
  • Desempeño del modelo:
    • El modelo logístico multivariado alcanzó un AUC de ~0.76, lo que indica un poder predictivo aceptable en un dataset desbalanceado (83.9% empleados no rotan vs 16.1% que sí).
    • El umbral de Youden (0.179) ofrece mayor sensibilidad (detecta más casos de rotación), mientras que el umbral de Máx F1 (0.294) prioriza la especificidad (clasifica mejor a quienes no rotan).
    • Existe un trade-off entre sensibilidad y precisión, propio de escenarios con clases desbalanceadas.
  • Predicciones individuales:
    • Un empleado joven (22 años), soltero, con 1 año de antigüedad, que trabaja horas extra y viaja frecuentemente fue clasificado con una probabilidad de rotación del 74.6%, resultando en alto riesgo según el criterio de Youden.

11.2 Recomendaciones gerenciales

  • Gestión de la carga laboral:
    • Reducir la dependencia de horas extra mediante redistribución de cargas o contratación de apoyo.
    • Implementar incentivos específicos para compensar jornadas extendidas.
  • Atención a perfiles vulnerables:
    • Diseñar estrategias de retención dirigidas a empleados jóvenes, solteros y con baja antigüedad.
    • Ofrecer planes de carrera, mentorías y programas de crecimiento para incrementar compromiso.
  • Política de viajes:
    • Revisar la política de viajes frecuentes, que impacta la satisfacción y permanencia.
    • Establecer mecanismos de rotación de responsabilidades o apoyo logístico para mitigar desgaste.
  • Incentivos económicos y equidad salarial:
    • Mantener políticas de incentivos económicos competitivos, ya que mayores ingresos reducen la rotación.
    • Asegurar la equidad interna para evitar brechas salariales injustificadas.
  • Uso del modelo en decisiones:
    • Aplicar el modelo como herramienta predictiva de apoyo gerencial, integrándolo con indicadores de clima y desempeño.
    • Ajustar el umbral de decisión según la estrategia:
      • Youden (0.179): priorizar la detección de la mayoría de los casos de rotación.
      • Máx F1 (0.294): reducir intervenciones innecesarias en empleados que no rotarían.

Síntesis: La rotación está asociada con condiciones de sobrecarga laboral, viajes frecuentes, menor edad y antigüedad.