Paso 1: Preparación y limpieza de datos

data <- read.csv("heart_failure_clinical_records_dataset.csv")
data$DEATH_EVENT <- as.factor(data$DEATH_EVENT)
table(data$DEATH_EVENT)
## 
##   0   1 
## 203  96
sum(is.na(data))
## [1] 0
str(data)
## 'data.frame':    299 obs. of  13 variables:
##  $ age                     : num  75 55 65 50 65 90 75 60 65 80 ...
##  $ anaemia                 : int  0 0 0 1 1 1 1 1 0 1 ...
##  $ creatinine_phosphokinase: int  582 7861 146 111 160 47 246 315 157 123 ...
##  $ diabetes                : int  0 0 0 0 1 0 0 1 0 0 ...
##  $ ejection_fraction       : int  20 38 20 20 20 40 15 60 65 35 ...
##  $ high_blood_pressure     : int  1 0 0 0 0 1 0 0 0 1 ...
##  $ platelets               : num  265000 263358 162000 210000 327000 ...
##  $ serum_creatinine        : num  1.9 1.1 1.3 1.9 2.7 2.1 1.2 1.1 1.5 9.4 ...
##  $ serum_sodium            : int  130 136 129 137 116 132 137 131 138 133 ...
##  $ sex                     : int  1 1 1 1 0 1 1 1 0 1 ...
##  $ smoking                 : int  0 0 1 0 0 1 0 1 0 1 ...
##  $ time                    : int  4 6 7 7 8 8 10 10 10 10 ...
##  $ DEATH_EVENT             : Factor w/ 2 levels "0","1": 2 2 2 2 2 2 2 2 2 2 ...

Podemos observar que la base de datos de datos no tiene valores faltantes. Además, convertimos la variable de clasisificación, Death_event, en factor. Por otro lado, aunque sí tiene diferentes escalas de medición, para este tipo de ejercicio no es importante estandarizarlos.

Paso 2: Dividir los datos en conjunto de entrenamiento y prueba

Con un muestreo aleatorio, separamos el conjunto de entrenamiento y en conjunto de prueba. Elegimos 5 grupos, 4 de entrenamiento y 1 de prueba. Esto dado a que luego de practicarlo con diferentes parámetros, observamos que con los elegimos se entiende mejor el árbol de decisión.

set.seed(2025)
library(caret)
folds         <- createFolds(data$DEATH_EVENT, k = 5)
entrenamiento <- data[-folds[[5]],]
prueba        <- data[folds[[5]],]

Separamos las etiquetas de nuestro conjunto de entrenamiento y prueba.

entrenamiento_labels <- data$DEATH_EVENT[-folds[[5]]]
prueba_labels        <- data$DEATH_EVENT[folds[[5]]]

Paso 3: Construcción del árbol de decisión

Construimos nuestro árbol de decisión con nuestros datos de entrenamiento.

library(rpart)
library(rpart.plot)
arbol_1 <- rpart(DEATH_EVENT ~ ., data = entrenamiento)
rpart.plot(arbol_1)

En nuestro árbol de decisión podemos observar que se utilizaron cuatro variables médicas principales para la clasificación, estas son: time, serum_creatinine, platelets y serum_sodio. Donde se identifican a los pacientes en dos categorías (0 y 1) tomando en consideración nuestra variable de clasificación death_event.

El nódulo principal toma la decisión inicial según si el tiempo es mayor o igual a 74; si es menor, la probabilidad de pertenecer a la categoría 1 es alta (0.80) pero solo un 25% de los datos pertenece a este grupo. Además, se evalua si el sodio en el suero es mayor a 137 se vuelve a subdividir analizando el tiempo.Mientras que si no lo es podemos observar una clasifiación más directa con el 13% de los datos.

En la parte izquiera de la rama, si el tiempo es mayor se volverá a subdividir examinando la creatinina en suero donde si es mayor a 1.5, la clasificación sigue dependiendo del conteo de plaquetas, donde valores menores a 236,000 indican una probabilidad de ser 1, mientras que valores mayores muestran la probabilidad de ser 0.

Paso 4: Validar la estabilidad del modelo

Aplicaremos la validación cruzada.

# Configurar validación cruzada

library(caret)
set.seed(2025)
train_control <- trainControl(method = "cv", number = 10, savePredictions = TRUE)

arbol_cv <- train(DEATH_EVENT ~ ., data=cbind(prueba, DEATH_EVENT=prueba_labels), 
                method = "rpart", trControl = train_control, 
                 tuneLength = 10)

# Matriz de confusión usando predicciones guardadas por caret
confusionMatrix(arbol_cv$pred$pred, arbol_cv$pred$obs)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0 397  50
##          1  13 140
##                                           
##                Accuracy : 0.895           
##                  95% CI : (0.8677, 0.9184)
##     No Information Rate : 0.6833          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.744           
##                                           
##  Mcnemar's Test P-Value : 5.745e-06       
##                                           
##             Sensitivity : 0.9683          
##             Specificity : 0.7368          
##          Pos Pred Value : 0.8881          
##          Neg Pred Value : 0.9150          
##              Prevalence : 0.6833          
##          Detection Rate : 0.6617          
##    Detection Prevalence : 0.7450          
##       Balanced Accuracy : 0.8526          
##                                           
##        'Positive' Class : 0               
## 

Podemos observar que realizando nuestro árbol de decisión con un conjunto de 5 grupos, 4 de entrenamiento y 1 de prueba, obtentemos en la validación cruzada un Acurrancy de 90% y un Kappa de 74%. Es decir, este nivel de rendimiento sugiere que el árbol de decisión es una bastante robusto para clasificar nuestros datos.

Paso 5: Interpretación de los resultados

Podemos calcular la predicción para los datos de entrenamiento.

pred_entrenamiento <- predict(arbol_1, newdata = entrenamiento, type = "class")
matriz_entrenamiento <- table(pred_entrenamiento, entrenamiento$DEATH_EVENT)
matriz_entrenamiento
##                   
## pred_entrenamiento   0   1
##                  0 151  21
##                  1  11  56
TA <- sum(diag(matriz_entrenamiento))/sum(matriz_entrenamiento) 
paste0("Tasa de aciertos con los datos de entrenamiento: ", round(TA,4)*100, "%", sep="")
## [1] "Tasa de aciertos con los datos de entrenamiento: 86.61%"

En el conjunto de entrenamiento, el modelo de entrenamiento predijo correctamente 151 casos como 0 (no fallecidos) y 56 como 1 (fallecidos). Además, hay 21 falsos negativos, es decir, casos que eran 1 pero fueron clasificados como 0. Mientras que observamos 11 falsos positivos, es decir, casos que eran 0 pero fueron clasificados como 1.

Esto sugiere que el modelo tiene un buen desempeño en los datos de entrenamiento, pero no es perfecto.

Podemos calcular la predicción para los datos de prueba.

pred_prueba <- predict(arbol_1, newdata = prueba, type = "class")
matriz_prueba <- table(prueba$DEATH_EVENT, pred_prueba)
matriz_prueba
##    pred_prueba
##      0  1
##   0 38  3
##   1  6 13
TAP <- sum(diag(matriz_prueba))/sum(matriz_prueba) 
paste0("Tasa de aciertos con los datos de prueba: ", round(TAP,4)*100, "%", sep="")
## [1] "Tasa de aciertos con los datos de prueba: 85%"

En el conjunto de prueba, el modelo predijo correctamente 38 casos como 0 (no fallecidos) y 13 como 1 (fallecidos). Además, observamos 3 falsos positivos (casos 0 clasificados como 1) y 6 falsos negativos (casos 1 clasificados como 0).

En comparación con los datos de entrenamiento, los resultados en los datos de prueba podrían reflejar una pequeña pérdida de desempeño, pero el modelo parece mantenerse razonablemente bien.