---
title: "IA para Científicos Sociales"
subtitle: "Sesión 1.4: Laboratorio - Exploración avanzada y comparación de modelos"
author:
  - name: Danilo Freire
    orcid: 0000-0002-4712-6810
    email: danilofreire@gmail.com
    affiliations: "Departament of Data and Decision Sciences <br> Emory University"
format:
  clean-revealjs:
    self-contained: true
    footer: "[Sesión 1.4](https://danilofreire.github.io/introduccion-ia-ucu/clases/dia-01/04-laboratorio-02.html)"
    transition: slide
    transition-speed: default
    scrollable: true
revealjs-plugins:
  - multimodal
engine: knitr
editor:
  render-on-save: true
lang: es
---

```{r setup, include=FALSE}
options(htmltools.dir.version = FALSE)
library(knitr)
opts_chunk$set(
  prompt = T,
  fig.align = "center",
  dpi = 300,
  cache = T,
  engine.opts = list(bash = "-l")
)

knit_hooks$set(
  prompt = function(before, options, envir) {
    options(
      prompt = if (options$engine %in% c("sh", "bash", "zsh")) "$ " else "R> ",
      continue = if (options$engine %in% c("sh", "bash", "zsh")) "$ " else "+ "
    )
  }
)

options(repos = c(CRAN = "https://cran.rstudio.com/"))

if (!require("fontawesome", character.only = TRUE)) {
  install.packages("fontawesome", dependencies = TRUE)
  library(fontawesome, character.only = TRUE)
}
```

# Laboratorio 2: Exploración avanzada {background-color="#2d4563"}

## Objetivos del laboratorio

:::{style="margin-top: 30px; font-size: 28px;"}
:::{.columns}
:::{.column width=50%}
**Lo que vamos a hacer:**

1. Trabajar con un nuevo dataset
2. Análisis exploratorio detallado
3. Feature engineering básico
4. Comparar múltiples modelos
5. Visualizar resultados
6. Discutir interpretaciones
:::

:::{.column width=50%}
**Lo que vamos a practicar:**

- Decisiones de [preprocesamiento]{.alert}
- [Comparación]{.alert} sistemática de modelos
- [Visualización]{.alert} de resultados
- [Interpretación]{.alert} de modelos

:::{style="margin-top: 30px; font-size: 28px;"}
[Hay menos código predefinido. Ustedes van a escribir más!]{.alert} 

[¡No se preocupen! Les damos una estructura para que puedan completar.]{.alert} 😉
:::
:::
:::
:::

# Parte 1: Nuevo dataset {background-color="#2d4563"}

## Cargar los paquetes

:::{style="margin-top: 30px; font-size: 22px;"}

```{r lab2-setup, message=FALSE, warning=FALSE, prompt=FALSE, echo=TRUE}
# Cargar paquetes
# install.packages("tidymodels", "tidyverse") # Instalar si es necesario
library(tidymodels)
library(tidyverse)

# Configurar tema de ggplot
theme_set(theme_minimal(base_size = 14))
```

O de una forma más automatizada:

```{r lab2-install, eval=FALSE, prompt=FALSE, echo=TRUE}
paquetes <- c("tidymodels", "tidyverse", "rpart")

# require(): intenta cargar el paquete y devuelve TRUE/FALSE
# Si no está instalado (FALSE), lo instala y carga
for (pkg in paquetes) {
  if (!require(pkg, character.only = TRUE)) {
    install.packages(pkg, dependencies = TRUE)
    library(pkg, character.only = TRUE)
  }
}
```
:::

## El dataset: satisfacción democrática

:::{style="margin-top: 30px; font-size: 22px;"}

Vamos a trabajar con datos simulados de [satisfacción con la democracia]{.alert} en América Latina:

```{r lab2-datos, message=FALSE, warning=FALSE, prompt=FALSE, echo=TRUE}
# Cargar datos
satisfaccion <- read_csv("datos/satisfaccion_democracia.csv")

# Ver estructura
glimpse(satisfaccion) # Ver las variables y tipos
```

**Variable objetivo:** `satisfecho` (sí/no) - ¿El encuestado está satisfecho con la democracia?
:::

## Descripción de las variables

:::{style="margin-top: 30px; font-size: 30px;"}

| Variable | Descripción | Tipo |
|----------|-------------|------|
| `satisfecho` | Satisfacción con la democracia (sí/no) | Categórica |
| `edad` | Edad del encuestado | Numérica |
| `educacion_anos` | Años de educación formal | Numérica |
| `ingreso_hogar` | Ingreso mensual del hogar (USD) | Numérica |
| `confianza_gobierno` | Confianza en el gobierno (1-10) | Numérica |
| `consumo_noticias` | Horas semanales de noticias | Numérica |
| `participacion_politica` | Índice de participación (0-100) | Numérica |
| `zona` | Urbano/Rural | Categórica |
| `genero` | Masculino/Femenino/Otro | Categórica |
| `pais` | País de residencia | Categórica |

:::

## Visualizar distribuciones

:::{style="margin-top: 30px; font-size: 22px;"}

```{r lab2-boxplots, prompt=FALSE, fig.width=10, fig.height=4.5, echo=TRUE}
# Variables numéricas por nivel de satisfacción
satisfaccion |>
  select(satisfecho, confianza_gobierno, participacion_politica, edad) |>
  pivot_longer(-satisfecho, names_to = "variable", values_to = "valor") |>
  ggplot(aes(x = satisfecho, y = valor, fill = satisfecho)) +
  geom_boxplot(alpha = 0.7) +
  facet_wrap(~variable, scales = "free_y") +
  scale_fill_manual(values = c("#e74c3c", "#27ae60")) +
  labs(title = "Variables numéricas por nivel de satisfacción") +
  theme(legend.position = "none")
```

Quienes tengan tiempo pueden ver también `geom_histogram()` por variable. Ejemplo completo en [Apéndice 1]{.alert}.
:::

## Ejercicio 1: Exploración {#sec:exercise01}

:::{style="margin-top: 30px; font-size: 24px;"}

**Tarea:** Exploren el dataset y produzcan una visualización que les llame la atención.

1. ¿Cuántas observaciones? ¿Hay valores faltantes?
2. ¿Cómo se distribuye la variable objetivo `satisfecho`?
3. Elijan [una]{.alert} de estas preguntas y hagan un gráfico para responderla:
    - ¿Cómo varía la satisfacción por [zona]{.alert} (urbano/rural) o por [país]{.alert}?
    - ¿Qué relación hay entre [ingreso]{.alert} y [educación]{.alert}?

```{r ej1-exploracion, eval=FALSE, prompt=FALSE, echo=TRUE}
# Código para empezar
dim(satisfaccion)
satisfaccion |> count(satisfecho) |> mutate(prop = n / sum(n))

# Ejemplo: satisfacción por zona
satisfaccion |>
  count(zona, satisfecho) |>
  group_by(zona) |> mutate(prop = n / sum(n)) |>
  ggplot(aes(x = zona, y = prop, fill = satisfecho)) +
  geom_col(position = "dodge")
```

[Tómense 5-10 minutos.]{.alert}

[[Ir al Apéndice 1]{.button}](#sec:appendix01) · [[Más visualizaciones: Apéndice 2]{.button}](#sec:appendix02)
:::

# Parte 2: Feature engineering {background-color="#2d4563"}

## Preparar los datos

:::{style="margin-top: 30px; font-size: 22px;"}

Primero, preparemos los datos básicos:

```{r lab2-preparar, prompt=FALSE, echo=TRUE}
# Convertir variables categóricas a factores
satisfaccion <- satisfaccion |>
  mutate(
    satisfecho = factor(satisfecho, levels = c("no", "si")),
    zona = factor(zona),
    genero = factor(genero),
    pais = factor(pais)
  )

# Verificar
glimpse(satisfaccion)
```
:::

## Crear nuevas variables

:::{style="margin-top: 30px; font-size: 22px;"}

El [feature engineering]{.alert} puede mejorar el rendimiento del modelo:

```{r lab2-features, prompt=FALSE, echo=TRUE}
# Crear nuevas variables
satisfaccion <- satisfaccion |>
  mutate(
    # Grupos de edad
    grupo_edad = cut(edad,
                     breaks = c(0, 30, 50, 70, 100),
                     labels = c("joven", "adulto", "mayor", "anciano")),

    # Ingreso per cápita (asumiendo hogar promedio de 3.5 personas)
    ingreso_percapita = ingreso_hogar / 3.5,

    # Indicador de alta participación
    alta_participacion = if_else(participacion_politica > 50, "alta", "baja")
  )

# Ver las nuevas variables
satisfaccion |> select(grupo_edad, ingreso_percapita, alta_participacion) |> head()
```
:::

## Ejercicio 2: Crear un feature {#sec:exercise03}

:::{style="margin-top: 30px; font-size: 24px;"}

**Tarea:** Elijan [una]{.alert} variable derivada y créenla.

Opciones sugeridas (elijan la que más les interese). Entre paréntesis, la variable base:

- Grupos de ingreso por terciles: bajo, medio, alto (`ingreso_hogar`)
- Consumidor alto de noticias: más de 5 horas por semana (`consumo_noticias`)
- Baja confianza en el gobierno: puntaje menor o igual a 4 (`confianza_gobierno`)
- Interacción zona y género: combinación de ambas categorías (`zona`, `genero`)

```{r ej3-features, eval=FALSE, prompt=FALSE, echo=TRUE}
# Ejemplo: Crear variable educacion_alta
satisfaccion <- satisfaccion |>
  mutate(
    educacion_alta = if_else(educacion_anos > 12, "alta", "baja")
  )
```

[Tómense 5 minutos.]{.alert}

[[Más ejemplos en el Apéndice 3]{.button}](#sec:appendix03)
:::

## Dividir los datos

:::{style="margin-top: 30px; font-size: 24px;"}

```{r lab2-split, prompt=FALSE, echo=TRUE}
# Fijar semilla
set.seed(2026)

# Dividir datos
datos_split <- initial_split(satisfaccion, prop = 0.75, strata = satisfecho)
datos_train <- training(datos_split)
datos_test <- testing(datos_split)

# Crear folds para validación cruzada
folds <- vfold_cv(datos_train, v = 5, strata = satisfecho)

cat("Train:", nrow(datos_train), "| Test:", nrow(datos_test))
```

- `initial_split()`: Divide el dataset en train/test
- `training()` y `testing()`: Extraen los datasets de train y test
- `vfold_cv()`: Crea folds para validación cruzada (5 folds, estratificados por `satisfecho`)
:::

# Parte 3: Comparación de modelos {background-color="#2d4563"}

## Definir los modelos a comparar

:::{style="margin-top: 30px; font-size: 22px;"}

Vamos a comparar dos modelos de naturaleza distinta: uno lineal y uno no lineal.

```{r lab2-modelos, prompt=FALSE, echo=TRUE}
# 1. Regresión logística (lineal)
modelo_logistico <- logistic_reg() |>
  set_engine("glm") |>
  set_mode("classification")

# 2. Árbol de decisión (no lineal, captura interacciones automáticamente)
modelo_arbol <- decision_tree() |>
  set_engine("rpart") |>
  set_mode("classification")
```

La [misma sintaxis]{.alert} sirve para ambos: esa es la ventaja de `parsnip`. Otros motores (`kknn`, `ranger`, `xgboost`) funcionan igual cambiando solo `set_engine()`.
:::

## Crear una fórmula

:::{style="margin-top: 30px; font-size: 24px;"}

Decidamos qué variables usar:

```{r lab2-formula, prompt=FALSE, echo=TRUE}
# Fórmula con variables originales
formula_basica <- satisfecho ~ edad + educacion_anos + ingreso_hogar +
                               confianza_gobierno + consumo_noticias +
                               participacion_politica + zona
```

Más adelante añadiremos una variable derivada para ver si mejora el ajuste.
:::

## Función para evaluar un modelo

:::{style="margin-top: 30px; font-size: 20px;"}

Creemos una función que facilite la evaluación:

```{r lab2-funcion-evaluar, prompt=FALSE, echo=TRUE}
evaluar_modelo <- function(modelo, formula, folds, nombre = "modelo") {
  # Validación cruzada
  # event_level = "second" porque "si" es el segundo nivel del factor
  resultados <- fit_resamples(
    modelo,
    formula,
    resamples = folds,
    metrics = metric_set(accuracy, precision, recall, roc_auc),
    control = control_resamples(event_level = "second")
  )

  # Extraer métricas
  collect_metrics(resultados) |>
    mutate(modelo = nombre)
}
```

```{r lab2-evaluar-logistico, prompt=FALSE, echo=TRUE}
# Evaluar regresión logística
eval_logistico <- evaluar_modelo(modelo_logistico, formula_basica, folds, "Logístico")
eval_logistico
```
:::

## Evaluar todos los modelos

:::{style="margin-top: 30px; font-size: 22px;"}

```{r lab2-evaluar-todos, prompt=FALSE, echo=TRUE}
# Evaluar el árbol con la misma fórmula
eval_arbol <- evaluar_modelo(modelo_arbol, formula_basica, folds, "Árbol")

# Combinar resultados
resultados <- bind_rows(eval_logistico, eval_arbol)

# Mostrar comparación
resultados |>
  select(modelo, .metric, mean, std_err) |>
  pivot_wider(names_from = .metric, values_from = c(mean, std_err))
```
:::

## Visualizar la comparación

:::{style="margin-top: 30px; font-size: 22px;"}

```{r lab2-visualizar-comparacion, prompt=FALSE, fig.width=10, fig.height=5, echo=TRUE}
resultados |>
  ggplot(aes(x = modelo, y = mean, fill = modelo)) +
  geom_col(alpha = 0.8) +
  geom_errorbar(aes(ymin = mean - std_err, ymax = mean + std_err),
                width = 0.2) +
  facet_wrap(~.metric, scales = "free_y") +
  scale_fill_manual(values = c("#2d4563", "#27ae60")) +
  labs(title = "Comparación de modelos", y = "Valor", x = "") +
  theme(legend.position = "none")
```
:::

## Ejercicio 3: ¿Ayuda el feature engineering? {#sec:exercise04}

:::{style="margin-top: 30px; font-size: 24px;"}

**Tarea:** Agreguen [la variable derivada que crearon antes]{.alert} a la fórmula y re-evalúen.

1. ¿Mejora la regresión logística? ¿Y el árbol?
2. ¿Cuál de los dos modelos es más sensible al feature nuevo?

```{r ej4-features-nuevos, eval=FALSE, prompt=FALSE, echo=TRUE}
# Ejemplo con la variable educacion_alta
formula_ext <- satisfecho ~ edad + educacion_anos + ingreso_hogar +
                            confianza_gobierno + consumo_noticias +
                            participacion_politica + zona + educacion_alta

eval_log_ext <- evaluar_modelo(modelo_logistico, formula_ext, folds, "Logístico (ext)")
eval_arb_ext <- evaluar_modelo(modelo_arbol,     formula_ext, folds, "Árbol (ext)")
bind_rows(eval_logistico, eval_log_ext, eval_arbol, eval_arb_ext) |>
  filter(.metric == "accuracy") |> select(modelo, mean, std_err)
```

[Tómense 10 minutos.]{.alert}

[[Ir al Apéndice 4]{.button}](#sec:appendix04)
:::

## Modelo final en datos de test

:::{style="margin-top: 30px; font-size: 22px;"}

Una vez elegido el mejor modelo, evaluamos en test:

```{r lab2-test-final, prompt=FALSE, echo=TRUE}
# Elegimos el modelo logístico (por ejemplo)
ajuste_final <- modelo_logistico |>
  fit(formula_basica, data = datos_train)

# Predicciones en test
pred_test <- ajuste_final |>
  predict(datos_test, type = "prob") |>
  bind_cols(predict(ajuste_final, datos_test)) |>
  bind_cols(datos_test)

# Métricas finales
pred_test |>
  metrics(truth = satisfecho, estimate = .pred_class)

# AUC-ROC
pred_test |>
  roc_auc(truth = satisfecho, .pred_si, event_level = "second")
```
:::

## Tarea para casa

:::{style="margin-top: 30px; font-size: 24px;"}

Hay una [tarea para esta sesión]{.alert} con preguntas prácticas que aplican todo lo que vimos hoy.

- Página de [Laboratorios](https://danilofreire.github.io/introduccion-ia-ucu/laboratorios.html) del sitio del curso
- [Tarea 2](https://danilofreire.github.io/introduccion-ia-ucu/clases/dia-01/tarea-02.html)
- [Respuestas Tarea 2](https://danilofreire.github.io/introduccion-ia-ucu/clases/dia-01/tarea-02-respuestas.html)

<br>

[Hagan la tarea antes de la próxima clase. Las respuestas ya están disponibles para que verifiquen su trabajo.]{.alert}
:::

# Cierre del Día 1 {background-color="#2d4563"}

## Lo que cubrimos hoy

:::{style="margin-top: 30px; font-size: 24px;"}
:::{.columns}
:::{.column width=50%}
**Sesión 1.1: ¿Qué es la IA?**

- Definición y tipos de IA
- Historia: de Turing a GPT
- Tipos de aprendizaje automático
- Ética básica

**Sesión 1.2: Fundamentos de ML**

- Flujo de trabajo de ML
- División y validación
- Sobreajuste y regularización
- Métricas de evaluación
:::

:::{.column width=50%}
**Sesión 1.3: Laboratorio 1**

- Ecosistema tidymodels
- Primer modelo de clasificación
- Matriz de confusión y métricas

**Sesión 1.4: Laboratorio 2**

- Exploración guiada
- Feature engineering básico
- Comparación logístico vs. árbol
- Tarea 2 disponible en la página de laboratorios
:::
:::
:::

## Para mañana

:::{style="margin-top: 40px; font-size: 28px;"}

**Día 2: Aprendizaje supervisado**

- Sesión 2.1: Métodos de clasificación
    - Regresión logística en detalle
    - Árboles de decisión
    - Random Forests

- Sesión 2.2: Regresión y predicción
    - Regularización: LASSO, Ridge, Elastic Net
    - Ingeniería de variables para datos sociales

- Sesiones 2.3 y 2.4: Laboratorios con datos de Latinobarómetro

[Descansen y vengan con energía mañana!]{.alert} 😄
:::

## Recursos adicionales

:::{style="margin-top: 30px; font-size: 24px;"}
:::{.columns}
:::{.column width=50%}
**Documentación oficial:**

- [tidymodels.org](https://www.tidymodels.org/)
- [Tidy Modeling with R](https://www.tmwr.org/) (libro gratuito)
- [parsnip models](https://parsnip.tidymodels.org/reference/index.html)
:::

:::{.column width=50%}
**Tutoriales recomendados:**

- [Get Started with tidymodels](https://www.tidymodels.org/start/)
- [Julia Silge's blog](https://juliasilge.com/)
- [R for Data Science](https://r4ds.hadley.nz/)
:::
:::

<br>

[Todos estos recursos están disponibles gratuitamente en línea.]{.alert}
:::

# Fin del Día 1 🥳 {background-color="#2d4563"}

# Apéndices {background-color="#2d4563"}

## Apéndice 1: Exploración inicial {#sec:appendix01}

:::{style="margin-top: 30px; font-size: 20px;"}

```{r appendix01, prompt=FALSE, echo=TRUE}
# 1. Dimensiones
dim(satisfaccion)

# 2. Distribución de la variable objetivo
satisfaccion |>
  count(satisfecho) |>
  mutate(prop = round(n / sum(n), 3))

# 3. Valores faltantes por variable
colSums(is.na(satisfaccion))

# 4. Resumen de variables numéricas
satisfaccion |>
  select(where(is.numeric)) |>
  summary()

# 5. Distribución por país y zona
satisfaccion |> count(pais, sort = TRUE)
satisfaccion |> count(zona)
```

[[Volver al Ejercicio 1]{.button}](#sec:exercise01)
:::

## Apéndice 2: Más visualizaciones {#sec:appendix02}

:::{style="margin-top: 30px; font-size: 20px;"}

```{r appendix02a, prompt=FALSE, fig.width=10, fig.height=4, echo=TRUE}
# 1. Satisfacción por zona
satisfaccion |>
  count(zona, satisfecho) |>
  group_by(zona) |>
  mutate(prop = n / sum(n)) |>
  ggplot(aes(x = zona, y = prop, fill = satisfecho)) +
  geom_col(position = "dodge", alpha = 0.8) +
  scale_fill_manual(values = c("#e74c3c", "#27ae60")) +
  labs(title = "Satisfacción por zona", y = "Proporción")
```
:::

## Apéndice 2 (cont.)

:::{style="margin-top: 30px; font-size: 20px;"}

```{r appendix02b, prompt=FALSE, fig.width=10, fig.height=4, echo=TRUE}
# 2. Satisfacción por país
satisfaccion |>
  count(pais, satisfecho) |>
  group_by(pais) |>
  mutate(prop = n / sum(n)) |>
  filter(satisfecho == "si") |>
  ggplot(aes(x = reorder(pais, prop), y = prop)) +
  geom_col(fill = "#27ae60", alpha = 0.8) +
  coord_flip() +
  labs(title = "Proporción de satisfechos por país", x = "", y = "Proporción")
```
:::

## Apéndice 2 (cont.)

:::{style="margin-top: 30px; font-size: 20px;"}

```{r appendix02c, prompt=FALSE, fig.width=10, fig.height=4, echo=TRUE}
# 3. Satisfacción por género
satisfaccion |>
  count(genero, satisfecho) |>
  group_by(genero) |>
  mutate(prop = n / sum(n)) |>
  ggplot(aes(x = genero, y = prop, fill = satisfecho)) +
  geom_col(position = "dodge", alpha = 0.8) +
  scale_fill_manual(values = c("#e74c3c", "#27ae60")) +
  labs(title = "Satisfacción por género", y = "Proporción")
```

```{r appendix02d, prompt=FALSE, fig.width=10, fig.height=4, echo=TRUE}
# 4. Ingreso vs. educación
ggplot(satisfaccion, aes(x = educacion_anos, y = ingreso_hogar, color = satisfecho)) +
  geom_point(alpha = 0.4) +
  geom_smooth(method = "lm", se = FALSE) +
  scale_color_manual(values = c("#e74c3c", "#27ae60")) +
  labs(title = "Ingreso vs. educación por satisfacción")
```

[[Volver al Ejercicio 1]{.button}](#sec:exercise01)
:::

## Apéndice 3: Crear más features {#sec:appendix03}

:::{style="margin-top: 30px; font-size: 20px;"}

```{r appendix03, prompt=FALSE, echo=TRUE}
# Una posible solución para cada una de las cuatro opciones sugeridas
satisfaccion <- satisfaccion |>
  mutate(
    # 1. Grupos de ingreso por terciles (ingreso_hogar)
    grupo_ingreso = cut(ingreso_hogar,
                        breaks = quantile(ingreso_hogar, c(0, 1/3, 2/3, 1)),
                        labels = c("bajo", "medio", "alto"),
                        include.lowest = TRUE),

    # 2. Consumidor alto de noticias (consumo_noticias)
    noticias_alto = if_else(consumo_noticias > 5, "alto", "bajo"),

    # 3. Baja confianza en el gobierno (confianza_gobierno)
    confianza_baja = if_else(confianza_gobierno <= 4, "si", "no"),

    # 4. Interacción zona + género
    zona_genero = paste(zona, genero, sep = "_")
  )

# Verificar las nuevas variables
satisfaccion |> count(grupo_ingreso)
satisfaccion |> count(noticias_alto)
satisfaccion |> count(confianza_baja)
satisfaccion |> count(zona_genero)
```

[[Volver al Ejercicio 3]{.button}](#sec:exercise03)
:::

## Apéndice 4: ¿Ayuda el feature engineering? {#sec:appendix04}

:::{style="margin-top: 30px; font-size: 20px;"}

```{r appendix04, prompt=FALSE, echo=TRUE}
# Crear el feature derivado (si no lo hicieron antes)
satisfaccion <- satisfaccion |>
  mutate(educacion_alta = if_else(educacion_anos > 12, "alta", "baja"),
         educacion_alta = factor(educacion_alta))

# Re-crear folds con la columna nueva disponible
set.seed(2026)
split_ext <- initial_split(satisfaccion, prop = 0.75, strata = satisfecho)
train_ext <- training(split_ext)
folds_ext <- vfold_cv(train_ext, v = 5, strata = satisfecho)

# Fórmula con el feature nuevo
formula_ext <- satisfecho ~ edad + educacion_anos + ingreso_hogar +
                            confianza_gobierno + consumo_noticias +
                            participacion_politica + zona + educacion_alta

# Evaluar los dos modelos con y sin feature nuevo
eval_log_ext <- evaluar_modelo(modelo_logistico, formula_ext, folds_ext, "Logístico (ext)")
eval_arb_ext <- evaluar_modelo(modelo_arbol,     formula_ext, folds_ext, "Árbol (ext)")

bind_rows(eval_logistico, eval_log_ext, eval_arbol, eval_arb_ext) |>
  filter(.metric == "accuracy") |>
  select(modelo, mean, std_err) |>
  arrange(desc(mean))
```

La mejora por agregar una sola variable derivada suele ser modesta. El feature engineering rinde cuando las variables nuevas capturan relaciones no lineales o interacciones que el modelo no puede descubrir por sí solo (en un árbol esto pesa menos, porque ya captura no linealidades automáticamente).

[[Volver al Ejercicio 3]{.button}](#sec:exercise04)
:::
