Ejercicio 1: Historiales clínicos de insuficiencia cardíaca

Paso 1: Preparación Inicial y limpieza de Datos

Para este ejercicio utilizamos la misma base de datos utilizada en el ejercici anterior de KNN, luego de descargar la base de datos factorizamos las variables categoricas utilizando as.factor.

heart <- read_csv("~/Desktop/ESTA Mineria de Datos/heart+failure+clinical+records.zip")
## Rows: 299 Columns: 13
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## dbl (13): age, anaemia, creatinine_phosphokinase, diabetes, ejection_fractio...
## 
## ℹ 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.
head(heart)
heart <- heart %>%
  mutate(
    across(c(anaemia, diabetes, high_blood_pressure, sex, smoking, DEATH_EVENT), as.factor)
  ) %>%
  mutate(
    DEATH_EVENT = recode(DEATH_EVENT, "0" = "muerto", "1" = "vivo"),
    DEATH_EVENT = factor(DEATH_EVENT, levels = c("vivo", "muerto")),
    platelets   = log(platelets)
  )

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

En este paso vamos a dividir en dos subconjutnos de entrenamiento y prueba. Creamoslas 5 grupos balanceados “K = 5”.Luego, tomé “fold” 5 como conjunto de prueba y 5 folds como conjunto de entrenamiento. Luego los guardamos por separado porque esto nos va a ayudar a cosntruir y evaluar el modelo. Y último, vamos a utilizar “dim” para verificar las observaciones que tenemos ahora.

folds        <- createFolds(heart$DEATH_EVENT, k = 5)
entrenamiento <- heart[-folds[[5]],]
prueba        <- heart[folds[[5]],]
dim(entrenamiento)[1]
## [1] 239
dim(prueba)[1]
## [1] 60

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

modelo1 <- rpart(DEATH_EVENT ~ ., data = entrenamiento)
modelo1
## n= 239 
## 
## node), split, n, loss, yval, (yprob)
##       * denotes terminal node
## 
##  1) root 239 77 muerto (0.32217573 0.67782427)  
##    2) time< 73.5 60 12 vivo (0.80000000 0.20000000)  
##      4) serum_sodium< 136.5 31  2 vivo (0.93548387 0.06451613) *
##      5) serum_sodium>=136.5 29 10 vivo (0.65517241 0.34482759)  
##       10) time< 48.5 19  3 vivo (0.84210526 0.15789474) *
##       11) time>=48.5 10  3 muerto (0.30000000 0.70000000) *
##    3) time>=73.5 179 29 muerto (0.16201117 0.83798883)  
##      6) serum_creatinine>=1.45 36 16 muerto (0.44444444 0.55555556)  
##       12) platelets< 12.37158 17  6 vivo (0.64705882 0.35294118) *
##       13) platelets>=12.37158 19  5 muerto (0.26315789 0.73684211) *
##      7) serum_creatinine< 1.45 143 13 muerto (0.09090909 0.90909091) *
modelo2 <- C5.0(DEATH_EVENT ~ ., data = entrenamiento)
modelo2
## 
## Call:
## C5.0.formula(formula = DEATH_EVENT ~ ., data = entrenamiento)
## 
## Classification Tree
## Number of samples: 239 
## Number of predictors: 12 
## 
## Tree size: 19 
## 
## Non-standard options: attempt to group attributes
rpart.plot(modelo1)

plot(modelo2)

Paso 4: Validar la estabilidad del modelo

train_control <- trainControl(method = "cv", number = 10, savePredictions = TRUE)

arbol_cv <- train(DEATH_EVENT ~ .,
                  data = prueba,
                  method = "C5.0",
                  trControl = train_control,
                  tuneLength = 3)
confusionMatrix(arbol_cv$pred$pred, arbol_cv$pred$obs)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction vivo muerto
##     vivo    176     40
##     muerto   52    452
##                                           
##                Accuracy : 0.8722          
##                  95% CI : (0.8456, 0.8957)
##     No Information Rate : 0.6833          
##     P-Value [Acc > NIR] : <2e-16          
##                                           
##                   Kappa : 0.7005          
##                                           
##  Mcnemar's Test P-Value : 0.2515          
##                                           
##             Sensitivity : 0.7719          
##             Specificity : 0.9187          
##          Pos Pred Value : 0.8148          
##          Neg Pred Value : 0.8968          
##              Prevalence : 0.3167          
##          Detection Rate : 0.2444          
##    Detection Prevalence : 0.3000          
##       Balanced Accuracy : 0.8453          
##                                           
##        'Positive' Class : vivo            
## 

Observaciones

Al observar el gráfico del árbol CART, se ve que el nodo raíz clasifica inicialmente la mayoría de los casos como muerto, con una proporción de 0.68, y después las ramas van refinando esa clasificación. Por ejemplo, cuando time < 74, aparecen subgrupos donde predominan pacientes vivos, mientras que en la rama de time ≥ 74 predominan los casos muertos, especialmente cuando serum_creatinine ≥ 1.5. Esto muestra que el modelo está encontrando reglas clínicas sencillas e interpretables para separar los pacientes según su desenlace.

Por otro lado, el modelo C5.0 también se ajustó con los mismos datos, pero creó un árbol más grande, con más divisiones. Eso sugiere que C5.0 busca una clasificación más detallada, mientras que CART produce un árbol más simple y más fácil de interpretar visualmente. En general, ambos modelos sirven para clasificar la variable DEATH_EVENT, pero CART se entiende más fácil al mirar el árbol.

Paso 5: Interpretación de los resultados finales

arbol_cart <- train(DEATH_EVENT ~ .,
                    data = prueba,
                    method = "rpart",
                    trControl = train_control,
                    tuneLength = 3)

arbol_c50 <- train(DEATH_EVENT ~ .,
                   data = prueba,
                   method = "C5.0",
                   trControl = train_control,
                   tuneLength = 3)

comparacion <- resamples(list(CART = arbol_cart, C50 = arbol_c50))
summary(comparacion)
## 
## Call:
## summary.resamples(object = comparacion)
## 
## Models: CART, C50 
## Number of resamples: 10 
## 
## Accuracy 
##           Min.   1st Qu. Median      Mean 3rd Qu. Max. NA's
## CART 0.6666667 0.8750000      1 0.9214286       1    1    0
## C50  0.6666667 0.8333333      1 0.9166667       1    1    0
## 
## Kappa 
##      Min.   1st Qu. Median      Mean 3rd Qu. Max. NA's
## CART 0.00 0.6785714      1 0.7821429       1    1    0
## C50  0.25 0.5952381      1 0.8059524       1    1    0
dotplot(comparacion)

En conclusión, los árboles de decisión lograron un buen desempeño en esta base de datos para predecir DEATH_EVENT. Cuando vemos la matriz de confusión observamos una exactitud de 87.22%, lo que indica que el modelo clasificó correctamente la mayoría de los casos. Además, el valor de Kappa = 0.7005 muestra un nivel de concordancia bueno entre lo predicho y lo observado.

También se puede notar que el modelo identificó bastante bien ambas clases, con una sensibilidad de 0.7719 y una especificidad de 0.9187. Así que, se puede decir que el modelo de árbol de decisión resulta útil para este problema y que las variables clínicas incluidas sí contienen información importante para clasificar si el paciente sobrevive o fallece.

Ejercicio 2: Calidad del Vino

Para este ejercicio estaremos utilizando la misma base de datos utilizada en el ejercicio del método KNN, viendo las diferentes variables que miden la calidad del vino blanco y vino tinto. Utilizando los arboles de decisión, queremos que nos ayude a seleccionar predictores de forma automática y visualizar mejor las relaciones entre predictores.

Paso 1: Preparación Inicial y limpieza de Datos

Al igual que el ejercicio del método KNN preparams nuestra base de datos, dándole nombre especfico a las variables “ID” y luego uniendo ambas bases de datos. A su vez, terminamos asignándole a R que “quality” y “tipo” son variables categóricas usando la función de “as.factor”.

winequality_red$tipo <- "rojo"
winequality_white$tipo <- "blanco"

winequality <- bind_rows (winequality_red, winequality_white)
winequality$quality <- as.factor(winequality$quality)
winequality$tipo <- as.factor(winequality$tipo)

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

En este paso vamos a dividir en dos subconjutnos de entrenamiento y prueba. Creamoslas 5 grupos balanceados “K = 5”.Luego, tomé “fold” 5 como conjunto de prueba y 5 folds como conjunto de entrenamiento. Luego los guardamos por separado porque esto nos va a ayudar a cosntruir y evaluar el modelo. Y último, vamos a utilizar “dim” para verificar las observaciones que tenemos ahora.

set.seed(2025)
folds <- createFolds(winequality$quality, k = 5)

entrenamiento <- winequality_norm[-folds[[5]], ]
prueba <- winequality_norm[folds[[5]], ]

entrenamiento_labels <- winequality$quality[-folds[[5]]]
prueba_labels <- winequality$quality[folds[[5]]]

dim(entrenamiento)[1]
## [1] 5198
dim(prueba)[1]
## [1] 1299

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

En este paso estaremos construyendo un modelo ajustado utilizando como variable de respuestar “entrenamiento_labels”, que esta variable contiene las diferentes categorías de calidad del vino. Luego de haber ajustado el modelo, vamos a poder general un arbol binario para observar como el modelo esta seprando las observaciones

library(rpart)
library(rpart.plot)

arbol_1 <- rpart(entrenamiento_labels ~ ., data = (entrenamiento))
arbol_1
## n= 5198 
## 
## node), split, n, loss, yval, (yprob)
##       * denotes terminal node
## 
##  1) root 5198 2929 6 (0.0046 0.033 0.33 0.44 0.17 0.03 0.00077)  
##    2) alcohol< 0.3804348 3073 1627 5 (0.0046 0.04 0.47 0.41 0.066 0.0098 0.00033)  
##      4) volatile.acidity>=0.105 2243  995 5 (0.0053 0.048 0.56 0.35 0.035 0.0027 0.00045)  
##        8) alcohol< 0.2681159 1486  551 5 (0.004 0.046 0.63 0.3 0.017 0.0013 0) *
##        9) alcohol>=0.2681159 757  418 6 (0.0079 0.053 0.41 0.45 0.071 0.0053 0.0013)  
##         18) volatile.acidity>=0.145 499  264 5 (0.008 0.064 0.47 0.4 0.054 0.002 0) *
##         19) volatile.acidity< 0.145 258  119 6 (0.0078 0.031 0.3 0.54 0.1 0.012 0.0039) *
##      5) volatile.acidity< 0.105 830  363 6 (0.0024 0.018 0.24 0.56 0.15 0.029 0) *
##    3) alcohol>=0.3804348 2125 1112 6 (0.0047 0.024 0.12 0.48 0.31 0.058 0.0014) *
rpart.plot(arbol_1) 
## Warning: Bad 'data' argument in model call (expected a data.frame or a matrix).
## To silence this warning:
##     Call rpart.plot with roundint=FALSE,
##     or rebuild the rpart model with model=TRUE.

Observaciones

Al ver el gráfico, la función del arbol decidió clasificar principalmente los vinos entre 5 y 6, usando como variables principales “alcohol” y “volatile.acidity”. Los nodos azules corresponden a la clase 5 y los verdes a la clase 6. Por el otro lado, También se observa que las clases 3, 4, 7, 8 y 9 salen como “unused”, lo cual indica que el árbol no terminó creando nodos finales dominados por esas clases.

Paso 4: Validar la estabilidad del modelos

Validar la estabilidad del modelo Cart

library(caret)
set.seed(2025)

train_control <- trainControl(method = "cv", number = 10, savePredictions = TRUE)

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

confusionMatrix(arbol_cv$pred$pred, arbol_cv$pred$obs)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction    3    4    5    6    7    8    9
##          3    0    0    0    0    0    0    0
##          4    0    1   16    3    0    0    0
##          5   34  216 2198 1354  135    1    0
##          6   26  210 2018 3902 1719  271   10
##          7    0    3   38  406  301  118    0
##          8    0    0    0    5    5    0    0
##          9    0    0    0    0    0    0    0
## 
## Overall Statistics
##                                           
##                Accuracy : 0.4928          
##                  95% CI : (0.4842, 0.5015)
##     No Information Rate : 0.4365          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.1755          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: 3  Class: 4 Class: 5 Class: 6 Class: 7  Class: 8
## Sensitivity          0.000000 2.326e-03   0.5148   0.6882  0.13935 0.0000000
## Specificity          1.000000 9.985e-01   0.8005   0.4189  0.94783 0.9992063
## Pos Pred Value            NaN 5.000e-02   0.5582   0.4784  0.34758 0.0000000
## Neg Pred Value       0.995381 9.669e-01   0.7711   0.6343  0.84667 0.9699538
## Prevalence           0.004619 3.310e-02   0.3287   0.4365  0.16628 0.0300231
## Detection Rate       0.000000 7.698e-05   0.1692   0.3004  0.02317 0.0000000
## Detection Prevalence 0.000000 1.540e-03   0.3032   0.6279  0.06667 0.0007698
## Balanced Accuracy    0.500000 5.004e-01   0.6576   0.5535  0.54359 0.4996032
##                       Class: 9
## Sensitivity          0.0000000
## Specificity          1.0000000
## Pos Pred Value             NaN
## Neg Pred Value       0.9992302
## Prevalence           0.0007698
## Detection Rate       0.0000000
## Detection Prevalence 0.0000000
## Balanced Accuracy    0.5000000

observación

Estos resultados muestran un modelo con Accuracy = 0.4928 y Kappa = 0.1755. Eso significa que el modelo apenas supera el nivel base y que el acuerdo real entre predicción y valores observados es bajo. Por el otro lado, este modelo casi no logra identificar bien las clases menos frecuentes, como 3, 8 y 9, porque su sensibilidad en esas categorías es prácticamente 0. Este modelo se esta concentrando más en las clases más comunes que son 5 y 6.

Validar la estabilidad del modelo C5.0

# Utilizando el metodo de C5.0 

library(caret)
set.seed(2025)

train_control <- trainControl(method = "cv", number = 10, savePredictions = TRUE)

arbol_c50 <- train(quality ~ ., data = cbind(prueba, quality = prueba_labels),
                   method = "C5.0", trControl = train_control,
                   tuneLength = 10)

confusionMatrix(arbol_c50$pred$pred, arbol_c50$pred$obs)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction     3     4     5     6     7     8     9
##          3     0     2    87     5     0     0     0
##          4     0   219   291   207     2     2     0
##          5   197   917 10960  6126   708    84     0
##          6    43   515  5090 12146  3847   598     2
##          7     0    63   634  3909  3762   666    18
##          8     0     4    18   285   321   208    20
##          9     0     0     0     2     0     2     0
## 
## Overall Statistics
##                                          
##                Accuracy : 0.5253         
##                  95% CI : (0.521, 0.5296)
##     No Information Rate : 0.4365         
##     P-Value [Acc > NIR] : < 2.2e-16      
##                                          
##                   Kappa : 0.2841         
##                                          
##  Mcnemar's Test P-Value : NA             
## 
## Statistics by Class:
## 
##                      Class: 3 Class: 4 Class: 5 Class: 6 Class: 7 Class: 8
## Sensitivity          0.000000 0.127326   0.6417   0.5355   0.4354 0.133333
## Specificity          0.998183 0.990008   0.7697   0.6552   0.8779 0.987143
## Pos Pred Value       0.000000 0.303745   0.5771   0.5461   0.4156 0.242991
## Neg Pred Value       0.995373 0.970706   0.8144   0.6455   0.8863 0.973544
## Prevalence           0.004619 0.033102   0.3287   0.4365   0.1663 0.030023
## Detection Rate       0.000000 0.004215   0.2109   0.2338   0.0724 0.004003
## Detection Prevalence 0.001809 0.013876   0.3655   0.4280   0.1742 0.016474
## Balanced Accuracy    0.499091 0.558667   0.7057   0.5954   0.6567 0.560238
##                       Class: 9
## Sensitivity          0.000e+00
## Specificity          9.999e-01
## Pos Pred Value       0.000e+00
## Neg Pred Value       9.992e-01
## Prevalence           7.698e-04
## Detection Rate       0.000e+00
## Detection Prevalence 7.698e-05
## Balanced Accuracy    5.000e-01

Observación

Al comparar ambos modelos, el segundo presentó mejor desempeño que el primero, ya que obtuvo una exactitud de 52.53% frente a 49.28%, y un Kappa de 0.2841 frente a 0.1755. La exactitud sube un poco y el valor de Kappa también mejora, lo que indica una clasificación más consistente que la del primer modelo. El segundo modelo logra una clasificación más consistente y con mejor acuerdo respecto a los valores reales. Por esta razón, C5.0 se considera el modelo de árbol de decisión más adecuado para esta base de datos.

Paso 5: Interpretación de resultados finales

comparacion <- resamples(list(CART = arbol_cv, C5.0 = arbol_c50))

summary(comparacion)
## 
## Call:
## summary.resamples(object = comparacion)
## 
## Models: CART, C5.0 
## Number of resamples: 10 
## 
## Accuracy 
##           Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
## CART 0.4651163 0.4809160 0.5039062 0.5019504 0.5246804 0.5384615    0
## C5.0 0.4846154 0.5133588 0.5325602 0.5383752 0.5678295 0.5937500    0
## 
## Kappa 
##           Min.   1st Qu.    Median      Mean   3rd Qu.      Max. NA's
## CART 0.1140639 0.1394339 0.1787954 0.1792001 0.2039295 0.2505045    0
## C5.0 0.2554919 0.2783630 0.3033057 0.3153326 0.3572629 0.3955685    0

Observación y conclusión:

En la comparación realizada, el modelo C5.0 presentó un mejor desempeño que el modelo CART. En la métrica de accuracy, C5.0 obtuvo un promedio de **0.5384*, mientras que CART alcanzó 0.5020, lo que indica que C5.0 clasificó correctamente una mayor proporción de observaciones. De igual forma, la mediana de accuracy también fue mayor en C5.0, por lo que su rendimiento fue superior en la mayoría de los folds.

Cuando miramos la métrica de KAPPA, el modelo C5.0 también mostró mejores resultados. Su promedio fue 0.3153, en comparación con 0.1792 obtenido por CART. Esto significa que C5.0 no solo tuvo más aciertos, sino que además presentó una mejor concordancia general en la clasificación.