Analisis de Accidentes Cerebrovasculares
Problema a abordar
El trabajo consiste en realizar un analisis de clasificación y predicción de Accidentes Cerebrovasculares. El objetivo es identificar a partir de ciertas variables la probabilidad de sufrir un accidente cerebrovascular.
Fuente de información: “stroke.csv” extraido de Kaggle. Muestra de 5110 observaciones.
Los datos incluyen información sobre:
-Características demográficas (edad, género)
-Condiciones de salud (hipertensión, enfermedad cardíaca)
-Habitos de vida (estado de fumador)
-Medidas clínicas (nivel promedio de glucosa)
Variables
id: Identicador de cada observación
gender: Variable Categórica con valores “Male” (masculino) y “Female” (femenino).
age: Una variable numérica que representa la edad del individuo en años.
hypertension: Hipertensión expresado de manera
binaria (0 o 1), como presencia o ausencia.
heart_disease: Una variable binaria (0 o 1) que indica
si el individuo tiene enfermedad cardíaca (1) o no (0)
ever_married: Una variable categórica que indica si el individuo ha estado casado alguna vez, con valores “Yes” (sí) o “No”.
work_type: variable categórica que describe el tipo de trabajo del individuo, con valores como “Private” (privado), Govt_job (trabajo gubernamental) y “Self-employed” (autónomo).
Residence_type: variable categórica que indica el tipo de residencia del individuo, con valores “Urban” (urbano) o “Rural”
avg_glucose_level: representa el nivel promedio de glucosa en sangre del individuo.
bmi: Rerepresenta el indice de Masa Corporal (IMC) del individuo.
smoking_status: variable categórica que indica el estado de fumador del individuo, con valores como “formerly smoked” (ex fumador), “never smoked” (nunca fumó) y “smokes” (fuma).
stroke Una variable binaria (0 o 1) que indica si el individuo ha sufrido un accidente cerebrovascular (1) o no (0).
Salidas:
-Ranking de importancia de las variables predictoras
-Odds ratios e intervalos de confianza para cada variable
-Curva ROC y valor AUC con intervalos de confianza
-Informe de los hallazgos y su interpretación clínica
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.0
## ✔ ggplot2 3.4.4 ✔ tibble 3.2.1
## ✔ lubridate 1.9.3 ✔ tidyr 1.3.0
## ✔ purrr 1.0.1
## ── 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
## Loading required package: lattice
##
## Attaching package: 'caret'
##
## The following object is masked from 'package:purrr':
##
## lift
##
## Attaching package: 'MASS'
##
## The following object is masked from 'package:dplyr':
##
## select
## Type 'citation("pROC")' for a citation.
##
## Attaching package: 'pROC'
##
## The following objects are masked from 'package:stats':
##
## cov, smooth, var
## Thank you for using fastDummies!
## To acknowledge our work, please cite the package:
## Kaplan, J. & Schlegel, B. (2023). fastDummies: Fast Creation of Dummy (Binary) Columns and Rows from Categorical Variables. Version 1.7.1. URL: https://github.com/jacobkap/fastDummies, https://jacobkap.github.io/fastDummies/.
## Loading required package: Matrix
##
## Attaching package: 'Matrix'
##
## The following objects are masked from 'package:tidyr':
##
## expand, pack, unpack
##
## Loaded glmnet 4.1-8
1.Carga de Datos
2.Analisis Exploratorio
## id gender age hypertension heart_disease ever_married work_type
## 1 9046 Male 67 0 1 Yes Private
## 2 51676 Female 61 0 0 Yes Self-employed
## 3 31112 Male 80 0 1 Yes Private
## 4 60182 Female 49 0 0 Yes Private
## 5 1665 Female 79 1 0 Yes Self-employed
## 6 56669 Male 81 0 0 Yes Private
## Residence_type avg_glucose_level bmi smoking_status stroke
## 1 Urban 228.69 36.6 formerly smoked 1
## 2 Rural 202.21 <NA> never smoked 1
## 3 Rural 105.92 32.5 never smoked 1
## 4 Urban 171.23 34.4 smokes 1
## 5 Rural 174.12 24 never smoked 1
## 6 Urban 186.21 29 formerly smoked 1
##Imputación de Valores faltantes
#Ante la presencia de valores nulos, se procede a imputar valores faltantes por medio de MissRanger
datos$gender <- as.factor(datos$gender)
datos$hypertension <- as.factor(datos$hypertension)
datos$ever_married <- as.factor(datos$ever_married)
datos$work_type <- as.factor(datos$work_type)
datos$Residence_type <- as.factor(datos$Residence_type)
datos$bmi <- as.numeric(datos$bmi)
datos$smoking_status <- as.factor(datos$smoking_status)
# Imputar valores faltantes con missRanger
datos_imputados <- missRanger(datos)##
## Missing value imputation by random forests
##
## Variables to impute: bmi, smoking_status
## Variables used to impute: id, gender, age, hypertension, heart_disease, ever_married, work_type, Residence_type, avg_glucose_level, bmi, smoking_status, stroke
##
## iter 1
##
|
| | 0%
|
|=================================== | 50%
|
|======================================================================| 100%
## iter 2
##
|
| | 0%
|
|=================================== | 50%
|
|======================================================================| 100%
## iter 3
##
|
| | 0%
|
|=================================== | 50%
|
|======================================================================| 100%
## iter 4
##
|
| | 0%
|
|=================================== | 50%
|
|======================================================================| 100%
## [1] 0
##
## formerly smoked never smoked smokes
## 885 1892 789
##
## formerly smoked never smoked smokes
## 973 3336 801
# Definir la función age_cohort
age_intervalos <- function(age) {
ifelse(age >= 0 & age <= 20, "0-20",
ifelse(age > 20 & age <= 40, "20-40",
ifelse(age > 40 & age <= 50, "40-50",
ifelse(age > 50 & age <= 60, "50-60",
ifelse(age > 60, "60+", NA)))))
}
edad <- datos_imputados %>%
mutate(age_group = sapply(age, age_intervalos))%>%
arrange(age_group)
plot_missing(datos_imputados)
# EDA de datos limpios
3.Analisis de Regresión Logísitica
# Seleccionar el mejor modelo en funcion a Akaike
full_model <- glm(stroke ~ ., data=datos_imputados, family=binomial)
step_model <- step(full_model, direction="both")## Start: AIC=1609.12
## stroke ~ id + gender + age + hypertension + heart_disease + ever_married +
## work_type + Residence_type + avg_glucose_level + bmi + smoking_status
##
## Df Deviance AIC
## - gender 2 1575.2 1605.2
## - bmi 1 1575.2 1607.2
## - id 1 1575.3 1607.3
## - Residence_type 1 1575.4 1607.4
## - work_type 4 1581.7 1607.7
## - ever_married 1 1576.0 1608.0
## - heart_disease 1 1577.0 1609.0
## <none> 1575.1 1609.1
## - hypertension 1 1580.7 1612.7
## - smoking_status 2 1583.6 1613.6
## - avg_glucose_level 1 1585.1 1617.1
## - age 1 1774.7 1806.7
##
## Step: AIC=1605.19
## stroke ~ id + age + hypertension + heart_disease + ever_married +
## work_type + Residence_type + avg_glucose_level + bmi + smoking_status
##
## Df Deviance AIC
## - bmi 1 1575.3 1603.3
## - id 1 1575.4 1603.4
## - Residence_type 1 1575.5 1603.5
## - work_type 4 1581.7 1603.7
## - ever_married 1 1576.0 1604.0
## - heart_disease 1 1577.0 1605.0
## <none> 1575.2 1605.2
## - hypertension 1 1580.8 1608.8
## + gender 2 1575.1 1609.1
## - smoking_status 2 1583.7 1609.7
## - avg_glucose_level 1 1585.1 1613.1
## - age 1 1775.1 1803.1
##
## Step: AIC=1603.26
## stroke ~ id + age + hypertension + heart_disease + ever_married +
## work_type + Residence_type + avg_glucose_level + smoking_status
##
## Df Deviance AIC
## - id 1 1575.5 1601.5
## - Residence_type 1 1575.5 1601.5
## - work_type 4 1581.7 1601.7
## - ever_married 1 1576.1 1602.1
## - heart_disease 1 1577.0 1603.0
## <none> 1575.3 1603.3
## + bmi 1 1575.2 1605.2
## - hypertension 1 1581.0 1607.0
## + gender 2 1575.2 1607.2
## - smoking_status 2 1583.8 1607.8
## - avg_glucose_level 1 1586.1 1612.1
## - age 1 1779.2 1805.2
##
## Step: AIC=1601.48
## stroke ~ age + hypertension + heart_disease + ever_married +
## work_type + Residence_type + avg_glucose_level + smoking_status
##
## Df Deviance AIC
## - Residence_type 1 1575.8 1599.8
## - work_type 4 1582.1 1600.1
## - ever_married 1 1576.3 1600.3
## - heart_disease 1 1577.3 1601.3
## <none> 1575.5 1601.5
## + id 1 1575.3 1603.3
## + bmi 1 1575.4 1603.4
## - hypertension 1 1581.3 1605.3
## + gender 2 1575.4 1605.4
## - smoking_status 2 1584.1 1606.1
## - avg_glucose_level 1 1586.2 1610.2
## - age 1 1779.4 1803.4
##
## Step: AIC=1599.76
## stroke ~ age + hypertension + heart_disease + ever_married +
## work_type + avg_glucose_level + smoking_status
##
## Df Deviance AIC
## - work_type 4 1582.4 1598.4
## - ever_married 1 1576.6 1598.6
## - heart_disease 1 1577.5 1599.5
## <none> 1575.8 1599.8
## + Residence_type 1 1575.5 1601.5
## + id 1 1575.5 1601.5
## + bmi 1 1575.7 1601.7
## - hypertension 1 1581.5 1603.5
## + gender 2 1575.7 1603.7
## - smoking_status 2 1584.6 1604.6
## - avg_glucose_level 1 1586.6 1608.6
## - age 1 1780.4 1802.4
##
## Step: AIC=1598.36
## stroke ~ age + hypertension + heart_disease + ever_married +
## avg_glucose_level + smoking_status
##
## Df Deviance AIC
## - ever_married 1 1583.4 1597.4
## - heart_disease 1 1584.3 1598.3
## <none> 1582.4 1598.4
## + work_type 4 1575.8 1599.8
## + id 1 1582.0 1600.0
## + Residence_type 1 1582.1 1600.1
## + bmi 1 1582.3 1600.3
## - hypertension 1 1587.9 1601.9
## + gender 2 1582.3 1602.3
## - smoking_status 2 1590.8 1602.8
## - avg_glucose_level 1 1593.9 1607.9
## - age 1 1795.0 1809.0
##
## Step: AIC=1597.41
## stroke ~ age + hypertension + heart_disease + avg_glucose_level +
## smoking_status
##
## Df Deviance AIC
## <none> 1583.4 1597.4
## - heart_disease 1 1585.6 1597.6
## + ever_married 1 1582.4 1598.4
## + work_type 4 1576.6 1598.6
## + Residence_type 1 1583.1 1599.1
## + id 1 1583.1 1599.1
## + bmi 1 1583.4 1599.4
## - hypertension 1 1589.0 1601.0
## + gender 2 1583.4 1601.4
## - smoking_status 2 1591.5 1601.5
## - avg_glucose_level 1 1594.6 1606.6
## - age 1 1822.8 1834.8
Definición del AIC:
AIC = 2k - 2ln(L) Donde k es el número de parámetros en el modelo y L es el valor máximo de la función de verosimilitud para el modelo.
El AIC busca un equilibrio entre la bondad de ajuste del modelo y su complejidad. Valores más bajos de AIC indican modelos preferibles.
En orden de importancia
age (AIC aumentaría a 1838.4 si se elimina) avg_glucose_level (AIC: 1608.8) hypertension (AIC: 1603.0) smoking_status (AIC: 1601.5) heart_disease (AIC: 1599.7)
-La edad es, con mucho, el predictor más importante del accidente cerebrovascular en este modelo.
-El nivel promedio de glucosa es el segundo predictor más importante.
-La hipertensión y el estado de fumador también son factores significativos.
-La enfermedad cardíaca se mantuvo en el modelo, aunque su contribución es relativamente pequeña.
#Modelamos con los predictores del AIC
modelo_aic <- glm(stroke ~ age + hypertension + heart_disease + avg_glucose_level + smoking_status, data = datos_imputados, family = binomial)
modelo_ampliado <- glm(stroke ~ age + hypertension + heart_disease + avg_glucose_level + smoking_status + ever_married, bmi, data = datos_imputados, family = binomial)## Warning in eval(family$initialize): non-integer #successes in a binomial glm!
## df AIC
## modelo_aic 7 1597.412
## modelo_ampliado 8 48308.354
# Comparamos rendimiento
roc_aic <- roc(datos_imputados$stroke, predict(modelo_aic, type = "response"))## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
## Area under the curve: 0.847
## Area under the curve: 0.8478
Complejidad del modelo:
El AIC mucho más bajo del modelo_aic (1599.468 vs 48434.618) indica que este modelo proporciona un mejor equilibrio entre la bondad de ajuste y la complejidad del modelo.
Basándonos únicamente en el AIC, el modelo_aic es claramente preferible. Ofrece una mejor explicación de los datos con mucha menos complejidad.
# Predictores seleccionados según AIC
vars_aic <- c("stroke", "age", "hypertension", "heart_disease", "avg_glucose_level", "smoking_status")
# Semilla de reproducibilidad
set.seed(123)
# Crear una partición de datos con 70% para entrenamiento y 30% para prueba
particion <- createDataPartition(datos_imputados$stroke, p = 0.7, list = FALSE)
datos_train <- datos_imputados[particion, ]
datos_test <- datos_imputados[-particion, ]
# Subconjuntos de datos de entrenamiento y prueba con las variables seleccionadas
datos_train_AIC <- subset(datos_train, select = vars_aic)
datos_test_AIC <- subset(datos_test, select = vars_aic)
# Eliminamos NA
datos_train_AIC <- na.omit(datos_train_AIC)
datos_test_AIC <- na.omit(datos_test_AIC)
# regresión logística con variables AIC
modelo_aic <- glm(stroke ~ age + hypertension + heart_disease + avg_glucose_level + smoking_status, data = datos_train_AIC, family = binomial)
# modelo AIC
summary(modelo_aic)##
## Call:
## glm(formula = stroke ~ age + hypertension + heart_disease + avg_glucose_level +
## smoking_status, family = binomial, data = datos_train_AIC)
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -7.372660 0.473624 -15.566 < 2e-16 ***
## age 0.069087 0.006382 10.826 < 2e-16 ***
## hypertension1 0.432991 0.195132 2.219 0.02649 *
## heart_disease 0.250724 0.233860 1.072 0.28367
## avg_glucose_level 0.004291 0.001385 3.098 0.00195 **
## smoking_statusnever smoked -0.360420 0.186677 -1.931 0.05352 .
## smoking_statussmokes -0.046477 0.247735 -0.188 0.85118
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 1361.6 on 3576 degrees of freedom
## Residual deviance: 1088.8 on 3570 degrees of freedom
## AIC: 1102.8
##
## Number of Fisher Scoring iterations: 7
## (Intercept) age
## 0.0006281953 1.0715289237
## hypertension1 heart_disease
## 1.5418629528 1.2849551127
## avg_glucose_level smoking_statusnever smoked
## 1.0042997625 0.6973834111
## smoking_statussmokes
## 0.9545863780
# Modelo ampliado con todas las variables
modelo_ampliado <- glm(stroke ~ ., data = datos_train, family = binomial)
# Comparar AIC
print(AIC(modelo_aic, modelo_ampliado))## df AIC
## modelo_aic 7 1102.845
## modelo_ampliado 17 1111.036
# Comparar rendimiento
predicciones_aic <- predict(modelo_aic, newdata = datos_train_AIC, type = "response")
predicciones_ampliado <- predict(modelo_ampliado, newdata = datos_train, type = "response")
roc_aic <- roc(datos_train_AIC$stroke, predicciones_aic)## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
## Area under the curve: 0.8464
## Area under the curve: 0.8499
La edad (age) es muy significativa (p < 2e-16) y tiene un odds ratio de 1.0727, lo que significa que por cada año de aumento en la edad, las probabilidades de tener un accidente cerebrovascular aumentan en un 7.27%.
La hipertensión (hypertension) no es estadísticamente significativa (p = 0.1160), pero tiene un odds ratio de 1.3613, sugiriendo un aumento del 36.13% en las probabilidades de accidente cerebrovascular.
La enfermedad cardíaca (heart_disease) no es estadísticamente significativa (p = 0.1981), con un odds ratio de 1.3348.
El nivel promedio de glucosa (avg_glucose_level) es significativo (p = 0.0117) con un odds ratio de 1.0035, indicando un pequeño aumento en las probabilidades por cada unidad de aumento en el nivel de glucosa.
El no haber fumado es significativo (p = 0.0150) y tiene un efecto digamos “protector”, con un odds ratio de 0.6416, lo que sugiere una reducción del 35.84% en las probabilidades de accidente cerebrovascular comparado con la categoría de referencia.
Ser fumador (smoking_statussmokes) no es significativo (p = 0.7718).
Por otro lado, podemos afirmar que el modelo tiene un buen poder predictivo, con un AUC de 0.8453, lo que se considera generalmente como un rendimiento bueno a excelente.
La diferencia en AUC entre el modelo AIC y el modelo ampliado es muy pequeña (0.0025), lo que sugiere que las variables adicionales en el modelo ampliado no aportan mucha mejora en la capacidad predictiva.
La edad y el nivel de glucosa son los predictores más significativos y robustos en este modelo. El efecto “protector” de nunca haber fumado es interesante y significativo.
#intervalos de confianza
ci_aic <- ci(roc_aic)
ci_ampliado <- ci(roc_ampliado)
# Imprimir los resultados
print("Intervalo de confianza para el AUC del Modelo AIC:")## [1] "Intervalo de confianza para el AUC del Modelo AIC:"
## 95% CI: 0.8218-0.8711 (DeLong)
## [1] "Intervalo de confianza para el AUC del Modelo Ampliado:"
## 95% CI: 0.8251-0.8747 (DeLong)
# Graficar las curvas ROC con intervalos de confianza
plot(roc_aic, main="Curva ROC - Comparación de Modelos con IC",
col="blue", lwd=2)
lines(roc_ampliado, col="red", lwd=2)Comparación de modelos:
El ROC muestra cómo se comporta el modelo a través de diferentes umbrales de clasificación, permitiendo evaluar su rendimiento general.
El área bajo la curva ROC (AUC-ROC) proporciona una medida única del rendimiento del modelo, independiente del umbral de clasificación elegido.
Permite comparar fácilmente diferentes modelos de clasificación en un mismo gráfico. Ayuda a determinar qué modelo tiene un mejor rendimiento general.
Las curvas están casi superpuestas, lo que sugiere que el rendimiento de ambos modelos es muy similar, lo cual es consistente con el hecho de que los valores de AUC (Area Under the Curve) son 0.8453 para el Modelo AIC y 0.8478 para el Modelo Ampliado.
Basándonos en estos intervalos de confianza, podemos afirmar con un 95% de confianza que el verdadero AUC para ambos modelos se encuentra entre aproximadamente 0.82 y 0.87. La superposición de los intervalos sugiere que no hay una diferencia estadísticamente significativa entre los dos modelos en términos de capacidad discriminativa.
Dado que no hay una diferencia significativa entre los modelos, sería razonable preferir el Modelo AIC por su simplicidad (principio de parsimonia). El Modelo AIC proporciona esencialmente el mismo rendimiento predictivo con menos variables, lo que lo hace más fácil de interpretar y posiblemente más robusto en la generalización a nuevos datos.
# prediccio en el conjunto de prueba
predicciones <- predict(modelo_aic, newdata = datos_test_AIC, type = "response")
summary(predicciones)## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0005953 0.0041272 0.0167256 0.0457966 0.0547986 0.4842657
#Con umbral de 0.2
predicciones_binarias <- ifelse(predicciones > 0.2, 1, 0)
valores_reales <- datos_test_AIC$stroke
# Crear matriz de confusión
cm <- confusionMatrix(factor(predicciones_binarias), factor(valores_reales))
print(cm)## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 1399 59
## 1 54 21
##
## Accuracy : 0.9263
## 95% CI : (0.912, 0.9389)
## No Information Rate : 0.9478
## P-Value [Acc > NIR] : 0.9999
##
## Kappa : 0.2322
##
## Mcnemar's Test P-Value : 0.7067
##
## Sensitivity : 0.9628
## Specificity : 0.2625
## Pos Pred Value : 0.9595
## Neg Pred Value : 0.2800
## Prevalence : 0.9478
## Detection Rate : 0.9126
## Detection Prevalence : 0.9511
## Balanced Accuracy : 0.6127
##
## 'Positive' Class : 0
##
# métricas específicas
sensibilidad <- cm$byClass['Sensitivity']
especificidad <- cm$byClass['Specificity']
precision <- cm$byClass['Precision']
exactitud <- cm$overall['Accuracy']
f1_score <- cm$byClass['F1']
# Calcular la curva ROC y AUC para los datos de prueba
roc_test <- roc(datos_test_AIC$stroke, predicciones)## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
auc_test <- auc(roc_test)
# ROC de prueba
plot(roc_test, main = "Curva ROC - Datos de Prueba", col = "blue")
abline(a = 0, b = 1, lty = 2, col = "gray")Sensibilidad (0.9621): Alta, indicando que la mayoría de los casos positivos reales fueron correctamente identificados.
Especificidad (0.2625): Baja, lo que sugiere que el modelo tiene dificultad para identificar correctamente los casos negativos.
Precisión (0.9595): Alta, indicando que la mayoría de las predicciones positivas son correctas.
Exactitud (0.9256): Alta, mostrando un buen rendimiento general del modelo.
F1-Score (0.9608): Alta, lo que indica un buen balance entre precisión y sensibilidad.
Luego de comparar con el umbral en 0.5 y 0.1, es 0.2 el umbral que proporciona un mejor balance entre sensibilidad (0.9621) y especificidad (0.2625).
4.Conclusión
Factores de riesgo principales:
La edad es el predictor más fuerte y significativo para el accidente cerebrovascular. Por cada año de aumento en la edad, el riesgo aumenta un 7.27%.
El nivel promedio de glucosa en sangre es el segundo factor más importante, aunque su efecto es más pequeño pero estadísticamente significativo.
Nunca haber fumado parece tener un efecto protector significativo, reduciendo el riesgo de accidente cerebrovascular en un 35.84% comparado con la categoría de referencia.
Modelo
El modelo tiene un buen poder predictivo, con un AUC de 0.8453 (IC 95%: 0.8214-0.8708). La diferencia en rendimiento entre el modelo AIC y el modelo ampliado es mínima, lo que sugiere que un modelo más simple es suficiente para predecir el riesgo de accidente cerebrovascular.
Implicaciones clínicas:
El control de la glucosa en sangre y la promoción de hábitos de no fumar podrían ser estrategias efectivas para reducir el riesgo de accidente cerebrovascular.
En resumen, este análisis parece proporcionarnos una herramienta útil para identificar individuos en riesgo de accidente cerebrovascular, con la edad y el nivel de glucosa como los principales factores a considerar. El modelo ofrece un buen equilibrio entre simplicidad y poder predictivo, lo que lo hace potencialmente valioso para su uso en entornos clínicos.