--- 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
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)
[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/) ::: :::
[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) :::