Este informe desarrolla un flujo reproducible para comprender y predecir la rotación de empleados. El análisis integra comprensión del problema, auditoría de calidad, limpieza controlada, caracterización univariada, contrastes bivariados, estimación de un modelo Logit binomial, evaluación fuera de muestra mediante curva ROC y AUC, análisis de sensibilidad del punto de corte y una predicción aplicada a un perfil hipotético.
La rotación se aborda como un evento binario. El modelo no sustituye el criterio de gestión humana: funciona como una herramienta de priorización para orientar conversaciones, revisar cargas de trabajo y diseñar acciones de retención proporcionales al riesgo estimado.
La continuidad del talento influye en la estabilidad de los equipos, la transferencia de conocimiento y la capacidad de ejecución. Cuando una persona abandona su cargo, la organización puede afrontar costes de selección, incorporación y aprendizaje, además de impactos en la carga de trabajo de quienes permanecen. Sin embargo, la rotación no debe entenderse únicamente como un resultado individual. Es un fenómeno asociado a condiciones laborales, trayectoria, compensación, características del rol y experiencias de bienestar.
El reto analítico consiste en convertir datos históricos de empleados en evidencia útil para la toma de decisiones. La pregunta central es: ¿qué características se asocian con una mayor probabilidad de rotación y con qué capacidad puede anticiparse este evento? Para responderla se emplea regresión logística, apropiada cuando la respuesta toma dos valores: rotación y no rotación.
La regresión logística forma parte de los modelos lineales generalizados y permite relacionar una respuesta binaria con covariables cuantitativas y categóricas mediante la función de enlace Logit [1], [2]. Sus coeficientes se interpretan en la escala del logaritmo de las razones de posibilidades, mientras que su exponencial produce odds ratios, útiles para comunicar cambios relativos en las posibilidades del evento.
En analítica de personas, la calidad del dato y la interpretación responsable son tan importantes como el ajuste estadístico. Una predicción de rotación no demuestra causalidad ni debe utilizarse como decisión automática sobre una persona. Debe servir para identificar patrones, formular hipótesis y priorizar intervenciones de apoyo. La evaluación fuera de muestra, la sensibilidad, la especificidad y el área bajo la curva ROC permiten valorar la discriminación del modelo sin depender de un único umbral [3], [4].
Este estudio adopta un flujo inspirado en CRISP-DM: comprensión, preparación, modelación, evaluación y comunicación [5]. Además, separa los valores originalmente ausentes de los valores detectados como inconsistentes. Esta decisión evita que una regla de imputación oculte problemas que deben permanecer visibles durante la auditoría.
Esquema metodologico del analisis. Fuente: elaboracion propia.
data("rotacion", package = "paqueteMODELOS")
rotacion_original <- as.data.frame(rotacion)
dplyr::glimpse(rotacion_original)
## 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, …
diccionario <- tibble::tibble(
Variable = names(rotacion_original),
Clase_R = vapply(rotacion_original, function(x) paste(class(x), collapse = "/"), character(1)),
Tipo_analitico = dplyr::case_when(
names(rotacion_original) == var_y ~ "Respuesta binaria",
names(rotacion_original) %in% vars_cat ~ "Categórica seleccionada",
names(rotacion_original) %in% vars_num ~ "Cuantitativa seleccionada",
vapply(rotacion_original, is.numeric, logical(1)) ~ "Cuantitativa/ordinal",
TRUE ~ "Categórica"
),
Registros = nrow(rotacion_original),
Unicos = vapply(rotacion_original, dplyr::n_distinct, integer(1), na.rm = TRUE),
Faltantes = vapply(rotacion_original, function(x) sum(is.na(x)), integer(1)),
Ejemplo = vapply(rotacion_original, function(x) {
z <- unique(stats::na.omit(as.character(x)))
if (length(z) == 0) "Sin dato" else paste(head(z, 3), collapse = " | ")
}, character(1))
)
# mostrar_tabla(diccionario, "Tabla 1. Diccionario reproducible de la base rotacion", longtable = TRUE)
# print(diccionario)
knitr::kable(
diccionario,
caption = "Tabla 1. Diccionario reproducible de la base rotación"
)
| Variable | Clase_R | Tipo_analitico | Registros | Unicos | Faltantes | Ejemplo |
|---|---|---|---|---|---|---|
| Rotación | character | Respuesta binaria | 1470 | 2 | 0 | Si | No |
| Edad | numeric | Cuantitativa seleccionada | 1470 | 43 | 0 | 41 | 49 | 37 |
| Viaje de Negocios | character | Categórica | 1470 | 3 | 0 | Raramente | Frecuentemente | No_Viaja |
| Departamento | character | Categórica | 1470 | 3 | 0 | Ventas | IyD | RH |
| Distancia_Casa | numeric | Cuantitativa seleccionada | 1470 | 29 | 0 | 1 | 8 | 2 |
| Educación | numeric | Cuantitativa/ordinal | 1470 | 5 | 0 | 2 | 1 | 4 |
| Campo_Educación | character | Categórica | 1470 | 6 | 0 | Ciencias | Otra | Salud |
| Satisfacción_Ambiental | numeric | Cuantitativa/ordinal | 1470 | 4 | 0 | 2 | 3 | 4 |
| Genero | character | Categórica | 1470 | 2 | 0 | F | M |
| Cargo | character | Categórica seleccionada | 1470 | 9 | 0 | Ejecutivo_Ventas | Investigador_Cientifico | Tecnico_Laboratorio |
| Satisfación_Laboral | numeric | Cuantitativa/ordinal | 1470 | 4 | 0 | 4 | 2 | 3 |
| Estado_Civil | character | Categórica seleccionada | 1470 | 3 | 0 | Soltero | Casado | Divorciado |
| Ingreso_Mensual | numeric | Cuantitativa seleccionada | 1470 | 1349 | 0 | 5993 | 5130 | 2090 |
| Trabajos_Anteriores | numeric | Cuantitativa/ordinal | 1470 | 10 | 0 | 8 | 1 | 6 |
| Horas_Extra | character | Categórica seleccionada | 1470 | 2 | 0 | Si | No |
| Porcentaje_aumento_salarial | numeric | Cuantitativa/ordinal | 1470 | 15 | 0 | 11 | 23 | 15 |
| Rendimiento_Laboral | numeric | Cuantitativa/ordinal | 1470 | 2 | 0 | 3 | 4 |
| Años_Experiencia | numeric | Cuantitativa/ordinal | 1470 | 40 | 0 | 8 | 10 | 7 |
| Capacitaciones | numeric | Cuantitativa/ordinal | 1470 | 7 | 0 | 0 | 3 | 2 |
| Equilibrio_Trabajo_Vida | numeric | Cuantitativa/ordinal | 1470 | 4 | 0 | 1 | 3 | 2 |
| Antigüedad | numeric | Cuantitativa/ordinal | 1470 | 37 | 0 | 6 | 10 | 0 |
| Antigüedad_Cargo | numeric | Cuantitativa/ordinal | 1470 | 19 | 0 | 4 | 7 | 0 |
| Años_ultima_promoción | numeric | Cuantitativa/ordinal | 1470 | 16 | 0 | 0 | 1 | 3 |
| Años_acargo_con_mismo_jefe | numeric | Cuantitativa/ordinal | 1470 | 18 | 0 | 5 | 7 | 0 |
seleccion <- tibble::tibble(
Variable = c(vars_cat, vars_num),
Tipo = c(rep("Categórica", 3), rep("Cuantitativa", 3)),
Justificacion = c(
"Puede representar diferencias en exigencias, condiciones o exposición asociadas al cargo.",
"Puede capturar grupos con experiencias laborales y expectativas de permanencia distintas.",
"Puede reflejar condiciones organizativas vinculadas con intención de permanencia.",
"Puede aproximar trayectoria, etapa vital o experiencia acumulada.",
"Puede representar compensación, reconocimiento o posición en la organización.",
"Puede reflejar permanencia, experiencia o consolidación en el rol actual."
),
Hipotesis = c(
"La probabilidad de rotación no es igual entre sus categorías.",
"La probabilidad de rotación no es igual entre sus categorías.",
"La probabilidad de rotación no es igual entre sus categorías.",
"Un cambio unitario se asocia con un cambio en las posibilidades de rotación.",
"Un cambio unitario se asocia con un cambio en las posibilidades de rotación.",
"Un cambio unitario se asocia con un cambio en las posibilidades de rotación."
)
)
# mostrar_tabla(seleccion, "Tabla 2. Variables seleccionadas, justificación e hipótesis", longtable = TRUE)
knitr::kable(
seleccion,
caption = "Tabla 2. Variables seleccionadas, justificación e hipótesis"
)
| Variable | Tipo | Justificacion | Hipotesis |
|---|---|---|---|
| Horas_Extra | Categórica | Puede representar diferencias en exigencias, condiciones o exposición asociadas al cargo. | La probabilidad de rotación no es igual entre sus categorías. |
| Estado_Civil | Categórica | Puede capturar grupos con experiencias laborales y expectativas de permanencia distintas. | La probabilidad de rotación no es igual entre sus categorías. |
| Cargo | Categórica | Puede reflejar condiciones organizativas vinculadas con intención de permanencia. | La probabilidad de rotación no es igual entre sus categorías. |
| Ingreso_Mensual | Cuantitativa | Puede aproximar trayectoria, etapa vital o experiencia acumulada. | Un cambio unitario se asocia con un cambio en las posibilidades de rotación. |
| Edad | Cuantitativa | Puede representar compensación, reconocimiento o posición en la organización. | Un cambio unitario se asocia con un cambio en las posibilidades de rotación. |
| Distancia_Casa | Cuantitativa | Puede reflejar permanencia, experiencia o consolidación en el rol actual. | Un cambio unitario se asocia con un cambio en las posibilidades de rotación. |
Nota metodológica. La selección se realiza mediante preferencias semánticas y disponibilidad real en la versión cargada de la base. Por ello, el documento sigue siendo reproducible aunque existan pequeñas diferencias en los nombres de columnas.
Se diferencian cuatro situaciones: ausencia real (NA),
texto vacío, no finitud numérica y valor cuantitativo atípico. Los
atípicos se identifican con la regla exploratoria de 1,5 rangos
intercuartílicos. Esta regla genera alertas, no elimina automáticamente
observaciones. Para variables numéricas que conceptualmente no deben ser
negativas, los valores negativos se marcan como inconsistentes. Las
reglas deben revisarse con conocimiento del negocio antes de una
aplicación productiva.
datos_audit <- rotacion_original
resumen_calidad <- purrr::map_dfr(names(datos_audit), function(v) {
x <- datos_audit[[v]]
vacios <- if (is.character(x) || is.factor(x)) sum(trimws(as.character(x)) == "", na.rm = TRUE) else 0L
no_finitos <- if (is.numeric(x)) sum(!is.finite(x) & !is.na(x)) else 0L
negativos <- if (is.numeric(x) && any(grepl("edad|age|ingreso|income|salario|salary|anios|years|distancia|distance|satisf", normalizar(v)))) {
sum(x < 0, na.rm = TRUE)
} else 0L
atipicos <- 0L
if (is.numeric(x) && sum(!is.na(x)) >= 4) {
q <- stats::quantile(x, c(.25, .75), na.rm = TRUE, names = FALSE)
riq <- q[2] - q[1]
if (is.finite(riq) && riq > 0) atipicos <- sum(x < q[1] - 1.5 * riq | x > q[2] + 1.5 * riq, na.rm = TRUE)
}
tibble::tibble(
Variable = v,
Faltantes = sum(is.na(x)),
Vacios = vacios,
No_finitos = no_finitos,
Inconsistentes_negativos = negativos,
Atipicos_IQR = atipicos
)
}) |>
mutate(
Total_alertas = Faltantes + Vacios + No_finitos + Inconsistentes_negativos + Atipicos_IQR,
Criticidad_pct = 100 * Total_alertas / nrow(datos_audit),
Nivel = case_when(
Criticidad_pct == 0 ~ "Aceptable",
Criticidad_pct <= 5 ~ "Bajo - revisar",
Criticidad_pct <= 15 ~ "Medio - intervenir",
TRUE ~ "Alto - rechazar/reformular"
)
)
# mostrar_tabla(resumen_calidad, "Tabla 3. Auditoría de calidad y criticidad por variable", longtable = TRUE)
knitr::kable(
resumen_calidad,
caption = "Tabla 3. Auditoría de calidad y criticidad por variable"
)
| Variable | Faltantes | Vacios | No_finitos | Inconsistentes_negativos | Atipicos_IQR | Total_alertas | Criticidad_pct | Nivel |
|---|---|---|---|---|---|---|---|---|
| Rotación | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Edad | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Viaje de Negocios | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Departamento | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Distancia_Casa | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Educación | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Campo_Educación | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Satisfacción_Ambiental | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Genero | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Cargo | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Satisfación_Laboral | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Estado_Civil | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Ingreso_Mensual | 0 | 0 | 0 | 0 | 114 | 114 | 7.755102 | Medio - intervenir |
| Trabajos_Anteriores | 0 | 0 | 0 | 0 | 52 | 52 | 3.537415 | Bajo - revisar |
| Horas_Extra | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Porcentaje_aumento_salarial | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Rendimiento_Laboral | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Años_Experiencia | 0 | 0 | 0 | 0 | 63 | 63 | 4.285714 | Bajo - revisar |
| Capacitaciones | 0 | 0 | 0 | 0 | 238 | 238 | 16.190476 | Alto - rechazar/reformular |
| Equilibrio_Trabajo_Vida | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Antigüedad | 0 | 0 | 0 | 0 | 104 | 104 | 7.074830 | Medio - intervenir |
| Antigüedad_Cargo | 0 | 0 | 0 | 0 | 21 | 21 | 1.428571 | Bajo - revisar |
| Años_ultima_promoción | 0 | 0 | 0 | 0 | 107 | 107 | 7.278912 | Medio - intervenir |
| Años_acargo_con_mismo_jefe | 0 | 0 | 0 | 0 | 14 | 14 | 0.952381 | Bajo - revisar |
Alertas de calidad por variable. Fuente: elaboración propia.
Diagnóstico. Las barras permiten distinguir ausencia, vacíos, no finitud, valores negativos incompatibles con variables no negativas y atípicos estadísticos. Una barra alta no implica necesariamente mala calidad: en particular, un valor atípico puede ser válido y relevante. La decisión de aceptación o rechazo debe combinar criticidad, reglas de negocio y trazabilidad.
datos_limpios <- rotacion_original
# 1. Estandarización de vacíos y no finitos.
for (v in names(datos_limpios)) {
if (is.character(datos_limpios[[v]]) || is.factor(datos_limpios[[v]])) {
z <- trimws(as.character(datos_limpios[[v]]))
z[z == ""] <- NA_character_
datos_limpios[[v]] <- z
}
if (is.numeric(datos_limpios[[v]])) {
datos_limpios[[v]][!is.finite(datos_limpios[[v]])] <- NA_real_
}
}
# 2. Registro separado de inconsistencias negativas. No serán imputadas.
marcas_inconsistentes <- matrix(FALSE, nrow(datos_limpios), ncol(datos_limpios), dimnames = list(NULL, names(datos_limpios)))
for (v in names(datos_limpios)) {
if (is.numeric(datos_limpios[[v]]) && any(grepl("edad|age|ingreso|income|salario|salary|anios|years|distancia|distance|satisf", normalizar(v)))) {
marcas_inconsistentes[, v] <- !is.na(datos_limpios[[v]]) & datos_limpios[[v]] < 0
datos_limpios[[v]][marcas_inconsistentes[, v]] <- NA_real_
}
}
# 3. Imputación solo de faltantes originales o vacíos, nunca de celdas marcadas como inconsistentes.
for (v in names(datos_limpios)) {
faltaba_originalmente <- is.na(rotacion_original[[v]]) |
((is.character(rotacion_original[[v]]) || is.factor(rotacion_original[[v]])) & trimws(as.character(rotacion_original[[v]])) == "")
imputables <- faltaba_originalmente & !marcas_inconsistentes[, v]
if (is.numeric(datos_limpios[[v]])) {
med <- stats::median(datos_limpios[[v]], na.rm = TRUE)
if (is.finite(med)) datos_limpios[[v]][imputables] <- med
} else {
tab <- sort(table(datos_limpios[[v]]), decreasing = TRUE)
moda <- if (length(tab) > 0) names(tab)[1] else "Sin información"
datos_limpios[[v]][imputables] <- moda
datos_limpios[[v]] <- factor(datos_limpios[[v]])
}
}
comparacion_limpieza <- tibble::tibble(
Etapa = c("Antes de la limpieza", "Después de la imputación controlada"),
Faltantes = c(sum(is.na(rotacion_original)), sum(is.na(datos_limpios))),
Inconsistencias_preservadas = c(NA_integer_, sum(marcas_inconsistentes))
)
# mostrar_tabla(comparacion_limpieza, "Tabla 4. Comparación antes y después de la limpieza")
knitr::kable(
comparacion_limpieza,
caption = "Tabla 4. Comparación antes y después de la limpieza"
)
| Etapa | Faltantes | Inconsistencias_preservadas |
|---|---|---|
| Antes de la limpieza | 0 | NA |
| Después de la imputación controlada | 0 | 0 |
Comparación de faltantes antes y después de la imputación controlada. Fuente: elaboración propia.
Diagnóstico. La reducción de faltantes corresponde
exclusivamente a ausencias originales imputables. Las celdas marcadas
como inconsistentes permanecen como NA, lo que preserva la
evidencia de calidad y evita convertir un valor inválido en un dato
aparentemente correcto.
Distribución de la variable rotación. Fuente: elaboración propia.
Interpretación. La gráfica cuantifica el equilibrio entre empleados que rotan y no rotan. Si la clase positiva es minoritaria, la exactitud por sí sola puede ser engañosa; por eso la evaluación posterior incorpora sensibilidad, especificidad y AUC.
resumen_num <- datos_modelo |>
summarise(across(all_of(vars_num), list(
n = ~sum(!is.na(.x)), media = ~mean(.x, na.rm = TRUE),
mediana = ~median(.x, na.rm = TRUE), de = ~sd(.x, na.rm = TRUE),
minimo = ~min(.x, na.rm = TRUE), maximo = ~max(.x, na.rm = TRUE)
))) |>
pivot_longer(everything(), names_to = c("Variable", ".value"), names_pattern = "(.*)_(n|media|mediana|de|minimo|maximo)$")
# mostrar_tabla(resumen_num, "Tabla 5. Estadísticos descriptivos de las variables cuantitativas")
knitr::kable(
resumen_num,
caption = "Tabla 5. Estadísticos descriptivos de las variables cuantitativas"
)
| Variable | n | media | mediana | de | minimo | maximo |
|---|---|---|---|---|---|---|
| Ingreso_Mensual | 1470 | 6502.931293 | 4919 | 4707.956783 | 1009 | 19999 |
| Edad | 1470 | 36.924490 | 36 | 9.135938 | 18 | 60 |
| Distancia_Casa | 1470 | 9.192517 | 7 | 8.106864 | 1 | 29 |
Distribuciones cuantitativas seleccionadas. Fuente: elaboración propia.
Distribuciones categóricas seleccionadas. Fuente: elaboración propia.
Para variables categóricas se utiliza chi-cuadrado de independencia cuando los recuentos esperados son adecuados; si alguna celda esperada es menor que 5, se usa la prueba exacta de Fisher con simulación Monte Carlo. Para variables cuantitativas se estima un modelo Logit simple y se reporta el signo, el odds ratio y la prueba de Wald del coeficiente.
resultados_cat <- purrr::map_dfr(vars_cat, function(v) {
tab <- table(datos_modelo[[v]], datos_modelo$.y, useNA = "no")
chi <- suppressWarnings(chisq.test(tab))
if (any(chi$expected < 5)) {
pr <- fisher.test(tab, simulate.p.value = TRUE, B = 10000)
tibble::tibble(Variable = v, Prueba = "Fisher simulada", Estadistico = NA_real_, gl = NA_real_, p_valor = pr$p.value)
} else {
tibble::tibble(Variable = v, Prueba = "Chi-cuadrado", Estadistico = unname(chi$statistic), gl = unname(chi$parameter), p_valor = chi$p.value)
}
})
resultados_num <- purrr::map_dfr(vars_num, function(v) {
mod <- glm(reformulate(v, response = ".y"), data = datos_modelo, family = binomial(), na.action = na.omit)
co <- broom::tidy(mod, conf.int = TRUE) |>
filter(term != "(Intercept)")
tibble::tibble(
Variable = v, Prueba = "Logit simple - Wald", Coeficiente = co$estimate,
OR = exp(co$estimate), IC95_inferior = exp(co$conf.low), IC95_superior = exp(co$conf.high),
p_valor = co$p.value,
Signo = ifelse(co$estimate > 0, "Positivo", "Negativo")
)
})
# mostrar_tabla(resultados_cat |> mutate(p_valor = fmt_p(p_valor)), "Tabla 6. Pruebas bivariadas para covariables categóricas")
knitr::kable(
resultados_cat |> mutate(p_valor = fmt_p(p_valor)),
caption = "Tabla 6. Pruebas bivariadas para covariables categóricas"
)
| Variable | Prueba | Estadistico | gl | p_valor |
|---|---|---|---|---|
| Horas_Extra | Chi-cuadrado | 87.56429 | 1 | < 0,001 |
| Estado_Civil | Chi-cuadrado | 46.16368 | 2 | < 0,001 |
| Cargo | Chi-cuadrado | 86.19025 | 8 | < 0,001 |
# mostrar_tabla(resultados_num |> mutate(p_valor = fmt_p(p_valor)), "Tabla 7. Modelos Logit bivariados para covariables cuantitativas")
knitr::kable(
resultados_num |> mutate(p_valor = fmt_p(p_valor)),
caption = "Tabla 7. Modelos Logit bivariados para covariables cuantitativas"
)
| Variable | Prueba | Coeficiente | OR | IC95_inferior | IC95_superior | p_valor | Signo |
|---|---|---|---|---|---|---|---|
| Ingreso_Mensual | Logit simple - Wald | -0.0001271 | 0.9998729 | 0.9998286 | 0.9999135 | < 0,001 | Negativo |
| Edad | Logit simple - Wald | -0.0522544 | 0.9490874 | 0.9327653 | 0.9651522 | < 0,001 | Negativo |
| Distancia_Casa | Logit simple - Wald | 0.0247101 | 1.0250179 | 1.0082994 | 1.0417320 | 0,003 | Positivo |
Relación entre covariables cuantitativas y rotación. Fuente: elaboración propia.
Comparación con las hipótesis. Una asociación o coeficiente con valor p inferior a 0,05 aporta evidencia contra la hipótesis nula. El signo positivo indica aumento del logit y de las posibilidades de rotación; el signo negativo indica reducción. La asociación estadística no prueba causalidad.
datos_completos <- datos_modelo |> tidyr::drop_na()
if (nrow(datos_completos) < 50) stop("Hay menos de 50 casos completos; no es prudente continuar con la partición y el modelo.")
particion <- rsample::initial_split(datos_completos, prop = 0.70, strata = .y)
train <- rsample::training(particion)
test <- rsample::testing(particion)
formula_modelo <- reformulate(c(vars_cat, vars_num), response = ".y")
modelo <- glm(formula_modelo, data = train, family = binomial(link = "logit"))
summary(modelo)
##
## Call:
## glm(formula = formula_modelo, family = binomial(link = "logit"),
## data = train)
##
## Coefficients:
## Estimate Std. Error z value
## (Intercept) -3.57044572 1.38446112 -2.579
## Horas_ExtraSi 1.71778880 0.20128776 8.534
## Estado_CivilDivorciado 0.02187373 0.28321837 0.077
## Estado_CivilSoltero 1.21834604 0.21780026 5.594
## CargoDirector_Manofactura 1.01221320 1.21518005 0.833
## CargoEjecutivo_Ventas 2.07059873 1.14741317 1.805
## CargoGerente 0.72982192 1.26322261 0.578
## CargoInvestigador_Cientifico 1.71116726 1.24725126 1.372
## CargoRecursos_Humanos 2.65130735 1.28124005 2.069
## CargoRepresentante_Salud 1.08998112 1.20333119 0.906
## CargoRepresentante_Ventas 2.99629841 1.28490567 2.332
## CargoTecnico_Laboratorio 2.43069840 1.24592282 1.951
## Ingreso_Mensual -0.00002117 0.00005961 -0.355
## Edad -0.03941735 0.01339928 -2.942
## Distancia_Casa 0.02952907 0.01178005 2.507
## Pr(>|z|)
## (Intercept) 0.00991 **
## Horas_ExtraSi < 0.0000000000000002 ***
## Estado_CivilDivorciado 0.93844
## Estado_CivilSoltero 0.0000000222 ***
## CargoDirector_Manofactura 0.40486
## CargoEjecutivo_Ventas 0.07114 .
## CargoGerente 0.56344
## CargoInvestigador_Cientifico 0.17008
## CargoRecursos_Humanos 0.03852 *
## CargoRepresentante_Salud 0.36504
## CargoRepresentante_Ventas 0.01970 *
## CargoTecnico_Laboratorio 0.05107 .
## Ingreso_Mensual 0.72252
## Edad 0.00326 **
## Distancia_Casa 0.01219 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 905.68 on 1027 degrees of freedom
## Residual deviance: 703.19 on 1013 degrees of freedom
## AIC: 733.19
##
## Number of Fisher Scoring iterations: 6
tabla_modelo <- broom::tidy(modelo, conf.int = TRUE) |>
mutate(
Odds_ratio = exp(estimate),
OR_IC95_inf = exp(conf.low),
OR_IC95_sup = exp(conf.high),
Significativo_5pct = ifelse(p.value < .05, "Sí", "No"),
p_valor = fmt_p(p.value)
) |>
select(Termino = term, Coeficiente = estimate, Error_estandar = std.error,
Estadistico_z = statistic, p_valor, Odds_ratio, OR_IC95_inf, OR_IC95_sup, Significativo_5pct)
# mostrar_tabla(tabla_modelo, "Tabla 8. Coeficientes del modelo Logit multivariable", longtable = TRUE)
knitr::kable(
tabla_modelo,
caption = "Tabla 8. Coeficientes del modelo Logit multivariable"
)
| Termino | Coeficiente | Error_estandar | Estadistico_z | p_valor | Odds_ratio | OR_IC95_inf | OR_IC95_sup | Significativo_5pct |
|---|---|---|---|---|---|---|---|---|
| (Intercept) | -3.5704457 | 1.3844611 | -2.5789426 | 0,010 | 0.0281433 | 0.0010451 | 0.3362288 | Sí |
| Horas_ExtraSi | 1.7177888 | 0.2012878 | 8.5339953 | < 0,001 | 5.5721936 | 3.7732329 | 8.3161939 | Sí |
| Estado_CivilDivorciado | 0.0218737 | 0.2832184 | 0.0772327 | 0,938 | 1.0221147 | 0.5790230 | 1.7644637 | No |
| Estado_CivilSoltero | 1.2183460 | 0.2178003 | 5.5938686 | < 0,001 | 3.3815901 | 2.2172322 | 5.2148458 | Sí |
| CargoDirector_Manofactura | 1.0122132 | 1.2151800 | 0.8329738 | 0,405 | 2.7516843 | 0.3326524 | 60.0603734 | No |
| CargoEjecutivo_Ventas | 2.0705987 | 1.1474132 | 1.8045799 | 0,071 | 7.9295694 | 1.1685551 | 161.3820308 | No |
| CargoGerente | 0.7298219 | 1.2632226 | 0.5777461 | 0,563 | 2.0747111 | 0.1849193 | 46.7093939 | No |
| CargoInvestigador_Cientifico | 1.7111673 | 1.2472513 | 1.3719507 | 0,170 | 5.5354190 | 0.6353082 | 125.9844360 | No |
| CargoRecursos_Humanos | 2.6513073 | 1.2812400 | 2.0693291 | 0,039 | 14.1725550 | 1.4905166 | 335.3440184 | Sí |
| CargoRepresentante_Salud | 1.0899811 | 1.2033312 | 0.9058031 | 0,365 | 2.9742179 | 0.3699380 | 64.0465721 | No |
| CargoRepresentante_Ventas | 2.9962984 | 1.2849057 | 2.3319209 | 0,020 | 20.0113259 | 2.1017319 | 476.2533646 | Sí |
| CargoTecnico_Laboratorio | 2.4306984 | 1.2459228 | 1.9509221 | 0,051 | 11.3668179 | 1.3118339 | 258.4446326 | No |
| Ingreso_Mensual | -0.0000212 | 0.0000596 | -0.3550946 | 0,723 | 0.9999788 | 0.9998600 | 1.0000943 | No |
| Edad | -0.0394174 | 0.0133993 | -2.9417527 | 0,003 | 0.9613494 | 0.9358955 | 0.9864410 | Sí |
| Distancia_Casa | 0.0295291 | 0.0117801 | 2.5067017 | 0,012 | 1.0299694 | 1.0062898 | 1.0539391 | Sí |
El coeficiente expresa el cambio esperado en el logit de rotación manteniendo constantes las demás covariables. Para una variable cuantitativa, un odds ratio mayor que 1 indica que cada unidad adicional multiplica las posibilidades de rotación por dicho valor; un resultado menor que 1 las reduce. Para una categoría, el odds ratio compara esa categoría con el nivel de referencia mostrado implícitamente por la codificación de R.
prueba_global <- tibble::tibble(
Estadistico_LR = modelo$null.deviance - modelo$deviance,
Grados_libertad = modelo$df.null - modelo$df.residual,
p_valor = pchisq(modelo$null.deviance - modelo$deviance,
df = modelo$df.null - modelo$df.residual, lower.tail = FALSE),
AIC = AIC(modelo),
BIC = BIC(modelo)
) |>
mutate(p_valor = fmt_p(p_valor))
# mostrar_tabla(prueba_global, "Tabla 9. Significancia global y criterios de información")
knitr::kable(
prueba_global,
caption = "Tabla 9. Significancia global y criterios de información"
)
| Estadistico_LR | Grados_libertad | p_valor | AIC | BIC |
|---|---|---|---|---|
| 202.4918 | 14 | < 0,001 | 733.1921 | 807.2227 |
vif_obj <- car::vif(modelo)
if (is.matrix(vif_obj)) {
tabla_vif <- as.data.frame(vif_obj) |>
tibble::rownames_to_column("Variable") |>
mutate(GVIF_ajustado = GVIF^(1/(2*Df)))
} else {
tabla_vif <- tibble::tibble(Variable = names(vif_obj), VIF = as.numeric(vif_obj))
}
mostrar_tabla(tabla_vif, "Tabla 10. Diagnóstico de multicolinealidad mediante VIF/GVIF")
knitr::kable(
tabla_vif,
caption = "Tabla 10. Diagnóstico de multicolinealidad mediante VIF/GVIF"
)
| Variable | GVIF | Df | GVIF^(1/(2*Df)) | GVIF_ajustado |
|---|---|---|---|---|
| Horas_Extra | 1.089537 | 1 | 1.043809 | 1.043809 |
| Estado_Civil | 1.063697 | 2 | 1.015557 | 1.015557 |
| Cargo | 3.526483 | 8 | 1.081954 | 1.081954 |
| Ingreso_Mensual | 3.616069 | 1 | 1.901596 | 1.901596 |
| Edad | 1.261579 | 1 | 1.123200 | 1.123200 |
| Distancia_Casa | 1.024871 | 1 | 1.012359 | 1.012359 |
Como guía práctica, valores ajustados claramente elevados requieren revisar redundancia entre predictores. El VIF no demuestra causalidad ni reemplaza la revisión conceptual de las covariables.
Curva ROC del modelo evaluado en muestra de prueba. Fuente: elaboración propia.
Diagnóstico. El AUC resume la capacidad de ordenar un caso positivo por encima de uno negativo a través de todos los puntos de corte. Un valor cercano a 0,5 indica discriminación limitada; valores mayores reflejan mejor capacidad de separación. Esta métrica debe leerse junto con sensibilidad, especificidad y el contexto de intervención.
metricas_corte <- function(real, prob, corte) {
pred <- as.integer(prob >= corte)
vp <- sum(pred == 1 & real == 1); vn <- sum(pred == 0 & real == 0)
fp <- sum(pred == 1 & real == 0); fn <- sum(pred == 0 & real == 1)
tibble::tibble(
Corte = corte, VP = vp, VN = vn, FP = fp, FN = fn,
Exactitud = (vp + vn) / length(real),
Sensibilidad = ifelse(vp + fn == 0, NA, vp / (vp + fn)),
Especificidad = ifelse(vn + fp == 0, NA, vn / (vn + fp)),
Precision = ifelse(vp + fp == 0, NA, vp / (vp + fp)),
VPN = ifelse(vn + fn == 0, NA, vn / (vn + fn)),
F1 = ifelse(2 * vp + fp + fn == 0, NA, 2 * vp / (2 * vp + fp + fn))
)
}
cortes <- sort(unique(c(seq(.10, .90, by = .05), .50, corte_youden)))
tabla_cortes <- purrr::map_dfr(cortes, ~metricas_corte(test$.y, test$prob, .x))
# mostrar_tabla(tabla_cortes, "Tabla 11. Análisis de sensibilidad para diferentes puntos de corte", longtable = TRUE)
knitr::kable(
tabla_cortes,
caption = "Tabla 11. Análisis de sensibilidad para diferentes puntos de corte"
)
| Corte | VP | VN | FP | FN | Exactitud | Sensibilidad | Especificidad | Precision | VPN | F1 |
|---|---|---|---|---|---|---|---|---|---|---|
| 0.1000000 | 53 | 218 | 152 | 19 | 0.6131222 | 0.7361111 | 0.5891892 | 0.2585366 | 0.9198312 | 0.3826715 |
| 0.1500000 | 46 | 264 | 106 | 26 | 0.7013575 | 0.6388889 | 0.7135135 | 0.3026316 | 0.9103448 | 0.4107143 |
| 0.1523413 | 46 | 266 | 104 | 26 | 0.7058824 | 0.6388889 | 0.7189189 | 0.3066667 | 0.9109589 | 0.4144144 |
| 0.2000000 | 37 | 299 | 71 | 35 | 0.7601810 | 0.5138889 | 0.8081081 | 0.3425926 | 0.8952096 | 0.4111111 |
| 0.2500000 | 32 | 317 | 53 | 40 | 0.7895928 | 0.4444444 | 0.8567568 | 0.3764706 | 0.8879552 | 0.4076433 |
| 0.3000000 | 26 | 330 | 40 | 46 | 0.8054299 | 0.3611111 | 0.8918919 | 0.3939394 | 0.8776596 | 0.3768116 |
| 0.3500000 | 23 | 343 | 27 | 49 | 0.8280543 | 0.3194444 | 0.9270270 | 0.4600000 | 0.8750000 | 0.3770492 |
| 0.4000000 | 17 | 354 | 16 | 55 | 0.8393665 | 0.2361111 | 0.9567568 | 0.5151515 | 0.8655257 | 0.3238095 |
| 0.4500000 | 15 | 357 | 13 | 57 | 0.8416290 | 0.2083333 | 0.9648649 | 0.5357143 | 0.8623188 | 0.3000000 |
| 0.5000000 | 11 | 359 | 11 | 61 | 0.8371041 | 0.1527778 | 0.9702703 | 0.5000000 | 0.8547619 | 0.2340426 |
| 0.5500000 | 8 | 362 | 8 | 64 | 0.8371041 | 0.1111111 | 0.9783784 | 0.5000000 | 0.8497653 | 0.1818182 |
| 0.6000000 | 4 | 366 | 4 | 68 | 0.8371041 | 0.0555556 | 0.9891892 | 0.5000000 | 0.8433180 | 0.1000000 |
| 0.6500000 | 1 | 367 | 3 | 71 | 0.8325792 | 0.0138889 | 0.9918919 | 0.2500000 | 0.8378995 | 0.0263158 |
| 0.7000000 | 1 | 367 | 3 | 71 | 0.8325792 | 0.0138889 | 0.9918919 | 0.2500000 | 0.8378995 | 0.0263158 |
| 0.7500000 | 1 | 369 | 1 | 71 | 0.8371041 | 0.0138889 | 0.9972973 | 0.5000000 | 0.8386364 | 0.0270270 |
| 0.8000000 | 0 | 369 | 1 | 72 | 0.8348416 | 0.0000000 | 0.9972973 | 0.0000000 | 0.8367347 | 0.0000000 |
| 0.8500000 | 0 | 370 | 0 | 72 | 0.8371041 | 0.0000000 | 1.0000000 | NA | 0.8371041 | 0.0000000 |
| 0.9000000 | 0 | 370 | 0 | 72 | 0.8371041 | 0.0000000 | 1.0000000 | NA | 0.8371041 | 0.0000000 |
Sensibilidad de las métricas al punto de corte. Fuente: elaboración propia.
metricas_finales <- metricas_corte(test$.y, test$prob, corte_youden)
mostrar_tabla(metricas_finales, "Tabla 12. Matriz de confusión resumida y métricas con corte de Youden")
knitr::kable(
metricas_finales,
caption = "Tabla 12. Matriz de confusión resumida y métricas con corte de Youden"
)
| Corte | VP | VN | FP | FN | Exactitud | Sensibilidad | Especificidad | Precision | VPN | F1 |
|---|---|---|---|---|---|---|---|---|---|---|
| 0.1523413 | 46 | 266 | 104 | 26 | 0.7058824 | 0.6388889 | 0.7189189 | 0.3066667 | 0.9109589 | 0.4144144 |
El corte de Youden maximiza la suma de sensibilidad y especificidad. Para gestión humana puede preferirse un corte menor si el coste de omitir a una persona en riesgo es mayor que el de ofrecer una intervención preventiva a un falso positivo. La decisión debe documentar costes, capacidad operativa y equidad.
# Perfil representativo: mediana en cuantitativas y categoría modal en categóricas.
nuevo <- train[1, c(vars_cat, vars_num), drop = FALSE]
for (v in vars_num) nuevo[[v]] <- median(train[[v]], na.rm = TRUE)
for (v in vars_cat) {
moda <- names(sort(table(train[[v]]), decreasing = TRUE))[1]
nuevo[[v]] <- factor(moda, levels = levels(train[[v]]))
}
prob_nuevo <- as.numeric(predict(modelo, newdata = nuevo, type = "response"))
decision <- ifelse(prob_nuevo >= corte_youden, "Intervenir preventivamente", "Seguimiento ordinario")
perfil_salida <- nuevo |>
mutate(Probabilidad_rotacion = prob_nuevo, Corte_decision = corte_youden, Decision = decision)
# mostrar_tabla(perfil_salida, "Tabla 13. Predicción para un empleado hipotético", longtable = TRUE)
knitr::kable(
perfil_salida,
caption = "Tabla 13. Predicción para un empleado hipotético"
)
| Horas_Extra | Estado_Civil | Cargo | Ingreso_Mensual | Edad | Distancia_Casa | Probabilidad_rotacion | Corte_decision | Decision |
|---|---|---|---|---|---|---|---|---|
| No | Casado | Ejecutivo_Ventas | 4898 | 35 | 7 | 0.0586111 | 0.1523413 | Seguimiento ordinario |
La probabilidad estimada se compara con el corte seleccionado. Una intervención preventiva no debe ser sancionatoria. Puede consistir en conversación de desarrollo, revisión de carga, análisis de reconocimiento, flexibilidad, oportunidades internas o acompañamiento del liderazgo, según las variables relevantes y el contexto del empleado.
El modelo Logit permite estimar una probabilidad individual de rotación y separar el análisis explicativo de la decisión operativa.
La evaluación se realizó fuera de muestra. El AUC obtenido fue 0.726, por lo que la capacidad discriminativa debe interpretarse conjuntamente con la curva ROC y las métricas del punto de corte.
Las covariables o categorías con evidencia estadística al 5 % fueron: Horas_ExtraSi, Estado_CivilSoltero, CargoRecursos_Humanos, CargoRepresentante_Ventas, Edad, Distancia_Casa. El signo de cada coeficiente muestra si aumenta o reduce el logit de rotación, manteniendo constantes las demás variables.
La estrategia de retención debe concentrarse en factores modificables y evitar decisiones adversas basadas en atributos personales. Se recomienda combinar: revisión de cargas y horas extra, conversaciones de carrera, movilidad interna, reconocimiento, revisión de compensación cuando corresponda y seguimiento del clima laboral.
El punto de corte no es universal. Debe seleccionarse según el coste de falsos negativos, el coste de falsos positivos y la capacidad real de atención. La gráfica de sensibilidad permite justificar esta decisión.
El modelo identifica asociaciones, no causas. Antes de desplegarlo se recomienda validación temporal, análisis de estabilidad, revisión de sesgos y monitoreo periódico del desempeño.
[1] P. McCullagh y J. A. Nelder, Generalized Linear Models, 2.ª ed. Chapman and Hall/CRC, 1989.
[2] A. Agresti, An Introduction to Categorical Data Analysis, 3.ª ed. Wiley, 2018.
[3] D. W. Hosmer, S. Lemeshow y R. X. Sturdivant, Applied Logistic Regression, 3.ª ed. Wiley, 2013.
[4] T. Fawcett, ‘An Introduction to ROC Analysis’, Pattern Recognition Letters, vol. 27, n.º 8, pp. 861-874, 2006.
[5] P. Chapman et al., CRISP-DM 1.0: Step-by-Step Data Mining Guide. SPSS, 2000.
[6] G. James, D. Witten, T. Hastie y R. Tibshirani, An Introduction to Statistical Learning: with Applications in R, 2.ª ed. Springer, 2021.
[7] M. Kuhn y K. Johnson, Applied Predictive Modeling. Springer, 2013.
[8] R. J. A. Little y D. B. Rubin, Statistical Analysis with Missing Data, 3.ª ed. Wiley, 2019.
# mostrar_tabla(resumen_calidad, "Tabla A1. Detalle completo de inconsistencias y nivel de criticidad", longtable = TRUE)
knitr::kable(
resumen_calidad,
caption = "Tabla A1. Detalle completo de inconsistencias y nivel de criticidad"
)
| Variable | Faltantes | Vacios | No_finitos | Inconsistentes_negativos | Atipicos_IQR | Total_alertas | Criticidad_pct | Nivel |
|---|---|---|---|---|---|---|---|---|
| Rotación | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Edad | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Viaje de Negocios | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Departamento | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Distancia_Casa | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Educación | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Campo_Educación | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Satisfacción_Ambiental | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Genero | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Cargo | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Satisfación_Laboral | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Estado_Civil | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Ingreso_Mensual | 0 | 0 | 0 | 0 | 114 | 114 | 7.755102 | Medio - intervenir |
| Trabajos_Anteriores | 0 | 0 | 0 | 0 | 52 | 52 | 3.537415 | Bajo - revisar |
| Horas_Extra | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Porcentaje_aumento_salarial | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Rendimiento_Laboral | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Años_Experiencia | 0 | 0 | 0 | 0 | 63 | 63 | 4.285714 | Bajo - revisar |
| Capacitaciones | 0 | 0 | 0 | 0 | 238 | 238 | 16.190476 | Alto - rechazar/reformular |
| Equilibrio_Trabajo_Vida | 0 | 0 | 0 | 0 | 0 | 0 | 0.000000 | Aceptable |
| Antigüedad | 0 | 0 | 0 | 0 | 104 | 104 | 7.074830 | Medio - intervenir |
| Antigüedad_Cargo | 0 | 0 | 0 | 0 | 21 | 21 | 1.428571 | Bajo - revisar |
| Años_ultima_promoción | 0 | 0 | 0 | 0 | 107 | 107 | 7.278912 | Medio - intervenir |
| Años_acargo_con_mismo_jefe | 0 | 0 | 0 | 0 | 14 | 14 | 0.952381 | Bajo - revisar |
sessionInfo()
## R version 4.5.0 (2025-04-11 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26100)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=Spanish_Colombia.utf8 LC_CTYPE=Spanish_Colombia.utf8
## [3] LC_MONETARY=Spanish_Colombia.utf8 LC_NUMERIC=C
## [5] LC_TIME=Spanish_Colombia.utf8
##
## time zone: America/Bogota
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] patchwork_1.3.2 car_3.1-5 carData_3.0-6
## [4] rsample_1.3.2 pROC_1.19.1 kableExtra_1.4.1
## [7] scales_1.4.0 forcats_1.0.1 stringr_1.6.0
## [10] purrr_1.2.1 tidyr_1.3.2 dplyr_1.2.0
## [13] paqueteMODELOS_0.1.0 summarytools_1.1.5 knitr_1.52
## [16] gridExtra_2.3 GGally_2.4.0 ggplot2_4.0.3
## [19] broom_1.0.13 boot_1.3-31
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 xfun_0.56 bslib_0.10.0 vctrs_0.7.1
## [5] tools_4.5.0 generics_0.1.4 parallel_4.5.0 tibble_3.3.1
## [9] pkgconfig_2.0.3 checkmate_2.3.4 RColorBrewer_1.1-3 S7_0.2.1
## [13] lifecycle_1.0.5 compiler_4.5.0 farver_2.1.2 rapportools_1.2
## [17] textshaping_1.0.4 codetools_0.2-20 htmltools_0.5.9 sass_0.4.10
## [21] yaml_2.3.12 Formula_1.2-5 pillar_1.11.1 furrr_0.4.0
## [25] jquerylib_0.1.4 MASS_7.3-65 cachem_1.1.0 magick_2.9.0
## [29] abind_1.4-8 parallelly_1.46.1 ggstats_0.13.0 tidyselect_1.2.1
## [33] digest_0.6.39 stringi_1.8.7 future_1.75.0 reshape2_1.4.5
## [37] pander_0.6.6 listenv_0.10.0 labeling_0.4.3 fastmap_1.2.0
## [41] grid_4.5.0 cli_3.6.5 magrittr_2.0.4 base64enc_0.1-6
## [45] withr_3.0.2 backports_1.5.0 timechange_0.4.0 lubridate_1.9.5
## [49] rmarkdown_2.32 matrixStats_1.5.0 globals_0.19.1 otel_0.2.0
## [53] evaluate_1.0.5 tcltk_4.5.0 viridisLite_0.4.3 rlang_1.1.7
## [57] Rcpp_1.1.1 glue_1.8.0 xml2_1.5.2 svglite_2.2.2
## [61] rstudioapi_0.18.0 jsonlite_2.0.0 plyr_1.8.9 R6_2.6.1
## [65] systemfonts_1.3.1
formula_modelo
## .y ~ Horas_Extra + Estado_Civil + Cargo + Ingreso_Mensual + Edad +
## Distancia_Casa