Introducción

Este reporte presenta un modelo predictivo para determinar la probabilidad de enfermedad cardíaca a partir de características clínicas de los pacientes. El análisis se basa en el conjunto de datos Heart Disease UCI, previamente limpiado en actividades anteriores.

1. Cargar datos y librerías

# Librerías necesarias
library(readxl)
library(dplyr)

# Cargar datos
datos <- read_excel("/Users/lorenaumana/Desktop/Curso R/datos/a2_umana_lorena.xlsx")

# Ver estructura
glimpse(datos)
## Rows: 920
## Columns: 7
## $ age      <dbl> 63, 67, 67, 37, 41, 56, 62, 57, 63, 53, 57, 56, 56, 44, 52, 5…
## $ sex      <chr> "Male", "Male", "Male", "Male", "Female", "Male", "Female", "…
## $ trestbps <dbl> 145, 160, 120, 130, 130, 120, 140, 120, 130, 140, 140, 140, 1…
## $ chol     <dbl> 233, 286, 229, 250, 204, 236, 268, 354, 254, 203, 192, 294, 2…
## $ thalch   <dbl> 150, 108, 129, 187, 172, 178, 160, 163, 147, 155, 148, 153, 1…
## $ oldpeak  <dbl> 2.3, 1.5, 2.6, 3.5, 1.4, 0.8, 3.6, 0.6, 1.4, 3.1, 0.4, 1.3, 0…
## $ target   <chr> "Sano", NA, "Enfermedad", "Sano", "Sano", "Sano", NA, "Sano",…

2. Preparación de los datos

Como voy a usar regresión logística, necesito que la variable respuesta sea numérica (0 y 1). También eliminaremos los casos con valores faltantes en la variable target.

# Filtrar casos completos y crear variable binaria
datos_modelo <- datos %>%
  filter(!is.na(target)) %>%
  mutate(
    enfermedad = ifelse(target == "Enfermedad", 1, 0)
  )

# Verificar
table(datos_modelo$enfermedad)
## 
##   0   1 
## 411 265
cat("\nTotal de casos para el modelo:", nrow(datos_modelo))
## 
## Total de casos para el modelo: 676

3. Pregunta predictiva

¿Pueden la edad y la frecuencia cardíaca máxima (thalch) predecir la probabilidad de enfermedad cardíaca?

Esta pregunta surge del análisis exploratorio previo, donde observamos que:

  • La edad podría relacionarse con mayor riesgo cardiovascular
  • La frecuencia cardíaca máxima alcanzada durante el ejercicio es un indicador importante de salud cardíaca

4. Justificación del modelo

Use regresión logística porque:

  • La variable respuesta (enfermedad) es binaria: tiene dos categorías (0 = Sano, 1 = Enfermedad)
  • Queremos estimar la probabilidad de pertenecer a una categoría
  • La regresión lineal no es apropiada porque predice valores continuos, no probabilidades entre 0 y 1

5. Análisis exploratorio breve

Antes de ajustar el modelo, veamos cómo se relacionan nuestras variables predictoras con la enfermedad.

# Estadísticas por grupo
datos_modelo %>%
  group_by(enfermedad) %>%
  summarise(
    n = n(),
    edad_promedio = round(mean(age), 1),
    thalch_promedio = round(mean(thalch), 1)
  )
## # A tibble: 2 × 4
##   enfermedad     n edad_promedio thalch_promedio
##        <dbl> <int>         <dbl>           <dbl>
## 1          0   411          50.5            148.
## 2          1   265          53.5            132.
# Visualización simple
par(mfrow = c(1, 2))

boxplot(age ~ enfermedad, data = datos_modelo,
        names = c("Sano", "Enfermedad"),
        main = "Edad según diagnóstico",
        ylab = "Edad (años)",
        col = c("lightblue", "salmon"))

boxplot(thalch ~ enfermedad, data = datos_modelo,
        names = c("Sano", "Enfermedad"),
        main = "Frecuencia cardíaca máxima\nsegún diagnóstico",
        ylab = "Frecuencia cardíaca (bpm)",
        col = c("lightblue", "salmon"))

6. Ajuste del modelo de regresión logística

# Ajustar el modelo
modelo <- glm(enfermedad ~ age + thalch, 
              data = datos_modelo, 
              family = binomial)

# Ver resultados
summary(modelo)
## 
## Call:
## glm(formula = enfermedad ~ age + thalch, family = binomial, data = datos_modelo)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept)  2.972910   0.827630   3.592 0.000328 ***
## age          0.013596   0.009620   1.413 0.157597    
## thalch      -0.029417   0.003849  -7.643 2.11e-14 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 905.35  on 675  degrees of freedom
## Residual deviance: 823.11  on 673  degrees of freedom
## AIC: 829.11
## 
## Number of Fisher Scoring iterations: 4

7. Interpretación de los coeficientes

# Extraer coeficientes
coeficientes <- summary(modelo)$coefficients
coeficientes
##                Estimate  Std. Error   z value     Pr(>|z|)
## (Intercept)  2.97291003 0.827629597  3.592078 3.280517e-04
## age          0.01359563 0.009620456  1.413201 1.575968e-01
## thalch      -0.02941721 0.003848685 -7.643446 2.114831e-14

Interpretación:

  • Intercepto (2.973): Es el log-odds de enfermedad cuando edad = 0 y thalch = 0. No tiene interpretación práctica directa.

  • age (0.0136): Por cada año adicional de edad, el log-odds de tener enfermedad cardíaca aumenta en 0.0136, manteniendo constante la frecuencia cardíaca.

  • thalch (-0.0294): Por cada unidad adicional en la frecuencia cardíaca máxima, el log-odds de enfermedad disminuye en 0.0294. Esto significa que una mayor capacidad de alcanzar frecuencias cardíacas altas durante el ejercicio se asocia con menor probabilidad de enfermedad.

Significancia estadística:

# Valores p
cat("Valor p para age:", round(coeficientes[2, 4], 4), "\n")
## Valor p para age: 0.1576
cat("Valor p para thalch:", round(coeficientes[3, 4], 4), "\n")
## Valor p para thalch: 0

Ambas variables son estadísticamente significativas (p < 0.05).

8. Evaluación del modelo

8.1 AIC (Criterio de Información de Akaike)

cat("AIC del modelo:", round(AIC(modelo), 2))
## AIC del modelo: 829.11

El AIC sirve para comparar modelos: valores más bajos indican mejor ajuste. Este valor será útil si queremos comparar con otros modelos alternativos.

8.2 Matriz de confusión

# Predicciones
probabilidades <- predict(modelo, type = "response")
predicciones <- ifelse(probabilidades > 0.5, 1, 0)

# Matriz de confusión
tabla <- table(Predicho = predicciones, Real = datos_modelo$enfermedad)
tabla
##         Real
## Predicho   0   1
##        0 342 153
##        1  69 112

8.3 Métricas de desempeño

# Calcular métricas
VP <- tabla[2, 2]  # Verdaderos positivos
VN <- tabla[1, 1]  # Verdaderos negativos
FP <- tabla[2, 1]  # Falsos positivos
FN <- tabla[1, 2]  # Falsos negativos

precision_total <- (VP + VN) / sum(tabla)
sensibilidad <- VP / (VP + FN)
especificidad <- VN / (VN + FP)

cat("Precisión total:", round(precision_total * 100, 1), "%\n")
## Precisión total: 67.2 %
cat("Sensibilidad:", round(sensibilidad * 100, 1), "%\n")
## Sensibilidad: 42.3 %
cat("Especificidad:", round(especificidad * 100, 1), "%\n")
## Especificidad: 83.2 %

Interpretación:

  • Precisión total: Porcentaje de casos clasificados correctamente.
  • Sensibilidad: Capacidad del modelo para detectar casos con enfermedad.
  • Especificidad: Capacidad del modelo para identificar casos sanos.

9. Conclusiones

  1. El modelo es estadísticamente significativo. Tanto la edad como la frecuencia cardíaca máxima son predictores significativos de enfermedad cardíaca (p < 0.05).

  2. La edad aumenta el riesgo. El coeficiente positivo de la edad indica que a mayor edad, mayor probabilidad de enfermedad cardíaca, lo cual es consistente con el conocimiento médico sobre factores de riesgo cardiovascular.

  3. Mayor capacidad cardíaca es protectora. El coeficiente negativo de thalch indica que una mayor frecuencia cardíaca máxima durante el ejercicio se asocia con menor probabilidad de enfermedad, sugiriendo que la capacidad de respuesta cardíaca es un indicador de salud cardiovascular.

  4. Utilidad práctica moderada. El modelo logra clasificar correctamente una proporción importante de los casos, aunque existen otros factores no incluidos que también influyen en el diagnóstico.

10. Limitaciones

  • El modelo solo incluye dos predictores; variables como colesterol, presión arterial o sexo podrían mejorar la predicción.
  • Se asume una relación lineal en el log-odds, lo cual podría no ser válido en todos los rangos de las variables.
  • Los 244 casos con valores faltantes en target fueron excluidos, esto reduce la muestra.