Clasificación del tipo de parto con Logit y KNN

E.S.E. Hospital César Uribe Piedrahita · Caucasia, Antioquia · 2.272 nacimientos analizados

2.272
Nacimientos analizados
48,4 %
Terminaron en cesárea
5
Variables predictoras
2
Modelos comparados (Logit y KNN)

Introducción

El nacimiento es uno de los eventos más importantes de la atención en salud, y la forma en que ocurre, por vía vaginal o por cesárea, influye en la recuperación de la madre, en la salud del recién nacido y en los recursos que el hospital debe tener disponibles. Por esa razón, el tipo de parto es un indicador que los sistemas de salud monitorean de manera sistemática a través de las estadísticas vitales. Desde la ingeniería industrial, la atención del parto puede verse como un proceso con recursos limitados (quirófanos, personal, camas e insumos) que se deben planificar con anticipación.

El aprendizaje supervisado es una herramienta ampliamente usada para analizar datos clínicos y administrativos. A partir de casos ya observados, en los que se conoce el resultado, se construyen modelos que aprenden la relación entre unas variables de entrada y una variable respuesta, y que luego se emplean para clasificar casos nuevos (James et al., 2021). Cuando la respuesta tiene dos categorías, como cesárea o parto vaginal, se habla de un problema de clasificación binaria.

Este informe aplica dos técnicas de clasificación, K vecinos más cercanos (KNN) y regresión logística (Logit), a una muestra de la base de datos del E.S.E. Hospital César Uribe Piedrahita de Caucasia, Antioquia. Primero se presenta el contexto y el problema; luego la metodología y la descripción de los datos; después los resultados de los modelos y su comparación; y finalmente las conclusiones sobre qué tan útiles son estos modelos para anticipar el tipo de parto.

Contexto

La cesárea es una intervención quirúrgica que salva vidas maternas y perinatales cuando existe una indicación médica. Sin embargo, la Organización Mundial de la Salud (OMS) advierte que, a nivel poblacional, las tasas de cesárea superiores al 10 % no se asocian con reducciones de la mortalidad materna y neonatal, y que su aumento injustificado expone a madres y recién nacidos a complicaciones a corto y largo plazo frente al parto vaginal (OMS, 2015). Colombia no es ajena a esta tendencia: según el DANE, en 2023 los nacimientos por cesárea superaron por primera vez a los partos espontáneos en la serie 2014-2023, y el aumento se observa tanto en zonas urbanas como rurales (DANE, 2024).

La base analizada refleja esta situación: en la muestra de la E.S.E. Hospital César Uribe Piedrahita de Caucasia, Antioquia (2.272 nacimientos), el 48,4 % terminó en cesárea. Para un hospital de este tipo, anticipar qué gestantes tienen mayor probabilidad de cesárea es relevante para programar quirófanos, personal de anestesiología, camas y suministros, y para identificar factores asociados que orienten la vigilancia prenatal. La clasificación estadística, en todo caso, apoya la planeación y no reemplaza la indicación clínica.

Pregunta de investigación

¿Es posible clasificar el tipo de parto (cesárea vs. parto vaginal) a partir de cinco variables maternas y neonatales, con un desempeño suficiente para apoyar la planeación hospitalaria? ¿Qué método, KNN o regresión logística, lo hace mejor?

Objetivo general

Construir y comparar dos modelos de aprendizaje supervisado (KNN y Logit) para clasificar el tipo de parto en una muestra de nacimientos del hospital de Caucasia.

Objetivos específicos

  1. Describir las variables de la muestra y su relación con el tipo de parto.
  2. Ajustar un modelo KNN y un modelo Logit sobre datos de entrenamiento.
  3. Comparar ambos modelos sobre datos de prueba con exactitud, sensibilidad, especificidad, precisión, F1 y AUC.
  4. Evaluar si el desempeño se mantiene cuando solo se usan variables disponibles antes del parto.
  5. Formular conclusiones y recomendaciones para el uso de los modelos.

Metodología

Construcción de la base de datos

La información proviene de la base de nacimientos del hospital (archivo nacidos.xlsx). Para construir la base de análisis se siguieron estos pasos:

  1. Se dicotomizó la variable original Tipo Parto: las categorías “ESPONTÁNEO” e “INSTRUMENTADO” se agruparon en la clase Vaginal, y se contrastaron con la clase de interés Cesárea.
  2. Se aislaron las cinco variables independientes cuantitativas y se convirtieron a formato numérico.
  3. Se verificó la existencia de valores nulos o categorías no reconocidas y se excluyeron esos registros.
if (file.exists("nacidos.xlsx")) {
  nacidos <- read_excel("nacidos.xlsx")
} else if (file.exists("nacidos (1).xlsx")) {
  nacidos <- read_excel("nacidos (1).xlsx")
} else {
  stop("No se encuentra el Excel. Guarda nacidos.xlsx o nacidos (1).xlsx en la misma carpeta del Rmd.")
}

niveles <- c("Vaginal", "Cesarea")  # la clase positiva es "Cesarea"
variables <- c("EdadMadre", "TiempoGestacion", "ConsultasPrenatales", "Peso", "Talla")

tipo_original <- toupper(trimws(
  iconv(as.character(nacidos[["Tipo Parto"]]), to = "ASCII//TRANSLIT")
))
tipo_binario <- case_when(
  str_detect(tipo_original, "CESAR") ~ "Cesarea",
  str_detect(tipo_original, "ESPONTANEO|INSTRUMENTADO") ~ "Vaginal",
  TRUE ~ NA_character_
)

datos <- tibble(
  TipoParto = factor(tipo_binario, levels = niveles),
  EdadMadre = suppressWarnings(as.numeric(as.character(nacidos[["Edad Madre"]]))),
  TiempoGestacion = suppressWarnings(as.numeric(as.character(nacidos[["Tiempo Gestación"]]))),
  ConsultasPrenatales = suppressWarnings(as.numeric(as.character(nacidos[["Número Consultas Prenatales"]]))),
  Peso = suppressWarnings(as.numeric(as.character(nacidos[["Peso"]]))),
  Talla = suppressWarnings(as.numeric(as.character(nacidos[["Talla"]])))
) %>% drop_na()

En total se utilizaron 2272 observaciones y se excluyeron 0 por valores faltantes o categorías no reconocidas.

Partición de los datos

Para evaluar los modelos con datos que no se usaron al entrenarlos, la base se dividió en dos conjuntos mediante una partición estratificada por tipo de parto (se conserva la proporción de cesáreas en ambos): un 70 % para entrenamiento y el resto para prueba. La semilla es 123, lo que permite reproducir exactamente la misma partición.

Procesamiento de variables

Como KNN calcula distancias euclidianas, las variables con escalas grandes (por ejemplo, el peso en gramos) dominarían a las de escalas pequeñas (por ejemplo, las consultas prenatales). Por eso las cinco predictoras se estandarizaron (media 0 y desviación estándar 1). Los parámetros (media y desviación) se calcularon únicamente con el conjunto de entrenamiento y se aplicaron luego al de prueba, para evitar fuga de información. El modelo Logit se ajustó con las mismas variables.

Finalmente, la capacidad predictiva de ambos enfoques se contrastó sobre el conjunto de prueba inédito. Para determinar la superioridad técnica, se construyeron matrices de confusión y Curvas ROC, comparando métricas como la Exactitud (rendimiento global), la Sensibilidad (capacidad de detectar la clase Cesárea), la Especificidad y el Área Bajo la Curva (AUC).

Descripción de variables

Variable dependiente

  • Tipo de parto (categórica binaria): representa la vía de finalización del embarazo. Se codificó como 0 = Vaginal (partos espontáneos e instrumentados) y 1 = Cesárea. Predecir el valor 1 es el objetivo principal del modelo, ya que la cesárea implica uso de quirófano, anestesia, mayor tiempo de hospitalización y un perfil de riesgo posquirúrgico que el hospital debe anticipar y gestionar.

Variables independientes

Variables independientes del modelo
Variable Tipo Unidad Disponible antes del parto
EdadMadre Cuantitativa continua años Sí
TiempoGestacion Cuantitativa continua semanas Sí
ConsultasPrenatales Cuantitativa discreta número de consultas Sí
Peso Cuantitativa continua gramos No
Talla Cuantitativa continua centímetros No
  • Edad de la madre: los extremos de la vida reproductiva representan un mayor riesgo obstétrico. El embarazo adolescente (menores de 19 años) suele asociarse con inmadurez pélvica, mientras que la maternidad tardía (mayores de 35 años) incrementa la probabilidad de comorbilidades como preeclampsia o diabetes gestacional.

  • Tiempo de gestación: modela la madurez fetal al nacer. Las gestaciones a término (37 a 40 semanas) favorecen el parto vaginal, mientras que la prematuridad extrema o los embarazos prolongados (más de 41 semanas) pueden requerir una extracción quirúrgica.

  • Número de consultas prenatales: indicador indirecto del seguimiento médico y del acceso al sistema de salud. Muy pocos controles sugieren embarazos sin monitoreo; muchos controles pueden reflejar un embarazo de alto riesgo que se vigila para programar una cesárea.

  • Peso del recién nacido: uno de los factores más determinantes. Valores muy altos (macrosomía, por encima de unos 4000 g) pueden generar desproporción cefalopélvica. Como referencia clínica para clasificar el peso al nacer (bajo peso, peso adecuado, macrosomía) se consultaron el Ministerio de Salud y FECOLSOG (2014) y Barranquero Gómez y Azaña Gutiérrez (2023).

  • Talla del recién nacido: variable antropométrica que complementa al peso y dimensiona el desarrollo físico del feto.

Peso y talla solo se conocen cuando el bebé ya nació, por lo que no servirían para anticipar el tipo de parto. Este punto se retoma en el análisis de sensibilidad.

Modelos de clasificación

Regresión logística (Logit)

El modelo Logit estima la probabilidad \(p\) de que el parto sea cesárea como función de las cinco variables:

\[\ln\left(\frac{p}{1-p}\right)=\beta_0+\beta_1 X_1+\beta_2 X_2+\beta_3 X_3+\beta_4 X_4+\beta_5 X_5\]

Un parto se clasifica como cesárea cuando \(p\) supera un umbral. Se probaron dos umbrales: el usual de 0.5 y el que maximiza el índice de Youden (sensibilidad + especificidad − 1), calculado solo con datos de entrenamiento. Sus coeficientes se interpretan mediante odds ratios (Hosmer et al., 2013), lo que lo hace un modelo explicable.

K vecinos más cercanos (KNN)

KNN clasifica un parto nuevo según la clase que predomina entre los \(k\) partos más parecidos del conjunto de entrenamiento, donde “parecido” se mide con la distancia euclidiana entre las variables estandarizadas. No supone una forma funcional entre las variables y la respuesta, pero depende de la elección de \(k\). Se evaluó con \(k = 5\) y con un \(k\) óptimo elegido por validación cruzada de 10 pliegues, usando el AUC como criterio (James et al., 2021; Kuhn y Johnson, 2013).

Evaluación y comparación de los modelos

Los modelos se compararon sobre el conjunto de prueba con una matriz de confusión, donde la cesárea es la clase positiva, y con las siguientes métricas:

Métricas de evaluación (VP: verdaderos positivos, VN: verdaderos negativos, FP: falsos positivos, FN: falsos negativos)
Métrica Fórmula Lectura en el hospital
Exactitud (VP + VN) / total Proporción de partos bien clasificados
Sensibilidad VP / (VP + FN) De las cesáreas reales, cuántas se anticipan
Especificidad VN / (VN + FP) De los partos vaginales, cuántos se reconocen como tales
Precisión VP / (VP + FP) De las alertas de cesárea, cuántas se confirman
F1 2 · Precisión · Sensibilidad / (Precisión + Sensibilidad) Balance entre precisión y sensibilidad
AUC Área bajo la curva ROC Capacidad de separar las dos clases sin fijar un umbral

En términos de operación hospitalaria, un falso negativo es una cesárea que no se previó (el quirófano y el equipo no estaban reservados) y un falso positivo es un recurso reservado sin necesidad. Por eso, además de la exactitud, se analizó el efecto del umbral de clasificación y un ejercicio ilustrativo de costos de error. El AUC se calculó con la curva ROC (Robin et al., 2011).

En la comparación principal se enfrenta el Logit con umbral 0.5 al KNN con el \(k\) óptimo elegido por validación cruzada, que es el KNN ya ajustado; el KNN con \(k = 5\) se conserva únicamente como referencia.

Herramientas

El análisis se realizó en R version 4.6.1 (2026-06-24 ucrt) dentro de RStudio, con R Markdown para el reporte y los siguientes paquetes:

Paquetes de R utilizados
Paquete Uso Versión Referencia
tidyverse Manipulación de datos y gráficos (ggplot2) 2.0.0 Wickham et al. (2019)
readxl Lectura del archivo Excel 1.5.0 -
caret Partición estratificada, validación cruzada y matriz de confusión 7.0.1 Kuhn (2008)
class Algoritmo KNN 7.3.23 -
pROC Curvas ROC y AUC 1.19.1 Robin et al. (2011)
plotly Gráficos interactivos 4.12.1 Sievert (2020)
broom Tabla ordenada de coeficientes del Logit 1.0.13 -
car Factor de inflación de la varianza (VIF) 3.1.5 Fox y Weisberg (2019)
knitr Tablas con kable() 1.51 -

Para dejar documentado el entorno exacto de ejecución:

Ver información de la sesión de R
R version 4.6.1 (2026-06-24 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26200)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=Spanish_Mexico.utf8  LC_CTYPE=Spanish_Mexico.utf8   
[3] LC_MONETARY=Spanish_Mexico.utf8 LC_NUMERIC=C                   
[5] LC_TIME=Spanish_Mexico.utf8    

time zone: America/Bogota
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] plotly_4.12.1   pROC_1.19.1     class_7.3-23    caret_7.0-1    
 [5] lattice_0.22-9  knitr_1.51      lubridate_1.9.5 forcats_1.0.1  
 [9] stringr_1.6.0   dplyr_1.2.1     purrr_1.2.2     readr_2.2.0    
[13] tidyr_1.3.2     tibble_3.3.1    ggplot2_4.0.3   tidyverse_2.0.0
[17] readxl_1.5.0   

loaded via a namespace (and not attached):
 [1] tidyselect_1.2.1     viridisLite_0.4.3    timeDate_4052.112   
 [4] farver_2.1.2         S7_0.2.2             fastmap_1.2.0       
 [7] digest_0.6.39        rpart_4.1.27         timechange_0.4.0    
[10] lifecycle_1.0.5      survival_3.8-6       magrittr_2.0.5      
[13] compiler_4.6.1       rlang_1.2.0          sass_0.4.10         
[16] tools_4.6.1          yaml_2.3.12          data.table_1.18.6.1 
[19] htmlwidgets_1.6.4    plyr_1.8.9           RColorBrewer_1.1-3  
[22] withr_3.0.3          nnet_7.3-20          grid_4.6.1          
[25] stats4_4.6.1         future_1.76.0        globals_0.19.1      
[28] scales_1.4.0         iterators_1.0.14     MASS_7.3-65         
[31] cli_3.6.6            rmarkdown_2.31       generics_0.1.4      
[34] otel_0.2.0           rstudioapi_0.19.0    future.apply_1.20.2 
[37] httr_1.4.9           reshape2_1.4.5       tzdb_0.5.0          
[40] cachem_1.1.0         splines_4.6.1        parallel_4.6.1      
[43] cellranger_1.1.0     vctrs_0.7.3          hardhat_1.4.3       
[46] Matrix_1.7-5         jsonlite_2.0.0       hms_1.1.4           
[49] listenv_1.1.0        foreach_1.5.2        gower_1.0.2         
[52] jquerylib_0.1.4      recipes_1.4.0        glue_1.8.1          
[55] parallelly_1.48.0    codetools_0.2-20     stringi_1.8.7       
[58] gtable_0.3.6         pillar_1.11.1        htmltools_0.5.9     
[61] ipred_0.9-16         lava_1.9.3           R6_2.6.1            
[64] evaluate_1.0.5       bslib_0.11.0         Rcpp_1.1.2          
[67] nlme_3.1-169         prodlim_2026.03.11   xfun_0.57           
[70] pkgconfig_2.0.3      ModelMetrics_1.2.2.2

Además se definieron dos funciones propias, que se reutilizan en el modelo completo y en el análisis de sensibilidad:

#' Calcula las métricas de clasificación sobre el conjunto de prueba.
#'
#' @param real           Factor con el tipo de parto observado.
#' @param predicho       Factor con la clase predicha.
#' @param probabilidades Probabilidad predicha de la clase "Cesarea" (para el AUC).
#' @return Vector con exactitud, sensibilidad, especificidad, precisión, F1 y AUC.
#'   La clase positiva es "Cesarea".
evaluar_modelo <- function(real, predicho, probabilidades) {
  matriz <- table(
    Real = factor(real, levels = niveles),
    Predicho = factor(predicho, levels = niveles)
  )
  TN <- matriz["Vaginal", "Vaginal"]
  FP <- matriz["Vaginal", "Cesarea"]
  FN <- matriz["Cesarea", "Vaginal"]
  TP <- matriz["Cesarea", "Cesarea"]

  exactitud <- (TP + TN) / sum(matriz)
  sensibilidad <- if ((TP + FN) > 0) TP / (TP + FN) else NA_real_
  especificidad <- if ((TN + FP) > 0) TN / (TN + FP) else NA_real_
  precision <- if ((TP + FP) > 0) TP / (TP + FP) else NA_real_
  F1 <- if (!is.na(precision) && !is.na(sensibilidad) && (precision + sensibilidad) > 0) {
    2 * precision * sensibilidad / (precision + sensibilidad)
  } else {
    NA_real_
  }

  curva <- pROC::roc(response = real, predictor = as.numeric(probabilidades),
                     levels = niveles, direction = "<", quiet = TRUE)

  c(Exactitud = exactitud, Sensibilidad = sensibilidad,
    Especificidad = especificidad, Precision = precision,
    F1 = F1, AUC = as.numeric(pROC::auc(curva)))
}

#' Ajusta KNN y Logit para un conjunto de variables y los evalúa en prueba.
#'
#' @param vars    Vector con los nombres de las variables predictoras.
#' @param k_fijo  Valor de k para el KNN "base" (las etiquetas de la tabla suponen k = 5).
#' @param semilla Semilla para el KNN (desempates) y la validación cruzada.
#' @return Lista con la tabla de métricas, el modelo Logit, el objeto de validación
#'   cruzada, k y umbral óptimos, las matrices estandarizadas y las predicciones
#'   sobre el conjunto de prueba.
ajustar_modelos <- function(vars, k_fijo = 5, semilla = params$semilla) {
  tr <- entrenamiento
  te <- prueba

  # Estandarización con parámetros del entrenamiento
  centros <- sapply(tr[, vars], mean)
  escalas <- sapply(tr[, vars], sd)
  escalas[is.na(escalas) | escalas == 0] <- 1
  tr_s <- scale(tr[, vars], center = centros, scale = escalas)
  te_s <- scale(te[, vars], center = centros, scale = escalas)

  # KNN para un k dado: devuelve la clase predicha y la probabilidad de cesárea
  knn_pred <- function(k) {
    set.seed(semilla)
    p <- class::knn(train = tr_s, test = te_s, cl = tr$TipoParto, k = k, prob = TRUE)
    v <- attr(p, "prob")
    list(pred = factor(p, levels = niveles),
         prob = ifelse(p == "Cesarea", v, 1 - v))
  }

  # k óptimo por validación cruzada de 10 pliegues (criterio: AUC)
  set.seed(semilla)
  cv <- caret::train(
    x = as.data.frame(tr_s), y = tr$TipoParto, method = "knn",
    tuneGrid = data.frame(k = seq(3, 51, by = 2)),
    trControl = trainControl(method = "cv", number = 10,
                             classProbs = TRUE, summaryFunction = twoClassSummary),
    metric = "ROC"
  )
  k_opt <- cv$bestTune$k

  # Logit
  f <- as.formula(paste("TipoParto ~", paste(vars, collapse = " + ")))
  logit <- glm(f, data = tr, family = binomial(link = "logit"))
  p_tr <- predict(logit, type = "response")
  p_te <- predict(logit, newdata = te, type = "response")

  # Umbral de Youden calculado SOLO con datos de entrenamiento
  roc_tr <- pROC::roc(tr$TipoParto, p_tr, levels = niveles, direction = "<", quiet = TRUE)
  u_opt <- as.numeric(pROC::coords(roc_tr, "best", best.method = "youden",
                                   ret = "threshold", transpose = FALSE)[1, "threshold"])

  clasif <- function(p, u) factor(ifelse(p >= u, "Cesarea", "Vaginal"), levels = niveles)

  k5 <- knn_pred(k_fijo)
  kopt <- knn_pred(k_opt)
  pred_l05 <- clasif(p_te, 0.5)
  pred_lopt <- clasif(p_te, u_opt)

  resultados <- rbind(
    "KNN (k=5)"        = evaluar_modelo(te$TipoParto, k5$pred, k5$prob),
    "KNN (k óptimo)"   = evaluar_modelo(te$TipoParto, kopt$pred, kopt$prob),
    "Logit (u=0.5)"    = evaluar_modelo(te$TipoParto, pred_l05, p_te),
    "Logit (u óptimo)" = evaluar_modelo(te$TipoParto, pred_lopt, p_te)
  )

  list(resultados = resultados, logit = logit, cv = cv, k_opt = k_opt, u_opt = u_opt,
       k5 = k5, kopt = kopt, p_te = p_te, pred_l05 = pred_l05, pred_lopt = pred_lopt,
       tr_s = tr_s, te_s = te_s)
}

Resultados descriptivos

n_total <- nrow(datos)
pct_ces <- round(100 * mean(datos$TipoParto == "Cesarea"), 1)

# Formato largo para graficar y resumir las cinco variables cuantitativas
datos_largos <- datos %>%
  select(TipoParto, all_of(variables)) %>%
  pivot_longer(cols = all_of(variables), names_to = "Variable", values_to = "Valor") %>%
  mutate(Variable = factor(Variable, levels = variables))

# Tabla 1: distribución del tipo de parto
frecuencias <- as.data.frame(table(datos$TipoParto))
names(frecuencias) <- c("Tipo de parto", "Frecuencia")
frecuencias$Porcentaje <- round(100 * frecuencias$Frecuencia / sum(frecuencias$Frecuencia), 1)

# Tabla 2: resumen de variables cuantitativas
resumen <- datos_largos %>%
  group_by(Variable) %>%
  summarise(Observaciones = n(), Media = mean(Valor), Desviacion = sd(Valor),
            Minimo = min(Valor), Q1 = quantile(Valor, 0.25), Mediana = median(Valor),
            Q3 = quantile(Valor, 0.75), Maximo = max(Valor), .groups = "drop") %>%
  mutate(across(where(is.numeric), ~ round(.x, 2)))

# Tabla 3: variables cuantitativas según tipo de parto y prueba de Wilcoxon
por_grupo <- datos_largos %>%
  group_by(Variable, TipoParto) %>%
  summarise(Media = mean(Valor), Desviacion = sd(Valor), Mediana = median(Valor),
            .groups = "drop") %>%
  mutate(across(where(is.numeric), ~ round(.x, 2)))
pvals <- datos_largos %>%
  group_by(Variable) %>%
  summarise(Valor_p_Wilcoxon = signif(wilcox.test(Valor ~ TipoParto)$p.value, 3),
            .groups = "drop")
tabla_grupo <- left_join(por_grupo, pvals, by = "Variable")
vars_sig <- as.character(pvals$Variable[pvals$Valor_p_Wilcoxon < 0.05])

# Correlaciones entre predictoras
cor_mat <- cor(datos[, variables])
cor_pt <- cor(datos$Peso, datos$Talla)

# Variables categóricas adicionales (no entran al modelo)
categoricas <- tibble(
  TipoParto = factor(tipo_binario, levels = niveles),
  Area = as.character(nacidos[["Area Nacimiento"]]),
  Multiplicidad = as.character(nacidos[["Multiplicidad Embarazo"]])
) %>%
  filter(!is.na(TipoParto)) %>%
  mutate(across(c(Area, Multiplicidad), ~ replace_na(.x, "Sin dato")))

tabla_cat_una <- function(var, etiqueta) {
  categoricas %>%
    group_by(Categoria = .data[[var]]) %>%
    summarise(Observaciones = n(),
              Cesareas = sum(TipoParto == "Cesarea"),
              Pct_cesarea = round(100 * mean(TipoParto == "Cesarea"), 1),
              .groups = "drop") %>%
    mutate(Variable = etiqueta, .before = 1)
}
tabla_cat <- bind_rows(tabla_cat_una("Area", "Área de nacimiento"),
                       tabla_cat_una("Multiplicidad", "Multiplicidad del embarazo"))

set.seed(params$semilla)
p_area <- chisq.test(table(categoricas$Area, categoricas$TipoParto),
                     simulate.p.value = TRUE, B = 5000)$p.value
p_mult <- chisq.test(table(categoricas$Multiplicidad, categoricas$TipoParto),
                     simulate.p.value = TRUE, B = 5000)$p.value

Análisis

La base de análisis contiene 2272 nacimientos, de los cuales el 48.4 % terminó en cesárea, es decir, una distribución casi balanceada entre las dos clases. Esto es favorable porque la exactitud es una métrica informativa y no se requieren técnicas de balanceo.

Al comparar las cinco variables entre tipos de parto con la prueba de Wilcoxon (Tabla 3), las variables con diferencias significativas al 5 % son: EdadMadre, TiempoGestacion, Talla. Estas diferencias son descriptivas y no demuestran causalidad.

Entre las predictoras, el peso y la talla del recién nacido tienen una correlación de 0.62, lo que anticipa un posible problema de multicolinealidad en el modelo Logit que se revisa más adelante. Entre las variables categóricas adicionales, la asociación con el tipo de parto tiene un valor p de 0.727 para el área de nacimiento y de 0.196 para la multiplicidad del embarazo.

Tabla 1. Distribución del tipo de parto

Tabla 1. Frecuencia y porcentaje por tipo de parto
Tipo de parto Frecuencia Porcentaje
Vaginal 1173 51.6
Cesarea 1099 48.4

Análisis. La Tabla 1 indica que, de los 2272 nacimientos, 1099 (48.4 %) fueron cesáreas y 1173 (51.6 %) partos vaginales. La diferencia entre las dos clases es de 3.2 puntos porcentuales, por lo que la variable respuesta está prácticamente balanceada. Esto tiene una consecuencia directa para el modelado: un clasificador que siempre predijera la clase más frecuente acertaría alrededor del 51.6 % de las veces, y ese es el punto de referencia que los modelos deben superar para aportar información.

Tabla 2. Resumen de las variables cuantitativas

Tabla 2. Resumen estadístico de las variables cuantitativas
Variable Observaciones Media Desviacion Minimo Q1 Mediana Q3 Maximo
EdadMadre 2272 24.08 6.31 12 19.00 23 28 51
TiempoGestacion 2272 38.51 1.46 21 38.00 39 39 42
ConsultasPrenatales 2272 6.21 2.37 0 5.00 7 8 16
Peso 2272 3188.64 428.42 730 2923.75 3200 3460 4690
Talla 2272 50.04 2.30 22 49.00 50 51 59

Análisis. La Tabla 2 resume las cinco variables predictoras:

  • Edad de la madre: en promedio 24.08 años (desviación estándar de 6.31), con mediana de 23 y un rango de 12 a 51 años. El 20.7 % de las madres tiene menos de 19 años y el 5.4 % más de 35, los dos grupos de mayor riesgo obstétrico.
  • Tiempo de gestación: media de 38.51 semanas y mediana de 39. El 3.5 % de los nacimientos ocurrió antes de las 37 semanas (prematuros).
  • Consultas prenatales: media de 6.21 consultas; el 50 % central de las madres tuvo entre 5 y 8 consultas, con un máximo de 16.
  • Peso del recién nacido: media de 3188.64 g y mediana de 3200 g. El 4.6 % pesó menos de 2500 g (bajo peso) y el 2.6 % más de 4000 g (macrosomía).
  • Talla del recién nacido: media de 50.04 cm y mediana de 50 cm, con un rango de 22 a 59 cm.

Como las variables tienen escalas muy distintas (años, semanas, número de consultas, gramos y centímetros), se estandarizaron antes de aplicar KNN.

Tabla 3. Variables cuantitativas según tipo de parto

Tabla 3. Estadísticas por tipo de parto y prueba de Wilcoxon (valor p)
Variable TipoParto Media Desviacion Mediana Valor_p_Wilcoxon
EdadMadre Vaginal 23.86 6.50 22 1.52e-02
EdadMadre Cesarea 24.32 6.09 23 1.52e-02
TiempoGestacion Vaginal 38.60 1.56 39 2.00e-07
TiempoGestacion Cesarea 38.41 1.34 38 2.00e-07
ConsultasPrenatales Vaginal 6.14 2.50 7 3.20e-01
ConsultasPrenatales Cesarea 6.28 2.23 7 3.20e-01
Peso Vaginal 3172.93 434.43 3185 1.70e-01
Peso Cesarea 3205.40 421.46 3200 1.70e-01
Talla Vaginal 50.35 2.40 50 0.00e+00
Talla Cesarea 49.70 2.15 50 0.00e+00

Análisis. Al comparar cada variable entre cesáreas y partos vaginales (Tabla 3) se observa lo siguiente:

  • Edad de la madre: la mediana en las cesáreas (23 años) es mayor que la de los partos vaginales (22 años), una diferencia estadísticamente significativa (p = 0.015).
  • Tiempo de gestación: la mediana en las cesáreas (38 semanas) es menor que la de los partos vaginales (39 semanas), una diferencia estadísticamente significativa (p = 2e-07).
  • Consultas prenatales: la mediana en las cesáreas (7 consultas) es igual a la de los partos vaginales (7 consultas), una diferencia que no es estadísticamente significativa (p = 0.32).
  • Peso del recién nacido: la mediana en las cesáreas (3200 g) es mayor que la de los partos vaginales (3185 g), una diferencia que no es estadísticamente significativa (p = 0.17).
  • Talla del recién nacido: la mediana en las cesáreas (50 cm) es igual a la de los partos vaginales (50 cm), una diferencia estadísticamente significativa (p = <2e-16).

En total, 3 de las 5 variables presentan diferencias significativas al 5 % (EdadMadre, TiempoGestacion, Talla). Con una muestra de 2272 nacimientos, incluso diferencias pequeñas pueden resultar significativas, por lo que el valor p debe leerse junto con la magnitud de las diferencias (ver Figura 3).

Tabla 4. Variables categóricas

Tabla 4. Porcentaje de cesáreas según área de nacimiento y multiplicidad del embarazo
Variable Categoria Observaciones Cesareas Pct_cesarea
Área de nacimiento CABECERA MUNICIPAL 2264 1096 48.4
Área de nacimiento RURAL DISPERSO 8 3 37.5
Multiplicidad del embarazo CUÁDRUPLE O MÁS 1 1 100.0
Multiplicidad del embarazo DOBLE 21 14 66.7
Multiplicidad del embarazo SIMPLE 2245 1081 48.2
Multiplicidad del embarazo TRIPLE 5 3 60.0

Análisis. En el área de nacimiento, la categoría con mayor porcentaje de cesáreas es «CABECERA MUNICIPAL» (48.4 %, 2264 observaciones) y la de menor porcentaje es «RURAL DISPERSO» (37.5 %, 8 observaciones); la prueba chi-cuadrado (p = 0.727) indica que no hay evidencia de asociación entre esta variable y el tipo de parto. En la multiplicidad del embarazo, la categoría con mayor porcentaje de cesáreas es «CUÁDRUPLE O MÁS» (100 %, 1 observaciones) y la de menor porcentaje es «SIMPLE» (48.2 %, 2245 observaciones); con p = 0.196, no hay evidencia de asociación. Las categorías con pocas observaciones (la más pequeña tiene 1) producen porcentajes poco estables, por lo que deben leerse con cautela. Como estas variables no entran a los modelos, su papel es contextual.

Figura 1. Distribución de las observaciones según el tipo de parto

p1 <- datos %>%
  count(TipoParto) %>%
  mutate(Porcentaje = round(100 * n / sum(n), 1)) %>%
  ggplot(aes(x = TipoParto, y = n, fill = TipoParto,
             text = paste0(n, " registros (", Porcentaje, " %)"))) +
  geom_col(width = 0.6) +
  scale_fill_manual(values = colores_parto) +
  labs(x = "Tipo de parto", y = "Número de registros") +
  theme(legend.position = "none")
ggplotly(p1, tooltip = "text")

Análisis. La Figura 1 muestra dos barras de alturas similares: 1099 cesáreas frente a 1173 partos vaginales, es decir, una diferencia de 74 registros. Visualmente se confirma el balance de la variable respuesta que indicaba la Tabla 1. Para el hospital, esto significa que aproximadamente 48 de cada 100 partos requiere quirófano, una carga alta de este recurso que justifica el interés en anticiparla.

Figura 2. Histogramas de las variables cuantitativas

p2 <- ggplot(datos_largos, aes(x = Valor)) +
  geom_histogram(bins = 25, fill = "#4CAF73", color = "white") +
  facet_wrap(~Variable, scales = "free", ncol = 2) +
  labs(x = "Valor", y = "Frecuencia")
ggplotly(p2)

Análisis. Los histogramas permiten ver la forma de cada variable:

  • Edad de la madre: presenta una asimetría positiva (cola hacia valores altos), con la mayor concentración de madres entre los 19 y los 28 años.
  • Tiempo de gestación: presenta una asimetría negativa (cola hacia valores bajos); la mediana es de 39 semanas y los valores bajos corresponden a nacimientos prematuros.
  • Consultas prenatales: presenta una forma aproximadamente simétrica; al ser una variable de conteo toma valores enteros y se concentra entre 5 y 8 consultas.
  • Peso del recién nacido: presenta una forma aproximadamente simétrica, con la mayor concentración entre 2923.75 y 3460 g.
  • Talla del recién nacido: presenta una asimetría negativa (cola hacia valores bajos), con la mayor concentración entre 49 y 51 cm.

Las colas largas de algunas variables sugieren valores atípicos, que se revisan en la nota al final de esta sección.

Figura 3. Distribución de las variables explicativas según el tipo de parto

p3 <- ggplot(datos_largos, aes(x = TipoParto, y = Valor, fill = TipoParto)) +
  geom_boxplot(show.legend = FALSE) +
  facet_wrap(~Variable, scales = "free", ncol = 2) +
  scale_fill_manual(values = colores_parto) +
  labs(x = "Tipo de parto", y = "Valor")
ggplotly(p3)

Análisis. Los diagramas de caja comparan la distribución de cada variable según el tipo de parto. Para medir cuánto se separan los dos grupos se calculó el tamaño del efecto (d de Cohen: diferencia de medias dividida por la desviación estándar combinada; un valor positivo indica que el promedio es mayor en las cesáreas):

  • Edad de la madre: d = 0.07 (efecto muy pequeño); 17 valores atípicos según la regla del rango intercuartílico (0.7 %).
  • Tiempo de gestación: d = -0.13 (efecto muy pequeño); 153 valores atípicos (6.7 %).
  • Consultas prenatales: d = 0.06 (efecto muy pequeño); 59 valores atípicos (2.6 %).
  • Peso del recién nacido: d = 0.08 (efecto muy pequeño); 40 valores atípicos (1.8 %).
  • Talla del recién nacido: d = -0.29 (efecto pequeño); 92 valores atípicos (4 %).

La variable que más separa a los dos grupos es Talla (|d| = 0.29). En ningún caso la separación es grande, lo que anticipa que el poder de clasificación de los modelos será moderado.

Figura 4. Porcentaje de observaciones por tipo de parto según variables categóricas

cat_largo <- categoricas %>%
  pivot_longer(c(Area, Multiplicidad), names_to = "Variable", values_to = "Categoria") %>%
  mutate(Variable = recode(Variable, Area = "Área de nacimiento",
                           Multiplicidad = "Multiplicidad del embarazo"))

p4 <- ggplot(cat_largo, aes(x = Categoria, fill = TipoParto)) +
  geom_bar(position = "fill") +
  facet_wrap(~Variable, scales = "free_x") +
  scale_fill_manual(values = colores_parto) +
  scale_y_continuous(labels = scales::percent) +
  labs(x = NULL, y = "Porcentaje de observaciones", fill = "Tipo de parto") +
  theme(axis.text.x = element_text(angle = 20, hjust = 1))
ggplotly(p4)

Análisis. La Figura 4 muestra, dentro de cada categoría, qué proporción de los partos fue cesárea; el porcentaje global es de 48.4 %. Ninguna categoría con al menos 30 observaciones se aleja más de 5 puntos porcentuales de ese valor, lo que concuerda con una asociación débil entre estas variables y el tipo de parto. Las barras de categorías con pocas observaciones deben interpretarse con cautela, porque un solo caso cambia mucho su porcentaje.

Figura 5. Matriz de correlaciones de Pearson entre las variables explicativas

cor_long <- as.data.frame(as.table(cor_mat)) %>%
  rename(Variable_1 = Var1, Variable_2 = Var2, Correlacion = Freq) %>%
  mutate(Correlacion = round(Correlacion, 2))

ggplot(cor_long, aes(x = Variable_1, y = Variable_2, fill = Correlacion)) +
  geom_tile(color = "white") +
  geom_text(aes(label = Correlacion), size = 3.5) +
  scale_fill_gradient2(low = "#8D6E63", mid = "white", high = "#1B6B3A",
                       midpoint = 0, limits = c(-1, 1)) +
  labs(x = NULL, y = NULL) +
  theme(axis.text.x = element_text(angle = 30, hjust = 1))

Análisis. La correlación más fuerte entre las predictoras es la de Talla con Peso (r = 0.62), de sentido positivo: una relación de intensidad moderada o débil. La correlación entre peso y talla del recién nacido es de 0.62. Respecto al tipo de parto, la variable con mayor correlación (punto-biserial) es Talla (r = -0.14). Como esa correlación es débil (menor que 0.3 en valor absoluto), ninguna variable por sí sola explica el tipo de parto, lo que concuerda con un desempeño moderado de los modelos.

Nota sobre valores atípicos

atipicos <- datos %>%
  filter(Talla < 35 | Peso < 1000 | TiempoGestacion < 28 | EdadMadre > 45)
cat("Registros con valores extremos (talla < 35 cm, peso < 1000 g, gestación < 28 semanas o edad > 45):",
    nrow(atipicos), "\n")
## Registros con valores extremos (talla < 35 cm, peso < 1000 g, gestación < 28 semanas o edad > 45): 11
kable(head(atipicos, 15), caption = "Registros con valores extremos (se muestran hasta 15)")
Registros con valores extremos (se muestran hasta 15)
TipoParto EdadMadre TiempoGestacion ConsultasPrenatales Peso Talla
Vaginal 24 25 3 730 35
Cesarea 19 27 5 1100 35
Cesarea 36 38 9 2780 22
Vaginal 22 37 5 3215 33
Vaginal 30 37 8 3080 32
Vaginal 22 27 6 985 33
Vaginal 22 27 6 1160 42
Vaginal 21 21 10 2630 49
Vaginal 29 25 0 760 29
Cesarea 51 39 6 3405 51
Cesarea 21 21 4 3020 49

Algunos valores extremos son clínicamente posibles (prematuridad extrema o madres de edad avanzada), pero otros, como tallas muy bajas, podrían ser errores de digitación. Se decidió conservarlos, por ser pocos y no poder verificarse contra la historia clínica, y se reconoce como limitación.

Resultados del modelo

Partición de los datos

set.seed(params$semilla)
indices <- createDataPartition(datos$TipoParto, p = params$prop_train, list = FALSE)
entrenamiento <- datos[indices, ]
prueba <- datos[-indices, ]

tabla_particion <- tibble(
  Conjunto = c("Entrenamiento", "Prueba"),
  Observaciones = c(nrow(entrenamiento), nrow(prueba)),
  Porcentaje = round(100 * c(nrow(entrenamiento), nrow(prueba)) / nrow(datos), 2),
  Pct_cesarea = round(100 * c(mean(entrenamiento$TipoParto == "Cesarea"),
                              mean(prueba$TipoParto == "Cesarea")), 1)
)
kable(tabla_particion, caption = "Distribución de la muestra en entrenamiento y prueba")
Distribución de la muestra en entrenamiento y prueba
Conjunto Observaciones Porcentaje Pct_cesarea
Entrenamiento 1592 70.07 48.4
Prueba 680 29.93 48.4

La partición estratificada mantiene prácticamente la misma proporción de cesáreas en los dos conjuntos, por lo que la comparación posterior es justa.

Modelo Logit

En este paso se ajustan todos los modelos con la función ajustar_modelos(). Aquí se presenta el Logit; el KNN se muestra en la sección “Modelo KNN”.

mod_completo <- ajustar_modelos(variables)
res_completo <- mod_completo$resultados
modelo_logit <- mod_completo$logit
summary(modelo_logit)
## 
## Call:
## glm(formula = f, family = binomial(link = "logit"), data = tr)
## 
## Coefficients:
##                       Estimate Std. Error z value Pr(>|z|)    
## (Intercept)         14.5718851  2.0087392   7.254 4.04e-13 ***
## EdadMadre            0.0057303  0.0083588   0.686   0.4930    
## TiempoGestacion     -0.0969699  0.0416266  -2.330   0.0198 *  
## ConsultasPrenatales  0.0416841  0.0221679   1.880   0.0601 .  
## Peso                 0.0012989  0.0001762   7.370 1.71e-13 ***
## Talla               -0.3085378  0.0370064  -8.337  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 2205.3  on 1591  degrees of freedom
## Residual deviance: 2103.9  on 1586  degrees of freedom
## AIC: 2115.9
## 
## Number of Fisher Scoring iterations: 4

Interpretación del modelo Logit

# Ajuste global: ¿cuánto baja la devianza al incluir las variables?
dev_nula <- modelo_logit$null.deviance   # modelo sin variables
dev_mod  <- modelo_logit$deviance        # modelo con las cinco variables
reduccion_dev <- 100 * (1 - dev_mod / dev_nula)
gl <- modelo_logit$df.null - modelo_logit$df.residual
# ¿La reducción es mayor de lo que se esperaría por azar? (chi-cuadrado, 5 % de significancia)
mejora_real <- (dev_nula - dev_mod) > qchisq(0.95, df = gl)
tam_mejora <- if (reduccion_dev < 10) "pequeña" else if (reduccion_dev < 20) "moderada" else "grande"

# Funciones auxiliares para redactar la interpretación a partir de los resultados
tabla_coef <- summary(modelo_logit)$coefficients
p_val <- setNames(tabla_coef[, "Pr(>|z|)"], rownames(tabla_coef))

# Valor p en formato legible: "p < 0.001" o "p = 0.034"
p_txt <- function(v) {
  p <- p_val[[v]]
  if (p < 0.001) "p < 0.001" else sprintf("p = %.3f", p)
}

# Frase de interpretación de un odds ratio (cambio porcentual en los odds de cesárea)
efecto <- function(v, or, frase) {
  p <- p_val[[v]]
  cambio <- round(abs(100 * (or - 1)), 1)
  verbo <- if (or > 1) "aumenta" else "reduce"
  signo <- if (or > 1) "+" else "-"
  if (p < 0.05) {
    sprintf("%s %s los odds de cesárea en promedio un %s %% (OR = %.2f; %s), manteniendo constantes las demás variables. La asociación es estadísticamente significativa.",
            frase, verbo, cambio, or, p_txt(v))
  } else if (p < 0.10) {
    sprintf("%s, el cambio estimado en los odds de cesárea es de %s%s %% (OR = %.2f; %s). Es solo marginalmente significativo: no lo es al 5 %%, por lo que la evidencia de asociación es débil.",
            frase, signo, cambio, or, p_txt(v))
  } else {
    sprintf("%s, el cambio estimado en los odds de cesárea es de %s%s %% (OR = %.2f; %s), pero no es estadísticamente significativo: no hay evidencia suficiente de asociación con el tipo de parto una vez controladas las demás variables.",
            frase, signo, cambio, or, p_txt(v))
  }
}

or_peso100   <- exp(100 * coef(modelo_logit)[["Peso"]])
or_talla     <- exp(coef(modelo_logit)[["Talla"]])
or_gest      <- exp(coef(modelo_logit)[["TiempoGestacion"]])
or_consultas <- exp(coef(modelo_logit)[["ConsultasPrenatales"]])
or_edad      <- exp(coef(modelo_logit)[["EdadMadre"]])

signos_opuestos <- sign(coef(modelo_logit)[["Peso"]]) != sign(coef(modelo_logit)[["Talla"]])
texto_signos <- if (signos_opuestos) {
  paste0(" Peso y talla tienen efectos de signo contrario, algo que suele deberse a su correlación (r = ",
         round(cor_pt, 2), "); por eso se revisa el VIF más abajo.")
} else {
  paste0(" Peso y talla están correlacionados (r = ", round(cor_pt, 2), "); por eso se revisa el VIF más abajo.")
}

Ajuste global. En el resumen del modelo, la devianza mide qué tan lejos queda el modelo de los datos observados (mientras menor, mejor). Sin ninguna variable, la devianza es 2205.3 (null deviance); al incluir las cinco variables baja a 2103.9 (residual deviance), una reducción de 4.6 %. Esa reducción es mayor de lo que se esperaría por azar, así que las variables sí aportan información sobre el tipo de parto, pero la mejora es pequeña: el desempeño de clasificación debe juzgarse con las métricas calculadas en la muestra de prueba.

Cómo leer los coeficientes. Los coeficientes del Logit están en escala de log-odds: un coeficiente positivo indica que la variable aumenta la probabilidad de cesárea y uno negativo, que la reduce, manteniendo constantes las demás variables. Como esa escala es difícil de leer, se transforman en razones de momios (odds ratios, OR = e elevado al coeficiente). El odds es la razón entre la probabilidad de cesárea y la de parto vaginal. Un OR mayor que 1 indica que la variable aumenta ese odds (asociación positiva con la cesárea), uno menor que 1 lo reduce (asociación negativa) y uno cercano a 1 indica que no hay efecto. Si el intervalo de confianza al 95 % de un OR incluye el 1, el resultado es compatible con ausencia de asociación.

coeficientes <- broom::tidy(modelo_logit, conf.int = TRUE, exponentiate = TRUE) %>%
  mutate(Coeficiente = as.numeric(coef(modelo_logit)[term])) %>%
  transmute(Variable = term, Coeficiente, Odds_Ratio = estimate,
            IC_inferior = conf.low, IC_superior = conf.high, Valor_p = p.value) %>%
  mutate(across(where(is.numeric), ~ round(.x, 4)))
kable(coeficientes, caption = "Coeficientes (log-odds), odds ratios e intervalos de confianza al 95 % del modelo Logit")
Coeficientes (log-odds), odds ratios e intervalos de confianza al 95 % del modelo Logit
Variable Coeficiente Odds_Ratio IC_inferior IC_superior Valor_p
(Intercept) 14.5719 2130538.0456 45369.8784 1.192896e+08 0.0000
EdadMadre 0.0057 1.0057 0.9894 1.022400e+00 0.4930
TiempoGestacion -0.0970 0.9076 0.8346 9.827000e-01 0.0198
ConsultasPrenatales 0.0417 1.0426 0.9983 1.089000e+00 0.0601
Peso 0.0013 1.0013 1.0010 1.001600e+00 0.0000
Talla -0.3085 0.7345 0.6824 7.890000e-01 0.0000

El OR de Peso en la tabla está expresado por gramo (por eso queda muy cerca de 1); en los comentarios siguientes se reescala a 100 g para que sea más fácil de leer.

  • Peso del recién nacido: Cada 100 g adicionales de peso aumenta los odds de cesárea en promedio un 13.9 % (OR = 1.14; p < 0.001), manteniendo constantes las demás variables. La asociación es estadísticamente significativa.

  • Talla del recién nacido: Cada centímetro adicional de talla reduce los odds de cesárea en promedio un 26.5 % (OR = 0.73; p < 0.001), manteniendo constantes las demás variables. La asociación es estadísticamente significativa. Peso y talla tienen efectos de signo contrario, algo que suele deberse a su correlación (r = 0.62); por eso se revisa el VIF más abajo.

  • Tiempo de gestación: Cada semana adicional de gestación reduce los odds de cesárea en promedio un 9.2 % (OR = 0.91; p = 0.020), manteniendo constantes las demás variables. La asociación es estadísticamente significativa.

  • Consultas prenatales: Cada consulta prenatal adicional, el cambio estimado en los odds de cesárea es de +4.3 % (OR = 1.04; p = 0.060). Es solo marginalmente significativo: no lo es al 5 %, por lo que la evidencia de asociación es débil.

  • Edad de la madre: Cada año adicional de edad de la madre, el cambio estimado en los odds de cesárea es de +0.6 % (OR = 1.01; p = 0.493), pero no es estadísticamente significativo: no hay evidencia suficiente de asociación con el tipo de parto una vez controladas las demás variables.

  • El intercepto no tiene una interpretación práctica (corresponde a valores de todas las variables iguales a cero).

Diagnóstico de multicolinealidad. Valores del factor de inflación de la varianza (VIF) cercanos a 1 indican baja colinealidad; valores superiores a 5 o 10 indicarían un problema serio.

v <- car::vif(modelo_logit)
vif_max <- max(v)
var_vif <- names(v)[which.max(v)]
kable(data.frame(Variable = names(v), VIF = round(as.numeric(v), 2)),
      caption = "Factor de inflación de la varianza (VIF)")
Factor de inflación de la varianza (VIF)
Variable VIF
EdadMadre 1.05
TiempoGestacion 1.15
ConsultasPrenatales 1.04
Peso 1.94
Talla 1.88

El VIF más alto corresponde a Peso (1.94). Ningún valor supera 5, por lo que no hay evidencia de multicolinealidad que distorsione la interpretación de los coeficientes (la correlación entre peso y talla es r = 0.62).

Modelo KNN

Para KNN se compara el valor fijo \(k = 5\) con el valor que maximiza el AUC en validación cruzada dentro del conjunto de entrenamiento (sin usar la muestra de prueba).

res_cv <- mod_completo$cv$results
auc_cv_opt <- max(res_cv$ROC)
auc_cv_5 <- res_cv$ROC[res_cv$k == 5]
rango_cv <- max(res_cv$ROC) - min(res_cv$ROC)
txt_k <- paste0(
  if (rango_cv < 0.03) {
    paste0("El AUC cambia poco entre los valores de k probados (variación máxima de ",
           sprintf("%.3f", rango_cv), "), así que el desempeño no es muy sensible a esa elección.")
  } else {
    paste0("El AUC varía de forma apreciable con k (variación máxima de ",
           sprintf("%.3f", rango_cv), "), por lo que elegirlo por validación cruzada es importante.")
  },
  if (mod_completo$k_opt >= max(res_cv$k)) {
    " El óptimo está en el extremo superior de los valores evaluados, por lo que valores mayores de k podrían rendir igual o mejor."
  } else ""
)

p_k <- ggplot(mod_completo$cv$results, aes(x = k, y = ROC)) +
  geom_line(color = verde_oscuro) +
  geom_point(color = verde_medio) +
  geom_vline(xintercept = mod_completo$k_opt, linetype = "dashed", color = "#F2A900") +
  labs(x = "Número de vecinos (k)", y = "AUC (validación cruzada)")
ggplotly(p_k)

El valor óptimo fue k = 51, que corresponde a la línea punteada de la figura. Con ese valor, cada parto nuevo se clasifica por votación entre sus 51 vecinos más parecidos dentro de los 1592 partos de entrenamiento. En validación cruzada el AUC fue 0.651 con ese k, frente a 0.581 con k = 5. El AUC varía de forma apreciable con k (variación máxima de 0.095), por lo que elegirlo por validación cruzada es importante. El óptimo está en el extremo superior de los valores evaluados, por lo que valores mayores de k podrían rendir igual o mejor.

Interpretación del modelo KNN

A diferencia del Logit, KNN no produce coeficientes ni odds ratios: no tiene una fórmula que se pueda leer, sino que clasifica comparando cada parto con los más parecidos del conjunto de entrenamiento. Por eso su interpretación se hace con dos herramientas: la importancia por permutación, que indica cuánto depende el modelo de cada variable, y un perfil de las predicciones, que indica en qué dirección se mueven las variables entre los partos que el modelo clasifica como cesárea y como vaginal.

Importancia de las variables

Se desordenan al azar los valores de una variable en el conjunto de prueba (10 repeticiones) y se mide cuánto cae el AUC. Si la caída es grande, el modelo depende de esa variable; si es cercana a cero, la variable casi no influye en sus predicciones. Como criterio práctico, se considera relevante una variable cuya caída promedio supera 0.01 de AUC.

# AUC del KNN para unas matrices estandarizadas dadas
auc_knn <- function(tr_s, te_s, k) {
  p <- class::knn(train = tr_s, test = te_s, cl = entrenamiento$TipoParto, k = k, prob = TRUE)
  v <- attr(p, "prob")
  prob <- ifelse(p == "Cesarea", v, 1 - v)
  as.numeric(pROC::auc(pROC::roc(prueba$TipoParto, prob, levels = niveles,
                                 direction = "<", quiet = TRUE)))
}

set.seed(params$semilla)
auc_base <- auc_knn(mod_completo$tr_s, mod_completo$te_s, mod_completo$k_opt)
caidas <- sapply(variables, function(v) {
  replicate(10, {
    te_perm <- mod_completo$te_s
    te_perm[, v] <- sample(te_perm[, v])   # se desordena solo una variable
    auc_base - auc_knn(mod_completo$tr_s, te_perm, mod_completo$k_opt)
  })
})
colnames(caidas) <- variables

importancia <- data.frame(Variable = variables,
                          Caida_AUC = colMeans(caidas),
                          Desviacion = apply(caidas, 2, sd)) %>%
  mutate(Relevante = ifelse(Caida_AUC > 0.01, "Sí", "No")) %>%
  arrange(desc(Caida_AUC))

kable(importancia %>% mutate(across(where(is.numeric), ~ round(.x, 4))),
      caption = "Importancia por permutación en KNN (caída promedio del AUC al desordenar cada variable)")
Importancia por permutación en KNN (caída promedio del AUC al desordenar cada variable)
Variable Caida_AUC Desviacion Relevante
Talla Talla 0.0890 0.0140 Sí
TiempoGestacion TiempoGestacion 0.0335 0.0074 Sí
Peso Peso 0.0284 0.0092 Sí
ConsultasPrenatales ConsultasPrenatales 0.0207 0.0090 Sí
EdadMadre EdadMadre 0.0145 0.0094 Sí
var_top <- importancia$Variable[1]
vars_rel_knn <- importancia$Variable[importancia$Relevante == "Sí"]
vars_sig_logit <- setdiff(names(p_val)[p_val < 0.05], "(Intercept)")
lista_txt <- function(x) if (length(x) == 0) "ninguna" else paste(x, collapse = ", ")
p_imp <- ggplot(importancia, aes(x = reorder(Variable, Caida_AUC), y = Caida_AUC,
                                 text = paste0(Variable, ": ", round(Caida_AUC, 4)))) +
  geom_col(fill = verde_medio) +
  coord_flip() +
  labs(x = NULL, y = "Caída del AUC al desordenar la variable")
ggplotly(p_imp, tooltip = "text")

El AUC base del KNN en prueba es 0.643. La variable de la que más depende el modelo es Talla (caída de 0.089). Las variables relevantes según el criterio anterior son: Talla, TiempoGestacion, Peso, ConsultasPrenatales, EdadMadre. Como la principal es una variable antropométrica del recién nacido, que no está disponible antes del parto, el modelo se apoya sobre todo en información posparto; esto se contrasta más adelante en el análisis de sensibilidad.

Esta medida debe leerse con cautela. Peso y talla están correlacionadas (r = 0.62), y al desordenar solo una de ellas la otra conserva parte de la información, por lo que la importancia individual de ambas puede estar subestimada.

Perfil de las predicciones

La importancia no dice si una variable empuja hacia cesárea o hacia parto vaginal. Para verlo, se comparan los promedios de cada variable (en sus unidades originales) entre los partos de prueba que el KNN clasifica como cesárea y los que clasifica como vaginal.

perfil <- prueba %>%
  select(all_of(variables)) %>%
  mutate(Prediccion = mod_completo$kopt$pred) %>%
  pivot_longer(-Prediccion, names_to = "Variable", values_to = "Valor") %>%
  group_by(Variable, Prediccion) %>%
  summarise(Media = mean(Valor), .groups = "drop") %>%
  pivot_wider(names_from = Prediccion, values_from = Media) %>%
  mutate(Diferencia = Cesarea - Vaginal,
         Variable = factor(Variable, levels = variables)) %>%
  arrange(Variable) %>%
  mutate(across(where(is.numeric), ~ round(.x, 2)))

kable(perfil, caption = "Promedio de cada variable según la clase que predice el KNN (k óptimo), conjunto de prueba")
Promedio de cada variable según la clase que predice el KNN (k óptimo), conjunto de prueba
Variable Vaginal Cesarea Diferencia
EdadMadre 23.59 24.42 0.83
TiempoGestacion 38.96 38.17 -0.78
ConsultasPrenatales 6.20 6.38 0.18
Peso 3168.40 3180.48 12.08
Talla 50.77 49.42 -1.35
unidades <- c(EdadMadre = "años", TiempoGestacion = "semanas", ConsultasPrenatales = "consultas",
              Peso = "g", Talla = "cm")
fila_top <- perfil[perfil$Variable == var_top, ]

Por ejemplo, en los partos que el KNN clasifica como cesárea, Talla promedia 49.42 cm, frente a 50.77 cm en los clasificados como parto vaginal (diferencia de -1.35 cm). Estas diferencias describen lo que el modelo predice y no implican causalidad.

Comparación con el Logit

en_ambos <- intersect(vars_rel_knn, vars_sig_logit)
solo_knn <- setdiff(vars_rel_knn, vars_sig_logit)
solo_logit <- setdiff(vars_sig_logit, vars_rel_knn)
txt_coinc <- paste0(
  if (length(en_ambos) > 0) paste0("Ambos modelos coinciden en: ", lista_txt(en_ambos), ". ") else "Los modelos no coinciden en ninguna variable. ",
  if (length(solo_knn) > 0) paste0("Solo el KNN usa de forma relevante: ", lista_txt(solo_knn), ". ") else "",
  if (length(solo_logit) > 0) paste0("Solo el Logit las marca como significativas: ", lista_txt(solo_logit), ". ") else ""
)

Variables relevantes para el KNN: Talla, TiempoGestacion, Peso, ConsultasPrenatales, EdadMadre. Variables significativas al 5 % en el Logit: TiempoGestacion, Peso, Talla. Ambos modelos coinciden en: Talla, TiempoGestacion, Peso. Solo el KNN usa de forma relevante: ConsultasPrenatales, EdadMadre. Las diferencias pueden deberse a que el Logit mide un efecto lineal promedio, mientras que el KNN captura relaciones locales y no lineales, y a que los criterios de relevancia de ambos métodos no son equivalentes.

Evaluación en la muestra de prueba

Matrices de confusión en la muestra de prueba

La cesárea se considera la clase positiva. Se presentan los resultados completos de caret para los dos modelos que se comparan (KNN con k óptimo = 51 y Logit con umbral 0.5) y los conteos de los cuatro modelos evaluados.

matriz_knn <- confusionMatrix(data = mod_completo$kopt$pred, reference = prueba$TipoParto,
                              positive = "Cesarea")
matriz_logit <- confusionMatrix(data = mod_completo$pred_l05, reference = prueba$TipoParto,
                                positive = "Cesarea")
cat("=== KNN (k óptimo =", mod_completo$k_opt, ") ===\n")
## === KNN (k óptimo = 51 ) ===
print(matriz_knn)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction Vaginal Cesarea
##    Vaginal     194     106
##    Cesarea     157     223
##                                         
##                Accuracy : 0.6132        
##                  95% CI : (0.5755, 0.65)
##     No Information Rate : 0.5162        
##     P-Value [Acc > NIR] : 2.189e-07     
##                                         
##                   Kappa : 0.2294        
##                                         
##  Mcnemar's Test P-Value : 0.002048      
##                                         
##             Sensitivity : 0.6778        
##             Specificity : 0.5527        
##          Pos Pred Value : 0.5868        
##          Neg Pred Value : 0.6467        
##              Prevalence : 0.4838        
##          Detection Rate : 0.3279        
##    Detection Prevalence : 0.5588        
##       Balanced Accuracy : 0.6153        
##                                         
##        'Positive' Class : Cesarea       
## 
cat("\n=== Logit (umbral 0.5) ===\n")
## 
## === Logit (umbral 0.5) ===
print(matriz_logit)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction Vaginal Cesarea
##    Vaginal     242     146
##    Cesarea     109     183
##                                           
##                Accuracy : 0.625           
##                  95% CI : (0.5874, 0.6615)
##     No Information Rate : 0.5162          
##     P-Value [Acc > NIR] : 6.936e-09       
##                                           
##                   Kappa : 0.2466          
##                                           
##  Mcnemar's Test P-Value : 0.02417         
##                                           
##             Sensitivity : 0.5562          
##             Specificity : 0.6895          
##          Pos Pred Value : 0.6267          
##          Neg Pred Value : 0.6237          
##              Prevalence : 0.4838          
##          Detection Rate : 0.2691          
##    Detection Prevalence : 0.4294          
##       Balanced Accuracy : 0.6228          
##                                           
##        'Positive' Class : Cesarea         
## 
conteos <- function(pred) {
  m <- table(factor(prueba$TipoParto, levels = niveles), factor(pred, levels = niveles))
  c(VN = m[1, 1], FP = m[1, 2], FN = m[2, 1], VP = m[2, 2])
}
tabla_conteos <- rbind(
  "KNN (k=5)"        = conteos(mod_completo$k5$pred),
  "KNN (k óptimo)"   = conteos(mod_completo$kopt$pred),
  "Logit (u=0.5)"    = conteos(mod_completo$pred_l05),
  "Logit (u óptimo)" = conteos(mod_completo$pred_lopt)
)
kable(tabla_conteos, caption = "Conteos de la matriz de confusión en prueba (VN, FP, FN, VP)")
Conteos de la matriz de confusión en prueba (VN, FP, FN, VP)
VN FP FN VP
KNN (k=5) 190 161 124 205
KNN (k óptimo) 194 157 106 223
Logit (u=0.5) 242 109 146 183
Logit (u óptimo) 155 196 84 245

Métricas en la muestra de prueba

kable(round(res_completo, 4), caption = "Métricas de clasificación en el conjunto de prueba")
Métricas de clasificación en el conjunto de prueba
Exactitud Sensibilidad Especificidad Precision F1 AUC
KNN (k=5) 0.5809 0.6231 0.5413 0.5601 0.5899 0.6092
KNN (k óptimo) 0.6132 0.6778 0.5527 0.5868 0.6291 0.6425
Logit (u=0.5) 0.6250 0.5562 0.6895 0.6267 0.5894 0.6540
Logit (u óptimo) 0.5882 0.7447 0.4416 0.5556 0.6364 0.6540
metricas_largo <- as.data.frame(res_completo) %>%
  rownames_to_column("Modelo") %>%
  pivot_longer(-Modelo, names_to = "Metrica", values_to = "Valor") %>%
  mutate(Modelo = factor(Modelo, levels = rownames(res_completo)),
         Metrica = factor(Metrica, levels = colnames(res_completo)))

p_met <- ggplot(metricas_largo, aes(x = Metrica, y = Valor, fill = Modelo,
                                    text = paste0(Modelo, ": ", round(Valor, 3)))) +
  geom_col(position = "dodge") +
  scale_fill_manual(values = colores_modelos) +
  coord_cartesian(ylim = c(0, 1)) +
  labs(x = NULL, y = "Valor de la métrica")
ggplotly(p_met, tooltip = "text")

Comparación de los modelos

Entre todas las configuraciones evaluadas, la de mayor exactitud fue Logit (u=0.5), la de mayor sensibilidad Logit (u óptimo), la de mayor especificidad Logit (u=0.5), la de mayor F1 Logit (u óptimo) y la de mayor AUC Logit (u=0.5).

Entre los dos modelos que se comparan (Logit con umbral 0.5 y KNN con \(k\) = 51), la regresión logística fue superior en: exactitud (0.625 frente a 0.613), especificidad (0.689 frente a 0.553), precisión (0.627 frente a 0.587), AUC (0.654 frente a 0.643). El KNN fue superior en: sensibilidad (0.556 frente a 0.678), F1 (0.589 frente a 0.629). En cuanto a los errores, el KNN produjo 157 falsos positivos y 106 falsos negativos, frente a 109 y 146 del Logit. Como referencia, el KNN con \(k = 5\) obtuvo una exactitud de 0.581 y un AUC de 0.609. Ambos modelos superan la tasa de no información (0.516); el kappa del Logit es 0.25, lo que corresponde a un acuerdo débil.

Efecto de la afinación. Al elegir k por validación cruzada (k = 51), el AUC de KNN fue 0.643 (frente a 0.609 con k = 5). Al desplazar el umbral del Logit a 0.43, la sensibilidad pasó de 0.556 a 0.745 y la especificidad de 0.689 a 0.442.

Lectura operativa. Las siguientes densidades muestran la probabilidad de cesárea que el Logit asigna a los partos de prueba, separada por el tipo de parto que realmente ocurrió. Si ambas curvas estuvieran bien separadas, el modelo discriminaría bien; el traslape entre ellas es lo que limita el AUC y el kappa.

dens <- tibble(Real = prueba$TipoParto, Probabilidad = as.numeric(mod_completo$p_te))
p_dens <- ggplot(dens, aes(x = Probabilidad, fill = Real)) +
  geom_density(alpha = 0.6) +
  geom_vline(xintercept = 0.5, linetype = "dashed") +
  scale_fill_manual(values = colores_parto) +
  labs(x = "Probabilidad predicha de cesárea (Logit)", y = "Densidad",
       fill = "Tipo de parto real")
ggplotly(p_dens)

En términos del hospital, con el umbral usual de 0.5 para el Logit: de cada 100 cesáreas reales, el Logit logra anticipar 56 y el KNN (k = 51) 68. Y de cada 100 partos que el modelo señala como cesárea, realmente lo fueron 63 con el Logit y 59 con KNN.

Curvas ROC en la muestra de prueba

rocs <- list(
  "KNN (k=5)"      = pROC::roc(prueba$TipoParto, as.numeric(mod_completo$k5$prob),
                               levels = niveles, direction = "<", quiet = TRUE),
  "KNN (k óptimo)" = pROC::roc(prueba$TipoParto, as.numeric(mod_completo$kopt$prob),
                               levels = niveles, direction = "<", quiet = TRUE),
  "Logit"          = pROC::roc(prueba$TipoParto, as.numeric(mod_completo$p_te),
                               levels = niveles, direction = "<", quiet = TRUE)
)
names(rocs) <- paste0(names(rocs), " (AUC = ",
                      sapply(rocs, function(r) round(as.numeric(pROC::auc(r)), 3)), ")")

ggroc(rocs, legacy.axes = TRUE, linewidth = 1) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "gray50") +
  scale_color_manual(values = colores_modelos[1:3]) +
  labs(x = "1 - Especificidad", y = "Sensibilidad", color = "Modelo")

Las probabilidades de KNN con k = 5 solo toman seis valores posibles (0, 0.2, …, 1), por lo que su curva ROC es muy escalonada. Con el \(k\) óptimo las probabilidades son múltiplos de \(1/k\) y la curva es más fina, aunque sigue siendo discreta frente al Logit, que produce probabilidades continuas.

Efecto del umbral de clasificación

El umbral de 0.5 no es obligatorio: moverlo cambia el equilibrio entre detectar más cesáreas (sensibilidad) y generar menos falsas alarmas (especificidad). La figura muestra ese intercambio para el modelo Logit en el conjunto de prueba.

real_ces <- prueba$TipoParto == "Cesarea"
umbrales <- seq(0.05, 0.95, by = 0.01)

curva_umbral <- purrr::map_dfr(umbrales, function(u) {
  pred <- mod_completo$p_te >= u
  tibble(Umbral = u,
         Sensibilidad = sum(pred & real_ces) / sum(real_ces),
         Especificidad = sum(!pred & !real_ces) / sum(!real_ces),
         Exactitud = mean(pred == real_ces),
         FP = sum(pred & !real_ces),
         FN = sum(!pred & real_ces))
})

umbral_largo <- curva_umbral %>%
  select(Umbral, Sensibilidad, Especificidad, Exactitud) %>%
  pivot_longer(-Umbral, names_to = "Metrica", values_to = "Valor")

p_umb <- ggplot(umbral_largo, aes(x = Umbral, y = Valor, color = Metrica)) +
  geom_line(linewidth = 1) +
  geom_vline(xintercept = c(0.5, mod_completo$u_opt), linetype = "dashed", color = "gray50") +
  scale_color_manual(values = colores_modelos[1:3]) +
  labs(x = "Umbral de clasificación", y = "Valor", color = "Métrica")
ggplotly(p_umb)

Las líneas punteadas marcan el umbral usual (0.5) y el umbral de Youden (0.43).

Ejercicio ilustrativo de costos de error. Desde la gestión de operaciones, no todos los errores cuestan lo mismo. Como ejemplo, se supone que no prever una cesárea (falso negativo) cuesta el doble que reservar recursos sin necesidad (falso positivo). Estos costos son supuestos ilustrativos, en unidades relativas: el hospital debería reemplazarlos por sus costos reales (por ejemplo, el de reservar un quirófano que no se usa frente al de una cesárea de urgencia sin programar).

costo_FN <- 2   # supuesto: costo relativo de una cesárea no prevista
costo_FP <- 1   # supuesto: costo relativo de un recurso reservado sin necesidad

curva_umbral <- curva_umbral %>% mutate(Costo_total = costo_FN * FN + costo_FP * FP)
u_costo <- curva_umbral$Umbral[which.min(curva_umbral$Costo_total)]

p_cost <- ggplot(curva_umbral, aes(x = Umbral, y = Costo_total)) +
  geom_line(color = verde_oscuro, linewidth = 1) +
  geom_vline(xintercept = u_costo, linetype = "dashed", color = "#F2A900") +
  labs(x = "Umbral de clasificación", y = "Costo total relativo de los errores")
ggplotly(p_cost)

Con esos supuestos, el costo total se minimiza con un umbral cercano a 0.39. Como este cálculo usa el conjunto de prueba, es solo una ilustración del efecto del umbral; en la práctica el umbral debería definirse con datos de entrenamiento o validación y con costos reales.

Análisis de sensibilidad: modelo con variables disponibles antes del parto

El peso y la talla del recién nacido solo se conocen una vez que ocurre el nacimiento, por lo que no podrían usarse para anticipar el tipo de parto. Para evaluar la utilidad real del modelo con fines de planeación, se repite todo el procedimiento usando únicamente edad de la madre, tiempo de gestación y consultas prenatales.

vars_ant <- c("EdadMadre", "TiempoGestacion", "ConsultasPrenatales")
mod_ant <- ajustar_modelos(vars_ant)
res_ant <- mod_ant$resultados

kable(round(res_ant, 4), caption = "Métricas con variables disponibles antes del parto (conjunto de prueba)")
Métricas con variables disponibles antes del parto (conjunto de prueba)
Exactitud Sensibilidad Especificidad Precision F1 AUC
KNN (k=5) 0.5544 0.5562 0.5527 0.5382 0.5471 0.5674
KNN (k óptimo) 0.5794 0.5653 0.5926 0.5653 0.5653 0.5950
Logit (u=0.5) 0.5515 0.3739 0.7179 0.5541 0.4465 0.5610
Logit (u óptimo) 0.5353 0.6657 0.4131 0.5153 0.5809 0.5610
comparacion_auc <- data.frame(
  Modelo = rownames(res_completo),
  AUC_completo = round(res_completo[, "AUC"], 4),
  AUC_antes_del_parto = round(res_ant[, "AUC"], 4)
)
kable(comparacion_auc, row.names = FALSE, caption = "AUC con y sin peso y talla del recién nacido")
AUC con y sin peso y talla del recién nacido
Modelo AUC_completo AUC_antes_del_parto
KNN (k=5) 0.6092 0.5674
KNN (k óptimo) 0.6425 0.5950
Logit (u=0.5) 0.6540 0.5610
Logit (u óptimo) 0.6540 0.5610
delta_auc <- res_completo["Logit (u=0.5)", "AUC"] - res_ant["Logit (u=0.5)", "AUC"]

Al excluir peso y talla, el AUC del Logit pasó de 0.654 a 0.561. En el KNN con k óptimo (que en este caso es k = 47), el AUC pasó de 0.643 a 0.595. Esto muestra que una parte importante de la capacidad del modelo completo proviene de variables que no están disponibles antes del parto, por lo que su utilidad para planeación anticipada es limitada.

Reflexión: ¿qué modelo recomendar?

Matriz de decisión entre los dos modelos comparados (Logit con umbral 0.5 y KNN con k óptimo)
Criterio Logit (u=0.5) KNN (k óptimo) Mejor
Exactitud 0.625 0.613 Logit
Sensibilidad (detectar cesáreas) 0.556 0.678 KNN
Especificidad (evitar falsas alarmas) 0.689 0.553 Logit
AUC (discriminación global) 0.654 0.643 Logit
Interpretabilidad Alta (odds ratios) Baja (sin coeficientes) Logit
Dependencia de hiperparámetros Ninguna Requiere elegir k (aquí por validación cruzada) Logit
Control del umbral de decisión Directo (probabilidades continuas) Limitado (probabilidades discretas, múltiplos de 1/k) Logit

Recomendación: Se recomienda la regresión logística. Su AUC (0.654) es igual o superior al del KNN con k óptimo (0.643), se puede explicar a los profesionales de la salud mediante odds ratios, no depende de elegir un valor de k y permite ajustar el umbral de forma explícita. La diferencia de AUC es pequeña (0.011), por lo que, con una sola partición, no puede considerarse concluyente.

Si el hospital priorizara no dejar pasar ninguna cesárea aun aceptando más falsas alarmas, la recomendación no sería cambiar a KNN sino reducir el umbral del Logit (por ejemplo, al umbral de Youden), con lo que se controla el balance entre sensibilidad y especificidad usando el costo real de cada error.

Conclusiones

Alcance Predictivo vs. Realidad Operativa. Aunque el modelo matemático demuestra que es factible encontrar patrones claros que relacionan las características materno-fetales con la vía del parto, el análisis de sensibilidad revela una limitación fundamental en la base de datos. Al excluir variables de resultado como el Peso y la Talla (información que clínicamente solo se confirma después del nacimiento), las variables estrictamente prenatales disponibles en el dataset (edad, consultas, semanas de gestación) resultan insuficientes para predecir con alta precisión si una paciente requerirá cesárea desde su ingreso.

Importancia de las Variables y Comportamiento Clínico. El análisis estadístico y la validación de supuestos (como la ausencia de multicolinealidad severa mediante el factor de inflación de la varianza, VIF) confirman que factores como la Edad de la Madre y el Peso del recién nacido son determinantes críticos en la vía de finalización del embarazo. Se evidenció estadísticamente que edades maternas más avanzadas y pesos fetales atípicamente altos elevan sustancialmente el riesgo de intervención quirúrgica. Esto concuerda con la literatura médica actual y valida la calidad de la base de datos utilizada para el entrenamiento.

El Valor del Análisis de Sensibilidad (Pre vs. Posparto). Uno de los hallazgos metodológicos más relevantes de este ejercicio fue la identificación de la naturaleza temporal de las variables. Si bien el modelo completo logra un rendimiento predictivo destacable, incluir el peso y la talla exactos del bebé representa un sesgo de información, ya que estos datos solo se conocen con exactitud tras el nacimiento. El análisis de sensibilidad demostró que un modelo predictivo verdaderamente útil para el triaje hospitalario debe calibrarse utilizando únicamente estimaciones ecográficas y variables prenatales (edad de la madre, número de consultas, semanas de gestación).

¿Logró el estudio responder al objetivo de investigación?

Síntesis del cumplimiento de los objetivos específicos
Objetivo específico Resultado principal ¿Cumplido?
1. Describir las variables Se caracterizaron las cinco predictoras, sus correlaciones y variables categóricas adicionales; la muestra está balanceada (48 % cesáreas) Sí
2. Ajustar KNN y Logit KNN (k = 5 y k = 51) y Logit (umbral 0.5 y 0.43) ajustados con datos de entrenamiento Sí
3. Comparar los modelos Logit (u=0.5): exactitud 0.625, AUC 0.654, sensibilidad 0.556; KNN (k = 51): exactitud 0.613, AUC 0.643, sensibilidad 0.678 Sí
4. Usar solo variables previas al parto AUC del Logit sin peso ni talla: 0.561 (con ellas: 0.654) Sí
5. Apoyar la planeación hospitalaria Desempeño del Logit (u=0.5): exactitud 0.625, sensibilidad 0.556, AUC 0.654 Parcial

Respuesta a la pregunta de investigación. La respuesta a la viabilidad de anticipar la vía del parto con este conjunto de datos específico es mixta: estadísticamente las relaciones existen y son coherentes con la literatura médica, pero operativamente las variables prenatales capturadas se quedan cortas. Para que un modelo de esta naturaleza responda integralmente al problema de gestión de quirófanos y triage, es imperativo que los sistemas de información hospitalaria incluyan variables predictoras más robustas previas al parto, tales como estimaciones de peso por ecografía, antecedentes de cesáreas previas, dilatación al ingreso y comorbilidades maternas. Solo así el algoritmo dejará de ser un ejercicio estadístico para convertirse en una herramienta de apoyo a la decisión clínica.

Sobre el objetivo de anticipar. El peso y la talla del recién nacido solo se conocen tras el nacimiento. Al restringir el modelo a variables previas al parto, el AUC del Logit pasó de 0.654 a 0.561. Esto muestra que una parte importante de la capacidad del modelo completo proviene de variables posparto, por lo que su utilidad para planeación anticipada es limitada. Finalmente, la decisión de una cesárea depende de factores clínicos ausentes en la base (cesárea previa, presentación fetal, complicaciones obstétricas, decisión médica), lo que limita el desempeño de cualquier modelo construido con estas variables.

Limitaciones

  1. Los resultados provienen de una única partición de la muestra, por lo que las métricas tienen incertidumbre.
  2. Se conservaron valores extremos que podrían ser errores de digitación.
  3. Los modelos son asociativos y no demuestran relaciones causales.
  4. Los datos corresponden a un solo hospital, por lo que no es seguro que los resultados se generalicen a otras instituciones.
  5. Los costos de error usados en el análisis del umbral son supuestos ilustrativos, no costos reales del hospital.

Recomendaciones

  • Incorporar variables clínicas que hoy no están en la base (cesárea previa, número de embarazos previos, presentación fetal, hipertensión o diabetes gestacional) y utilizar únicamente información disponible antes del parto.
  • Definir con el hospital el costo relativo de un falso negativo (cesárea no anticipada) frente a un falso positivo (recursos reservados sin necesidad) para fijar el umbral de decisión.
  • Validar los modelos con validación cruzada repetida o con una muestra de otro periodo para tener estimaciones más estables.
  • Explorar modelos más flexibles (árboles de decisión, bosques aleatorios, regresión penalizada) y comparar su AUC con el del Logit.
  • Si se implementa como herramienta de apoyo, monitorear sus indicadores (sensibilidad y precisión) de forma periódica y reentrenarlo cuando cambien los datos.
  • Usar los resultados como apoyo a la planeación, nunca como sustituto de la valoración de los profesionales de la salud.

Enlace de RPubs:

http://rpubs.com/karol_r/1469921

Bibliografía

Barranquero Gómez, M., & Azaña Gutiérrez, S. (2023, 11 de octubre). ¿Cuál es el peso adecuado del bebé en el momento de nacer? Reproducción Asistida ORG. https://www.reproduccionasistida.org/pesos-en-el-nacimiento/#bebes-de-bajo-peso-al-nacer

Departamento Administrativo Nacional de Estadística (DANE). (2024). Estadísticas Vitales (nacimientos y defunciones): principales resultados IV trimestre 2023 [Comunicado de prensa]. https://www.dane.gov.co/files/operaciones/EEVV/cp-EEVV-IVtrim2023.pdf

Fox, J., & Weisberg, S. (2019). An R Companion to Applied Regression (3.ª ed.). Sage.

Hosmer, D. W., Lemeshow, S., & Sturdivant, R. X. (2013). Applied Logistic Regression (3.ª ed.). Wiley.

James, G., Witten, D., Hastie, T., & Tibshirani, R. (2021). An Introduction to Statistical Learning (2.ª ed.). Springer. https://www.statlearning.com/

Kuhn, M. (2008). Building predictive models in R using the caret package. Journal of Statistical Software, 28(5), 1-26. https://doi.org/10.18637/jss.v028.i05

Kuhn, M., & Johnson, K. (2013). Applied Predictive Modeling. Springer.

Ministerio de Salud, & Federación Colombiana de Sociedades de Obstetricia y Ginecología. (2014). Guía de atención del bajo peso al nacer (Guía No. 13). Ministerio de Salud y Protección Social de Colombia. https://www.minsalud.gov.co/sites/rid/Lists/BibliotecaDigital/RIDE/VS/PP/13Atencion%20del%20Bajo%20peso.pdf

Organización Mundial de la Salud (OMS). (2015). Declaración de la OMS sobre las tasas de cesárea. Ginebra: Organización Mundial de la Salud. https://www.who.int/es/publications/i/item/WHO-RHR-15.02

Robin, X., Turck, N., Hainard, A., et al. (2011). pROC: an open-source package for R and S+ to analyze and compare ROC curves. BMC Bioinformatics, 12, 77. https://doi.org/10.1186/1471-2105-12-77

Sievert, C. (2020). Interactive Web-Based Data Visualization with R, plotly, and shiny. Chapman and Hall/CRC.

Wickham, H., et al. (2019). Welcome to the tidyverse. Journal of Open Source Software, 4(43), 1686. https://doi.org/10.21105/joss.01686