# VALIDACIÓN DE CALIDAD DE DATOS:
# 1. Estructura básica
cat(" - Observaciones:", nrow(rotacion), "\n")
## - Observaciones: 1470
cat(" - Variables:", ncol(rotacion), "\n")
## - Variables: 24
# 2. Análisis de missing values
missing_summary <- miss_var_summary(rotacion)
print(missing_summary)
## # A tibble: 24 × 3
## variable n_miss pct_miss
## <chr> <int> <num>
## 1 Rotación 0 0
## 2 Edad 0 0
## 3 Viaje de Negocios 0 0
## 4 Departamento 0 0
## 5 Distancia_Casa 0 0
## 6 Educación 0 0
## 7 Campo_Educación 0 0
## 8 Satisfacción_Ambiental 0 0
## 9 Genero 0 0
## 10 Cargo 0 0
## # ℹ 14 more rows
if(any(missing_summary$n_miss > 0)) {
cat(" ⚠️ EXISTEN VALORES MISSING\n")
} else {
cat(" ✅ NO HAY VALORES MISSING\n")
}
## ✅ NO HAY VALORES MISSING
# 3. Verificar valores únicos de variables clave
cat("Departamento:", unique(rotacion$Departamento), "\n")
## Departamento: Ventas IyD RH
cat("Estado_Civil:", unique(rotacion$Estado_Civil), "\n")
## Estado_Civil: Soltero Casado Divorciado
cat("Horas_Extra:", unique(rotacion$Horas_Extra), "\n")
## Horas_Extra: Si No
cat("Rotación:", unique(rotacion$Rotación), "\n")
## Rotación: Si No
# 4. Preparar datos limpios
rotacion_clean <- rotacion
cat("\n4. DATOS PREPARADOS PARA ANÁLISIS\n")
##
## 4. DATOS PREPARADOS PARA ANÁLISIS
# Variable respuesta: Rotación
rotacion_clean <- rotacion_clean %>%
mutate(Rotacion_bin = ifelse(Rotación == "Si", 1, 0))
tabla_rotacion <- table(rotacion_clean$Rotación)
prop_rotacion <- prop.table(tabla_rotacion) * 100
cat("No rotó:", tabla_rotacion["No"], "(", round(prop_rotacion["No"], 1), "%)\n")
## No rotó: 1233 ( 83.9 %)
cat("Sí rotó:", tabla_rotacion["Si"], "(", round(prop_rotacion["Si"], 1), "%)\n\n")
## Sí rotó: 237 ( 16.1 %)
# Gráfico de rotación
ggplot(rotacion_clean, aes(x = Rotación, fill = Rotación)) +
geom_bar() +
geom_text(stat = 'count', aes(label = ..count..), vjust = -0.5) +
labs(title = "Figura 1.Distribución de Rotación de Empleados",
x = "", y = "Frecuencia") +
theme_minimal()
# Horas Extra
ggplot(rotacion_clean, aes(x = Horas_Extra)) +
geom_bar(aes(y = after_stat(count/sum(count)*100)), fill = "steelblue") +
geom_text(aes(label = scales::percent(after_stat(count/sum(count)), accuracy = 0.1),
y = after_stat(count/sum(count)*100)),
stat = "count", vjust = -0.5, size = 3) +
labs(title = "Figura 2. Distribución de Horas Extra", x = "", y = "Porcentaje (%)") +
scale_y_continuous(labels = scales::percent_format(scale = 1)) +
theme_minimal()
# Estado Civil
ggplot(rotacion_clean, aes(x = fct_infreq(Estado_Civil))) +
geom_bar(aes(y = after_stat(count/sum(count)*100)), fill = "darkred") +
geom_text(aes(label = scales::percent(after_stat(count/sum(count)), accuracy = 0.1),
y = after_stat(count/sum(count)*100)),
stat = "count", vjust = -0.5, size = 3) +
labs(title = "Figura 3. Distribución por Estado Civil", x = "", y = "Porcentaje (%)") +
scale_y_continuous(labels = scales::percent_format(scale = 1)) +
theme_minimal()
# Departamento
ggplot(rotacion_clean, aes(x = fct_infreq(Departamento))) +
geom_bar(aes(y = after_stat(count/sum(count)*100)), fill = "darkgreen") +
geom_text(aes(label = scales::percent(after_stat(count/sum(count)), accuracy = 0.1),
y = after_stat(count/sum(count)*100)),
stat = "count", vjust = -0.5, size = 3) +
labs(title = "Figura 4. Distribución por Departamento", x = "", y = "Porcentaje (%)") +
scale_y_continuous(labels = scales::percent_format(scale = 1)) +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
# Función para análisis
analisis_univariado <- function(variable, nombre, color) {
cat(paste("\n", strrep("=", 60), "\n"))
cat(paste("RESUMEN ESTADÍSTICO:", nombre, "\n"))
cat(paste(strrep("=", 60), "\n"))
# Calcular estadísticas
estadisticas <- data.frame(
Estadístico = c("Mínimo", "1er Cuartil", "Mediana", "Media", "3er Cuartil", "Máximo",
"Desviación Estándar", "Varianza", "IQR", "Coef. Variación", "NAs"),
Valor = c(
min(variable, na.rm = TRUE),
quantile(variable, 0.25, na.rm = TRUE),
median(variable, na.rm = TRUE),
mean(variable, na.rm = TRUE),
quantile(variable, 0.75, na.rm = TRUE),
max(variable, na.rm = TRUE),
sd(variable, na.rm = TRUE),
var(variable, na.rm = TRUE),
IQR(variable, na.rm = TRUE),
sd(variable, na.rm = TRUE) / mean(variable, na.rm = TRUE),
sum(is.na(variable))
)
)
# Formatear números
estadisticas$Valor <- round(estadisticas$Valor, 3)
# Mostrar tabla formateada
print(knitr::kable(estadisticas, align = c('l', 'r'),
caption = paste("Tabla Estadística:", nombre)))
# Crear gráfico
p <- ggplot(rotacion_clean, aes(x = variable)) +
geom_histogram(fill = color, alpha = 0.7, bins = 30, color = "white") +
geom_vline(aes(xintercept = mean(variable, na.rm = TRUE)),
color = "red", linetype = "dashed", size = 1) +
geom_vline(aes(xintercept = median(variable, na.rm = TRUE)),
color = "blue", linetype = "dashed", size = 1) +
labs(title = nombre,
subtitle = "Línea roja: Media | Línea azul: Mediana",
x = nombre, y = "Frecuencia") +
theme_minimal()
print(p)
return(estadisticas)
}
# Aplicar a variables seleccionadas
resultado1 <- analisis_univariado(rotacion_clean$Ingreso_Mensual, "Figura 5. Ingreso Mensual", "red")
##
## ============================================================
## RESUMEN ESTADÍSTICO: Figura 5. Ingreso Mensual
## ============================================================
##
##
## Table: Tabla Estadística: Figura 5. Ingreso Mensual
##
## |Estadístico | Valor|
## |:-------------------|------------:|
## |Mínimo | 1009.000|
## |1er Cuartil | 2911.000|
## |Mediana | 4919.000|
## |Media | 6502.931|
## |3er Cuartil | 8379.000|
## |Máximo | 19999.000|
## |Desviación Estándar | 4707.957|
## |Varianza | 22164857.072|
## |IQR | 5468.000|
## |Coef. Variación | 0.724|
## |NAs | 0.000|
resultado2 <- analisis_univariado(rotacion_clean$Años_ultima_promoción, "Figura 6. Años desde Última Promoción", "blue")
##
## ============================================================
## RESUMEN ESTADÍSTICO: Figura 6. Años desde Última Promoción
## ============================================================
##
##
## Table: Tabla Estadística: Figura 6. Años desde Última Promoción
##
## |Estadístico | Valor|
## |:-------------------|------:|
## |Mínimo | 0.000|
## |1er Cuartil | 0.000|
## |Mediana | 1.000|
## |Media | 2.188|
## |3er Cuartil | 3.000|
## |Máximo | 15.000|
## |Desviación Estándar | 3.222|
## |Varianza | 10.384|
## |IQR | 3.000|
## |Coef. Variación | 1.473|
## |NAs | 0.000|
resultado3 <- analisis_univariado(rotacion_clean$Edad, "Figura 7. Edad", "green")
##
## ============================================================
## RESUMEN ESTADÍSTICO: Figura 7. Edad
## ============================================================
##
##
## Table: Tabla Estadística: Figura 7. Edad
##
## |Estadístico | Valor|
## |:-------------------|------:|
## |Mínimo | 18.000|
## |1er Cuartil | 30.000|
## |Mediana | 36.000|
## |Media | 36.924|
## |3er Cuartil | 43.000|
## |Máximo | 60.000|
## |Desviación Estándar | 9.136|
## |Varianza | 83.465|
## |IQR | 13.000|
## |Coef. Variación | 0.247|
## |NAs | 0.000|
# Función para análisis bivariado categórico
analisis_bivariado_cat <- function(var_cat, nombre_var, numero_figura) {
# Crear tablas
tabla <- table(rotacion_clean[[var_cat]], rotacion_clean$Rotacion_bin)
prop_tabla <- prop.table(tabla, margin = 1) * 100
# Encabezado organizado
cat("\n", rep("=", 60), "\n", sep = "")
cat("ANÁLISIS:", nombre_var, "vs ROTACIÓN\n")
cat(rep("=", 60), "\n\n", sep = "")
# Tabla de frecuencias absolutas
cat("FRECUENCIAS ABSOLUTAS:\n")
cat(rep("-", 40), "\n", sep = "")
tabla_con_margenes <- addmargins(tabla)
# Asignar nombres descriptivos a las columnas
colnames(tabla_con_margenes) <- c("No Rotó", "Sí Rotó", "Total")
rownames(tabla_con_margenes) <- c(rownames(tabla), "Total")
print(tabla_con_margenes)
# Tabla de porcentajes
cat("\nPORCENTAJES POR FILA (%):\n")
cat(rep("-", 40), "\n", sep = "")
prop_df <- as.data.frame.matrix(prop_tabla)
colnames(prop_df) <- c("No Rotó (%)", "Sí Rotó (%)")
# Agregar totales
prop_df$Total <- rowSums(prop_df)
print(round(prop_df, 1))
# Test chi-cuadrado con interpretación
test_chi <- chisq.test(tabla)
cat("\nPRUEBA ESTADÍSTICA:\n")
cat(rep("-", 40), "\n", sep = "")
cat("Chi-cuadrado =", round(test_chi$statistic, 3), "\n")
cat("p-value =", round(test_chi$p.value, 4), "\n")
# Interpretación del resultado
if (test_chi$p.value < 0.05) {
cat("Resultado: RELACIÓN SIGNIFICATIVA (p < 0.05)\n")
cat("→ Existe asociación entre", nombre_var, "y la rotación\n")
} else {
cat("Resultado: NO HAY RELACIÓN SIGNIFICATIVA (p ≥ 0.05)\n")
cat("→ No hay evidencia de asociación entre", nombre_var, "y la rotación\n")
}
# Preparar datos para el gráfico
datos_grafico <- rotacion_clean %>%
group_by(!!sym(var_cat), Rotacion_bin) %>%
summarise(n = n(), .groups = 'drop') %>%
group_by(!!sym(var_cat)) %>%
mutate(
porcentaje = n / sum(n) * 100,
etiqueta = paste0(round(porcentaje, 1), "%")
)
# Gráfico
grafico <- ggplot(datos_grafico, aes(x = !!sym(var_cat), y = n, fill = factor(Rotacion_bin))) +
geom_bar(stat = "identity", position = "fill") +
geom_text(aes(label = etiqueta),
position = position_fill(vjust = 0.5),
size = 3, color = "white", fontface = "bold") +
labs(title = paste("Figura", numero_figura, ". Rotación por", nombre_var),
subtitle = paste("Chi-cuadrado p-value =", round(test_chi$p.value, 4)),
x = nombre_var, y = "Proporción") +
scale_fill_manual(values = c("0" = "steelblue", "1" = "tomato"),
labels = c("No Rotó", "Sí Rotó"),
name = "Rotación") +
scale_y_continuous(labels = scales::percent) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
plot.title = element_text(face = "bold", size = 14))
return(grafico)
}
# Aplicar a variables categóricas c
cat("ANÁLISIS BIVARIADO DE ROTACIÓN LABORAL\n")
## ANÁLISIS BIVARIADO DE ROTACIÓN LABORAL
cat(rep("=", 50), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
resultado1 <- analisis_bivariado_cat("Horas_Extra", "Horas Extra", 8)
##
## ============================================================
## ANÁLISIS: Horas Extra vs ROTACIÓN
## ============================================================
##
## FRECUENCIAS ABSOLUTAS:
## ----------------------------------------
##
## No Rotó Sí Rotó Total
## No 944 110 1054
## Si 289 127 416
## Total 1233 237 1470
##
## PORCENTAJES POR FILA (%):
## ----------------------------------------
## No Rotó (%) Sí Rotó (%) Total
## No 89.6 10.4 100
## Si 69.5 30.5 100
##
## PRUEBA ESTADÍSTICA:
## ----------------------------------------
## Chi-cuadrado = 87.564
## p-value = 0
## Resultado: RELACIÓN SIGNIFICATIVA (p < 0.05)
## → Existe asociación entre Horas Extra y la rotación
resultado2 <- analisis_bivariado_cat("Estado_Civil", "Estado Civil", 9)
##
## ============================================================
## ANÁLISIS: Estado Civil vs ROTACIÓN
## ============================================================
##
## FRECUENCIAS ABSOLUTAS:
## ----------------------------------------
##
## No Rotó Sí Rotó Total
## Casado 589 84 673
## Divorciado 294 33 327
## Soltero 350 120 470
## Total 1233 237 1470
##
## PORCENTAJES POR FILA (%):
## ----------------------------------------
## No Rotó (%) Sí Rotó (%) Total
## Casado 87.5 12.5 100
## Divorciado 89.9 10.1 100
## Soltero 74.5 25.5 100
##
## PRUEBA ESTADÍSTICA:
## ----------------------------------------
## Chi-cuadrado = 46.164
## p-value = 0
## Resultado: RELACIÓN SIGNIFICATIVA (p < 0.05)
## → Existe asociación entre Estado Civil y la rotación
resultado3 <- analisis_bivariado_cat("Departamento", "Departamento", 10)
##
## ============================================================
## ANÁLISIS: Departamento vs ROTACIÓN
## ============================================================
##
## FRECUENCIAS ABSOLUTAS:
## ----------------------------------------
##
## No Rotó Sí Rotó Total
## IyD 828 133 961
## RH 51 12 63
## Ventas 354 92 446
## Total 1233 237 1470
##
## PORCENTAJES POR FILA (%):
## ----------------------------------------
## No Rotó (%) Sí Rotó (%) Total
## IyD 86.2 13.8 100
## RH 81.0 19.0 100
## Ventas 79.4 20.6 100
##
## PRUEBA ESTADÍSTICA:
## ----------------------------------------
## Chi-cuadrado = 10.796
## p-value = 0.0045
## Resultado: RELACIÓN SIGNIFICATIVA (p < 0.05)
## → Existe asociación entre Departamento y la rotación
# Mostrar gráficos
print(resultado1)
print(resultado2)
print(resultado3)
# Análisis bivariado para variables numéricas con mejor presentación
analisis_bivariado_num <- function(var_num, nombre_var, numero_figura) {
# Realizar test t
test_t <- t.test(rotacion_clean[[var_num]] ~ rotacion_clean$Rotacion_bin)
# Calcular estadísticas descriptivas
stats_no_roto <- rotacion_clean[[var_num]][rotacion_clean$Rotacion_bin == 0]
stats_roto <- rotacion_clean[[var_num]][rotacion_clean$Rotacion_bin == 1]
# Encabezado organizado
cat("\n", rep("=", 60), "\n", sep = "")
cat("ANÁLISIS:", nombre_var, "vs ROTACIÓN\n")
cat(rep("=", 60), "\n\n", sep = "")
# Tabla de estadísticas descriptivas
cat("ESTADÍSTICAS DESCRIPTIVAS:\n")
cat(rep("-", 50), "\n", sep = "")
tabla_stats <- data.frame(
Estadístico = c("n", "Media", "Desviación Estándar", "Mínimo", "Mediana", "Máximo"),
No_Rotó = c(
length(stats_no_roto),
round(mean(stats_no_roto), 2),
round(sd(stats_no_roto), 2),
round(min(stats_no_roto), 2),
round(median(stats_no_roto), 2),
round(max(stats_no_roto), 2)
),
Rotó = c(
length(stats_roto),
round(mean(stats_roto), 2),
round(sd(stats_roto), 2),
round(min(stats_roto), 2),
round(median(stats_roto), 2),
round(max(stats_roto), 2)
)
)
colnames(tabla_stats) <- c("Estadístico", "No Rotó", "Sí Rotó")
print(tabla_stats, row.names = FALSE)
# Test t-student
cat("\nPRUEBA T-STUDENT (Comparación de Medias):\n")
cat(rep("-", 50), "\n", sep = "")
cat("Diferencia de medias:", round(test_t$estimate[1] - test_t$estimate[2], 2), "\n")
cat("t-value:", round(test_t$statistic, 3), "\n")
cat("p-value:", round(test_t$p.value, 4), "\n")
cat("Intervalo de confianza (95%): [",
round(test_t$conf.int[1], 2), ", ",
round(test_t$conf.int[2], 2), "]\n", sep = "")
# Interpretación del resultado
if (test_t$p.value < 0.05) {
cat("Resultado: DIFERENCIA SIGNIFICATIVA (p < 0.05)\n")
cat("→ Existe diferencia significativa en", nombre_var, "entre grupos\n")
} else {
cat("Resultado: NO HAY DIFERENCIA SIGNIFICATIVA (p ≥ 0.05)\n")
cat("→ No hay evidencia de diferencia en", nombre_var, "entre grupos\n")
}
# Gráfico con numeración de figura
grafico <- ggplot(rotacion_clean, aes(x = factor(Rotacion_bin), y = .data[[var_num]],
fill = factor(Rotacion_bin))) +
geom_boxplot(alpha = 0.8) +
stat_summary(fun = mean, geom = "point", shape = 23, size = 3, fill = "white") +
labs(title = paste("Figura", numero_figura, ".", nombre_var, "vs Rotación"),
subtitle = paste("Test t: p =", round(test_t$p.value, 4)),
x = "Rotación",
y = nombre_var,
caption = paste("Puntos blancos = Media | n(total) =", nrow(rotacion_clean))) +
scale_fill_manual(values = c("0" = "steelblue", "1" = "tomato"),
labels = c("No Rotó", "Sí Rotó")) +
scale_x_discrete(labels = c("No Rotó", "Sí Rotó")) +
theme_minimal() +
theme(legend.position = "none",
plot.title = element_text(face = "bold", size = 14),
axis.text = element_text(size = 10))
return(grafico)
}
# Aplicar a variables numéricas con numeración
cat("ANÁLISIS BIVARIADO - VARIABLES NUMÉRICAS\n")
## ANÁLISIS BIVARIADO - VARIABLES NUMÉRICAS
cat(rep("=", 50), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# Especificar el número de figura para cada análisis (comenzando desde 11)
resultado_num1 <- analisis_bivariado_num("Ingreso_Mensual", "Ingreso Mensual", 11)
##
## ============================================================
## ANÁLISIS: Ingreso Mensual vs ROTACIÓN
## ============================================================
##
## ESTADÍSTICAS DESCRIPTIVAS:
## --------------------------------------------------
## Estadístico No Rotó Sí Rotó
## n 1233.00 237.00
## Media 6832.74 4787.09
## Desviación Estándar 4818.21 3640.21
## Mínimo 1051.00 1009.00
## Mediana 5204.00 3202.00
## Máximo 19999.00 19859.00
##
## PRUEBA T-STUDENT (Comparación de Medias):
## --------------------------------------------------
## Diferencia de medias: 2045.65
## t-value: 7.483
## p-value: 0
## Intervalo de confianza (95%): [1508.24, 2583.05]
## Resultado: DIFERENCIA SIGNIFICATIVA (p < 0.05)
## → Existe diferencia significativa en Ingreso Mensual entre grupos
resultado_num2 <- analisis_bivariado_num("Años_ultima_promoción", "Años desde Última Promoción", 12)
##
## ============================================================
## ANÁLISIS: Años desde Última Promoción vs ROTACIÓN
## ============================================================
##
## ESTADÍSTICAS DESCRIPTIVAS:
## --------------------------------------------------
## Estadístico No Rotó Sí Rotó
## n 1233.00 237.00
## Media 2.23 1.95
## Desviación Estándar 3.23 3.15
## Mínimo 0.00 0.00
## Mediana 1.00 1.00
## Máximo 15.00 15.00
##
## PRUEBA T-STUDENT (Comparación de Medias):
## --------------------------------------------------
## Diferencia de medias: 0.29
## t-value: 1.288
## p-value: 0.1987
## Intervalo de confianza (95%): [-0.15, 0.73]
## Resultado: NO HAY DIFERENCIA SIGNIFICATIVA (p ≥ 0.05)
## → No hay evidencia de diferencia en Años desde Última Promoción entre grupos
resultado_num3 <- analisis_bivariado_num("Edad", "Edad", 13)
##
## ============================================================
## ANÁLISIS: Edad vs ROTACIÓN
## ============================================================
##
## ESTADÍSTICAS DESCRIPTIVAS:
## --------------------------------------------------
## Estadístico No Rotó Sí Rotó
## n 1233.00 237.00
## Media 37.56 33.61
## Desviación Estándar 8.89 9.69
## Mínimo 18.00 18.00
## Mediana 36.00 32.00
## Máximo 60.00 58.00
##
## PRUEBA T-STUDENT (Comparación de Medias):
## --------------------------------------------------
## Diferencia de medias: 3.95
## t-value: 5.829
## p-value: 0
## Intervalo de confianza (95%): [2.62, 5.29]
## Resultado: DIFERENCIA SIGNIFICATIVA (p < 0.05)
## → Existe diferencia significativa en Edad entre grupos
# Mostrar gráficos
print(resultado_num1)
print(resultado_num2)
print(resultado_num3)
cat("=== 4. MODELO DE REGRESIÓN LOGÍSTICA ===\n\n")
## === 4. MODELO DE REGRESIÓN LOGÍSTICA ===
# Preparar datos para el modelo
rotacion_modelo <- rotacion_clean %>%
mutate(
Rotacion_bin = as.factor(Rotacion_bin),
Horas_Extra = as.factor(Horas_Extra),
Estado_Civil = as.factor(Estado_Civil),
Departamento = as.factor(Departamento)
)
# Ajustar modelo
modelo <- glm(Rotacion_bin ~ Ingreso_Mensual + Años_ultima_promoción + Edad +
Horas_Extra + Estado_Civil + Departamento,
data = rotacion_modelo, family = "binomial")
cat("RESUMEN DEL MODELO:\n")
## RESUMEN DEL MODELO:
print(summary(modelo))
##
## Call:
## glm(formula = Rotacion_bin ~ Ingreso_Mensual + Años_ultima_promoción +
## Edad + Horas_Extra + Estado_Civil + Departamento, family = "binomial",
## data = rotacion_modelo)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.104e+00 3.521e-01 -3.134 0.001725 **
## Ingreso_Mensual -1.277e-04 2.754e-05 -4.636 3.55e-06 ***
## Años_ultima_promoción 5.140e-02 2.796e-02 1.838 0.066040 .
## Edad -2.859e-02 1.014e-02 -2.818 0.004826 **
## Horas_ExtraSi 1.495e+00 1.572e-01 9.512 < 2e-16 ***
## Estado_CivilDivorciado -2.945e-01 2.294e-01 -1.284 0.199222
## Estado_CivilSoltero 8.493e-01 1.698e-01 5.001 5.72e-07 ***
## DepartamentoRH 7.061e-01 3.567e-01 1.980 0.047744 *
## DepartamentoVentas 6.166e-01 1.664e-01 3.705 0.000211 ***
## ---
## 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: 1098.6 on 1461 degrees of freedom
## AIC: 1116.6
##
## Number of Fisher Scoring iterations: 5
# Tabla de resultados formateada
resultados_tidy <- tidy(modelo, conf.int = TRUE, exponentiate = TRUE)
# Crear nombres legibles para las variables
resultados_tidy$Variable <- resultados_tidy$term
resultados_tidy$Variable <- gsub("Horas_Extra", "Horas Extra: ", resultados_tidy$Variable)
resultados_tidy$Variable <- gsub("Estado_Civil", "Estado Civil: ", resultados_tidy$Variable)
resultados_tidy$Variable <- gsub("Departamento", "Departamento: ", resultados_tidy$Variable)
resultados_tidy$Variable <- gsub("Ingreso_Mensual", "Ingreso Mensual", resultados_tidy$Variable)
resultados_tidy$Variable <- gsub("Años_ultima_promoción", "Años última promoción", resultados_tidy$Variable)
resultados_tidy$Variable <- gsub("Edad", "Edad", resultados_tidy$Variable)
resultados_tidy$Variable <- gsub("^\\(Intercept\\)", "Intercepto", resultados_tidy$Variable)
# Crear tabla formateada
tabla_final <- resultados_tidy %>%
mutate(
across(c(estimate, std.error, statistic, p.value, conf.low, conf.high),
~round(., 3)),
Significancia = case_when(
p.value < 0.001 ~ "***",
p.value < 0.01 ~ "**",
p.value < 0.05 ~ "*",
p.value < 0.1 ~ ".",
TRUE ~ ""
)
) %>%
select(Variable, `Odds Ratio` = estimate, `Error Estándar` = std.error,
`Valor z` = statistic, `Valor p` = p.value, Significancia,
`IC 95% Bajo` = conf.low, `IC 95% Alto` = conf.high)
kbl(tabla_final, caption = "Resultados del Modelo de Regresión Logística") %>%
kable_styling(bootstrap_options = c("striped", "hover"))
| Variable | Odds Ratio | Error Estándar | Valor z | Valor p | Significancia | IC 95% Bajo | IC 95% Alto |
|---|---|---|---|---|---|---|---|
| Intercepto | 0.332 | 0.352 | -3.134 | 0.002 | ** | 0.166 | 0.662 |
| Ingreso Mensual | 1.000 | 0.000 | -4.636 | 0.000 | *** | 1.000 | 1.000 |
| Años última promoción | 1.053 | 0.028 | 1.838 | 0.066 | . | 0.995 | 1.111 |
| Edad | 0.972 | 0.010 | -2.818 | 0.005 | ** | 0.952 | 0.991 |
| Horas Extra: Si | 4.458 | 0.157 | 9.512 | 0.000 | *** | 3.282 | 6.080 |
| Estado Civil: Divorciado | 0.745 | 0.229 | -1.284 | 0.199 | 0.470 | 1.158 | |
| Estado Civil: Soltero | 2.338 | 0.170 | 5.001 | 0.000 | *** | 1.679 | 3.269 |
| Departamento: RH | 2.026 | 0.357 | 1.980 | 0.048 |
|
0.970 | 3.967 |
| Departamento: Ventas | 1.853 | 0.166 | 3.705 | 0.000 | *** | 1.336 | 2.567 |
# Interpretación de resultados
cat("INTERPRETACIÓN DE RESULTADOS:\n\n")
## INTERPRETACIÓN DE RESULTADOS:
vars_significativas <- resultados_tidy %>%
filter(p.value < 0.05, !grepl("Intercepto", term))
if(nrow(vars_significativas) > 0) {
cat("VARIABLES SIGNIFICATIVAS (p < 0.05):\n")
for(i in 1:nrow(vars_significativas)) {
or <- vars_significativas$estimate[i]
var <- vars_significativas$Variable[i]
if(or > 1) {
cat("•", var, ": AUMENTA probabilidad de rotación (OR =", round(or, 2), ")\n")
} else {
cat("•", var, ": DISMINUYE probabilidad de rotación (OR =", round(or, 2), ")\n")
}
}
} else {
cat("No hay variables significativas al nivel 0.05\n")
}
## VARIABLES SIGNIFICATIVAS (p < 0.05):
## • Intercepto : DISMINUYE probabilidad de rotación (OR = 0.33 )
## • Ingreso Mensual : DISMINUYE probabilidad de rotación (OR = 1 )
## • Edad : DISMINUYE probabilidad de rotación (OR = 0.97 )
## • Horas Extra: Si : AUMENTA probabilidad de rotación (OR = 4.46 )
## • Estado Civil: Soltero : AUMENTA probabilidad de rotación (OR = 2.34 )
## • Departamento: RH : AUMENTA probabilidad de rotación (OR = 2.03 )
## • Departamento: Ventas : AUMENTA probabilidad de rotación (OR = 1.85 )
cat("=== 5. EVALUACIÓN DEL MODELO ===\n\n")
## === 5. EVALUACIÓN DEL MODELO ===
# Predicciones y curva ROC
predicciones <- predict(modelo, type = "response")
roc_curve <- roc(rotacion_modelo$Rotacion_bin, predicciones)
auc_valor <- auc(roc_curve)
# Encabezado organizado
cat(rep("=", 60), "\n", sep = "")
## ============================================================
cat("EVALUACIÓN DEL MODELO DE PREDICCIÓN\n")
## EVALUACIÓN DEL MODELO DE PREDICCIÓN
cat(rep("=", 60), "\n\n", sep = "")
## ============================================================
cat("MÉTRICAS DE EVALUACIÓN:\n")
## MÉTRICAS DE EVALUACIÓN:
cat(rep("-", 40), "\n", sep = "")
## ----------------------------------------
# Crear tabla de métricas ROC
metricas_roc <- data.frame(
Métrica = c("AUC (Área bajo curva ROC)", "Interpretación"),
Valor = c(
round(auc_valor, 3),
if(auc_valor >= 0.9) "Excelente poder predictivo"
else if(auc_valor >= 0.8) "Buen poder predictivo"
else if(auc_valor >= 0.7) "Poder predictivo aceptable"
else "Poder predictivo limitado"
)
)
print(metricas_roc, row.names = FALSE)
## Métrica Valor
## AUC (Área bajo curva ROC) 0.763
## Interpretación Poder predictivo aceptable
# Gráfico ROC con numeración
cat("\n\nFigura 14. Curva ROC del Modelo Predictivo\n")
##
##
## Figura 14. Curva ROC del Modelo Predictivo
par(mar = c(5, 4, 4, 2) + 0.1)
plot(roc_curve,
main = "Figura 14. Curva ROC del Modelo Predictivo",
col = "blue", lwd = 2.5,
xlab = "1 - Especificidad (Tasa de Falsos Positivos)",
ylab = "Sensibilidad (Tasa de Verdaderos Positivos)")
abline(a = 0, b = 1, lty = 2, col = "red", lwd = 1.5)
legend("bottomright",
legend = c(paste("AUC =", round(auc_valor, 3)), "Línea de referencia (AUC = 0.5)"),
col = c("blue", "red"),
lwd = c(2.5, 1.5),
lty = c(1, 2),
cex = 0.8)
grid()
# Matriz de confusión
punto_corte <- 0.5
predicciones_class <- ifelse(predicciones > punto_corte, 1, 0)
conf_matrix <- confusionMatrix(as.factor(predicciones_class), rotacion_modelo$Rotacion_bin)
cat("\n", rep("=", 60), "\n", sep = "")
##
## ============================================================
cat("MATRIZ DE CONFUSIÓN Y MÉTRICAS\n")
## MATRIZ DE CONFUSIÓN Y MÉTRICAS
cat(rep("=", 60), "\n\n", sep = "")
## ============================================================
# Matriz de confusión formateada
cat("MATRIZ DE CONFUSIÓN (Punto de corte = 0.5):\n")
## MATRIZ DE CONFUSIÓN (Punto de corte = 0.5):
cat(rep("-", 50), "\n", sep = "")
## --------------------------------------------------
matriz_bonita <- as.matrix(conf_matrix$table)
colnames(matriz_bonita) <- c("Real: No Rotó", "Real: Sí Rotó")
rownames(matriz_bonita) <- c("Pred: No Rotó", "Pred: Sí Rotó")
# Imprimir matriz con formato
matriz_impresa <- as.data.frame(matriz_bonita)
matriz_impresa$Total <- rowSums(matriz_bonita)
print(matriz_impresa)
## Prediction Reference Freq Total
## 1 Pred: No Rotó Real: No Rotó 1217 1413
## 2 Pred: Sí Rotó Real: No Rotó 16 57
## 3 Pred: No Rotó Real: Sí Rotó 196 1413
## 4 Pred: Sí Rotó Real: Sí Rotó 41 57
# Totales por columna
totales_col <- c("Total", colSums(matriz_bonita))
cat("\n")
print(totales_col, row.names = FALSE)
## Real: No Rotó Real: Sí Rotó
## "Total" "1233" "237"
# Métricas de clasificación en tabla
cat("\nMÉTRICAS DE CLASIFICACIÓN:\n")
##
## MÉTRICAS DE CLASIFICACIÓN:
cat(rep("-", 40), "\n", sep = "")
## ----------------------------------------
metricas_clasificacion <- data.frame(
Métrica = c("Exactitud (Accuracy)",
"Sensibilidad (Recall)",
"Especificidad",
"Precisión",
"F1-Score"),
Valor = c(
round(conf_matrix$overall["Accuracy"], 3),
round(conf_matrix$byClass["Sensitivity"], 3),
round(conf_matrix$byClass["Specificity"], 3),
round(conf_matrix$byClass["Precision"], 3),
round(conf_matrix$byClass["F1"], 3)
),
Descripción = c(
"Porcentaje total de aciertos",
"Capacidad de detectar NO rotación",
"Capacidad de detectar SI rotación",
"Aciertos al predecir rotación",
"Balance entre Precisión y Sensibilidad"
)
)
print(metricas_clasificacion, row.names = FALSE)
## Métrica Valor Descripción
## Exactitud (Accuracy) 0.856 Porcentaje total de aciertos
## Sensibilidad (Recall) 0.987 Capacidad de detectar NO rotación
## Especificidad 0.173 Capacidad de detectar SI rotación
## Precisión 0.861 Aciertos al predecir rotación
## F1-Score 0.920 Balance entre Precisión y Sensibilidad
# Interpretación de métricas clave
cat("\nINTERPRETACIÓN DE RESULTADOS:\n")
##
## INTERPRETACIÓN DE RESULTADOS:
cat(rep("-", 40), "\n", sep = "")
## ----------------------------------------
cat("• EXACTITUD:", round(conf_matrix$overall["Accuracy"] * 100, 1), "% -",
if(conf_matrix$overall["Accuracy"] > 0.8) "MUY BUENA"
else if(conf_matrix$overall["Accuracy"] > 0.7) "ACEPTABLE"
else "BAJA", "\n")
## • EXACTITUD: 85.6 % - MUY BUENA
cat("• SENSIBILIDAD:", round(conf_matrix$byClass["Sensitivity"] * 100, 1), "% -",
"Excelente para detectar NO rotación\n")
## • SENSIBILIDAD: 98.7 % - Excelente para detectar NO rotación
cat("• ESPECIFICIDAD:", round(conf_matrix$byClass["Specificity"] * 100, 1), "% -",
"BAJA para detectar SI rotación\n")
## • ESPECIFICIDAD: 17.3 % - BAJA para detectar SI rotación
cat("\nRECOMENDACIÓN: El modelo es conservador - predice bien la NO rotación")
##
## RECOMENDACIÓN: El modelo es conservador - predice bien la NO rotación
cat("\npero necesita mejorar la detección de empleados que SÍ rotarán.\n")
##
## pero necesita mejorar la detección de empleados que SÍ rotarán.
cat("=== 6. PREDICCIONES Y ESTRATEGIAS ===\n\n")
## === 6. PREDICCIONES Y ESTRATEGIAS ===
# Encabezado organizado
cat(rep("=", 60), "\n", sep = "")
## ============================================================
cat("PREDICCIONES PARA PERFILES DE EMPLEADOS\n")
## PREDICCIONES PARA PERFILES DE EMPLEADOS
cat(rep("=", 60), "\n\n", sep = "")
## ============================================================
# Obtener valores válidos del dataset
deptos_validos <- unique(rotacion$Departamento)
estados_validos <- unique(rotacion$Estado_Civil)
horas_validas <- unique(rotacion$Horas_Extra)
cat("VALORES VÁLIDOS IDENTIFICADOS:\n")
## VALORES VÁLIDOS IDENTIFICADOS:
cat(rep("-", 35), "\n", sep = "")
## -----------------------------------
cat("• Departamentos:", paste(deptos_validos, collapse = ", "), "\n")
## • Departamentos: Ventas, IyD, RH
cat("• Estados civiles:", paste(estados_validos, collapse = ", "), "\n")
## • Estados civiles: Soltero, Casado, Divorciado
cat("• Horas extra:", paste(horas_validas, collapse = ", "), "\n\n")
## • Horas extra: Si, No
# Crear empleados hipotéticos con valores VÁLIDOS
empleados_hipoteticos <- data.frame(
Perfil = c("Alto Riesgo", "Riesgo Moderado", "Bajo Riesgo"),
Ingreso_Mensual = c(2500, 4500, 6500),
Años_ultima_promoción = c(5, 2, 1),
Edad = c(28, 35, 42),
Horas_Extra = factor(c("Si", "Si", "No"), levels = horas_validas),
Estado_Civil = factor(c("Soltero", "Casado", "Casado"), levels = estados_validos),
Departamento = factor(c(deptos_validos[1], deptos_validos[2], deptos_validos[1]),
levels = deptos_validos)
)
# Realizar predicciones
predicciones_hipo <- predict(modelo, newdata = empleados_hipoteticos, type = "response")
empleados_hipoteticos$Probabilidad_Rotacion <- round(predicciones_hipo, 3)
empleados_hipoteticos$Porcentaje_Rotacion <- round(predicciones_hipo * 100, 1)
# Tabla de predicciones formateada
cat("TABLA DE PREDICCIONES:\n")
## TABLA DE PREDICCIONES:
cat(rep("-", 80), "\n", sep = "")
## --------------------------------------------------------------------------------
tabla_predicciones <- empleados_hipoteticos %>%
select(Perfil, Ingreso_Mensual, Edad, Horas_Extra, Estado_Civil,
Departamento, Años_ultima_promoción, Porcentaje_Rotacion)
print(tabla_predicciones, row.names = FALSE)
## Perfil Ingreso_Mensual Edad Horas_Extra Estado_Civil Departamento
## Alto Riesgo 2500 28 Si Soltero Ventas
## Riesgo Moderado 4500 35 Si Casado IyD
## Bajo Riesgo 6500 42 No Casado Ventas
## Años_ultima_promoción Porcentaje_Rotacion
## 5 73.0
## 2 25.3
## 1 7.8
# Gráfico de probabilidades
cat("\n\nFigura 15. Probabilidades de Rotación por Perfil\n")
##
##
## Figura 15. Probabilidades de Rotación por Perfil
grafico_predicciones <- ggplot(empleados_hipoteticos,
aes(x = reorder(Perfil, -Probabilidad_Rotacion),
y = Probabilidad_Rotacion,
fill = Perfil)) +
geom_col(alpha = 0.8) +
geom_text(aes(label = paste0(Porcentaje_Rotacion, "%")),
vjust = -0.5, size = 5, fontface = "bold") +
labs(title = "Figura 15. Probabilidades de Rotación por Perfil",
x = "Perfil de Empleado",
y = "Probabilidad de Rotación") +
scale_y_continuous(labels = scales::percent, limits = c(0, 0.8)) +
scale_fill_manual(values = c("Alto Riesgo" = "tomato",
"Riesgo Moderado" = "gold",
"Bajo Riesgo" = "steelblue")) +
theme_minimal() +
theme(legend.position = "none",
axis.text.x = element_text(angle = 0, hjust = 0.5),
plot.title = element_text(face = "bold", size = 14))
print(grafico_predicciones)
# Estrategia de intervención mejorada
cat("\n", rep("=", 60), "\n", sep = "")
##
## ============================================================
cat("PLAN DE ACCIÓN Y ESTRATEGIAS\n")
## PLAN DE ACCIÓN Y ESTRATEGIAS
cat(rep("=", 60), "\n\n", sep = "")
## ============================================================
punto_corte_intervencion <- 0.6
cat("RECOMENDACIONES POR PERFIL:\n")
## RECOMENDACIONES POR PERFIL:
cat(rep("-", 50), "\n", sep = "")
## --------------------------------------------------
for(i in 1:nrow(empleados_hipoteticos)) {
prob <- empleados_hipoteticos$Probabilidad_Rotacion[i]
perfil <- empleados_hipoteticos$Perfil[i]
if(prob > punto_corte_intervencion) {
decision <- "🔴 INTERVENCIÓN INMEDIATA"
color <- "\\033[31m" # Rojo
acciones <- c(
"• Revisión salarial urgente (+15-20%)",
"• Reducción inmediata de horas extra",
"• Plan de desarrollo personalizado",
"• Mentoría ejecutiva",
"• Revisión cada 2 semanas"
)
} else if(prob > 0.3) {
decision <- "🟡 MONITOREAR ACTIVAMENTE"
color <- "\\033[33m" # Amarillo
acciones <- c(
"• Seguimiento trimestral",
"• Programa de mentoría",
"• Oportunidades de desarrollo",
"• Revisión salarial en 6 meses"
)
} else {
decision <- "🟢 ESTABLE - MANTENER"
color <- "\\033[32m" # Verde
acciones <- c(
"• Revisión anual normal",
"• Mantener condiciones actuales",
"• Incluir en programas de retención básicos"
)
}
cat("\n", perfil, " - ", decision, "\n", sep = "")
cat("Probabilidad de rotación: ", prob*100, "%\n", sep = "")
cat("Acciones recomendadas:\n")
for(accion in acciones) {
cat(" ", accion, "\n")
}
cat("\n")
}
##
## Alto Riesgo - 🔴 INTERVENCIÓN INMEDIATA
## Probabilidad de rotación: 73%
## Acciones recomendadas:
## • Revisión salarial urgente (+15-20%)
## • Reducción inmediata de horas extra
## • Plan de desarrollo personalizado
## • Mentoría ejecutiva
## • Revisión cada 2 semanas
##
##
## Riesgo Moderado - 🟢 ESTABLE - MANTENER
## Probabilidad de rotación: 25.3%
## Acciones recomendadas:
## • Revisión anual normal
## • Mantener condiciones actuales
## • Incluir en programas de retención básicos
##
##
## Bajo Riesgo - 🟢 ESTABLE - MANTENER
## Probabilidad de rotación: 7.8%
## Acciones recomendadas:
## • Revisión anual normal
## • Mantener condiciones actuales
## • Incluir en programas de retención básicos
cat("• El modelo identifica correctamente patrones de riesgo conocidos\n")
## • El modelo identifica correctamente patrones de riesgo conocidos
cat("• Las horas extra son el factor de riesgo MÁS importante\n")
## • Las horas extra son el factor de riesgo MÁS importante
cat("• La combinación de múltiples factores aumenta el riesgo exponencialmente\n")
## • La combinación de múltiples factores aumenta el riesgo exponencialmente
cat("• Las predicciones permiten acciones PROACTIVAS de retención\n")
## • Las predicciones permiten acciones PROACTIVAS de retención
cat("• Se recomienda aplicar este análisis a empleados reales\n")
## • Se recomienda aplicar este análisis a empleados reales