IA para Científicos Sociales

Sesión 2.4: Laboratorio - Regresión y regularización

Danilo Freire

Departament of Data and Decision Sciences
Emory University

Laboratorio 4: Regresión y regularización

Objetivos del laboratorio

Lo que vamos a hacer:

  1. Predecir satisfacción con la vida
  2. Baseline con OLS
  3. Ajustar LASSO con validación cruzada
  4. Interpretar coeficientes y selección
  5. Contrastar con Ridge en una slide
  6. Cerrar con una tabla comparativa final

Lo que vamos a aprender:

  • Cuándo usar regularización
  • Diferencias prácticas entre LASSO y Ridge
  • Tuning de penalty con CV
  • Trade-off interpretabilidad vs. rendimiento


Usaremos el mismo dataset de Latinobarómetro (datos simulados).

Parte 1: Preparación y baseline

Cargar los paquetes necesarios

# Paquetes necesarios
paquetes <- c("tidyverse", "tidymodels", "glmnet", "ranger", "vip")

for (pkg in paquetes) {
  if (!require(pkg, character.only = TRUE)) {
    install.packages(pkg, dependencies = TRUE)
    library(pkg, character.only = TRUE)
  }
}

library(tidymodels)
library(glmnet)   # Motor para LASSO, Ridge, Elastic Net

set.seed(2026)

# Cargar el dataset
datos <- read_csv("datos/latinobarometro_sim.csv", show_col_types = FALSE)
glimpse(datos)
Rows: 500
Columns: 14
$ pais                    <chr> "Argentina", "Costa Rica", "Panamá", "Perú", "…
$ edad                    <dbl> 65, 19, 82, 54, 32, 21, 57, 21, 57, 80, 38, 69…
$ educacion_anios         <dbl> 15, 18, 11, 9, 14, 9, 7, 17, 3, 12, 12, 14, 9,…
$ ingreso_hogar           <dbl> 7, 10, 3, 8, 5, 5, 5, 9, 3, 7, 6, 6, 5, 8, 9, …
$ zona                    <chr> "urbana", "rural", "urbana", "urbana", "urbana…
$ genero                  <chr> "hombre", "hombre", "mujer", "mujer", "hombre"…
$ confianza_gobierno      <dbl> 2, 3, 2, 4, 5, 5, 2, 4, 3, 1, 1, 2, 3, 3, 3, 3…
$ confianza_justicia      <dbl> 3, 3, 3, 2, 4, 2, 2, 4, 2, 3, 3, 2, 4, 3, 4, 3…
$ satisfaccion_democracia <dbl> 2, 1, 2, 2, 4, 3, 3, 4, 3, 3, 2, 4, 2, 2, 2, 3…
$ percepcion_economia     <dbl> 3, 5, 5, 5, 2, 5, 4, 2, 3, 3, 4, 4, 4, 1, 2, 4…
$ uso_internet            <chr> "semanal", "diario", "diario", "diario", "nunc…
$ interes_politica        <dbl> 3, 3, 3, 3, 3, 4, 4, 1, 1, 1, 1, 1, 2, 1, 3, 1…
$ satisfaccion_vida       <dbl> 8.6, 8.3, 5.3, 7.8, 6.3, 8.7, 5.9, 6.4, 4.5, 6…
$ voto                    <chr> "no", "si", "si", "no", "si", "si", "no", "si"…

Cargar y explorar los datos

# Convertir variables categóricas
datos <- datos |>
  mutate(
    pais = factor(pais),
    zona = factor(zona),
    genero = factor(genero),
    uso_internet = factor(uso_internet, levels = c("nunca", "semanal", "diario"))
  )

# Nuestra variable objetivo
summary(datos$satisfaccion_vida)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.900   5.800   6.650   6.682   7.600  10.000 
# Histograma
ggplot(datos, aes(x = satisfaccion_vida)) +
  geom_histogram(binwidth = 1, fill = "#2d4563", color = "white") +
  labs(title = "Distribución de satisfacción con la vida",
       x = "Satisfacción (1-10)", y = "Frecuencia") +
  theme_minimal()

Dividir los datos

# División train/test
division <- initial_split(datos, prop = 0.75)

datos_train <- training(division)
datos_test <- testing(division)

cat("Observaciones en train:", nrow(datos_train), "\n")
Observaciones en train: 375 
cat("Observaciones en test:", nrow(datos_test), "\n")
Observaciones en test: 125 
# Media de la variable objetivo
cat("\nMedia satisfacción (train):", mean(datos_train$satisfaccion_vida))

Media satisfacción (train): 6.719733
cat("\nMedia satisfacción (test):", mean(datos_test$satisfaccion_vida))

Media satisfacción (test): 6.568

Receta de preprocesamiento

# Definir predictores (excluimos voto y pais)
receta <- recipe(satisfaccion_vida ~ edad + educacion_anios + ingreso_hogar +
                 zona + genero + confianza_gobierno + confianza_justicia +
                 satisfaccion_democracia + percepcion_economia +
                 uso_internet + interes_politica,
                 data = datos_train) |>
  # Dummies para categóricas
  step_dummy(all_nominal_predictors()) |>
  # Normalizar (importante para regularización)
  step_normalize(all_numeric_predictors()) |>
  # Eliminar varianza cero
  step_zv(all_predictors())

# Verificar
receta |> prep() |> juice() |> glimpse()
Rows: 375
Columns: 13
$ edad                    <dbl> 1.2595504, -0.4806457, -0.3751792, 1.1013507, …
$ educacion_anios         <dbl> 0.71927007, -0.76410084, -0.02241538, -0.26964…
$ ingreso_hogar           <dbl> -0.6428788, -1.0960358, -0.1897217, 0.7165923,…
$ confianza_gobierno      <dbl> -0.3060254, 0.7974315, -1.4094822, -0.3060254,…
$ confianza_justicia      <dbl> -0.5648555, 0.4535138, -1.5832247, -0.5648555,…
$ satisfaccion_democracia <dbl> 0.5461895, -0.5491103, -0.5491103, 0.5461895, …
$ percepcion_economia     <dbl> 1.57455077, -0.08170881, -0.90983860, 0.746420…
$ interes_politica        <dbl> 1.5995530, 1.5995530, -0.4546675, -0.4546675, …
$ satisfaccion_vida       <dbl> 7.6, 6.3, 7.2, 6.0, 6.3, 5.5, 7.4, 8.5, 4.3, 3…
$ zona_urbana             <dbl> -1.8877612, 0.5283154, 0.5283154, 0.5283154, 0…
$ genero_mujer            <dbl> -1.0450130, 0.9543741, -1.0450130, -1.0450130,…
$ uso_internet_semanal    <dbl> -0.598113, -0.598113, -0.598113, 1.667466, -0.…
$ uso_internet_diario     <dbl> 0.8154072, -1.2231108, 0.8154072, -1.2231108, …

Modelo baseline: OLS

# Regresión lineal simple
modelo_ols <- linear_reg() |>
  set_engine("lm") |>
  set_mode("regression")

# Workflow
wf_ols <- workflow() |>
  add_recipe(receta) |>
  add_model(modelo_ols)

# Ajustar
ajuste_ols <- fit(wf_ols, data = datos_train)

# Coeficientes
tidy(ajuste_ols) |>
  arrange(desc(abs(estimate)))
# A tibble: 13 × 5
   term                    estimate std.error statistic   p.value
   <chr>                      <dbl>     <dbl>     <dbl>     <dbl>
 1 (Intercept)               6.72      0.0628   107.    4.01e-276
 2 educacion_anios           0.382     0.0734     5.21  3.23e-  7
 3 satisfaccion_democracia   0.261     0.0652     4.01  7.51e-  5
 4 confianza_gobierno        0.250     0.0717     3.48  5.62e-  4
 5 ingreso_hogar             0.223     0.0734     3.05  2.49e-  3
 6 percepcion_economia       0.215     0.0638     3.37  8.45e-  4
 7 edad                      0.181     0.0635     2.84  4.75e-  3
 8 uso_internet_diario      -0.155     0.0935    -1.66  9.85e-  2
 9 interes_politica          0.113     0.0704     1.60  1.11e-  1
10 uso_internet_semanal     -0.0968    0.0942    -1.03  3.05e-  1
11 genero_mujer              0.0745    0.0642     1.16  2.47e-  1
12 confianza_justicia       -0.0245    0.0648    -0.378 7.06e-  1
13 zona_urbana              -0.0178    0.0642    -0.278 7.81e-  1

Evaluar OLS en test

# Predicciones en test
pred_ols <- predict(ajuste_ols, datos_test) |>
  bind_cols(datos_test |> select(satisfaccion_vida))

# Métricas
metricas_ols <- pred_ols |>
  metrics(truth = satisfaccion_vida, estimate = .pred)

metricas_ols
# A tibble: 3 × 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard       1.19 
2 rsq     standard       0.264
3 mae     standard       0.945
# Visualizar predicciones vs. reales
ggplot(pred_ols, aes(x = satisfaccion_vida, y = .pred)) +
  geom_point(alpha = 0.5) +
  geom_abline(color = "red", linetype = "dashed") +
  labs(title = "Predicciones OLS vs. valores reales",
       x = "Satisfacción real", y = "Satisfacción predicha") +
  theme_minimal()

Parte 2: LASSO con tuning

¿Por qué regularizar?

Nuestro caso:

  • 11 predictores, ~375 observaciones en train
  • La ratio n/p no es tan extrema
  • Pero algunas variables podrían ser irrelevantes

¿Cuándo regularizar?

  • Muchos predictores relativos a las observaciones
  • Sospecha de multicolinealidad
  • Queremos selección automática de variables
  • Prevenir sobreajuste

Nota

En este ejemplo

Usamos regularización principalmente para:

  1. Demostrar el método
  2. Identificar variables que no aportan
  3. Comparar con OLS

En la práctica, con p = 11 y n = 375, OLS probablemente está bien.

Definir LASSO con penalty a ajustar

# LASSO: mixture = 1
modelo_lasso <- linear_reg(
  penalty = tune(),   # λ a ajustar
  mixture = 1         # 1 = LASSO puro
) |>
  set_engine("glmnet") |>
  set_mode("regression")

# Workflow
wf_lasso <- workflow() |>
  add_recipe(receta) |>
  add_model(modelo_lasso)

# Grilla de valores de penalty (escala logarítmica)
grilla_lambda <- grid_regular(
  penalty(range = c(-4, 0)),  # 10^-4 a 10^0 = 0.0001 a 1
  levels = 30
)

head(grilla_lambda)
# A tibble: 6 × 1
   penalty
     <dbl>
1 0.0001  
2 0.000137
3 0.000189
4 0.000259
5 0.000356
6 0.000489

Validación cruzada para LASSO

# 10-fold CV
folds <- vfold_cv(datos_train, v = 10)

# Ajustar todas las lambdas
resultados_lasso <- tune_grid(
  wf_lasso,
  resamples = folds,
  grid = grilla_lambda,
  metrics = metric_set(rmse, rsq, mae)
)

# Ver resultados
resultados_lasso |>
  collect_metrics() |>
  filter(.metric == "rmse") |>
  arrange(mean) |>
  head(10)
# A tibble: 10 × 7
    penalty .metric .estimator  mean     n std_err .config         
      <dbl> <chr>   <chr>      <dbl> <int>   <dbl> <chr>           
 1 0.0304   rmse    standard    1.25    10  0.0401 pre0_mod19_post0
 2 0.0418   rmse    standard    1.25    10  0.0399 pre0_mod20_post0
 3 0.0001   rmse    standard    1.25    10  0.0422 pre0_mod01_post0
 4 0.000137 rmse    standard    1.25    10  0.0422 pre0_mod02_post0
 5 0.000189 rmse    standard    1.25    10  0.0422 pre0_mod03_post0
 6 0.000259 rmse    standard    1.25    10  0.0422 pre0_mod04_post0
 7 0.000356 rmse    standard    1.25    10  0.0422 pre0_mod05_post0
 8 0.000489 rmse    standard    1.25    10  0.0422 pre0_mod06_post0
 9 0.000672 rmse    standard    1.25    10  0.0422 pre0_mod07_post0
10 0.000924 rmse    standard    1.25    10  0.0422 pre0_mod08_post0

Visualizar el tuning de LASSO

# Gráfico de RMSE vs. penalty
autoplot(resultados_lasso) +
  scale_x_log10() +
  theme_minimal() +
  labs(title = "Tuning de LASSO: RMSE vs. penalty (λ)")

Ejercicio 1: Efecto del λ en la selección

Instrucciones:

  1. Ajustar LASSO con varios valores de λ: 0.001, 0.01, 0.1, 0.5 y 1
  2. Contar cuántas variables se eliminan en cada caso
  3. ¿Qué pasa con un λ muy grande? ¿Y con uno muy pequeño?

Pista: usen map_df() con una función que ajuste el workflow para cada lambda y cuente los coeficientes iguales a cero con tidy().

Tómense 5 minutos para experimentar.

Apéndice 1: Solución

Seleccionar λ y ajustar LASSO final

# λ mínimo vs. λ 1SE (modelo más simple dentro de 1 error estándar)
lambda_min <- select_best(resultados_lasso, metric = "rmse")
lambda_1se <- select_by_one_std_err(resultados_lasso, metric = "rmse",
                                    desc(penalty))

cat("Lambda mínimo:", lambda_min$penalty,
    " | Lambda 1SE:", lambda_1se$penalty, "\n")
Lambda mínimo: 0.03039195  | Lambda 1SE: 0.1487352 
# Usamos lambda mínimo y ajustamos con todo el training
wf_lasso_final <- finalize_workflow(wf_lasso, lambda_min)
ajuste_lasso <- fit(wf_lasso_final, data = datos_train)

# Coeficientes y variables eliminadas
coef_lasso <- tidy(ajuste_lasso) |>
  filter(term != "(Intercept)") |>
  arrange(desc(abs(estimate)))

coef_lasso
# A tibble: 12 × 3
   term                    estimate penalty
   <chr>                      <dbl>   <dbl>
 1 educacion_anios           0.359   0.0304
 2 confianza_gobierno        0.233   0.0304
 3 satisfaccion_democracia   0.230   0.0304
 4 ingreso_hogar             0.199   0.0304
 5 percepcion_economia       0.185   0.0304
 6 edad                      0.154   0.0304
 7 interes_politica          0.0980  0.0304
 8 uso_internet_diario      -0.0515  0.0304
 9 genero_mujer              0.0408  0.0304
10 confianza_justicia        0       0.0304
11 zona_urbana               0       0.0304
12 uso_internet_semanal      0       0.0304
cat("\nVariables eliminadas:", sum(coef_lasso$estimate == 0),
    "de", nrow(coef_lasso))

Variables eliminadas: 3 de 12

Comparar coeficientes OLS vs. LASSO

# Extraer coeficientes de ambos
coef_ols <- tidy(ajuste_ols) |>
  filter(term != "(Intercept)") |>
  select(term, estimate_ols = estimate)

coef_lasso_comp <- tidy(ajuste_lasso) |>
  filter(term != "(Intercept)") |>
  select(term, estimate_lasso = estimate)

# Combinar
comparacion <- left_join(coef_ols, coef_lasso_comp, by = "term") |>
  pivot_longer(cols = starts_with("estimate"),
               names_to = "modelo", values_to = "coef") |>
  mutate(modelo = ifelse(modelo == "estimate_ols", "OLS", "LASSO"))

# Visualizar
ggplot(comparacion, aes(x = reorder(term, abs(coef)), y = coef, fill = modelo)) +
  geom_col(position = "dodge") +
  coord_flip() +
  scale_fill_manual(values = c("OLS" = "#3498DB", "LASSO" = "#E74C3C")) +
  labs(title = "Comparación de coeficientes: OLS vs. LASSO",
       x = "Variable", y = "Coeficiente (normalizado)") +
  theme_minimal()

Evaluar LASSO en test

# Predicciones
pred_lasso <- predict(ajuste_lasso, datos_test) |>
  bind_cols(datos_test |> select(satisfaccion_vida))

# Métricas
metricas_lasso <- pred_lasso |>
  metrics(truth = satisfaccion_vida, estimate = .pred)

# Comparar con OLS
cat("OLS:\n")
OLS:
print(metricas_ols)
# A tibble: 3 × 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard       1.19 
2 rsq     standard       0.264
3 mae     standard       0.945
cat("\nLASSO:\n")

LASSO:
print(metricas_lasso)
# A tibble: 3 × 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard       1.18 
2 rsq     standard       0.273
3 mae     standard       0.940

Parte 3: Ridge y cierre

Ridge regression

# Ridge: mixture = 0 (LASSO usa mixture = 1)
modelo_ridge <- linear_reg(penalty = tune(), mixture = 0) |>
  set_engine("glmnet") |>
  set_mode("regression")

wf_ridge <- workflow() |>
  add_recipe(receta) |>
  add_model(modelo_ridge)

# Reutilizamos los folds y la grilla de lambda de LASSO
resultados_ridge <- tune_grid(
  wf_ridge, resamples = folds, grid = grilla_lambda,
  metrics = metric_set(rmse)
)

lambda_ridge <- select_best(resultados_ridge, metric = "rmse")
ajuste_ridge <- finalize_workflow(wf_ridge, lambda_ridge) |>
  fit(data = datos_train)

# Predicciones y métricas en test
pred_ridge <- predict(ajuste_ridge, datos_test) |>
  bind_cols(datos_test |> select(satisfaccion_vida))

metricas_ridge <- pred_ridge |>
  metrics(truth = satisfaccion_vida, estimate = .pred)

# Ridge nunca elimina variables (coefs se achican, no se anulan)
cat("Variables con coef = 0 en Ridge:",
    sum(tidy(ajuste_ridge)$estimate[-1] == 0), "\n\n")
Variables con coef = 0 en Ridge: 0 
metricas_ridge
# A tibble: 3 × 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard       1.18 
2 rsq     standard       0.271
3 mae     standard       0.945
  • LASSO selecciona (achica y anula); Ridge encoge todos los coeficientes
  • Para una versión intermedia (Elastic Net), ver Apéndice 4

Parte 4: Comparación final

Tabla comparativa en test

# Combinar métricas de los tres modelos lineales
tabla_comparacion <- bind_rows(
  metricas_ols |> mutate(modelo = "OLS"),
  metricas_lasso |> mutate(modelo = "LASSO"),
  metricas_ridge |> mutate(modelo = "Ridge")
) |>
  select(modelo, .metric, .estimate) |>
  pivot_wider(names_from = .metric, values_from = .estimate) |>
  arrange(rmse)

tabla_comparacion
# A tibble: 3 × 4
  modelo  rmse   rsq   mae
  <chr>  <dbl> <dbl> <dbl>
1 LASSO   1.18 0.273 0.940
2 Ridge   1.18 0.271 0.945
3 OLS     1.19 0.264 0.945


Nota

Random Forest y Elastic Net están en los apéndices con código listo para correr en casa. En nuestras pruebas, RF reduce el RMSE ~0.15 puntos; Elastic Net se parece mucho a LASSO.

Visualización de predicciones

# Combinar predicciones de los tres modelos lineales
todas_pred <- bind_rows(
  pred_ols |> mutate(modelo = "OLS"),
  pred_lasso |> mutate(modelo = "LASSO"),
  pred_ridge |> mutate(modelo = "Ridge")
)

ggplot(todas_pred, aes(x = satisfaccion_vida, y = .pred)) +
  geom_point(alpha = 0.4, color = "#2d4563") +
  geom_abline(color = "red", linetype = "dashed") +
  facet_wrap(~modelo, nrow = 1) +
  labs(title = "Predicciones vs. valores reales",
       x = "Satisfacción real", y = "Satisfacción predicha") +
  theme_minimal()

Discusión: ¿Qué modelo elegir?

Observaciones:

  • Los tres modelos lineales dan resultados muy similares
  • LASSO eliminó algunas variables sin perder RMSE
  • La mejora de regularización sobre OLS es marginal

¿Por qué?

  • El dataset tiene relaciones aproximadamente lineales
  • No hay muchas variables irrelevantes
  • n/p no es extremo

Recomendación:

  • Si quiero interpretabilidad: OLS o LASSO
  • Si quiero selección de variables: LASSO
  • Si quiero mejor predicción: ver Random Forest en el Apéndice 5


En este caso, OLS y LASSO dan casi el mismo error. La regularización brilla cuando p/n es grande o hay colinealidad fuerte.

Resumen del laboratorio

Lo que practicamos:

  • Regularización con LASSO y Ridge
  • Tuning de λ con CV
  • Comparación con OLS en test
  • Interpretación de coeficientes

Conceptos clave:

  • mixture = 1: LASSO (selecciona)
  • mixture = 0: Ridge (reduce)
  • Validación cruzada para elegir λ
  • λ 1SE para modelos más simples
  • Apéndices: Elastic Net, Random Forest, ejercicios extra


En el próximo día veremos aprendizaje no supervisado y análisis de texto.

Apéndice: Soluciones

Apéndice 1: Efecto del λ

# Probar varios valores de lambda
lambdas <- c(0.001, 0.01, 0.1, 0.5, 1)

# Para cada lambda, ajustar y contar variables eliminadas
resultados_lambda <- map_df(lambdas, function(l) {
  modelo <- linear_reg(penalty = l, mixture = 1) |>
    set_engine("glmnet") |>
    set_mode("regression")

  ajuste <- workflow() |>
    add_recipe(receta) |>
    add_model(modelo) |>
    fit(data = datos_train)

  coefs <- tidy(ajuste) |> filter(term != "(Intercept)")
  n_eliminadas <- sum(coefs$estimate == 0)
  tibble(lambda = l, vars_eliminadas = n_eliminadas, vars_total = nrow(coefs))
})

resultados_lambda
# A tibble: 5 × 3
  lambda vars_eliminadas vars_total
   <dbl>           <int>      <int>
1  0.001               0         12
2  0.01                0         12
3  0.1                 5         12
4  0.5                12         12
5  1                  12         12
  • Con λ muy pequeño (0.001), LASSO se comporta casi como OLS: no elimina variables
  • Con λ muy grande (1), LASSO elimina la mayoría o todas las variables
  • El λ óptimo balancea entre sesgo (simplificación) y varianza (sobreajuste)

Volver al ejercicio

Apéndice 2: Interacciones

# Receta con interacciones
receta_interact <- recipe(satisfaccion_vida ~ edad + educacion_anios + ingreso_hogar +
                 zona + genero + confianza_gobierno + confianza_justicia +
                 satisfaccion_democracia + percepcion_economia +
                 uso_internet + interes_politica,
                 data = datos_train) |>
  step_dummy(all_nominal_predictors()) |>
  step_interact(terms = ~ edad:educacion_anios) |>
  step_normalize(all_numeric_predictors()) |>
  step_zv(all_predictors())

# Ajustar LASSO con la receta de interacciones
wf_lasso_interact <- workflow() |>
  add_recipe(receta_interact) |>
  add_model(modelo_lasso)

resultados_interact <- tune_grid(
  wf_lasso_interact, resamples = folds,
  grid = grilla_lambda, metrics = metric_set(rmse)
)

# Mejor lambda y modelo final
mejor_interact <- select_best(resultados_interact, metric = "rmse")
ajuste_interact <- finalize_workflow(wf_lasso_interact, mejor_interact) |>
  fit(data = datos_train)

# ¿La interaccion sobrevive?
tidy(ajuste_interact) |>
  filter(term != "(Intercept)") |>
  arrange(desc(abs(estimate)))
# A tibble: 13 × 3
   term                    estimate penalty
   <chr>                      <dbl>   <dbl>
 1 educacion_anios          0.373    0.0117
 2 satisfaccion_democracia  0.248    0.0117
 3 confianza_gobierno       0.242    0.0117
 4 ingreso_hogar            0.214    0.0117
 5 percepcion_economia      0.204    0.0117
 6 edad                     0.170    0.0117
 7 uso_internet_diario     -0.111    0.0117
 8 interes_politica         0.108    0.0117
 9 genero_mujer             0.0612   0.0117
10 uso_internet_semanal    -0.0542   0.0117
11 confianza_justicia      -0.00843  0.0117
12 zona_urbana             -0.00733  0.0117
13 edad_x_educacion_anios   0        0.0117
  • Si la interacción tiene coeficiente distinto de cero, LASSO la considera útil
  • La mejora en RMSE suele ser pequeña porque la relación edad-educación ya se captura parcialmente por separado
  • Agregar interacciones aumenta el número de predictores, lo que da más trabajo al regularizador

Apéndice 3: Países

# Receta que incluye pais
receta_pais <- recipe(satisfaccion_vida ~ ., data = datos_train) |>
  step_rm(voto) |>
  step_dummy(all_nominal_predictors()) |>
  step_normalize(all_numeric_predictors()) |>
  step_zv(all_predictors())

# Ajustar LASSO
wf_lasso_pais <- workflow() |>
  add_recipe(receta_pais) |>
  add_model(modelo_lasso)

resultados_pais <- tune_grid(
  wf_lasso_pais, resamples = folds,
  grid = grilla_lambda, metrics = metric_set(rmse)
)

mejor_pais <- select_best(resultados_pais, metric = "rmse")
ajuste_pais <- finalize_workflow(wf_lasso_pais, mejor_pais) |>
  fit(data = datos_train)

# ¿Qué países sobreviven?
coef_pais <- tidy(ajuste_pais) |>
  filter(str_detect(term, "pais"), estimate != 0) |>
  arrange(desc(abs(estimate)))

cat("Países con coeficiente distinto de cero:", nrow(coef_pais), "\n")
Países con coeficiente distinto de cero: 6 
coef_pais
# A tibble: 6 × 3
  term                      estimate penalty
  <chr>                        <dbl>   <dbl>
1 pais_Brasil               -0.0712   0.0574
2 pais_Guatemala             0.0495   0.0574
3 pais_Costa.Rica            0.0444   0.0574
4 pais_El.Salvador          -0.0419   0.0574
5 pais_México                0.00836  0.0574
6 pais_República.Dominicana -0.00624  0.0574
  • LASSO selecciona automáticamente los países que aportan información predictiva
  • Con muchas dummies de país, la regularización es más útil que en el modelo base
  • Los países con coeficientes positivos tienen mayor satisfacción (controlando por las otras variables)

Apéndice 4: Elastic Net

# Elastic Net: combina L1 (LASSO) y L2 (Ridge)
# Ajustamos penalty y mixture simultáneamente
modelo_enet <- linear_reg(penalty = tune(), mixture = tune()) |>
  set_engine("glmnet") |>
  set_mode("regression")

wf_enet <- workflow() |>
  add_recipe(receta) |>
  add_model(modelo_enet)

# Grilla 2D: 15 lambdas x 5 valores de mixture
grilla_enet <- grid_regular(
  penalty(range = c(-4, 0)),
  mixture(range = c(0, 1)),
  levels = c(15, 5)
)

resultados_enet <- tune_grid(
  wf_enet, resamples = folds, grid = grilla_enet,
  metrics = metric_set(rmse)
)

mejor_enet <- select_best(resultados_enet, metric = "rmse")
mejor_enet
# A tibble: 1 × 3
  penalty mixture .config         
    <dbl>   <dbl> <chr>           
1   0.268       0 pre0_mod61_post0
# Ajustar modelo final
ajuste_enet <- finalize_workflow(wf_enet, mejor_enet) |>
  fit(data = datos_train)

pred_enet <- predict(ajuste_enet, datos_test) |>
  bind_cols(datos_test |> select(satisfaccion_vida))

pred_enet |> metrics(truth = satisfaccion_vida, estimate = .pred)
# A tibble: 3 × 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard       1.18 
2 rsq     standard       0.271
3 mae     standard       0.945
  • Elastic Net se sitúa entre LASSO (mixture = 1) y Ridge (mixture = 0)
  • Útil cuando hay grupos de predictores correlacionados: LASSO tiende a elegir solo uno, Ridge los encoge a todos, Elastic Net captura un balance
  • En este dataset el RMSE queda casi idéntico al de LASSO; el método brilla con dimensiones mucho más altas

Apéndice 5: Random Forest para regresión

# Random Forest para regresión (mode = "regression")
modelo_rf <- rand_forest(
  trees = 500,
  mtry = tune(),
  min_n = tune()
) |>
  set_engine("ranger") |>
  set_mode("regression")

wf_rf <- workflow() |>
  add_recipe(receta) |>
  add_model(modelo_rf)

grilla_rf <- grid_regular(
  mtry(range = c(2, 8)),
  min_n(range = c(5, 20)),
  levels = c(4, 4)
)

resultados_rf <- tune_grid(
  wf_rf, resamples = folds, grid = grilla_rf,
  metrics = metric_set(rmse)
)

mejor_rf <- select_best(resultados_rf, metric = "rmse")
ajuste_rf <- finalize_workflow(wf_rf, mejor_rf) |>
  fit(data = datos_train)

pred_rf <- predict(ajuste_rf, datos_test) |>
  bind_cols(datos_test |> select(satisfaccion_vida))

metricas_rf <- pred_rf |>
  metrics(truth = satisfaccion_vida, estimate = .pred)

metricas_rf
# A tibble: 3 × 3
  .metric .estimator .estimate
  <chr>   <chr>          <dbl>
1 rmse    standard       1.16 
2 rsq     standard       0.308
3 mae     standard       0.920
  • RF reduce el RMSE unas décimas frente a los modelos lineales
  • El costo es interpretabilidad: sin coeficientes, hay que usar VIP y PDP
  • En ciencias sociales, pesa la pregunta: ¿importa entender los coeficientes o predecir lo mejor posible?

Fin del día 2! 🤓