Sesión 2.1: Métodos de clasificación
logistic_reg() en nuestro primer modelo. Hoy vamos a entenderla mejor\[P(y = 1 | x) = \frac{1}{1 + e^{-(w_0 + w_1 x_1 + \ldots + w_n x_n)}}\]
Fuente: Wikipedia
Ventajas
Limitaciones
Ejemplo en ciencias sociales
¿Qué predice si una persona
vota en la última elección?
Predictores:
- Edad
- Años de educación
- Ingreso del hogar
- Confianza en el gobierno
- Zona (urbana/rural)
Outcome: voto (sí/no)
El modelo nos dice qué factores
aumentan o disminuyen la probabilidad
de votar, y en qué magnitud.
Regresión logística en R (tidymodels)
# Ya lo vimos ayer:
modelo_log <- logistic_reg() |>
set_engine("glm") |>
set_mode("classification")
ajuste <- fit(modelo_log,
voto ~ edad + educacion + ingreso,
data = datos_train)
# Los coeficientes:
tidy(ajuste)
# Resultado:
# edad: 0.023 (más edad → más voto)
# educacion: 0.085 (más edu → más voto)
# ingreso: 0.041 (más ingreso → más voto)Pregunta de investigación:
¿Qué factores predicen si un uruguayo apoya la democracia como forma de gobierno?
Datos: Latinobarómetro Uruguay (simulados)
Variables predictoras:
Variable objetivo: apoya_democracia (sí/no)
La regresión logística nos dice qué factores aumentan o disminuyen la probabilidad de apoyo.
Resultados típicos:
Variable Coef Odds Ratio
─────────────────────────────────────────
Intercept -1.20 0.30
edad 0.02 1.02
educacion_anos 0.08 1.08 ***
ingreso_log 0.15 1.16 *
confianza_gobierno 0.25 1.28 ***
percepcion_economia 0.18 1.20 **
zona_urbano 0.12 1.13
*** p < 0.001, ** p < 0.01, * p < 0.05
Pregunta: ¿Qué significa un OR de 1.28 para confianza en el gobierno?
Interpretación del odds ratio:
Ejemplo: OR = 1.28 para confianza en el gobierno
Cálculo en R:
Siempre reportar intervalos de confianza para los odds ratios.
Fuente: Medium
Ejemplo paso a paso
Datos: 500 personas, 265 votaron
Paso 1: ¿edad > 35?
├── Sí (300 personas, 200 votaron)
│ Paso 2: ¿educación > 12?
│ ├── Sí → Predecir: VOTÓ ✅
│ └── No → Predecir: NO VOTÓ ❌
└── No (200 personas, 65 votaron)
Paso 2: ¿interés_política > 3?
├── Sí → Predecir: VOTÓ ✅
└── No → Predecir: NO VOTÓ ❌
Cada pregunta busca la "mejor"
separación posible.
\[\text{Gini}(t) = 1 - \sum_{k=1}^{K} p_k^2\]
donde \(p_k\) es la proporción de la clase \(k\) en el nodo \(t\)
\[\text{Gini}_{\text{split}} = \frac{n_L}{n} \text{Gini}(t_L) + \frac{n_R}{n} \text{Gini}(t_R)\]
Ejemplo con datos de votación
| Nodo | Votó | No votó | Gini |
|---|---|---|---|
| Raíz | 265 | 235 | 0.497 |
| Edad > 35 | 200 | 100 | 0.444 |
| Edad ≤ 35 | 65 | 135 | 0.442 |
\(\text{Gini}_{\text{split}} = \frac{300}{500}(0.444) + \frac{200}{500}(0.442)\)
\(= 0.267 + 0.177 = 0.443\)
Como \(0.443 < 0.497\), esta división mejora la separación de las clases
Ventajas
Problemas
Con tidymodels, solo necesitamos cambiar la especificación del modelo:
# Especificar un árbol de decisión
modelo_arbol <- decision_tree(
tree_depth = 5, # profundidad máxima
min_n = 10 # mínimo de observaciones por nodo
) |>
set_engine("rpart") |>
set_mode("classification")
# Ajustar (exactamente igual que antes)
ajuste_arbol <- fit(modelo_arbol, voto ~ ., data = datos_train)
# Predecir (exactamente igual que antes)
pred_arbol <- predict(ajuste_arbol, datos_test) |>
bind_cols(datos_test)logistic_reg() por decision_tree()tree_depth y min_n son hiperparámetros: controlan la complejidad del modelorpart.plot:# install.packages("rpart.plot")
library(rpart.plot)
# Extraer el modelo rpart del ajuste
arbol_rpart <- extract_fit_engine(ajuste_arbol)
# Visualizar
# type=4: muestra reglas en todos los nodos
# extra=104: clase predicha + % de observaciones
# roundint=FALSE: evita warning con predictores
# no enteros
rpart.plot(arbol_rpart,
type = 4,
extra = 104,
roundint = FALSE)Ejemplo de visualización:
[voto]
/ \
edad < 35 edad >= 35
/ \
[no votó] educacion < 12
65% / \
[no votó] [votó]
55% 78%
Cualquier persona puede entender este modelo, incluso sin conocimientos técnicos.
Es ideal para comunicar resultados a tomadores de decisiones.
tree_depth: profundidad máximamin_n: mínimo de observaciones por nodocost_complexity: penalización por complejidadEl trade-off:
Profundidad Error train Error test
─────────────────────────────────────────
1 0.35 0.36
3 0.22 0.25
5 0.15 0.23
10 0.08 0.28 ←
15 0.03 0.35 ←
20 0.01 0.42 ←
A partir de cierta profundidad, el error en test empeora mientras el error en train sigue bajando.
La poda previene el sobreajuste.
Random Forest con 500 árboles:
Árbol 1: muestra 1, variables {A,C,F}
→ Predicción: VOTÓ
Árbol 2: muestra 2, variables {B,D,E}
→ Predicción: NO VOTÓ
Árbol 3: muestra 3, variables {A,D,G}
→ Predicción: VOTÓ
...
Árbol 500: muestra 500, variables {C,E,F}
→ Predicción: VOTÓ
Predicción final: VOTÓ
(mayoría de votos entre los 500 árboles)
La sabiduría de la multitud: muchos modelos “mediocres” juntos hacen un modelo excelente.
Hiperparámetros principales
| Parámetro | Descripción |
|---|---|
trees |
Número de árboles (más = mejor, pero más lento) |
mtry |
Variables a considerar en cada división |
min_n |
Mínimo de observaciones por nodo |
mtry = \(\sqrt{p}\) para clasificación (donde \(p\) = número de variables)
De nuevo, con tidymodels solo cambiamos la especificación:
# Especificar Random Forest
modelo_rf <- rand_forest(
trees = 500, # 500 árboles
mtry = 3, # 3 variables por división
min_n = 5 # mínimo 5 observaciones por nodo
) |>
set_engine("ranger") |> # ranger es una implementación rápida
set_mode("classification")
# Ajustar
ajuste_rf <- fit(modelo_rf, voto ~ ., data = datos_train)
# Predecir
pred_rf <- predict(ajuste_rf, datos_test) |>
bind_cols(datos_test)
# Evaluar
metrics(pred_rf, truth = voto, estimate = .pred_class)ranger es una implementación rápida y eficiente de Random Forest en Redad hace que el modelo sea mucho peor, entonces edad es importantezona no cambia nada, entonces zona no aporta mucho# Con tidymodels + vip
library(tidymodels)
library(vip) # Variable Importance Plots
rf_spec <- rand_forest(trees = 500) |>
set_engine("ranger",
importance = "permutation") |>
set_mode("classification")
rf_fit <- rf_spec |>
fit(voto ~ ., data = datos_train)
# Ver importancia
rf_fit |>
vip(num_features = 10)
# edad ████████████ 0.042
# educacion_anios ██████████ 0.038
# interes_politica ████████ 0.031
# ingreso_hogar ██████ 0.025
# confianza_gobierno █████ 0.019Importancia por permutación
importance = "permutation"Importancia por impureza (Gini)
SHAP values (más avanzado)
shapr o fastshapPara este curso usaremos importancia por permutación, que es la más robusta.
:::
tree_depth, min_ntrees, mtry, min_nvfold_cv())tune() es la función de tidymodels para marcar qué parámetros queremos ajustargrid_regular() o grid_random() nos ayudan a crear combinaciones de valores para probartune_grid() hace el proceso de ajuste y evaluación automáticamenteEl proceso:
1. Definir grilla de valores a probar
mtry: [2, 3, 4, 5]
min_n: [5, 10, 20]
2. Para cada combinación:
- Validación cruzada (5 folds)
- Calcular métrica promedio
3. Elegir la mejor combinación
4. Entrenar modelo final con
todos los datos de train
Este proceso se llama “grid search”.
# 1. Especificar modelo con parámetros a ajustar (tune())
modelo_rf_tune <- rand_forest(
trees = 500,
mtry = tune(), # <- ajustar este
min_n = tune() # <- y este
) |>
set_engine("ranger") |>
set_mode("classification")
# 2. Crear grilla de valores
grilla <- grid_regular( # grid_regular: combinaciones sistemáticas
mtry(range = c(2, 6)),
min_n(range = c(5, 20)),
levels = 4
)
# 3. Validación cruzada
folds <- vfold_cv(datos_train, v = 5, strata = voto) # estratificar por voto para mantener proporciones
# 4. Ajustar
resultados <- tune_grid( # ajusta el modelo para cada combinación y fold
modelo_rf_tune,
voto ~ .,
resamples = folds,
grid = grilla,
metrics = metric_set(accuracy, roc_auc)
)
# 5. Ver mejores combinaciones
show_best(resultados, metric = "roc_auc")# Seleccionar la mejor combinación
mejor <- select_best(resultados, metric = "roc_auc")
mejor
# Finalizar el modelo con los mejores hiperparámetros
modelo_final <- finalize_model(modelo_rf_tune, mejor)
# Entrenar con todos los datos de entrenamiento
ajuste_final <- fit(modelo_final, voto ~ ., data = datos_train)
# Evaluar en datos de test
pred_final <- predict(ajuste_final, datos_test) |>
bind_cols(datos_test)
metrics(pred_final, truth = voto, estimate = .pred_class)select_best() nos muestra la mejor combinación según la métrica que elijamos (accuracy, AUC, etc.)finalize_model() fija los hiperparámetros en el modelo para el entrenamiento finalLa gran ventaja de tidymodels es que cambiar de modelo es trivial:
# Modelo 1: Regresión logística
modelo_1 <- logistic_reg() |> set_engine("glm") |> set_mode("classification")
# Modelo 2: Árbol de decisión
modelo_2 <- decision_tree(tree_depth = 5) |> set_engine("rpart") |> set_mode("classification")
# Modelo 3: Random Forest
modelo_3 <- rand_forest(trees = 500) |> set_engine("ranger") |> set_mode("classification")
# Todos se ajustan y evalúan con la misma sintaxis:
ajuste_1 <- fit(modelo_1, voto ~ ., data = datos_train)
ajuste_2 <- fit(modelo_2, voto ~ ., data = datos_train)
ajuste_3 <- fit(modelo_3, voto ~ ., data = datos_train)
# Y se comparan con las mismas métricas| Criterio | Reg. logística | Árbol de decisión | Random Forest |
|---|---|---|---|
| Interpretabilidad | Alta (coeficientes) | Alta (visual) | Baja (caja negra) |
| Capacidad predictiva | Moderada | Moderada-baja | Alta |
| Riesgo de sobreajuste | Bajo | Alto | Bajo |
| Velocidad | Muy rápida | Rápida | Moderada |
| Relaciones no lineales | No | Sí | Sí |
| Datos necesarios | Pocos | Pocos | Moderados |
Suele tener mejor rendimiento que RF, pero es más difícil de ajustar.
Random Forest vs. Gradient Boosting:
| RF | Boosting | |
|---|---|---|
| Entrenamiento | Paralelo | Secuencial |
| Hiperparámetros | Pocos | Muchos |
| Riesgo de sobreajuste | Bajo | Más alto |
| Velocidad | Rápido | Variable |
| Rendimiento típico | Muy bueno | Excelente |
En tidymodels:
Lo veremos con más detalle en los laboratorios.
Muchlinski et al. (2016): Predicción de guerras civiles
Jean et al. (2016): Pobreza desde el espacio
Otros ejemplos en ciencias sociales:
En todos estos casos, RF es un punto de partida común.
Modelos de clasificación:
Herramientas aprendidas:
La elección del modelo depende del objetivo: ¿explicar o predecir?
Nos vemos después del descanso!