El presente informe analiza la rotacion de empleados en una organizacion mediante un modelo de regresion logistica binomial. El objetivo es identificar los factores que mas inciden en la probabilidad de que un trabajador cambie de cargo, apoyando decisiones estrategicas de retencion del talento humano.
La base de datos rotacion contiene 1,470
registros y 24 variables sobre caracteristicas
laborales y personales de los empleados.
# install.packages("devtools")
# devtools::install_github("centromagis/paqueteMODELOS", force = TRUE)
library(paqueteMODELOS)
library(dplyr)
library(ggplot2)
library(pROC)
library(knitr)
library(kableExtra)
library(gridExtra)
library(scales)
data("rotacion")
glimpse(rotacion)## Rows: 1,470
## Columns: 24
## $ Rotación <chr> "Si", "No", "Si", "No", "No", "No", "No", …
## $ Edad <dbl> 41, 49, 37, 33, 27, 32, 59, 30, 38, 36, 35…
## $ `Viaje de Negocios` <chr> "Raramente", "Frecuentemente", "Raramente"…
## $ Departamento <chr> "Ventas", "IyD", "IyD", "IyD", "IyD", "IyD…
## $ Distancia_Casa <dbl> 1, 8, 2, 3, 2, 2, 3, 24, 23, 27, 16, 15, 2…
## $ Educación <dbl> 2, 1, 2, 4, 1, 2, 3, 1, 3, 3, 3, 2, 1, 2, …
## $ Campo_Educación <chr> "Ciencias", "Ciencias", "Otra", "Ciencias"…
## $ Satisfacción_Ambiental <dbl> 2, 3, 4, 4, 1, 4, 3, 4, 4, 3, 1, 4, 1, 2, …
## $ Genero <chr> "F", "M", "M", "F", "M", "M", "F", "M", "M…
## $ Cargo <chr> "Ejecutivo_Ventas", "Investigador_Cientifi…
## $ Satisfación_Laboral <dbl> 4, 2, 3, 3, 2, 4, 1, 3, 3, 3, 2, 3, 3, 4, …
## $ Estado_Civil <chr> "Soltero", "Casado", "Soltero", "Casado", …
## $ Ingreso_Mensual <dbl> 5993, 5130, 2090, 2909, 3468, 3068, 2670, …
## $ Trabajos_Anteriores <dbl> 8, 1, 6, 1, 9, 0, 4, 1, 0, 6, 0, 0, 1, 0, …
## $ Horas_Extra <chr> "Si", "No", "Si", "Si", "No", "No", "Si", …
## $ Porcentaje_aumento_salarial <dbl> 11, 23, 15, 11, 12, 13, 20, 22, 21, 13, 13…
## $ Rendimiento_Laboral <dbl> 3, 4, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 3, 3, …
## $ Años_Experiencia <dbl> 8, 10, 7, 8, 6, 8, 12, 1, 10, 17, 6, 10, 5…
## $ Capacitaciones <dbl> 0, 3, 3, 3, 3, 2, 3, 2, 2, 3, 5, 3, 1, 2, …
## $ Equilibrio_Trabajo_Vida <dbl> 1, 3, 3, 3, 3, 2, 2, 3, 3, 2, 3, 3, 2, 3, …
## $ Antigüedad <dbl> 6, 10, 0, 8, 2, 7, 1, 1, 9, 7, 5, 9, 5, 2,…
## $ Antigüedad_Cargo <dbl> 4, 7, 0, 7, 2, 7, 0, 0, 7, 7, 4, 5, 2, 2, …
## $ Años_ultima_promoción <dbl> 0, 1, 0, 3, 2, 3, 0, 0, 1, 7, 0, 0, 4, 1, …
## $ Años_acargo_con_mismo_jefe <dbl> 5, 7, 0, 0, 2, 6, 0, 0, 8, 7, 3, 8, 3, 2, …
# Detectar columnas por patron para ser robusto ante diferencias de codificacion
col_rot <- grep("otaci", names(rotacion), value = TRUE)[1]
col_sat <- grep("atisf", names(rotacion), value = TRUE)[1]
col_antic <- grep("ntig.*argo|argo.*ntig", names(rotacion),
ignore.case = TRUE, value = TRUE)[1]
col_viaje <- grep("iaje", names(rotacion), value = TRUE)[1]
col_ec <- grep("stado", names(rotacion), value = TRUE)[1]
col_he <- grep("ora.*xtra|xtra.*ora", names(rotacion),
ignore.case = TRUE, value = TRUE)[1]
col_ing <- grep("ngreso", names(rotacion), value = TRUE)[1]
cat("Columnas identificadas:\n")## Columnas identificadas:
## Rotacion: Rotación
## Satisfaccion: Satisfacción_Ambiental
## Antiguedad Cargo: Antigüedad_Cargo
## Viaje Negocios: Viaje de Negocios
## Estado Civil: Estado_Civil
## Horas Extra: Horas_Extra
## Ingreso Mensual: Ingreso_Mensual
# Crear alias limpios
rotacion <- rotacion %>%
mutate(
ROT = .data[[col_rot]],
SATLAB = .data[[col_sat]],
ANTICARGO= .data[[col_antic]],
VIAJE = .data[[col_viaje]],
EC = .data[[col_ec]],
HE = .data[[col_he]],
ING = .data[[col_ing]],
# Variable respuesta binaria
ROT_bin = ifelse(.data[[col_rot]] == "Si", 1, 0),
# Indicadores para el modelo
HE_bin = ifelse(.data[[col_he]] == "Si", 1, 0),
SOLTERO = ifelse(.data[[col_ec]] == "Soltero", 1, 0),
VJ_FREC = ifelse(.data[[col_viaje]] == "Frecuentemente", 1, 0)
)
cat("\nDistribucion de la variable respuesta:\n")##
## Distribucion de la variable respuesta:
##
## No Si
## 1233 237
Se seleccionan 3 variables categoricas y 3 variables cuantitativas con relacion hipotetica frente a la rotacion.
tabla_rot <- rotacion %>%
count(ROT) %>%
mutate(Porcentaje = round(n / sum(n) * 100, 1))
tabla_rot %>%
kable(col.names = c("Rotacion", "Frecuencia", "Porcentaje (%)"),
caption = "Tabla 1. Frecuencia de Rotacion") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE)| Rotacion | Frecuencia | Porcentaje (%) |
|---|---|---|
| No | 1233 | 83.9 |
| Si | 237 | 16.1 |
ggplot(tabla_rot, aes(x = ROT, y = Porcentaje, fill = ROT)) +
geom_bar(stat = "identity", width = 0.5) +
geom_text(aes(label = paste0(Porcentaje, "%")), vjust = -0.5, size = 5) +
scale_fill_manual(values = c("Si" = "#E74C3C", "No" = "#2ECC71")) +
labs(title = "Distribucion de la Variable Rotacion",
x = "Rotacion", y = "Porcentaje (%)") +
theme_minimal(base_size = 13) +
theme(legend.position = "none")Distribucion de la variable Rotacion
Interpretacion: El 16.1% de los empleados presenta rotacion (237 personas). Se observa un claro desbalance de clases, tipico en fenomenos de rotacion laboral donde la mayoria permanece en sus cargos. Este desbalance debe tenerse en cuenta al interpretar las metricas del modelo.
p1 <- ggplot(rotacion, aes(x = HE, fill = HE)) +
geom_bar(alpha = 0.9) +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.4, size = 4) +
scale_fill_brewer(palette = "Set2") +
labs(title = "Horas Extra", x = "", y = "Frecuencia") +
theme_minimal(base_size = 12) + theme(legend.position = "none")
p2 <- ggplot(rotacion, aes(x = EC, fill = EC)) +
geom_bar(alpha = 0.9) +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.4, size = 4) +
scale_fill_brewer(palette = "Set1") +
labs(title = "Estado Civil", x = "", y = "Frecuencia") +
theme_minimal(base_size = 12) + theme(legend.position = "none")
p3 <- ggplot(rotacion, aes(x = VIAJE, fill = VIAJE)) +
geom_bar(alpha = 0.9) +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.4, size = 4) +
scale_fill_brewer(palette = "Set3") +
labs(title = "Viaje de Negocios", x = "", y = "Frecuencia") +
theme_minimal(base_size = 12) +
theme(legend.position = "none",
axis.text.x = element_text(angle = 15, hjust = 1))
grid.arrange(p1, p2, p3, ncol = 3)Distribucion de variables categoricas
tab_he <- rotacion %>% count(HE) %>% mutate(Pct = round(n/sum(n)*100, 1))
tab_ec <- rotacion %>% count(EC) %>% mutate(Pct = round(n/sum(n)*100, 1))
tab_vn <- rotacion %>% count(VIAJE) %>% mutate(Pct = round(n/sum(n)*100, 1))
tab_he %>%
kable(col.names = c("Horas Extra", "n", "%"),
caption = "Tabla 2. Horas Extra") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE)| Horas Extra | n | % |
|---|---|---|
| No | 1054 | 71.7 |
| Si | 416 | 28.3 |
tab_ec %>%
kable(col.names = c("Estado Civil", "n", "%"),
caption = "Tabla 3. Estado Civil") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE)| Estado Civil | n | % |
|---|---|---|
| Casado | 673 | 45.8 |
| Divorciado | 327 | 22.2 |
| Soltero | 470 | 32.0 |
tab_vn %>%
kable(col.names = c("Viaje de Negocios", "n", "%"),
caption = "Tabla 4. Viaje de Negocios") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE)| Viaje de Negocios | n | % |
|---|---|---|
| Frecuentemente | 277 | 18.8 |
| No_Viaja | 150 | 10.2 |
| Raramente | 1043 | 71.0 |
Interpretacion:
- Horas Extra: El 28.3% trabaja horas extra. Aunque es minoria, sera una variable clave en el modelo.
- Estado Civil: La mayoria son casados (45.8%), seguidos de solteros (32%) y divorciados.
- Viaje de Negocios: El 71% viaja raramente. Solo un porcentaje reducido lo hace frecuentemente.
p4 <- ggplot(rotacion, aes(x = factor(SATLAB))) +
geom_bar(fill = "#3498DB", alpha = 0.85) +
geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.4, size = 4) +
labs(title = "Satisfaccion Laboral",
x = "Nivel (1=Bajo, 4=Alto)", y = "Frecuencia") +
theme_minimal(base_size = 12)
p5 <- ggplot(rotacion, aes(x = ING)) +
geom_histogram(bins = 30, fill = "#9B59B6", color = "white", alpha = 0.85) +
labs(title = "Ingreso Mensual", x = "Ingreso", y = "Frecuencia") +
theme_minimal(base_size = 12)
p6 <- ggplot(rotacion, aes(x = ANTICARGO)) +
geom_histogram(bins = 20, fill = "#E67E22", color = "white", alpha = 0.85) +
labs(title = "Antiguedad en el Cargo", x = "Anos", y = "Frecuencia") +
theme_minimal(base_size = 12)
grid.arrange(p4, p5, p6, ncol = 3)Distribucion de variables cuantitativas
data.frame(
Variable = c("Satisfaccion Laboral", "Ingreso Mensual", "Antiguedad Cargo"),
Min = c(min(rotacion$SATLAB), min(rotacion$ING), min(rotacion$ANTICARGO)),
Media = c(round(mean(rotacion$SATLAB), 2), round(mean(rotacion$ING), 2),
round(mean(rotacion$ANTICARGO), 2)),
Mediana = c(median(rotacion$SATLAB), median(rotacion$ING), median(rotacion$ANTICARGO)),
Max = c(max(rotacion$SATLAB), max(rotacion$ING), max(rotacion$ANTICARGO)),
Desv.Est = c(round(sd(rotacion$SATLAB), 2), round(sd(rotacion$ING), 2),
round(sd(rotacion$ANTICARGO), 2))
) %>%
kable(col.names = c("Variable","Min","Media","Mediana","Max","Desv. Estandar"),
caption = "Tabla 5. Estadisticas descriptivas - Variables cuantitativas") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE)| Variable | Min | Media | Mediana | Max | Desv. Estandar |
|---|---|---|---|---|---|
| Satisfaccion Laboral | 1 | 2.72 | 3 | 4 | 1.09 |
| Ingreso Mensual | 1009 | 6502.93 | 4919 | 19999 | 4707.96 |
| Antiguedad Cargo | 0 | 4.23 | 3 | 18 | 3.62 |
Interpretacion:
- Satisfaccion Laboral: Media de 2.72 en escala 1-4. Distribucion aproximadamente uniforme entre niveles.
- Ingreso Mensual: Distribucion asimetrica positiva (media 6503). La mayoria percibe ingresos bajos con pocos casos de salarios muy altos.
- Antiguedad en el Cargo: Mediana de 3 anos. Concentracion en empleados con poca antiguedad en el cargo actual.
p7 <- rotacion %>%
count(HE, ROT) %>% group_by(HE) %>%
mutate(pct = n / sum(n) * 100) %>%
ggplot(aes(x = HE, y = pct, fill = ROT)) +
geom_bar(stat = "identity", position = "fill", alpha = 0.9) +
geom_text(aes(label = paste0(round(pct,1),"%")),
position = position_fill(vjust = 0.5), size = 3.5, color = "white") +
scale_fill_manual(values = c("Si"="#E74C3C","No"="#2ECC71")) +
scale_y_continuous(labels = percent) +
labs(title = "Horas Extra", x = "", y = "Proporcion", fill = "Rota") +
theme_minimal(base_size = 12)
p8 <- rotacion %>%
count(EC, ROT) %>% group_by(EC) %>%
mutate(pct = n / sum(n) * 100) %>%
ggplot(aes(x = EC, y = pct, fill = ROT)) +
geom_bar(stat = "identity", position = "fill", alpha = 0.9) +
geom_text(aes(label = paste0(round(pct,1),"%")),
position = position_fill(vjust = 0.5), size = 3.5, color = "white") +
scale_fill_manual(values = c("Si"="#E74C3C","No"="#2ECC71")) +
scale_y_continuous(labels = percent) +
labs(title = "Estado Civil", x = "", y = "Proporcion", fill = "Rota") +
theme_minimal(base_size = 12)
p9 <- rotacion %>%
count(VIAJE, ROT) %>% group_by(VIAJE) %>%
mutate(pct = n / sum(n) * 100) %>%
ggplot(aes(x = VIAJE, y = pct, fill = ROT)) +
geom_bar(stat = "identity", position = "fill", alpha = 0.9) +
geom_text(aes(label = paste0(round(pct,1),"%")),
position = position_fill(vjust = 0.5), size = 3.5, color = "white") +
scale_fill_manual(values = c("Si"="#E74C3C","No"="#2ECC71")) +
scale_y_continuous(labels = percent) +
labs(title = "Viaje de Negocios", x = "", y = "Proporcion", fill = "Rota") +
theme_minimal(base_size = 12) +
theme(axis.text.x = element_text(angle = 15, hjust = 1))
grid.arrange(p7, p8, p9, ncol = 3)Proporcion de rotacion por variable categorica
p10 <- ggplot(rotacion, aes(x = ROT, y = SATLAB, fill = ROT)) +
geom_boxplot(alpha = 0.75) +
scale_fill_manual(values = c("Si"="#E74C3C","No"="#2ECC71")) +
labs(title = "Satisfaccion Laboral", x = "Rotacion", y = "Nivel") +
theme_minimal(base_size = 12) + theme(legend.position = "none")
p11 <- ggplot(rotacion, aes(x = ROT, y = ING, fill = ROT)) +
geom_boxplot(alpha = 0.75) +
scale_fill_manual(values = c("Si"="#E74C3C","No"="#2ECC71")) +
labs(title = "Ingreso Mensual", x = "Rotacion", y = "Ingreso") +
theme_minimal(base_size = 12) + theme(legend.position = "none")
p12 <- ggplot(rotacion, aes(x = ROT, y = ANTICARGO, fill = ROT)) +
geom_boxplot(alpha = 0.75) +
scale_fill_manual(values = c("Si"="#E74C3C","No"="#2ECC71")) +
labs(title = "Antiguedad en el Cargo", x = "Rotacion", y = "Anos") +
theme_minimal(base_size = 12) + theme(legend.position = "none")
grid.arrange(p10, p11, p12, ncol = 3)Distribucion de variables cuantitativas segun Rotacion
biv_result <- function(var_name, label) {
form <- as.formula(paste("ROT_bin ~", var_name))
m <- glm(form, data = rotacion, family = binomial)
s <- coef(summary(m))[2, ]
data.frame(
Variable = label,
Beta = round(s["Estimate"], 4),
OR = round(exp(s["Estimate"]), 4),
pvalor = round(s["Pr(>|z|)"], 4),
Sig = ifelse(s["Pr(>|z|)"] < 0.05, "Significativa", "No significativa"),
stringsAsFactors = FALSE
)
}
res_biv <- rbind(
biv_result("HE_bin", "Horas Extra (Si=1)"),
biv_result("SOLTERO", "Estado Civil Soltero (1=Si)"),
biv_result("VJ_FREC", "Viaje Frecuente (1=Si)"),
biv_result("SATLAB", "Satisfaccion Laboral"),
biv_result("ING", "Ingreso Mensual"),
biv_result("ANTICARGO", "Antiguedad en el Cargo")
)
res_biv %>%
kable(col.names = c("Variable", "Beta", "OR", "p-valor", "Significancia"),
caption = "Tabla 6. Analisis bivariado: Regresion logistica simple por variable") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE) %>%
row_spec(which(res_biv$Sig == "Significativa"), bold = TRUE, color = "#C0392B")| Variable | Beta | OR | p-valor | Significancia | |
|---|---|---|---|---|---|
| Estimate | Horas Extra (Si=1) | 1.3274 | 3.7712 | 0e+00 | Significativa |
| Estimate1 | Estado Civil Soltero (1=Si) | 0.9507 | 2.5875 | 0e+00 | Significativa |
| Estimate2 | Viaje Frecuente (1=Si) | 0.7051 | 2.0240 | 0e+00 | Significativa |
| Estimate3 | Satisfaccion Laboral | -0.2531 | 0.7764 | 1e-04 | Significativa |
| Estimate4 | Ingreso Mensual | -0.0001 | 0.9999 | 0e+00 | Significativa |
| Estimate5 | Antiguedad en el Cargo | -0.1463 | 0.8639 | 0e+00 | Significativa |
Interpretacion:
- Horas Extra: Beta positivo y significativo. Trabajar horas extra aumenta la probabilidad de rotar. Confirma la hipotesis.
- Estado Civil Soltero: Beta positivo y significativo. Los solteros rotan mas. Confirma la hipotesis.
- Viaje Frecuente: Beta positivo y significativo. Los viajes frecuentes aumentan la rotacion. Confirma la hipotesis.
- Satisfaccion Laboral: Beta negativo y significativo. Mayor satisfaccion reduce la rotacion. Confirma la hipotesis.
- Ingreso Mensual: Beta negativo y significativo. Mayores ingresos reducen la rotacion. Confirma la hipotesis.
- Antiguedad en el Cargo: Beta negativo y significativo. Mayor antiguedad reduce la rotacion. Confirma la hipotesis.Las seis hipotesis planteadas en el Punto 1 fueron respaldadas por el analisis bivariado.
modelo <- glm(
ROT_bin ~ HE_bin + SOLTERO + VJ_FREC + SATLAB + ING + ANTICARGO,
data = rotacion,
family = binomial(link = "logit")
)
summary(modelo)##
## Call:
## glm(formula = ROT_bin ~ HE_bin + SOLTERO + VJ_FREC + SATLAB +
## ING + ANTICARGO, family = binomial(link = "logit"), data = rotacion)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -8.805e-01 2.520e-01 -3.494 0.000476 ***
## HE_bin 1.520e+00 1.604e-01 9.478 < 2e-16 ***
## SOLTERO 9.471e-01 1.578e-01 6.002 1.95e-09 ***
## VJ_FREC 7.070e-01 1.801e-01 3.925 8.66e-05 ***
## SATLAB -3.437e-01 7.125e-02 -4.824 1.41e-06 ***
## ING -1.056e-04 2.344e-05 -4.506 6.59e-06 ***
## ANTICARGO -1.038e-01 2.737e-02 -3.793 0.000149 ***
## ---
## 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: 1073.7 on 1463 degrees of freedom
## AIC: 1087.7
##
## Number of Fisher Scoring iterations: 5
coefs <- coef(summary(modelo))
OR_vec <- exp(coef(modelo))
ic <- exp(confint.default(modelo))
data.frame(
Variable = c("Intercepto", "Horas Extra (Si)", "Soltero (Si)",
"Viaje Frecuente (Si)", "Satisfaccion Laboral",
"Ingreso Mensual", "Antiguedad en Cargo"),
Beta = round(coefs[, "Estimate"], 4),
OR = round(OR_vec, 4),
IC_inf = round(ic[, 1], 4),
IC_sup = round(ic[, 2], 4),
pvalor = round(coefs[, "Pr(>|z|)"], 4),
Sig = ifelse(coefs[, "Pr(>|z|)"] < 0.05, "Si *", "No"),
row.names = NULL
) %>%
kable(col.names = c("Variable","Beta","OR","IC 2.5%","IC 97.5%","p-valor","Sig (p<0.05)"),
caption = "Tabla 7. Coeficientes del modelo logistico multiple con OR e IC 95%") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"), full_width = FALSE) %>%
row_spec(which(coefs[, "Pr(>|z|)"] < 0.05), bold = TRUE, color = "#C0392B")| Variable | Beta | OR | IC 2.5% | IC 97.5% | p-valor | Sig (p<0.05) |
|---|---|---|---|---|---|---|
| Intercepto | -0.8805 | 0.4146 | 0.2530 | 0.6794 | 5e-04 | Si * |
| Horas Extra (Si) | 1.5198 | 4.5714 | 3.3386 | 6.2596 | 0e+00 | Si * |
| Soltero (Si) | 0.9471 | 2.5783 | 1.8924 | 3.5128 | 0e+00 | Si * |
| Viaje Frecuente (Si) | 0.7070 | 2.0279 | 1.4247 | 2.8864 | 1e-04 | Si * |
| Satisfaccion Laboral | -0.3437 | 0.7092 | 0.6167 | 0.8154 | 0e+00 | Si * |
| Ingreso Mensual | -0.0001 | 0.9999 | 0.9998 | 0.9999 | 0e+00 | Si * |
| Antiguedad en Cargo | -0.1038 | 0.9014 | 0.8543 | 0.9511 | 1e-04 | Si * |
Horas Extra (OR = 4.571):
Los empleados que trabajan horas extra tienen 4.57 veces
mas probabilidad de rotar en comparacion con quienes no
trabajan horas extra, manteniendo las demas variables constantes. Es el
factor de mayor impacto en el modelo.
Estado Civil Soltero (OR = 2.578):
Los empleados solteros tienen 2.58 veces mas
probabilidad de rotar que los no solteros.
Viaje Frecuente (OR = 2.028):
Quienes viajan frecuentemente tienen 2.03 veces mas
probabilidad de rotar respecto a quienes no viajan o lo hacen
raramente.
Satisfaccion Laboral (OR = 0.709):
Por cada unidad adicional de satisfaccion, la probabilidad de rotar
disminuye en 29.1%, manteniendo el resto constante.
Ingreso Mensual (OR = 0.999894):
Cada unidad adicional de ingreso reduce marginalmente la probabilidad de
rotacion (OR cercano pero menor a 1). El efecto es significativo
acumulado sobre el rango completo de salarios.
Antiguedad en el Cargo (OR = 0.901):
Por cada ano adicional en el cargo, la probabilidad de rotar
disminuye en 9.9%.
rotacion$prob_pred <- predict(modelo, type = "response")
roc_obj <- roc(rotacion$ROT_bin, rotacion$prob_pred, quiet = TRUE)
auc_val <- auc(roc_obj)
plot(roc_obj,
col = "#E74C3C",
lwd = 2.5,
main = paste0("Curva ROC | AUC = ", round(auc_val, 4)),
xlab = "1 - Especificidad (Tasa de Falsos Positivos)",
ylab = "Sensibilidad (Tasa de Verdaderos Positivos)",
legacy.axes = TRUE)
abline(a = 0, b = 1, col = "gray60", lty = 2)
legend("bottomright",
legend = paste0("AUC = ", round(auc_val, 4)),
col = "#E74C3C", lwd = 2.5, bty = "n")Curva ROC del modelo de regresion logistica
Interpretacion del AUC = 0.7849:
Rango AUC Interpretacion 0.50 Sin poder discriminatorio (azar) 0.70 - 0.80 Poder aceptable 0.80 - 0.90 Poder bueno > 0.90 Poder excelente El modelo presenta un poder predictivo ACEPTABLE, siendo capaz de discriminar de forma adecuada entre empleados que rotan y los que permanecen.
rotacion$clase_pred <- ifelse(rotacion$prob_pred >= 0.5, 1, 0)
conf_mat <- table(Real = rotacion$ROT_bin,
Predicho = rotacion$clase_pred)
conf_mat %>%
kable(caption = "Tabla 8. Matriz de confusion con punto de corte = 0.50") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE)| 0 | 1 | |
|---|---|---|
| 0 | 1208 | 25 |
| 1 | 197 | 40 |
VP <- conf_mat["1","1"]; VN <- conf_mat["0","0"]
FP <- conf_mat["0","1"]; FN <- conf_mat["1","0"]
data.frame(
Metrica = c("Exactitud (Accuracy)", "Sensibilidad (Recall)", "Especificidad"),
Formula = c("(VP+VN) / Total", "VP / (VP+FN)", "VN / (VN+FP)"),
Valor = paste0(c(
round((VP+VN)/sum(conf_mat)*100, 2),
round(VP/(VP+FN)*100, 2),
round(VN/(VN+FP)*100, 2)
), "%")
) %>%
kable(caption = "Tabla 9. Metricas de desempeno del modelo",
col.names = c("Metrica", "Formula", "Valor")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE)| Metrica | Formula | Valor |
|---|---|---|
| Exactitud (Accuracy) | (VP+VN) / Total | 84.9% |
| Sensibilidad (Recall) | VP / (VP+FN) | 16.88% |
| Especificidad | VN / (VN+FP) | 97.97% |
Se define un empleado con caracteristicas de alto riesgo de rotacion para ilustrar el uso del modelo:
empleado <- data.frame(
HE_bin = 1,
SOLTERO = 1,
VJ_FREC = 1,
SATLAB = 1,
ING = 2500,
ANTICARGO = 1
)
data.frame(
Caracteristica = c("Horas Extra", "Estado Civil", "Viaje de Negocios",
"Satisfaccion Laboral", "Ingreso Mensual",
"Antiguedad en el Cargo"),
Valor = c("Si (1)", "Soltero (1)", "Frecuente (1)",
"1 (Muy baja)", "$2,500", "1 ano"),
Justificacion = c("Perfil de riesgo maximo", "Mayor movilidad laboral",
"Alto desgaste", "Insatisfecho",
"Salario por debajo del promedio",
"Recien ingresado al cargo")
) %>%
kable(caption = "Tabla 10. Perfil del empleado hipotetico",
col.names = c("Caracteristica", "Valor", "Justificacion")) %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE)| Caracteristica | Valor | Justificacion |
|---|---|---|
| Horas Extra | Si (1) | Perfil de riesgo maximo |
| Estado Civil | Soltero (1) | Mayor movilidad laboral |
| Viaje de Negocios | Frecuente (1) | Alto desgaste |
| Satisfaccion Laboral | 1 (Muy baja) | Insatisfecho |
| Ingreso Mensual | $2,500 | Salario por debajo del promedio |
| Antiguedad en el Cargo | 1 ano | Recien ingresado al cargo |
prob_rot <- predict(modelo, newdata = empleado, type = "response")
cat("\nProbabilidad estimada de rotacion:", round(prob_rot * 100, 2), "%\n")##
## Probabilidad estimada de rotacion: 82.95 %
## --------------------------------------------------------
## Umbral de intervencion definido : 30 %
## Probabilidad estimada del empleado: 82.95 %
## --------------------------------------------------------
if (prob_rot >= corte) {
cat("DECISION: INTERVENIR al empleado.\n")
cat("La probabilidad supera el umbral del", corte*100,
"%. Activar estrategias de retencion.\n")
} else {
cat("DECISION: No requiere intervencion inmediata.\n")
}## DECISION: INTERVENIR al empleado.
## La probabilidad supera el umbral del 30 %. Activar estrategias de retencion.
Justificacion del umbral al 30%: Dado que la rotacion tiene costos organizacionales elevados (reclutamiento, capacitacion, perdida de conocimiento), se adopta un umbral conservador. Esto maximiza la sensibilidad del modelo (detectar empleados que si rotaran), aceptando intervenir algunos que no necesariamente lo harian. El beneficio de retener un empleado clave supera el costo de una intervencion preventiva innecesaria.
data.frame(
Variable = c("Horas Extra","Estado Civil Soltero","Viaje Frecuente",
"Satisfaccion Laboral","Ingreso Mensual","Antiguedad Cargo"),
Efecto = c("Aumenta rotacion","Aumenta rotacion","Aumenta rotacion",
"Reduce rotacion","Reduce rotacion","Reduce rotacion"),
OR = round(c(OR_vec["HE_bin"], OR_vec["SOLTERO"], OR_vec["VJ_FREC"],
OR_vec["SATLAB"], OR_vec["ING"], OR_vec["ANTICARGO"]), 4),
pvalor = round(coefs[c("HE_bin","SOLTERO","VJ_FREC",
"SATLAB","ING","ANTICARGO"), "Pr(>|z|)"], 4),
Hipotesis = rep("Confirmada", 6),
row.names = NULL
) %>%
kable(col.names = c("Variable","Efecto sobre Rotacion","OR","p-valor","Hipotesis"),
caption = "Tabla 11. Resumen de variables significativas del modelo") %>%
kable_styling(bootstrap_options = c("striped","hover","condensed"),
full_width = FALSE) %>%
row_spec(1:3, color = "#C0392B") %>%
row_spec(4:6, color = "#1A7A4A")| Variable | Efecto sobre Rotacion | OR | p-valor | Hipotesis |
|---|---|---|---|---|
| Horas Extra | Aumenta rotacion | 4.5714 | 0e+00 | Confirmada |
| Estado Civil Soltero | Aumenta rotacion | 2.5783 | 0e+00 | Confirmada |
| Viaje Frecuente | Aumenta rotacion | 2.0279 | 1e-04 | Confirmada |
| Satisfaccion Laboral | Reduce rotacion | 0.7092 | 0e+00 | Confirmada |
| Ingreso Mensual | Reduce rotacion | 0.9999 | 0e+00 | Confirmada |
| Antiguedad Cargo | Reduce rotacion | 0.9014 | 1e-04 | Confirmada |
El modelo de regresion logistica desarrollado alcanza un AUC = 0.785, evidenciando un poder predictivo aceptable para discriminar entre empleados que rotan y los que permanecen. Todas las hipotesis planteadas inicialmente fueron confirmadas por los datos.
Implementar este modelo como herramienta de alerta temprana en Recursos Humanos permitira a la empresa actuar de forma proactiva sobre los empleados con mayor riesgo, antes de que la decision de rotar sea irreversible. La estrategia mas costo-efectiva a corto plazo es controlar las horas extra, por ser el predictor de mayor impacto. A mediano plazo, mejorar la satisfaccion laboral y la competitividad salarial generara un ambiente organizacional mas estable y comprometido.
paqueteMODELOS. Centro MAGIS.Informe generado con R Markdown | Actividad 3 - Modelos Lineales Generalizados