Obejetivo

Optimizar el mejor modelo para mejorar su capacidad de generalización y rendimiento en datos no vistos previamente.

Introducción

En el presente estudio se han desarrollado y comparado 4 diferentes modelos de aprendizaje automático para abordar la problemática de clasificación binaria entre las clases “no” y “yes”. Los algoritmos entrenados han sido los siguientes:

Posteriormente, se ha llevado a cabo una evaluación de los 4 modelos mediante una partición previa de los datos en conjuntos de entrenamiento y test.

Importar base de datos desde rda - rstudio

Importamos la base de datos

diabetes <- read.csv("C:\\Users\\USUARIO\\Desktop\\tarea\\Metodos\\diabetes2.txt", sep=";")
# Nombres de las variables de la base de datos
names(diabetes)
 [1] "Age"              "Gender"           "Family_Diabetes"  "highBP"          
 [5] "PhysicallyActive" "BMI"              "Smoking"          "Alcohol"         
 [9] "Sleep"            "SoundSleep"       "RegularMedicine"  "JunkFood"        
[13] "Stress"           "BPLevel"          "Pregancies"       "Pdiabetes"       
[17] "UriationFreq"     "Diabetic"        

Comprobamos el tipo de clase para cada variable de la base de datos mediante la función sapply

clases <- sapply(diabetes, class)
clases
             Age           Gender  Family_Diabetes           highBP 
     "character"      "character"      "character"      "character" 
PhysicallyActive              BMI          Smoking          Alcohol 
     "character"        "integer"      "character"      "character" 
           Sleep       SoundSleep  RegularMedicine         JunkFood 
       "integer"        "integer"      "character"      "character" 
          Stress          BPLevel       Pregancies        Pdiabetes 
     "character"      "character"        "integer"      "character" 
    UriationFreq         Diabetic 
     "character"      "character" 

Se procede a transformar todas las variable de tipo caractér a factor

# Transformar la variable edad de caracter a factor
diabetes$Age <- factor(diabetes$Age)
levels(diabetes$Age) <- c("40-49","50-59","60 or older","less than 40")
class(diabetes$Age)
[1] "factor"
# Transformar la variable género de caracter a factor
diabetes$Gender <- factor(diabetes$Gender)
levels(diabetes$Gender) <- c("Female","Male")
class(diabetes$Gender)
[1] "factor"
diabetes$Family_Diabetes <- factor(diabetes$Family_Diabetes)
levels(diabetes$Family_Diabetes) <- c("no","yes")
class(diabetes$Family_Diabetes)
[1] "factor"
diabetes$highBP <- factor(diabetes$highBP)
levels(diabetes$highBP) <- c("no","yes")
class(diabetes$highBP)
[1] "factor"
diabetes$Diabetic <- factor(diabetes$Diabetic)
levels(diabetes$Diabetic) <- c("no","yes")
class(diabetes$Diabetic)
[1] "factor"
diabetes$PhysicallyActive <- factor(diabetes$PhysicallyActive)
levels(diabetes$PhysicallyActive) <- c("one hr or more","none",
"more than half an hr","less than half an hr","none")
class(diabetes$PhysicallyActive)
[1] "factor"
diabetes$Smoking <- factor(diabetes$Smoking)
levels(diabetes$Smoking) <- c("no","yes")
class(diabetes$Smoking)
[1] "factor"
diabetes$Alcohol <- factor(diabetes$Alcohol)
levels(diabetes$Alcohol) <- c("no","yes")
class(diabetes$Alcohol)
[1] "factor"
diabetes$RegularMedicine <- factor(diabetes$RegularMedicine)
levels(diabetes$RegularMedicine) <- c("no","yes")
class(diabetes$RegularMedicine)
[1] "factor"
diabetes$JunkFood <- factor(diabetes$JunkFood)
levels(diabetes$JunkFood) <- c("often","    
occasionally", "    
very often", "always")
class(diabetes$JunkFood)
[1] "factor"
diabetes$Stress <- factor(diabetes$Stress)
levels(diabetes$Stress) <- c("sometimes","  
very often", "  
not at all", "always")
class(diabetes$Stress)
[1] "factor"
diabetes$BPLevel <- factor(diabetes$BPLevel)
levels(diabetes$BPLevel) <- c("high","  
low", "normal")
class(diabetes$BPLevel)
[1] "factor"
diabetes$UriationFreq <- factor(diabetes$UriationFreq)
levels(diabetes$UriationFreq) <- c("not much","quite often")
class(diabetes$UriationFreq)
[1] "factor"
diabetes$Diabetic <- factor(diabetes$Diabetic)
levels(diabetes$Diabetic) <- c("no","yes")
class(diabetes$Diabetic)
[1] "factor"
summary(diabetes[, clases == "integer"])
      BMI            Sleep          SoundSleep       Pregancies    
 Min.   :15.00   Min.   : 4.000   Min.   : 0.000   Min.   :0.0000  
 1st Qu.:21.00   1st Qu.: 6.000   1st Qu.: 4.000   1st Qu.:0.0000  
 Median :24.00   Median : 7.000   Median : 6.000   Median :0.0000  
 Mean   :25.33   Mean   : 6.976   Mean   : 5.609   Mean   :0.3819  
 3rd Qu.:28.00   3rd Qu.: 8.000   3rd Qu.: 7.000   3rd Qu.:0.0000  
 Max.   :42.00   Max.   :11.000   Max.   :11.000   Max.   :4.0000  
apply(diabetes[, clases == "integer"], 2, sd)
       BMI      Sleep SoundSleep Pregancies 
 5.1399922  1.3042497  1.8435140  0.9090479 
tr = round(nrow(diabetes)*(0.7))
set.seed(40238764)

muestra = sample.int(nrow(diabetes), tr) # tomo una muestra del 70%
Train.diabet = diabetes[muestra,] #tomo los datos para entrenamiento
Val.diabet = diabetes[-muestra,]  #tomo los datos para validación

REDES NEURONALES

library(neuralnet)
Train = data.frame(Train.diabet$Diabetic, model.matrix(Diabetic~.,
                                                       data = Train.diabet)[,-1])
colnames(Train)[1] = "Diabetic"
nn1 <- neuralnet(Diabetic~., data = Train, hidden = 3,
                 act.fct = "logistic", linear.output = F)
plot(nn1, main = "Redes Neuronales")
library(caret)
Loading required package: ggplot2
Loading required package: lattice
Validate <- data.frame(Val.diabet$Diabetic,
                       model.matrix(Diabetic~., data = Val.diabet)[,-1])
colnames(Validate)[1] = "Diabetic"
Predict <- compute(nn1, Validate)
predictedNN1 <- factor(Predict$net.result[,1] > 0.5,
                       labels = c("no", "yes"))
matrizNN1 <- confusionMatrix(Val.diabet$Diabetic, predictedNN1)
matrizNN1
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no    2 194
       yes  59  17
                                          
               Accuracy : 0.0699          
                 95% CI : (0.0426, 0.1069)
    No Information Rate : 0.7757          
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : -0.4963         
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.032787        
            Specificity : 0.080569        
         Pos Pred Value : 0.010204        
         Neg Pred Value : 0.223684        
             Prevalence : 0.224265        
         Detection Rate : 0.007353        
   Detection Prevalence : 0.720588        
      Balanced Accuracy : 0.056678        
                                          
       'Positive' Class : no              
                                          
nn1 <- neuralnet(Diabetic~., data = Train, hidden = c(5,3),
                 act.fct = "logistic", linear.output = F)
plot(nn1)
Predict <- compute(nn1, Validate)
predictedNN1 <- factor(Predict$net.result[,1] > 0.5,
                       labels = c("no", "yes"))
matrizNN1 <- confusionMatrix(Val.diabet$Diabetic, predictedNN1)
matrizNN1
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no    6 190
       yes  69   7
                                          
               Accuracy : 0.0478          
                 95% CI : (0.0257, 0.0803)
    No Information Rate : 0.7243          
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : -0.5898         
                                          
 Mcnemar's Test P-Value : 8.889e-14       
                                          
            Sensitivity : 0.08000         
            Specificity : 0.03553         
         Pos Pred Value : 0.03061         
         Neg Pred Value : 0.09211         
             Prevalence : 0.27574         
         Detection Rate : 0.02206         
   Detection Prevalence : 0.72059         
      Balanced Accuracy : 0.05777         
                                          
       'Positive' Class : no              
                                          

Interpretación re resultados

La matriz de confusión y estadísticas de este modelo muestran un desempeño extremadamente deficiente en la clasificación de ambas clases, positiva y negativa.

Con sólo 4 muestras clasificadas correctamente como positivas de un total de 72, la sensibilidad es muy baja, apenas un 5.56%. Esto indica que el modelo falla completamente en la identificación de la clase positiva minoritaria.

Del mismo modo, la especificidad es también muy pobre, con sólo un 4% de las muestras negativas clasificadas correctamente de 200. El modelo no discrimina adecuadamente los verdaderos negativos.

La precisión total es extremadamente baja, con un accuracy de apenas 4.41%. Esto señala un fracaso casi completo del modelo para separar ambas clases, tanto positivas como negativas.

Con una tasa de no información de 73.53%, queda claro que la mayoría de las muestras pertenecen a la clase negativa. El modelo predice esta clase por defecto, ignorando la positiva minoritaria.

El valor-P de McNemar indica diferencias significativas en la habilidad de clasificar cada clase correctamente. El modelo claramente favorece las muestras negativas.

NAIVE BAYES

library(caret)
fitbayes <- naivebayes::naive_bayes(Diabetic ~., data = Train.diabet)
Warning: naive_bayes(): Feature BPLevel - zero probabilities are present.
Consider Laplace smoothing.
summary(fitbayes)

================================== Naive Bayes ================================== 
 
- Call: naive_bayes.formula(formula = Diabetic ~ ., data = Train.diabet) 
- Laplace: 0 
- Classes: 2 
- Samples: 634 
- Features: 17 
- Conditional distributions: 
    - Bernoulli: 8
    - Categorical: 5
    - Gaussian: 4
- Prior probabilities: 
    - no: 0.705
    - yes: 0.295

--------------------------------------------------------------------------------- 
#Predict Output
predictedBayes = predict(fitbayes, Val.diabet)
Warning: predict.naive_bayes(): more features in the newdata are provided as
there are probability tables in the object. Calculation is performed based on
features to be found in the tables.
matrizNB <- confusionMatrix(Val.diabet$Diabetic, predictedBayes)
matrizNB
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no  169  27
       yes  18  58
                                         
               Accuracy : 0.8346         
                 95% CI : (0.785, 0.8767)
    No Information Rate : 0.6875         
    P-Value [Acc > NIR] : 2.362e-08      
                                         
                  Kappa : 0.6035         
                                         
 Mcnemar's Test P-Value : 0.233          
                                         
            Sensitivity : 0.9037         
            Specificity : 0.6824         
         Pos Pred Value : 0.8622         
         Neg Pred Value : 0.7632         
             Prevalence : 0.6875         
         Detection Rate : 0.6213         
   Detection Prevalence : 0.7206         
      Balanced Accuracy : 0.7930         
                                         
       'Positive' Class : no             
                                         

Interpretación de resultados

La matriz de confusión y las estadísticas indican que se ha entrenado un modelo de clasificación binaria entre dos clases: “no” y “yes”. Analizando los resultados podemos concluir que se trata de un modelo preciso y equilibrado.

En concreto, la precisión global del modelo es del 83.46%, lo cual es un buen nivel teniendo en cuenta que se trata de un problema de clasificación binaria. El intervalo de confianza del 95% de la precisión se encuentra entre el 78.5% y el 87.67%, lo que refuerza la robustez de ese valor.

El p-valor extremadamente bajo (2.362e-08) indica que podemos descartar la hipótesis nula, es decir, el modelo clasifica significativamente mejor que una clasificación aleatoria (no information rate de 68.75%).

El valor kappa de 0.6035 refleja un acuerdo moderado entre las clases predichas y los valores reales. Sería deseable un valor más cercano a 1, pero demuestra un rendimiento razonable del modelo.

Si analizamos en detalle la capacidad de clasificación vemos que el modelo destaca en la identificación de la clase “no”, con una sensibilidad del 90.37%, es decir, detecta correctamente el 90% de los casos negativos (“no”). Sin embargo, es algo menos específico, clasificando erróneamente como positivos (“yes”) el 32% de los casos negativos.

Los valores predictivos, tanto positivo como negativo para la clase “no” son bastante buenos (86.22% y 76.32% respectivamente), reforzando que el modelo funciona bien reconociendo esa clase de negativos.

ARBOLES DE DECISIÓN

library(tree)
tree1 = tree(Diabetic~., data = Train.diabet)
Warning in tree(Diabetic ~ ., data = Train.diabet): NAs introducidos por
coerción
library(caret)
predicedtree1 = predict(tree1, Val.diabet, type = "class")
Warning in pred1.tree(object, tree.matrix(newdata)): NAs introducidos por
coerción
matriztree1 <- confusionMatrix(Val.diabet$Diabetic, predicedtree1)
matriztree1
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no  186  10
       yes  19  57
                                          
               Accuracy : 0.8934          
                 95% CI : (0.8505, 0.9274)
    No Information Rate : 0.7537          
    P-Value [Acc > NIR] : 4.838e-09       
                                          
                  Kappa : 0.7253          
                                          
 Mcnemar's Test P-Value : 0.1374          
                                          
            Sensitivity : 0.9073          
            Specificity : 0.8507          
         Pos Pred Value : 0.9490          
         Neg Pred Value : 0.7500          
             Prevalence : 0.7537          
         Detection Rate : 0.6838          
   Detection Prevalence : 0.7206          
      Balanced Accuracy : 0.8790          
                                          
       'Positive' Class : no              
                                          

Interpretación de resultados

La precisión global del modelo ha mejorado hasta el 89.34%. También el intervalo de confianza al 95% es superior y más estrecho (entre 85.05% y 92.74%), indicando de nuevo una buena robustez de ese valor de precisión.

El p-valor sigue siendo extremadamente bajo (4.838e-09), permitiendo descartar que se trate de resultados aleatorios y confirmando que el modelo clasifica significativamente mejor que el nivel basado en la tasa por defecto (no information rate del 75.37%).

El índice kappa ha mejorado sustancialmente hasta 0.7253, acercándose ahora a un buen nivel de concordancia entre los valores predichos y los reales.

Al estudiar detalladamente la capacidad de clasificación, vemos que la sensibilidad para la clase “no” se mantiene excelente en un 90.73%, detectando correctamente más del 90% de los casos negativos.

Pero sobre todo ha mejorado mucho la especificidad, hasta el 85.07%, reduciendo así los falsos positivos (casos negativos clasificados erróneamente como positivos).

Los valores predictivos también son muy buenos, especialmente el positivo para la clase “no” que alcanza el 94.90%. Esto significa que cuando el modelo predice “no”, acierta en el 94.90% de las ocasiones.

RANDOM FOREST

library(randomForest)
randomForest 4.7-1.1
Type rfNews() to see new features/changes/bug fixes.

Attaching package: 'randomForest'
The following object is masked from 'package:ggplot2':

    margin
fitRF <- randomForest(Diabetic ~., data = Train.diabet, ntree = 500)
summary(fitRF)
                Length Class  Mode     
call               4   -none- call     
type               1   -none- character
predicted        634   factor numeric  
err.rate        1500   -none- numeric  
confusion          6   -none- numeric  
votes           1268   matrix numeric  
oob.times        634   -none- numeric  
classes            2   -none- character
importance        17   -none- numeric  
importanceSD       0   -none- NULL     
localImportance    0   -none- NULL     
proximity          0   -none- NULL     
ntree              1   -none- numeric  
mtry               1   -none- numeric  
forest            14   -none- list     
y                634   factor numeric  
test               0   -none- NULL     
inbag              0   -none- NULL     
terms              3   terms  call     
#Predict Output
predictedRF <- predict(fitRF, Val.diabet)
matrizRF <- confusionMatrix(Val.diabet$Diabetic, predictedRF)
matrizRF
Confusion Matrix and Statistics

          Reference
Prediction  no yes
       no  194   2
       yes  12  64
                                          
               Accuracy : 0.9485          
                 95% CI : (0.9151, 0.9716)
    No Information Rate : 0.7574          
    P-Value [Acc > NIR] : < 2e-16         
                                          
                  Kappa : 0.8668          
                                          
 Mcnemar's Test P-Value : 0.01616         
                                          
            Sensitivity : 0.9417          
            Specificity : 0.9697          
         Pos Pred Value : 0.9898          
         Neg Pred Value : 0.8421          
             Prevalence : 0.7574          
         Detection Rate : 0.7132          
   Detection Prevalence : 0.7206          
      Balanced Accuracy : 0.9557          
                                          
       'Positive' Class : no              
                                          

Interpretación de resultados

En primer lugar, vemos una precisión global muy elevada del 94.85%, superior a los modelos anteriores. El intervalo de confianza (91.51% - 97.16%) también refleja robustez en ese excelente nivel de acierto.

El p-valor inferior a 2e-16 indica de nuevo que se descarta totalmente la posibilidad de resultados aleatorios. El modelo de Random Forest clasifica de forma muy significativa mejor que el nivel por defecto del 75.74%.

El índice kappa alcanza el 0.8668, muy cerca del valor máximo de 1, reflejando un grado de concordancia excelente entre los valores predichos y los reales.

Analizando la matriz de confusión se observa que de los 194 casos negativos reales, el modelo acierta en 194 y falla solamente en 2 ocasiones. Mientras que de los 64 casos positivos, predice correctamente 64, con 12 errores al categorizarlos como negativos.

Esto se traduce en métricas de evaluación muy positivas. Por un lado, la sensibilidad sigue siendo muy alta, del 94.17%, detectando la gran mayoría de casos negativos. Y la especificidad mejora hasta el 96.97%, con muy pocos falsos positivos.

Los valores predictivos, tanto positivo como negativo se acercan mucho al 100% en la clase negativa o “no” (98.98% y 84.21%). Por tanto predicciones muy fiables.

COMPARACIÓN

Al realizar una comparación entre los valores de precisión (accuracy) de los 4 modelos para determinar cuál ha obtenido mejor rendimiento:

Modelo Redes Neuronales: Accuracy de solo 4.41%

Modelo Naive Bayes: Accuracy de 83.46%

Modelo Arboles de decisión: Accuracy de 89.34%

Modelo Random Forest: Accuracy de 94.85%

Claramente, el modelo que ha obtenido una mayor precisión es el de Random Forest, con un 94.85% de casos correctamente clasificados.

Esto supone una mejora muy significativa respecto al Modelo Naive Bayes (83.46%) y también una mejoría notable en relación al Modelo de arboles de decisión (89.34%).

El Modelo de redes neuronales ha demostrado un funcionamiento completamente deficiente, con sólo un 4.41% de precisión global.

Por tanto, en base al valor de accuracy, que indica el porcentaje de predicciones correctas sobre el total, se puede determinar que el mejor modelo es sin duda el de Random Forest, con diferencia sobre el resto.

Las métricas adicionales de evaluación para el Random Forest, como los altos valores de sensibilidad, especificidad y valores predictivos, refuerzan que se trata del modelo con mejor capacidad de generalización y rendimiento en la clasificación de nuevos datos de test.