El clasificador Bayesiano también conocido como Naive Bayes es uno de los clasificadores más utilizados por su simplicidad y rapidez. Se trata de una técnica de clasificación y predicción supervisada que construye modelos que predicen la probabilidad de posibles resultados, con base en el Teorema de Bayes.
Se basa en la suposición de que todos las variables son independientes dada la clase; esto es, cada variable \(x_i\) es condicionalmente independiente de los demás atributos dada la clase. Es decir, asume que la presencia o ausencia de una característica particular no está relacionada con la presencia o ausencia de cualquier otra característica, dada la clase. Por ejemplo, una fruta puede ser considerada como una manzana si es roja, redonda y de alrededor de 7 cm de diámetro. El clasificador Bayesiano considera que cada una de estas características contribuye de manera independiente a la probabilidad de que esta fruta sea una manzana, independientemente de la presencia o ausencia de las otras características.
Una ventaja de este clasificador es que solo se requiere una pequeña cantidad de datos de entrenamiento para estimar los parámetros (las medias y las varianzas de las variables) necesarias para la clasificación. Como se asumen que las variables son independientes, solo es necesario determinar las varianzas de las variables de cada clase y no toda la matriz de covarianza.
El aprendizaje se puede ver como el proceso de encontrar la clase más probable, dado un conjunto de entrenamiento y un conocimiento a priori sobre la probabilidad de cada clase.
Antes de entrenar el modelo, es importante:
Revisar que no haya valores faltantes.
Codificar variables categóricas si es necesario.
Escoger las variables predictoras relevantes.
+Analizar la distribución de las clases (balance o desbalance).
Este paso garantiza que el algoritmo pueda trabajar correctamente con los datos y que las probabilidades calculadas sean representativas.
Divide el conjunto total en dos subconjuntos de forma aleatoria:
Un conjunto de entrenamiento (aproximadamente 70-80%), que se usa para ajustar el modelo.
Un conjunto de prueba (20-30%) que no se usará en la construcción inicial del modelo y servirá para evaluar el desempeño del mismo.
Suponiendo que las variables son independientes dada la clase y con ayuda del teorema de Bayes, se realizan los siguientes pasos para aplicar el clasificador:
Como el denominador \(P(x)\) es común a todas las clases, se omite en la comparación.
Calcula las probabilidades posteriores: la probabilidad posterior de cada clase es proporcional al producto entre las probabilidad a priori y las probabilidades condicionales. \[ P(C_k|{\bf x}) \propto P(C_k) \prod_{j=1}^n P(x_j|C_k)\]
Predicciones: el modelo asigna a cada observación la clase que tenga la mayor probabilidad posterior \(P(C_k|{\bf x})\)
Una vez entrenado, es importante validar si el modelo es consistente y robusto. Algunas estrategias comunes son:
Validación cruzada (k-fold cross-validation) para ver cómo varía el desempeño al cambiar los subconjuntos de entrenamiento/prueba.
Revisar si hay sobreajuste (overfitting). Muy buen desempeño en el conjunto de entrenamiento, pero malo en prueba.
Verificar qué tanto influye cada variable en la clasificación.
Finalmente, es importante aprender a analizar e interpretar:
La matriz de confusión: para ver cuántas observaciones fueron correctamente o incorrectamente clasificadas.
La exactitud (accuracy) y otras métricas como precisión, sensibilidad y especificidad, dependiendo del problema.
La probabilidad posterior de pertenecer a cada clase, útil en contextos donde queremos interpretar el grado de certeza del modelo.
Supongamos que tenemos un conjunto de datos sobre las horas de estudio y número de ausencias en una clase y queremos predecir si el estudiante aprueba o reprueba.
En la figura podemos observar datos simulados de esta situación. Queremos predecir la clase de una nueva observación (🔵) que representa un nuevo estudiante.
library(ggplot2)
set.seed(2025)
n <- 20
data <- data.frame(
horas_estudio = c(rnorm(n, 5, 1.5), rnorm(n, 3, 1.2)),
ausencias = c(rnorm(n, 2, 1), rnorm(n, 6, 1)),
clase = factor(c(rep("Aprueba", n), rep("Reprueba", n)))
)
nuevo <- data.frame(horas_estudio = 5, ausencias = 4)
ggplot(data, aes(x =ausencias , y = horas_estudio, color = clase)) +
geom_point(size = 3) +
scale_color_manual(values = c("Aprueba" = "green4", "Reprueba" = "red")) +
geom_point(data = nuevo, aes(x = ausencias, y = horas_estudio),
color = "blue", shape = 19, size = 3) +
labs(x = "Número de ausencias", y = "Horas de estudio") +
theme_minimal()
¿Cómo actúa el clasificador Bayesiano?
El Clasificador Bayesiano estima la probabilidad de que este punto azul pertenezca a cada clase, en función de:
Cuán frecuente es cada clase (probabilidad a priori).
Cómo se distribuyen las horas de estudio y ausencias dentro de cada clase (probabilidades condicionales).
Luego, predice la clase con mayor probabilidad posterior.
Para este ejemplo, tenemos dos variables sintéticas:
hora
y finde
. Queremos predecir la variable
lugar
donde se encuentra la persona.
hora <- c(8,14,24,8,14,24,8,14,24,8,14,24,8,14,24,8,14,24,24,24)
finde <- c(1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,1,1)
lugar <- c("casa","restaurante","casa","trabajo","trabajo","casa","trabajo",
"trabajo","casa","casa","restaurante","casa","trabajo",
"trabajo","casa","casa","restaurante","casa","cine","cine")
data1 <- data.frame(hora,finde,lugar)
Preparación inicial y limpieza de los datos:
Veamos como se distribuyen los registros según las variables.
tabla1 <-table(data1$hora,data1$lugar)
tabla1
/ | casa | cine | restaurante | trabajo |
---|---|---|---|---|
8 | 3 | 0 | 0 | 3 |
14 | 0 | 0 | 3 | 3 |
24 | 6 | 2 | 0 | 0 |
tabla2 <-table(data1$finde,data1$lugar)
tabla2
/ | casa | cine | restaurante | trabajo |
---|---|---|---|---|
0 | 3 | 0 | 0 | 6 |
1 | 6 | 2 | 3 | 0 |
Dividir los datos en conjunto de entrenamiento y prueba
Para este ejemplo, todos nuestros datos serán el conjunto de entrenamiento y supondremos nuevos datos para el conjunto de prueba. Por ejemplo, supongamos las siguientes dos nuevas observaciones.
nuevos <- data.frame(hora=24,finde=0)
Aplicar el Clasificador Bayesiano
En R, usaremos se tiene la librería e1071() , que implementa Naive Bayes fácilmente.
Los modelos de pronostico Bayesiano, y en particular el clasificador
Bayesiano pierden información cuando las variables son numéricas
continuos, y vamos a ver la prueba en este ejemplo. Crearemos un modelo
con la variable hora
tal cual, y después haremos el mismo
modelo con la variable hora convertida en factor.
library(e1071)
modelo1 <- naiveBayes(lugar ~ ., data = data1)
modelo1
##
## Naive Bayes Classifier for Discrete Predictors
##
## Call:
## naiveBayes.default(x = X, y = Y, laplace = laplace)
##
## A-priori probabilities:
## Y
## casa cine restaurante trabajo
## 0.45 0.10 0.15 0.30
##
## Conditional probabilities:
## hora
## Y [,1] [,2]
## casa 18.66667 8.000000
## cine 24.00000 0.000000
## restaurante 14.00000 0.000000
## trabajo 11.00000 3.286335
##
## finde
## Y [,1] [,2]
## casa 0.6666667 0.5
## cine 1.0000000 0.0
## restaurante 1.0000000 0.0
## trabajo 0.0000000 0.0
Podemos calcular la predicción para los datos de entrenamiento y la tasa de aciertos.
pred <- predict(modelo1,data1[,-3],type="class")
tt <- table(pred,data1[,3])
tt
pred/ | casa | cine | restaurante | trabajo |
---|---|---|---|---|
casa | 0 | 0 | 0 | 0 |
cine | 6 | 2 | 0 | 0 |
restaurante | 3 | 0 | 3 | 0 |
trabajo | 0 | 0 | 0 | 6 |
TA <- (sum(diag(tt)))/sum(tt) # tasa de aciertos
paste("Tasa de aciertos: ",round(TA,4)*100, "%",sep="")
## [1] "Tasa de aciertos: 55%"
La predicción que obtenemos con el modelo es baja. Este problema es habitual cuando usamos datos continuos, que generan distribuciones de probabilidad continuas. Revisemos los datos.
str(data1)
## 'data.frame': 20 obs. of 3 variables:
## $ hora : num 8 14 24 8 14 24 8 14 24 8 ...
## $ finde: num 1 1 1 0 0 0 0 0 0 1 ...
## $ lugar: chr "casa" "restaurante" "casa" "trabajo" ...
Vamos a convertir las variables en factores para que funcione correctamente el modelo:
data1 <- data1 %>%
mutate(across(c(hora,hora,finde), as.factor))
str(data1)
## 'data.frame': 20 obs. of 3 variables:
## $ hora : Factor w/ 3 levels "8","14","24": 1 2 3 1 2 3 1 2 3 1 ...
## $ finde: Factor w/ 2 levels "0","1": 2 2 2 1 1 1 1 1 1 2 ...
## $ lugar: chr "casa" "restaurante" "casa" "trabajo" ...
Calculamos de nuevo, el modelo y su predicción.
modelo1a <- naiveBayes(lugar ~ ., data = data1)
modelo1a
##
## Naive Bayes Classifier for Discrete Predictors
##
## Call:
## naiveBayes.default(x = X, y = Y, laplace = laplace)
##
## A-priori probabilities:
## Y
## casa cine restaurante trabajo
## 0.45 0.10 0.15 0.30
##
## Conditional probabilities:
## hora
## Y 8 14 24
## casa 0.3333333 0.0000000 0.6666667
## cine 0.0000000 0.0000000 1.0000000
## restaurante 0.0000000 1.0000000 0.0000000
## trabajo 0.5000000 0.5000000 0.0000000
##
## finde
## Y 0 1
## casa 0.3333333 0.6666667
## cine 0.0000000 1.0000000
## restaurante 0.0000000 1.0000000
## trabajo 1.0000000 0.0000000
preda <- predict(modelo1a,data1[,-3],type="class")
tta <- table(preda,data1[,3])
tta
preda/ | casa | cine | restaurante | trabajo |
---|---|---|---|---|
casa | 9 | 2 | 0 | 0 |
cine | 0 | 0 | 0 | 0 |
restaurante | 0 | 0 | 3 | 0 |
trabajo | 0 | 0 | 0 | 6 |
TAa <- (sum(diag(tta)))/sum(tta) # tasa de aciertos
paste("Tasa de aciertos: ",round(TAa,4)*100, "%",sep="")
## [1] "Tasa de aciertos: 90%"
Notamos que ahora lo hace mejor. Ahora, podemos predecir la categoría que tomaría el dato nuevo.
predict(modelo1a,nuevos)
## [1] casa
## Levels: casa cine restaurante trabajo
Validar la estabilidad del modelo
Al ser un ejemplo sencillo no se realizó este paso.
Interpretación de los resultados finales
cine
es en el que se equivoca el modelo.Para este ejemplo, usaremos los datos de supervivientes del Titanic que vienen en R.
Preparación inicial y limpieza de los datos:
data2 <- as.data.frame(Titanic)
dim(data2)
## [1] 32 5
head(data2)
Class | Sex | Age | Survived | Freq |
---|---|---|---|---|
1st | Male | Child | No | 0 |
2nd | Male | Child | No | 0 |
3rd | Male | Child | No | 35 |
Crew | Male | Child | No | 0 |
1st | Female | Child | No | 0 |
2nd | Female | Child | No | 0 |
Esta base de datos, debe acomodarse, pues hay muchas columnas donde la frecuencias es cero y podríamos separar individualmente.
library(tidyr)
library(dplyr)
data2_long <- data2 %>% uncount(weights = Freq)
dim(data2_long)
## [1] 2201 4
head(data2_long)
Class | Sex | Age | Survived |
---|---|---|---|
3rd | Male | Child | No |
3rd | Male | Child | No |
3rd | Male | Child | No |
3rd | Male | Child | No |
3rd | Male | Child | No |
3rd | Male | Child | No |
Ahora, data2_long
tiene una fila por
pasajero individual, lista para ser usada con modelos de
clasificación.
Dividir los datos en conjunto de entrenamiento y prueba
Mediante un muestreo aleatorio, separamos el conjunto de entrenamiento y en conjunto de prueba. Supongamos en 5 grupos (folds=5), 4 para entrenamiento y 1 para prueba.
set.seed(2025)
library(caret)
folds <- createFolds(data2_long$Survived, k =5)
entrenamiento <- data2_long[-folds[[5]],]
prueba <- data2_long[folds[[5]],]
Por otra parte, guardamos las etiquetas del diagnóstico de todas las observaciones en dos vectores por separado.
# Etiquetas
entrenamiento_labels <- data2_long$Survived[-folds[[5]]]
prueba_labels <- data2_long$Survived[folds[[5]]]
Aplicar el Clasificador Bayesiano
modelo2 <- naiveBayes(Survived ~ ., data2_long)
modelo2
##
## Naive Bayes Classifier for Discrete Predictors
##
## Call:
## naiveBayes.default(x = X, y = Y, laplace = laplace)
##
## A-priori probabilities:
## Y
## No Yes
## 0.676965 0.323035
##
## Conditional probabilities:
## Class
## Y 1st 2nd 3rd Crew
## No 0.08187919 0.11208054 0.35436242 0.45167785
## Yes 0.28551336 0.16596343 0.25035162 0.29817159
##
## Sex
## Y Male Female
## No 0.91543624 0.08456376
## Yes 0.51617440 0.48382560
##
## Age
## Y Child Adult
## No 0.03489933 0.96510067
## Yes 0.08016878 0.91983122
Calculamos la predicción del modelo.
pred2 <- predict(modelo2,prueba[,-4])
table(pred2,prueba_labels)
pred2/prueba_labels | No | Yes |
---|---|---|
No | 280 | 69 |
Yes | 18 | 73 |
confusionMatrix(pred2,prueba_labels)
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 280 69
## Yes 18 73
##
## Accuracy : 0.8023
## 95% CI : (0.7619, 0.8385)
## No Information Rate : 0.6773
## P-Value [Acc > NIR] : 3.402e-09
##
## Kappa : 0.5008
##
## Mcnemar's Test P-Value : 8.296e-08
##
## Sensitivity : 0.9396
## Specificity : 0.5141
## Pos Pred Value : 0.8023
## Neg Pred Value : 0.8022
## Prevalence : 0.6773
## Detection Rate : 0.6364
## Detection Prevalence : 0.7932
## Balanced Accuracy : 0.7268
##
## 'Positive' Class : No
##
Validar la estabilidad del modelo
Apliquemos validación cruzada para validar la estabilidad del modelo. La idea es analizar que la tasa de aciertos conseguida no dependa de la partición utilizada.
set.seed(2025)
train_control <- trainControl(method="cv",number=20,savePredictions = TRUE)
NBC_cv <- train(Survived ~ ., data=cbind(prueba, diagnosis=prueba_labels),
method = "naive_bayes", trControl = train_control)
# Resultados de validación cruzada
NBC_cv
## Naive Bayes
##
## 440 samples
## 4 predictor
## 2 classes: 'No', 'Yes'
##
## No pre-processing
## Resampling: Cross-Validated (20 fold)
## Summary of sample sizes: 418, 418, 418, 418, 418, 418, ...
## Resampling results across tuning parameters:
##
## usekernel Accuracy Kappa
## FALSE 0.9954545 0.9902655
## TRUE 1.0000000 1.0000000
##
## 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 = TRUE
## and adjust = 1.
# Matriz de confusión usando predicciones guardadas por caret
confusionMatrix(NBC_cv$pred$pred, NBC_cv$pred$obs)
## Confusion Matrix and Statistics
##
## Reference
## Prediction No Yes
## No 594 0
## Yes 2 284
##
## Accuracy : 0.9977
## 95% CI : (0.9918, 0.9997)
## No Information Rate : 0.6773
## P-Value [Acc > NIR] : <2e-16
##
## Kappa : 0.9948
##
## Mcnemar's Test P-Value : 0.4795
##
## Sensitivity : 0.9966
## Specificity : 1.0000
## Pos Pred Value : 1.0000
## Neg Pred Value : 0.9930
## Prevalence : 0.6773
## Detection Rate : 0.6750
## Detection Prevalence : 0.6750
## Balanced Accuracy : 0.9983
##
## 'Positive' Class : No
##
Interpretación de los resultados finales
Al aplicar validación cruzada con 20 particiones, el modelo fue entrenado y evaluado múltiples veces con diferentes subconjuntos del conjunto de datos. Esto permite obtener una estimación más robusta del desempeño general del modelo.
Veamos algunos puntos clave que se deben analizar:
Matriz de confusión: nos permite observar en promedio cuántas observaciones fueron clasificadas correctamente y cuántas erróneamente. Notamos que se equivocó en los sobrevivientes.
Exactitud (Accuracy): el valor reportado representa el promedio de aciertos que tuvo el clasificador a lo largo de los 20 folds. Una exactitud de 99.97% indica buen desempeño.
Índice Kappa: compara la precisión del modelo con la que se esperaría por azar. Sus valores típicamente se interpretan así:
Nota importante: Si el valor de Kappa es bajo pero la exactitud es alta, puede indicar que el modelo está sesgado hacia la clase mas grande.
Clasificación del rendimiento académico de estudiantes de matemáticas
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 aprobará o no el curso de matemáticas, utilizando información relacionada con:
La base de datos la puedes descargar desde Moodle. Para mayor información de las variables pueden consultar: Student Mat Analysis