Aprendizaje supervisado y no supervisado

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.

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

  1. Cuando y se restringe a los valores 0 y 1, corresponde a problemas de clasificación binaria porque la respuesta es categórica y se codifica como 0 y 1 por conveniencia matemática. Ejemplos: Spam (1) vs No spam (0), Enfermo (1) vs Sano (0).

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:

  • Predicción: estimar el valor de la respuesta para nuevas observaciones.
  • Inferencia: entender cómo las variables predictoras afectan a la respuesta.

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.


Aprendizaje no supervisado

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:

  • Relaciones entre variables
  • Patrones entre observaciones
  • Agrupaciones naturales

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


Árboles de decisión

Un árbol de decisión es un modelo de aprendizaje supervisado que se utiliza para:

  • Clasificación (valores discretos)
  • Regresión (valores continuos)

Funciona mediante reglas del tipo if – then – else y se representa como una estructura similar a un diagrama de flujo:

  • Cada nodo interno → decisión sobre una variable
  • Cada rama → resultado de esa decisión
  • Cada hoja → predicción final

Entr sus componentes principales tenemos:

  • Nodo raíz: representa todo el dataset
  • Nodos de decisión: puntos donde se divide el dataset
  • Hojas (nodos terminales): contienen la predicción final

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:

  • Índice de Gini
  • Entropía (y ganancia de información)



1. Datos

  • Cada punto representa una observación
  • x1 y x2 son variables predictoras
  • Los colores indican la clase (vienen de los datos, o del problema)
  • Objetivo: separar correctamente las clases

2. Primera división

  • El modelo busca el mejor primer corte
  • Se divide en x1 <= 3
  • Se generan dos grupos: x1 <= 3, x1 > 3
  • Este es el nodo raíz
  • Uno de los grupos queda casi puro

3. Segunda división

  • Se toma el grupo que sigue mezclado
  • Se divide en x2 <= 4
  • Se generan dos nuevos grupos: x2 <= 4 y x2 > 4

4. Estructura resultante

  • Cada división genera un nodo
  • Primer corte: nodo raíz
  • Cortes siguientes: nodos hijos
  • Regiones finales: hojas con la predicción

5. Criterio de división

  • El modelo busca reducir la mezcla de clases considerando que

    • Nodo puro: una sola clase
    • Nodo impuro: varias clases

6. Método utilizado

  • Se utiliza el índice de Gini
  • Se prueban múltiples variables y puntos de corte
  • Se selecciona el que mejor separa las clases

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.


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

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

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

  1. Segundo paso: Segunda división

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

  1. Nodos Cada división corresponde a un nodo en el árbol:

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?



Matriz de confusión

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.

  1. La exactitud del modelo es la relación entre el número de aciertos (datos clasificados correctamente) sobre el número total de datos. Dentro de sus limitaciones puede “enmascarar” el desempeño del clasificador para alguna categoría en particular.

\[ \text{Accuracy} = \frac{TN + TP}{TN + TP + FN + FP} \]

  1. La precisión del modelo es la proporción de datos clasificados como positivos y que realmente son positivos.

\[ \text{Precision} = \frac{TP}{TP + FP} \]

  1. La sensibilidad del modelo es la proporción de datos positivos que fueron detectados correctamente como positivos.

\[ \text{Recall} = \frac{TP}{TP + FN} \]

  1. El puntaje F1 es la media armónica del precisión y el recall.

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


Cierre

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! ʕ•́ᴥ•̀ʔっ♡