# Configuración global de los bloques de código (chunks)
knitr::opts_chunk$set(
echo = TRUE, # Muestra el código (quedará oculto por defecto gracias a code_folding: hide)
warning = FALSE, # Evita que salgan advertencias en el informe final
message = FALSE, # Evita mensajes de carga de librerías
fig.align = "center",
fig.width = 8,
fig.height = 4.5,
dpi = 96 # Resolución óptima para pantalla/PDF sin consumir exceso de RAM en Posit Cloud
)
# 1. Instalar solo los paquetes específicos necesarios (ligeros y rápidos)
install.packages(c("remotes", "dplyr", "tidyr", "ggplot2", "knitr", "kableExtra", "pROC"))
## Installing packages into '/cloud/lib/x86_64-pc-linux-gnu-library/4.6'
## (as 'lib' is unspecified)
# 2. Instalar paqueteMODELOS desde GitHub usando remotes (más liviano que devtools)
remotes::install_github("centromagis/paqueteMODELOS", force = TRUE, upgrade = "never")
## Downloading GitHub repo centromagis/paqueteMODELOS@HEAD
## Running `R CMD build`...
## * checking for file ‘/tmp/RtmpsKT6Tr/remotesc784b23876e/Centromagis-paqueteMODELOS-3b06257/DESCRIPTION’ ... OK
## * preparing ‘paqueteMODELOS’:
## * checking DESCRIPTION meta-information ... OK
## * checking for LF line-endings in source and make files and shell scripts
## * checking for empty or unneeded directories
## * building ‘paqueteMODELOS_0.1.0.tar.gz’
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.6'
## (as 'lib' is unspecified)
# Carga de librerías necesarias (ligeras y optimizadas)
library(paqueteMODELOS)
library(dplyr)
library(tidyr)
library(ggplot2)
library(knitr)
library(kableExtra)
library(pROC)
# Carga de la base de datos en memoria
data("rotacion")
En el ámbito de la gestión del talento humano, la rotación de personal representa uno de los costos ocultos más relevantes para las organizaciones, afectando tanto la continuidad operativa como el clima organizacional. El presente informe desarrolla un Modelo Lineal General - Logit Binomial (Regresión Logística) sobre los registros históricos de \(n = 1,470\) empleados de la compañía, con el propósito de estimar la probabilidad de que un colaborador rote de su cargo (\(Y = 1\)) frente a que permanezca en él (\(Y = 0\)), identificando los factores determinantes para estructurar políticas proactivas de retención.
Siguiendo los lineamientos metodológicos, se seleccionan tres variables categóricas y tres variables cuantitativas que teóricamente inciden en la decisión de rotación laboral. A continuación, se presenta su justificación y la hipótesis sobre la probabilidad de rotación \(P(Y = 1 \vert{} X)\):
Horas_Extra):
Si) presenten una mayor
probabilidad de rotación (\(\beta
> 0\), Odds Ratio \(>
1\)) en comparación con aquellos que no realizan horas extra
(No).Viaje de Negocios):
Frecuentemente tengan una
mayor probabilidad de rotar (\(\beta > 0\)) respecto a quienes viajan
Raramente o No_Viaja.Estado_Civil):
Soltero presenten una mayor propensión a
la rotación (\(\beta > 0\))
que los empleados Casado o Divorciado, dado
que suelen tener mayor flexibilidad y menores cargas financieras
fijas.Ingreso_Mensual):
Edad):
Antigüedad_Cargo):
# Selección de las variables de estudio y codificación de la variable dependiente binaria Y = {0, 1}
datos_estudio <- rotacion %>%
select(
Rotacion = Rotación,
Horas_Extra,
Viaje_Negocios = `Viaje de Negocios`,
Estado_Civil,
Ingreso_Mensual,
Edad,
Antiguedad_Cargo = Antigüedad_Cargo
) %>%
mutate(
# Variable dependiente numérica (1 = Si rota, 0 = No rota) para el modelo Logit
y = ifelse(Rotacion == "Si", 1, 0),
# Conversión a factores definiendo la categoría base (referencia) de forma explícita
Rotacion = factor(Rotacion, levels = c("No", "Si")),
Horas_Extra = factor(Horas_Extra, levels = c("No", "Si")),
Viaje_Negocios = factor(Viaje_Negocios, levels = c("No_Viaja", "Raramente", "Frecuentemente")),
Estado_Civil = factor(Estado_Civil, levels = c("Casado", "Divorciado", "Soltero"))
)
# Resumen rápido de verificación de la estructura preparada
resumen_vars <- data.frame(
Variable = c("Rotación (Dependiente)", "Horas_Extra", "Viaje_Negocios", "Estado_Civil",
"Ingreso_Mensual", "Edad", "Antigüedad_Cargo"),
Tipo = c("Binaria (Factor / 0-1)", "Categórica (Binaria)", "Categórica (Politómica)", "Categórica (Politómica)",
"Cuantitativa (Continua)", "Cuantitativa (Discreta)", "Cuantitativa (Discreta)"),
Hipotesis_Esperada = c("Variable Respuesta (Y)", "Positiva (+ riesgo en 'Si')", "Positiva (+ riesgo en 'Frecuentemente')",
"Positiva (+ riesgo en 'Soltero')", "Negativa (- riesgo a mayor ingreso)",
"Negativa (- riesgo a mayor edad)", "Negativa (- riesgo a mayor antigüedad)")
)
kable(resumen_vars, caption = "Tabla 1. Resumen de variables seleccionadas e hipótesis planteadas") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
| Variable | Tipo | Hipotesis_Esperada |
|---|---|---|
| Rotación (Dependiente) | Binaria (Factor / 0-1) | Variable Respuesta (Y) |
| Horas_Extra | Categórica (Binaria) | Positiva (+ riesgo en ‘Si’) |
| Viaje_Negocios | Categórica (Politómica) | Positiva (+ riesgo en ‘Frecuentemente’) |
| Estado_Civil | Categórica (Politómica) | Positiva (+ riesgo en ‘Soltero’) |
| Ingreso_Mensual | Cuantitativa (Continua) | Negativa (- riesgo a mayor ingreso) |
| Edad | Cuantitativa (Discreta) | Negativa (- riesgo a mayor edad) |
| Antigüedad_Cargo | Cuantitativa (Discreta) | Negativa (- riesgo a mayor antigüedad) |
Con el fin de comprender la estructura base de los \(n = 1,470\) registros antes de modelar la probabilidad conjunta, se realiza la caracterización univariada diferenciando el tratamiento estadístico según la naturaleza de las variables: tablas de frecuencias absolutas y relativas con gráficos de barras para la variable respuesta y las covariables cualitativas; y medidas de tendencia central, dispersión y posición con histogramas/densidades para las covariables cuantitativas.
Rotación) y Variables
Categóricas# 1. Tabla consolidada de frecuencias absolutas (n) y relativas (%) para variables categóricas
tabla_cat <- datos_estudio %>%
select(Rotacion, Horas_Extra, Viaje_Negocios, Estado_Civil) %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Categoria") %>%
group_by(Variable, Categoria) %>%
summarise(Frecuencia = n(), .groups = "drop_last") %>%
mutate(Porcentaje = round((Frecuencia / sum(Frecuencia)) * 100, 2)) %>%
ungroup()
kable(tabla_cat,
col.names = c("Variable", "Categoría", "Frecuencia (n)", "Participación (%)"),
caption = "Tabla 2. Distribución de frecuencias de la variable Rotación y covariables categóricas") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
| Variable | Categoría | Frecuencia (n) | Participación (%) |
|---|---|---|---|
| Estado_Civil | Casado | 673 | 45.78 |
| Estado_Civil | Divorciado | 327 | 22.24 |
| Estado_Civil | Soltero | 470 | 31.97 |
| Horas_Extra | No | 1054 | 71.70 |
| Horas_Extra | Si | 416 | 28.30 |
| Rotacion | No | 1233 | 83.88 |
| Rotacion | Si | 237 | 16.12 |
| Viaje_Negocios | No_Viaja | 150 | 10.20 |
| Viaje_Negocios | Raramente | 1043 | 70.95 |
| Viaje_Negocios | Frecuentemente | 277 | 18.84 |
# 2. Panel gráfico de barras con etiquetas de porcentaje
ggplot(tabla_cat, aes(x = Categoria, y = Porcentaje, fill = Variable)) +
geom_col(width = 0.6, alpha = 0.85, show.legend = FALSE) +
geom_text(aes(label = paste0(Porcentaje, "%\n(n=", Frecuencia, ")")),
vjust = -0.2, size = 3.2, fontface = "bold") +
facet_wrap(~ Variable, scales = "free_x", ncol = 2) +
scale_y_continuous(limits = c(0, 100), expand = expansion(mult = c(0, 0.1))) +
scale_fill_brewer(palette = "Paired") +
labs(
title = "Distribución Univariada de la Variable Respuesta y Covariables Categóricas",
x = "Categorías",
y = "Porcentaje de Empleados (%)"
) +
theme_minimal() +
theme(
strip.text = element_text(face = "bold", size = 11, color = "#1a5276"),
plot.title = element_text(face = "bold", hjust = 0.5)
)
Interpretación de la Variable Respuesta
(Rotación) y Covariables Categóricas:
Si), mientras que el
83.88% (\(n = 1,233\))
permaneció en su puesto (No). Desde la perspectiva del
modelamiento estadístico y el negocio, este hallazgo conlleva dos
implicaciones clave:
No), aspecto que será determinante en
las secciones 5 y 6 al momento de evaluar la sensibilidad/especificidad
y definir un umbral o punto de corte óptimo cercano a la prevalencia
real (\(0.16 - 0.20\)) en lugar del
\(0.50\) estándar.Horas_Extra): La mayoría
de la plantilla laboral (71.70%, \(n = 1,054\)) cumple con su jornada
ordinaria sin requerir tiempo suplementario, pero existe un segmento
crítico del 28.30% (\(n =
416\)) expuesto a jornadas extendidas (Si), grupo
sobre el cual se evaluará la hipótesis de desgaste laboral.Viaje_Negocios):
La dinámica operativa de la organización exige desplazamientos
ocasionales (Raramente) para el 70.95%
(\(n = 1,043\)) del personal. Sin
embargo, el 18.84% (\(n =
277\)) viaja Frecuentemente, conformando el segmento
de mayor exposición al estrés por movilidad constante, frente a solo un
10.20% (\(n = 150\))
que No_Viaja.Estado_Civil): La
estructura demográfica está liderada por colaboradores en condición de
Casado (45.78%, \(n = 673\)), seguidos por el grupo de
Soltero (31.97%, \(n = 470\)) y Divorciado
(22.24%, \(n = 327\)),
lo que ofrece una variabilidad adecuada en las categorías para
contrastar el efecto de las responsabilidades familiares sobre la
estabilidad en el cargo.# 1. Tabla de indicadores descriptivos (Tendencia central, dispersión y posición)
tabla_cuant <- datos_estudio %>%
select(Ingreso_Mensual, Edad, Antiguedad_Cargo) %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor") %>%
group_by(Variable) %>%
summarise(
Media = round(mean(Valor, na.rm = TRUE), 2),
Desv_Est = round(sd(Valor, na.rm = TRUE), 2),
CV_Porc = round((sd(Valor, na.rm = TRUE) / mean(Valor, na.rm = TRUE)) * 100, 2),
Minimo = min(Valor, na.rm = TRUE),
Q1 = quantile(Valor, 0.25, na.rm = TRUE),
Mediana = round(median(Valor, na.rm = TRUE), 2),
Q3 = quantile(Valor, 0.75, na.rm = TRUE),
Maximo = max(Valor, na.rm = TRUE),
.groups = "drop"
)
kable(tabla_cuant,
col.names = c("Variable", "Media", "Desv. Est.", "CV (%)", "Mín", "Q1 (25%)", "Mediana", "Q3 (75%)", "Máx"),
caption = "Tabla 3. Indicadores descriptivos de las variables cuantitativas seleccionadas") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
| Variable | Media | Desv. Est. | CV (%) | Mín | Q1 (25%) | Mediana | Q3 (75%) | Máx |
|---|---|---|---|---|---|---|---|---|
| Antiguedad_Cargo | 4.23 | 3.62 | 85.67 | 0 | 2 | 3 | 7 | 18 |
| Edad | 36.92 | 9.14 | 24.74 | 18 | 30 | 36 | 43 | 60 |
| Ingreso_Mensual | 6502.93 | 4707.96 | 72.40 | 1009 | 2911 | 4919 | 8379 | 19999 |
# 2. Panel de histogramas para evaluar la forma de la distribución
datos_estudio %>%
select(Ingreso_Mensual, Edad, Antiguedad_Cargo) %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor") %>%
ggplot(aes(x = Valor)) +
geom_histogram(bins = 25, fill = "#2874a6", color = "white", alpha = 0.85) +
facet_wrap(~ Variable, scales = "free", ncol = 3) +
labs(
title = "Distribución Univariada de las Covariables Cuantitativas",
x = "Valor de la Variable",
y = "Frecuencia Absoluta (Empleados)"
) +
theme_minimal() +
theme(
strip.text = element_text(face = "bold", size = 11, color = "#1a5276"),
plot.title = element_text(face = "bold", hjust = 0.5)
)
Interpretación de las Variables Cuantitativas:
Ingreso_Mensual):
Presenta una media de $6,502.93 con una desviación
estándar elevada ($4,707.96) y un Coeficiente de
Variación (CV) de 72.40%, lo que evidencia una alta
heterogeneidad salarial en la organización (rango entre $1,009 y
$19,999). Al contrastar la media ($6,502.93) con la mediana
($4,919.00), junto con el histograma de frecuencias, se
confirma una marcada asimetría positiva (sesgo a la
derecha): el 50% de los trabajadores percibe ingresos iguales o
inferiores a $4,919 (y el 25% gana menos de $2,911), mientras que una
cola superior de cargos directivos eleva el promedio general.Edad): Es la variable más
homogénea y simétrica del conjunto analizado, con un CV bajo de
24.74% (dispersión moderada de \(9.14\) años). El promedio de edad
(36.92 años) prácticamente coincide con la mediana
(36.00 años), concentrando al 50% central de la fuerza
laboral (rango intercuartílico \(Q_1 -
Q_3\)) entre los 30 y 43 años, dentro de un
rango total de 18 a 60 años.Antiguedad_Cargo): Registra un promedio de
4.23 años y una mediana de 3.00 años,
pero exhibe la mayor dispersión relativa del estudio (CV =
85.67%, desviación de \(3.62\)
años). El histograma muestra una distribución multimodal y fuertemente
sesgada a la derecha: el 25% del personal lleva 2 años o menos en su rol
actual (incluyendo un pico importante de recién ingresados con 0 años en
el puesto) y el 75% tiene 7 años o menos, existiendo pocos casos con
permanencias prolongadas de hasta 18 años.Con el propósito de identificar de manera preliminar cuáles de las variables seleccionadas son determinantes en la rotación (\(Y = 1\) si rota, \(Y = 0\) si no rota) y evaluar la dirección de su efecto (signo del coeficiente estimado \(\hat{\beta}\)), se realiza un análisis descriptivo bivariado acompañado de la estimación de modelos Logit simples marginales (\(\ln\left(\frac{P}{1-P}\right) = \beta_0 + \beta_1 X_j\)) para cada covariable por separado.
# Gráfico de barras apiladas al 100% (proporción de rotación por categoría)
datos_estudio %>%
select(Rotacion, Horas_Extra, Viaje_Negocios, Estado_Civil) %>%
pivot_longer(cols = -Rotacion, names_to = "Variable", values_to = "Categoria") %>%
group_by(Variable, Categoria, Rotacion) %>%
summarise(n = n(), .groups = "drop_last") %>%
mutate(Porcentaje = round((n / sum(n)) * 100, 1)) %>%
ungroup() %>%
ggplot(aes(x = Categoria, y = Porcentaje, fill = Rotacion)) +
geom_col(width = 0.65, alpha = 0.9) +
geom_text(aes(label = paste0(Porcentaje, "%")),
position = position_stack(vjust = 0.5), size = 3.2, color = "white", fontface = "bold") +
facet_wrap(~ Variable, scales = "free_x", ncol = 3) +
scale_fill_manual(values = c("No" = "#2e86c1", "Si" = "#cb4335"), name = "Rotación (Y)") +
labs(
title = "Proporción de Rotación (Y = 1 vs Y = 0) según Covariables Categóricas",
x = "Categoría de la Covariable",
y = "Porcentaje dentro del grupo (%)"
) +
theme_minimal() +
theme(
strip.text = element_text(face = "bold", size = 11, color = "#1a5276"),
plot.title = element_text(face = "bold", hjust = 0.5),
axis.text.x = element_text(angle = 15, hjust = 1)
)
#3.2. Relación Bivariada: Variables Cuantitativas vs. Rotación
# Boxplots comparativos + medias por grupo de Rotación
datos_estudio %>%
select(Rotacion, Ingreso_Mensual, Edad, Antiguedad_Cargo) %>%
pivot_longer(cols = -Rotacion, names_to = "Variable", values_to = "Valor") %>%
ggplot(aes(x = Rotacion, y = Valor, fill = Rotacion)) +
geom_boxplot(width = 0.5, alpha = 0.8, outlier.color = "gray40", outlier.size = 1) +
stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "yellow", color = "black") +
facet_wrap(~ Variable, scales = "free_y", ncol = 3) +
scale_fill_manual(values = c("No" = "#2e86c1", "Si" = "#cb4335"), name = "Rotación (Y)") +
labs(
title = "Distribución de Covariables Cuantitativas según Rotación (Rombo amarillo = Media)",
x = "Rotación del Empleado",
y = "Valor de la Variable"
) +
theme_minimal() +
theme(
strip.text = element_text(face = "bold", size = 11, color = "#1a5276"),
plot.title = element_text(face = "bold", hjust = 0.5),
legend.position = "none"
)
#3.3. Estimación de Coeficientes Bivariados (Signo, Odds Ratio y Significancia Individual)
# Lista de las 6 covariables para ajustar modelos logit individuales Y ~ X_j
vars_predictoras <- c("Horas_Extra", "Viaje_Negocios", "Estado_Civil",
"Ingreso_Mensual", "Edad", "Antiguedad_Cargo")
# Extracción vectorizada de coeficientes bivariados (excluyendo el intercepto)
resumen_bivariado <- lapply(vars_predictoras, function(var) {
formula_biv <- as.formula(paste("y ~", var))
mod_biv <- glm(formula_biv, data = datos_estudio, family = binomial(link = "logit"))
coefs <- summary(mod_biv)$coefficients[-1, , drop = FALSE]
data.frame(
Variable_Base = var,
Termino_Efecto = rownames(coefs),
Beta_Estimado = round(coefs[, "Estimate"], 5),
Signo = ifelse(coefs[, "Estimate"] > 0, "Positivo (+)", "Negativo (-)"),
Odds_Ratio = round(exp(coefs[, "Estimate"]), 4),
Estadistico_Wald_z = round(coefs[, "z value"], 2),
Valor_p = format.pval(coefs[, "Pr(>|z|)"], digits = 3, eps = 0.0001)
)
}) %>% bind_rows()
rownames(resumen_bivariado) <- NULL
kable(resumen_bivariado,
col.names = c("Variable", "Categoría / Término", "Coeficiente (β)", "Signo del Efecto",
"Odds Ratio exp(β)", "Wald (z)", "Valor p"),
caption = "Tabla 4. Resultados del análisis bivariado mediante modelos Logit simples (y = 1: Si rota)") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
| Variable | Categoría / Término | Coeficiente (β) | Signo del Efecto | Odds Ratio exp(β) | Wald (z) | Valor p |
|---|---|---|---|---|---|---|
| Horas_Extra | Horas_ExtraSi | 1.32741 | Positivo (+) | 3.7712 | 9.06 | <1e-04 |
| Viaje_Negocios | Viaje_NegociosRaramente | 0.70436 | Positivo (+) | 2.0225 | 2.25 | 0.0245 |
| Viaje_Negocios | Viaje_NegociosFrecuentemente | 1.33892 | Positivo (+) | 3.8149 | 4.04 | <1e-04 |
| Estado_Civil | Estado_CivilDivorciado | -0.23946 | Negativo (-) | 0.7871 | -1.10 | 0.271 |
| Estado_Civil | Estado_CivilSoltero | 0.87717 | Positivo (+) | 2.4041 | 5.57 | <1e-04 |
| Ingreso_Mensual | Ingreso_Mensual | -0.00013 | Negativo (-) | 0.9999 | -5.88 | <1e-04 |
| Edad | Edad | -0.05225 | Negativo (-) | 0.9491 | -6.01 | <1e-04 |
| Antiguedad_Cargo | Antiguedad_Cargo | -0.14628 | Negativo (-) | 0.8639 | -6.03 | <1e-04 |
Interpretación del Análisis Bivariado y Contraste con las Hipótesis (Punto 1):
A partir de las visualizaciones bivariadas y de las estimaciones de los modelos Logit simples marginales (Tabla 4), se concluye que las 6 variables seleccionadas son estadísticamente determinantes de la rotación laboral (\(p < 0.0001\)) y sus coeficientes ratifican en su totalidad las hipótesis formuladas en el Punto 1:
Horas_Extra):
Viaje_Negocios):
No_Viaja, categoría base), pasando por el
15.0% en quienes viajan Raramente (\(\hat{\beta} = 0.70436\), \(p = 0.0245\)), hasta alcanzar el
24.9% en quienes viajan Frecuentemente
(\(\hat{\beta} = 1.33892\), \(z = 4.04\), \(p
< 0.0001\)).Estado_Civil):
Casado (rotación del 12.5%),
el grupo de Divorciado presenta una rotación del
10.1% cuyo coeficiente negativo (\(\hat{\beta} = -0.23946\), \(p = 0.271\)) no es estadísticamente
distinto de los casados. En contraste, los empleados en condición de
Soltero registran una tasa de rotación del
25.5%, con un coeficiente positivo y
altamente significativo (\(\hat{\beta} =
0.87717\), \(z = 5.57\), \(p < 0.0001\)).Ingreso_Mensual):
Edad):
Antiguedad_Cargo):
Una vez verificado el efecto bivariado de cada factor, se procede a estimar el Modelo Lineal Generalizado - Logit Binomial Múltiple integrando simultáneamente las 6 covariables seleccionadas. La especificación funcional del modelo en términos del logit (logaritmo natural de la razón de probabilidades) está dada por:
\[\ln\left(\frac{P(Y_i = 1 | X_i)}{1 - P(Y_i = 1 | X_i)}\right) = \beta_0 + \beta_1 \text{Horas\_Extra}_i + \beta_2 \text{Viaje\_Raramente}_i + \beta_3 \text{Viaje\_Frecuentemente}_i + \beta_4 \\ text{Divorciado}_i + \beta_5 \text{Soltero}_i + \beta_6 \text{Ingreso\_Mensual}_i + \beta_7 \text{Edad}_i + \beta_8 \text{Antiguedad\_Cargo}_i\]
# 1. Estimación del modelo Logit mediante Máxima Verosimilitud
modelo_logit <- glm(
y ~ Horas_Extra + Viaje_Negocios + Estado_Civil + Ingreso_Mensual + Edad + Antiguedad_Cargo,
data = datos_estudio,
family = binomial(link = "logit")
)
# 2. Extracción de coeficientes, prueba de Wald, Odds Ratios e Intervalos de Confianza al 95%
resumen_mod <- summary(modelo_logit)$coefficients
ic_beta <- confint.default(modelo_logit, level = 0.95)
tabla_modelo <- data.frame(
Parametro = rownames(resumen_mod),
Beta = round(resumen_mod[, "Estimate"], 5),
Error_Est = round(resumen_mod[, "Std. Error"], 5),
Wald_z = round(resumen_mod[, "z value"], 2),
Valor_p = format.pval(resumen_mod[, "Pr(>|z|)"], digits = 3, eps = 0.0001),
Odds_Ratio = round(exp(resumen_mod[, "Estimate"]), 4),
OR_IC_Inf = round(exp(ic_beta[, 1]), 4),
OR_IC_Sup = round(exp(ic_beta[, 2]), 4)
)
rownames(tabla_modelo) <- NULL
kable(tabla_modelo,
col.names = c("Parámetro / Variable", "Coeficiente (β)", "Error Est.", "Wald (z)",
"Valor p", "Odds Ratio exp(β)", "IC 95% Inf (OR)", "IC 95% Sup (OR)"),
caption = "Tabla 5. Estimación de parámetros, significancia individual (Wald) y Odds Ratios del modelo Logit múltiple") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
| Parámetro / Variable | Coeficiente (β) | Error Est. | Wald (z) | Valor p | Odds Ratio exp(β) | IC 95% Inf (OR) | IC 95% Sup (OR) |
|---|---|---|---|---|---|---|---|
| (Intercept) | -1.42588 | 0.46053 | -3.10 | 0.001961 | 0.2403 | 0.0974 | 0.5926 |
| Horas_ExtraSi | 1.45119 | 0.15820 | 9.17 | < 1e-04 | 4.2682 | 3.1303 | 5.8198 |
| Viaje_NegociosRaramente | 0.67004 | 0.33058 | 2.03 | 0.042676 | 1.9543 | 1.0224 | 3.7358 |
| Viaje_NegociosFrecuentemente | 1.32461 | 0.35296 | 3.75 | 0.000175 | 3.7607 | 1.8829 | 7.5112 |
| Estado_CivilDivorciado | -0.29127 | 0.22947 | -1.27 | 0.204340 | 0.7473 | 0.4766 | 1.1717 |
| Estado_CivilSoltero | 0.79351 | 0.17136 | 4.63 | < 1e-04 | 2.2111 | 1.5804 | 3.0937 |
| Ingreso_Mensual | -0.00007 | 0.00003 | -2.70 | 0.006870 | 0.9999 | 0.9999 | 1.0000 |
| Edad | -0.02885 | 0.01009 | -2.86 | 0.004232 | 0.9716 | 0.9525 | 0.9910 |
| Antiguedad_Cargo | -0.10412 | 0.02731 | -3.81 | 0.000138 | 0.9011 | 0.8541 | 0.9507 |
# 3. Prueba de Significancia Global del Modelo (Likelihood Ratio Test / Diferencia de Devianzas)
dev_nula <- modelo_logit$null.deviance
dev_res <- modelo_logit$deviance
gl_nulo <- modelo_logit$df.null
gl_res <- modelo_logit$df.residual
chi_global <- dev_nula - dev_res
gl_diff <- gl_nulo - gl_res
p_val_global <- pchisq(chi_global, df = gl_diff, lower.tail = FALSE)
tabla_global <- data.frame(
Null_Deviance = paste0(round(dev_nula, 2), " (gl = ", gl_nulo, ")"),
Residual_Deviance = paste0(round(dev_res, 2), " (gl = ", gl_res, ")"),
Chi_Cuadrado = round(chi_global, 2),
Grados_Libertad = gl_diff,
Valor_p_Global = format.pval(p_val_global, digits = 4, eps = 0.0001),
AIC = round(modelo_logit$aic, 2)
)
kable(tabla_global,
col.names = c("Devianza Nula", "Devianza Residual", "Estadístico χ² (G)", "gl", "Valor p Global", "AIC"),
caption = "Tabla 6. Bondad de ajuste y prueba de significancia global del modelo Logit") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
| Devianza Nula | Devianza Residual | Estadístico χ² (G) | gl | Valor p Global | AIC |
|---|---|---|---|---|---|
| 1298.58 (gl = 1469) | 1082.38 (gl = 1461) | 216.21 | 8 | < 1e-04 | 1100.38 |
Ecuación Estimada e Interpretación del Modelo Logit Múltiple:
A partir de las estimaciones por Máxima Verosimilitud (Tabla 5), la ecuación del modelo en escala logit queda definida como:
\[\ln\left(\frac{\hat{P}_i}{1 - \hat{P}_i}\right) = -1.42588 + 1.45119(\text{Horas\_Extra}_{\text{Si}}) + 0.67004(\text{Viaje}_{\text{Raramente}}) + 1.32461(\text{Viaje}_{\text{Frecuente}}) - 0.29127(\text{Divorciado}) +\\ 0.79351(\text{Soltero}) - 0.00007(\text{Ingreso}) - 0.02885(\text{Edad}) - 0.10412(\text{Antigüedad\_Cargo})\]
Horas_Extra = No, Viaje_Negocios = No_Viaja,
Estado_Civil = Casado).Horas_ExtraSi): Presenta
el mayor impacto relativo del modelo (\(\hat{\beta}_1 = 1.45119\), \(z = 9.17\), \(p
< 0.0001\)). Manteniendo constantes las demás variables,
trabajar horas extra multiplica por 4.27 veces (\(OR = 4.2682\), \(IC_{95\%}: [3.1303, 5.8198]\)) las
posibilidades (odds) de que el empleado rote frente a quien no
realiza horas extra.Viaje_Negocios):
Ambos niveles son estadísticamente significativos respecto a no viajar
(No_Viaja). Viajar Raramente (\(\hat{\beta}_2 = 0.67004\), \(p = 0.0427\)) casi duplica las
posibilidades de rotar (\(OR =
1.9543\)), mientras que viajar Frecuentemente (\(\hat{\beta}_3 = 1.32461\), \(z = 3.75\), \(p =
0.000175\)) incrementa los odds de rotación en
3.76 veces (\(OR =
3.7607\), \(IC_{95\%}: [1.8829,
7.5112]\)), mantendo constantes los demás factores.Estado_Civil): Respecto
a los empleados casados, ser Soltero es altamente
significativo (\(\hat{\beta}_5 =
0.79351\), \(z = 4.63\), \(p < 0.0001\)) e incrementa en
2.21 veces (\(OR =
2.2111\), \(IC_{95\%}: [1.5804,
3.0937]\)) las posibilidades de rotar. Por su parte, la categoría
Divorciado (\(\hat{\beta}_4 =
-0.29127\), \(p = 0.2043\)) no
presenta diferencias estadísticamente significativas frente a estar
casado (su intervalo de confianza para el \(OR\) \([0.4766,
1.1717]\) incluye el valor neutro \(1\)).Ingreso_Mensual): Es
estadísticamente significativo y protector (\(\hat{\beta}_6 = -0.00007\), \(z = -2.70\), \(p
= 0.00687\)). Aunque el cambio por cada dólar unitario es \(OR = 0.9999\), al escalarlo a incrementos
de $1,000 dólares manteniendo lo demás constante, el
Odds Ratio es \(\exp(-0.00007 \times
1000) \approx 0.9324\), lo que indica que cada $1,000 adicionales
de salario reducen los odds de rotación en un
6.76%.Edad): Muestra un efecto inverso
y significativo (\(\hat{\beta}_7 =
-0.02885\), \(z = -2.86\), \(p = 0.00423\), \(OR = 0.9716\)). Manteniendo constantes la
antigüedad y el salario, por cada año adicional de edad del colaborador,
las posibilidades (odds) de rotar disminuyen en un
2.84% (\((1 - 0.9716) \times
100\)).Antiguedad_Cargo): Es altamente significativa y
retentiva (\(\hat{\beta}_8 =
-0.10412\), \(z = -3.81\), \(p = 0.000138\), \(OR = 0.9011\), \(IC_{95\%}: [0.8541, 0.9507]\)). Por cada
año adicional de permanencia en el cargo actual, los odds de
rotación se reducen en un 9.89% (\((1 - 0.9011) \times 100\)), ceteris
paribus.Para evaluar la capacidad discriminante del modelo Logit estimado entre los empleados que rotan (\(Y = 1\)) y los que permanecen (\(Y = 0\)), se construye la Curva ROC (Receiver Operating Characteristic), se calcula el Área Bajo la Curva (AUC-ROC) y se evalúan las métricas derivadas de la Matriz de Confusión (Sensibilidad, Especificidad y Exactitud/Precisión global).
# 1. Probabilidades predichas por el modelo sobre los datos
prob_pred <- predict(modelo_logit, type = "response")
# 2. Construcción del objeto ROC y cálculo del AUC + Umbral óptimo de Youden
roc_obj <- roc(response = datos_estudio$y, predictor = prob_pred, quiet = TRUE)
auc_val <- as.numeric(auc(roc_obj))
ic_auc <- ci.auc(roc_obj)
# Punto de corte óptimo según el índice de Youden (maximiza Sensibilidad + Especificidad - 1)
corte_optimo <- coords(roc_obj, "best", best.method = "youden", transpose = FALSE)
umbral_youden <- corte_optimo$threshold[1]
# 3. Gráfico de la Curva ROC con ggplot2 (est limpio y optimizado para PDF)
df_roc <- data.frame(
Especificidad = roc_obj$specificities,
Sensibilidad = roc_obj$sensitivities
)
ggplot(df_roc, aes(x = 1 - Especificidad, y = Sensibilidad)) +
geom_line(color = "#1a5276", linewidth = 1.2) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "gray50") +
annotate("point", x = 1 - corte_optimo$specificity[1], y = corte_optimo$sensitivity[1],
color = "#cb4335", size = 3.5) +
annotate("text", x = (1 - corte_optimo$specificity[1]) + 0.22, y = corte_optimo$sensitivity[1] - 0.05,
label = paste0("Corte Óptimo (Youden) = ", round(umbral_youden, 3),
"\nSens = ", round(corte_optimo$sensitivity[1]*100, 1), "% | Esp = ",
round(corte_optimo$specificity[1]*100, 1), "%"),
color = "#cb4335", fontface = "bold", size = 3.3) +
annotate("label", x = 0.65, y = 0.20,
label = paste0("AUC-ROC = ", round(auc_val, 4), "\nIC 95%: [",
round(ic_auc[1], 4), " - ", round(ic_auc[3], 4), "]"),
fill = "#ebf5fb", color = "#1a5276", fontface = "bold", size = 3.8) +
labs(
title = "Curva ROC del Modelo de Regresión Logística Múltiple",
subtitle = "Capacidad discriminante para la predicción de Rotación de Cargo",
x = "1 - Especificidad (Tasa de Falsos Positivos - FP)",
y = "Sensibilidad (Tasa de Verdaderos Positivos - VP)"
) +
theme_minimal() +
theme(plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5))
# 4. Comparación de Métricas de Matriz de Confusión: Corte Estándar (0.50) vs. Corte Óptimo (Youden)
calcular_metricas <- function(umbral, nombre_corte) {
pred_clase <- ifelse(prob_pred >= umbral, 1, 0)
VN <- sum(pred_clase == 0 & datos_estudio$y == 0)
VP <- sum(pred_clase == 1 & datos_estudio$y == 1)
FN <- sum(pred_clase == 0 & datos_estudio$y == 1)
FP <- sum(pred_clase == 1 & datos_estudio$y == 0)
data.frame(
Umbral = paste0(nombre_corte, " (p >= ", round(umbral, 3), ")"),
VN = VN, FP = FP, FN = FN, VP = VP,
Exactitud_Porc = round(((VN + VP) / length(prob_pred)) * 100, 2),
Sensibilidad_Porc = round((VP / (VP + FN)) * 100, 2),
Especificidad_Porc = round((VN / (VN + FP)) * 100, 2)
)
}
tabla_desempeno <- bind_rows(
calcular_metricas(0.50, "Estándar"),
calcular_metricas(umbral_youden, "Óptimo Youden")
)
kable(tabla_desempeno,
col.names = c("Punto de Corte", "VN", "FP", "FN", "VP", "Exactitud Global (%)", "Sensibilidad (%)", "Especificidad (%)"),
caption = "Tabla 7. Matriz de confusión y métricas de desempeño según el umbral de probabilidad") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
| Punto de Corte | VN | FP | FN | VP | Exactitud Global (%) | Sensibilidad (%) | Especificidad (%) |
|---|---|---|---|---|---|---|---|
| Estándar (p >= 0.5) | 1211 | 22 | 197 | 40 | 85.10 | 16.88 | 98.22 |
| Óptimo Youden (p >= 0.2) | 968 | 265 | 78 | 159 | 76.67 | 67.09 | 78.51 |
Interpretación del Poder Predictivo (Curva ROC, AUC y Matriz de Confusión):
# Definición de los perfiles en un dataframe con la misma estructura del modelo
perfiles_eval <- data.frame(
Perfil = c("Perfil A (Sujeto Hipotético - Alto Riesgo)", "Perfil B (Contraste - Bajo Riesgo)"),
Horas_Extra = factor(c("Si", "No"), levels = levels(datos_estudio$Horas_Extra)),
Viaje_Negocios = factor(c("Frecuentemente", "Raramente"), levels = levels(datos_estudio$Viaje_Negocios)),
Estado_Civil = factor(c("Soltero", "Casado"), levels = levels(datos_estudio$Estado_Civil)),
Ingreso_Mensual = c(2800, 6500),
Edad = c(28, 40),
Antiguedad_Cargo = c(1, 6)
)
# Estimación del logit (link) y de la probabilidad P(Y = 1 | X)
logit_est <- predict(modelo_logit, newdata = perfiles_eval, type = "link")
prob_est <- predict(modelo_logit, newdata = perfiles_eval, type = "response")
odds_est <- prob_est / (1 - prob_est)
tabla_pred <- perfiles_eval %>%
mutate(
Logit_Estimado = round(logit_est, 4),
Odds_Estimado = round(odds_est, 4),
Prob_Rotacion_Porc = paste0(round(prob_est * 100, 2), "%"),
Decision_Corte = ifelse(prob_est >= umbral_youden, "INTERVENIR (Alerta de Fuga)", "Monitoreo Regular")
) %>%
select(Perfil, Horas_Extra, Viaje_Negocios, Estado_Civil, Ingreso_Mensual,
Edad, Antiguedad_Cargo, Odds_Estimado, Prob_Rotacion_Porc, Decision_Corte)
kable(tabla_pred,
col.names = c("Perfil Evaluado", "H. Extra", "Viajes", "E. Civil", "Ingreso ($)",
"Edad", "Antig.", "Odds", "Prob. P(Y=1)", "Decisión (Corte Óptimo)"),
caption = "Tabla 8. Predicción de probabilidad de rotación y decisión de intervención") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
| Perfil Evaluado | H. Extra | Viajes | E. Civil | Ingreso ($) | Edad | Antig. | Odds | Prob. P(Y=1) | Decisión (Corte Óptimo) |
|---|---|---|---|---|---|---|---|---|---|
| Perfil A (Sujeto Hipotético - Alto Riesgo) | Si | Frecuentemente | Soltero | 2800 | 28 | 1 | 2.8314 | 73.9% | INTERVENIR (Alerta de Fuga) |
| Perfil B (Contraste - Bajo Riesgo) | No | Raramente | Casado | 6500 | 40 | 6 | 0.0509 | 4.85% | Monitoreo Regular |
Interpretación de la Predicción Individual y Regla de Intervención:
Si) y viaja
Frecuentemente, el modelo estima un valor
logit lineal de \(\hat{L} =
1.0408\), equivalente a una razón de posibilidades
(Odds) de 2.8314. Esto indica que para este trabajador
es 2.83 veces más probable que abandone su cargo a que
permanezca en él, traduciéndose en una probabilidad
predicha de rotación de \(\hat{P}(Y = 1
\vert{} X) = 73.90\%\).El desarrollo del Modelo Lineal Generalizado Logit Binomial permitió comprobar empíricamente que la rotación de personal en la organización (16.12% de la plantilla) no ocurre de forma aleatoria, sino que responde de manera altamente significativa (\(\chi^2 = 216.21, p < 0.0001\), \(\text{AUC-ROC} = 0.7757\)) a factores estructurales de sobrecarga operativa, compensación y ciclo de vida laboral.
Con base en las variables que resultaron estadísticamente significativas en los análisis bivariado y multivariado, se propone a la gerencia una Estrategia Integral de Retención de Talento estructurada en cuatro ejes de acción:
Frecuentemente tienen 3.76 veces más posibilidades de rotar
que aquellos que no viajan (tasa de deserción del
24.9%).Soltero presentan una mayor propensión al
cambio laboral (tasa de rotación del 25.5%).Siguiendo la última etapa metodológica del ciclo de modelamiento estadístico (Despliegue del modelo), se desarrolló un Simulador Interactivo de Alerta Temprana para Recursos Humanos. Esta herramienta traduce la ecuación estimada del modelo Logit múltiple en una interfaz de soporte a la decisión donde la gerencia puede modificar en tiempo real las características de cualquier colaborador y observar instantáneamente:
Enlace a la versión interactiva publicada en la web: Haz clic aquí para abrir el Informe y Simulador Interactivo en línea (Nota: Reemplaza el símbolo
#por tu enlace de RPubs una vez lo publiques).