0.0.1 Controles de Visualización de Código

Utilice los siguientes controles para gestionar la visualización del código en este documento

1 Introducción

El análisis y la predicción de la estructura salarial constituyen un pilar fundamental en los estudios socioeconómicos y del mercado de trabajo. En el ámbito de la ciencia de datos y el aprendizaje automático (Machine Learning), comprender los factores que determinan si un individuo pertenece a un segmento de ingresos altos o bajos se plantea como un problema clásico de clasificación supervisada.La relevancia de clasificar el ingreso laboral radica en dos aspectos fundamentales: en primer lugar, permite identificar con precisión qué características sociodemográficas y formativas (como la educación, la edad o las horas trabajadas) están más fuertemente asociadas a niveles salariales superiores. En segundo lugar, desde la perspectiva metodológica, clasificar el ingreso mediante analítica de datos permite evaluar la efectividad real de diversas herramientas de aprendizaje automático al enfrentarse a la complejidad y variabilidad inherentes a las bases de datos de empleo.Para este estudio, se aborda la categorización de los ingresos laborales a partir de una muestra representativa de 3,000 registros sociodemográficos y de empleo. La variable de respuesta u output Y se definió de manera binaria utilizando como umbral de corte la mediana del ingreso laboral (1,580,000 COP). Bajo este criterio, se asigna el valor 1 a los trabajadores con un salario alto (superior a la mediana) y el valor 0 a aquellos con un salario bajo (inferior o igual a la mediana). Esta transformación garantiza un conjunto de datos perfectamente balanceado (50% – 50%), lo que elimina sesgos de clase y permite una evaluación cuantitativa rigurosa sobre la capacidad predictiva de los algoritmos.Por su parte, las variables independientes o explicativas X analizan diversas dimensiones socio-laborales, incluyendo variables cualitativas (sexo, nivel educativo, tipo de empleado) y cuantitativas continuas (edad y horas trabajadas).El objetivo central de este proyecto es realizar una evaluación comparativa entre dos modelos de clasificación supervisada con enfoques metodológicos distintos: la Regresión Logística (modelo paramétrico e interpretable) y el algoritmo de K-Vecinos Más Cercanos / KNN (modelo no paramétrico basado en distancias). A través del entrenamiento y la validación en un conjunto de prueba (Test), este informe examinará el desempeño de ambos modelos mediante la matriz de confusión, la exactitud (accuracy), la sensibilidad, la especificidad y la curva ROC/AUC, determinando cuál de las dos aproximaciones logra un mejor equilibrio entre precisión predictiva y practicidad en la clasificación del ingreso laboral.

2 Metodología

la metodologia consiste en simples partes

2.1 Preparación y construcción de la base de datos

La información utilizada en el presente estudio proviene de la Gran Encuesta Integrada de Hogares (GEIH) 2025, realizada por el Departamento Administrativo Nacional de Estadística (DANE). La GEIH es una operación estadística orientada a la caracterización del mercado laboral colombiano y recopila información sobre las condiciones sociodemográficas y laborales de la población, incluyendo aspectos relacionados con educación, ocupación, horas trabajadas e ingresos. La encuesta cuenta con cobertura nacional y permite analizar diferentes características de la población y del mercado de trabajo. Para el desarrollo de este estudio se utilizó específicamente la información correspondiente al mes de junio de 2025, disponible en el catálogo de microdatos del DANE. Los archivos utilizados fueron “Características generales, seguridad social, salud y educación” y “Ocupados”, debido a que contienen las variables necesarias para construir la base de datos requerida para el análisis. Fuente: Departamento Administrativo Nacional de Estadística (DANE). Gran Encuesta Integrada de Hogares (GEIH) – 2025. Enlace: https://microdatos.dane.gov.co/index.php/catalog/853

2.2 Limpieza e integración de la Base de Datos

La construcción del conjunto de datos final se realizó mediante la integración de dos módulos principales de la Gran Encuesta Integrada de Hogares (GEIH) de junio de 2025: “Características generales, seguridad social, salud y educación” y “Ocupados”. La fusión se efectuó utilizando como llaves únicas las variables identificadoras DIRECTORIO, SECUENCIA_P y ORDEN, obteniendo una base consolidada inicial de 13,885 observaciones. Con el propósito de adecuar la base al objetivo principal del proyecto la evaluación comparativa de algoritmos de clasificación supervisada (Regresión Logística vs. KNN), se aplicó un proceso de depuración, filtrado y balanceo estructurado en las siguientes fases: Se definió la variable dependiente de respuesta (Y) como una estructura cualitativa binaria (ingreso_laboral), calculando la mediana del ingreso laboral de la población ocupada (1,580,000 COP) como punto de corte decisional. De esta forma, se categorizaron los salarios superiores a la mediana como 1 (“Ingreso Alto”) y aquellos iguales o inferiores como 0 (“Ingreso Bajo”) Para garantizar la máxima eficacia, sensibilidad y comparabilidad entre la Regresión Logística y KNN. La selección de la muestra de 3,000 observaciones se realizó mediante un muestreo aleatorio representativo a partir de la base consolidada inicial (13,885 registros). La obtención de un conjunto de datos perfectamente balanceado (50% de Ingreso Alto y 50% de Ingreso Bajo) es el resultado directo del criterio metodológico adoptado: la binarización de la variable dependiente Y tomó como punto de corte la mediana del ingreso laboral (1,580,000 COP). Al utilizar la mediana como umbral, la variable Y queda naturalmente dividida en dos mitades equivalentes en la población original. El muestreo aleatorio posterior conservó proporcionalmente esta distribución, garantizando un dataset sin desbalance de clases (1,500 registros para Y=1 y 1,500 para Y=0), lo cual favorece la sensibilidad y capacidad predictiva de la Regresión Logística y KNN.

2.3 Recodificación y Estructuración de Variables Independientes

Posterior al balanceo de la variable objetivo, se reorganizaron las características independientes (\(X\)) para su inclusión en los clasificadores:
Nivel Educativo: Se agruparon las categorías originales en 5 niveles ordenados: Primaria, Secundaria, Técnica/Tecnología, Universitaria y Postgrado. Sexo y Tipo de Empleado: Se mantuvieron como variables categóricas nominales identificando género (Hombre / Mujer) y sector ocupacional (Particular / Gobierno). Edad y Horas Trabajadas: Se conservaron en sus magnitudes continuas originales (años cumplidos y horas semanales) para el análisis exploratorio y su posterior estandarización en la fase de modelado. Como resultado final de esta fase, se obtuvo un conjunto de datos optimizado de 3,000 registros y 6 variables clave (sexo, edad, nivel_educativo, horas_trabajadas, tipo_empleado e ingreso_laboral), constituido bajo las condiciones ideales para el entrenamiento y validación de los modelos predictivos.

2.4 Particion de Datos

la partcicion de datos consiste en separar el porcenta a entrenar y el otro porcentaje para verlo por fuera de la muestra, en este caso fue (70-30). lo que quiere decir 70 % entrenamiento para variables de entrada ouputs y salida inputs y 30 % de prueba.

2.5 Metodos Utlizados

2.5.1 Modelo K-Vecinos Mas Cercanos (KNN)

Modelo K-Vecinos Más Cercanos (KNN) Se ajustó el modelo KNN utilizando el paquete caret⁠. Las variables predictoras (cuantitativas y categóricas convertidas a variables dummy) se estandarizaron mediante centrado y escalado. El número de vecinos (k) se eligió mediante validación cruzada de 10 pliegues (10-fold cross-validation) aplicada sobre el conjunto de entrenamiento (70% de los datos, con semilla 123), evaluando valores impares de k en el rango de 1 a 151 y seleccionando aquel que maximizó la exactitud (Accuracy). El conjunto de prueba (30% restante) se reservó únicamente para la evaluación final fuera de muestra, evitando cualquier fuga de información.

2.5.2 Regresion Logistica (Logit)

Se estimó con glm() (familia binomial, enlace logit). Las probabilidades predichas se convirtieron en clases con dos umbrales: 0,5 y el umbral óptimo según el índice de Youden, calculado sobre el conjunto de entrenamiento.

2.6 Definicion de las Variables

Para desarrollar este estudio se seleccionaron seis variables de la base de datos depurada, relacionadas con las características personales y laborales de los trabajadores. De acuerdo con el objetivo del Taller 2, estas variables se organizaron en una variable dependiente (Y) y cinco variables independientes (X), con el propósito de construir y comparar dos modelos de clasificación: K-Vecinos Más Cercanos (KNN) y Regresión Logística.

La variable dependiente es ingreso_laboral, que permite clasificar a los trabajadores en dos categorías: ingreso bajo (0) e ingreso alto (1). Esta variable representa el resultado que se busca predecir mediante los modelos. Su importancia está en que permite analizar si las características personales y laborales incluidas en la base de datos ayudan a distinguir entre los dos grupos de ingreso. Es importante aclarar que esta variable contiene una clasificación y no el valor exacto del salario de cada trabajador.

Como variables independientes se seleccionaron cinco características. La primera es sexo, que identifica si la persona es hombre o mujer. Se incluye porque permite explorar si existen diferencias en la clasificación del ingreso entre ambos grupos, sin asumir de antemano que estas diferencias necesariamente se presentan.

La segunda variable es edad, que corresponde a los años cumplidos de cada trabajador. Se considera relevante porque las características laborales y las oportunidades de ingreso pueden cambiar a lo largo de la vida. Su inclusión permite evaluar si la edad aporta información útil para clasificar a los trabajadores según su categoría de ingreso.

La tercera variable es nivel_educativo, que representa el nivel de formación académica registrado para cada persona: primaria, secundaria, técnica o tecnología, universitaria y postgrado. Esta variable se utiliza porque la formación puede estar relacionada con el acceso a diferentes ocupaciones y condiciones laborales. Por ello, resulta útil para examinar si el nivel educativo contribuye a diferenciar las categorías de ingreso.

La cuarta variable es horas_trabajadas, que indica la cantidad de horas trabajadas por semana. Se seleccionó porque el tiempo dedicado al trabajo puede estar relacionado con el ingreso laboral. Sin embargo, esta relación también puede depender de otras condiciones, como el tipo de empleo y las características de la ocupación, por lo que su efecto debe analizarse a partir de los resultados obtenidos.

Por último, la variable tipo_empleado distingue entre trabajadores del sector particular y del gobierno. Se incluye porque las condiciones de contratación, las funciones desempeñadas y las formas de remuneración pueden variar entre estos sectores. Su análisis permite establecer si esta característica aporta información para clasificar a los trabajadores según su categoría de ingreso.

En conjunto, las cinco variables independientes se utilizarán como predictoras de la variable dependiente. Su selección responde a la información disponible en la base de datos y al objetivo de evaluar el desempeño de los modelos KNN y Regresión Logística. La comparación permitirá determinar qué modelo clasifica mejor los niveles de ingreso, de acuerdo con las métricas de evaluación establecidas en el taller.

La Tabla 1 presenta la descripción, el tipo de variable y algunos ejemplos de los valores que puede tomar cada una de las seis variables seleccionadas.

Tabla 1. Definición y clasificación de las variables del estudio
Variable Descripción Tipo de variable Ejemplos / notas
Ingreso laboral Variable dependiente que clasifica a los trabajadores según la categoría de ingreso definida para el estudio. Es el resultado que los modelos buscan predecir. Categórica binaria (dependiente) 0 = ingreso bajo; 1 = ingreso alto.
Sexo Identifica el sexo registrado de cada persona y permite explorar su posible relación con la categoría de ingreso laboral. Categórica nominal Hombre, mujer.
Edad Representa la edad del trabajador en años cumplidos y permite analizar su relación con la clasificación del ingreso. Cuantitativa discreta Edad expresada en años.
Nivel educativo Indica el nivel educativo alcanzado por el trabajador y permite examinar su relación con la categoría de ingreso. Categórica ordinal Primaria, secundaria, técnica/tecnología, universitaria y postgrado.
Horas trabajadas Registra la cantidad de horas trabajadas por semana y permite estudiar su posible relación con el nivel de ingreso. Cuantitativa discreta Cantidad de horas trabajadas por semana.
Tipo de empleado Identifica la categoría de vinculación laboral registrada en la base y permite explorar su relación con la clasificación del ingreso. Categórica nominal Particular, Gobierno.
Fuente: Elaboración propia con base en la base de datos depurada utilizada en el estudio.

2.7 Base de Datos

3 ANALISIS DESCRIPTIVO

Con el fin de comprender de manera preliminar la composición y variabilidad de las observaciones, se presenta a continuación un resumen de las principales estadísticas descriptivas y la distribución de frecuencias por nivel de ingreso

3.1 Estadisticas Descriptivas de las Variables Principales (Cuantitativas)

la tabla 1 resume las medidas de tendencia central y dispersion de las variables Cuantitativas incluidas en el estudio

library(kableExtra)
library(readxl)
datos <- read_excel("base_salarios_Limpia.xlsx")

tabla_cuanti <- datos %>%
  select(edad, horas_trabajadas) %>%
  pivot_longer(everything(), names_to = "variable", values_to = "valor") %>%
  group_by(variable) %>%
  summarise(
    minimo  = min(valor, na.rm = TRUE),
    q1      = quantile(valor, 0.25, na.rm = TRUE),
    mediana = median(valor, na.rm = TRUE),
    media   = mean(valor, na.rm = TRUE),
    desv    = sd(valor, na.rm = TRUE),
    q3      = quantile(valor, 0.75, na.rm = TRUE),
    maximo  = max(valor, na.rm = TRUE)
  ) %>%
  mutate(variable = recode(variable,
                           edad = "Edad",
                           horas_trabajadas = "Horas trabajadas"))

tabla_cuanti %>%
  kable(
    format = "html",
    digits = 2,
    col.names = c("Variable", "Mínimo", "1.er Cuartil", "Mediana", "Media",
                  "Desv. Estándar", "3.er Cuartil", "Máximo"),
    align = c("l", "r", "r", "r", "r", "r", "r", "r"),
    caption = "Tabla 1: Estadísticas Descriptivas de las Variables Del Dataset"
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width = FALSE,
    font_size = 14,
    position = "center"
  ) %>%
  row_spec(0, background = "#0a8f3c", color = "white", bold = TRUE) %>%
  column_spec(1, bold = TRUE, color = "#064e2b", width = "4cm") %>%
  column_spec(2:8, width = "2.5cm") %>%
  footnote(
    general = "Fuente: Elaboración propia con base de datos de GEIH",
    general_title = "Nota:",
    footnote_as_chunk = TRUE
  )
Tabla 1: Estadísticas Descriptivas de las Variables Del Dataset
Variable Mínimo 1.er Cuartil Mediana Media Desv. Estándar 3.er Cuartil Máximo
Edad 18 28 37 38.34 12.38 47 78
Horas trabajadas 5 44 47 46.10 9.11 48 130
Nota: Fuente: Elaboración propia con base de datos de GEIH

Los trabajadores tienen una edad media de 38,34 años y una mediana de 37, muy cercanas entre sí, lo que indica una distribución bastante simétrica con una ligera asimetría hacia la derecha, porque la media queda un poco por encima de la mediana.La desviación estándar es de 12,38 años, es decir, que en promedio las edades se alejan unos 12 años de la media. los valores extremos van de 18 a 78 años

La jornada semanal tiene una media de 46,10 horas y una mediana de 47. Que la media sea menor que la mediana sugiere una ligera asimetría hacia la izquierda, causada por quienes trabajan pocas horas (el mínimo es de 5) con una desviación estándar de 9,11 horas. Esta cifra debe leerse junto con el rango intercuartílico, que es de solo 4 horas (de 44 a 48): la mayoría de las personas tiene jornadas muy parecidas, pero la desviación estándar es mucho mayor de lo que sugiere ese rango central, lo que indica que está inflada por los valores extremos.El mínimo de 5 horas y, sobre todo, el máximo de 130 horas semanales (unas 18,5 horas diarias, todos los días) son casos poco plausibles que conviene revisar como posibles valores atípicos o errores de registro.

3.2 Destribucion Indiviual de las variables Numericas

3.2.1 Distribución de la Edad de los Trabajadores

library(plotly)

p1 <- ggplot(datos, aes(x = edad)) +
  geom_histogram(binwidth = 5, fill = "#0a8f3c", color = "white", alpha = 0.9) +
  labs(x = "Edad (años)", y = "Frecuencia") +
  theme_minimal(base_size = 14) +
  theme(
    axis.title       = element_text(face = "bold", color = "#064e2b"),
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "#d5ecdc")
  )

ggplotly(p1) %>%
  layout(
    title = list(
      text = paste0("<b>Figura 1, Distribución de la Edad de los Trabajadores</b>",
                    "<br><sup><span style='color:#0a8f3c'>Histograma de frecuencias</span></sup>"),
      x = 0.5,
      font = list(color = "#064e2b", size = 20)
    ),
    hoverlabel = list(bgcolor = "#064e2b", font = list(color = "white")),
    margin = list(t = 90)
  ) %>%
  config(displaylogo = FALSE)

La distribución de la edad presenta una forma unimodal con asimetría positiva (hacia la derecha). La mayor concentración de trabajadores se encuentra entre los 25 y los 35 años, con un máximo cercano a los 30 años, donde se acumulan alrededor de 460 personas en un solo intervalo. A partir de ese punto, la frecuencia disminuye de manera gradual, se mantiene relativamente alta hasta los 45 años, desciende de forma más marcada después de los 50 y se vuelve muy baja pasados los 65 años, con apenas unos pocos casos por encima de los 70.

Se observa también que el primer intervalo (18 a 22 años) tiene una frecuencia menor, cercana a las 250 personas, que los dos siguientes. Esto puede asociarse con que una parte de la población joven aún se encuentra estudiando o apenas está ingresando al mercado laboral. Por el lado contrario, la caída después de los 60 años puede relacionarse con el retiro y la pensión, ya que en colombia los hombres se retiran a los 62 años y las mujeres a los 57 años, por eso representa el 8 % de la muestra

3.2.2 Distrubucion de las horas de los trabajadores

la figura 2

library(plotly)

# 1. Guardar la estructura básica del histograma en un objeto 'p'
p <- ggplot(datos, aes(x = horas_trabajadas)) +
  geom_histogram(
    binwidth = 4, fill = "#0a8f3c", color = "white", alpha = 0.95
  ) +
  scale_x_continuous(breaks = seq(0, 120, by = 10)) +
  labs(
    title = "Figura 2, Distribución de Frecuencia de la Jornada Laboral Semanal",
    subtitle = "Muestra GEIH 2025 (N = 3,000)",
    x = "Horas Trabajadas a la Semana",
    y = "Frecuencia (Número de Personas)"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    plot.title    = element_text(face = "bold", size = 16, color = "#064e2b", hjust = 0.5),
    plot.subtitle = element_text(size = 13, color = "#0a8f3c", hjust = 0.5),
    axis.title    = element_text(face = "bold", color = "#064e2b"),
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "#d5ecdc")
  )

# 2. Renderizar como gráfico interactivo
ggplotly(p) %>%
  layout(
    hoverlabel = list(bgcolor = "#064e2b", font = list(color = "white")),
    margin = list(t = 70)
  ) %>%
  config(displaylogo = FALSE)

Analisis

3.3 Distribución de las variables categoricas (Cualitativas)

3.3.1 Distribución de la variable ingreso laboral

La variable ingreso_laboral es la variable dependiente del estudio y permite clasificar a los trabajadores en dos categorías: ingreso bajo (0) e ingreso alto (1). Para conocer la composición de la muestra, se presenta una tabla interactiva que permite consultar los registros y observar cómo se distribuyen las categorías de ingreso laboral. Este análisis permite identificar la proporción de trabajadores en cada grupo antes de construir los modelos de clasificación KNN y regresión logística.

Interpretación de los resultados

La base de datos contiene 3.000 observaciones, distribuidas equitativamente entre las dos categorías: 1.500 trabajadores con ingreso bajo y 1.500 con ingreso alto. Cada grupo representa el 50 % del total, por lo que la variable dependiente presenta una distribución equilibrada. Esto permite comparar el desempeño de los modelos de clasificación sin que una categoría tenga mayor representación que la otra. Posteriormente, se evaluarán indicadores como la exactitud, la sensibilidad y el AUC para determinar cuál de los modelos ofrece mejores resultados.

3.3.2 Distribucion Por sexo

La variable sexo permite identificar cómo se distribuyen los trabajadores de la muestra según esta característica. Para analizar su composición, se presenta un gráfico de dona interactivo que muestra la cantidad y el porcentaje de hombres y mujeres. Al pasar el cursor sobre cada segmento, es posible consultar la información correspondiente y comparar la participación de cada categoría dentro del total de observaciones.

Interpretación de los resultados

El gráfico muestra que, de los 3.000 trabajadores incluidos en la base de datos, 1.584 son hombres, equivalentes al 52,8 %, y 1.416 son mujeres, que representan el 47,2 %. Se observa una mayor participación de hombres, aunque la diferencia entre ambos grupos no es muy amplia. Esta distribución permite conocer la composición de la muestra y sirve como punto de partida para analizar posteriormente si el sexo presenta alguna relación con la clasificación del ingreso laboral.

3.3.3 Distribución de las variables categóricas por nivel educativo

La variable nivel_educativo permite identificar el nivel de formación académica de los trabajadores incluidos en la base de datos. Para analizar su distribución, se presenta un gráfico de barras horizontal e interactivo que muestra la cantidad de trabajadores en cada categoría y el porcentaje que representan sobre el total. El gráfico cuenta con una animación automática que permite observar cómo crecen las barras desde cero hasta alcanzar su frecuencia correspondiente, facilitando la comparación entre los diferentes niveles educativos.

Interpretación de los resultados

La distribución por nivel educativo permite observar las diferencias en la formación académica de los trabajadores incluidos en la muestra. La categoría con mayor frecuencia es secundaria, con 1.057 personas, que representan aproximadamente el 35,2 % del total. Le siguen el nivel universitario, con 674 trabajadores (22,5 %); técnica o tecnología, con 577 (19,2 %); primaria, con 442 (14,7 %); y postgrado, con 250 trabajadores (8,3 %).

Estos resultados muestran que la mayor proporción de trabajadores tiene formación secundaria, mientras que el nivel de postgrado presenta la menor participación. Esta variable es importante para el estudio porque permite explorar si la formación académica está relacionada con la clasificación del ingreso laboral. Sin embargo, esta distribución por sí sola no demuestra que un nivel educativo determinado produzca ingresos más altos; esa relación deberá analizarse con los modelos de clasificación.

3.3.4 Distribución según el tipo de empleado

En este apartado se analiza la distribución de los trabajadores según su tipo de vinculación laboral, diferenciando entre empleados del sector particular y empleados del Gobierno. Para ello, se presenta un gráfico de barras animado que permite observar cómo aumenta progresivamente la cantidad de trabajadores y su porcentaje dentro del total de la muestra. De esta manera, se facilita la comparación entre las dos categorías y se identifica cuál tiene mayor participación.

Interpretación de los resultados

El gráfico muestra que, de los 3.000 trabajadores analizados, 2.672 pertenecen al sector particular, lo que representa el 89,1 % de la muestra. Por su parte, 328 trabajadores pertenecen al Gobierno, equivalentes al 10,9 %. Estos resultados evidencian que existe una mayor concentración de trabajadores en el sector particular, cuya participación es considerablemente superior a la del Gobierno. En conclusión, en esta base de datos predomina el empleo particular.

3.4 Variables Relacionadas

3.4.1 Distribucion de la Edad segun el Ingreso Laboral

library(plotly)

datos_box <- datos %>%
  mutate(nivel_ingreso = factor(
    ifelse(ingreso_laboral == 1, "Ingreso Alto", "Ingreso Bajo"),
    levels = c("Ingreso Bajo", "Ingreso Alto")
  ))

plot_ly(
  datos_box,
  x = ~nivel_ingreso, y = ~edad, color = ~nivel_ingreso,
  colors = c("Ingreso Bajo" = "#a8e6bf", "Ingreso Alto" = "#0a8f3c"),
  type = "box", boxmean = TRUE,            # la línea punteada es la media
  line = list(color = "#064e2b", width = 2),
  showlegend = FALSE
) %>%
  layout(
    title = list(
      text = "<b>Figura 7,Distribución de la Edad según Nivel de Ingreso Laboral</b>",
      x = 0.5, font = list(color = "#064e2b", size = 20)
    ),
    xaxis = list(
      title = list(text = "Nivel de ingreso laboral", font = list(color = "#064e2b")),
      tickfont = list(color = "#064e2b", size = 14)
    ),
    yaxis = list(
      title = list(text = "Edad (años)", font = list(color = "#064e2b")),
      tickfont = list(color = "#064e2b"),
      gridcolor = "#d5ecdc", zeroline = FALSE
    ),
    hoverlabel = list(bgcolor = "#064e2b", font = list(color = "white")),
    plot_bgcolor = "white", paper_bgcolor = "white",
    margin = list(t = 80, b = 70)
  ) %>%
  config(displaylogo = FALSE)

Los trabajadores con ingreso alto tienden a ser algo mayores que los de ingreso bajo. Su mediana es de 39 años frente a 35, y su media de 39,9 frente a 36,8. El 50 % central también está desplazado hacia edades más altas: de 31 a 48 años en el grupo alto y de 26 a 46 en el bajo.

La diferencia es moderada, porque las cajas se traslapan en gran parte y las medianas están a solo 4 años. El grupo de ingreso bajo es más heterogéneo (rango intercuartílico de 20 años frente a 17). Ambos grupos parten del mismo mínimo de 18 años y presentan unos pocos valores atípicos en edades altas (77 y 78 años). Se puede apreciar como la caja de ingresos alto se encuentra un poco mas arriba que la de ingresos bajos pero como se habia mencionaddo la diferencia no es mucha, pero si se puede deducir que por lo general las personas perciben mayor ingreso por su edad debido a su experiencia.

3.4.2 Distribución de las horas trabajadas según el ingreso laboral

En este apartado se analiza la distribución de las horas trabajadas por semana, diferenciando a los trabajadores según su nivel de ingreso laboral (bajo o alto). El gráfico de barras interactivo permite comparar la cantidad de trabajadores en cada grupo e identificar posibles diferencias en sus jornadas laborales.

Ingreso alto: Se registraron 1500 trabajadores. La jornada más frecuente fue de 48 horas semanales, correspondiente a 448 personas (29.87% del grupo). El promedio de horas trabajadas fue de 46.76 horas semanales.

Ingreso bajo: Se registraron 1500 trabajadores. La jornada más frecuente fue de 48 horas semanales, correspondiente a 493 personas (32.87% del grupo). El promedio de horas trabajadas fue de 45.44 horas semanales.

En conjunto, estos resultados permiten comparar las jornadas laborales de las personas con ingresos bajos y altos. Las diferencias observadas describen cómo se distribuyen las horas trabajadas en cada grupo, pero no demuestran por sí solas que una jornada más larga cause mayores ingresos.

3.4.3 Nivel educativo según el ingreso laboral

En este gráfico se compara el número de trabajadores de cada nivel educativo según su clasificación de ingreso laboral, ya sea bajo o alto. Las barras agrupadas permiten observar las diferencias entre ambos grupos y analizar cómo se distribuyen los ingresos de acuerdo con el nivel de formación académica.

En la base de datos se comparan los trabajadores con ingreso bajo y alto en cinco niveles educativos: Primaria, Secundaria, Técnica/Tecnológica, Universitaria y Postgrado. El nivel con mayor cantidad de trabajadores con ingreso bajo es Secundaria, con 684 trabajadores. Por su parte, el nivel con mayor cantidad de trabajadores con ingreso alto es Universitaria, con 471 trabajadores. Estos resultados permiten identificar cómo se distribuyen los ingresos según el nivel educativo en esta muestra. Sin embargo, las cantidades representan el número de trabajadores y no permiten concluir por sí solas qué nivel educativo tiene mayor probabilidad de obtener ingresos altos.

3.4.4 Distribución del sexo según el ingreso laboral

El siguiente gráfico de dona muestra cómo se distribuyen los trabajadores hombres y mujeres dentro de las categorías de ingreso laboral bajo y alto. Los colores permiten diferenciar el sexo y la clasificación del ingreso. Al pasar el cursor sobre cada segmento, se pueden consultar la cantidad de trabajadores y su porcentaje dentro del grupo correspondiente.

En el grupo de Ingreso bajo, el sexo con mayor representación es Hombres, con 763 trabajadores, respectivamente. En el grupo de Ingreso alto, el sexo con mayor representación es Hombres, con 821 trabajadores, respectivamente.

La comparación de las dos donas permite observar las diferencias en la composición por sexo entre los trabajadores con ingreso bajo y alto. Los porcentajes corresponden a la distribución dentro de cada grupo de ingreso. Por lo tanto, los resultados describen la muestra analizada y no demuestran, por sí solos, que el sexo sea la causa de las diferencias salariales.

3.4.5 Tipo de empleado según el ingreso laboral

Este gráfico permite comparar el tipo de empleado (Particular y Gobierno) según la clasificación del ingreso laboral (bajo y alto). Su objetivo es observar cómo se distribuyen los trabajadores en cada nivel de ingreso e identificar posibles diferencias entre ambos tipos de empleo.

El gráfico de barras interactivo muestra la distribución del tipo de empleado según el ingreso laboral en una base de datos de 3.000 observaciones. Entre las personas con ingresos bajos, 1.487 pertenecen al sector particular y 13 al Gobierno, lo que evidencia una amplia predominancia de los trabajadores particulares en esta categoría. Por otro lado, entre las personas con ingresos altos, 1.185 son trabajadores particulares y 315 pertenecen al Gobierno. Aunque los trabajadores particulares son mayoría en ambos niveles de ingreso, los empleados del Gobierno tienen una mayor representación entre quienes presentan ingresos altos. Estos resultados permiten observar diferencias en la distribución del ingreso laboral según el tipo de empleado; sin embargo, no se puede afirmar que el tipo de empleo sea la causa directa de tener ingresos altos o bajos, ya que el gráfico únicamente muestra la relación entre ambas variables en la muestra analizada.

3.4.6 Correlacion de las variables edad y horas trabajadas

options(knitr.kable.NA = "—")

ct <- cor.test(datos$edad, datos$horas_trabajadas, method = "pearson")
cs <- cor.test(datos$edad, datos$horas_trabajadas, method = "spearman", exact = FALSE)

data.frame(
  Prueba      = c("Pearson", "Spearman"),
  Coeficiente = c(unname(ct$estimate), unname(cs$estimate)),
  IC_inf      = c(ct$conf.int[1], NA),
  IC_sup      = c(ct$conf.int[2], NA),
  Valor_P     = ifelse(c(ct$p.value, cs$p.value) < 0.0001, "<0.0001",
                       sprintf("%.4f", c(ct$p.value, cs$p.value)))
) %>%
  kable(
    format = "html",
    digits = 3,
    col.names = c("Prueba", "Coeficiente (r)", "IC 95% inferior", "IC 95% superior", "Valor P"),
    align = c("l", "r", "r", "r", "r"),
    caption = "Tabla 2: Correlación entre la Edad y las Horas Trabajadas"
  ) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed"),
    full_width = FALSE,
    font_size = 14,
    position = "center"
  ) %>%
  row_spec(0, background = "#0a8f3c", color = "white", bold = TRUE) %>%
  column_spec(1, bold = TRUE, color = "#064e2b") %>%
  footnote(
    general = "Spearman no tiene intervalo de confianza en esta prueba. Fuente: Elaboración propia con base de datos de GEIH",
    general_title = "Nota:",
    footnote_as_chunk = TRUE
  )
Tabla 2: Correlación entre la Edad y las Horas Trabajadas
Prueba Coeficiente (r) IC 95% inferior IC 95% superior Valor P
Pearson -0.011 -0.047 0.024 0.5356
Spearman -0.024 — — 0.1962
Nota: Spearman no tiene intervalo de confianza en esta prueba. Fuente: Elaboración propia con base de datos de GEIH

El coeficiente de correlación de Pearson entre la edad y las horas trabajadas es de -0,011, un valor prácticamente nulo. el respectivo intervalo de confianza al 95 % va de -0,047 a 0,024, incluye el cero, y el valor P (0,5356) es mucho mayor que 0,05, por lo que no se puede afirmar que exista una correlación lineal entre ambas variables. Entonces ahi es donde entra el resultado que confirma con Spearman, que mide relaciones monótonas y es menos sensible a valores extremos como el máximo de 130 horas, su coeficiente es de -0,024 y tampoco es significativo (valor P = 0,1962). Que los dos coeficientes coincidan en ser casi cero muestra que la conclusión no depende de los valores atípicos.

para concluir con este analisis se puede decir que la edad de una persona no permite anticipar cuántas horas trabaja a la semana, los trabajadores jóvenes y los mayores tienen jornadas semmilares. Además, como ambas variables serán predictoras en los modelos de clasificación, esta correlación tan baja indica que no hay riesgo de colinealidad entre ellas, así que cada una aporta información distinta.

4 Resultados del Modelo

La clasificación es un enfoque fundamental dentro del aprendizaje supervisado cuyo objetivo es asignar observaciones a categorías predefinidas basándose en un conjunto conocido de características. Este tipo de modelos aprenden a partir de datos etiquetados, donde cada instancia cuenta con una clase o categoría asociada, para después predecir la clase de nuevas observaciones. La clasificación se aplica en diversos campos y problemas donde es necesario discriminar entre dos o más opciones. A continuación, se detalla la división de los datos y la implementación de cada modelo utilizado para evaluar su desempeño en la clasificación.


library(caret)
library(pROC)
library(kableExtra)

# Paleta de la plantilla
col_knn    <- "#064e2b"   # verde oscuro
col_logit  <- "#1fc15a"   # verde brillante
col_acento <- "#f5c400"   # dorado

tema_verde <- theme_minimal(base_size = 14) +
  theme(
    plot.title       = element_text(face = "bold", size = 16, color = "#064e2b", hjust = 0.5),
    axis.title       = element_text(face = "bold", color = "#064e2b"),
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "#d5ecdc")
  )

# Tabla con el estilo de la plantilla
tabla_verde <- function(df, caption, digits = 3) {
  kable(df, format = "html", digits = digits, caption = caption, row.names = FALSE) %>%
    kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                  full_width = FALSE, position = "center", font_size = 14) %>%
    row_spec(0, background = "#0a8f3c", color = "white", bold = TRUE)
}

# Matriz de confusión como tabla
tabla_cm <- function(cm, caption) {
  m <- as.data.frame.matrix(cm$table)
  m <- cbind(`Predicción` = rownames(m), m)
  names(m)[2:3] <- paste("Real:", names(m)[2:3])
  tabla_verde(m, caption, digits = 0)
}

# Gráfico de una matriz de confusión
graficar_cm <- function(cm, titulo, color = "#0a8f3c") {
  df <- as.data.frame(cm$table)
  names(df) <- c("Prediccion", "Real", "Frecuencia")
  ggplot(df, aes(x = Prediccion, y = Real, fill = Frecuencia)) +
    geom_tile(color = "white") +
    geom_text(aes(label = Frecuencia, color = Frecuencia > max(Frecuencia) / 2),
              size = 7, fontface = "bold") +
    scale_color_manual(values = c("TRUE" = "white", "FALSE" = "#064e2b"), guide = "none") +
    scale_fill_gradient(low = "#e6f7ec", high = color) +
    scale_y_discrete(limits = rev(levels(df$Real))) +
    labs(title = titulo, x = "Predicción", y = "Valor real") +
    tema_verde + theme(legend.position = "none")
}

# Extrae las métricas de una confusionMatrix
extraer_metricas <- function(cm, auc_val = NA) {
  c(
    "Exactitud (Accuracy)"         = unname(cm$overall["Accuracy"]),
    "IC 95% inferior"              = unname(cm$overall["AccuracyLower"]),
    "IC 95% superior"              = unname(cm$overall["AccuracyUpper"]),
    "Tasa de no información (NIR)" = unname(cm$overall["AccuracyNull"]),
    "Valor P [Acc > NIR]"          = unname(cm$overall["AccuracyPValue"]),
    "Kappa de Cohen"               = unname(cm$overall["Kappa"]),
    "Valor P McNemar"              = unname(cm$overall["McnemarPValue"]),
    "Sensibilidad"                 = unname(cm$byClass["Sensitivity"]),
    "Especificidad"                = unname(cm$byClass["Specificity"]),
    "VPP (Pos Pred Value)"         = unname(cm$byClass["Pos Pred Value"]),
    "VPN (Neg Pred Value)"         = unname(cm$byClass["Neg Pred Value"]),
    "Exactitud balanceada"         = unname(cm$byClass["Balanced Accuracy"]),
    "F1"                           = unname(cm$byClass["F1"]),
    "AUC"                          = auc_val
  )
}

4.1 Construcción de los Modelos de Clasificación

4.1.1 Preparación de los datos

La variable respuesta es Alto si el ingreso laboral supera la mediana y Bajo en caso contrario.

# Copia para modelar (no modifica `datos`)
datos_m <- datos %>%
  mutate(
    sexo            = relevel(factor(sexo), ref = "Hombre"),
    nivel_educativo = factor(nivel_educativo,
                             levels = c("Primaria", "Secundaria", "Técnica/Tecnología",
                                        "Universitaria", "Postgrado")),
    tipo_empleado   = factor(tipo_empleado, levels = c("Particular", "Gobierno")),
    ingreso_laboral = factor(ingreso_laboral, levels = c(0, 1), labels = c("Bajo", "Alto"))
  ) %>%
  select(ingreso_laboral, sexo, edad, nivel_educativo, horas_trabajadas, tipo_empleado)

# Si algún nombre de nivel no coincide con tu base, aquí se detiene
stopifnot("Hay NA: revisa los nombres de los niveles de los factores" = !anyNA(datos_m))

datos_m %>%
  count(ingreso_laboral) %>%
  mutate(Porcentaje = round(100 * n / sum(n), 1)) %>%
  setNames(c("Nivel de ingreso", "Frecuencia", "Porcentaje (%)")) %>%
  tabla_verde("Tabla: Distribución de la variable respuesta", digits = 1)
Tabla: Distribución de la variable respuesta
Nivel de ingreso Frecuencia Porcentaje (%)
Bajo 1500 50
Alto 1500 50

4.1.2 Partición entrenamiento y prueba

set.seed(123)
idx   <- createDataPartition(datos_m$ingreso_laboral, p = 0.7, list = FALSE)
train <- datos_m[idx, ]
test  <- datos_m[-idx, ]

data.frame(Conjunto = c("Entrenamiento (70 %)", "Prueba (30 %)"),
           Observaciones = c(nrow(train), nrow(test))) %>%
  tabla_verde("Tabla: Tamaño de los conjuntos", digits = 0)
Tabla: Tamaño de los conjuntos
Conjunto Observaciones
Entrenamiento (70 %) 2100
Prueba (30 %) 900

4.2 Modelo KNN

4.2.1 Selección del número de vecinos (k)

ctrl <- trainControl(method = "cv", number = 10)

set.seed(123)
knn_fit <- train(
  ingreso_laboral ~ ., data = train,
  method     = "knn",
  preProcess = c("center", "scale"),   # estandariza las variables
  trControl  = ctrl,
  tuneGrid   = data.frame(k = seq(1, 151, by = 2))
)
k_opt <- knn_fit$bestTune$k

El valor óptimo de k fue 55, elegido por validación cruzada de 10 pliegues.

res_k <- knn_fit$results

ggplot(res_k, aes(x = k, y = Accuracy)) +
  geom_line(color = col_knn, linewidth = 1) +
  geom_point(color = col_knn, size = 1.5) +
  geom_point(data = subset(res_k, k == k_opt), color = col_acento, size = 5) +
  geom_vline(xintercept = k_opt, linetype = "dashed", color = col_acento) +
  annotate("text", x = k_opt + 5, y = max(res_k$Accuracy),
           label = paste0("k óptimo = ", k_opt), hjust = 0,
           color = col_knn, fontface = "bold") +
  labs(title = "Selección del k óptimo (validación cruzada, 10 pliegues)",
       x = "Número de vecinos (k)", y = "Exactitud (CV)") +
  tema_verde

La Figura muestra la exactitud obtenida por validación cruzada para distintos valores de k. Con un solo vecino la exactitud es baja (cercana a 0,63), lo que indica sobreajuste, porque el modelo se adapta demasiado al ruido de los datos de entrenamiento. A medida que k aumenta, la exactitud mejora rápidamente hasta cerca de k = 13 (aproximadamente 0,71) y luego se estabiliza, con pequeñas oscilaciones entre 0,70 y 0,715. El valor óptimo fue k = 55, con una exactitud en validación cruzada cercana a 0,714. Sin embargo, la diferencia entre este valor y otros de k superiores a 13 es muy pequeña (alrededor de 0,01), por lo que el desempeño del modelo es poco sensible a la elección exacta de k una vez que se supera ese punto. ### Evaluación en el conjunto de prueba

pred_knn <- predict(knn_fit, newdata = test)
prob_knn <- predict(knn_fit, newdata = test, type = "prob")[, "Alto"]
cm_knn   <- confusionMatrix(pred_knn, test$ingreso_laboral, positive = "Alto")

tabla_cm(cm_knn, paste0("Tabla: Matriz de confusión del KNN (k = ", k_opt, ")"))
Tabla: Matriz de confusión del KNN (k = 55)
Predicción Real: Bajo Real: Alto
Bajo 345 161
Alto 105 289
graficar_cm(cm_knn, paste0("Matriz de confusión – KNN (k = ", k_opt, ")"), color = col_knn)

La matriz de confusión se calculó sobre 900 observaciones (450 con ingreso bajo y 450 con ingreso alto). El modelo clasificó correctamente 345 trabajadores con ingreso bajo (verdaderos negativos) y 289 con ingreso alto (verdaderos positivos). Cometió 105 falsos positivos (trabajadores de ingreso bajo clasificados como altos) y 161 falsos negativos (trabajadores de ingreso alto clasificados como bajos).

A partir de la matriz se obtienen las siguientes métricas:

  • Exactitud: (345 + 289) / 900 = 0,704. El modelo clasifica bien al 70,4 % de los casos, un resultado consistente con el de la validación cruzada (0,714), lo que sugiere que no hay sobreajuste.
  • Sensibilidad: 289 / 450 = 0,642. Detecta al 64,2 % de los trabajadores con ingreso alto.
  • Especificidad: 345 / 450 = 0,767. Identifica correctamente al 76,7 % de los de ingreso bajo. Valor predictivo positivo: 289 / 394 = 0,733. Cuando el modelo predice ingreso alto, acierta el 73,3 % de las veces.

El KNN reconoce mejor a los trabajadores de ingreso bajo que a los de ingreso alto: su error más frecuente es dejar pasar casos altos (161 falsos negativos), más que dar falsas alarmas (105).

4.3 Modelo de regresión logística

4.3.1 Resultados del modelo

logit_fit <- glm(ingreso_laboral ~ sexo + edad + nivel_educativo +
                   horas_trabajadas + tipo_empleado,
                 data = train, family = binomial(link = "logit"))

coefs <- summary(logit_fit)$coefficients
ic    <- confint.default(logit_fit)

data.frame(
  Variable   = rownames(coefs),
  Coef       = coefs[, "Estimate"],
  Error_Est  = coefs[, "Std. Error"],
  z          = coefs[, "z value"],
  Valor_P    = coefs[, "Pr(>|z|)"],
  Odds_Ratio = exp(coefs[, "Estimate"]),
  OR_inf     = exp(ic[, 1]),
  OR_sup     = exp(ic[, 2]),
  row.names  = NULL
) %>%
  mutate(Valor_P = ifelse(Valor_P < 0.0001, "<0.0001", sprintf("%.4f", Valor_P))) %>%
  setNames(c("Variable", "Coeficiente", "Error est.", "z", "Valor P",
             "Odds Ratio", "OR IC 95% inf.", "OR IC 95% sup.")) %>%
  tabla_verde("Tabla: Resultados de la regresión logística", digits = 4)
Tabla: Resultados de la regresión logística
Variable Coeficiente Error est. z Valor P Odds Ratio OR IC 95% inf. OR IC 95% sup.
(Intercept) -4.1081 0.4274 -9.6113 <0.0001 0.0164 0.0071 0.0380
sexoMujer -0.4804 0.1075 -4.4705 <0.0001 0.6185 0.5011 0.7635
edad 0.0187 0.0044 4.2250 <0.0001 1.0189 1.0101 1.0278
nivel_educativoSecundaria 0.7692 0.1675 4.5923 <0.0001 2.1581 1.5542 2.9968
nivel_educativoTécnica/Tecnología 1.5951 0.1846 8.6405 <0.0001 4.9288 3.4324 7.0774
nivel_educativoUniversitaria 2.4240 0.1915 12.6569 <0.0001 11.2907 7.7572 16.4339
nivel_educativoPostgrado 5.5084 0.7341 7.5031 <0.0001 246.7438 58.5247 1040.2874
horas_trabajadas 0.0447 0.0070 6.3404 <0.0001 1.0457 1.0314 1.0603
tipo_empleadoGobierno 2.4289 0.3420 7.1022 <0.0001 11.3469 5.8046 22.1813

Todas las variables resultaron estadísticamente significativas (valor P < 0,0001). Las categorías de referencia son hombre, primaria y sector particular. Los odds ratio se interpretan manteniendo constantes las demás variables:

Sexo: el odds ratio de las mujeres es 0,62, es decir, que sus probabilidades relativas de tener ingreso alto son un 38 % menores que las de los hombres. Edad: cada año adicional aumenta los odds de ingreso alto en un 1,9 % (OR = 1,019), lo que equivale a cerca de un 21 % por cada 10 años. Nivel educativo: el efecto crece con la formación. Frente a primaria, los odds de ingreso alto son 2,2 veces mayores con secundaria, 4,9 veces con técnica o tecnología y 11,3 veces con formación universitaria. Postgrado: el odds ratio es de 246,7, pero su intervalo de confianza es extremadamente amplio (58,5 a 1040,3). Esto indica que la estimación es inestable, probablemente porque casi todos los trabajadores con postgrado tienen ingreso alto. Por eso lo correcto es interpretar que el postgrado se asocia con una probabilidad de ingreso alto muy superior, sin darle un valor exacto a esa magnitud. Horas trabajadas: cada hora adicional a la semana aumenta los odds en un 4,6 % (OR = 1,046), cerca de un 56 % por cada 10 horas. Tipo de empleado: los empleados del Gobierno tienen odds 11,3 veces mayores que los del sector particular. El intervalo (5,8 a 22,2) es amplio, lo cual es coherente con que en la muestra hay muy pocos empleados de Gobierno con ingreso bajo.

ll1 <- as.numeric(logLik(logit_fit))
ll0 <- as.numeric(logLik(update(logit_fit, . ~ 1)))
chi2 <- logit_fit$null.deviance - logit_fit$deviance
gl   <- logit_fit$df.null - logit_fit$df.residual

data.frame(
  Medida = c("Observaciones", "Devianza nula", "Devianza residual",
             "Chi2 razón de verosimilitud", "g.l.", "Valor P (LR)",
             "AIC", "R2 de McFadden"),
  Valor  = c(nobs(logit_fit), logit_fit$null.deviance, logit_fit$deviance,
             chi2, gl, pchisq(chi2, gl, lower.tail = FALSE),
             AIC(logit_fit), 1 - ll1 / ll0)
) %>%
  tabla_verde("Tabla: Ajuste global del modelo logit", digits = 4)
Tabla: Ajuste global del modelo logit
Medida Valor
Observaciones 2100.0000
Devianza nula 2911.2182
Devianza residual 2247.7033
Chi2 razón de verosimilitud 663.5149
g.l. 8.0000
Valor P (LR) 0.0000
AIC 2265.7033
R2 de McFadden 0.2279

El modelo se estimó con 2.100 observaciones de entrenamiento. La devianza se redujo de 2.911,2 (modelo nulo) a 2.247,7 (modelo ajustado), una diferencia de 663,5 que resulta significativa según la prueba de razón de verosimilitud con 8 grados de libertad (valor P < 0,0001). Esto significa que las variables incluidas aportan información relevante sobre el nivel de ingreso. El R² de McFadden es de 0,228, y como valores entre 0,2 y 0,4 suelen considerarse un buen ajuste en modelos logit, el resultado es aceptable.

4.3.2 Umbral de clasificación

p_train <- predict(logit_fit, newdata = train, type = "response")
p_test  <- predict(logit_fit, newdata = test,  type = "response")

# Desempeño según el umbral (sobre entrenamiento)
cutoffs <- seq(0.01, 0.99, by = 0.01)
curva <- do.call(rbind, lapply(cutoffs, function(cu) {
  pr   <- ifelse(p_train >= cu, "Alto", "Bajo")
  real <- as.character(train$ingreso_laboral)
  data.frame(
    cutoff        = cu,
    Sensibilidad  = sum(pr == "Alto" & real == "Alto") / sum(real == "Alto"),
    Especificidad = sum(pr == "Bajo" & real == "Bajo") / sum(real == "Bajo"),
    Exactitud     = mean(pr == real)
  )
}))

# Umbral óptimo por el índice de Youden
roc_train <- roc(train$ingreso_laboral, p_train,
                 levels = c("Bajo", "Alto"), direction = "<", quiet = TRUE)
co <- coords(roc_train, x = "best", best.method = "youden",
             ret = "threshold", transpose = FALSE)
umbral_opt <- as.numeric(co$threshold[1])

curva_larga <- pivot_longer(curva, cols = -cutoff, names_to = "Metrica", values_to = "Valor")

ggplot(curva_larga, aes(x = cutoff, y = Valor, color = Metrica)) +
  geom_line(linewidth = 1.1) +
  geom_vline(xintercept = umbral_opt, linetype = "dotted", color = "#064e2b") +
  annotate("text", x = umbral_opt + 0.02, y = 0.05, hjust = 0, color = "#064e2b",
           fontface = "bold", label = paste0("Umbral óptimo = ", round(umbral_opt, 3))) +
  scale_color_manual(values = c(Sensibilidad = col_knn, Especificidad = col_logit,
                                Exactitud = col_acento)) +
  labs(title = "Desempeño del modelo logístico según el umbral",
       x = "Umbral (cutoff)", y = "Valor de la métrica", color = NULL) +
  tema_verde + theme(legend.position = "bottom")

La figura muestra cómo cambian la sensibilidad, la especificidad y la exactitud del logit según el umbral. Al subir el umbral, el modelo exige más certeza para clasificar como ingreso alto, por lo que la especificidad aumenta y la sensibilidad cae. Ambas se cruzan cerca de 0,44, con un valor cercano a 0,71. El umbral óptimo según el índice de Youden fue 0,517, donde la exactitud alcanza su máximo (aproximadamente 0,71). Como la exactitud es casi plana entre 0,4 y 0,6, usar 0,5 o 0,517 produce resultados muy parecidos. El umbral podría ajustarse según el interés del problema: uno más bajo detectaría más casos de ingreso alto, a costa de más falsas alarmas.

4.3.3 Evaluación en el conjunto de prueba

pred_log_05  <- factor(ifelse(p_test >= 0.5,        "Alto", "Bajo"), levels = c("Bajo", "Alto"))
pred_log_opt <- factor(ifelse(p_test >= umbral_opt, "Alto", "Bajo"), levels = c("Bajo", "Alto"))

cm_log_05  <- confusionMatrix(pred_log_05,  test$ingreso_laboral, positive = "Alto")
cm_log_opt <- confusionMatrix(pred_log_opt, test$ingreso_laboral, positive = "Alto")

tabla_cm(cm_log_05,  "Tabla: Matriz de confusión del logit (umbral 0.5)")
Tabla: Matriz de confusión del logit (umbral 0.5)
Predicción Real: Bajo Real: Alto
Bajo 339 156
Alto 111 294
tabla_cm(cm_log_opt, paste0("Tabla: Matriz de confusión del logit (umbral óptimo = ",
                            round(umbral_opt, 3), ")"))
Tabla: Matriz de confusión del logit (umbral óptimo = 0.517)
Predicción Real: Bajo Real: Alto
Bajo 348 165
Alto 102 285
graficar_cm(cm_log_opt,
            paste0("Matriz de confusión – Logit (umbral ", round(umbral_opt, 3), ")"),
            color = "#0a8f3c")

Análisis de la Matriz de Confusión – Logit (Umbral óptimo: 0.517) En la primera gráfica se evalúa el rendimiento del modelo de Regresión Logística (Logit) ajustado con un umbral de decisión óptimo de \(0.517\), aplicado sobre un total de 900 observaciones de prueba:

Verdaderos Negativos (Bajo - Bajo): 348 casos. El modelo identificó correctamente 348 instancias pertenecientes a la categoría “Bajo”. Falsos Positivos (Bajo - Alto): 102 casos. Corresponde a observaciones cuyo valor real era “Bajo”, pero fueron clasificadas erróneamente como “Alto” por el modelo. Falsos Negativos (Alto - Bajo): 165 casos. Casos donde la categoría real era “Alto”, pero el modelo los clasificó de manera incorrecta como “Bajo”. Verdaderos Positivos (Alto - Alto): 285 casos. Instancias donde la clase real era “Alto” y el modelo predijo correctamente la categoría “Alto”. Interpretación:

Ajustar el umbral a 0.517 permite balancear la tasa de aciertos entre ambas clases. Se observa una mayor capacidad del modelo para clasificar correctamente los valores pertenecientes a la clase “Bajo” (348 aciertos) en comparación con los de la clase “Alto” (285 aciertos). El principal punto de error se concentra en los 165 falsos negativos, lo cual indica que hay una proporción considerable de casos positivos que el modelo no logra detectar.

4.4 Curvas ROC

roc_knn <- roc(test$ingreso_laboral, prob_knn,
               levels = c("Bajo", "Alto"), direction = "<", quiet = TRUE)
roc_log <- roc(test$ingreso_laboral, p_test,
               levels = c("Bajo", "Alto"), direction = "<", quiet = TRUE)

auc_knn <- as.numeric(auc(roc_knn)); ci_knn <- as.numeric(ci.auc(roc_knn))
auc_log <- as.numeric(auc(roc_log)); ci_log <- as.numeric(ci.auc(roc_log))

# Prueba de DeLong (muestras pareadas)
test_auc <- roc.test(roc_log, roc_knn, method = "delong")
p_delong <- test_auc$p.value

data.frame(
  Modelo = c("KNN", "Regresión logística"),
  AUC    = c(auc_knn, auc_log),
  inf    = c(ci_knn[1], ci_log[1]),
  sup    = c(ci_knn[3], ci_log[3])
) %>%
  setNames(c("Modelo", "AUC", "IC 95% inferior", "IC 95% superior")) %>%
  tabla_verde("Tabla: AUC de cada modelo en el conjunto de prueba")
Tabla: AUC de cada modelo en el conjunto de prueba
Modelo AUC IC 95% inferior IC 95% superior
KNN 0.770 0.740 0.801
Regresión logística 0.785 0.756 0.815
lista_roc <- list(roc_knn, roc_log)
names(lista_roc) <- c(paste0("KNN (AUC = ", round(auc_knn, 3), ")"),
                      paste0("Logit (AUC = ", round(auc_log, 3), ")"))

ggroc(lista_roc, legacy.axes = TRUE, linewidth = 1.2) +
  geom_abline(linetype = "dashed", color = "gray50") +
  scale_color_manual(values = c(col_knn, col_logit)) +
  labs(title = "Curvas ROC: KNN vs. Regresión logística",
       x = "1 − Especificidad", y = "Sensibilidad", color = "Modelo") +
  coord_equal() + tema_verde + theme(legend.position = "bottom")

Ambas curvas están claramente por encima de la diagonal, lo que indica que los dos modelos discriminan mejor que el azar. El AUC es de 0,770 para el KNN y de 0,785 para el logit, valores que se consideran de discriminación aceptable (entre 0,7 y 0,8). Las curvas son muy parecidas, aunque la del logit queda ligeramente por encima en la zona intermedia. La prueba de DeLong arrojó un valor P de 0,0321, menor que 0,05, por lo que la diferencia entre los AUC es estadísticamente significativa a favor del logit. Sin embargo, es una diferencia pequeña (0,015) en términos prácticos..

5 Comparación de los modelos

mets <- data.frame(
  Metrica = names(extraer_metricas(cm_knn)),
  KNN     = extraer_metricas(cm_knn, auc_knn),
  Logit05 = extraer_metricas(cm_log_05, auc_log),
  LogitOpt = extraer_metricas(cm_log_opt, auc_log)
)

mets %>%
  setNames(c("Métrica", "KNN", "Logit (umbral 0.5)", "Logit (umbral óptimo)")) %>%
  tabla_verde("Tabla: Comparación de las métricas de desempeño (conjunto de prueba)")
Tabla: Comparación de las métricas de desempeño (conjunto de prueba)
Métrica KNN Logit (umbral 0.5) Logit (umbral óptimo)
Exactitud (Accuracy) 0.704 0.703 0.703
IC 95% inferior 0.673 0.672 0.672
IC 95% superior 0.734 0.733 0.733
Tasa de no información (NIR) 0.500 0.500 0.500
Valor P [Acc > NIR] 0.000 0.000 0.000
Kappa de Cohen 0.409 0.407 0.407
Valor P McNemar 0.001 0.007 0.000
Sensibilidad 0.642 0.653 0.633
Especificidad 0.767 0.753 0.773
VPP (Pos Pred Value) 0.734 0.726 0.736
VPN (Neg Pred Value) 0.682 0.685 0.678
Exactitud balanceada 0.704 0.703 0.703
F1 0.685 0.688 0.681
AUC 0.770 0.785 0.785
data.frame(
  Modelo = c("KNN", "Logit (umbral 0.5)", "Logit (umbral óptimo)"),
  `Falsos positivos` = c(cm_knn$table["Alto", "Bajo"],
                         cm_log_05$table["Alto", "Bajo"],
                         cm_log_opt$table["Alto", "Bajo"]),
  `Falsos negativos` = c(cm_knn$table["Bajo", "Alto"],
                         cm_log_05$table["Bajo", "Alto"],
                         cm_log_opt$table["Bajo", "Alto"]),
  check.names = FALSE
) %>%
  tabla_verde("Tabla: Errores de clasificación por modelo", digits = 0)
Tabla: Errores de clasificación por modelo
Modelo Falsos positivos Falsos negativos
KNN 105 161
Logit (umbral 0.5) 111 156
Logit (umbral óptimo) 102 165
met_graf <- c("Exactitud (Accuracy)", "Sensibilidad", "Especificidad",
              "VPP (Pos Pred Value)", "VPN (Neg Pred Value)",
              "Exactitud balanceada", "AUC")
pos <- match(met_graf, mets$Metrica)

df_graf <- data.frame(
  Metrica = factor(rep(met_graf, 3), levels = met_graf),
  Modelo  = factor(rep(c("KNN", "Logit (0.5)", "Logit (umbral óptimo)"), each = length(met_graf)),
                   levels = c("KNN", "Logit (0.5)", "Logit (umbral óptimo)")),
  Valor   = c(mets$KNN[pos], mets$Logit05[pos], mets$LogitOpt[pos])
)

ggplot(df_graf, aes(x = Metrica, y = Valor, fill = Modelo)) +
  geom_col(position = position_dodge(width = 0.85), width = 0.8) +
  geom_text(aes(label = round(Valor, 2)),
            position = position_dodge(width = 0.85), vjust = -0.4,
            size = 3.5, color = "#064e2b") +
  scale_fill_manual(values = c(col_knn, col_logit, col_acento)) +
  coord_cartesian(ylim = c(0, 1.1)) +
  labs(title = "Comparación de Métricas: KNN vs. Regresión Logística",
       x = NULL, y = "Valor", fill = "Modelo") +
  tema_verde +
  theme(axis.text.x = element_text(angle = 25, hjust = 1),
        legend.position = "bottom")

En el gráfico de barras se realiza una comparativa directa entre tres configuraciones de modelos: KNN (verde oscuro), Logit con umbral estándar 0.5 (verde claro) y Logit con umbral óptimo (amarillo).

-Sensibilidad: El modelo Logit con umbral estándar (0.5) alcanza el valor más alto con \(0.65\) (\(65\%\)), seguido por KNN con \(0.64\) (\(64\%\)) y Logit con umbral óptimo con \(0.63\) (\(63\%\)). Esto implica que el modelo Logit (0.5) tiene una ligera ventaja en la detección de casos positivos verdaderos.

# Comparación principal: ambos modelos con el corte por defecto (0.5)
mets %>%
  filter(Metrica %in% c("Exactitud (Accuracy)", "Sensibilidad", "Especificidad",
                        "VPP (Pos Pred Value)", "F1", "Kappa de Cohen",
                        "Exactitud balanceada", "AUC")) %>%
  transmute(
    `Métrica`     = Metrica,
    KNN           = round(KNN, 3),
    `Logit (0.5)` = round(Logit05, 3),
    `Mejor modelo` = case_when(KNN > `Logit (0.5)` ~ "KNN",
                               KNN < `Logit (0.5)` ~ "Logit",
                               TRUE ~ "Empate")
  ) %>%
  tabla_verde("Tabla: Mejor modelo por métrica (ambos con umbral 0.5)")
Tabla: Mejor modelo por métrica (ambos con umbral 0.5)
Métrica KNN Logit (0.5) Mejor modelo
Exactitud (Accuracy) 0.704 0.703 KNN
Kappa de Cohen 0.409 0.407 KNN
Sensibilidad 0.642 0.653 Logit
Especificidad 0.767 0.753 KNN
VPP (Pos Pred Value) 0.734 0.726 KNN
Exactitud balanceada 0.704 0.703 KNN
F1 0.685 0.688 Logit
AUC 0.770 0.785 Logit
# ¿Difieren significativamente los aciertos de ambos modelos?
ok_knn <- pred_knn    == test$ingreso_laboral
ok_log <- pred_log_05 == test$ingreso_laboral
p_mcnemar <- mcnemar.test(table(ok_knn, ok_log))$p.value

La prueba de McNemar sobre los aciertos de ambos modelos arroja un valor P de 1. Si es mayor que 0,05, la diferencia de exactitud entre los dos modelos no es estadísticamente significativa.

6 Conclusiones

El objetivo principal de esta investigación consistió en comparar el desempeño predictivo de los algoritmos de K-Vecinos Más Cercanos (KNN) y Regresión Logística (Logit) para clasificar a los ocupados de Colombia en dos niveles de ingreso laboral (variable binaria: 1 = Ingresos superiores a la mediana, 0 = Ingresos menores o iguales a la mediana), buscando determinar cuál modelo ofrece el mejor equilibrio entre precisión predictiva y practicidad analítica.

A la luz de los resultados empíricos obtenidos en la muestra de evaluación (conjunto de prueba), se concluye que el modelo logró responder PARCIALMENTE al objetivo de la investigación. La justificación detallada de este veredicto, sustentada en la evidencia cuantitativa, se presenta a continuación:

7 Bibliografía