1 Resumen ejecutivo

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.

2 Contexto del problema

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.

3 Introducció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.

4 Metodología

Esquema metodologico del analisis. Fuente: elaboracion propia.

5 Carga y reconocimiento de los datos

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, …

5.1 Diccionario de datos

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"
)
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

6 Selección de variables e hipótesis

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"
)
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.

7 Auditoría de calidad de datos

7.1 Reglas de auditoría

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"
)
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.

8 Limpieza controlada e imputación

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"
)
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.

9 Análisis estadístico descriptivo

9.1 Variable respuesta: rotación

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.

9.2 Variables cuantitativas

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"
)
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.

9.3 Variables categóricas

Distribuciones categóricas seleccionadas. Fuente: elaboración propia.

10 Análisis bivariado

10.1 Pruebas por variable

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"
)
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"
)
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.

11 Estimación del modelo Logit

11.1 División entrenamiento y prueba

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

11.2 Coeficientes, significancia y odds ratios

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"
)
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"
)
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

11.3 Diagnóstico de multicolinealidad

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"
)
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.

12 Evaluación predictiva

12.1 Curva ROC y AUC

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.

12.2 Sensibilidad del punto de corte

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"
)
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.

12.3 Matriz de confusión con corte seleccionado

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"
)
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.

13 Predicción de un empleado hipotético

# 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"
)
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.

14 Conclusiones y estrategia de retención

  1. El modelo Logit permite estimar una probabilidad individual de rotación y separar el análisis explicativo de la decisión operativa.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

15 Limitaciones

16 Referencias

[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.

17 Anexos

17.1 Anexo A. Tabla completa de inconsistencias

# 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"
)
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

17.2 Anexo B. Información de reproducibilidad

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

17.3 Anexo C. Fórmula estimada

formula_modelo
## .y ~ Horas_Extra + Estado_Civil + Cargo + Ingreso_Mensual + Edad + 
##     Distancia_Casa