1 Contexto

En una organización, se busca comprender y prever los factores que influyen en la rotación de empleados entre distintos cargos. La empresa ha recopilado datos históricos sobre el empleo de sus trabajadores, incluyendo variables como la antigüedad en el cargo actual, el nivel de satisfacción laboral, el salario actual, edad y otros factores relevantes. La gerencia planea desarrollar un modelo de regresión logística que permita estimar la probabilidad de que un empleado cambie de cargo en el próximo período y determinar cuales factores indicen en mayor proporción a estos cambios.

Con esta información, la empresa podrá tomar medidas proactivas para retener a su talento clave, identificar áreas de mejora en la gestión de recursos humanos y fomentar un ambiente laboral más estable y tranquilo. La predicción de la probabilidad de rotación de empleados ayudará a la empresa a tomar decisiones estratégicas informadas y a mantener un equipo de trabajo comprometido y satisfecho en sus roles actuales.

Los tipos de variable de acuerdo con su naturaleza son los siguientes:

tabla_variables <- data.frame(
  Variable = c("Rotación",
               "Edad",
               "Viaje_de_Negocios",
               "Departamento",
               "Distancia_Casa","Educación","Campo_Educación",
               "Satisfacción_Ambiental","Genero","Cargo",
               "Satisfación_Laboral","Estado_Civil","Ingreso_Mensual",
               "Trabajos_Anteriores","Horas_Extra",
               "Porcentaje_aumento_salarial","Rendimiento_Laboral",
               "Años_Experiencia","Capacitaciones",
               "Equilibrio_Trabajo_Vida","Antigüedad",
               "Antigüedad_Cargo","Años_ultima_promoción",
               "Años_acargo_con_mismo_jefe"),
  
  Tipo_estadistico = c("Categórica nominal",
                       "Numérica discreta",
                       "Categórica ordinal",
                       "Categórica nominal",
                       "Numérica continua",
                       "Ordinal",
                       "Categórica nominal",
                       "Ordinal",
                       "Categórica nominal",
                       "Categórica nominal",
                       "Ordinal",
                       "Categórica nominal",
                       "Numérica continua",
                       "Numérica discreta",
                       "Binaria",
                       "Numérica continua",
                       "Ordinal",
                       "Numérica continua",
                       "Numérica discreta",
                       "Ordinal",
                       "Numérica continua",
                       "Numérica continua",
                       "Numérica discreta",
                       "Numérica continua"),
  
  Codificacion = c(
    "0=No rota, 1=Rota",
    "Edad en años",
    "Raramente / Frecuentemente / No viaja",
    "Área funcional (Ventas, IyD, etc.)",
    "Kilómetros desde la casa al trabajo",
    "1=Primaria, 2=Secundaria, 3=Técnico, 4=Pregrado, 5=Posgrado",
    "Área de formación",
    "1=Muy insatisfecho, 2=Insatisfecho, 3=Satisfecho, 4=Muy satisfecho",
    "F / M",
    "Tipo de cargo",
    "1=Muy insatisfecho, 2=Insatisfecho, 3=Satisfecho, 4=Muy satisfecho",
    "Soltero / Casado / Divorciado",
    "Ingreso mensual",
    "Número de trabajos previos",
    "0=No, 1=Sí",
    "Porcentaje de incremento salarial",
    "1=Bajo, 2=Medio, 3=Alto, 4=Muy alto",
    "Años de experiencia laboral",
    "Número de capacitaciones",
    "1=Muy bajo, 2=Bajo, 3=Medio, 4=Alto",
    "Años en la empresa",
    "Años en el cargo",
    "Años desde última promoción",
    "Años con el mismo jefe"
  )
)

kable(tabla_variables,caption = "Descripción de las variables de la base de datos")
Descripción de las variables de la base de datos
Variable Tipo_estadistico Codificacion
Rotación Categórica nominal 0=No rota, 1=Rota
Edad Numérica discreta Edad en años
Viaje_de_Negocios Categórica ordinal Raramente / Frecuentemente / No viaja
Departamento Categórica nominal Área funcional (Ventas, IyD, etc.)
Distancia_Casa Numérica continua Kilómetros desde la casa al trabajo
Educación Ordinal 1=Primaria, 2=Secundaria, 3=Técnico, 4=Pregrado, 5=Posgrado
Campo_Educación Categórica nominal Área de formación
Satisfacción_Ambiental Ordinal 1=Muy insatisfecho, 2=Insatisfecho, 3=Satisfecho, 4=Muy satisfecho
Genero Categórica nominal F / M
Cargo Categórica nominal Tipo de cargo
Satisfación_Laboral Ordinal 1=Muy insatisfecho, 2=Insatisfecho, 3=Satisfecho, 4=Muy satisfecho
Estado_Civil Categórica nominal Soltero / Casado / Divorciado
Ingreso_Mensual Numérica continua Ingreso mensual
Trabajos_Anteriores Numérica discreta Número de trabajos previos
Horas_Extra Binaria 0=No, 1=Sí
Porcentaje_aumento_salarial Numérica continua Porcentaje de incremento salarial
Rendimiento_Laboral Ordinal 1=Bajo, 2=Medio, 3=Alto, 4=Muy alto
Años_Experiencia Numérica continua Años de experiencia laboral
Capacitaciones Numérica discreta Número de capacitaciones
Equilibrio_Trabajo_Vida Ordinal 1=Muy bajo, 2=Bajo, 3=Medio, 4=Alto
Antigüedad Numérica continua Años en la empresa
Antigüedad_Cargo Numérica continua Años en el cargo
Años_ultima_promoción Numérica discreta Años desde última promoción
Años_acargo_con_mismo_jefe Numérica continua Años con el mismo jefe

Se observa que es necesario recodificar varias variables para poder incluirlas en la modelación.

# Preparación de datos

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
Rotación 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_Educación 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 ▇▅▂▂▂
Educación 0 1 2.91 1.02 1 2 3 4 5 ▂▃▇▆▁
Satisfacción_Ambiental 0 1 2.72 1.09 1 2 3 4 4 ▅▅▁▇▇
Satisfación_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 ▇▁▁▁▂
Años_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 ▁▃▁▇▂
Antigüedad 0 1 7.01 6.13 0 3 5 9 40 ▇▂▁▁▁
Antigüedad_Cargo 0 1 4.23 3.62 0 2 3 7 18 ▇▃▂▁▁
Años_ultima_promoción 0 1 2.19 3.22 0 0 1 3 15 ▇▁▁▁▁
Años_acargo_con_mismo_jefe 0 1 4.12 3.57 0 2 3 7 17 ▇▂▅▁▁

La base de datos contiene 1470 observaciones y 24 variables, incluyendo variables categóricas (como Rotación, Departamento, Horas_Extra) y variables cuantitativas (como Edad, Ingreso_Mensual y Antigüedad).

# RECODIFICACIÓN COMPLETA DE VARIABLES

# Copia de seguridad (opcional pero recomendado)
rotacion2 <- rotacion


# 1. Variable respuesta


rotacion2 <- rotacion2 %>%
  mutate(
    Rotación = factor(Rotación,
                      levels = c("No","Si"),
                      labels = c("No rota","Rota")),
    
    y = ifelse(Rotación == "Rota", 1, 0)
  )


# 2. Variables categóricas nominales


rotacion2 <- rotacion2 %>%
  mutate(
    `Viaje de Negocios` = factor(`Viaje de Negocios`),
    Departamento = factor(Departamento),
    Campo_Educación = factor(Campo_Educación),
    Genero = factor(Genero),
    Cargo = factor(Cargo),
    Estado_Civil = factor(Estado_Civil),
    Horas_Extra = factor(Horas_Extra, levels = c("No","Si"))
  )


# 3. Variables ordinales


rotacion2 <- rotacion2 %>%
  mutate(
    Educación = factor(Educación,
                       levels = c(1,2,3,4,5),
                       labels = c("Primaria","Secundaria","Tecnico","Pregrado","Posgrado"),
                       ordered = TRUE),
    
    Satisfacción_Ambiental = factor(Satisfacción_Ambiental,
                                   levels = c(1,2,3,4),
                                   labels = c("Muy insatisfecho","Insatisfecho","Satisfecho","Muy satisfecho"),
                                   ordered = TRUE),
    
    Satisfación_Laboral = factor(Satisfación_Laboral,
                                levels = c(1,2,3,4),
                                labels = c("Muy insatisfecho","Insatisfecho","Satisfecho","Muy satisfecho"),
                                ordered = TRUE),
    
    Rendimiento_Laboral = factor(Rendimiento_Laboral,
                                levels = c(1,2,3,4),
                                labels = c("Bajo","Medio","Alto","Muy alto"),
                                ordered = TRUE),
    
    Equilibrio_Trabajo_Vida = factor(Equilibrio_Trabajo_Vida,
                                    levels = c(1,2,3,4),
                                    labels = c("Muy bajo","Bajo","Medio","Alto"),
                                    ordered = TRUE)
  )


# 4. Variables numéricas


rotacion2 <- rotacion2 %>%
  mutate(
    Edad = as.numeric(Edad),
    Distancia_Casa = as.numeric(Distancia_Casa),
    Ingreso_Mensual = as.numeric(Ingreso_Mensual),
    Trabajos_Anteriores = as.numeric(Trabajos_Anteriores),
    Porcentaje_aumento_salarial = as.numeric(Porcentaje_aumento_salarial),
    Años_Experiencia = as.numeric(Años_Experiencia),
    Capacitaciones = as.numeric(Capacitaciones),
    Antigüedad = as.numeric(Antigüedad),
    Antigüedad_Cargo = as.numeric(Antigüedad_Cargo),
    Años_ultima_promoción = as.numeric(Años_ultima_promoción),
    Años_acargo_con_mismo_jefe = as.numeric(Años_acargo_con_mismo_jefe)
  )

La correcta clasificación de las variables permite diferenciar entre variables nominales, ordinales y numéricas, lo cual es fundamental para la especificación del modelo de datos. En particular, las variables ordinales fueron tratadas respetando su estructura jerárquica, evitando su incorrecta transformación en variables nominales, lo que podría generar pérdida de información relevante. Una vez codificadas correctamente se procede a realizar el procesamiento de datos.

2 Procesamiento de Datos - Análisis exploratorio

2.1 Análisis univariado

# Variables numéricas
vars_num <- c("Edad","Distancia_Casa","Ingreso_Mensual",
              "Trabajos_Anteriores","Porcentaje_aumento_salarial",
              "Años_Experiencia","Capacitaciones",
              "Antigüedad","Antigüedad_Cargo",
              "Años_ultima_promoción","Años_acargo_con_mismo_jefe")

# Función para crear histogramas pequeños
crear_hist <- function(var){
  ggplot(rotacion2, aes_string(x = var)) +
    geom_histogram(bins = 25,
                   fill = "#66c2a5",   # color bonito
                   color = "white",
                   alpha = 0.9) +
    theme_minimal(base_size = 9) +
    labs(title = var,
         x = "",
         y = "") +
    theme(
      plot.title = element_text(size = 9, face = "bold"),
      axis.text = element_text(size = 7)
    )
}

# Crear lista de gráficos
plots <- lapply(vars_num, crear_hist)

# Organizar en grilla (3 columnas)
wrap_plots(plots, ncol = 3)

vars_muy_alta<- c("Ingreso_Mensual")
vars_alta <- c("Antigüedad","Antigüedad_Cargo",
               "Años_Experiencia","Distancia_Casa",
               "Años_acargo_con_mismo_jefe","Edad","Porcentaje_aumento_salarial")

vars_baja <- c("Trabajos_Anteriores","Capacitaciones","Años_ultima_promoción")

data_baja <- rotacion2 %>%
  select(all_of(vars_baja)) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "valor")


g_baja <- ggplot(data_baja, aes(x = variable, y = valor, fill = variable)) +
  geom_violin(alpha = 0.6) +
  geom_boxplot(width = 0.15, color = "black", alpha = 0.8, outlier.color = "red") +
  theme_minimal(base_size = 10) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        legend.position = "none") +
  labs(title = "Distribución variables numéricas (escala baja)",
       x = "", y = "Valor") +
  scale_fill_brewer(palette = "Pastel1")

g_baja

data_alta <- rotacion2 %>%
  select(all_of(vars_alta)) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "valor")

g_alta <- ggplot(data_alta, aes(x = variable, y = valor, fill = variable)) +
  geom_violin(alpha = 0.6) +
  geom_boxplot(width = 0.15, color = "black", alpha = 0.8, outlier.color = "red") +
  theme_minimal(base_size = 10) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        legend.position = "none") +
  labs(title = "Distribución variables numéricas (escala alta)",
       x = "", y = "Valor") +
  scale_fill_brewer(palette = "Pastel1")

g_alta

Las variables de escala alta presentan distribuciones asimétricas con presencia de valores extremos, como por ejemplo, en ingreso y años desde la última promoción.

Se puede identificar datos atípicos con respecto a los años de experiencia de los colaboradores, esto puede estar relacionado con la edad de estos y con la cantidad de empresas en las que han laborado anteriormente. Por otra parte, hay valores atípicos en los ingresos mensuales producto de la variedad de perfiles laborales dentro de la empresa, personas con altos cargos gerenciales técnicos especializados y profesionales con alto número de capacitaciones y responsabilidades.

La edad presenta una distribución concentrada en adultos jóvenes. Esto puede indicar una fuerza laboral relativamente joven, lo cual podría estar asociado con mayor rotación debido a menor estabilidad laboral.

data_muy_alta <- rotacion2 %>%
  select(all_of(vars_muy_alta)) %>%
  pivot_longer(cols = everything(),
               names_to = "variable",
               values_to = "valor")


g_muy_alta <- ggplot(data_muy_alta, aes(x = variable, y = valor, fill = variable)) +
  geom_violin(alpha = 0.6) +
  geom_boxplot(width = 0.15, color = "black", alpha = 0.8, outlier.color = "red") +
  theme_minimal(base_size = 10) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1),
        legend.position = "none") +
  labs(title = "Distribución variables numéricas (escala muy alta)",
       x = "", y = "Valor") +
  scale_fill_brewer(palette = "Pastel1")

g_muy_alta

El ingreso mensual presenta dispersión, lo que sugiere desigualdad salarial dentro de la empresa. Niveles bajos de ingreso podrían estar asociados con mayor oportunidad de rotación. Adicionalmente, se observan datos atípicos superiores, pero se consideran esperados, ya que pueden corresponder a cargos directivos, por lo cual son pocos quienes tienen muy altos ingresos.

Los gráficos de barras permiten observar la distribución de las variables categóricas. Se identifican concentraciones importantes en ciertas categorías, particularmente en variables como departamento y cargo, lo que sugiere una estructura organizacional no homogénea. Asimismo, variables como horas extra y estado civil muestran distribuciones que podrían estar asociadas con la rotación laboral.

Se observa que la mayoría de empleadostrabaja horas extra (71.7%). Una alta frecuencia de esta condición puede indicar sobrecarga laboral, lo cual podría estar relacionado con mayores niveles de rotación.

Se identifica la distribución del estado civil de los empleados. Si predominan los empleados solteros, esto podría influir en una mayor movilidad laboral y, por tanto, en la rotación.

Se observan diferencias importantes entre departamentos, lo que puede sugerir condiciones laborales distintas que influyen en la rotación.

La edad presenta una distribución concentrada en adultos jóvenes. Esto puede indicar una fuerza laboral relativamente joven, lo cual podría estar asociado con mayor rotación debido a menor estabilidad laboral.

De los diagramas anteriores se puede identificar que:

  • La satisfacción laboral del personal se compone de cuatro niveles, siendo 1 mala satisfacción laboral y 4 una excelente satisfacción laboral. Para este estudio, se puede afirmar que el 64% de los empleados presenta una satisfacción aceptable.

  • El departamento que más personal agrupa es IyD con un 65% seguido del departamento de ventas con un 31% y finalmente RH con un 4%.

  • Solo el 29% del personal realiza horas extras.

  • Solo el 16% del personal presenta rotación.

Es importante comprender que la rotación de personal no se puede asociar en concreto a una variable que algún colaborador presente o no.

Posteriormente, se realiza un estudio con indicadores de tendencia central, posición y varianza para comprender los datos, esta información se ilustra en la sección de anexos.

ggplot(rotacion2, aes(x = Rotación, fill = Rotación)) +
  geom_bar(alpha = 0.9) +
  
  # Etiquetas: conteo + porcentaje
  geom_text(
    stat = "count",
    aes(label = paste0(after_stat(count), "\n(",
                       round(after_stat(count) / sum(after_stat(count)) * 100, 1),
                       "%)")),
    vjust = 0.5,
    color = "black",
    size = 4
  ) +
  
  theme_minimal() +
  labs(title = "Distribución de la Rotación",
       x = "", y = "Frecuencia") +
  scale_fill_manual(values = c("#66c2a5", "#fc8d62"))

La variable de rotación presenta desbalance, lo cual es relevante para la estimación del modelo logístico y su capacidad predictiva.

Se observa que la mayoría de los empleados no rotan (83.9%), mientras que una menor proporción sí lo hace (16.1%). Sin embargo, este grupo representa un porcentaje relevante que justifica el análisis, ya que la rotación implica costos importantes para la empresa.

2.2 Análisis bivariado

Con el objetivo de identificar la relación entre la variable respuesta Rotación y las variables explicativas seleccionadas, se realizaron pruebas estadísticas bivariadas adecuadas según el tipo de variable.

vars_cat <- c("Viaje de Negocios","Departamento","Campo_Educación",
              "Genero","Cargo","Estado_Civil","Horas_Extra")


crear_bivar_cat <- function(var){
  
  # Calcular proporciones correctamente
  data_plot <- rotacion2 %>%
    group_by(.data[[var]], Rotación) %>%
    summarise(n = n(), .groups = "drop") %>%
    group_by(.data[[var]]) %>%
    mutate(prop = n / sum(n))
  
  ggplot(data_plot,
         aes(x = .data[[var]], y = prop, fill = Rotación)) +
    
    geom_bar(stat = "identity", position = "fill", alpha = 0.9) +
    
    # Etiquetas correctas
    geom_text(aes(label = paste0(round(prop*100,1), "%")),
              position = position_fill(vjust = 0.5),
              size = 3) +
    
    theme_minimal(base_size = 9) +
    labs(title = var,
         x = "",
         y = "Proporción") +
    
    scale_fill_manual(values = c("#66c2a5", "#fc8d62")) +
    
    theme(
      axis.text.x = element_text(angle = 45, hjust = 1, size = 7),
      plot.title = element_text(size = 9, face = "bold")
    )
}

plots_cat_biv <- lapply(vars_cat, crear_bivar_cat)

wrap_plots(plots_cat_biv, ncol = 2)

2.2.1 Matriz de correlación de variables numéricas

La matriz de correlación permite identificar relaciones lineales entre variables numéricas. Valores cercanos a 1 o -1 indican relaciones fuertes entre variables.

2.2.2 Test sobre coeficientes de Correlación

Considerando lo mencionado anteriormente, es pertinente realizar un pequeño test mediante coeficientes de correlación, chi cuadrado para variables categóricas o dicotómicas (Satisfacción laboral, Horas extra y Departamento) y el test de Mann-Whitney como alternativa a la prueba t-student dado que no se sigue una distribución normal.

library(lsr)
library(kableExtra)

datos_filtrado<-rotacion2
test_mw <- wilcox.test(Ingreso_Mensual ~ Rotación, data = datos_filtrado)
p_val_mw <- test_mw$p.value


test_anos <- wilcox.test(Años_Experiencia ~ Rotación, data = datos_filtrado)
p_val_anos <- test_anos$p.value


test_casa <- wilcox.test(Distancia_Casa ~ Rotación, data = datos_filtrado)
p_val_casa <- test_anos$p.value


test_chihoras <- chisq.test(table(datos_filtrado$Horas_Extra, datos_filtrado$Rotación))
p_valHoras_extra <- test_chihoras$p.value


test_chidep <- chisq.test(table(datos_filtrado$Departamento, datos_filtrado$Rotación))
p_valDepartamento <- test_chidep$p.value



test_chisat <- chisq.test(table(datos_filtrado$Satisfación_Laboral, datos_filtrado$Rotación))
p_valSatisfaccion <- test_chisat$p.value



# 1. Cálculos de Coeficientes (Fuerza)
# Para Mann-Whitney/Spearman usamos cor(method="spearman")
r_ingreso <- abs(cor(datos_filtrado$Ingreso_Mensual, as.numeric(as.factor(datos_filtrado$Rotación)), method="spearman"))
r_años    <- abs(cor(datos_filtrado$Años_Experiencia, as.numeric(as.factor(datos_filtrado$Rotación)), method="spearman"))
r_casa    <- abs(cor(datos_filtrado$Distancia_Casa, as.numeric(as.factor(datos_filtrado$Rotación)), method="spearman"))


# Para Categorías usamos V de Cramer
v_satisfaccion <- lsr::cramersV(table(datos_filtrado$Satisfación_Laboral, datos_filtrado$Rotación))
v_depto        <- lsr::cramersV(table(datos_filtrado$Departamento, datos_filtrado$Rotación))
v_horas        <- lsr::cramersV(table(datos_filtrado$Horas_Extra, datos_filtrado$Rotación))

# 2. Crear el Dataframe con AMBOS datos
Tabla_enunciado <- data.frame(
  Metrica = c("Fuerza (r / V)", "P-Valor"),
  Ingreso = c(round(r_ingreso, 3), round(p_val_mw, 4)),
  Años = c(round(r_años, 3), round(p_val_anos, 4)),
  Distancia = c(round(r_casa, 3), round(p_val_casa, 4)),
  Satisfaccion = c(round(v_satisfaccion, 3), round(p_valSatisfaccion, 4)),
  Depto = c(round(v_depto, 3), round(p_valDepartamento, 4)),
  Horas_Extra = c(round(v_horas, 3), round(p_valHoras_extra, 4))
)

# 3. Formatear la tabla
colnames(Tabla_enunciado) <- c("Indicador", "Ingreso Mensual", "Años Experiencia", "Distancia Casa", "Satisfacción", "Departamento", "Horas Extra")

knitr::kable(Tabla_enunciado, 
             booktabs = TRUE, 
             caption = "<center><b>Análisis de Correlaciones</b></center>",
             escape = FALSE) %>%
  kable_styling(full_width = TRUE, bootstrap_options = c("striped", "hover"))
Análisis de Correlaciones
Indicador Ingreso Mensual Años Experiencia Distancia Casa Satisfacción Departamento Horas Extra
Fuerza (r / V) 0.198 0.199 0.079 0.1090 0.0860 0.244
P-Valor 0.000 0.000 0.000 0.0006 0.0045 0.000

De la anterior tabla podemos confirmar la relación entre las variables y rotación, empezando por horas extra (0.244), seguido de años de experiencia (0.199), ingreso mensual (0.198), satisfacción (0.109), departamento (0.086) y Distancia de la casa (0.079).

El análisis bivariado evidencia que variables como horas extra, ingreso mensual y antigüedad presentan diferencias significativas entre los grupos de empleados que rotan y los que no. En particular, la rotación se asocia con mayores cargas laborales y menores niveles de ingreso, lo cual es consistente con las hipótesis planteadas inicialmente. Asimismo, se observan diferencias entre departamentos, lo que sugiere heterogeneidad en las condiciones laborales dentro de la organización.

Se observan correlaciones positivas entre variables como antigüedad y experiencia, lo cual es consistente con la dinámica laboral. Asimismo, se identifican diferencias en la distribución de los datos según la rotación, particularmente en variables como ingreso y satisfacción laboral.

3 Selección de variables

La selección de variables se realizó considerando tanto el análisis exploratorio previo como la relevancia teórica de cada variable en la explicación de la rotación laboral. Se optó por incluir un conjunto parsimonioso de predictores que combinan características laborales, organizacionales y de experiencia del empleado.

3.1 Variables categóricas seleccionadas:

  • Horas_Extra: Representa la carga laboral adicional del empleado.

Hipótesis: Se espera que la realización de horas extra incremente la oportunidad de rotación, debido al desgaste laboral, menor equilibrio trabajo-vida y posibles efectos negativos sobre el bienestar del empleado.

  • Cargo: Refleja el rol desempeñado dentro de la organización.

Hipótesis: Se espera que la oportunidad de rotación varíe según el tipo de cargo, particularmente en aquellos roles con mayores niveles de presión comercial, carga operativa o menor estabilidad, como ventas o cargos técnicos, los cuales podrían presentar mayores niveles de rotación en comparación con cargos administrativos o directivos.

  • Equilibrio_Trabajo_Vida: Mide el balance entre las responsabilidades laborales y la vida personal.

Hipótesis: Se espera que un menor nivel de equilibrio entre el trabajo y la vida personal incremente la oportunidad de rotación, ya que afecta negativamente la satisfacción y el bienestar del empleado.

3.2 Variables cuantitativas seleccionadas:

  • Distancia_Casa: Indica la distancia entre el hogar y el lugar de trabajo.

Hipótesis: Se espera que un mayor nivel de distancia entre el hogar y el lugar de trabajo incremente la oportunidad de rotación, debido a mayores costos de desplazamiento y menor bienestar asociado al tiempo de traslado.

  • Trabajos_Anteriores: Número de empleos previos del trabajador.

Hipótesis: Los empleados con mayor número de trabajos anteriores presentan mayor probabilidad de rotación, ya que pueden reflejar menor estabilidad laboral.

  • Antigüedad: Se espera que un mayor número de trabajos anteriores esté asociado con una mayor oportunidad de rotación, ya que refleja una menor estabilidad laboral o una mayor propensión a cambiar de empleo.

Hipótesis: Se espera que una mayor antigüedad en la empresa reduzca la oportunidad de rotación, dado que los empleados con mayor tiempo tienden a desarrollar mayor estabilidad, compromiso organizacional y costos de salida más altos.

4 Estimación del modelo

# Definir receta (preprocesamiento)



rotacion<-rotacion2 %>% 
  select(-y)

# receta con todas las variables


#receta <- recipe(Rotación ~ ., data = rotacion) %>%

# receta con 3 variables categóricas y 3 numéricas  
# mis variables elegidas son: 
receta <- recipe(
  Rotación ~ Horas_Extra + Cargo + Equilibrio_Trabajo_Vida +
              Distancia_Casa + Trabajos_Anteriores + Antigüedad,
  data = rotacion
) %>% 

  # Convertir variables categóricas a dummies
  step_dummy(all_nominal_predictors()) %>%

  # Normalizar variables numéricas
  step_normalize(all_numeric_predictors()) %>%

  # Eliminar variables con varianza cero (si existen)
  step_zv(all_predictors())


# Definir modelo logístico

modelo_log <- logistic_reg() %>%
  set_engine("glm")


# Crear workflow


workflow_modelo <- workflow() %>%
  add_recipe(receta) %>%
  add_model(modelo_log)


# Ajustar modelo


ajuste <- fit(workflow_modelo, data = rotacion)


# Resultados del modelo


# Coeficientes del modelo

resultados <- tidy(ajuste)

print(resultados)
## # A tibble: 16 × 5
##    term                          estimate std.error statistic  p.value
##    <chr>                            <dbl>     <dbl>     <dbl>    <dbl>
##  1 (Intercept)                   -2.07       0.0999  -20.8    9.14e-96
##  2 Distancia_Casa                 0.249      0.0757    3.28   1.02e- 3
##  3 Trabajos_Anteriores            0.219      0.0767    2.86   4.30e- 3
##  4 Antigüedad                    -0.223      0.108    -2.07   3.82e- 2
##  5 Horas_Extra_Si                 0.682      0.0722    9.43   3.92e-21
##  6 Cargo_Director_Manofactura     0.301      0.238     1.26   2.07e- 1
##  7 Cargo_Ejecutivo_Ventas         0.890      0.308     2.89   3.86e- 3
##  8 Cargo_Gerente                  0.207      0.220     0.941  3.47e- 1
##  9 Cargo_Investigador_Cientifico  0.754      0.298     2.53   1.14e- 2
## 10 Cargo_Recursos_Humanos         0.465      0.149     3.12   1.82e- 3
## 11 Cargo_Representante_Salud      0.271      0.231     1.18   2.39e- 1
## 12 Cargo_Representante_Ventas     0.801      0.179     4.48   7.45e- 6
## 13 Cargo_Tecnico_Laboratorio      0.979      0.284     3.45   5.67e- 4
## 14 Equilibrio_Trabajo_Vida_1     -0.187      0.0779   -2.40   1.63e- 2
## 15 Equilibrio_Trabajo_Vida_2      0.254      0.0750    3.39   7.01e- 4
## 16 Equilibrio_Trabajo_Vida_3     -0.00770    0.0862   -0.0893 9.29e- 1
# Importancia de variables (VIP)


# Extraer modelo y graficar importancia
vip(extract_fit_parsnip(ajuste)) +
  geom_col(fill = "salmon")

La importancia de variables fue evaluada a partir de la magnitud de los coeficientes estimados en el modelo logístico. Se observa que variables como las horas extra y el cargo presentan mayor influencia en la probabilidad de rotación. No obstante, esta medida debe interpretarse con cautela, ya que no considera el tipo de variable, ni la significancia estadística.

# Construir tabla de OR
tabla_or <- resultados %>%
  mutate(
    Odds_Ratio = exp(estimate),
    OR_inf = exp(estimate - 1.96 * std.error),
    OR_sup = exp(estimate + 1.96 * std.error),
    p_value = round(p.value, 4)
  ) %>%
  select(term, Odds_Ratio, OR_inf, OR_sup, p_value) %>%
  mutate(across(c(Odds_Ratio, OR_inf, OR_sup), round, 2)) %>%
  filter(term != "(Intercept)")

# Mostrar tabla bonita
tabla_or %>%
  kable(caption = "Odds Ratios del modelo logístico",
        col.names = c("Variable", "Odds Ratio", "IC 2.5%", "IC 97.5%", "p-valor")) %>%
  kable_styling(full_width = FALSE, position = "center")
Odds Ratios del modelo logístico
Variable Odds Ratio IC 2.5% IC 97.5% p-valor
Distancia_Casa 1.28 1.11 1.49 0.0010
Trabajos_Anteriores 1.24 1.07 1.45 0.0043
Antigüedad 0.80 0.65 0.99 0.0382
Horas_Extra_Si 1.98 1.72 2.28 0.0000
Cargo_Director_Manofactura 1.35 0.85 2.16 0.2071
Cargo_Ejecutivo_Ventas 2.44 1.33 4.46 0.0039
Cargo_Gerente 1.23 0.80 1.89 0.3468
Cargo_Investigador_Cientifico 2.13 1.19 3.81 0.0114
Cargo_Recursos_Humanos 1.59 1.19 2.13 0.0018
Cargo_Representante_Salud 1.31 0.83 2.06 0.2393
Cargo_Representante_Ventas 2.23 1.57 3.16 0.0000
Cargo_Tecnico_Laboratorio 2.66 1.53 4.65 0.0006
Equilibrio_Trabajo_Vida_1 0.83 0.71 0.97 0.0163
Equilibrio_Trabajo_Vida_2 1.29 1.11 1.49 0.0007
Equilibrio_Trabajo_Vida_3 0.99 0.84 1.17 0.9288

4.1 Forest Plot

El forest plot muestra los odds ratios estimados y sus intervalos de confianza. Los valores superiores a 1 indican un aumento en la probabilidad de rotación, mientras que valores inferiores a 1 indican un efecto protector. Las variables cuyos intervalos de confianza no cruzan el valor 1 son estadísticamente significativas.

library(ggplot2)

# Preparar datos
forest_data <- resultados %>%
  mutate(
    Odds_Ratio = exp(estimate),
    OR_inf = exp(estimate - 1.96 * std.error),
    OR_sup = exp(estimate + 1.96 * std.error)
  ) %>%
  filter(term != "(Intercept)")

# Ordenar variables (opcional pero recomendado)
forest_data <- forest_data %>%
  arrange(Odds_Ratio) %>%
  mutate(term = factor(term, levels = term))

# Forest plot
ggplot(forest_data, aes(x = Odds_Ratio, y = term)) +
  geom_errorbarh(aes(xmin = OR_inf, xmax = OR_sup),
                 height = 0.2, color = "#75c2d5",size=1.1) +
  geom_vline(xintercept = 1, linetype = "dashed", color = "gray50",size=1.1) +
    geom_vline(xintercept = 2, linetype = "dashed", color = "gray40",size=1.1) +
      geom_vline(xintercept = 3, linetype = "dashed", color = "gray30",size=1.1) +

    geom_point(color = "#db8e62", size = 3) +

  scale_x_log10() +
  labs(
    title = "Forest Plot - Odds Ratios del modelo",
    x = "Odds Ratio (escala logarítmica)",
    y = ""
  ) +
  theme_classic(base_size = 11)

La interpretación de los coeficientes del modelo logístico se realiza en términos de odds ratios. Se encuentra que variables como la realización de horas extra duplican aproximadamente las odds de rotación (OR ≈ 1.98). Asimismo, cargos como técnico de laboratorio, ejecutivo de ventas y representante de ventas presentan entre 2.2 y 2.7 veces mayores odds de rotación respecto a la categoría base. Por su parte, la antigüedad en la empresa reduce las odds de rotación en aproximadamente un 20% por cada año adicional (OR ≈ 0.80). En conjunto, estos resultados evidencian que tanto las condiciones laborales como el tipo de cargo desempeñado influyen significativamente en la decisión de rotar.

Para cada una de las variables y características la interpretación sería:

  • Por cada unidad adicional en distancia al trabajo, la oportunidad de rotar aumenta en 1.28 veces, manteniendo constantes las demás variables.

  • Por cada unidad adicional en trabajos anteriores, la oportunidad de rotar aumenta en 1.24 veces, manteniendo constantes las demás variables.

  • Por cada unidad adicional en antigüedad, la oportunidad de rotar se reduce en un 20%, manteniendo constantes las demás variables.

  • Los empleados que realizan horas extra tienen 1.98 veces la oportunidad de rotar respecto de los que no.

  • Los empleados con cargo de Ejecutivo de ventas tienen 2.44 veces la oportunidad de rotar respecto a los representantes de ventas.

  • Los empleados con cargo de Investigador científico tienen 2.13 veces la oportunidad de rotar respecto a los representantes de ventas.

  • Los empleados con cargo de Recursos humanos tienen 1.59 veces la oportunidad de rotar respecto a los representantes de ventas.

  • Los empleados con cargo de Representante de ventas tienen 2.23 veces la oportunidad de rotar respecto a los representantes de ventas.

  • Los empleados con cargo de Técnico de laboratorio tienen 2.66 veces la oportunidad de rotar respecto a los representantes de ventas.

  • Los empleados con equilibrio trabajo-vida: muy bajo tienen un 17% menos oportunidad de rotar respecto de la categoría alto equilibrio trabajo-vida

  • Los empleados con equilibrio trabajo-vida: bajo tienen 1.29 veces la oportunidad de rotar respecto de la categoría alto equilibrio trabajo-vida

Algunas características, como ciertos cargos y el nivel medio de equilibrio trabajo-vida, no resultaron estadísticamente significativas, por lo que no existe evidencia suficiente para afirmar que influyan en la rotación. Sus intervalos de confianza incluyen el valor 1, lo que sugiere que los efectos estimados podrían explicarse por variabilidad aleatoria.

5 Evaluación del modelo

Con el fin de evaluar el desempeño del modelo de regresión logística estimado, se analizaron diferentes métricas de clasificación, incluyendo la curva ROC, el área bajo la curva (AUC), la matriz de confusión y la sensibilidad frente a distintos puntos de corte.

5.1 Curva ROC y AUC

La curva ROC (Receiver Operating Characteristic) permite evaluar la capacidad del modelo para discriminar entre empleados que rotan (y = 1) y aquellos que no (y = 0), considerando todos los posibles puntos de corte.

# Generar probabilidades
predicciones <- predict(ajuste, rotacion, type = "prob") %>%
  bind_cols(rotacion)

head(predicciones)
## # A tibble: 6 × 26
##   `.pred_No rota` .pred_Rota Rotación  Edad `Viaje de Negocios` Departamento
##             <dbl>      <dbl> <fct>    <dbl> <fct>               <fct>       
## 1           0.348     0.652  Rota        41 Raramente           Ventas      
## 2           0.948     0.0520 No rota     49 Frecuentemente      IyD         
## 3           0.523     0.477  Rota        37 Raramente           IyD         
## 4           0.813     0.187  No rota     33 Frecuentemente      IyD         
## 5           0.805     0.195  No rota     27 Raramente           IyD         
## 6           0.895     0.105  No rota     32 Frecuentemente      IyD         
## # ℹ 20 more variables: Distancia_Casa <dbl>, Educación <ord>,
## #   Campo_Educación <fct>, Satisfacción_Ambiental <ord>, Genero <fct>,
## #   Cargo <fct>, Satisfación_Laboral <ord>, Estado_Civil <fct>,
## #   Ingreso_Mensual <dbl>, Trabajos_Anteriores <dbl>, Horas_Extra <fct>,
## #   Porcentaje_aumento_salarial <dbl>, Rendimiento_Laboral <ord>,
## #   Años_Experiencia <dbl>, Capacitaciones <dbl>,
## #   Equilibrio_Trabajo_Vida <ord>, Antigüedad <dbl>, Antigüedad_Cargo <dbl>, …
  # Evaluación: ROC y AUC

  
  # Curva ROC
  roc_obj <- roc(
    response = ifelse(predicciones$Rotación == "Rota", 1, 0),
    predictor = predicciones$.pred_Rota
  )
  
  
  # AUC
  auc_valor <- auc(roc_obj)
  
  cat("Área bajo la curva (AUC):", round(auc_valor, 3))
## Área bajo la curva (AUC): 0.786
  plot(roc_obj, col = "#fc8d62", lwd = 2)
  text(0.6, 0.2,
       paste("AUC =", round(auc(roc_obj), 3)),
       cex = 1.2)

El valor del AUC obtenido indica la capacidad predictiva del modelo:

  • Un AUC cercano a 0.5 indica que el modelo no tiene capacidad de discriminación.

  • Un AUC superior a 0.7 indica un modelo con buen poder predictivo.

  • Un AUC superior a 0.8 representa un modelo muy bueno.

En este caso se obtiene un AUC de 0.786 que indica que el modelo tiene una capacidad discriminativa moderadamente buena. Es decir, el modelo es capaz de separar a los empleados que rotan de los que no rotan con una probabilidad de acierto cercana al 78.6%. Si el AUC fuera cercano a 0.5, significaría que el modelo no discrimina mejor que al azar. Si fuera superior a 0.8, indicaría un muy buen desempeño. Así se considera que tiene un buen ajuste, considerando que solo fueron tenidas en cuenta 6 variables de todas las que se encontraban disponibles en la tabla. Se podría decir que es un modelo parsimonioso.

5.2 Matriz de Confusión, Sensibilidad y Especificidad

La matriz de confusión muestra la clasificación real y la clasificación predicha por el modelo. Es útil para calcular métricas como la precisión, la tasa de falsos positivos y la tasa de falsos negativos a partir de la matriz de confusión. La métricas de Sensibilidad y Especificidad miden la capacidad del modelo para clasificar correctamente las observaciones positivas y negativas, respectivamente. Una alta sensibilidad y especificidad indican un buen ajuste.

predicciones_clas <- predict(ajuste, rotacion, type = "class") %>%
  bind_cols(rotacion)

library(caret)
conf_matrix <- confusionMatrix(predicciones_clas$.pred_class, rotacion$Rotación)
print(conf_matrix)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No rota Rota
##    No rota    1212  201
##    Rota         21   36
##                                           
##                Accuracy : 0.849           
##                  95% CI : (0.8296, 0.8669)
##     No Information Rate : 0.8388          
##     P-Value [Acc > NIR] : 0.1517          
##                                           
##                   Kappa : 0.1945          
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 0.9830          
##             Specificity : 0.1519          
##          Pos Pred Value : 0.8577          
##          Neg Pred Value : 0.6316          
##              Prevalence : 0.8388          
##          Detection Rate : 0.8245          
##    Detection Prevalence : 0.9612          
##       Balanced Accuracy : 0.5674          
##                                           
##        'Positive' Class : No rota         
## 

### Umbral óptimo

# Obtener mejor umbral (Youden)
coords_roc <- coords(roc_obj, "best", ret = "threshold")

coords_roc
##   threshold
## 1 0.1684637
umbral_opt <- coords_roc

predicciones$pred_opt <- ifelse(predicciones$.pred_Rota > umbral_opt,
                               "Rota", "No rota")

# Convertir a factor
predicciones$pred_opt <- factor(predicciones$pred_opt,
                               levels = c("No rota","Rota"))

rotacion$Rotación <- factor(rotacion$Rotación,
                           levels = c("No rota","Rota"))

library(caret)
confusionMatrix(predicciones$pred_opt, rotacion$Rotación)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No rota Rota
##    No rota       0    0
##    Rota       1233  237
##                                          
##                Accuracy : 0.1612         
##                  95% CI : (0.1428, 0.181)
##     No Information Rate : 0.8388         
##     P-Value [Acc > NIR] : 1              
##                                          
##                   Kappa : 0              
##                                          
##  Mcnemar's Test P-Value : <2e-16         
##                                          
##             Sensitivity : 0.0000         
##             Specificity : 1.0000         
##          Pos Pred Value :    NaN         
##          Neg Pred Value : 0.1612         
##              Prevalence : 0.8388         
##          Detection Rate : 0.0000         
##    Detection Prevalence : 0.0000         
##       Balanced Accuracy : 0.5000         
##                                          
##        'Positive' Class : No rota        
## 
summary(predicciones$.pred_Rota)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## 0.004209 0.055356 0.109769 0.161224 0.232343 0.823997

El umbral óptimo basado en el criterio de Youden maximiza simultáneamente la sensibilidad y la especificidad sin considerar el desbalance de la muestra ni los costos asociados a los errores de clasificación. En este caso, dicho criterio conduce a una regla de decisión extrema que clasifica la mayoría de las observaciones como rotación, lo cual resulta poco informativo y operacionalmente inviable.

5.3 Evaluación de diferentes umbrales

umbrales <- c(0.2, 0.3, 0.4, 0.5)

evaluar_umbral <- function(u){
  
  pred <- ifelse(predicciones$.pred_Rota > u, "Rota", "No rota")
  pred <- factor(pred, levels = c("No rota","Rota"))
  
  cm <- caret::confusionMatrix(pred, rotacion$Rotación)
  
  data.frame(
    umbral = u,
    accuracy = cm$overall["Accuracy"],
    sensitivity = cm$byClass["Sensitivity"],
    specificity = cm$byClass["Specificity"]
  )
}

do.call(rbind, lapply(umbrales, evaluar_umbral))
##           umbral  accuracy sensitivity specificity
## Accuracy     0.2 0.7585034   0.7785888   0.6540084
## Accuracy1    0.3 0.8299320   0.9026764   0.4514768
## Accuracy2    0.4 0.8510204   0.9610706   0.2784810
## Accuracy3    0.5 0.8489796   0.9829684   0.1518987

El análisis de diferentes umbrales de clasificación evidencia un trade-off entre la precisión global del modelo y su capacidad para identificar correctamente empleados con alta probabilidad de rotación. Aunque umbrales más altos maximizan la precisión, reducen significativamente la capacidad del modelo para detectar rotación. En este contexto, un umbral de 0.2 resulta más adecuado, ya que permite un mejor equilibrio entre sensibilidad y especificidad, incrementando la capacidad del modelo para identificar empleados en riesgo.

En contextos organizacionales, el costo de no identificar a un empleado con alta probabilidad de rotación (falso negativo) suele ser mayor que el costo de intervenir innecesariamente a un empleado que no rotará (falso positivo). Por esta razón, es preferible utilizar un umbral más bajo que incremente la detección de casos de rotación, incluso a costa de una menor precisión global.

Aunque el criterio estadístico de Youden sugiere un umbral óptimo, este no resulta adecuado en contextos con desbalance de clases y objetivos de predicción orientados a la toma de decisiones. En este estudio, se selecciona un umbral de 0.2 debido a que permite un mejor equilibrio entre la capacidad de detección de rotación y la estabilidad del modelo, generando resultados más útiles desde una perspectiva operativa.

5.4 Curva de calibración

library(scales) # Para escalar
calibration_data <- data.frame(
  probabilidad = predicciones$.pred_Rota,
  rotacion = ifelse(predicciones$Rotación == "Rota", 1, 0)
)


calibration_plot <- predicciones %>%
  mutate(
    y = ifelse(Rotación == "Rota", 1, 0),
    bin = ntile(.pred_Rota, 10)  # 10 grupos
  ) %>%
  group_by(bin) %>%
  summarise(
    prob_predicha = mean(.pred_Rota),
    prob_real = mean(y)
  )

ggplot(calibration_plot, aes(x = prob_predicha, y = prob_real)) +
  geom_point(size = 3, color = "#fc8d62") +
  geom_line(color = "#fc8d62") +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
  labs(title = "Curva de calibración",
       x = "Probabilidad predicha",
       y = "Proporción observada") +
  theme_minimal()

La curva de calibración permite evaluar la correspondencia entre las probabilidades predichas por el modelo y las frecuencias observadas de rotación. Los resultados sugieren que, aunque el modelo presenta una adecuada capacidad discriminatoria, la calibración de las probabilidades no es perfecta, lo cual indica que las probabilidades estimadas deben interpretarse con cautela en contextos de toma de decisiones.

5.5 Validación Cruzada

Dividir los datos en conjuntos de entrenamiento y conjunto de prueba repetidamente (por ejemplo, mediante validación cruzada k-fold) y calcular métricas de bondad de ajuste en cada iteración. Esto proporciona una evaluación más robusta del modelo.

La elección de la métrica de bondad de ajuste depende de tus objetivos específicos y del contexto del problema. En general, es recomendable utilizar múltiples métricas y pruebas para evaluar el rendimiento del modelo desde diferentes perspectivas.

# 4. Validación cruzada (ejemplo rápido con tidymodels)
library(tidymodels)

# Definir validación cruzada (5 pliegues)
set.seed(123)
cv_splits <- vfold_cv(rotacion, v = 5)

# Ajustar modelo con validación cruzada
cv_results <- fit_resamples(
  ajuste,
  resamples = cv_splits,
  metrics = metric_set(roc_auc),
  control = control_resamples(save_pred = TRUE)
)

# Resumen de resultados de validación cruzada
collect_metrics(cv_results)
## # A tibble: 1 × 6
##   .metric .estimator  mean     n std_err .config        
##   <chr>   <chr>      <dbl> <int>   <dbl> <chr>          
## 1 roc_auc binary     0.769     5  0.0123 pre0_mod0_post0
library(yardstick)

# Agregar predicciones de CV
cv_pred <- collect_predictions(cv_results)

# Crear variable binaria real
cv_pred <- cv_pred %>%
  mutate(
    y = ifelse(Rotación == "Rota", 1, 0),
    pred_02 = ifelse(.pred_Rota > 0.2, 1, 0)
  )


cv_pred %>%
  summarise(
    accuracy = mean(pred_02 == y),
    sensibilidad = sum(pred_02 == 1 & y == 1) / sum(y == 1),
    especificidad = sum(pred_02 == 0 & y == 0) / sum(y == 0)
  )
## # A tibble: 1 × 3
##   accuracy sensibilidad especificidad
##      <dbl>        <dbl>         <dbl>
## 1    0.754        0.620         0.780

La validación cruzada de 5 pliegues muestra un AUC promedio de 0.769, ligeramente inferior al obtenido en la muestra completa (0.786), lo cual indica una buena capacidad de generalización del modelo. La baja variabilidad entre pliegues (error estándar de 0.012) sugiere que el desempeño del modelo es estable y consistente, evidenciando la ausencia de sobreajuste significativo.

Para evaluar la robustez de la regla de decisión, se implementó una validación cruzada con almacenamiento de predicciones. Esto permitió analizar el desempeño del modelo bajo un umbral de clasificación específico (0.2) en datos fuera de muestra, evidenciando su capacidad real para identificar empleados con alta probabilidad de rotación.

La evaluación del modelo mediante validación cruzada y un umbral de clasificación de 0.2 muestra una precisión global del 75.4%. Más importante aún, el modelo logra identificar el 62% de los empleados que rotan, manteniendo una correcta clasificación del 78% de aquellos que no rotan. Estos resultados evidencian un adecuado balance entre sensibilidad y especificidad, lo cual es fundamental en contextos donde la identificación temprana de rotación es prioritaria.

En el presente análisis no se incluyeron criterios tradicionales de bondad de ajuste como la prueba de razón de verosimilitud (Likelihood Ratio Test), la deviance, ni los criterios de información AIC y BIC. Si bien estas métricas son ampliamente utilizadas en modelos de regresión logística, su principal utilidad radica en la comparación entre modelos alternativos o en la evaluación del ajuste desde una perspectiva inferencial. En este caso, dado que la tarea se enfocó en la construcción de un modelo con fines predictivos y no en la comparación de múltiples especificaciones, se priorizaron métricas orientadas al desempeño predictivo, como el AUC, la validación cruzada y las medidas de clasificación asociadas a un umbral de decisión. Estas métricas permiten evaluar de manera más directa la capacidad del modelo para identificar correctamente la rotación de empleados, lo cual resulta más relevante para los objetivos de la actividad.

library(dplyr)
library(knitr)
library(kableExtra)

# Crear tabla base manual de hipótesis
hipotesis <- data.frame(
  variable = c("Distancia_Casa",
               "Trabajos_Anteriores",
               "Antigüedad",
               "Horas_Extra_Si",
               "Cargo",
               "Equilibrio_Trabajo_Vida"),
  
  hipotesis = c(
    "Aumenta oportunidad de rotación",
    "Aumenta oportunidad de rotación",
    "Reduce oportunidad de rotación",
    "Aumenta oportunidad de rotación",
    "Depende oportunidad de del cargo",
    "Reduce oportunidad de rotación"
  )
)

# Resultados del modelo (solo variables clave)
resultados_modelo <- resultados %>%
  filter(term %in% c("Distancia_Casa",
                     "Trabajos_Anteriores",
                     "Antigüedad",
                     "Horas_Extra_Si")) %>%
  mutate(
    efecto = ifelse(estimate > 0, "Aumenta rotación", "Reduce rotación"),
    significancia = ifelse(p.value < 0.05, "Significativa", "No significativa")
  ) %>%
  select(term, efecto, significancia)

# Unir tablas
tabla_final <- hipotesis %>%
  left_join(resultados_modelo, by = c("variable" = "term"))

# Evaluación de cumplimiento
tabla_final <- tabla_final %>%
  mutate(
    cumple = case_when(
      variable == "Cargo" ~ "Parcial",
      variable == "Equilibrio_Trabajo_Vida" ~ "Parcial",
      hipotesis == efecto ~ "Sí",
      TRUE ~ "No"
    )
  )

# Mostrar tabla
kable(tabla_final, caption = "Contraste de hipótesis vs resultados") %>%
  kable_styling(full_width = FALSE)
Contraste de hipótesis vs resultados
variable hipotesis efecto significancia cumple
Distancia_Casa Aumenta oportunidad de rotación Aumenta rotación Significativa No
Trabajos_Anteriores Aumenta oportunidad de rotación Aumenta rotación Significativa No
Antigüedad Reduce oportunidad de rotación Reduce rotación Significativa No
Horas_Extra_Si Aumenta oportunidad de rotación Aumenta rotación Significativa No
Cargo Depende oportunidad de del cargo NA NA Parcial
Equilibrio_Trabajo_Vida Reduce oportunidad de rotación NA NA Parcial

6 Predicción

# 9. Predicciones

nuevo_empleado <- rotacion[1, ]

nuevo_empleado$Horas_Extra <- "Si"
nuevo_empleado$Cargo <- "Ejecutivo_Ventas"

nuevo_empleado$Equilibrio_Trabajo_Vida <- factor(
  levels(rotacion$Equilibrio_Trabajo_Vida)[1],  # categoría 
  levels = levels(rotacion$Equilibrio_Trabajo_Vida),
  ordered = TRUE
)

nuevo_empleado$Distancia_Casa <- 27
nuevo_empleado$Trabajos_Anteriores <- 1
nuevo_empleado$Antigüedad <- 2

prediccion <- predict(ajuste, new_data = nuevo_empleado, type = "prob")
prediccion
## # A tibble: 1 × 2
##   `.pred_No rota` .pred_Rota
##             <dbl>      <dbl>
## 1           0.278      0.722

Se realizó la predicción de la probabilidad de rotación para un empleado hipotético con características específicas, incluyendo la realización de horas extra, cargo de ejecutivo de ventas, muy bajo equilibrio entre trabajo y vida personal, distancia al trabajo de 27 km, un trabajo anterior y una antigüedad de 2 años.

El modelo logístico estimó una probabilidad de rotación de 0.722 (72.2%), lo que indica un alto riesgo de que el empleado abandone la organización.

Dado que la probabilidad estimada es superior al punto de corte, se concluye que se recomienda intervenir al empleado.

Como estrategia, se sugiere implementar acciones orientadas a mejorar el equilibrio entre la vida laboral y personal, así como revisar la carga de trabajo y ofrecer incentivos que contribuyan a aumentar la satisfacción y permanencia del empleado en la organización.

7 Conclusiones

Los resultados del modelo logístico evidencian que la rotación laboral está principalmente determinada por factores relacionados con las condiciones laborales y la estructura organizacional. En particular, el trabajo en horas extra incrementa significativamente la probabilidad de rotación, casi duplicando las probabilidades de abandono. Asimismo, variables como la distancia al trabajo y un mayor número de trabajos anteriores aumentan la probabilidad de rotación, mientras que la antigüedad en la empresa actúa como un factor de retención, lo cual es consistente con la literatura sobre estabilidad laboral. En cuanto a los cargos, se observa que posiciones como ejecutivo de ventas, representante de ventas, técnico de laboratorio e investigador científico presentan aumentos importantes en la oportunidad de rotación, siendo estas algunas de las variables con mayor magnitud de efecto dentro del modelo.

De manera contraintuitiva, el nivel más bajo de equilibrio trabajo-vida presenta una menor oportunidad de rotación respecto al nivel alto. Este resultado podría reflejar efectos de selección o restricciones en la movilidad laboral, donde empleados con condiciones menos favorables no necesariamente rotan más debido a limitaciones en sus alternativas de empleo. Por tanto, este resultado debe interpretarse con cautela.

En cuanto a las características que no resultaron estadísticamente significativas, como los cargos de director de manufactura, gerente y representante de salud, así como el nivel medio de equilibrio trabajo-vida, no se encuentra evidencia suficiente para afirmar que estas categorías influyan en la oportunidad de rotación en comparación con sus respectivas categorías base. Aunque algunos coeficientes presentan odds ratios mayores a 1, sus intervalos de confianza incluyen el valor 1 y los p-valores son superiores a los niveles convencionales de significancia, lo que indica que estos efectos podrían deberse al azar. Por lo tanto, no es posible establecer una relación concluyente entre estas variables y la rotación, por lo que su interpretación debe realizarse con cautela.

A partir de los resultados del modelo, se identifican varias líneas de intervención orientadas a disminuir la rotación laboral. En primer lugar, la fuerte asociación entre la realización de horas extra y una mayor oportunidad de rotación sugiere la necesidad de implementar políticas de gestión de carga laboral, como la redistribución de turnos, la contratación adicional de personal o el establecimiento de límites efectivos a las horas extra.

En segundo lugar, el efecto positivo de la distancia al trabajo indica que los costos de desplazamiento son un factor relevante, por lo que la empresa podría considerar esquemas de trabajo flexible, modalidades híbridas o apoyos al transporte.

En tercer lugar, el hecho de que empleados con mayor número de trabajos anteriores presenten mayor rotación sugiere la conveniencia de fortalecer los procesos de selección y retención temprana, así como programas de onboarding que incrementen el compromiso organizacional desde el inicio.

Adicionalmente, la antigüedad reduce la rotación, lo que indica que estrategias de fidelización, planes de carrera y desarrollo profesional pueden ser efectivos para retener talento en el mediano y largo plazo.

En cuanto a los cargos con mayores niveles de rotación (como ventas y roles técnicos), se recomienda diseñar incentivos específicos, esquemas de compensación variables y mejoras en las condiciones laborales adaptadas a las características de cada puesto. Finalmente, aunque el equilibrio trabajo-vida presenta resultados no lineales, su relevancia sugiere que políticas de bienestar laboral deben mantenerse, pero evaluarse con mayor profundidad para identificar segmentos donde estas intervenciones sean más efectivas.

En términos de desempeño predictivo, el modelo presenta una capacidad de discriminación adecuada, con un valor de AUC cercano a 0.79, lo que indica que logra diferenciar razonablemente entre empleados que rotan y los que no. La validación cruzada confirma la estabilidad de este resultado, con métricas consistentes entre muestras, lo que sugiere que el modelo no está sobreajustado. No obstante, el análisis de la matriz de confusión evidencia un trade-off importante entre sensibilidad y especificidad, particularmente al seleccionar el umbral de clasificación. En este sentido, aunque el umbral tradicional de 0.5 ofrece una alta precisión global, resulta poco efectivo para identificar correctamente a los empleados que rotan. Por el contrario, un umbral más bajo, como 0.2, mejora sustancialmente la capacidad del modelo para detectar casos de rotación, lo cual es más relevante desde una perspectiva de gestión preventiva. En conjunto, estos resultados indican que el modelo es útil como herramienta de apoyo a la toma de decisiones, especialmente para priorizar intervenciones en empleados con mayor riesgo de rotación, aunque su uso debe complementarse con criterios organizacionales y análisis adicionales.