Introducción

En este ejercicio utilizaremos datos reales recopilados de estudiantes de educación secundaria en dos escuelas de Portugal. El objetivo es predecir si un estudiante aprueba o no el curso de matemáticas, utilizando información académica, familiar, personal y social.Para esto, se aplicará el Clasificador Bayesiano (Naive Bayes), una técnica de clasificación supervisada basada en el Teorema de Bayes.

Paso 1. Preparación inicial y limpieza de los datos:

student.mat <- read.csv("student-mat.csv")
dim(student.mat)
## [1] 395  33
head(student.mat)
##   school sex age address famsize Pstatus Medu Fedu     Mjob     Fjob     reason
## 1     GP   F  18       U     GT3       A    4    4  at_home  teacher     course
## 2     GP   F  17       U     GT3       T    1    1  at_home    other     course
## 3     GP   F  15       U     LE3       T    1    1  at_home    other      other
## 4     GP   F  15       U     GT3       T    4    2   health services       home
## 5     GP   F  16       U     GT3       T    3    3    other    other       home
## 6     GP   M  16       U     LE3       T    4    3 services    other reputation
##   guardian traveltime studytime failures schoolsup famsup paid activities
## 1   mother          2         2        0       yes     no   no         no
## 2   father          1         2        0        no    yes   no         no
## 3   mother          1         2        3       yes     no  yes         no
## 4   mother          1         3        0        no    yes  yes        yes
## 5   father          1         2        0        no    yes  yes         no
## 6   mother          1         2        0        no    yes  yes        yes
##   nursery higher internet romantic famrel freetime goout Dalc Walc health
## 1     yes    yes       no       no      4        3     4    1    1      3
## 2      no    yes      yes       no      5        3     3    1    1      3
## 3     yes    yes      yes       no      4        3     2    2    3      3
## 4     yes    yes      yes      yes      3        2     2    1    1      5
## 5     yes    yes       no       no      4        3     2    1    2      5
## 6     yes    yes      yes       no      5        4     2    1    2      5
##   absences G1 G2 G3
## 1        6  5  6  6
## 2        4  5  5  6
## 3       10  7  8 10
## 4        2 15 14 15
## 5        4  6 10 10
## 6       10 15 15 15
str(student.mat)
## 'data.frame':    395 obs. of  33 variables:
##  $ school    : chr  "GP" "GP" "GP" "GP" ...
##  $ sex       : chr  "F" "F" "F" "F" ...
##  $ age       : int  18 17 15 15 16 16 16 17 15 15 ...
##  $ address   : chr  "U" "U" "U" "U" ...
##  $ famsize   : chr  "GT3" "GT3" "LE3" "GT3" ...
##  $ Pstatus   : chr  "A" "T" "T" "T" ...
##  $ Medu      : int  4 1 1 4 3 4 2 4 3 3 ...
##  $ Fedu      : int  4 1 1 2 3 3 2 4 2 4 ...
##  $ Mjob      : chr  "at_home" "at_home" "at_home" "health" ...
##  $ Fjob      : chr  "teacher" "other" "other" "services" ...
##  $ reason    : chr  "course" "course" "other" "home" ...
##  $ guardian  : chr  "mother" "father" "mother" "mother" ...
##  $ traveltime: int  2 1 1 1 1 1 1 2 1 1 ...
##  $ studytime : int  2 2 2 3 2 2 2 2 2 2 ...
##  $ failures  : int  0 0 3 0 0 0 0 0 0 0 ...
##  $ schoolsup : chr  "yes" "no" "yes" "no" ...
##  $ famsup    : chr  "no" "yes" "no" "yes" ...
##  $ paid      : chr  "no" "no" "yes" "yes" ...
##  $ activities: chr  "no" "no" "no" "yes" ...
##  $ nursery   : chr  "yes" "no" "yes" "yes" ...
##  $ higher    : chr  "yes" "yes" "yes" "yes" ...
##  $ internet  : chr  "no" "yes" "yes" "yes" ...
##  $ romantic  : chr  "no" "no" "no" "yes" ...
##  $ famrel    : int  4 5 4 3 4 5 4 4 4 5 ...
##  $ freetime  : int  3 3 3 2 3 4 4 1 2 5 ...
##  $ goout     : int  4 3 2 2 2 2 4 4 2 1 ...
##  $ Dalc      : int  1 1 2 1 1 1 1 1 1 1 ...
##  $ Walc      : int  1 1 3 1 2 2 1 1 1 1 ...
##  $ health    : int  3 3 3 5 5 5 3 1 1 5 ...
##  $ absences  : int  6 4 10 2 4 10 0 6 0 0 ...
##  $ G1        : int  5 5 7 15 6 15 12 6 16 14 ...
##  $ G2        : int  6 5 8 14 10 15 12 5 18 15 ...
##  $ G3        : int  6 6 10 15 10 15 11 6 19 15 ...
sum(is.na(student.mat))
## [1] 0
student.mat$resultado <- ifelse(student.mat$G3 >= 10, "Aprobado", "Reprobado")
student.mat$resultado <- as.factor(student.mat$resultado)

table(student.mat$resultado)
## 
##  Aprobado Reprobado 
##       265       130
prop.table(table(student.mat$resultado))
## 
##  Aprobado Reprobado 
## 0.6708861 0.3291139

En este primer paso, se exploró y preparó la base de datos student.mat para asegurarnos de que estuviera lista para el modelo. La base cuenta con 395 estudiantes y 33 variables, incluyendo tanto variables categóricas como numéricas. Además, se confirmó que no hay valores faltantes, así que no fue necesario limpiar datos adicionales. Luego se creó la variable resultado usando la nota final G3, clasificando a cada estudiante como Aprobado o Reprobado. Al analizar esta nueva variable, se observó que 265 estudiantes aprobaron (67.09%) y 130 reprobaron (32.91%), por lo que existe un desbalance moderado a favor de los aprobados. En general, este paso permitió verificar que la base está completa, bien estructurada y lista para continuar con el análisis.

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

folds <- createFolds(student.mat$resultado, k = 5)

entrenamiento <- student.mat[-folds[[5]], ]
prueba <- student.mat[folds[[5]], ]

# Etiquetas 
entrenamiento_labels <- student.mat$resultado[-folds[[5]]]
prueba_labels <- student.mat$resultado[folds[[5]]]

# Dimensiones de los conjuntos
dim(entrenamiento)
## [1] 316  34
dim(prueba)
## [1] 79 34
# Distribución de clases en entrenamiento
table(entrenamiento$resultado)
## 
##  Aprobado Reprobado 
##       212       104
prop.table(table(entrenamiento$resultado))
## 
##  Aprobado Reprobado 
## 0.6708861 0.3291139
# Distribución de clases en prueba
table(prueba$resultado)
## 
##  Aprobado Reprobado 
##        53        26
prop.table(table(prueba$resultado))
## 
##  Aprobado Reprobado 
## 0.6708861 0.3291139

Aquí, se dividió la base en dos subconjuntos: 316 observaciones para entrenamiento y 79 para prueba, lo que equivale aproximadamente a una partición de 80% y 20%. Además, la variable resultado mantuvo la misma proporción en ambos grupos: en entrenamiento hubo 212 aprobados y 104 reprobados, y en prueba 53 aprobados y 26 reprobados. En ambos casos, los porcentajes fueron prácticamente iguales, con 67.09% de aprobados y 32.91% de reprobados. Esto indica que la partición se hizo de forma consistente y que tanto el conjunto de entrenamiento como el de prueba representan bien la distribución original de las clases, lo cual es importante para entrenar y evaluar el modelo de manera más confiable.

Paso 3: Aplicar el Clasificador Bayesiano (NBC):

# Quitar G3 para evitar fuga de información
entrenamiento_nb <- entrenamiento[, !(names(entrenamiento) %in% c("G3"))]
prueba_nb <- prueba[, !(names(prueba) %in% c("G3"))]

# Convertir variables categóricas tipo character a factor
entrenamiento_nb[] <- lapply(entrenamiento_nb, function(x) {
  if(is.character(x)) as.factor(x) else x
})

prueba_nb[] <- lapply(prueba_nb, function(x) {
  if(is.character(x)) as.factor(x) else x
})

# Ajustar el modelo Naive Bayes
modelo_nb <- naiveBayes(resultado ~ ., data = entrenamiento_nb)
modelo_nb
## 
## Naive Bayes Classifier for Discrete Predictors
## 
## Call:
## naiveBayes.default(x = X, y = Y, laplace = laplace)
## 
## A-priori probabilities:
## Y
##  Aprobado Reprobado 
## 0.6708861 0.3291139 
## 
## Conditional probabilities:
##            school
## Y                  GP        MS
##   Aprobado  0.8867925 0.1132075
##   Reprobado 0.8653846 0.1346154
## 
##            sex
## Y                   F         M
##   Aprobado  0.5235849 0.4764151
##   Reprobado 0.6153846 0.3846154
## 
##            age
## Y               [,1]     [,2]
##   Aprobado  16.55189 1.193246
##   Reprobado 17.02885 1.361444
## 
##            address
## Y                   R         U
##   Aprobado  0.2264151 0.7735849
##   Reprobado 0.2307692 0.7692308
## 
##            famsize
## Y                 GT3       LE3
##   Aprobado  0.6886792 0.3113208
##   Reprobado 0.7307692 0.2692308
## 
##            Pstatus
## Y                    A          T
##   Aprobado  0.11320755 0.88679245
##   Reprobado 0.07692308 0.92307692
## 
##            Medu
## Y               [,1]     [,2]
##   Aprobado  2.849057 1.103999
##   Reprobado 2.509615 1.105987
## 
##            Fedu
## Y               [,1]     [,2]
##   Aprobado  2.603774 1.085704
##   Reprobado 2.375000 1.098874
## 
##            Mjob
## Y              at_home     health      other   services    teacher
##   Aprobado  0.15566038 0.09433962 0.32075472 0.26415094 0.16509434
##   Reprobado 0.19230769 0.06730769 0.39423077 0.21153846 0.13461538
## 
##            Fjob
## Y              at_home     health      other   services    teacher
##   Aprobado  0.04716981 0.03773585 0.56603774 0.26886792 0.08018868
##   Reprobado 0.07692308 0.04807692 0.50000000 0.30769231 0.06730769
## 
##            reason
## Y               course       home      other reputation
##   Aprobado  0.34433962 0.26415094 0.11320755 0.27830189
##   Reprobado 0.45192308 0.27884615 0.04807692 0.22115385
## 
##            guardian
## Y               father     mother      other
##   Aprobado  0.24056604 0.70283019 0.05660377
##   Reprobado 0.21153846 0.65384615 0.13461538
## 
##            traveltime
## Y               [,1]      [,2]
##   Aprobado  1.438679 0.6891262
##   Reprobado 1.480769 0.7238083
## 
##            studytime
## Y               [,1]      [,2]
##   Aprobado  2.061321 0.8767747
##   Reprobado 2.009615 0.7943391
## 
##            failures
## Y                [,1]      [,2]
##   Aprobado  0.1650943 0.5207618
##   Reprobado 0.6923077 0.9860869
## 
##            schoolsup
## Y                  no       yes
##   Aprobado  0.8962264 0.1037736
##   Reprobado 0.8365385 0.1634615
## 
##            famsup
## Y                  no       yes
##   Aprobado  0.4103774 0.5896226
##   Reprobado 0.3461538 0.6538462
## 
##            paid
## Y                  no       yes
##   Aprobado  0.4858491 0.5141509
##   Reprobado 0.6250000 0.3750000
## 
##            activities
## Y                  no       yes
##   Aprobado  0.5047170 0.4952830
##   Reprobado 0.4807692 0.5192308
## 
##            nursery
## Y                  no       yes
##   Aprobado  0.2216981 0.7783019
##   Reprobado 0.1826923 0.8173077
## 
##            higher
## Y                   no        yes
##   Aprobado  0.02830189 0.97169811
##   Reprobado 0.09615385 0.90384615
## 
##            internet
## Y                  no       yes
##   Aprobado  0.1367925 0.8632075
##   Reprobado 0.2019231 0.7980769
## 
##            romantic
## Y                  no       yes
##   Aprobado  0.6981132 0.3018868
##   Reprobado 0.5961538 0.4038462
## 
##            famrel
## Y               [,1]      [,2]
##   Aprobado  3.938679 0.9241438
##   Reprobado 3.903846 0.9086342
## 
##            freetime
## Y               [,1]      [,2]
##   Aprobado  3.212264 0.9915475
##   Reprobado 3.317308 0.9478071
## 
##            goout
## Y               [,1]     [,2]
##   Aprobado  2.962264 1.083478
##   Reprobado 3.403846 1.119286
## 
##            Dalc
## Y               [,1]      [,2]
##   Aprobado  1.443396 0.9089360
##   Reprobado 1.519231 0.8241591
## 
##            Walc
## Y               [,1]     [,2]
##   Aprobado  2.287736 1.297917
##   Reprobado 2.326923 1.295566
## 
##            health
## Y               [,1]     [,2]
##   Aprobado  3.481132 1.419105
##   Reprobado 3.682692 1.388332
## 
##            absences
## Y               [,1]     [,2]
##   Aprobado  5.051887 5.928655
##   Reprobado 5.500000 8.297853
## 
##            G1
## Y                [,1]     [,2]
##   Aprobado  12.452830 2.743824
##   Reprobado  7.740385 1.723595
## 
##            G2
## Y                [,1]     [,2]
##   Aprobado  12.608491 2.446106
##   Reprobado  6.769231 2.749160

En este paso, se entrenó el modelo Naive Bayes con el conjunto de entrenamiento. Antes de ajustarlo, se eliminó la variable G3 para evitar fuga de información, ya que de esa variable se construyó directamente resultado, y también se convirtieron a factor las variables categóricas que estaban como texto. Al estimar las probabilidades del modelo, se obtuvo una probabilidad a priori de 67.09% para Aprobado y 32.91% para Reprobado, lo que coincide con la distribución observada en los datos de entrenamiento. Además, al revisar las probabilidades condicionales, se nota que variables como failures, G1 y G2 presentan diferencias más marcadas entre aprobados y reprobados, por lo que parecen ser de las más útiles para la clasificación.

# Predicción sobre el conjunto de prueba
pred_nb <- predict(modelo_nb, prueba_nb[, -which(names(prueba_nb) == "resultado")], type = "class")

# Tabla de clasificación
tt <- table(pred_nb, prueba_labels)
tt
##            prueba_labels
## pred_nb     Aprobado Reprobado
##   Aprobado        45         6
##   Reprobado        8        20
# Matriz de confusión
confusionMatrix(pred_nb, prueba_labels)
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  Aprobado Reprobado
##   Aprobado        45         6
##   Reprobado        8        20
##                                           
##                Accuracy : 0.8228          
##                  95% CI : (0.7206, 0.8996)
##     No Information Rate : 0.6709          
##     P-Value [Acc > NIR] : 0.001988        
##                                           
##                   Kappa : 0.6064          
##                                           
##  Mcnemar's Test P-Value : 0.789268        
##                                           
##             Sensitivity : 0.8491          
##             Specificity : 0.7692          
##          Pos Pred Value : 0.8824          
##          Neg Pred Value : 0.7143          
##              Prevalence : 0.6709          
##          Detection Rate : 0.5696          
##    Detection Prevalence : 0.6456          
##       Balanced Accuracy : 0.8091          
##                                           
##        'Positive' Class : Aprobado        
## 
# Tasa de aciertos
TA <- (sum(diag(tt))) / sum(tt)
paste("Tasa de aciertos: ", round(TA, 4) * 100, "%", sep = "")
## [1] "Tasa de aciertos: 82.28%"

Al aplicar el modelo al conjunto de prueba, la tabla de clasificación mostró que se predijeron correctamente 45 estudiantes aprobados y 20 reprobados, mientras que 6 reprobados fueron clasificados como aprobados y 8 aprobados como reprobados. En conjunto, esto produjo una precisión de 82.28%, lo que indica que el modelo acertó en más de cuatro de cada cinco casos. Además, este resultado fue mejor que el No Information Rate de 67.09%, lo que demuestra que el clasificador sí está capturando patrones reales de los datos y no simplemente asignando siempre la clase mayoritaria. La matriz de confusión también mostró una sensibilidad de 0.8491 y una especificidad de 0.7692, por lo que el modelo identifica mejor a los estudiantes que aprueban, aunque también mantiene un desempeño aceptable para detectar a los que reprueban. En general, los resultados sugieren que el clasificador bayesiano funciona bastante bien para este problema y ofrece un desempeño sólido en la predicción del rendimiento académico.

Paso 4: Validar la estabilidad del modelo

# Base para validación cruzada 
student.cv <- student.mat[, !(names(student.mat) %in% c("G3"))]#

# Convertir variables a factor
student.cv[] <- lapply(student.cv, function(x) {
  if(is.character(x)) as.factor(x) else x
})

# Control de validación cruzada
train_control <- trainControl(method = "cv",
                              number = 5,
                              savePredictions = TRUE)

# Entrenar Naive Bayes con validación cruzada
NBC_cv <- train(resultado ~ .,
                data = student.cv,
                method = "naive_bayes",
                trControl = train_control)

# Resultados de validación cruzada
NBC_cv
## Naive Bayes 
## 
## 395 samples
##  32 predictor
##   2 classes: 'Aprobado', 'Reprobado' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 316, 316, 316, 316, 316 
## Resampling results across tuning parameters:
## 
##   usekernel  Accuracy   Kappa    
##   FALSE      0.8101266  0.5768033
##    TRUE      0.7924051  0.4450763
## 
## Tuning parameter 'laplace' was held constant at a value of 0
## Tuning
##  parameter 'adjust' was held constant at a value of 1
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were laplace = 0, usekernel = FALSE
##  and adjust = 1.

En este paso, se evaluó la estabilidad del modelo mediante una validación cruzada de 5 particiones, usando toda la base sin la variable G3. El modelo se entrenó con 395 observaciones y 32 predictores, y los resultados muestran que la mejor configuración fue la de usekernel = FALSE, con una accuracy promedio de 0.8101 y un Kappa de 0.5768. Esto sugiere que, al repetir el entrenamiento en distintas particiones, el modelo mantiene un desempeño bastante consistente y no depende demasiado de un solo corte de los datos.

# Matriz de confusión usando las predicciones guardadas por caret
confusionMatrix(NBC_cv$pred$pred, NBC_cv$pred$obs)
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  Aprobado Reprobado
##   Aprobado       488       115
##   Reprobado       42       145
##                                           
##                Accuracy : 0.8013          
##                  95% CI : (0.7717, 0.8286)
##     No Information Rate : 0.6709          
##     P-Value [Acc > NIR] : 2.662e-16       
##                                           
##                   Kappa : 0.5153          
##                                           
##  Mcnemar's Test P-Value : 9.126e-09       
##                                           
##             Sensitivity : 0.9208          
##             Specificity : 0.5577          
##          Pos Pred Value : 0.8093          
##          Neg Pred Value : 0.7754          
##              Prevalence : 0.6709          
##          Detection Rate : 0.6177          
##    Detection Prevalence : 0.7633          
##       Balanced Accuracy : 0.7392          
##                                           
##        'Positive' Class : Aprobado        
## 

La matriz de confusión con las predicciones guardadas por caret confirma ese comportamiento. En total, el modelo clasificó correctamente 488 aprobados y 145 reprobados, con una precisión global de 80.13%, que sigue siendo claramente superior al No Information Rate de 67.09%. Además, la sensibilidad de 0.9208 muestra que el modelo identifica muy bien a los estudiantes que aprueban, aunque la especificidad de 0.5577 indica que tiene más dificultad para detectar a los que reprueban. En general, estos resultados muestran que el clasificador bayesiano es relativamente estable y robusto, aunque su desempeño sigue siendo mejor para la clase mayoritaria de aprobados que para la de reprobados.

Paso 5: Interpretación de los resultados finales:

Al aplicar validación cruzada con 5 particiones, el modelo fue entrenado y evaluado múltiples veces con diferentes subconjuntos del conjunto de datos. Esto nos permite obtener una estimación más robusta del desempeño general del modelo.

Matriz de confusión

En el conjunto de prueba (79 estudiantes), el modelo clasificó correctamente 45 como “Aprobado” y 20 como “Reprobado”, pero se equivocó en 8 estudiantes que sí aprobaron y en 6 que no aprobaron.

En la validación cruzada, el modelo clasificó correctamente 488 casos de “Aprobado” y 145 de “Reprobado”. Sin embargo, se equivocó en 115 casos de “Reprobado” que clasificó como “Aprobado”, y en 42 casos de “Aprobado” que clasificó como “Reprobado”.

Notamos que el modelo comete más errores en la clase “Reprobado”.

Exactitud (Accuracy)

En el conjunto de prueba, el modelo obtuvo una exactitud de 0.8228, lo que indica que aproximadamente 82 de cada 100 estudiantes fueron clasificados correctamente.

En la validación cruzada, la exactitud promedio fue de aproximadamente 0.8013, lo que confirma que el modelo mantiene un desempeño estable.

Índice Kappa

A diferencia de la exactitud, el Kappa toma en cuenta que algunos aciertos pueden ocurrir por azar. El modelo obtuvo un Kappa de 0.6064 en el conjunto de prueba, pero en la validación cruzada el Kappa promedio fue de 0.5153, indicando un desempeño moderado y más representativo del comportamiento general del modelo.

Conclusión

El modelo logró predecir correctamente si un estudiante aprobaría o reprobaría matemáticas en aproximadamente 80% a 82% de los casos, lo que indica un buen desempeño general. Sin embargo, el Índice Kappa de 0.5153 obtenido en la validación cruzada muestra que el desempeño del modelo es moderado. Esto sugiere que parte de las predicciones podrían estar influenciados por el desbalance entre las clases. Además, se observa que el modelo comete más errores al identificar a los estudiantes “Reprobados”, lo que indica una menor capacidad para predecir correctamente esta clase. En general, el modelo es útil, pero podría mejorarse aplicando técnicas como el balanceo de datos o la selección de variables más relevantes.