Sesión 1.3: Laboratorio 1 - Primer flujo de trabajo con tidymodels
Lo que vamos a hacer:
Lo que vamos a aprender:
Trabajen en sus computadoras y pregunten si tienen dudas 😉
Necesitamos instalar y cargar los paquetes. Ejecuten este código en R:
tidymodels es un conjunto de paquetes que comparten la filosofía del tidyverse. Los que usaremos hoy:
Ventaja: misma sintaxis para cualquier modelo, sea regresión logística, random forest o redes neuronales. La lista completa queda en la documentación
Vamos a trabajar con un dataset de indicadores socioeconómicos de países de todo el mundo (datos simulados inspirados en el Banco Mundial):
Rows: 179
Columns: 11
$ pais <chr> "Angola", "Argelia", "Benín", "Botsuana", "Bur…
$ continente <chr> "África", "África", "África", "África", "Áfric…
$ gasto_educacion <dbl> 5.7, 4.0, 3.2, 5.0, 4.6, 4.8, 4.6, 4.3, 5.9, 5…
$ acceso_internet <dbl> 50.2, 48.0, 34.3, 46.2, 62.5, 66.0, 55.5, 39.1…
$ urbanizacion <dbl> 69.1, 61.8, 62.9, 68.6, 54.5, 32.3, 57.6, 52.1…
$ gasto_salud <dbl> 8.3, 7.6, 4.5, 5.5, 4.3, 4.6, 8.3, 6.9, 4.8, 6…
$ inflacion <dbl> 9.4, 9.2, 10.3, 14.7, 12.2, 11.8, 11.2, 23.4, …
$ desempleo <dbl> 5.9, 10.0, 7.2, 13.1, 12.7, 9.7, 9.5, 14.0, 9.…
$ inversion_extranjera <dbl> 2.4, 4.8, 2.5, 4.1, 2.3, 2.3, 5.5, 3.6, 5.2, 1…
$ indice_gobierno_digital <dbl> 0.38, 0.78, 0.13, 0.49, 0.52, 0.33, 0.57, 0.33…
$ crecimiento_alto <chr> "si", "si", "no", "no", "no", "si", "no", "no"…
Antes de modelar, siempre hay que explorar los datos:
pais continente gasto_educacion acceso_internet
Length :179 Length :179 Min. :2.100 Min. : 5.00
N.unique :179 N.unique : 5 1st Qu.:4.400 1st Qu.:46.60
N.blank : 0 N.blank : 0 Median :5.000 Median :57.50
Min.nchar: 4 Min.nchar: 4 Mean :5.027 Mean :57.52
Max.nchar: 20 Max.nchar: 7 3rd Qu.:5.600 3rd Qu.:68.35
Max. :8.000 Max. :99.00
urbanizacion gasto_salud inflacion desempleo
Min. :16.40 Min. :3.200 Min. : 5.20 Min. : 1.500
1st Qu.:44.70 1st Qu.:5.800 1st Qu.: 9.35 1st Qu.: 6.700
Median :58.10 Median :6.700 Median :11.70 Median : 8.400
Mean :58.42 Mean :6.631 Mean :12.73 Mean : 8.434
3rd Qu.:73.15 3rd Qu.:7.450 3rd Qu.:14.70 3rd Qu.:10.100
Max. :97.60 Max. :9.500 Max. :37.40 Max. :16.100
inversion_extranjera indice_gobierno_digital crecimiento_alto
Min. :0.200 Min. :0.0800 Length :179
1st Qu.:3.050 1st Qu.:0.4050 N.unique : 2
Median :4.200 Median :0.5200 N.blank : 0
Mean :4.079 Mean :0.5136 Min.nchar: 2
3rd Qu.:5.100 3rd Qu.:0.6350 Max.nchar: 2
Max. :7.400 Max. :0.9600
crecimiento_alto (sí/no)Instrucciones: Antes de continuar, exploren los datos ustedes mismos
Pista: estos comandos les sirven para las cuatro preguntas. ¡Tómense unos minutos para explorar! 🤓
dim(datos) # 1. filas y columnas
glimpse(datos) # tipo de cada variable
sum(is.na(datos)) # 2. total de valores faltantes
colSums(is.na(datos)) # faltantes por columna
datos |> # 3. mínimo, mediana, máximo de cada variable
select(where(is.numeric)) |> summary()
datos |> # 4. correlaciones entre predictores
select(where(is.numeric)) |> cor() |> round(2)Convertimos la variable de resultado a factor y descartamos las columnas que no usaremos como predictores (pais tiene 179 valores únicos y rompería el modelo; continente la dejamos fuera para mantenernos en predictores numéricos):
Rows: 179
Columns: 9
$ gasto_educacion <dbl> 5.7, 4.0, 3.2, 5.0, 4.6, 4.8, 4.6, 4.3, 5.9, 5…
$ acceso_internet <dbl> 50.2, 48.0, 34.3, 46.2, 62.5, 66.0, 55.5, 39.1…
$ urbanizacion <dbl> 69.1, 61.8, 62.9, 68.6, 54.5, 32.3, 57.6, 52.1…
$ gasto_salud <dbl> 8.3, 7.6, 4.5, 5.5, 4.3, 4.6, 8.3, 6.9, 4.8, 6…
$ inflacion <dbl> 9.4, 9.2, 10.3, 14.7, 12.2, 11.8, 11.2, 23.4, …
$ desempleo <dbl> 5.9, 10.0, 7.2, 13.1, 12.7, 9.7, 9.5, 14.0, 9.…
$ inversion_extranjera <dbl> 2.4, 4.8, 2.5, 4.1, 2.3, 2.3, 5.5, 3.6, 5.2, 1…
$ indice_gobierno_digital <dbl> 0.38, 0.78, 0.13, 0.49, 0.52, 0.33, 0.57, 0.33…
$ crecimiento_alto <fct> si, si, no, no, no, si, no, no, si, no, no, no…
levels = c("no", "si")
levels(...) para asegurarnos de que el orden es correctoUsamos initial_split() de rsample para crear la división train/test:
# Fijar semilla para reproducibilidad
set.seed(2026)
# Dividir: 75% entrenamiento, 25% prueba
datos_split <- initial_split(datos_modelo, prop = 0.75, strata = crecimiento_alto)
# Extraer los conjuntos
datos_train <- training(datos_split)
datos_test <- testing(datos_split)
# Verificar tamaños
cat("Entrenamiento:", nrow(datos_train), "filas\n")Entrenamiento: 133 filas
Prueba: 46 filas
strata = crecimiento_alto asegura que la proporción de sí/no sea similar en ambos conjuntosComprobemos que las proporciones son similares en ambos conjuntos:
# A tibble: 2 × 3
crecimiento_alto n prop
<fct> <int> <dbl>
1 no 69 0.519
2 si 64 0.481
# A tibble: 2 × 3
crecimiento_alto n prop
<fct> <int> <dbl>
1 no 24 0.522
2 si 22 0.478
Las proporciones deberían ser muy similares (¡y realmente lo son en este caso!)
Usamos logistic_reg() de parsnip para definir una regresión logística:
Logistic Regression Model Specification (classification)
Computational engine: glm
logistic_reg() define qué tipo de modelo queremosset_engine("glm") define qué implementación usar (glm es la función base de R)set_mode("classification") indica que es un problema de clasificaciónAhora ajustamos el modelo a los datos de entrenamiento:
# A tibble: 9 × 5
term estimate std.error statistic p.value
<chr> <dbl> <dbl> <dbl> <dbl>
1 (Intercept) -4.64 2.56 -1.81 0.0701
2 gasto_educacion 0.588 0.271 2.17 0.0299
3 acceso_internet 0.0634 0.0186 3.41 0.000658
4 urbanizacion -0.00648 0.0159 -0.408 0.683
5 gasto_salud -0.168 0.219 -0.767 0.443
6 inflacion -0.166 0.0638 -2.60 0.00938
7 desempleo -0.318 0.112 -2.85 0.00438
8 inversion_extranjera 0.355 0.182 1.96 0.0505
9 indice_gobierno_digital 5.35 2.03 2.63 0.00848
crecimiento_alto ~ . significa: predecir crecimiento_alto usando todas las demás variablesfit() entrena el modelo con los datos de entrenamientotidy() nos da una tabla limpia con coeficientes, errores estándar y p-valores.pred_class es la clase predicha por el modelo. El punto al principio es la convención de tidymodels para columnas que el paquete crea por ustedes (evita chocar con columnas del usuario)crecimiento_alto (la verdad)# A tibble: 8 × 2
crecimiento_alto .pred_class
<fct> <fct>
1 si no
2 no no
3 no no
4 si no
5 no si
6 no si
7 no no
8 si si
truth es la clase real, estimate la clase predicha Truth
Prediction no si
no 19 4
si 5 18
# A tibble: 13 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 accuracy binary 0.804
2 kap binary 0.609
3 sens binary 0.818
4 spec binary 0.792
5 ppv binary 0.783
6 npv binary 0.826
7 mcc binary 0.609
8 j_index binary 0.610
9 bal_accuracy binary 0.805
10 detection_prevalence binary 0.5
11 precision binary 0.783
12 recall binary 0.818
13 f_meas binary 0.8
Recordatorio: accuracy = (VP + VN)/(VP + VN + FP + FN): de todas las predicciones, cuántas acertó; precision (también aparece como ppv) = VP/(VP + FP): de las que predijo “sí”, cuántas eran “sí”; recall (también sens) = VP/(VP + FN): de los “sí” reales, cuántos detectó
Instrucciones: Reflexionen sobre los resultados
Discutan en grupos de 2-3 personas durante 3-5 minutos 🤓
El modelo no solo predice clases: también devuelve probabilidades. Por defecto, predict() usa un umbral de 0.5, pero podemos cambiarlo
# A tibble: 5 × 3
crecimiento_alto .pred_no .pred_si
<fct> <dbl> <dbl>
1 si 0.622 0.378
2 no 0.935 0.0648
3 no 0.994 0.00588
4 si 0.586 0.414
5 no 0.331 0.669
| Umbral | Efecto sobre las predicciones |
|---|---|
| 0.3 | predice “sí” con facilidad: alta recall, baja precisión |
| 0.5 | punto de equilibrio (por defecto) |
| 0.7 | predice “sí” con cautela: alta precisión, baja recall |
Apéndice 3: Predicciones inciertas · Apéndice 4: Comparación completa
La curva ROC resume el rendimiento en todos los umbrales a la vez: cada punto de la curva es un umbral distinto
# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 roc_auc binary 0.911
Dos temas que no tocaremos hoy pero aparecen en los apéndices:
vfold_cv + fit_resamples): en lugar de una sola división train/test, el modelo se evalúa en varios folds y se promedian las métricas. Da estimaciones más estables. La veremos en Laboratorio 2. Ejemplo en Apéndice 5Material opcional: carguen el script laboratorio-01.R para ejecutar todos los ejemplos
Flujo de trabajo completo:
Conceptos clave:
En el Laboratorio 2 (Sesión 1.4) vamos a:
¡Guarden su trabajo y tómense un descanso! 😉
Comandos para cada pregunta:
[1] 179 11
[1] 179
[1] 11
[1] 0
pais continente gasto_educacion
0 0 0
acceso_internet urbanizacion gasto_salud
0 0 0
inflacion desempleo inversion_extranjera
0 0 0
indice_gobierno_digital crecimiento_alto
0 0
gasto_educacion acceso_internet urbanizacion gasto_salud
Min. :2.100 Min. : 5.00 Min. :16.40 Min. :3.200
1st Qu.:4.400 1st Qu.:46.60 1st Qu.:44.70 1st Qu.:5.800
Median :5.000 Median :57.50 Median :58.10 Median :6.700
Mean :5.027 Mean :57.52 Mean :58.42 Mean :6.631
3rd Qu.:5.600 3rd Qu.:68.35 3rd Qu.:73.15 3rd Qu.:7.450
Max. :8.000 Max. :99.00 Max. :97.60 Max. :9.500
inflacion desempleo inversion_extranjera indice_gobierno_digital
Min. : 5.20 Min. : 1.500 Min. :0.200 Min. :0.0800
1st Qu.: 9.35 1st Qu.: 6.700 1st Qu.:3.050 1st Qu.:0.4050
Median :11.70 Median : 8.400 Median :4.200 Median :0.5200
Mean :12.73 Mean : 8.434 Mean :4.079 Mean :0.5136
3rd Qu.:14.70 3rd Qu.:10.100 3rd Qu.:5.100 3rd Qu.:0.6350
Max. :37.40 Max. :16.100 Max. :7.400 Max. :0.9600
gasto_educacion acceso_internet urbanizacion
gasto_educacion 1.00 0.07 0.24
acceso_internet 0.07 1.00 0.10
urbanizacion 0.24 0.10 1.00
gasto_salud 0.26 0.09 0.21
inflacion -0.08 -0.05 -0.13
desempleo -0.22 -0.13 -0.28
inversion_extranjera 0.21 0.17 0.14
indice_gobierno_digital 0.18 0.20 0.21
gasto_salud inflacion desempleo inversion_extranjera
gasto_educacion 0.26 -0.08 -0.22 0.21
acceso_internet 0.09 -0.05 -0.13 0.17
urbanizacion 0.21 -0.13 -0.28 0.14
gasto_salud 1.00 -0.17 -0.15 0.09
inflacion -0.17 1.00 0.18 -0.18
desempleo -0.15 0.18 1.00 -0.18
inversion_extranjera 0.09 -0.18 -0.18 1.00
indice_gobierno_digital 0.32 -0.28 -0.30 0.39
indice_gobierno_digital
gasto_educacion 0.18
acceso_internet 0.20
urbanizacion 0.21
gasto_salud 0.32
inflacion -0.28
desempleo -0.30
inversion_extranjera 0.39
indice_gobierno_digital 1.00
Respuestas esperadas:
pais, continente, 8 predictores numéricos y crecimiento_altoacceso_internet y urbanizacion tienen distribuciones amplias: países con muy poco acceso vs. muy conectadosindice_gobierno_digital se correlaciona positivamente con acceso_internet y negativamente con inflacionPista: ggplot2 permite visualizar distribuciones con geom_histogram() y correlaciones con el paquete corrplot o ggcorrplot
Calcular las métricas:
# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 precision binary 0.783
# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 recall binary 0.818
# A tibble: 13 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 accuracy binary 0.804
2 kap binary 0.609
3 sens binary 0.818
4 spec binary 0.792
5 ppv binary 0.783
6 npv binary 0.826
7 mcc binary 0.609
8 j_index binary 0.610
9 bal_accuracy binary 0.805
10 detection_prevalence binary 0.5
11 precision binary 0.783
12 recall binary 0.818
13 f_meas binary 0.8
Cómo interpretar la diferencia:
¿Qué métrica priorizar?
f_meas), el promedio armónico de ambasNo hay respuesta universal. El contexto define la métrica correcta
¿Cómo identificar predicciones inciertas?
# A tibble: 5 × 4
crecimiento_alto .pred_no .pred_si incertidumbre
<fct> <dbl> <dbl> <dbl>
1 no 0.527 0.473 0.0265
2 no 0.460 0.540 0.0399
3 no 0.580 0.420 0.0804
4 si 0.582 0.418 0.0823
5 si 0.586 0.414 0.0856
.pred_si cercano a 0.5 son las más difíciles de clasificar: pequeños cambios en los predictores pueden cambiar la clase predicha# 1. Tres copias de las predicciones, cada una con su umbral
pred_umbrales <- bind_rows(
pred_probs |> mutate(umbral = 0.3),
pred_probs |> mutate(umbral = 0.5),
pred_probs |> mutate(umbral = 0.7)
) |>
# 2. Clasificar como "si" si la probabilidad supera el umbral de esa copia
mutate(
clase_predicha = if_else(.pred_si >= umbral, "si", "no"),
clase_predicha = factor(clase_predicha, levels = c("no", "si"))
)
# 3. Calcular precisión y recall dentro de cada umbral
pred_umbrales |>
group_by(umbral) |>
summarise(
precision = precision_vec(crecimiento_alto, clase_predicha,
event_level = "second"),
recall = recall_vec(crecimiento_alto, clase_predicha,
event_level = "second")
)# A tibble: 3 × 3
umbral precision recall
<dbl> <dbl> <dbl>
1 0.3 0.688 1
2 0.5 0.783 0.818
3 0.7 0.85 0.773
| Umbral | Precisión | Recall | Interpretación |
|---|---|---|---|
| 0.3 | baja | alta | predice “sí” fácilmente, más falsos positivos |
| 0.5 | media | media | punto de equilibrio (por defecto) |
| 0.7 | alta | baja | predice “sí” con cautela, más falsos negativos |
Este es el compromiso precisión-recall en acción. El umbral óptimo depende del problema
# Crear los folds para validación cruzada (5 particiones, estratificadas)
set.seed(2026)
folds <- vfold_cv(datos_train, v = 5, strata = crecimiento_alto)
# Ajustar el modelo en cada fold
cv_results <- modelo_log |>
fit_resamples(
crecimiento_alto ~ .,
resamples = folds,
metrics = metric_set(accuracy, roc_auc),
control = control_resamples(event_level = "second")
)
# collect_metrics() devuelve media y error estándar de cada métrica
collect_metrics(cv_results)# A tibble: 2 × 6
.metric .estimator mean n std_err .config
<chr> <chr> <dbl> <int> <dbl> <chr>
1 accuracy binary 0.759 5 0.0236 pre0_mod0_post0
2 roc_auc binary 0.864 5 0.0136 pre0_mod0_post0
# A tibble: 1 × 3
.metric .estimator .estimate
<chr> <chr> <dbl>
1 accuracy binary 0.804
collect_metrics() devuelve la media de cada métrica sobre los 5 folds junto con su error estándar (std_err)Interpretar el AUC:
| AUC | Interpretación |
|---|---|
| 0.5 | No mejor que el azar |
| 0.7–0.8 | Aceptable |
| 0.8–0.9 | Bueno |
| > 0.9 | Excelente (verifiquen data leakage) |
Recordatorio: regresión logística
exp(coeficiente)Pregunta para discutir:
# A tibble: 9 × 4
term estimate odds_ratio p.value
<chr> <dbl> <dbl> <dbl>
1 (Intercept) -4.64 0.00963 0.0701
2 gasto_educacion 0.588 1.80 0.0299
3 acceso_internet 0.0634 1.07 0.000658
4 urbanizacion -0.00648 0.994 0.683
5 gasto_salud -0.168 0.845 0.443
6 inflacion -0.166 0.847 0.00938
7 desempleo -0.318 0.727 0.00438
8 inversion_extranjera 0.355 1.43 0.0505
9 indice_gobierno_digital 5.35 211. 0.00848