En el campo del aprendizaje estadístico, la mayoría de los problemas se pueden clasificar en dos grandes categorías: aprendizaje supervisado y aprendizaje no supervisado.
En el aprendizaje supervisado contamos con un conjunto de datos en el que, para cada observación, disponemos de variables predictoras y una variable respuesta asociada.
\[ i \in \{1, 2, \dots, n\}, \quad (x_i, y_i), \quad x_i \in \mathbb{R}^p, \quad y_i \in \mathbb{R} \;\text{o}\; y_i \in \{0,1\} \] 1. Cuando y pertenece a los reales, la variable respuesta es numérica continua y corresponde a problemas de regresión. Ejemplos: precio de una casa, altura de una persona, demanda de un producto.
El objetivo es construir una función que relacione las variables predictoras con la respuesta.
\[ f: \mathbb{R}^p \rightarrow \mathbb{R}, \quad y_i \approx f(x_i) \]
Este modelo puede tener dos fines principales:
Algunos métodos clásicos dentro de este enfoque incluyen:
Regresión lineal
Regresión logística
Árboles de decisión
Modelos más flexibles como Generalized Additive Model (GAM), boosting y máquinas de soporte vectorial (SVM)
Estos modelos tienen diferentes niveles de flexibilidad: la regresión lineal ofrece una aproximación simple y fácil de interpretar bajo supuestos de linealidad, mientras que la regresión logística adapta esta idea para modelar probabilidades en problemas de clasificación.
Por otro lado, enfoques más flexibles como los modelos aditivos generalizados (GAM) permiten capturar relaciones no lineales de manera interpretable, el boosting mejora el desempeño combinando múltiples modelos simples de forma secuencial, y las máquinas de soporte vectorial (SVM) construyen fronteras de decisión óptimas al maximizar la separación entre observaciones.
En esencia, el aprendizaje supervisado utiliza la información de la variable respuesta como una “guía” para entrenar el modelo.
En contraste, el aprendizaje no supervisado se presenta en situaciones donde no existe una variable respuesta. Es decir, para cada observación solo contamos con un vector de variables.
\[ i \in \{1, 2, \dots, n\}, \quad x_i \in \mathbb{R}^p, \quad \{x_1, x_2, \dots, x_n\} \subset \mathbb{R}^p \]
Esto implica que no podemos aplicar directamente modelos de predicción como la regresión, ya que no hay una variable objetivo que predecir. Por esta razón, se dice que estamos trabajando “a ciegas”.
El objetivo aquí no es predecir, sino descubrir estructura en los datos, como:
Uno de los enfoques más importantes en este contexto es el análisis de clusters (clustering).
\[ \mathcal{C} = \{C_1, C_2, \dots, C_K\}, \quad \bigcup_{k=1}^{K} C_k = \{1,2,\dots,n\}, \quad C_k \cap C_j = \emptyset \quad \text{si } k \neq j \] El objetivo es agrupar observaciones de tal forma que aquellas dentro de un mismo grupo sean más similares entre sí que con las de otros grupos.
Esta similitud suele definirse mediante una medida de distancia (como la distancia euclidiana), por lo que la forma en que se escalan las variables puede influir significativamente en los resultados.
En contraste con el aprendizaje supervisado, aquí no existe una “respuesta correcta”, por lo que los clusters encontrados deben interpretarse con cuidado y en función del contexto del problema. Entre los métodos más comunes se encuentran k-means, que busca particiones compactas alrededor de centroides, y el clustering jerárquico, que construye una estructura de agrupamiento en forma de árbol.
Ambos enfoques son fundamentales y complementarios en el análisis de datos, y la elección entre uno u otro depende de la disponibilidad de la variable respuesta y del objetivo del análisis.
Trabajaremos con una base de datos del Titanic, enfocándonos en identificar qué factores influyeron en la supervivencia de los pasajeros.
# install.packages("titanic")
library(titanic)
data("titanic_train") # Carga en memoria el dataset
titanic <- titanic_train # Copia el dataset en un objeto
head(titanic) # Muestra las primeras 6 observaciones
## PassengerId Survived Pclass
## 1 1 0 3
## 2 2 1 1
## 3 3 1 3
## 4 4 1 1
## 5 5 0 3
## 6 6 0 3
## Name Sex Age SibSp Parch
## 1 Braund, Mr. Owen Harris male 22 1 0
## 2 Cumings, Mrs. John Bradley (Florence Briggs Thayer) female 38 1 0
## 3 Heikkinen, Miss. Laina female 26 0 0
## 4 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35 1 0
## 5 Allen, Mr. William Henry male 35 0 0
## 6 Moran, Mr. James male NA 0 0
## Ticket Fare Cabin Embarked
## 1 A/5 21171 7.2500 S
## 2 PC 17599 71.2833 C85 C
## 3 STON/O2. 3101282 7.9250 S
## 4 113803 53.1000 C123 S
## 5 373450 8.0500 S
## 6 330877 8.4583 Q
dim(titanic)
## [1] 891 12
Dado que contamos con una variable respuesta bien definida, podríamos emplear métodos clásicos como la regresión lineal o la regresión logística. Sin embargo, nos enfocaremos en modelos que construyen reglas de decisión directamente a partir de los datos, como los árboles de decisión.
Estos modelos resultan especialmente útiles por su interpretabilidad, ya que representan el proceso de predicción mediante reglas simples que pueden visualizarse fácilmente. Además, permiten comprender de manera clara conceptos fundamentales como la partición del espacio de variables, el sobreajuste y la complejidad del modelo.
Este enfoque no solo facilita la interpretación de los resultados, sino que también sirve como base para métodos más avanzados que extienden la lógica de los árboles, como los ensambles (por ejemplo, random forests y boosting).
Un árbol de decisión es un modelo de aprendizaje supervisado que se utiliza para:
Funciona mediante reglas del tipo if – then – else y se
representa como una estructura similar a un diagrama de flujo:
Entr sus componentes principales tenemos:
El desafío de una implementación de árboles de decisión es elegir la mejor variable y punto de corte en cada paso. Esto se logra con métricas de impureza:
El modelo busca reducir la mezcla de clases considerando que
En R, este proceso se implementa de forma automática con rpart, donde el modelo evalúa distintas variables y puntos de corte, los compara y selecciona las mejores divisiones de manera recursiva; como resultado, el árbol construye una secuencia de reglas que va particionando el espacio hasta formar grupos lo más homogéneos posible.
Cada punto en la gráfica representa una observación de nuestro dataset. x1 y x2 son variables predictoras Los colores representan distintas clases (por ejemplo: 0, 1 y 2) El objetivo es construir un modelo que pueda separar correctamente estas clases
Un árbol de decisión clasifica dividiendo el espacio en regiones mediante reglas simples del tipo “if–then”. Estas reglas generan cortes como: x1 <= 3 y x2 <= 4.
Primer paso: Primera división
El modelo comienza buscando la mejor primera división posible. Se traza una línea vertical en x1 = 3 y esto separa los datos en izquierda x< 3 y por la derecha x1 > 3
Esta es la raíz del árbol. Observamos que en la parte izquierda, los puntos pertenecen casi todos a una misma clase → el nodo es bastante puro.
El modelo continúa dividiendo el subconjunto que aún está mezclado. Se traza una línea horizontal en x2 = 4 y se generan dos nuevas regiones: abajo x2 < 4 y arriba con x2 > 4
Primer corte → nodo raíz
Segundo corte → nodos hijos
Regiones finales → hojas (predicción final)
-> ¿Por qué se hacen estos cortes?
Las líneas no se eligen manualmente, el modelo las aprende automáticamente con la meta de separar los datos en grupos lo más “puros” posible
-> ¿Cómo decide dónde cortar?
El algoritmo prueba muchas posibles divisiones usando una métrica como el Índice de Gini (usado por defecto en R con rpart) bajo la idea clave:
Nodo puro → una sola clase
Nodo impuro → mezcla de clases
Entonces, el modelo busca el corte que más reduce la mezcla de clases.
-> ¿Cómo lo hace R? Todo este proceso es automático.
Cargamos bibliotecas.
library(dplyr) # Manipulación y transformación de datos
library(rpart) # Implementación de árboles de decisión
library(rpart.plot) # Visualización de árboles de decisión
library(tidyr) # Limpieza y reestructuración de datos
library(caret) # Entrenamiento, validación y comparación de modelos
library(caTools) # División de datos en train/test
Seleccionamos variables relevantes para el modelo.
head(titanic)
## PassengerId Survived Pclass
## 1 1 0 3
## 2 2 1 1
## 3 3 1 3
## 4 4 1 1
## 5 5 0 3
## 6 6 0 3
## Name Sex Age SibSp Parch
## 1 Braund, Mr. Owen Harris male 22 1 0
## 2 Cumings, Mrs. John Bradley (Florence Briggs Thayer) female 38 1 0
## 3 Heikkinen, Miss. Laina female 26 0 0
## 4 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35 1 0
## 5 Allen, Mr. William Henry male 35 0 0
## 6 Moran, Mr. James male NA 0 0
## Ticket Fare Cabin Embarked
## 1 A/5 21171 7.2500 S
## 2 PC 17599 71.2833 C85 C
## 3 STON/O2. 3101282 7.9250 S
## 4 113803 53.1000 C123 S
## 5 373450 8.0500 S
## 6 330877 8.4583 Q
titanic <- titanic %>% select(Survived,
Pclass,
Sex,
Age,
SibSp,
Parch,
Fare,
Embarked)
colnames(titanic)
## [1] "Survived" "Pclass" "Sex" "Age" "SibSp" "Parch" "Fare"
## [8] "Embarked"
Estas son las variables del dataset:
PassengerId: Identificador único de cada pasajero. Es un número consecutivo.
Survived: Indicador de supervivencia del pasajero: 0 = No sobrevivió, 1 = Sobrevivió.
Pclass: Clase del boleto del pasajero: 1 = Primera clase, 2 = Segunda clase, 3 = Tercera clase. Es una variable ordinal que representa nivel socioeconómico.
Name: Nombre completo del pasajero.
Sex: Sexo del pasajero: male o female.
Age: Edad del pasajero (en años).
SibSp: Número de hermanos/esposos a bordo.
Parch: Número de padres/hijos (parents/children) a bordo.
Ticket: Número del boleto. Puede tener letras y números, dependiendo del tipo de emisión. No sigue una codificación uniforme.
Fare: Precio que pagó por el boleto (en libras esterlinas). Variable continua.
Cabin: Número de cabina asignada. Puede contener letras (indican sección del barco). Muchos valores están ausentes.
Embarked: Puerto de embarque
Verificamos la clase de cada variable
glimpse(titanic)
## Rows: 891
## Columns: 8
## $ Survived <int> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0…
## $ Pclass <int> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, 3, 2…
## $ Sex <chr> "male", "female", "female", "female", "male", "male", "male",…
## $ Age <dbl> 22, 38, 26, 35, 35, NA, 54, 2, 27, 14, 4, 58, 20, 39, 14, 55,…
## $ SibSp <int> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0…
## $ Parch <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0, 0…
## $ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21…
## $ Embarked <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", "S", "…
# Quitemos caracteres y transformemos a factor
Trabajamos las variables factor
titanic$Survived <- factor(titanic$Survived, levels = c(0,1), labels = c("Muere", "Vive"))
titanic$Pclass <- factor(titanic$Pclass, levels = 1:3, labels = c("Primera clase", "Segunda clase", "Tercera clase"))
titanic$Sex <- factor(titanic$Sex)
titanic$Embarked <- factor(titanic$Embarked)
glimpse(titanic)
## Rows: 891
## Columns: 8
## $ Survived <fct> Muere, Vive, Vive, Vive, Muere, Muere, Muere, Muere, Vive, Vi…
## $ Pclass <fct> Tercera clase, Primera clase, Tercera clase, Primera clase, T…
## $ Sex <fct> male, female, female, female, male, male, male, male, female,…
## $ Age <dbl> 22, 38, 26, 35, 35, NA, 54, 2, 27, 14, 4, 58, 20, 39, 14, 55,…
## $ SibSp <int> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0…
## $ Parch <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0, 0…
## $ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21…
## $ Embarked <fct> S, C, S, S, S, Q, S, S, S, C, S, S, S, S, S, S, Q, S, S, C, S…
Verificamos la presencia de missing values
colSums(is.na(titanic))
## Survived Pclass Sex Age SibSp Parch Fare Embarked
## 0 0 0 177 0 0 0 0
En el dataset del Titanic se observa que la única variable con valores faltantes es Age, con 177 valores NA sobre un total de 891 observaciones.
Estos datos representan aproximadamente un 20% de la variable Age, pero no afectan a las demás variables.
Eliminar estos registros simplifica el análisis y evita introducir supuestos adicionales
Los árboles de decisión son sensibles a la calidad de los datos, por lo que trabajar con datos completos mejora la interpretabilidad.
Dado esto, se decide eliminar las observaciones con valores faltantes en lugar de imputarlos.
Limpiamos la base de datos
titanic <- drop_na(titanic)
colSums(is.na(titanic))
## Survived Pclass Sex Age SibSp Parch Fare Embarked
## 0 0 0 0 0 0 0 0
El dataset es el conjunto de datos completo que tienes inicialmente. Este se divide en diferentes partes para poder entrenar, validar y probar modelos de aprendizaje automático.
Dividimos la base de datos en entrenamiento y prueba
Training: es la parte del conjunto de datos que el modelo usa para aprender patrones. Los árboles de decisión parten de aquí identificando divisiones óptimas basadas en variables.
Testing: es una porción separada del conjunto de datos, utilizada para medir qué tan bien funciona el modelo una vez que ha sido entrenado.
Una regla práctica común es utilizar una proporción 70-30 o 80-20, donde la mayor parte de los datos se usa para entrenar el modelo y el resto para evaluarlo.
La elección depende del tamaño del dataset: si los datos son limitados, se prefiere usar 80-20 para aprovechar más información en el entrenamiento; si se dispone de más datos, 70-30 permite una evaluación más sólida.
set.seed(123) # para reproducibilidad de datos
# La base podría estar ordenada, por lo que conviene shakearla
barajeado <- slice_sample(titanic, prop = 1)
split <- sample.split(barajeado$Survived, SplitRatio = 0.8)
# barajeado
# split # vector lógico
titanic_training <- barajeado %>% subset(split == "TRUE")
titanic_testing <- barajeado %>% subset(split == "FALSE")
dim(titanic_training)[1]
## [1] 571
dim(titanic_testing)[1]
## [1] 143
Entrenamiento del árbol con el índice de Gini
# Este modelo se construye con el índice de Gini
modelo1 <- rpart(Survived ~ ., data = titanic_training, method = "class", parms = list(split = "gini"))
Visualizamos el árbol
extra = 104 es una opción que muestra tanto la probabilidad de cada clase como el número de observaciones en cada nodo, lo cual es útil para entender la distribución de los datos en cada parte del árbol. También muestra el número de observaciones que llegan a cada nodo y las probabilidades de las clases en esos nodos.
rpart.plot(modelo1, type = 2, extra = 104)
Realizamos nuestras predicciones:
y_pred <- predict(modelo1, newdata = titanic_testing, type = "class")
y_pred
## 14 16 23 25 31 32 42 49 70 72 77 81 84
## Vive Muere Muere Muere Vive Muere Muere Muere Muere Muere Vive Muere Muere
## 86 89 90 91 93 115 117 124 136 139 141 143 152
## Muere Vive Vive Vive Muere Muere Muere Muere Vive Vive Muere Muere Muere
## 153 155 158 160 169 171 179 182 189 203 206 208 218
## Vive Muere Muere Muere Muere Vive Vive Vive Muere Muere Vive Muere Muere
## 219 222 232 238 239 245 250 251 252 258 261 262 267
## Muere Vive Muere Muere Muere Muere Muere Muere Muere Muere Muere Vive Vive
## 272 273 274 288 292 293 297 301 325 328 333 337 348
## Muere Muere Vive Muere Muere Muere Muere Vive Muere Muere Muere Vive Muere
## 357 362 365 366 371 378 386 388 391 396 397 401 402
## Muere Vive Muere Muere Vive Vive Muere Muere Vive Vive Vive Vive Vive
## 404 431 434 437 449 450 454 456 460 461 470 474 475
## Muere Muere Muere Muere Vive Muere Muere Muere Muere Muere Vive Muere Muere
## 490 494 501 502 504 511 513 515 518 522 536 537 541
## Muere Muere Muere Muere Vive Muere Muere Vive Muere Vive Muere Muere Muere
## 546 552 558 572 577 585 587 599 601 609 610 611 612
## Vive Vive Muere Muere Muere Muere Muere Vive Muere Muere Muere Muere Muere
## 622 623 624 630 632 638 644 647 648 654 658 670 672
## Muere Vive Muere Muere Muere Vive Muere Muere Vive Muere Muere Vive Muere
## 676 677 683 685 690 692 693 698 700 702 706 711 714
## Muere Muere Muere Muere Vive Muere Vive Muere Muere Muere Muere Muere Vive
## Levels: Muere Vive
# ¿Cómo determinar qué tan buenas son estas predicciones?
La matriz de confusión es una herramienta que permite evaluar el desempeño de un modelo de clasificación comparando las predicciones del modelo con los valores reales. En ella se organizan los resultados en una tabla donde las filas representan los valores reales y las columnas las predicciones del modelo.
En un problema binario, la matriz se compone de cuatro cantidades fundamentales: verdaderos negativos (TN), verdaderos positivos (TP), falsos positivos (FP) y falsos negativos (FN).
Los verdaderos positivos corresponden a los casos en los que el modelo predice correctamente la clase positiva, mientras que los verdaderos negativos son los casos correctamente clasificados como negativos.
Por otro lado, los falsos positivos ocurren cuando el modelo predice positivo pero en realidad es negativo, y los falsos negativos cuando predice negativo siendo realmente positivo.
En R, esta matriz se puede construir directamente comparando las predicciones del modelo con los valores reales.
Generamos la matriz de confusión
y_real <- titanic_testing$Survived
conf_matrix <- confusionMatrix(data = y_pred, reference = y_real, positive = "Vive")
print(conf_matrix$table)
## Reference
## Prediction Muere Vive
## Muere 77 24
## Vive 8 34
Verdaderos negativos (TN = 77): El modelo clasificó correctamente a 80 pasajeros como no sobrevivientes, y efectivamente no sobrevivieron.
Verdaderos positivos (TP = 34): El modelo clasificó correctamente a 41 pasajeros como sobrevivientes, y efectivamente sobrevivieron.
Falsos negativos (FN = 24): El modelo clasificó incorrectamente a 17 pasajeros como no sobrevivientes, cuando en realidad sí sobrevivieron.
Falsos positivos (FP = 8): El modelo clasificó incorrectamente a 5 pasajeros como sobrevivientes, cuando en realidad no sobrevivieron.
Una vez construida la matriz de confusión, es necesario resumir su información mediante métricas que permitan evaluar el desempeño del modelo de forma más clara y comparable.
\[ \text{Accuracy} = \frac{TN + TP}{TN + TP + FN + FP} \]
\[ \text{Precision} = \frac{TP}{TP + FP} \]
\[ \text{Recall} = \frac{TP}{TP + FN} \]
\[ F1 = \frac{2 \cdot (\text{Precision} \cdot \text{Recall})}{\text{Precision} + \text{Recall}} \]
Extraemos las métricas
exactitud <- conf_matrix$overall["Accuracy"]
precision <- conf_matrix$byClass["Precision"]
sensibilidad <- conf_matrix$byClass["Recall"]
F1_score <- conf_matrix$byClass["F1"]
exactitud; precision; sensibilidad; F1_score
## Accuracy
## 0.7762238
## Precision
## 0.8095238
## Recall
## 0.5862069
## F1
## 0.68
Accuracy (Exactitud = 0.7762238 ): El modelo acertó en aproximadamente el 77.62% de las predicciones totales, es decir, clasificó correctamente tanto a sobrevivientes como a no sobrevivientes en la mayoría de los casos.
Precision (Precisión = 0.8095238 ): De todas las personas que el modelo predijo como sobrevivientes, el 80.95% realmente sobrevivió. Es decir, cometió pocos errores al decir que alguien sobrevivió.
Recall (Sensibilidad = 0.5862069 ): El modelo fue capaz de identificar correctamente al 58.62% de los sobrevivientes reales. Esto indica que se le escaparon bastantes verdaderos sobrevivientes (falsos negativos).
F1 Score (Puntaje F1 = 0.68 ): Indica que, aunque el modelo es bastante preciso cuando predice que alguien sobrevivió, no logra encontrar a todos los sobrevivientes reales. Es un desempeño intermedio.
#Modelo 2
# Este modelo se construye con el índice de Ganancia de información
modelo2 <- rpart(Survived ~ ., data = titanic_training, method = "class", parms = list(split = "information"))
Visualizamos el árbol
rpart.plot(modelo2, type = 2, extra = 104)
Realizamos nuestras predicciones
y_pred2 <- predict(modelo2, newdata = titanic_testing, type = "class")
y_pred2
## 14 16 23 25 31 32 42 49 70 72 77 81 84
## Vive Muere Muere Muere Vive Muere Muere Muere Muere Muere Vive Muere Muere
## 86 89 90 91 93 115 117 124 136 139 141 143 152
## Muere Vive Vive Vive Muere Muere Muere Muere Vive Vive Muere Muere Muere
## 153 155 158 160 169 171 179 182 189 203 206 208 218
## Vive Muere Muere Muere Muere Vive Vive Vive Muere Muere Vive Muere Muere
## 219 222 232 238 239 245 250 251 252 258 261 262 267
## Muere Vive Muere Muere Muere Muere Muere Muere Muere Muere Muere Vive Vive
## 272 273 274 288 292 293 297 301 325 328 333 337 348
## Muere Muere Vive Muere Muere Muere Muere Vive Muere Muere Vive Vive Muere
## 357 362 365 366 371 378 386 388 391 396 397 401 402
## Muere Vive Muere Muere Vive Vive Muere Muere Vive Vive Vive Muere Vive
## 404 431 434 437 449 450 454 456 460 461 470 474 475
## Muere Muere Muere Muere Vive Muere Muere Muere Muere Muere Vive Muere Muere
## 490 494 501 502 504 511 513 515 518 522 536 537 541
## Muere Muere Muere Muere Vive Muere Muere Vive Muere Vive Muere Muere Muere
## 546 552 558 572 577 585 587 599 601 609 610 611 612
## Vive Vive Muere Muere Muere Muere Muere Vive Muere Muere Muere Muere Muere
## 622 623 624 630 632 638 644 647 648 654 658 670 672
## Muere Vive Muere Muere Muere Vive Muere Muere Vive Muere Muere Vive Muere
## 676 677 683 685 690 692 693 698 700 702 706 711 714
## Muere Muere Muere Muere Vive Muere Vive Muere Muere Muere Muere Muere Vive
## Levels: Muere Vive
Generamos la matriz de confusión
y_real <- titanic_testing$Survived
conf_matrix2 <- confusionMatrix(data = y_pred2, reference = y_real, positive = "Vive")
print(conf_matrix2$table)
## Reference
## Prediction Muere Vive
## Muere 77 24
## Vive 8 34
Aunque utilizamos dos criterios distintos (Gini e Information Gain), en este caso ambos producen el mismo resultado porque identifican las mismas divisiones óptimas en los datos. Esto ocurre cuando la estructura del dataset es lo suficientemente clara como para que diferentes métricas de impureza coincidan en las decisiones del árbol.
En los modelos anteriores, el árbol se construyó utilizando los parámetros por defecto, lo que implica que la estructura del modelo se determina automáticamente sin restricciones explícitas sobre su complejidad. Sin embargo, esto puede llevar a árboles demasiado grandes o demasiado simples, afectando su capacidad de generalización.
La construcción de múltiples modelos responde a la necesidad de comparar distintas configuraciones y entender cómo afectan el desempeño. Un solo modelo no garantiza ser el mejor, ya que sus resultados dependen tanto de los datos como de los parámetros utilizados. Al variar criterios de división y parámetros de control, es posible identificar qué combinaciones producen mejores predicciones y, al mismo tiempo, comprender el impacto de la complejidad del árbol en su capacidad de generalización.
El argumento control en la función rpart se usa para
proporcionar una lista de parámetros que controlan la complejidad del
árbol y otros aspectos del proceso de ajuste del modelo. Estos
parámetros se establecen usando la función
rpart.control().
maxdepth: establece la profundidad máxima de cualquier nodo en el árbol final
minsplit: El número mínimo de observaciones que deben existir en un nodo antes de intentar una división.
cp: El parámetro de complejidad. Su función es determinar qué tan “buena” debe ser una división para que el algoritmo rpart decida hacerla.
xval: El número de validaciones cruzadas a realizar.
#Modelo 3
modelo3 <- rpart( Survived ~ ., data = titanic_training,
method = "class", parms = list(split = "gini"),
control = rpart.control(
maxdepth = 10,
minsplit = 2,
cp = 0.001))
Visualizamos el árbol
rpart.plot(modelo3, type = 2, extra = 104)
## Warning: labs do not fit even at cex 0.15, there may be some overplotting
Realizamos nuestras predicciones
y_pred3 <- predict(modelo3, newdata = titanic_testing, type = "class")
Generamos la matriz de confusión
conf_matrix3 <- confusionMatrix(y_pred3, titanic_testing$Survived, positive = "Vive")
conf_matrix3$table
## Reference
## Prediction Muere Vive
## Muere 68 20
## Vive 17 38
Extraemos las métricas
exactitud3 <- conf_matrix3$overall["Accuracy"]
precision3 <- conf_matrix3$byClass["Precision"]
sensibilidad3 <- conf_matrix3$byClass["Recall"]
F1_score3 <- conf_matrix3$byClass["F1"]
exactitud3; precision3; sensibilidad3; F1_score3
## Accuracy
## 0.7412587
## Precision
## 0.6909091
## Recall
## 0.6551724
## F1
## 0.6725664
En este modelo estamos controlando qué tan grande puede crecer el árbol. Al limitar su profundidad y exigir un número mínimo de observaciones para dividir, buscamos evitar que el modelo se ajuste demasiado a los datos de entrenamiento. Esto nos permite analizar el balance entre simplicidad del modelo y capacidad predictiva.
#Comparación de Modelos
# Función para extraer métricas y componentes de la matriz
extraer_metricas <- function(conf_matrix) {
tabla <- conf_matrix$table
TN <- tabla["Muere","Muere"]
TP <- tabla["Vive","Vive"]
FP <- tabla["Vive","Muere"]
FN <- tabla["Muere","Vive"]
acc <- round(conf_matrix$overall["Accuracy"] * 100, 2)
prec <- round(conf_matrix$byClass["Precision"] * 100, 2)
rec <- round(conf_matrix$byClass["Recall"] * 100, 2)
f1 <- round(conf_matrix$byClass["F1"] * 100, 2)
return(c(TN, FP, FN, TP, acc, prec, rec, f1))
}
# Extraer métricas de cada modelo
m1 <- extraer_metricas(conf_matrix)
m2 <- extraer_metricas(conf_matrix2)
m3 <- extraer_metricas(conf_matrix3)
# Crear tabla LaTeX
cat("$$\n")
## $$
cat("\\begin{array}{|l|c|c|c|}\n")
## \begin{array}{|l|c|c|c|}
cat("\\hline\n")
## \hline
cat("\\textbf{Métrica} & \\textbf{Modelo 1} & \\textbf{Modelo 2} & \\textbf{Modelo 3} \\\\\n")
## \textbf{Métrica} & \textbf{Modelo 1} & \textbf{Modelo 2} & \textbf{Modelo 3} \\
cat("\\hline\n")
## \hline
filas <- c("Verdaderos Negativos (TN)",
"Falsos Positivos (FP)",
"Falsos Negativos (FN)",
"Verdaderos Positivos (TP)",
"Exactitud (Accuracy)",
"Precisión (Precision)",
"Sensibilidad (Recall)",
"F1 Score")
for (i in 1:8) {
fila <- paste0(
"\\text{", filas[i], "} & ",
m1[i], ifelse(i >= 5, "\\%", ""), " & ",
m2[i], ifelse(i >= 5, "\\%", ""), " & ",
m3[i], ifelse(i >= 5, "\\%", ""), " \\\\\n"
)
cat(fila)
}
## \text{Verdaderos Negativos (TN)} & 77 & 77 & 68 \\
## \text{Falsos Positivos (FP)} & 8 & 8 & 17 \\
## \text{Falsos Negativos (FN)} & 24 & 24 & 20 \\
## \text{Verdaderos Positivos (TP)} & 34 & 34 & 38 \\
## \text{Exactitud (Accuracy)} & 77.62\% & 77.62\% & 74.13\% \\
## \text{Precisión (Precision)} & 80.95\% & 80.95\% & 69.09\% \\
## \text{Sensibilidad (Recall)} & 58.62\% & 58.62\% & 65.52\% \\
## \text{F1 Score} & 68\% & 68\% & 67.26\% \\
cat("\\hline\n")
## \hline
cat("\\end{array}\n")
## \end{array}
cat("$$\n")
## $$
Show in New Window Warning simpleWarning: labs do not fit even at cex 0.15, there may be some overplotting
Show in New Window Reference Prediction Muere Vive Muere 68 20 Vive 17 38 Show in New Window Accuracy 0.7412587 Precision 0.6909091 Recall 0.6551724 F1 0.6725664 Show in New Window \[ \begin{array}{|l|c|c|c|} \hline \textbf{Métrica} & \textbf{Modelo 1} & \textbf{Modelo 2} & \textbf{Modelo 3} \\ \hline \text{Verdaderos Negativos (TN)} & 77 & 77 & 68 \\ \text{Falsos Positivos (FP)} & 8 & 8 & 17 \\ \text{Falsos Negativos (FN)} & 24 & 24 & 20 \\ \text{Verdaderos Positivos (TP)} & 34 & 34 & 38 \\ \text{Exactitud (Accuracy)} & 77.62\% & 77.62\% & 74.13\% \\ \text{Precisión (Precision)} & 80.95\% & 80.95\% & 69.09\% \\ \text{Sensibilidad (Recall)} & 58.62\% & 58.62\% & 65.52\% \\ \text{F1 Score} & 68\% & 68\% & 67.26\% \\ \hline \end{array} \]
El Modelo 3, tras ajustar el umbral de decisión y permitir que el árbol crezca más libremente, logra capturar más casos positivos (TP = 38) y reducir los falsos negativos (FN = 20). Esto aumenta la sensibilidad (recall = 65.52%), lo que significa que ahora detecta un mayor porcentaje de sobrevivientes reales.
Como consecuencia, el F1 Score también mejora ligeramente (67.26%), reflejando un mejor balance entre precisión y recall. Sin embargo, este ajuste genera un aumento de falsos positivos (FP = 17), lo que reduce la precisión (69.09%) y baja un poco la exactitud general (74.13%).
En resumen, el Modelo 3 ahora prioriza identificar correctamente a los sobrevivientes, logrando un modelo más sensible a la clase positiva, a costa de un pequeño incremento en falsos positivos y una leve caída en exactitud y precisión. Esto lo hace más adecuado para escenarios donde es más importante no pasar por alto casos positivos.
En la práctica, encontrar el mejor modelo requiere ajustar y evaluar sistemáticamente los parámetros que controlan su complejidad y comportamiento, como la profundidad del árbol, el tamaño mínimo de los nodos o los umbrales de clasificación.
Este proceso combina técnicas como validación cruzada y búsqueda de hiperparámetros, midiendo el impacto de cada ajuste en métricas clave como precisión, recall o F1, según el objetivo del problema.
Dominar el análisis de datos requiere práctica y claridad sobre lo que buscamos descubrir, entendiendo cómo nuestras decisiones influyen en los resultados. R se convierte en una herramienta poderosa en este proceso, permitiendo explorar, visualizar y analizar datos de manera ágil y precisa. Con sus capacidades para manipular información, generar gráficos intuitivos y aplicar modelos estadísticos, R facilita identificar patrones y construir modelos confiables. Con práctica, aprender a aprovechar R nos permite transformar datos en conocimiento sólido y tomar decisiones fundamentadas.
¡Cada dato es una oportunidad para aprender algo nuevo! ʕ•́ᴥ•̀ʔっ♡