1. Carga y Recodificación de variables

El dataset contiene información clínica de pacientes con o sin enfermedad cardíaca. Las variables categóricas se recodificaron para facilitar la interpretación.

datos = read_excel("Heart _disease_Env.xlsx")

Se recodificaron las variables categóricas del conjunto de datos, que originalmente venían como valores numéricos, para facilitar su interpretación. Por ejemplo, la variable cp que representaba el tipo de dolor en el pecho con valores 0 a 3, fue recodificada a categorías como “Angina típica”, “Dolor no anginoso”, etc. También se limpiaron los datos de la variable thal, eliminando valores codificados como 0, que no representan una categoría válida.

# 1. Sexo: 0 = mujer, 1 = hombre
datos$sex = factor(datos$sex, levels = c(0, 1), labels = c("Mujer", "Hombre"))


# 2. Tipo de dolor en el pecho (0-3)
datos$cp = factor(datos$cp,
                           levels = c(0, 1, 2, 3),
                           labels = c("Angina tipica", "Angina atipica", "Dolor no anginoso", "Asintomatico"))

# 3. Azúcar en sangre
datos$fbs = factor(datos$fbs,
                           levels = c(0, 1),
                           labels = c("≤ 120 mg/dl", "> 120 mg/dl"))

# 4. Resultado ECG (0-2)
datos$restecg = factor(datos$restecg,
                         levels = c(0, 1, 2),
                         labels = c("Normal", "Anormalidad ST-T", "Hipertrofia ventricular izquierda"))

# 5. Angina inducida por ejercicio
datos$exang = factor(datos$exang,
                        levels = c(0, 1),
                        labels = c("No","Si"))


# 6. Pendiente del segmento ST
datos$slope = factor(datos$slope,
                      levels = c(0, 1, 2),
                      labels = c("Ascendente", "Plana", "Descendente"))

# 7. N° Vasos Coronarios principales observados por fluoroscopia
datos = subset(datos, ca != 4)
datos$ca = factor(datos$ca,
                  levels = c(0, 1, 2, 3),
                  labels = c("0 vasos", "1 Vaso", "2 vasos", "3 vasos"))

# 8. Thalassemia
datos = subset(datos, thal != 0)
datos$thal = factor(datos$thal,
                     levels = c( 1, 2, 3),
                     labels = c("Flujo normal", "Defecto fijo", "Defecto reversible"))


# 9. Variable objetivo (Target)
datos$target = factor(datos$target,
                       levels = c(0, 1),
                       labels = c("Sin enfermedad", "Con enfermedad"))

2. Descripción de las Variables

El conjunto de datos incluye variables clínicas como edad, sexo, tipo de dolor en el pecho, azúcar en sangre, resultados del electrocardiograma, y otras que han sido estudiadas por su posible relación con la enfermedad cardíaca. Las variables edad y sexo fueron tratadas como suplementarias para el Análisis de Correspondencias Múltiples (MCA), mientras que target (presencia o ausencia de enfermedad) se usó como variable activa, para observar qué categorías están más asociadas con la enfermedad.

3. Análisis de Correspondencias Múltiples (MCA)

Se aplicó MCA para identificar qué categorías están más asociadas a la enfermedad. Edad y sexo se trataron como variables suplementarias.

fviz_mca_var(mca, repel = TRUE, title = "Asociaciones entre categorias (MCA)")
Fig 1. Análisis de Correspondencias Múltiples

Fig 1. Análisis de Correspondencias Múltiples

En la la Figura 1 se muestra ilustrado que ciertas categorías como 0 vasos,Dolor no anginioso, Descendiente, defecto fijo y el no dolor anginoso inducido por ejercicio estan asociados a la categoría ilustrativa Mujer y estan asociados a la categoría Con enfermedad, mientras las categoría ilustrativa Hombre estan asociadas la pendiente Normal.

Tambien se puede observar cierta independencia de la categoría Edad, ya que la categoría mas asociada son las personas con glucosa en la sangre en ayunas ≤ 120 mg/dl (normal). Para poder analizar mejor estas categorías relacionadas a la enfermedad se realiza otro grafico de elipses para ver sus asociaciones.

# 1. Extraer coordenadas y limpiar nombres
coord = as.data.frame(mca$var$coord)
coord$nombre = rownames(coord)

# 2. Validar que estén las categorías
unique(coord$nombre)
##  [1] "Angina tipica"                     "Angina atipica"                   
##  [3] "Dolor no anginoso"                 "Asintomatico"                     
##  [5] "≤ 120 mg/dl"                       "> 120 mg/dl"                      
##  [7] "Normal"                            "Anormalidad ST-T"                 
##  [9] "Hipertrofia ventricular izquierda" "No"                               
## [11] "Si"                                "Ascendente"                       
## [13] "Plana"                             "Descendente"                      
## [15] "0 vasos"                           "1 Vaso"                           
## [17] "2 vasos"                           "3 vasos"                          
## [19] "Flujo normal"                      "Defecto fijo"                     
## [21] "Defecto reversible"                "Sin enfermedad"                   
## [23] "Con enfermedad"
# 3. Crear agrupación
coord$grupo = ifelse(coord$nombre == "Con enfermedad", "Con enfermedad",
               ifelse(coord$nombre == "Sin enfermedad", "Sin enfermedad", "Otras"))

# 4. Guardar posiciones de las dos categorías de target
pos_enf = coord[coord$nombre == "Con enfermedad", c("Dim 1", "Dim 2")]
pos_sano = coord[coord$nombre == "Sin enfermedad", c("Dim 1", "Dim 2")]

# 5. Calcular distancias
coord$dist_enf = sqrt((coord$`Dim 1` - pos_enf$`Dim 1`)^2 + (coord$`Dim 2` - pos_enf$`Dim 2`)^2)
coord$dist_sano = sqrt((coord$`Dim 1` - pos_sano$`Dim 1`)^2 + (coord$`Dim 2` - pos_sano$`Dim 2`)^2)

# 6. Clasificar por cercanía
coord$asociado_a = ifelse(coord$grupo != "Otras", coord$grupo,
                    ifelse(coord$dist_enf < coord$dist_sano, "Con enfermedad", "Sin enfermedad"))

# 7. Graficar
ggplot(coord, aes(x = `Dim 1`, y = `Dim 2`)) +
  geom_point(aes(color = asociado_a), size = 3.5) +
  geom_text_repel(aes(label = nombre, color = asociado_a), size = 4) +
  scale_color_manual(values = c(
    "Con enfermedad" = "#e74c3c",
    "Sin enfermedad" = "#2ecc71"
  )) +
  geom_mark_ellipse(aes(fill = asociado_a, label = asociado_a),
                    concavity = 10, expand = unit(0.5, "lines"),
                    show.legend = FALSE, alpha = 0.1, color = NA) +
  scale_fill_manual(values = c(
    "Con enfermedad" = "#e74c3c",
    "Sin enfermedad" = "#2ecc71"
  )) +
  labs(
    title = "Asociación de Categorías con la Enfermedad Cardiaca",
    subtitle = "Visualización de proximidad según análisis MCA",
    color = "Asociado a",
    x = "Dim 1", y = "Dim 2"
  ) +
  theme_minimal(base_size = 10)
Fig 2. Asociación de Categorías con la Enfermedad Cardiaca

Fig 2. Asociación de Categorías con la Enfermedad Cardiaca

En la Figura 2 se observa mas claro que categorías estan asociadas a Con Enfermedad y Sin Enfermedad, se puede apreciar que por ejemplo para 0 vasos visualizados por la fluoroscopia representa una alta probabilidad de tener una enfermedad cardíaca, mientras que si se visualiza al menos 1 vaso se puede asociar a que no tiene ninguna enfermedad cardíaca. Otra categoría a resaltar es la de la pendinte del segmento ST durante el ejercicio, una pendiente Plana y Ascendente esta relacionada a que no tiene ninguna enfermedad cardíaca, mientras que una pendiente Descendiente esta asociada a Enfermedades Cardíacas.

4. Modelo GLM

Se ajustó un modelo logístico (GLM) para predecir la presencia de enfermedad cardíaca (target) a partir de las demás variables del conjunto de datos.

Este modelo permite ver qué variables tienen mayor influencia en la probabilidad de tener la enfermedad, y cuáles no aportan tanto según su significancia estadística (valor p).

# Ajustar el modelo logístico
modelo_glm <- glm(target ~ ., data = datos, family = "binomial")

summary(modelo_glm)
## 
## Call:
## glm(formula = target ~ ., family = "binomial", data = datos)
## 
## Coefficients:
##                                           Estimate Std. Error z value Pr(>|z|)
## (Intercept)                               1.554442   1.666178   0.933 0.350852
## age                                      -0.002108   0.022197  -0.095 0.924327
## sexHombre                                -1.529005   0.510491  -2.995 0.002743
## cpAngina atipica                          1.007762   0.553037   1.822 0.068420
## cpDolor no anginoso                       1.922751   0.511856   3.756 0.000172
## cpAsintomatico                            1.992947   0.645050   3.090 0.002004
## fbs> 120 mg/dl                            0.433605   0.555970   0.780 0.435445
## restecgAnormalidad ST-T                   0.517397   0.380823   1.359 0.174264
## restecgHipertrofia ventricular izquierda -1.761113   1.804819  -0.976 0.329172
## exangSi                                  -0.930469   0.429707  -2.165 0.030360
## slopePlana                               -0.340765   0.721686  -0.472 0.636799
## slopeDescendente                          1.501363   0.742450   2.022 0.043158
## ca1 Vaso                                 -2.159729   0.499656  -4.322 1.54e-05
## ca2 vasos                                -3.367709   0.750849  -4.485 7.28e-06
## ca3 vasos                                -2.480149   0.883373  -2.808 0.004991
## thalDefecto fijo                          0.132456   0.781165   0.170 0.865355
## thalDefecto reversible                   -1.514235   0.760121  -1.992 0.046360
##                                             
## (Intercept)                                 
## age                                         
## sexHombre                                ** 
## cpAngina atipica                         .  
## cpDolor no anginoso                      ***
## cpAsintomatico                           ** 
## fbs> 120 mg/dl                              
## restecgAnormalidad ST-T                     
## restecgHipertrofia ventricular izquierda    
## exangSi                                  *  
## slopePlana                                  
## slopeDescendente                         *  
## ca1 Vaso                                 ***
## ca2 vasos                                ***
## ca3 vasos                                ** 
## thalDefecto fijo                            
## thalDefecto reversible                   *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 408.40  on 295  degrees of freedom
## Residual deviance: 190.39  on 279  degrees of freedom
## AIC: 224.39
## 
## Number of Fisher Scoring iterations: 6

En los resultados se observa que algunas variables son estadísticamente significativas:

  • Sexo (Hombre): Tiene una relación negativa con la enfermedad (p < 0.01), lo cual sugiere que ser hombre está asociado a menor probabilidad de enfermedad en este modelo, aunque esto puede deberse a cómo se distribuyen otras variables.

  • Tipo de dolor en el pecho:

    • “Dolor no anginoso” y “Asintomático” tienen asociaciones positivas y significativas (p < 0.01), lo que indica que quienes presentan estos tipos de dolor tienen mayor probabilidad de enfermedad.
  • Angina inducida por ejercicio (exang = Sí): También es significativa (p ≈ 0.03) y con un coeficiente negativo, lo que sugiere que esta condición se asocia con menor probabilidad de tener la enfermedad (lo cual puede parecer contraintuitivo y requeriría analizar más a fondo).

  • Pendiente del segmento ST descendente (slope Descendente): Es significativa (p ≈ 0.04), lo cual indica que tener una pendiente descendente del ST también está asociada con mayor probabilidad de enfermedad.

  • Número de vasos coronarios coloreados por fluoroscopia (ca): Tener 1, 2 o 3 vasos está altamente relacionado con menor probabilidad de enfermedad (coeficientes negativos y p < 0.001), lo cual tiene sentido clínico, ya que indica mejor flujo coronario.

  • Thalassemia - Defecto reversible: Esta categoría tiene una relación negativa con la enfermedad (p ≈ 0.046), por lo que podría estar asociada a menor probabilidad de diagnóstico positivo, aunque no es tan fuerte como otras variables.

En cambio, variables como la edad, fbs (azúcar en sangre en ayunas) y los resultados de ECG, thal defecto fijo y slope plana no resultaron significativas en este modelo (p > 0.05), lo que no significa que no influyan en la salud del paciente, sino que no aportan poder predictivo estadístico significativo en este conjunto específico.

5. Evaluación del Modelo

5.1 Matriz de confusión

La siguiente matriz de confusión permite evaluar el rendimiento del modelo de regresión logística para predecir la presencia o ausencia de enfermedad cardíaca:

# Ver las probabilidades predichas
probabilidades <- predict(modelo_glm, type = "response")

# Clasificación con un umbral de 0.5
predicciones <- ifelse(probabilidades > 0.5, "Enfermedad", "No enfermedad")


# Matriz de confusion
matriz_confusion <- table(Real = datos$target, Predicho = predicciones)

# Convertir a data frame para graficar
conf_df <- as.data.frame(matriz_confusion)
names(conf_df) <- c("Predicho", "Real", "Frecuencia")

# Gráfico de calor
ggplot(conf_df, aes(x = Real, y = Predicho, fill = Frecuencia)) +
  geom_tile(color = "white") +
  geom_text(aes(label = Frecuencia), size = 6, color = "black") +
  scale_fill_gradient(low = "white", high = "steelblue") +
  labs(
    title = "Matriz de confusión",
    x = "Clase real",
    y = "Clase predicha"
  ) +
  theme_minimal()

Como se puede observar:

  • 145 personas que sí tenían enfermedad fueron correctamente identificadas (Verdaderos Positivos).

  • 115 personas sanas fueron correctamente clasificadas como sanas (Verdaderos Negativos).

  • 21 personas con enfermedad fueron clasificadas como sanas (Falsos Negativos).

  • 15 personas sanas fueron clasificadas erróneamente como enfermas (Falsos Positivos).

Este resultado muestra un buen rendimiento del modelo, aunque aún hay margen de mejora en la reducción de falsos negativos.

5.2 Métricas

A partir de la matriz de confusión se calcularon las métricas de desempeño más importantes:

# Extraer valores
VP <- matriz_confusion["Con enfermedad","Enfermedad"]  # Verdaderos Positivos
FN <- matriz_confusion["Sin enfermedad","Enfermedad"]  # Falsos Negativos  
FP <- matriz_confusion["Con enfermedad","No enfermedad"]  # Falsos Positivos
VN <- matriz_confusion["Sin enfermedad","No enfermedad"]  # Verdaderos Negativos

A continuación, se interpreta cada una de las métricas extraídas del modelo GLM:

Exactitud

exactitud = (VP + VN) / (VP + VN + FP + FN)
exactitud
## [1] 0.8783784
  • Exactitud: El modelo acierta en el 87.8% de los casos totales, es decir, clasifica correctamente tanto a enfermos como a sanos en la mayoría de las veces.

Sensibilidad

sensibilidad = VP / (VP + FN)
sensibilidad
## [1] 0.873494
  • Sensibilidad (Recall): Detecta correctamente al 87.3% de los pacientes con enfermedad cardíaca. Es una métrica clave en contextos donde es más costoso no detectar una enfermedad que generar una falsa alarma.

Especificidad

especificidad = VN / (VN + FP)
especificidad
## [1] 0.8846154
  • Especificidad: Identifica correctamente al 88.4% de los pacientes sanos, lo que indica una baja tasa de falsos positivos.

Precisión

precision = VP / (VP + FP)
precision
## [1] 0.90625
  • Precisión: Cuando el modelo predice que una persona tiene enfermedad cardíaca, acierta en el 90.6% de los casos. Esto refleja qué tan confiables son sus predicciones positivas.

6. Conclusiones

Se realizó una limpieza y recodificación del conjunto de datos Heart Disease, mejorando así la legibilidad de las variables categóricas para facilitar su análisis estadístico.

  • A través del Análisis de Correspondencias Múltiples (MCA) se identificaron asociaciones entre distintas categorías y la presencia o ausencia de enfermedad cardíaca. Se evidenció, por ejemplo, que los pacientes con 0 vasos coronarios visibles, pendiente descendente del segmento ST y ciertos tipos de dolor en el pecho están más asociados a la presencia de enfermedad.

  • El modelo de regresión logística (GLM) permitió identificar las variables con mayor peso predictivo. Variables como el tipo de dolor en el pecho, el número de vasos observados, y el tipo de thalassemia mostraron asociaciones estadísticamente significativas con la enfermedad.

  • La matriz de confusión mostró que el modelo logra una adecuada clasificación: 145 verdaderos positivos y 115 verdaderos negativos. Sin embargo, aún se presentan 21 falsos negativos y 15 falsos positivos, lo cual indica espacio para mejorar la sensibilidad del modelo.

  • Las métricas de evaluación del modelo fueron destacables:

    • Exactitud: 87.8%
    • Sensibilidad: 87.3%
    • Especificidad: 88.4%
    • Precisión: 90.6%
    • F1-Score: 88.9%
  • En conjunto, estos resultados indican que el modelo presenta un rendimiento aceptable para identificar pacientes con enfermedad cardíaca, aunque podría complementarse con otros enfoques o ajustes (como cambio de umbral o inclusión de variables clínicas adicionales) para optimizar la detección de casos positivos.

  • Finalmente, tanto el análisis exploratorio (MCA) como el modelo predictivo (GLM) aportan perspectivas valiosas y complementarias para el estudio de enfermedades cardíacas, mostrando cómo el análisis estadístico puede apoyar decisiones clínicas y la interpretación de datos médicos.