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.
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.