El presente documento es el informe integrador del proyecto de exploración, inferencia y modelado estadístico, utilizando el dataset Heart, que contiene información clínica y demográfica de 303 pacientes evaluados por posible enfermedad cardiaca.
Este proyecto tiene como objetivo general construir e interpretar modelos estadísticos sencillos, uno de regresión lineal múltiple y uno de regresión logística múltiple, que permitan describir la relación entre variables clínicas de un grupo de pacientes. Nos enfocamos en estudiar la frecuencia cardiaca máxima alcanzada durante una prueba de esfuerzo, usando un modelo de regresión lineal, y la presencia o ausencia de enfermedad cardiaca, en un modelo de regresión lineal.
El análisis de datos puede ser relevante en problemas clínicos como éste, ya que permite identificar de manera sistemática y reproducible qué variables se asocian con una condición de salud, buscando evidencia estadística que respalde la existencia de esas asociaciones. Es importante especificar que en este tipo de análisis tienen limitaciones y que debe evitarse caer en afirmaciones diagnósticas o de causalidad.
El desarrollo del proyecto se organizó en cuatro etapas. En la primera se trabajó con el conjunto de datos original, identificando inconsistencias en los tipos de variable, dando tratamiento a los valores faltantes encontrado y generando una versión limpia de la base. En la segunda se realizó un análisis exploratorio (univariado, bivariado y multivariado), utilizando estadísticas descriptivas y visualizaciones, con el objetivo de identificar qué variables podrían relacionarse entre sí y con la enfermedad cardiaca. En la tercera etapa buscamos obtener evidencia estadística que respaldara la asociación entre las variables, mediante intervalos de confianza, pruebas de hipótesis (t de Student, Wilcoxon y chi-cuadrada) y analizando la correlación. Esto permitió realizar una selección de variables candidatas para el modelado. En la cuarta etapa se plantearon y compararon distintos modelos de regresión lineal múltiple (con la frecuencia cardiaca máxima como variable respuesta) y de un modelo de regresión logística múltiple (con la presencia de enfermedad cardiaca como respuesta), interpretando sus coeficientes y evaluando su desempeño de forma básica.
El proyecto se organizó siguiendo, de manera adaptada, las etapas de la metodología CRISP-DM (Cross Industry Standard Process for Data Mining). La comprensión del negocio corresponde al planteamiento del problema descrito arriba: entender qué se busca explicar, en este caso la presencia de enfermedad cardiaca y, secundariamente, la capacidad funcional cardiovascular. La comprensión de los datos se llevó a cabo principalmente en la primera etapa.
La preparación de los datos también se realizó en esa etapa (limpieza inicial, tratamiento de valores faltantes y recodificación de tipos de variables). El modelado, por su parte, se concentró en la última etapa, pero se apoyó en el trabajo de comprensión y preparación de los datos y, sobre todo, en el análisis inferencial y de selección de variables realizado en la tercera etapa. La evaluación de los modelos se hizo de forma básica, comparando los valores de R² y AIC (en el caso lineal) o su AIC, devianza y exactitud de clasificación (en el caso logístico). El presente informe constituye la etapa de comunicación del proyecto.
El dataset Heart Disease contiene 303 observaciones, correspondientes a pacientes evaluados por posible presencia de enfermedad cardiaca. La base incluye variables relacionadas con características personales, resultados de mediciones médicas, respuestas a pruebas de esfuerzo y algunos indicadores clínicos utilizados como apoyo en la evaluación cardiovascular.
Cada fila del conjunto de datos representa a un paciente y cada
columna corresponde a una variable observada. Se cuenta con 14 variables
predictoras de tipo clínico y demográfico: edad, sexo, tipo de dolor
torácico, presión arterial en reposo, colesterol sérico, glucosa en
ayunas, resultado electrocardiográfico en reposo, frecuencia cardiaca
máxima alcanzada, presencia de angina inducida por ejercicio, depresión
del segmento ST, pendiente del segmento ST, número de vasos principales
observados por fluoroscopía y resultado de una prueba de talasemia,
además de la variable ahd, la cual indica si el paciente
presenta o no enfermedad cardiaca.
Los modelos generados consideran dos variables objetivo distintas y
complementarias. La de interés inicial fue ahd, misma que
motivó el trabajo de selección de variables en la segunda etapa,
utilizada para plantear un problema de clasificación mediante regresión
logística. La frecuencia cardiaca máxima, max_hr, se
utilizó como variable respuesta de un modelo de regresión lineal
múltiple, debido a que se identificó que se relaciona tanto con
variables demográficas (la edad) como con otras variables clínicas (la
depresión del segmento ST) y con la propia presencia de enfermedad
cardiaca.
El propósito académico de los análisis realizados fue poner en práctica las fases iniciales de la metodología CRISP-DM; incluyendo limpieza, exploración, inferencia formal, modelado, interpretación de resultados y comunicación de los hallazgos.
heart <- read_csv("a1_Ruiz_Perla_limpia.csv")
heart$sex <- factor(heart$sex, levels = 0:1)
heart$fbs <- factor(heart$fbs, levels = 0:1)
heart$rest_ecg <- factor(heart$rest_ecg, levels = 0:2)
heart$ex_ang <- factor(heart$ex_ang, levels = 0:1)
heart$slope <- factor(heart$slope, levels = 1:3)
heart <- heart |>
mutate(
chest_pain = factor(
chest_pain,
levels = c("typical", "nontypical", "nonanginal", "asymptomatic"),
labels = c("Typical", "Nontypical", "Nonanginal", "Asymptomatic")
),
ahd = factor(
ahd,
levels = c("no", "yes"),
labels = c("Sin_enfermedad", "Con_enfermedad")
),
thal = factor(
thal,
levels = c("normal", "fixed", "reversable", "sd"),
labels = c("Normal", "Fixed", "Reversable", "Sin dato")
)
)
heart <- heart |>
select(-x1)
glimpse(heart)
## Rows: 303
## Columns: 14
## $ age <dbl> 63, 67, 67, 37, 41, 56, 62, 57, 63, 53, 57, 56, 56, 44, 52,~
## $ sex <fct> 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1,~
## $ chest_pain <fct> Typical, Asymptomatic, Asymptomatic, Nonanginal, Nontypical~
## $ rest_bp <dbl> 145, 160, 120, 130, 130, 120, 140, 120, 130, 140, 140, 140,~
## $ chol <dbl> 233, 286, 229, 250, 204, 236, 268, 354, 254, 203, 192, 294,~
## $ fbs <fct> 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0,~
## $ rest_ecg <fct> 2, 2, 2, 0, 2, 0, 2, 0, 2, 2, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0,~
## $ max_hr <dbl> 150, 108, 129, 187, 172, 178, 160, 163, 147, 155, 148, 153,~
## $ ex_ang <fct> 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 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.4, 1.3,~
## $ slope <fct> 3, 2, 2, 3, 1, 1, 3, 1, 2, 3, 2, 2, 2, 1, 1, 1, 3, 1, 1, 1,~
## $ ca <dbl> 0, 3, 2, 0, 0, 0, 2, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,~
## $ thal <fct> Fixed, Normal, Reversable, Normal, Normal, Normal, Normal, ~
## $ ahd <fct> Sin_enfermedad, Con_enfermedad, Con_enfermedad, Sin_enferme~
colSums(is.na(heart))
## age sex chest_pain rest_bp chol fbs rest_ecg
## 0 0 0 0 0 0 0
## max_hr ex_ang oldpeak slope ca thal ahd
## 0 0 0 0 0 0 0
El archivo Heart.csv contiene 303 observaciones y 15
columnas, incluyendo una columna de índice sin significado clínico
(x1) que se eliminó. De las 14 variables restantes, seis
son numéricas: la edad en años (age), la presión arterial
en reposo en mmHg (rest_bp), el colesterol sérico en mg/dL
(chol), la frecuencia cardiaca máxima alcanzada en la
prueba de esfuerzo (max_hr), la depresión del segmento ST
inducida por el ejercicio (oldpeak) y el número de vasos
principales observados mediante fluoroscopía (ca, numérica
discreta). Las ocho variables restantes son categóricas y tuvieron que
codificarse como tal después de importar los datos: el sexo
(sex), el tipo de dolor torácico (chest_pain,
con cuatro categorías), la glucosa en ayunas mayor a 120 mg/dL
(fbs), el resultado electrocardiográfico en reposo
(rest_ecg, tres niveles), la angina inducida por ejercicio
(ex_ang), la pendiente del segmento ST
(slope), el resultado de la prueba de talasemia
(thal, con cuatro categorías incluyendo un nivel para
registros sin dato) y, por supuesto, la variable objetivo
ahd.
En cuanto a valores faltantes, encontramos únicamente 4 valores
faltantes en la variable ca y 2 valores faltantes en
thal . Dado que eran muy pocos valores faltantes, no se
utilizó una estrategia de imputación más elaborada. A los dos registros
sin dato en thal se les asignó la categoría “sd” (sin
dato), tratándola como un nivel adicional de la variable en vez de
eliminarla, mientras que a los cuatro registros sin dato en
ca, al tratarse de una variable numérica discreta, se les
asignó la moda de la variable (el valor 0, el más frecuente con 176 de
303 observaciones). Adicionalmente, se limpiaron las variables de texto
(chest_pain, thal, ahd)
recortando espacios en blanco y convirtiéndolas a minúsculas.
En el análisis exploratorio, se identificó que en las variables numéricas:
ahd se distribuye de forma
balanceada: 164 pacientes (54.1%) no presentan enfermedad cardiaca y 139
(45.9%) sí la presentan.Entre las variables categóricas, se observó que:
-El tipo de dolor torácico más frecuente es el asintomático (47.5% de los pacientes), seguido del no anginal (28.4%), el no típico (16.5%) y el típico (7.6%). - El 67.8% de los pacientes son hombres. - Sólo el 14.9% tiene glucosa en ayunas elevada. - El 32.7% presenta angina inducida por ejercicio. - En el resultado de talasemia, 55% de los pacientes tiene un resultado normal, 38% reversible y el resto fijo, con dos registros sin dato disponible. - El colesterol es la variable con mayor dispersión absoluta (rango de 438 unidades, desviación estándar de 51.8). - La depresión del segmento ST presenta la distribución más asimétrica: más de la mitad de los pacientes tiene valores por debajo de 1, con una cola de valores altos (hasta 6.2). - La edad y el colesterol tienen distribuciones aproximadamente simétricas. - La frecuencia cardiaca máxima presenta un ligero sesgo a la izquierda y la depresión del segmento ST un sesgo marcado a la derecha.
Los diagramas de caja individuales identificaron valores atípicos puntuales: colesterol por encima de 400 mg/dL, presión arterial en reposo por encima de 170 mmHg y un valor mínimo de frecuencia cardiaca máxima de 71 latidos por minuto, claramente separado del resto de la distribución.
Al agrupar las variables numéricas según la presencia de enfermedad cardiaca aparecieron las primeras diferencias relevantes entre grupos. La edad promedio es de 56.6 años en los pacientes con enfermedad frente a 52.6 años en los que no la tienen; la frecuencia cardiaca máxima promedia 158.4 en el grupo sin enfermedad frente a un valor notablemente menor en el grupo con enfermedad; y la depresión del segmento ST muestra la diferencia más marcada de las cuatro variables numéricas exploradas, con valores sistemáticamente mayores en los pacientes enfermos. El colesterol, en cambio, mostró diferencias más leves y menos consistentes entre grupos. Estas diferencias se visualizaron con diagramas de caja separados por diagnóstico, como el de la frecuencia cardiaca máxima y el de la depresión del segmento ST que se muestran a continuación.
ggplot(heart, aes(x = max_hr, y = ahd)) +
geom_boxplot() +
labs(
title = "Distribución de la FCM por diagnóstico de enfermedad cardiaca",
x = "Frecuencia cardiaca máxima",
y = "Diagnóstico"
) +
theme_minimal()
En esta gráfica se aprecia cómo los pacientes sin enfermedad alcanzan, en general, una mayor frecuencia cardiaca máxima durante la prueba de esfuerzo que los pacientes con enfermedad, aunque hay traslape entre los rangos intercuartílicos de ambos grupos; también se identifican algunos valores atípicos inferiores, principalmente en el grupo sin enfermedad.
ggplot(heart, aes(x = oldpeak, y = ahd)) +
geom_boxplot() +
labs(
title = "Distribución de la depresión del segmento ST por diagnóstico",
x = "Depresión del segmento ST",
y = "Diagnóstico"
) +
theme_minimal()
Aquí la diferencia entre grupos es más clara: la cuarta parte de los pacientes con enfermedad tiene valores por debajo de 0.5, mientras que en los pacientes sin enfermedad esa proporción es más del doble, lo que sugiere una asociación más fuerte entre esta variable y el diagnóstico que la observada para el colesterol.
En cuanto a las variables categóricas, las tablas de frecuencias mostraron que el dolor torácico asintomático es, con mucho, el más común entre los pacientes con enfermedad, mientras que los otros tres tipos de dolor predominan entre los pacientes sanos; algo similar ocurre con la angina inducida por ejercicio, presente en una proporción mucho mayor de pacientes enfermos que sanos, y con el resultado de la prueba de talasemia, donde la categoría reversible concentra la mayor proporción de casos con enfermedad. La matriz de correlación entre las variables numéricas mostró, en general, relaciones débiles a moderadas: la correlación más fuerte observada fue la negativa entre edad y frecuencia cardiaca máxima (-0.39), seguida de la positiva entre edad y número de vasos observados (0.37) y de la negativa entre frecuencia cardiaca máxima y depresión del segmento ST (-0.34); ninguna correlación fue lo suficientemente alta como para sugerir redundancia extrema entre variables, aunque sí se identificó a la edad y la frecuencia cardiaca máxima como un par a vigilar en modelos multivariados posteriores.
heart_num <- heart |>
select(age, rest_bp, chol, max_hr, oldpeak, ca) |>
drop_na()
correlaciones <- cor(heart_num)
round(correlaciones, 2)
## age rest_bp chol max_hr oldpeak ca
## age 1.00 0.28 0.21 -0.39 0.20 0.37
## rest_bp 0.28 1.00 0.13 -0.05 0.19 0.10
## chol 0.21 0.13 1.00 0.00 0.05 0.12
## max_hr -0.39 -0.05 0.00 1.00 -0.34 -0.27
## oldpeak 0.20 0.19 0.05 -0.34 1.00 0.30
## ca 0.37 0.10 0.12 -0.27 0.30 1.00
En conjunto, el análisis exploratorio señaló que las variables con diferencias más marcadas entre pacientes con y sin enfermedad cardiaca fueron la edad, la frecuencia cardiaca máxima y la depresión del segmento ST entre las numéricas, y el tipo de dolor torácico, la angina inducida por ejercicio y el resultado de talasemia entre las categóricas; el colesterol mostró diferencias más leves y menos consistentes. La mayor dispersión relativa se observó en el colesterol y, en términos de forma de la distribución, en la depresión del segmento ST. Los valores atípicos más notorios se encontraron en el colesterol, la presión arterial en reposo y, de forma puntual, en la frecuencia cardiaca máxima. Con esta base, en la siguiente etapa del análisis se sometieron estas impresiones exploratorias a pruebas estadísticas formales.
La etapa 3 comenzó calculando intervalos de confianza al 95% para la media de las variables numéricas principales. Para toda la muestra, la edad promedio se ubica entre 53.4 y 55.5 años, la frecuencia cardiaca máxima entre 147.0 y 152.2, la depresión del segmento ST entre 0.91 y 1.17, y el colesterol entre 240.8 y 252.5 mg/dL. Al calcular estos mismos intervalos por separado para los grupos con y sin enfermedad, se observó que los intervalos de edad, frecuencia cardiaca máxima y depresión del segmento ST no se traslapan entre grupos, lo que sugiere diferencias relevantes, mientras que los intervalos de colesterol sí se traslapan, indicando que la diferencia entre grupos es menos clara para esta variable; en todo caso, quedó explícito que la falta de traslape entre intervalos de confianza no equivale, por sí sola, a una prueba de hipótesis formal.
Las pruebas de hipótesis confirmaron en general estas impresiones. La
prueba t para la edad, que se distribuye de forma razonablemente
simétrica, arrojó una diferencia de medias estadísticamente
significativa entre pacientes con y sin enfermedad (con un intervalo de
confianza para la diferencia que no incluye el cero). Para la depresión
del segmento ST y la frecuencia cardiaca máxima, cuyas distribuciones
son asimétricas y con valores atípicos, se utilizó la prueba de Wilcoxon
en lugar de la prueba t, ya que no depende del supuesto de normalidad y
es más robusta ante ese tipo de distribuciones; ambas variables
mostraron diferencias significativas entre grupos (p < 0.05). El
colesterol, evaluado también con Wilcoxon por la presencia de un valor
atípico importante, mostró igualmente una diferencia significativa, a
pesar de que sus intervalos de confianza se traslapaban, lo que confirma
que la prueba de hipótesis formal puede detectar diferencias que el
simple traslape de intervalos no deja ver con claridad. En conjunto, de
las variables numéricas evaluadas, oldpeak,
max_hr y age fueron las que mostraron la
evidencia estadística más sólida (los valores p y estadísticos más
grandes), seguidas por chol.
t.test(age ~ ahd, data = heart)
##
## Welch Two Sample t-test
##
## data: age by ahd
## t = -4.0303, df = 300.93, p-value = 7.061e-05
## alternative hypothesis: true difference in means between group Sin_enfermedad and group Con_enfermedad is not equal to 0
## 95 percent confidence interval:
## -6.013385 -2.067682
## sample estimates:
## mean in group Sin_enfermedad mean in group Con_enfermedad
## 52.58537 56.62590
wilcox.test(oldpeak ~ ahd, data = heart)
##
## Wilcoxon rank sum test with continuity correction
##
## data: oldpeak by ahd
## W = 6037, p-value = 6.813e-13
## alternative hypothesis: true location shift is not equal to 0
wilcox.test(max_hr ~ ahd, data = heart)
##
## Wilcoxon rank sum test with continuity correction
##
## data: max_hr by ahd
## W = 16990, p-value = 1.861e-13
## alternative hypothesis: true location shift is not equal to 0
Para las variables categóricas se utilizó la prueba ji-cuadrada de
independencia, que compara las frecuencias observadas en una tabla de
contingencia contra las que se esperarían si la variable categórica y
ahd fueran independientes. El tipo de dolor torácico mostró
una asociación muy fuerte con la enfermedad (estadístico de 81.8, p <
2.2e-16), con el dolor asintomático concentrando el 73% de los pacientes
enfermos que lo presentan; el sexo también mostró una asociación
significativa (p ≈ 2.7e-06); la angina inducida por ejercicio mostró una
asociación clara (p < 0.001), con el 76% de quienes la presentan
diagnosticados con enfermedad; y el resultado de la prueba de talasemia
mostró la asociación más fuerte de todas las variables categóricas
evaluadas (p ≈ 2.2e-16), aunque con la advertencia de que dos de sus
cuatro categorías (fija y “sin dato”) tienen pocos casos, lo que puede
afectar la estabilidad de la prueba. La glucosa en ayunas, en cambio, no
mostró asociación significativa con la enfermedad (p ≈ 0.78), por lo que
se descartó como variable relevante, al igual que el resultado
electrocardiográfico en reposo.
tabla_chest_ahd <- table(heart$chest_pain, heart$ahd)
chisq.test(tabla_chest_ahd)
##
## Pearson's Chi-squared test
##
## data: tabla_chest_ahd
## X-squared = 81.816, df = 3, p-value < 2.2e-16
tabla_thal <- table(heart$thal, heart$ahd)
chisq.test(tabla_thal)
##
## Pearson's Chi-squared test
##
## data: tabla_thal
## X-squared = 83.296, df = 3, p-value < 2.2e-16
Con toda esta evidencia (exploratoria, inferencial y de correlación)
llegué a la selección final de variables para el modelado:
ahd, oldpeak, max_hr y
chest_pain. Las tres últimas mostraron, de manera
consistente, la evidencia estadística más fuerte de asociación con la
presencia de enfermedad cardiaca; la edad, aunque también mostró
evidencia fuerte, se dejó fuera de esta selección principal por su
correlación con max_hr (r = -0.39), que podría generar
inestabilidad en los coeficientes de un modelo multivariado si ambas
variables se incluyen juntas. La variable thal, a pesar de
su fuerte asociación estadística, se descartó por los problemas de
registros sin dato y categorías con pocos casos ya mencionados;
chol y rest_bp se consideraron variables
secundarias, con evidencia más débil o menos consistente; y
fbs y rest_ecg se descartaron por no mostrar
asociación clara con la enfermedad.
En la etapa 4 se construyeron modelos de regresión lineal múltiple,
con max_hr como variable respuesta, y de regresión
logística múltiple, con ahd como variable respuesta,
retomando en ambos casos la selección de variables de la etapa 3, aunque
en el caso lineal fue necesario redistribuir esas variables, ya que
max_hr deja de ser predictora para convertirse en
respuesta.
El primer modelo lineal (modelo_lm_1) explica
max_hr a partir de age, oldpeak y
ahd. Las tres variables resultaron altamente significativas
(p < 0.003 en los tres casos) y con coeficientes negativos: cada año
adicional de edad se asocia con una disminución de 0.75 latidos por
minuto en la frecuencia cardiaca máxima; cada unidad adicional de
depresión del segmento ST se asocia con una disminución de 3.21 latidos
por minuto; y los pacientes con enfermedad cardiaca alcanzan, en
promedio, casi 13 latidos por minuto menos que los pacientes sin
enfermedad, siendo éste el coeficiente de mayor magnitud del modelo. El
modelo explica alrededor del 29.1% de la variabilidad de
max_hr (R² ajustada ≈ 28.3%), una proporción moderada pero
razonable considerando el número reducido de predictoras. Al revisar los
residuales no se observaron patrones evidentes de curvatura ni
heterocedasticidad marcada, aunque sí algunos residuales grandes
(superiores a 50 latidos por minuto en valor absoluto, en su mayoría
negativos), lo que indica que existen pacientes cuya frecuencia cardiaca
real es notablemente menor a la predicha por el modelo, posiblemente por
factores no incluidos (por ejemplo, el uso de medicamentos que reducen
directamente la frecuencia cardiaca).
modelo_lm_1 <- lm(max_hr ~ age + oldpeak + ahd, data = heart)
tidy(modelo_lm_1, conf.int = TRUE)
## # A tibble: 4 x 7
## term estimate std.error statistic p.value conf.low conf.high
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 200. 6.82 29.3 7.10e-90 186. 213.
## 2 age -0.754 0.127 -5.91 9.12e- 9 -1.00 -0.503
## 3 oldpeak -3.21 1.07 -3.00 2.89e- 3 -5.31 -1.11
## 4 ahdCon_enfermedad -12.9 2.50 -5.17 4.25e- 7 -17.8 -7.99
glance(modelo_lm_1) |> select(r.squared, adj.r.squared, AIC, BIC)
## # A tibble: 1 x 4
## r.squared adj.r.squared AIC BIC
## <dbl> <dbl> <dbl> <dbl>
## 1 0.291 0.283 2662. 2680.
Como segunda versión ajusté un modelo más amplio
(modelo_lm_2), agregando sex,
rest_bp y chol a las tres variables
anteriores, ya que en el análisis exploratorio se habían identificado
como variables con cierto potencial explicativo para
max_hr, aunque con evidencia más débil. Este segundo modelo
mejora ligeramente el ajuste (R² ≈ 0.314, AIC de 2657.29 frente a
2661.65 del modelo más simple), pero dos de sus seis predictoras,
sex y chol, no resultan estadísticamente
significativas, y el comportamiento de los residuales no mejora de forma
clara respecto al primer modelo. Ante una mejora tan modesta en el
ajuste, y considerando el principio de parsimonia, decidí conservar el
modelo más simple (modelo_lm_1) como primera aproximación
para estimar la frecuencia cardiaca máxima promedio. De forma
exploratoria, también utilicé la función step() para una
selección automática de variables por AIC a partir de todas las
variables disponibles; el resultado sugirió un modelo distinto al que yo
había elegido manualmente (con variables adicionales como el tipo de
dolor torácico, la presión arterial, el colesterol, la angina inducida,
la pendiente del segmento ST y la propia condición de enfermedad), lo
que ilustra que un criterio puramente estadístico de selección
automática no siempre coincide con una selección de variables guiada por
la evidencia inferencial previa y por consideraciones de
interpretabilidad.
Para el modelo logístico, la variable respuesta se recodificó como
ahd_bin (1 para pacientes con enfermedad, 0 en caso
contrario). El primer modelo logístico (modelo_log_1)
utiliza oldpeak, max_hr y
chest_pain como predictoras, exactamente la selección de la
etapa 3. Los momios (odds) de tener enfermedad cardiaca se multiplican,
en promedio, por 2.13 por cada unidad adicional de depresión del
segmento ST, y por 0.976 por cada latido adicional en la frecuencia
cardiaca máxima (es decir, disminuyen ligeramente); en cuanto al dolor
torácico, un paciente con dolor asintomático tiene, en promedio, siete
veces más momios de enfermedad que uno con dolor típico, mientras que
las categorías de dolor no típico y no anginal no mostraron diferencias
significativas frente a la categoría de referencia. El modelo alcanzó
una exactitud de clasificación del 77.2% sobre los mismos datos con los
que fue ajustado (234 de 303 pacientes correctamente clasificados, con
41 falsos negativos y 28 falsos positivos), y un AIC de 294.4.
heart <- heart |>
mutate(ahd_bin = if_else(ahd == "Con_enfermedad", 1, 0))
modelo_log_1 <- glm(
ahd_bin ~ oldpeak + max_hr + chest_pain,
data = heart,
family = binomial
)
tidy(modelo_log_1, exponentiate = TRUE, conf.int = TRUE)
## # A tibble: 6 x 7
## term estimate std.error statistic p.value conf.low conf.high
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 (Intercept) 5.81 1.29 1.37 1.71e-1 0.467 73.9
## 2 oldpeak 2.13 0.164 4.62 3.91e-6 1.56 2.98
## 3 max_hr 0.976 0.00731 -3.33 8.62e-4 0.962 0.990
## 4 chest_painNontypical 1.39 0.660 0.494 6.22e-1 0.385 5.26
## 5 chest_painNonanginal 0.878 0.584 -0.223 8.23e-1 0.287 2.90
## 6 chest_painAsymptomatic 7.10 0.564 3.48 5.06e-4 2.45 22.9
glance(modelo_log_1) |> select(AIC, BIC, deviance, null.deviance)
## # A tibble: 1 x 4
## AIC BIC deviance null.deviance
## <dbl> <dbl> <dbl> <dbl>
## 1 294. 317. 282. 418.
Como segunda versión del modelo logístico probé una combinación
distinta de predictoras: ex_ang, ca,
rest_bp y sex (modelo_log_2). La
justificación fue, por un lado, sustituir oldpeak por
ex_ang, ya que ambas se refieren a condiciones medidas
durante la prueba de esfuerzo y ex_ang guarda cierto
paralelismo conceptual con chest_pain; por otro lado,
incorporar rest_bp como contraste con lo observado en
esfuerzo, ca por ser un indicador directo de afectación
vascular, y sex por reflejar diferencias poblacionales
relevantes en los rangos normales de algunas mediciones clínicas. Este
segundo modelo obtuvo un AIC de 283.3 (menor que el de
modelo_log_1) y una exactitud de clasificación ligeramente
mayor, de 77.9%, con todos sus coeficientes estadísticamente
significativos, a diferencia de modelo_log_1, donde dos de
las categorías de chest_pain no lo eran. Aunque ambos
modelos tienen un número similar de parámetros
(modelo_log_1 incluye cinco coeficientes más el intercepto,
por las cuatro categorías de chest_pain, y
modelo_log_2 incluye cuatro, por tratarse de variables
binarias o casi binarias), considero que modelo_log_2
ofrece un mejor balance entre ajuste, significancia estadística de sus
coeficientes e interpretabilidad, por lo que lo conservaría como primera
aproximación, sin dejar de reconocer que la selección de variables de la
etapa 3 (oldpeak, max_hr,
chest_pain) seguía siendo informativa y estadísticamente
defendible, aunque no resultó ser, en este caso, la que mejor ajuste
ofreció entre las combinaciones exploradas.
En ambos tipos de modelo, lineal y logístico, es importante señalar que se ajustaron y evaluaron sobre la misma muestra que se usó para entrenarlos, sin un conjunto de prueba independiente, por lo que las medidas de desempeño reportadas (R², exactitud, AIC) reflejan qué tan bien los modelos describen a estos 303 pacientes, no qué tan bien generalizarían a pacientes nuevos.
El análisis exploratorio permitió identificar, desde una etapa
temprana del proyecto, que la edad, la frecuencia cardiaca máxima y la
depresión del segmento ST son las variables numéricas con diferencias
más marcadas entre pacientes con y sin enfermedad cardiaca, y que el
tipo de dolor torácico, la angina inducida por ejercicio y el resultado
de la prueba de talasemia son las variables categóricas con asociaciones
visuales más claras. El análisis inferencial confirmó estadísticamente
todas estas impresiones (con pruebas t, Wilcoxon y ji-cuadrada, todas
con p < 0.05 para las variables mencionadas) y permitió, además,
descartar formalmente a la glucosa en ayunas y al resultado
electrocardiográfico en reposo como variables relevantes para explicar
la enfermedad, algo que en el EDA sólo se había intuido. Las variables
finalmente seleccionadas para el modelado, ahd,
oldpeak, max_hr y chest_pain,
resultaron ser, en efecto, informativas: las tres funcionaron como
predictoras estadísticamente significativas del modelo logístico
principal, y oldpeak y ahd (junto con la edad,
reincorporada en esta etapa) resultaron igualmente relevantes para
explicar max_hr en el modelo lineal.
En cuanto al modelado, el mejor modelo lineal explorado
(modelo_lm_2, con seis predictoras) explica alrededor del
31.4% de la variabilidad de la frecuencia cardiaca máxima, aunque el
modelo más simple de tres predictoras (modelo_lm_1) ofrece
un ajuste casi idéntico (29.1%) con menos variables y mayor
interpretabilidad, por lo que se prefirió este último. En el caso
logístico, el modelo con ex_ang, ca,
rest_bp y sex (modelo_log_2)
superó ligeramente, en AIC y exactitud, al modelo con las variables
originalmente seleccionadas en la etapa 3 (modelo_log_1),
alcanzando una exactitud de clasificación de 77.9% frente a 77.2%, lo
que sugiere que, si bien la selección de variables basada en pruebas de
hipótesis fue un buen punto de partida, no necesariamente identificó la
combinación óptima de predictoras para el modelo final.
El problema más relevante detectado durante el desarrollo del
proyecto fue un error de codificación en la variable slope,
cometido en la etapa 1. El documento de descripción del dataset sugería
que esta variable tomaba valores de 0 a 2, y con base en ello la
codifiqué como factor(slope, levels = 0:2); sin embargo,
los datos crudos en realidad contienen los valores 1, 2 y 3, sin ningún
caso con valor 0. Al restringir los niveles válidos de 0 a 2, los 21
registros con valor 3 (cerca del 7% de la muestra) se convirtieron
silenciosamente en valores faltantes, y como ese archivo ya recodificado
fue el que se exportó a .csv y el que alimentó todas las
actividades posteriores, la pérdida de esos 21 valores se propagó a lo
largo de todo el proyecto sin posibilidad de recuperación, más allá de
volver a partir del archivo Heart.csv original. En
actividades posteriores ajusté el rango de niveles utilizado al
recodificar slope (primero a 0:3 y finalmente a 1:3, que es
el rango realmente observado en los datos), pero esos ajustes sólo
corrigieron la definición de las categorías válidas hacia adelante; no
pudieron recuperar los 21 valores ya perdidos desde la etapa 1.
Afortunadamente, slope no llegó a formar parte de las
variables predictoras de ninguno de los modelos finales, por lo que este
error no comprometió los resultados del modelado, pero sí constituye una
lección clara sobre la importancia de revisar los conteos de una
variable categórica inmediatamente después de recodificarla, y no sólo
confiar en la documentación de origen del dataset.
Un segundo problema, de naturaleza más recurrente que grave, fue que
el formato .csv no conserva la información de tipo factor
de R: cada vez que se reimportó el archivo limpio en una nueva etapa fue
necesario reaplicar manualmente la conversión a factor de las variables
categóricas (sex, fbs, rest_ecg,
ex_ang, slope, chest_pain,
thal y ahd), repitiendo el mismo bloque de
código en cada documento. Esto no generó errores en los resultados, pero
sí fue una fuente de trabajo repetitivo que, en un proyecto de mayor
escala, convendría resolver guardando la base limpia en un formato que
sí preserve los tipos de dato, como .rds.
También surgieron dificultades de interpretación relacionadas con
variables categóricas de varios niveles, en particular
chest_pain y thal. Al tener más de dos
categorías, cada modelo que las incluye requiere interpretar cada
categoría por separado en comparación con una categoría de referencia,
lo que resulta menos directo que interpretar una variable numérica o
binaria; además, no todas las categorías resultaron informativas (por
ejemplo, las categorías “no típico” y “no anginal” de
chest_pain no se distinguieron claramente de la categoría
de referencia en el modelo logístico), lo que obliga a matizar las
conclusiones categoría por categoría en lugar de hacer afirmaciones
generales sobre la variable completa. De forma relacionada, la
advertencia identificada en la etapa 3 sobre la posible colinealidad
entre age y max_hr (correlación de -0.39)
requirió tomar decisiones explícitas sobre qué variable incluir en cada
modelo, dependiendo de si max_hr era la variable respuesta
o una posible predictora, decisiones que fueron revisadas más de una vez
a lo largo del proyecto conforme se compararon distintas versiones de
los modelos.
De este dataset aprendí que, aunque se trata de un conjunto de datos
relativamente pequeño y ya ampliamente utilizado en la literatura de
aprendizaje estadístico, requiere un trabajo cuidadoso de limpieza antes
de poder analizarse: varias variables categóricas están codificadas como
números, algunos valores faltantes no son evidentes en un primer resumen
numérico, y la documentación de origen no siempre coincide exactamente
con los valores observados en los datos, como ocurrió con
slope. Las variables que parecieron más relevantes a lo
largo de todo el proyecto, de forma consistente entre el EDA, las
pruebas inferenciales y el modelado, fueron la depresión del segmento ST
(oldpeak), la frecuencia cardiaca máxima
(max_hr), la edad (age) y el tipo de dolor
torácico (chest_pain); en el modelado logístico final
también resultaron relevantes la angina inducida por ejercicio
(ex_ang), el número de vasos observados (ca) y
el sexo (sex).
El análisis exploratorio aportó una primera aproximación, necesaria pero no suficiente, a las relaciones presentes en los datos: permitió formular hipótesis razonables sobre qué variables podrían asociarse con la enfermedad cardiaca y detectar valores atípicos y patrones de dispersión que orientaron las decisiones metodológicas posteriores, pero por sí solo no puede confirmar si esas relaciones son estadísticamente significativas. El análisis inferencial aportó precisamente esa confirmación formal, mediante intervalos de confianza y pruebas de hipótesis específicas para cada tipo de variable, y permitió descartar con mayor seguridad variables que en el EDA parecían poco informativas (como la glucosa en ayunas). El modelado estadístico, finalmente, aportó una forma de combinar varias variables a la vez y cuantificar su relación conjunta con los desenlaces de interés, mostrando que un modelo de regresión logística con apenas tres o cuatro predictoras puede alcanzar una exactitud de clasificación cercana al 78% sobre la propia muestra de entrenamiento, y que un modelo de regresión lineal con tres predictoras explica cerca de una tercera parte de la variabilidad de la frecuencia cardiaca máxima.
Lo que debe revisarse con cautela, antes de dar demasiado peso a
estos resultados, es que ambos modelos se evaluaron sobre la misma
muestra con la que fueron ajustados, sin un conjunto de prueba
independiente ni validación cruzada, por lo que las medidas de desempeño
reportadas son probablemente optimistas y no reflejan necesariamente qué
tan bien generalizarían estos modelos a pacientes nuevos. Tampoco deben
interpretarse los coeficientes de estos modelos como relaciones
causales, ni mucho menos como criterios de diagnóstico individual: son
asociaciones estadísticas ajustadas, obtenidas de una muestra particular
de 303 pacientes, útiles para orientar hipótesis futuras pero
insuficientes, por sí solas, para sustentar decisiones clínicas. Las
siguientes etapas naturales de este proyecto serían precisamente abordar
esa limitación mediante validación con datos independientes o validación
cruzada, además de explorar con mayor formalidad la selección de
variables (por ejemplo, contrastando de forma sistemática la selección
basada en pruebas de hipótesis con métodos de selección automática como
el usado con step()).
James, G., Witten, D., Hastie, T., Tibshirani, R., & Taylor, J. (2023). An Introduction to Statistical Learning with Applications in R. https://www.statlearning.com/
Robinson, D., Hayes, A., & Couch, S. (s. f.). broom: Convert statistical objects into tidy tibbles. https://broom.tidymodels.org/
Waring, E., Quinn, M., McNamara, A., de la Rubia, E. A., Zhu, H., & Ellis, S. (s. f.). skimr: Compact and Flexible Summaries of Data. https://docs.ropensci.org/skimr/
Wickham, H. (s. f.). ggplot2: Create Elegant Data Visualisations Using the Grammar of Graphics. https://ggplot2.tidyverse.org/
Wickham, H., Çetinkaya-Rundel, M., & Grolemund, G. (s. f.). R for Data Science. https://r4ds.hadley.nz/
Detrano, R., Janosi, A., Steinbrunn, W., Pfisterer, M., Schmid, J., Sandhu, S., Guppy, K., Lee, S., & Froelicher, V. (1989). International application of a new probability algorithm for the diagnosis of coronary artery disease. American Journal of Cardiology, 64(5), 304-310. (Fuente original del conjunto de datos Heart Disease / Cleveland Clinic Foundation.)