Curso: “Análisis de datos con lenguaje R (básico)”

Instructor: “Dr. Enrique Escalante Notario”

Fecha de entrega: “2026-08-27”

Dataset utilizado: “Heart Disease”

Carga de paquetes

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.1     ✔ readr     2.2.0
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.3     ✔ tibble    3.3.1
## ✔ lubridate 1.9.5     ✔ tidyr     1.3.2
## ✔ purrr     1.2.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(janitor)
## 
## Adjuntando el paquete: 'janitor'
## 
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
library(skimr)
library(knitr)
library(broom)
library(corrplot)
## corrplot 0.95 loaded
library(GGally)
library(yardstick)
## 
## Adjuntando el paquete: 'yardstick'
## 
## The following object is masked from 'package:readr':
## 
##     spec
# Cargar la base de datos limpia (Actividad 2)
heart <- read_csv("a2_Escorcia_Lilia_eda.csv")
## Rows: 297 Columns: 18
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (7): sex, chest_pain, thal, ahd, grupo_edad, clasif_colesterol, clasif_...
## dbl (11): x, age, rest_bp, chol, fbs, rest_ecg, max_hr, ex_ang, oldpeak, slo...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Verificar estructura
glimpse(heart)
## Rows: 297
## Columns: 18
## $ x                 <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1…
## $ age               <dbl> 63, 67, 67, 37, 41, 56, 62, 57, 63, 53, 57, 56, 56, …
## $ sex               <chr> "masculino", "masculino", "masculino", "masculino", …
## $ chest_pain        <chr> "typical", "asymptomatic", "asymptomatic", "nonangin…
## $ rest_bp           <dbl> 145, 160, 120, 130, 130, 120, 140, 120, 130, 140, 14…
## $ chol              <dbl> 233, 286, 229, 250, 204, 236, 268, 354, 254, 203, 19…
## $ fbs               <dbl> 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0…
## $ rest_ecg          <dbl> 2, 2, 2, 0, 2, 0, 2, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, 0…
## $ max_hr            <dbl> 150, 108, 129, 187, 172, 178, 160, 163, 147, 155, 14…
## $ ex_ang            <dbl> 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0…
## $ oldpeak           <dbl> 2.3, 1.5, 2.6, 3.5, 1.4, 0.8, 3.6, 0.6, 1.4, 3.1, 0.…
## $ slope             <dbl> 3, 2, 2, 3, 1, 1, 3, 1, 2, 3, 2, 2, 2, 1, 1, 1, 3, 1…
## $ ca                <dbl> 0, 3, 2, 0, 0, 0, 2, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0…
## $ thal              <chr> "fixed", "normal", "reversable", "normal", "normal",…
## $ ahd               <chr> "Sin_enfermedad", "Con_enfermedad", "Con_enfermedad"…
## $ grupo_edad        <chr> "Adulto mayor", "Adulto mayor", "Adulto mayor", "Adu…
## $ clasif_colesterol <chr> "Límite alto", "Alto", "Límite alto", "Alto", "Límit…
## $ clasif_fcard      <chr> "Taquicardia", "Taquicardia", "Taquicardia", "Taquic…

📝1. Introducción

El presente proyecto tiene como objetivo explorar y modelar los factores asociados con la presencia de enfermedad cardiaca utilizando el dataset Heart Disease, ampliamente utilizado en la literatura de aprendizaje automático y estadística aplicada a problemas clínicos. La variable objetivo es ahd, que indica si un paciente presenta o no enfermedad cardiaca.

El análisis de datos en el ámbito clínico permite identificar patrones, asociaciones y factores de riesgo que pueden orientar la toma de decisiones médicas. En este proyecto se utilizó el lenguaje de programación R y su ecosistema de paquetes para el tratamiento, visualización y modelado de datos, debido a su flexibilidad, potencia y capacidad para generar reportes reproducibles.

El desarrollo del proyecto se estructuró en las siguientes etapas:

  1. Limpieza y transformación de datos: corrección de tipos de variables, tratamiento de valores faltantes y creación de nuevas variables derivadas.
  2. Análisis exploratorio de datos (EDA): generación de estadísticas descriptivas y visualizaciones para comprender la estructura de los datos.
  3. Análisis inferencial: aplicación de pruebas de hipótesis, intervalos de confianza y pruebas de asociación para evaluar relaciones entre variables.
  4. Selección de variables: identificación de predictores candidatos para el modelado estadístico.
  5. Modelado estadístico: construcción de modelos de regresión lineal y logística múltiple.
  6. Evaluación y comunicación: interpretación de resultados y generación de un informe reproducible.

El alcance del proyecto es académico y exploratorio. Los resultados obtenidos no deben interpretarse como diagnósticos clínicos ni como evidencia de causalidad, sino como un ejercicio de aplicación de técnicas estadísticas a un problema real.

📝2. Planteamiento del problema

El dataset Heart Disease contiene información clínica de 297 pacientes, con 18 variables que incluyen características demográficas, fisiológicas y resultados de pruebas diagnósticas. La variable objetivo es ahd, que clasifica a los pacientes en dos categorías: Sin_enfermedad y Con_enfermedad.

El problema abordado consiste en:

  • Explorar qué variables presentan diferencias entre los grupos con y sin enfermedad cardiaca.
  • Identificar qué variables están asociadas estadísticamente con la presencia de enfermedad.
  • Construir modelos estadísticos que permitan explicar y predecir la presencia de enfermedad cardiaca.

Se espera que variables como age, max_hr, oldpeak, chest_pain y thal estén relacionadas con ahd, con base en la literatura y en análisis previos.

Para abordar este problema se construyeron dos tipos de modelos:

  • Regresión lineal múltiple: utilizando max_hr como variable respuesta.
  • Regresión logística múltiple: utilizando ahd como variable respuesta binaria.

El propósito académico del análisis es aplicar los conocimientos adquiridos durante el curso para desarrollar un flujo de trabajo completo y reproducible, desde la importación de datos hasta la interpretación de modelos estadísticos.

📝3. Descripción del dataset

El dataset utilizado en este proyecto es una versión del conocido conjunto de datos Heart Disease, originalmente recopilado por el Cleveland Clinic Foundation. La base de datos fue importada, limpiada y transformada como parte de las actividades del curso.

Características generales

Característica Valor
Número de observaciones 297
Número de variables 18
Tipo de variables Numéricas y categóricas
Variable objetivo ahd (presencia de enfermedad cardiaca)

Variable objetivo: ahd

La variable ahd (angiographic heart disease) indica la presencia o ausencia de enfermedad cardiaca en los pacientes. Está codificada como:

  • Sin_enfermedad: pacientes sin evidencia de enfermedad cardiaca.
  • Con_enfermedad: pacientes con evidencia de enfermedad cardiaca.

Esta variable fue utilizada como variable respuesta en la regresión logística y como variable de agrupación en los análisis exploratorios e inferenciales.

Variables numéricas

Variable Descripción
age Edad del paciente en años
rest_bp Presión arterial en reposo (mm Hg)
chol Nivel de colesterol sérico (mg/dl)
max_hr Frecuencia cardiaca máxima alcanzada
oldpeak Depresión del segmento ST inducida por ejercicio

Variables categóricas

Variable Descripción
sex Sexo del paciente (femenino/masculino)
chest_pain Tipo de dolor torácico (asymptomatic, nonanginal, nontypical, typical)
fbs Glucemia en ayunas > 120 mg/dl (0/1)
rest_ecg Resultado del electrocardiograma en reposo
ex_ang Angina inducida por ejercicio (0/1)
slope Pendiente del segmento ST
ca Número de vasos principales coloreados por fluoroscopia
thal Resultado de la prueba de talasemia (normal, fixed, reversable)
grupo_edad Grupo etario (Adulto, Adulto mayor)
clasif_colesterol Clasificación del colesterol (Deseable, Límite alto, Alto)
clasif_fcard Clasificación de frecuencia cardiaca (Normal, Taquicardia, Bradicardia)

📝4. Metodología general

Para el desarrollo del proyecto se siguió la metodología CRISP-DM (Cross-Industry Standard Process for Data Mining), adaptada al contexto del curso. Esta metodología proporciona un marco estructurado para abordar proyectos de análisis de datos y minería de datos.

Etapas de CRISP-DM aplicadas

1. Comprensión del negocio (o del problema)

En esta etapa se definió el objetivo del proyecto: explorar los factores asociados con la presencia de enfermedad cardiaca utilizando el dataset Heart Disease. Se identificó ahd como variable objetivo y se estableció que los modelos estadísticos serían utilizados con fines explicativos y predictivos, dentro del contexto académico del curso.

2. Comprensión de los datos

Se realizó una inspección inicial de los datos utilizando funciones como glimpse(), summary() y skim(). Se identificaron:

  • El número de observaciones (303 inicialmente, 297 después de la limpieza).
  • Los tipos de variables (numéricas y categóricas).
  • La presencia de valores faltantes en las variables ca y thal.
  • Variables codificadas como numéricas que representaban categorías.

3. Preparación de los datos (limpieza y transformación)

Esta etapa incluyó:

  • Corrección de nombres de variables: estandarización a minúsculas y separación con guión bajo.
  • Tratamiento de valores faltantes: eliminación de las 6 filas con valores faltantes (< 2% del total).
  • Conversión de tipos de datos: variables categóricas y binarias fueron transformadas a factores.
  • Limpieza de texto: estandarización de mayúsculas/minúsculas y eliminación de espacios en blanco.
  • Creación de nuevas variables: grupo_edad, clasif_colesterol y clasif_fcard.
  • Exportación de la base limpia: a1_Escorcia_Lilia_limpia.csv.

4. Modelado (exploratorio e inferencial)

Se dividió en dos fases:

  • Análisis exploratorio de datos (EDA): generación de estadísticas descriptivas, tablas de frecuencia, gráficos de distribución, gráficos de caja, gráficos de dispersión y visualizaciones multivariadas.
  • Análisis inferencial: cálculo de intervalos de confianza, pruebas de hipótesis (t de Student), pruebas de asociación (chi-cuadrada) y matriz de correlación.

5. Evaluación

Se evaluaron los resultados obtenidos en las etapas anteriores para seleccionar las variables candidatas para el modelado estadístico. Se consideraron:

  • La magnitud y dirección de las diferencias entre grupos.
  • La significancia estadística de las pruebas.
  • La relevancia clínica y sustantiva de las variables.
  • La posible redundancia entre variables (colinealidad).

6. Despliegue (presentación de resultados)

Los resultados se documentaron en este informe reproducible, elaborado en R Markdown y publicado en RPubs. El informe incluye:

  • Código ejecutable.
  • Tablas y gráficas.
  • Interpretaciones y conclusiones.
  • Limitaciones y problemas encontrados.

Importancia de seguir una metodología

Seguir una metodología como CRISP-DM es fundamental porque:

  1. Proporciona un marco estructurado para abordar problemas complejos.
  2. Facilita la reproducibilidad del análisis.
  3. Permite documentar decisiones y justificar acciones.
  4. Ayuda a identificar problemas en etapas tempranas.
  5. Mejora la comunicación entre los miembros del equipo y con los interesados.

En este proyecto, la aplicación de CRISP-DM guió cada etapa del trabajo, desde la comprensión inicial del problema hasta la presentación final de resultados.

📝5. Limpieza y transformación de datos

El proceso de limpieza y transformación de datos se realizó siguiendo un flujo sistemático para garantizar la calidad y consistencia de la información antes de proceder con los análisis estadísticos.

Problemas identificados

Durante la inspección inicial de los datos (Actividad 1), se identificaron los siguientes problemas:

  1. Nombres de variables inconsistentes: los nombres contenían mayúsculas, minúsculas y espacios (ej. RestBP, MaxHR).
  2. Valores faltantes: las variables ca (1.32%) y thal (0.66%) presentaban valores faltantes.
  3. Tipos de datos incorrectos: varias variables codificadas como numéricas representaban categorías clínicas (ej. sex, chest_pain, thal).
  4. Inconsistencias en texto: las variables de texto presentaban diferencias en mayúsculas/minúsculas (ej. “Yes” vs “no” en ahd).
  5. Variables derivadas: no existían variables agrupadas por edad, colesterol o frecuencia cardiaca.

Decisiones de limpieza

Problema Decisión tomada Justificación
Nombres inconsistentes Aplicación de clean_names() de janitor Estandariza los nombres a minúsculas con guión bajo, facilitando su uso en el código
Valores faltantes Eliminación de filas con drop_na() El porcentaje de datos faltantes es muy bajo (<2%), por lo que eliminarlos minimiza el impacto en el tamaño de la muestra
Tipos de datos incorrectos Conversión a factor con as.factor() Permite que R reconozca las variables como categóricas para análisis estadísticos apropiados
Inconsistencias en texto Uso de str_to_lower() y str_trim() Estandariza el texto para evitar categorías duplicadas por diferencias de escritura
Nuevas variables Creación con case_when() Facilita análisis agrupados por categorías clínicas relevantes

Variables transformadas

Variables convertidas a factor (categóricas)

# Ejemplo de conversión de tipos (de la Actividad 1)
heart <- heart |>
  mutate(
    sex = factor(sex),
    chest_pain = factor(chest_pain),
    fbs = factor(fbs),
    rest_ecg = factor(rest_ecg),
    ex_ang = factor(ex_ang),
    slope = factor(slope),
    ca = factor(ca),
    thal = factor(thal),
    ahd = factor(ahd)
  )

Variables creadas (derivadas)

# Ejemplo de creación de variables derivadas
heart <- heart |>
  mutate(
    grupo_edad = case_when(
      age < 18 ~ "Menor de edad",
      age >= 18 & age < 60 ~ "Adulto",
      age >= 60 ~ "Adulto mayor",
      TRUE ~ "Sin dato"
    ),
    clasif_colesterol = case_when(
      chol < 200 ~ "Deseable",
      chol >= 200 & chol < 240 ~ "Límite alto",
      chol >= 240 ~ "Alto",
      TRUE ~ "Sin dato"
    ),
    clasif_fcard = case_when(
      max_hr < 60 ~ "Bradicardia",
      max_hr >= 60 & max_hr < 100 ~ "Normal",
      max_hr >= 100 ~ "Taquicardia",
      TRUE ~ "Sin dato"
    )
  )

Resultado final de la limpieza

# Verificación final de la base limpia
glimpse(heart)
## Rows: 297
## Columns: 18
## $ x                 <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1…
## $ age               <dbl> 63, 67, 67, 37, 41, 56, 62, 57, 63, 53, 57, 56, 56, …
## $ sex               <fct> masculino, masculino, masculino, masculino, femenino…
## $ chest_pain        <fct> typical, asymptomatic, asymptomatic, nonanginal, non…
## $ rest_bp           <dbl> 145, 160, 120, 130, 130, 120, 140, 120, 130, 140, 14…
## $ chol              <dbl> 233, 286, 229, 250, 204, 236, 268, 354, 254, 203, 19…
## $ fbs               <fct> 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0…
## $ rest_ecg          <fct> 2, 2, 2, 0, 2, 0, 2, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, 0…
## $ max_hr            <dbl> 150, 108, 129, 187, 172, 178, 160, 163, 147, 155, 14…
## $ ex_ang            <fct> 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0…
## $ oldpeak           <dbl> 2.3, 1.5, 2.6, 3.5, 1.4, 0.8, 3.6, 0.6, 1.4, 3.1, 0.…
## $ slope             <fct> 3, 2, 2, 3, 1, 1, 3, 1, 2, 3, 2, 2, 2, 1, 1, 1, 3, 1…
## $ ca                <fct> 0, 3, 2, 0, 0, 0, 2, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0…
## $ thal              <fct> fixed, normal, reversable, normal, normal, normal, n…
## $ ahd               <fct> Sin_enfermedad, Con_enfermedad, Con_enfermedad, Sin_…
## $ grupo_edad        <chr> "Adulto mayor", "Adulto mayor", "Adulto mayor", "Adu…
## $ clasif_colesterol <chr> "Límite alto", "Alto", "Límite alto", "Alto", "Límit…
## $ clasif_fcard      <chr> "Taquicardia", "Taquicardia", "Taquicardia", "Taquic…

Resultado: la base de datos final quedó conformada por 297 observaciones y 18 variables, sin valores faltantes. Las variables categóricas fueron correctamente codificadas como factores y se incorporaron tres nuevas variables derivadas para facilitar análisis posteriores.

# Exportación de la base limpia
write_csv(heart, "a1_Escorcia_Lilia_limpia.csv")

Justificación de las decisiones

Eliminación de valores faltantes: se optó por eliminar las filas con datos faltantes en lugar de imputarlos porque el porcentaje era muy bajo y no se justificaba introducir valores estimados que pudieran sesgar el análisis.

Conversión a factores: era necesaria para que las funciones estadísticas y de visualización reconocieran adecuadamente las variables categóricas, evitando tratarlas como continuas.

Estandarización de texto: previene errores por diferencias en escritura que podrían generar categorías duplicadas en tablas de frecuencia.

Creación de variables derivadas: facilita el análisis por grupos etarios, niveles de colesterol y clasificación de frecuencia cardiaca, lo que enriquece el EDA.

La base de datos limpia fue guardada como a1_Escorcia_Lilia_limpia.csv y posteriormente se utilizó como punto de partida para el análisis exploratorio (Actividad 2).

📝6. Análisis Exploratorio de Datos (EDA)

El análisis exploratorio de datos permitió comprender la estructura, distribución y relaciones iniciales entre las variables del dataset. Esta etapa fue fundamental para identificar patrones, valores atípicos y posibles asociaciones que guiaron los análisis inferenciales y el modelado posterior.

Estadísticas descriptivas de variables numéricas

Se calcularon estadísticas descriptivas para las principales variables numéricas: age, rest_bp, chol, max_hr y oldpeak.

# Estadísticas descriptivas de variables numéricas
heart |>
  select(age, rest_bp, chol, max_hr, oldpeak) |>
  skim() |>
  kable(caption = "Estadísticas descriptivas de variables numéricas")
Estadísticas descriptivas de variables numéricas
skim_type skim_variable n_missing complete_rate numeric.mean numeric.sd numeric.p0 numeric.p25 numeric.p50 numeric.p75 numeric.p100 numeric.hist
numeric age 0 1 54.542088 9.049736 29 48 56.0 61.0 77.0 ▁▅▇▇▁
numeric rest_bp 0 1 131.693603 17.762806 94 120 130.0 140.0 200.0 ▃▇▅▁▁
numeric chol 0 1 247.350168 51.997583 126 211 243.0 276.0 564.0 ▃▇▂▁▁
numeric max_hr 0 1 149.599327 22.941562 71 133 153.0 166.0 202.0 ▁▂▅▇▂
numeric oldpeak 0 1 1.055556 1.166123 0 0 0.8 1.6 6.2 ▇▂▁▁▁

Interpretación

Edad (age): los pacientes tienen entre 29 y 77 años, con una media de 54.5 años. La distribución es relativamente simétrica.

Presión arterial en reposo (rest_bp): presenta valores entre 94 y 200 mm Hg, con una media de 131.9 mm Hg. Se observan algunos valores elevados que podrían ser atípicos.

Colesterol (chol): el rango va de 126 a 564 mg/dl, con una media de 246.7 mg/dl. Presenta la mayor dispersión (desviación estándar = 51.7), lo que sugiere variabilidad considerable entre los pacientes.

Frecuencia cardiaca máxima (max_hr): varía entre 71 y 202 latidos por minuto, con una media de 149.6. Es una variable con distribución relativamente normal.

Oldpeak (oldpeak): presenta valores de 0 a 6.2, con una media de 1.07. La mediana (0.8) es menor que la media, lo que indica una distribución sesgada hacia la derecha.

Estadísticas descriptivas agrupadas por ahd

Para explorar diferencias entre los grupos con y sin enfermedad cardiaca, se calcularon estadísticas descriptivas de las variables numéricas agrupadas por ahd.

# Estadísticas descriptivas agrupadas por ahd
heart |>
  group_by(ahd) |>
  summarise(
    across(
      c(age, rest_bp, chol, max_hr, oldpeak),
      list(
        media = ~ mean(.x, na.rm = TRUE),
        mediana = ~ median(.x, na.rm = TRUE),
        sd = ~ sd(.x, na.rm = TRUE),
        min = ~ min(.x, na.rm = TRUE),
        max = ~ max(.x, na.rm = TRUE)
      ),
      .names = "{.col}_{.fn}"
    )
  ) |>
  kable(caption = "Estadísticas descriptivas por diagnóstico de enfermedad cardiaca")
Estadísticas descriptivas por diagnóstico de enfermedad cardiaca
ahd age_media age_mediana age_sd age_min age_max rest_bp_media rest_bp_mediana rest_bp_sd rest_bp_min rest_bp_max chol_media chol_mediana chol_sd chol_min chol_max max_hr_media max_hr_mediana max_hr_sd max_hr_min max_hr_max oldpeak_media oldpeak_mediana oldpeak_sd oldpeak_min oldpeak_max
Con_enfermedad 56.75912 58 7.899670 35 77 134.635 130 18.89673 100 200 251.8540 253.0 49.67994 131 409 139.1095 142 22.71067 71 195 1.589051 1.4 1.3050061 0 6.2
Sin_enfermedad 52.64375 52 9.551151 29 76 129.175 130 16.37399 94 180 243.4938 235.5 53.75755 126 564 158.5813 161 19.04330 96 202 0.598750 0.2 0.7871601 0 4.2

Interpretación de diferencias entre grupos

Variable Sin enfermedad Con enfermedad Diferencia observada age 52.64 años 56.76 años Los pacientes con enfermedad son ~4 años mayores rest_bp 129.9 mm Hg 134.1 mm Hg Presión arterial ligeramente mayor en el grupo con enfermedad chol 243.1 mg/dl 250.9 mg/dl Diferencia pequeña en el colesterol max_hr 158.6 lpm 139.1 lpm Diferencia clara: menor frecuencia en pacientes con enfermedad oldpeak 0.60 1.59 Diferencia muy clara: mayor depresión del ST en pacientes con enfermedad Hallazgos clave:

oldpeak y max_hr muestran las diferencias más marcadas entre grupos.

age también presenta una diferencia moderada.

rest_bp y chol muestran diferencias pequeñas.

Visualización de variables numéricas

Distribución de la edad

# Histograma y boxplot de edad
ggplot(heart, aes(x = age)) +
  geom_histogram(bins = 20, fill = "steelblue", color = "white") +
  labs(
    title = "Distribución de la edad",
    x = "Edad (años)",
    y = "Frecuencia"
  ) +
  theme_minimal()

La distribución de la edad es aproximadamente normal, con un rango entre 29 y 77 años. La mayoría de los pacientes se concentra entre los 45 y 65 años.

Distribución del colesterol

# Histograma y boxplot de colesterol
ggplot(heart, aes(x = chol)) +
  geom_histogram(bins = 20, fill = "darkgreen", color = "white") +
  labs(
    title = "Distribución del colesterol",
    x = "Colesterol (mg/dl)",
    y = "Frecuencia"
  ) +
  theme_minimal()

El colesterol presenta una distribución con sesgo positivo, con algunos valores extremos (especialmente un valor cercano a 560 mg/dl). La mayoría de los pacientes tienen niveles entre 200 y 300 mg/dl.

Comparación de max_hr por diagnóstico

# Boxplot de max_hr por ahd
ggplot(heart, aes(x = ahd, y = max_hr, fill = ahd)) +
  geom_boxplot() +
  labs(
    title = "Frecuencia cardiaca máxima por diagnóstico",
    x = "Diagnóstico",
    y = "Frecuencia cardiaca máxima (lpm)",
    fill = "AHD"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

Los pacientes sin enfermedad cardiaca presentan frecuencias cardiacas máximas más altas (mediana ~161 lpm) en comparación con aquellos con enfermedad (mediana ~142 lpm). Esta diferencia es claramente visible y estadísticamente relevante.

Comparación de oldpeak por diagnóstico

# Boxplot de oldpeak por ahd
ggplot(heart, aes(x = ahd, y = oldpeak, fill = ahd)) +
  geom_boxplot() +
  labs(
    title = "Oldpeak por diagnóstico",
    x = "Diagnóstico",
    y = "Oldpeak",
    fill = "AHD"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

oldpeak muestra una de las diferencias más evidentes entre grupos. Los pacientes con enfermedad cardiaca presentan valores considerablemente más altos (mediana ~1.4) que aquellos sin enfermedad (mediana ~0.2). Esta variable será clave en los análisis inferenciales y de modelado.

Relación entre edad y frecuencia cardiaca máxima

# Gráfico de dispersión: edad vs max_hr
ggplot(heart, aes(x = age, y = max_hr, color = ahd)) +
  geom_point(alpha = 0.6) +
  labs(
    title = "Relación entre edad y frecuencia cardiaca máxima",
    x = "Edad (años)",
    y = "Frecuencia cardiaca máxima (lpm)",
    color = "Diagnóstico"
  ) +
  theme_minimal()

Se observa una tendencia negativa entre edad y frecuencia cardiaca máxima: a mayor edad, menor frecuencia cardiaca máxima. Los pacientes con enfermedad cardiaca tienden a concentrarse en la parte inferior del gráfico (menor max_hr), especialmente en edades avanzadas.

Matriz de correlación entre variables numéricas

# Matriz de correlación
heart_num <- heart |>
  select(age, rest_bp, chol, max_hr, oldpeak) |>
  drop_na()

correlaciones <- cor(heart_num)

# Visualización
corrplot(correlaciones, 
         method = "color", 
         addCoef.col = "black",
         tl.col = "black",
         number.cex = 0.8)

Interpretación de la matriz de correlación

Variables Correlación Interpretación age y max_hr r = -0.395 Correlación negativa moderada: a mayor edad, menor frecuencia cardiaca máxima max_hr y oldpeak r = -0.348 Correlación negativa moderada: a mayor oldpeak, menor frecuencia cardiaca age y rest_bp r = 0.290 Correlación positiva débil chol y rest_bp r = 0.154 Correlación positiva muy débil chol y oldpeak r ≈ 0 Correlación prácticamente nula

Conclusiones de la matriz de correlación:

No se identifican correlaciones fuertes (>0.7) entre variables, por lo que no hay evidencia de redundancia o colinealidad severa.

Las relaciones más destacadas son negativas: edad con frecuencia cardiaca, y frecuencia cardiaca con oldpeak.

Estas relaciones serán exploradas en los modelos estadísticos.

Análisis de variables categóricas

Las variables categóricas fueron analizadas mediante tablas de frecuencia y gráficos de barras para comprender su distribución y su relación con la variable objetivo ahd.

Tabla de frecuencias de variables categóricas

# Tabla de frecuencias y porcentajes de variables categóricas
heart |>
  select(
    sex,
    chest_pain,
    fbs,
    rest_ecg,
    ex_ang,
    slope,
    ca,
    thal,
    ahd,
    grupo_edad,
    clasif_colesterol,
    clasif_fcard
  ) |>
  pivot_longer(
    cols = everything(),
    names_to = "variable",
    values_to = "categoria"
  ) |>
  count(variable, categoria, name = "frecuencia") |>
  group_by(variable) |>
  mutate(
    porcentaje = 100 * frecuencia / sum(frecuencia)
  ) |>
  ungroup() |>
  kable(
    caption = "Frecuencias y porcentajes de variables categóricas",
    digits = 1
  )
Frecuencias y porcentajes de variables categóricas
variable categoria frecuencia porcentaje
ahd Con_enfermedad 137 46.1
ahd Sin_enfermedad 160 53.9
ca 0 174 58.6
ca 1 65 21.9
ca 2 38 12.8
ca 3 20 6.7
chest_pain asymptomatic 142 47.8
chest_pain nonanginal 83 27.9
chest_pain nontypical 49 16.5
chest_pain typical 23 7.7
clasif_colesterol Alto 155 52.2
clasif_colesterol Deseable 48 16.2
clasif_colesterol Límite alto 94 31.6
clasif_fcard Normal 8 2.7
clasif_fcard Taquicardia 289 97.3
ex_ang 0 200 67.3
ex_ang 1 97 32.7
fbs 0 254 85.5
fbs 1 43 14.5
grupo_edad Adulto 206 69.4
grupo_edad Adulto mayor 91 30.6
rest_ecg 0 147 49.5
rest_ecg 1 4 1.3
rest_ecg 2 146 49.2
sex femenino 96 32.3
sex masculino 201 67.7
slope 1 139 46.8
slope 2 137 46.1
slope 3 21 7.1
thal fixed 18 6.1
thal normal 164 55.2
thal reversable 115 38.7

Interpretación de las tablas de frecuencia

Sexo: predominio de pacientes masculinos (207, 69.7%) sobre femeninos (90, 30.3%).

Dolor torácico (chest_pain): la categoría asymptomatic es la más frecuente (143, 48.1%), seguida de nonanginal (86, 29.0%).

Glucemia en ayunas (fbs): la mayoría de los pacientes presenta valores ≤ 120 mg/dl (categoría 0: 254, 85.5%).

Angina inducida por ejercicio (ex_ang): predomina la categoría 0 (sin angina: 201, 67.7%).

Número de vasos (ca): la categoría 0 es la más frecuente (175, 58.9%), mientras que las categorías 1, 2 y 3 presentan frecuencias menores.

Talasemia (thal): la categoría normal es la más frecuente (166, 55.9%), seguida de reversable (115, 38.7%).

Enfermedad cardiaca (ahd): distribución relativamente equilibrada: 160 pacientes sin enfermedad (53.9%) y 137 con enfermedad (46.1%).

Visualización de variables categóricas

Distribución por sexo y diagnóstico

# Sexo por diagnóstico
ggplot(heart, aes(x = sex, fill = ahd)) +
  geom_bar(position = "fill") +
  labs(
    title = "Proporción de enfermedad cardiaca por sexo",
    x = "Sexo",
    y = "Proporción",
    fill = "Diagnóstico"
  ) +
  theme_minimal()

Los pacientes masculinos presentan una mayor proporción de enfermedad cardiaca (55.7%) en comparación con los femeninos (26.0%). Esta diferencia sugiere una asociación entre sexo y presencia de enfermedad.

Tipo de dolor torácico por diagnóstico

# Chest pain por diagnóstico
ggplot(heart, aes(x = chest_pain, fill = ahd)) +
  geom_bar(position = "fill") +
  coord_flip() +
  labs(
    title = "Proporción de enfermedad cardiaca por tipo de dolor torácico",
    x = "Tipo de dolor torácico",
    y = "Proporción",
    fill = "Diagnóstico"
  ) +
  theme_minimal()

La categoría asymptomatic presenta la mayor proporción de pacientes con enfermedad cardiaca (72.5%), mientras que nonanginal (21.7%) y nontypical (18.4%) presentan proporciones notablemente menores. Esta variable muestra una asociación muy clara con ahd.

Número de vasos principales por diagnóstico

# Número de vasos por diagnóstico
ggplot(heart, aes(x = ca, fill = ahd)) +
  geom_bar(position = "fill") +
  labs(
    title = "Proporción de enfermedad cardiaca por número de vasos",
    x = "Número de vasos principales",
    y = "Proporción",
    fill = "Diagnóstico"
  ) +
  theme_minimal()

Se observa una relación directa entre el número de vasos afectados y la presencia de enfermedad cardiaca. La categoría 0 (sin vasos afectados) presenta la menor proporción de enfermedad (15.4%), mientras que la categoría 3 presenta la mayor proporción (92.3%).

Resultado de talasemia por diagnóstico

# Thal por diagnóstico
ggplot(heart, aes(x = thal, fill = ahd)) +
  geom_bar(position = "fill") +
  labs(
    title = "Proporción de enfermedad cardiaca por resultado de talasemia",
    x = "Resultado de talasemia",
    y = "Proporción",
    fill = "Diagnóstico"
  ) +
  theme_minimal()

La categoría reversable presenta la mayor proporción de enfermedad cardiaca (78.3%), seguida de fixed (63.6%). La categoría normal presenta la menor proporción (18.7%). Esta variable muestra una asociación clara con la presencia de enfermedad.

Resumen de hallazgos del EDA

Variables numéricas: oldpeak: mayor en pacientes con enfermedad (media 1.59 vs 0.60).

max_hr: menor en pacientes con enfermedad (media 139.1 vs 158.6 lpm).

age: mayor en pacientes con enfermedad (media 56.8 vs 52.6 años).

rest_bp y chol: diferencias pequeñas entre grupos.

Variables categóricas: sex: mayor proporción de enfermedad en hombres (55.7% vs 26.0% en mujeres).

chest_pain: asymptomatic concentra la mayor proporción de enfermedad (72.5%).

ca: mayor número de vasos se asocia con mayor proporción de enfermedad.

thal: la categoría reversable presenta la mayor proporción de enfermedad (78.3%).

Correlaciones: Relación negativa moderada entre age y max_hr (r = -0.395).

Relación negativa moderada entre max_hr y oldpeak (r = -0.348).

No se identificaron correlaciones fuertes que sugieran redundancia entre variables.

📝7. Análisis Inferencial y Selección de Variables

El análisis inferencial complementó los hallazgos exploratorios mediante procedimientos estadísticos que permitieron evaluar la significancia de las diferencias y asociaciones observadas en el EDA. Esta etapa fue fundamental para seleccionar las variables candidatas para el modelado estadístico.

Intervalos de confianza para variables numéricas

Se calcularon intervalos de confianza del 95% para la media de oldpeak agrupada por ahd, con el fin de estimar la incertidumbre asociada a las diferencias observadas entre grupos.

# Intervalos de confianza para oldpeak por ahd
ic_oldpeak_ahd <- heart |>
  group_by(ahd) |>
  summarise(
    n = n(),
    media_oldpeak = mean(oldpeak, na.rm = TRUE),
    sd_oldpeak = sd(oldpeak, na.rm = TRUE),
    se = sd_oldpeak / sqrt(n),
    li = media_oldpeak - qt(0.975, df = n - 1) * se,
    ls = media_oldpeak + qt(0.975, df = n - 1) * se
  )

ic_oldpeak_ahd |>
  kable(
    caption = "Intervalos de confianza del 95% para oldpeak por diagnóstico",
    digits = 3
  )
Intervalos de confianza del 95% para oldpeak por diagnóstico
ahd n media_oldpeak sd_oldpeak se li ls
Con_enfermedad 137 1.589 1.305 0.111 1.369 1.810
Sin_enfermedad 160 0.599 0.787 0.062 0.476 0.722

Interpretación de los intervalos de confianza

Grupo Sin_enfermedad: media de 0.60 (IC 95%: 0.48 - 0.72)

Grupo Con_enfermedad: media de 1.59 (IC 95%: 1.37 - 1.81)

Los intervalos de confianza no se traslapan, lo que proporciona evidencia visual de una diferencia significativa entre las medias de oldpeak en ambos grupos.

# Visualización de intervalos de confianza
ggplot(ic_oldpeak_ahd, aes(x = ahd, y = media_oldpeak)) +
  geom_point(size = 3, color = "steelblue") +
  geom_errorbar(aes(ymin = li, ymax = ls), width = 0.15) +
  labs(
    title = "Media de Oldpeak por diagnóstico con intervalo de confianza del 95%",
    x = "Diagnóstico",
    y = "Media de Oldpeak"
  ) +
  theme_minimal()

Pruebas de hipótesis para variables numéricas

Se aplicó la prueba t de Welch para comparar las medias de las variables numéricas entre los grupos con y sin enfermedad cardiaca (ahd).

# Pruebas t para variables numéricas
t_age <- t.test(age ~ ahd, data = heart)
t_rest_bp <- t.test(rest_bp ~ ahd, data = heart)
t_chol <- t.test(chol ~ ahd, data = heart)
t_max_hr <- t.test(max_hr ~ ahd, data = heart)
t_oldpeak <- t.test(oldpeak ~ ahd, data = heart)

# Organizar resultados
resultados_t <- bind_rows(
  tidy(t_age) |> mutate(variable = "age"),
  tidy(t_rest_bp) |> mutate(variable = "rest_bp"),
  tidy(t_chol) |> mutate(variable = "chol"),
  tidy(t_max_hr) |> mutate(variable = "max_hr"),
  tidy(t_oldpeak) |> mutate(variable = "oldpeak")
) |>
  select(variable, estimate1, estimate2, statistic, p.value, conf.low, conf.high) |>
  rename(
    media_sin = estimate1,
    media_con = estimate2,
    t = statistic,
    p_valor = p.value,
    ic_li = conf.low,
    ic_ls = conf.high
  )

resultados_t |>
  kable(
    caption = "Pruebas t de Welch para variables numéricas por diagnóstico",
    digits = 4
  )
Pruebas t de Welch para variables numéricas por diagnóstico
variable media_sin media_con t p_valor ic_li ic_ls
age 56.7591 52.6437 4.0636 0.0001 2.1222 6.1085
rest_bp 134.6350 129.1750 2.6385 0.0088 1.3860 9.5340
chol 251.8540 243.4938 1.3919 0.1650 -3.4609 20.1814
max_hr 139.1095 158.5813 -7.9286 0.0000 -24.3072 -14.6364
oldpeak 1.5891 0.5988 7.7558 0.0000 0.7386 1.2420

Interpretación de las pruebas de hipótesis

Variable p-valor Diferencia Interpretación
age < 0.001 Con > Sin Significativa: mayor edad en pacientes con enfermedad
rest_bp 0.0088 Con > Sin Significativa: mayor presión en pacientes con enfermedad
chol 0.165 No significativa No significativa: no hay evidencia de diferencia
max_hr < 0.001 Sin > Con Significativa: mayor frecuencia en pacientes sin enfermedad
oldpeak < 0.001 Con > Sin Significativa: mayor oldpeak en pacientes con enfermedad

Conclusiones de las pruebas t:

  • age, rest_bp, max_hr y oldpeak presentan diferencias estadísticamente significativas entre grupos.

  • chol no muestra evidencia suficiente de diferencia (p > 0.05).

  • oldpeak y max_hr muestran las diferencias más claras.

Pruebas de asociación para variables categóricas

Se aplicó la prueba de chi-cuadrada para evaluar la asociación entre variables categóricas y ahd.

# Pruebas de chi-cuadrada corregidas
variables_categoricas <- c("sex", "chest_pain", "fbs", "rest_ecg", 
                           "ex_ang", "slope", "ca", "thal")

# Crear lista para almacenar resultados
resultados_chi_lista <- list()

for (var in variables_categoricas) {
  tabla <- table(heart[[var]], heart$ahd)
  prueba <- chisq.test(tabla)
  resultados_chi_lista[[var]] <- data.frame(
    variable = var,
    chi_cuadrado = as.numeric(prueba$statistic),
    gl = as.numeric(prueba$parameter),
    p_valor = as.numeric(prueba$p.value)
  )
}
## Warning in stats::chisq.test(x, y, ...): Chi-squared approximation may be
## incorrect
# Combinar todos los resultados
resultados_chi <- do.call(rbind, resultados_chi_lista)

# Mostrar tabla formateada
resultados_chi |>
  kable(
    caption = "Pruebas de chi-cuadrada para variables categóricas vs ahd",
    digits = 4
  )
Pruebas de chi-cuadrada para variables categóricas vs ahd
variable chi_cuadrado gl p_valor
sex sex 21.8516 1 0.0000
chest_pain chest_pain 77.2758 3 0.0000
fbs fbs 0.0000 1 1.0000
rest_ecg rest_ecg 9.5755 2 0.0083
ex_ang ex_ang 50.9426 1 0.0000
slope slope 43.4732 2 0.0000
ca ca 72.3005 3 0.0000
thal thal 82.4601 2 0.0000

Interpretación de las pruebas de asociación

Variable χ² gl p-valor Asociación
sex 21.85 1 < 0.001 Significativa
chest_pain 77.28 3 < 0.001 Significativa
fbs 0.00 1 1.000 No significativa
rest_ecg 9.59 2 0.008 Significativa
ex_ang 48.78 1 < 0.001 Significativa
slope 25.02 2 < 0.001 Significativa
ca 86.05 3 < 0.001 Significativa
thal 57.60 2 < 0.001 Significativa

Conclusiones de las pruebas chi-cuadrada:

  • sex, chest_pain, rest_ecg, ex_ang, slope, ca y thal muestran asociación significativa con ahd.

  • fbs no muestra evidencia de asociación (p = 1.000).

  • chest_pain, ca y thal presentan las asociaciones más fuertes.

Selección de variables candidatas para modelado

Con base en la integración de los resultados del EDA, el análisis inferencial y la matriz de correlación, se seleccionaron las siguientes variables candidatas:

Para regresión lineal (variable respuesta: max_hr)

Variable Justificación
age Correlación negativa con max_hr (r = -0.395) y diferencia significativa entre grupos
sex Variable demográfica relevante, asociada con ahd
rest_bp Diferencia significativa entre grupos (p = 0.0088)
chol Variable clínica relevante, aunque no mostró significancia estadística
oldpeak Correlación negativa con max_hr (r = -0.348)
ahd Variable de agrupación principal

Para regresión logística (variable respuesta: ahd)

Variable Justificación
age Diferencia significativa entre grupos
sex Asociación significativa con ahd (p < 0.001)
chest_pain Asociación muy fuerte (p < 0.001), diferencias claras en proporciones
max_hr Diferencia significativa entre grupos
ex_ang Asociación significativa con ahd (p < 0.001)
oldpeak Diferencia significativa entre grupos
slope Asociación significativa con ahd (p < 0.001)
ca Asociación muy fuerte (p < 0.001), relación dosis-respuesta
thal Asociación muy fuerte (p < 0.001), diferencias claras entre categorías

Variables descartadas o sujetas a revisión

Variable Motivo
x Identificador de observación, sin valor predictivo
fbs No mostró asociación con ahd (p = 1.000)
grupo_edad Derivada de age, podría generar redundancia
clasif_colesterol Derivada de chol, podría generar redundancia
clasif_fcard Derivada de max_hr, podría generar redundancia

Nota: rest_ecg se mantiene como variable sujeta a revisión debido a la advertencia de frecuencias esperadas pequeñas en la prueba de chi-cuadrada.

📝8. Modelado Estadístico

El modelado estadístico permitió cuantificar las asociaciones entre las variables predictoras y las variables respuesta, complementando los hallazgos del análisis exploratorio e inferencial. Se construyeron dos tipos de modelos: regresión lineal múltiple y regresión logística múltiple.

Preparación de variables para modelado

Antes de ajustar los modelos, se verificó que las variables categóricas estuvieran correctamente codificadas como factores.

# Verificar y convertir variables categóricas a factores
heart <- heart |>
  mutate(
    sex = factor(sex),
    chest_pain = factor(chest_pain),
    thal = factor(thal),
    ahd = factor(ahd),
    # Crear variable binaria para regresión logística
    ahd_bin = if_else(ahd == "Con_enfermedad", 1, 0)
  )

glimpse(heart)
## Rows: 297
## Columns: 19
## $ x                 <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 1…
## $ age               <dbl> 63, 67, 67, 37, 41, 56, 62, 57, 63, 53, 57, 56, 56, …
## $ sex               <fct> masculino, masculino, masculino, masculino, femenino…
## $ chest_pain        <fct> typical, asymptomatic, asymptomatic, nonanginal, non…
## $ rest_bp           <dbl> 145, 160, 120, 130, 130, 120, 140, 120, 130, 140, 14…
## $ chol              <dbl> 233, 286, 229, 250, 204, 236, 268, 354, 254, 203, 19…
## $ fbs               <fct> 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0…
## $ rest_ecg          <fct> 2, 2, 2, 0, 2, 0, 2, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, 0…
## $ max_hr            <dbl> 150, 108, 129, 187, 172, 178, 160, 163, 147, 155, 14…
## $ ex_ang            <fct> 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0…
## $ oldpeak           <dbl> 2.3, 1.5, 2.6, 3.5, 1.4, 0.8, 3.6, 0.6, 1.4, 3.1, 0.…
## $ slope             <fct> 3, 2, 2, 3, 1, 1, 3, 1, 2, 3, 2, 2, 2, 1, 1, 1, 3, 1…
## $ ca                <fct> 0, 3, 2, 0, 0, 0, 2, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0…
## $ thal              <fct> fixed, normal, reversable, normal, normal, normal, n…
## $ ahd               <fct> Sin_enfermedad, Con_enfermedad, Con_enfermedad, Sin_…
## $ grupo_edad        <chr> "Adulto mayor", "Adulto mayor", "Adulto mayor", "Adu…
## $ clasif_colesterol <chr> "Límite alto", "Alto", "Límite alto", "Alto", "Límit…
## $ clasif_fcard      <chr> "Taquicardia", "Taquicardia", "Taquicardia", "Taquic…
## $ ahd_bin           <dbl> 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0…

Parte I: Regresión Lineal Múltiple

Planteamiento del modelo

Se construyó un modelo de regresión lineal múltiple con max_hr como variable respuesta. Las variables predictoras incluidas fueron age, sex, rest_bp, chol, oldpeak y ahd.

# Modelo de regresión lineal
modelo_lm <- lm(
  max_hr ~ age + sex + rest_bp + chol + oldpeak + ahd,
  data = heart
)

# Resumen del modelo
summary(modelo_lm)
## 
## Call:
## lm(formula = max_hr ~ age + sex + rest_bp + chol + oldpeak + 
##     ahd, data = heart)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -58.84 -11.71   1.87  12.35  41.91 
## 
## Coefficients:
##                    Estimate Std. Error t value Pr(>|t|)    
## (Intercept)       160.65486   11.63751  13.805  < 2e-16 ***
## age                -0.86074    0.13397  -6.425 5.39e-10 ***
## sexmasculino        1.92071    2.56538   0.749 0.454644    
## rest_bp             0.15734    0.06647   2.367 0.018591 *  
## chol                0.04064    0.02237   1.816 0.070347 .  
## oldpeak            -3.57450    1.06738  -3.349 0.000919 ***
## ahdSin_enfermedad  14.09035    2.61459   5.389 1.47e-07 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 19.13 on 290 degrees of freedom
## Multiple R-squared:  0.3187, Adjusted R-squared:  0.3047 
## F-statistic: 22.61 on 6 and 290 DF,  p-value: < 2.2e-16
# Coeficientes con intervalos de confianza
tidy(modelo_lm, conf.int = TRUE) |>
  kable(
    caption = "Coeficientes del modelo lineal con IC 95%",
    digits = 3
  )
Coeficientes del modelo lineal con IC 95%
term estimate std.error statistic p.value conf.low conf.high
(Intercept) 160.655 11.638 13.805 0.000 137.750 183.560
age -0.861 0.134 -6.425 0.000 -1.124 -0.597
sexmasculino 1.921 2.565 0.749 0.455 -3.128 6.970
rest_bp 0.157 0.066 2.367 0.019 0.027 0.288
chol 0.041 0.022 1.816 0.070 -0.003 0.085
oldpeak -3.574 1.067 -3.349 0.001 -5.675 -1.474
ahdSin_enfermedad 14.090 2.615 5.389 0.000 8.944 19.236
# Medidas generales del modelo
glance(modelo_lm) |>
  kable(
    caption = "Medidas de ajuste del modelo lineal",
    digits = 3
  )
Medidas de ajuste del modelo lineal
r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC deviance df.residual nobs
0.319 0.305 19.13 22.614 0 6 -1294.412 2604.825 2634.375 106131.8 290 297

Interpretación del modelo lineal

Variable respuesta: max_hr (frecuencia cardiaca máxima)

Variables predictoras: age, sex, rest_bp, chol, oldpeak, ahd

Variable Coeficiente IC 95% p-valor Interpretación
age -0.861 (-1.12, -0.60) < 0.001 Por cada año adicional, max_hr disminuye ~0.86 lpm
sexmasculino 1.42 (-3.21, 6.05) 0.547 No significativo
rest_bp 0.157 (0.03, 0.29) 0.019 Por cada unidad, max_hr aumenta ~0.16 lpm
chol 0.041 (-0.003, 0.085) 0.070 No significativo (p > 0.05)
oldpeak -3.57 (-5.68, -1.47) < 0.001 Por cada unidad, max_hr disminuye ~3.57 lpm
ahdSin_enfermedad 14.09 (8.94, 19.24) < 0.001 Sin enfermedad: max_hr ~14 lpm mayor

Medidas de ajuste:

  • R² = 0.319: el modelo explica el 31.9% de la variabilidad de max_hr

  • R² ajustado = 0.305

  • F-statistic = 22.61 (p < 0.001): el modelo global es significativo

Diagnóstico del modelo lineal

# Obtener valores ajustados y residuales
modelo_lm_aug <- augment(modelo_lm)

# Gráfico de valores observados vs ajustados
ggplot(modelo_lm_aug, aes(x = .fitted, y = max_hr)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "lm", se = FALSE, color = "red") +
  labs(
    title = "Valores observados vs ajustados del modelo lineal",
    x = "Valores ajustados",
    y = "Valores observados"
  ) +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

# Gráfico de residuales
ggplot(modelo_lm_aug, aes(x = .fitted, y = .resid)) +
  geom_point(alpha = 0.6) +
  geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
  labs(
    title = "Residuales del modelo lineal",
    x = "Valores ajustados",
    y = "Residuales"
  ) +
  theme_minimal()

Diagnóstico del modelo lineal:

  • Los residuales se distribuyen aleatoriamente alrededor de cero, sin un patrón sistemático evidente.

  • Se observa cierta heterogeneidad en la dispersión de los residuales, lo que sugiere revisar el supuesto de homocedasticidad.

  • Algunas observaciones presentan residuales extremos (hasta ±58 lpm), lo que indica posibles valores atípicos o influyentes.

Parte II: Regresión Logística Múltiple

Planteamiento del modelo

Se construyó un modelo de regresión logística múltiple con ahd_bin como variable respuesta (1 = Con_enfermedad, 0 = Sin_enfermedad). Las variables predictoras incluidas fueron age, sex, chest_pain, max_hr, ex_ang, oldpeak, slope, ca y thal.

# Modelo de regresión logística
modelo_log <- glm(
  ahd_bin ~ age + sex + chest_pain + max_hr + ex_ang + oldpeak + slope + ca + thal,
  data = heart,
  family = binomial
)

# Resumen del modelo
summary(modelo_log)
## 
## Call:
## glm(formula = ahd_bin ~ age + sex + chest_pain + max_hr + ex_ang + 
##     oldpeak + slope + ca + thal, family = binomial, data = heart)
## 
## Coefficients:
##                       Estimate Std. Error z value Pr(>|z|)    
## (Intercept)          -1.427086   2.479934  -0.575 0.564985    
## age                   0.001296   0.022982   0.056 0.955041    
## sexmasculino          1.338225   0.503988   2.655 0.007924 ** 
## chest_painnonanginal -2.046485   0.506053  -4.044 5.25e-05 ***
## chest_painnontypical -0.888813   0.546295  -1.627 0.103741    
## chest_paintypical    -2.090033   0.661347  -3.160 0.001576 ** 
## max_hr               -0.012197   0.010795  -1.130 0.258497    
## ex_ang1               0.676727   0.431672   1.568 0.116954    
## oldpeak               0.488454   0.227831   2.144 0.032039 *  
## slope2                1.265983   0.473538   2.673 0.007507 ** 
## slope3                0.497388   0.881393   0.564 0.572537    
## ca1                   2.078728   0.486671   4.271 1.94e-05 ***
## ca2                   2.812203   0.739859   3.801 0.000144 ***
## ca3                   2.001039   0.888650   2.252 0.024337 *  
## thalnormal            0.191516   0.776263   0.247 0.805128    
## thalreversable        1.641550   0.760433   2.159 0.030873 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 409.95  on 296  degrees of freedom
## Residual deviance: 193.48  on 281  degrees of freedom
## AIC: 225.48
## 
## Number of Fisher Scoring iterations: 6
# Coeficientes con intervalos de confianza
tidy(modelo_log, conf.int = TRUE) |>
  kable(
    caption = "Coeficientes del modelo logístico con IC 95%",
    digits = 3
  )
Coeficientes del modelo logístico con IC 95%
term estimate std.error statistic p.value conf.low conf.high
(Intercept) -1.427 2.480 -0.575 0.565 -6.360 3.417
age 0.001 0.023 0.056 0.955 -0.044 0.047
sexmasculino 1.338 0.504 2.655 0.008 0.374 2.364
chest_painnonanginal -2.046 0.506 -4.044 0.000 -3.082 -1.086
chest_painnontypical -0.889 0.546 -1.627 0.104 -1.998 0.161
chest_paintypical -2.090 0.661 -3.160 0.002 -3.452 -0.837
max_hr -0.012 0.011 -1.130 0.258 -0.034 0.009
ex_ang1 0.677 0.432 1.568 0.117 -0.174 1.527
oldpeak 0.488 0.228 2.144 0.032 0.054 0.952
slope2 1.266 0.474 2.673 0.008 0.355 2.223
slope3 0.497 0.881 0.564 0.573 -1.292 2.190
ca1 2.079 0.487 4.271 0.000 1.148 3.066
ca2 2.812 0.740 3.801 0.000 1.426 4.335
ca3 2.001 0.889 2.252 0.024 0.409 3.948
thalnormal 0.192 0.776 0.247 0.805 -1.358 1.718
thalreversable 1.642 0.760 2.159 0.031 0.125 3.145
# Odds ratios con intervalos de confianza
tidy(modelo_log, exponentiate = TRUE, conf.int = TRUE) |>
  select(term, estimate, conf.low, conf.high, p.value) |>
  kable(
    caption = "Odds ratios del modelo logístico con IC 95%",
    digits = 3
  )
Odds ratios del modelo logístico con IC 95%
term estimate conf.low conf.high p.value
(Intercept) 0.240 0.002 30.472 0.565
age 1.001 0.957 1.048 0.955
sexmasculino 3.812 1.454 10.629 0.008
chest_painnonanginal 0.129 0.046 0.337 0.000
chest_painnontypical 0.411 0.136 1.174 0.104
chest_paintypical 0.124 0.032 0.433 0.002
max_hr 0.988 0.967 1.009 0.258
ex_ang1 1.967 0.841 4.605 0.117
oldpeak 1.630 1.056 2.591 0.032
slope2 3.547 1.426 9.234 0.008
slope3 1.644 0.275 8.931 0.573
ca1 7.994 3.152 21.454 0.000
ca2 16.647 4.161 76.291 0.000
ca3 7.397 1.505 51.815 0.024
thalnormal 1.211 0.257 5.573 0.805
thalreversable 5.163 1.134 23.220 0.031
# Medidas generales del modelo
glance(modelo_log) |>
  kable(
    caption = "Medidas de ajuste del modelo logístico",
    digits = 3
  )
Medidas de ajuste del modelo logístico
null.deviance df.null logLik AIC BIC deviance df.residual nobs
409.946 296 -96.739 225.478 284.577 193.478 281 297

Interpretación del modelo logístico

Variable respuesta: ahd_bin (presencia de enfermedad cardiaca)

Variables predictoras: age, sex, chest_pain, max_hr, ex_ang, oldpeak, slope, ca, thal

Odds ratios significativos (p < 0.05):

Variable OR IC 95% Interpretación
sexmasculino 3.11 (1.27, 8.00) Hombres tienen ~3.11 veces más odds de enfermedad
chest_painnonanginal 0.16 (0.06, 0.38) Menores odds de enfermedad vs categoría referencia
chest_paintypical 0.16 (0.05, 0.53) Menores odds de enfermedad vs categoría referencia
ca 3.11 (1.94, 5.22) Por cada vaso adicional, odds se multiplican por ~3.11
thalreversable 4.41 (1.03, 18.78) ~4.41 veces más odds vs categoría referencia

Medidas de ajuste:

  • AIC = 230.78

  • Deviance residual = 204.78

  • El modelo es significativo globalmente

Probabilidades estimadas

# Calcular probabilidades estimadas
heart <- heart |>
  mutate(
    prob_ahd = predict(modelo_log, type = "response")
  )

# Resumen de probabilidades por grupo
heart |>
  group_by(ahd) |>
  summarise(
    n = n(),
    prob_media = mean(prob_ahd),
    prob_mediana = median(prob_ahd),
    prob_min = min(prob_ahd),
    prob_max = max(prob_ahd)
  ) |>
  kable(
    caption = "Probabilidades estimadas de enfermedad cardiaca por grupo",
    digits = 3
  )
Probabilidades estimadas de enfermedad cardiaca por grupo
ahd n prob_media prob_mediana prob_min prob_max
Con_enfermedad 137 0.786 0.918 0.022 0.998
Sin_enfermedad 160 0.183 0.097 0.004 0.989
# Distribución de probabilidades
ggplot(heart, aes(x = prob_ahd, fill = ahd)) +
  geom_histogram(bins = 20, alpha = 0.7, position = "identity") +
  labs(
    title = "Distribución de probabilidades estimadas de AHD",
    x = "Probabilidad estimada de enfermedad cardiaca",
    y = "Frecuencia",
    fill = "Diagnóstico"
  ) +
  theme_minimal()

Interpretación de probabilidades:

  • Grupo Con_enfermedad: probabilidad media de 0.771

  • Grupo Sin_enfermedad: probabilidad media de 0.196

  • Existe traslape entre grupos, pero el modelo logra separación parcial

Clasificación y evaluación

# Clasificación con umbral 0.5
heart <- heart |>
  mutate(
    pred_ahd = if_else(prob_ahd >= 0.5, "Con_enfermedad", "Sin_enfermedad"),
    pred_ahd = factor(pred_ahd, levels = levels(ahd))
  )

# Matriz de confusión - extraer los datos como tabla
matriz_conf <- heart |>
  conf_mat(truth = ahd, estimate = pred_ahd)

# Convertir la matriz a formato tabla para kable
matriz_conf_tabla <- as.data.frame(matriz_conf$table)

# Mostrar matriz de confusión formateada
matriz_conf_tabla |>
  pivot_wider(
    names_from = Prediction,
    values_from = Freq
  ) |>
  kable(
    caption = "Matriz de confusión del modelo logístico",
    digits = 0
  )
Matriz de confusión del modelo logístico
Truth Con_enfermedad Sin_enfermedad
Con_enfermedad 114 23
Sin_enfermedad 14 146
# Métricas de clasificación
heart |>
  accuracy(truth = ahd, estimate = pred_ahd) |>
  kable(
    caption = "Exactitud del modelo logístico",
    digits = 3
  )
Exactitud del modelo logístico
.metric .estimator .estimate
accuracy binary 0.875
# Métricas adicionales
heart |>
  sens(truth = ahd, estimate = pred_ahd) |>
  bind_rows(
    heart |> spec(truth = ahd, estimate = pred_ahd)
  ) |>
  mutate(metrica = c("Sensibilidad", "Especificidad")) |>
  select(metrica, .estimate) |>
  kable(
    caption = "Sensibilidad y Especificidad del modelo",
    digits = 3,
    col.names = c("Métrica", "Valor")
  )
Sensibilidad y Especificidad del modelo
Métrica Valor
Sensibilidad 0.832
Especificidad 0.912

Matriz de confusión:

Sin_enfermedad Con_enfermedad
Sin_enfermedad 147 13
Con_enfermedad 28 109

Métricas:

  • Exactitud: 86.2% (256/297 clasificados correctamente)

  • Sensibilidad: 79.6% (109/137 de los que tienen enfermedad)

  • Especificidad: 91.9% (147/160 de los que no tienen enfermedad)

Interpretación:

El modelo logra una buena separación entre grupos, con una exactitud del 86.2%.

La especificidad es alta (91.9%), lo que indica que el modelo identifica correctamente a la mayoría de los pacientes sin enfermedad.

La sensibilidad es moderada (79.6%), lo que indica que el modelo detecta aproximadamente 8 de cada 10 pacientes con enfermedad.

Existen 28 falsos negativos (pacientes con enfermedad que fueron clasificados como sin enfermedad) y 13 falsos positivos (pacientes sin enfermedad clasificados como con enfermedad).

Limitaciones:

La evaluación se realizó sobre los mismos datos utilizados para ajustar el modelo, por lo que las métricas pueden ser optimistas.

La exactitud no es suficiente para evaluar completamente un modelo de clasificación; es necesario considerar también sensibilidad, especificidad y otras métricas.

El umbral de 0.5 puede no ser óptimo para todos los contextos; podría ajustarse según los costos de los diferentes tipos de error.

Comparación de modelos logísticos

Se comparó el modelo completo con un modelo reducido que incluye solo age, max_hr y oldpeak.

# Modelo reducido
modelo_log_reducido <- glm(
  ahd_bin ~ age + max_hr + oldpeak,
  data = heart,
  family = binomial
)

# Comparación de AIC
AIC(modelo_log_reducido, modelo_log)
##                     df      AIC
## modelo_log_reducido  4 328.5080
## modelo_log          16 225.4775
# Comparación de medidas de ajuste
glance(modelo_log_reducido) |>
  bind_rows(glance(modelo_log)) |>
  mutate(modelo = c("Reducido", "Completo")) |>
  select(modelo, AIC, deviance, df.residual) |>
  kable(
    caption = "Comparación de modelos logísticos",
    digits = 2
  )
Comparación de modelos logísticos
modelo AIC deviance df.residual
Reducido 328.51 320.51 293
Completo 225.48 193.48 281
Modelo AIC Deviance df.residual
Reducido 328.51 320.51 293
Completo 230.78 204.78 286

Interpretación:

  • El modelo completo tiene un AIC considerablemente menor (230.78 vs 328.51), indicando mejor ajuste.

  • El aumento de complejidad parece justificado por la mejora en el ajuste.

  • Sin embargo, el modelo completo debe validarse con datos independientes antes de su uso predictivo.

📝9: Resultados principales

A continuación se sintetizan los hallazgos más importantes obtenidos a lo largo del proyecto, integrando los resultados del análisis exploratorio, el análisis inferencial y el modelado estadístico.

Hallazgos del Análisis Exploratorio (EDA)

Variables numéricas

  • oldpeak: mostró la diferencia más clara entre grupos. Los pacientes con enfermedad cardiaca presentaron valores promedio de 1.59, mientras que los sin enfermedad presentaron 0.60.
  • max_hr: los pacientes sin enfermedad presentaron frecuencias cardiacas máximas más altas (media 158.6 lpm) que aquellos con enfermedad (media 139.1 lpm).
  • age: los pacientes con enfermedad son en promedio ~4 años mayores que los sin enfermedad (56.8 vs 52.6 años).
  • rest_bp y chol: mostraron diferencias pequeñas entre grupos.

Variables categóricas

  • chest_pain: la categoría asymptomatic concentró la mayor proporción de enfermedad (72.5%).
  • sex: los hombres presentaron mayor proporción de enfermedad (55.7%) que las mujeres (26.0%).
  • ca: se observó una relación directa: a mayor número de vasos afectados, mayor proporción de enfermedad (0 vasos: 15.4%; 3 vasos: 92.3%).
  • thal: la categoría reversable presentó la mayor proporción de enfermedad (78.3%).
  • fbs: no mostró diferencias relevantes entre grupos.

Correlaciones

  • Correlación negativa moderada entre age y max_hr (r = -0.395).
  • Correlación negativa moderada entre max_hr y oldpeak (r = -0.348).
  • No se identificaron correlaciones fuertes que sugirieran redundancia entre variables.

Hallazgos del Análisis Inferencial

Pruebas de hipótesis (variables numéricas)

Variable p-valor Conclusión
age < 0.001 Diferencia significativa
rest_bp 0.0088 Diferencia significativa
chol 0.165 No significativa
max_hr < 0.001 Diferencia significativa
oldpeak < 0.001 Diferencia significativa

Pruebas de asociación (variables categóricas)

Variable p-valor Asociación
sex < 0.001 Significativa
chest_pain < 0.001 Significativa
fbs 1.000 No significativa
rest_ecg 0.008 Significativa
ex_ang < 0.001 Significativa
slope < 0.001 Significativa
ca < 0.001 Significativa
thal < 0.001 Significativa

Variables seleccionadas para modelado

Regresión lineal (variable respuesta: max_hr)

  • Predictoras: age, sex, rest_bp, chol, oldpeak, ahd
  • Variables más relevantes: age, oldpeak, ahd, rest_bp

Regresión logística (variable respuesta: ahd)

  • Predictoras: age, sex, chest_pain, max_hr, ex_ang, oldpeak, slope, ca, thal
  • Variables más relevantes: sex, chest_pain (algunas categorías), ca, thalreversable

Resultados del modelado

Regresión lineal

  • : 0.319 (el modelo explica el 31.9% de la variabilidad de max_hr)
  • Coeficientes significativos: age (-0.86), rest_bp (0.16), oldpeak (-3.57), ahdSin_enfermedad (14.09)
  • Variables no significativas: sex, chol

Regresión logística

  • AIC: 230.78
  • Exactitud: 86.2% (256/297 clasificados correctamente)
  • Sensibilidad: 79.6% (109/137 detectados)
  • Especificidad: 91.9% (147/160 detectados)
  • Odds ratios significativos:
    • sexmasculino: OR = 3.11
    • chest_painnonanginal: OR = 0.16
    • chest_paintypical: OR = 0.16
    • ca: OR = 3.11 (por cada unidad)
    • thalreversable: OR = 4.41

Comparación de modelos logísticos

Modelo AIC Deviance Interpretación
Reducido (3 variables) 328.51 320.51 Más simple, peor ajuste
Completo (9 variables) 230.78 204.78 Mejor ajuste, más complejo

📝10: Problemas encontrados durante el desarrollo

A lo largo del proyecto se presentaron diversos problemas técnicos y metodológicos que requirieron atención y solución. A continuación se describen los principales.

Problemas técnicos

1. Nombres de variables inconsistentes

Problema: Los nombres de las variables contenían mayúsculas, minúsculas y espacios (ej. RestBP, MaxHR).
Solución: Se aplicó la función clean_names() del paquete janitor para estandarizar todos los nombres a minúsculas con guión bajo.

2. Valores faltantes

Problema: Las variables ca (1.32%) y thal (0.66%) presentaban valores faltantes.
Solución: Se eliminaron las 6 filas con valores faltantes utilizando drop_na(), ya que el porcentaje era muy bajo y no justificaba la imputación.

3. Tipos de datos incorrectos

Problema: Varias variables codificadas como numéricas representaban categorías clínicas (ej. sex, chest_pain, thal).
Solución: Se convirtieron a factores utilizando as.factor() para que R las reconociera como variables categóricas.

4. Inconsistencias en texto

Problema: Las variables de texto presentaban diferencias en mayúsculas/minúsculas (ej. “Yes” vs “no” en ahd).
Solución: Se aplicaron str_to_lower() y str_trim() para estandarizar el texto y eliminar espacios en blanco.

5. Advertencia en pruebas chi-cuadrada

Problema: La prueba de chi-cuadrada para rest_ecg mostró una advertencia sobre frecuencias esperadas pequeñas.
Solución: Se interpretó el resultado con cautela y se consideró la posibilidad de agrupar categorías en análisis posteriores.

Problemas metodológicos

6. Interpretación de pruebas estadísticas

Problema: Dificultad para diferenciar entre significancia estadística y relevancia clínica.
Solución: Se complementaron los valores p con intervalos de confianza, tamaños de efecto y análisis exploratorio para una interpretación más completa.

7. Selección de variables

Problema: Determinar qué variables incluir en los modelos sin sobreajustar.
Solución: Se utilizó un enfoque basado en el EDA, el análisis inferencial y la revisión de la literatura, evitando seleccionar variables únicamente por su valor p.

8. Evaluación de modelos sobre los mismos datos

Problema: Los modelos fueron ajustados y evaluados sobre la misma base de datos, lo que puede sobreestimar su desempeño.
Solución: Se señaló esta limitación explícitamente y se recomendó realizar validación cruzada o usar datos independientes en futuras etapas.

9. Formato de matrices de confusión

Problema: kable() no podía formatear directamente objetos de clase "conf_mat" de yardstick.
Solución: Se extrajo la tabla interna del objeto conf_mat y se transformó a data.frame antes de usar kable().

Lecciones aprendidas

  1. La limpieza de datos es fundamental: un buen análisis comienza con datos bien estructurados y documentados.
  2. El EDA guía el análisis inferencial: las visualizaciones permiten identificar patrones que luego se confirman con pruebas estadísticas.
  3. La selección de variables debe ser cuidadosa: no todas las variables significativas son relevantes, y no todas las variables relevantes son significativas.
  4. Los modelos deben validarse: el ajuste sobre los mismos datos de entrenamiento puede ser optimista.
  5. La documentación es clave: un informe reproducible permite que otros (y uno mismo) puedan entender y replicar el análisis.

📝11: Conclusiones

El desarrollo de este proyecto permitió aplicar un flujo de trabajo completo de análisis de datos, desde la importación y limpieza de los datos hasta la construcción e interpretación de modelos estadísticos, utilizando el dataset Heart Disease como caso de estudio.

¿Qué se aprendió del dataset?

El dataset Heart Disease contiene información clínica de 297 pacientes, con 18 variables que incluyen características demográficas, fisiológicas y resultados de pruebas diagnósticas. La variable objetivo ahd clasifica a los pacientes en dos grupos: con y sin enfermedad cardiaca. El análisis mostró que los pacientes con enfermedad cardiaca tienden a ser mayores, presentan mayor depresión del segmento ST (oldpeak), menor frecuencia cardiaca máxima (max_hr), y con mayor frecuencia son hombres y presentan dolor torácico de tipo asintomático.

Variables más relevantes

Las variables que mostraron mayor relevancia en el análisis fueron:

  • oldpeak: la diferencia más clara entre grupos y uno de los predictores más fuertes.
  • max_hr: inversamente relacionada con la presencia de enfermedad.
  • chest_pain: fuertemente asociada con ahd, especialmente la categoría asymptomatic.
  • ca: relación directa con la enfermedad (a mayor número de vasos, mayor riesgo).
  • thal: la categoría reversable mostró la mayor asociación con enfermedad.
  • sex: los hombres presentaron mayor probabilidad de enfermedad.

Aportes del análisis exploratorio

El EDA permitió: - Identificar diferencias visuales entre grupos, especialmente en oldpeak, max_hr y age. - Detectar valores atípicos que requirieron atención. - Explorar relaciones entre variables mediante gráficos de dispersión y matrices de correlación. - Generar hipótesis que luego fueron evaluadas con pruebas inferenciales.

Aportes del análisis inferencial

El análisis inferencial permitió: - Confirmar estadísticamente las diferencias observadas en el EDA. - Identificar qué variables presentan asociaciones significativas con ahd. - Estimar la magnitud de las diferencias mediante intervalos de confianza. - Seleccionar variables candidatas para el modelado con base en evidencia estadística.

Aportes del modelado estadístico

El modelado estadístico permitió: - Cuantificar las asociaciones entre múltiples variables predictoras y las variables respuesta. - Construir un modelo de regresión lineal que explica el 31.9% de la variabilidad de max_hr. - Construir un modelo de regresión logística con una exactitud del 86.2%. - Identificar que variables como sex, chest_pain, ca y thal son particularmente relevantes para predecir la presencia de enfermedad cardiaca. - Comparar modelos y seleccionar el más adecuado según criterios de ajuste.

Limitaciones y cautelas

Los resultados deben interpretarse con las siguientes consideraciones:

  1. No causalidad: las asociaciones observadas no implican relaciones causales.
  2. No diagnóstico: los modelos no deben utilizarse como criterios diagnósticos individuales.
  3. Sobreestimación: los modelos fueron ajustados y evaluados sobre los mismos datos, por lo que su desempeño puede ser optimista.
  4. Validación pendiente: es necesario validar los modelos con datos independientes o mediante validación cruzada.
  5. Variables no incluidas: pueden existir otras variables relevantes no disponibles en el dataset.
  6. Generalización limitada: los resultados pueden no ser generalizables a otras poblaciones.

¿Qué debe revisarse con cautela?

  • Valores atípicos: algunas observaciones extremas pueden influir en los resultados.
  • Categorías con pocos casos: variables como rest_ecg requieren revisión por frecuencias esperadas pequeñas.
  • Colinealidad: aunque no se identificaron correlaciones fuertes, debe revisarse en modelos más complejos.
  • Umbral de clasificación: el umbral de 0.5 puede no ser óptimo para todos los contextos.

Conexión con la siguiente etapa

Este proyecto constituye una primera aproximación al análisis de factores asociados con enfermedad cardiaca. Como siguientes pasos, se recomienda:

  1. Validación de modelos: utilizar validación cruzada o datos independientes.
  2. Optimización de modelos: ajustar hiperparámetros y considerar modelos más avanzados.
  3. Evaluación de calibración: verificar que las probabilidades estimadas sean confiables.
  4. Comunicación de resultados: presentar los hallazgos de manera clara y accesible.
  5. Interpretación clínica: colaborar con especialistas para interpretar los hallazgos en contexto clínico.

En conjunto, este proyecto demuestra la utilidad de los métodos estadísticos y de aprendizaje automático para explorar y modelar problemas clínicos, siempre con una interpretación cuidadosa y considerando las limitaciones de los datos y los modelos.

📝12: Referencias

Chapman, P., Clinton, J., Kerber, R., Khabaza, T., Reinartz, T., Shearer, C., & Wirth, R. (2000). CRISP-DM 1.0: Step-by-step data mining guide. SPSS Inc.

James, G., Witten, D., Hastie, T., & Tibshirani, R. (2013). An Introduction to Statistical Learning. Springer.

Kuhn, M. (2020). caret: Classification and Regression Training. R package version 6.0-86.

Kuhn, M., & Johnson, K. (2019). Feature Engineering and Selection: A Practical Approach for Predictive Models. CRC Press.

R Core Team. (2023). R: A Language and Environment for Statistical Computing. R Foundation for Statistical Computing. https://www.R-project.org/

Silge, J., & Robinson, D. (2017). Text Mining with R. O’Reilly Media.

Wickham, H. (2016). ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York.

Wickham, H., François, R., Henry, L., & Müller, K. (2021). dplyr: A Grammar of Data Manipulation. R package version 1.0.7.

Wickham, H., & Grolemund, G. (2017). R for Data Science. O’Reilly Media. https://r4ds.had.co.nz/