class: inverse, left, bottom background-image: url("img/fondo.jpg") background-size: cover # **Modelo de regresión lineal** ---- ## **<br/> Tema 2** ### Orlando Joaqui-Barandica ### --- class: center, middle <img src="img/univalle.jpg" width="20%" style="display: block; margin: auto;" /> --- class: center, middle .center[ <br> <a href="https://www.joaquibarandica.com" target="_blank"> <img src="img/avatar.png" style="border-radius: 80%; width: 220px; max-width: 30%; height: auto;" alt="Avatar"/> </a> ## Orlando Joaqui-Barandica Doctor en Ingeniería, Enf. Ingeniería Industrial **|** Magíster en Economía Aplicada **|** Estadístico.
<i class="fas fa-envelope faa-passing animated "></i>
orlando.joaqui@correounivalle.edu.co ] .center[ ### [www.joaquibarandica.com](https://www.joaquibarandica.com) ] --- class: center, middle #
# ¿Por dónde arrancamos el Bloque B? --- # Idea central del cambio de enfoque .pull-left[ ### En el Bloque A - Ajustábamos modelos para **explicar** relaciones. - Mirábamos coeficientes, pruebas t, ANOVA, supuestos. - La pregunta típica era: **¿qué efecto tiene X sobre Y?** ] .pull-right[ ### En el Bloque B - Ajustamos modelos para **predecir bien**. - Nos importa mucho cómo se comporta el modelo en **datos no vistos**. - La pregunta típica ahora es: **¿qué tan bien predice fuera de la muestra?** ] -- .center[ .orange[.font130[La gran idea de hoy: un modelo no se evalúa donde se entrenó.]] ] --- class: center, middle, inverse # Error clásico ## Ajustar y evaluar con los mismos datos --- # ¿Cuál es el problema? Si yo ajusto un modelo con una base de datos y luego lo evalúo sobre esa **misma** base: -- - el modelo ya "vio" esos datos, - ya se acomodó a ellos, - y el desempeño que reporto suele verse **mejor de lo que realmente es**. -- .center[ .orange[.font150[Eso produce una evaluación demasiado optimista.]] ] --- # Analogía simple .center[.font150[**Entrenar y evaluar en los mismos datos es como estudiar con las respuestas del examen... y luego presentar ese mismo examen.**]] -- ### Lo correcto sería: - **Train** = para aprender. - **Validation** = para comparar opciones y tomar decisiones. - **Test** = para el examen final. --- # Entonces, ¿qué se hace? ### Se divide la base de datos en partes 1. Una parte para **entrenar** el modelo. 2. Otra parte para **probarlo** en datos no vistos. -- ### Y si además voy a comparar varias alternativas: - dejo una parte interna para **validación**, - y reservo el **test** para el final. --- class: center, middle #
# Esquema general .center[.font140[ Datos completos `\(\downarrow\)` **Train** + **Test** `\(\downarrow\)` dentro de Train: **Subtrain** + **Validation** `\(\downarrow\)` elige el mejor modelo `\(\downarrow\)` reentrena en Train completo `\(\downarrow\)` evalúa una sola vez en Test ]] --- # ¿Qué hace cada parte? ### Train Sirve para que el modelo **aprenda patrones**. -- ### Validation Sirve para **comparar modelos**, elegir variables, ajustar decisiones o hiperparámetros. -- ### Test Sirve para obtener una evaluación **honesta y final** del modelo. -- .center[ .gray[El test no se toca durante el proceso de decisión.] ] --- # La regla más importante de todas .center[ .orange[.font170[El conjunto de test se guarda hasta el final.]] ] -- ### ¿Por qué? Porque si empezamos a mirar el test para decidir: - qué modelo dejar, - qué transformación usar, - qué variables incluir, - qué valor de un parámetro escoger, entonces el test deja de ser una evaluación limpia. -- ### En otras palabras: el test no es para **decidir**; el test es para **verificar** al final. --- # ¿Y qué significa "validación interna"? Es una validación que ocurre **dentro del conjunto de entrenamiento**. -- ### Su función es: - comparar varias especificaciones, - decidir entre alternativas, - detectar si un modelo parece muy ajustado, - y no "quemar" el conjunto de test. -- ### Hoy la veremos con una idea simple: **subdividir el train en subtrain + validation.** Luego, en el siguiente tema, veremos que una forma más sólida de hacerlo es con **validación cruzada**. --- # Proporciones típicas No hay una única regla universal, pero en la práctica: - **70% train / 30% test** - **80% train / 20% test** son particiones comunes. -- ### Si además quiero validación interna: una opción simple es pensar algo cercano a: - 60% subtrain - 20% validation - 20% test -- .gray[ Si la muestra es pequeña, esta estrategia puede ser inestable. Y precisamente por eso luego aparece la validación cruzada. ] --- # Errores frecuentes ### 1. Evaluar en train y reportarlo como si fuera desempeño real Eso suele inflar los resultados. -- ### 2. Usar el test muchas veces Cada vez que lo uso para decidir, lo contamino. -- ### 3. Hacer preprocesamiento con toda la base antes de dividir Por ejemplo: - escalar con toda la muestra, - imputar con toda la muestra, - seleccionar variables con toda la muestra. -- .center[ .orange[Eso es leakage: información del test se filtra al entrenamiento.] ] --- # Puente con regresión lineal Lo importante es que esto **no reemplaza** la regresión lineal. -- ### Más bien cambia la lógica de evaluación: Antes: - ajusto un modelo, - interpreto betas, - reviso supuestos, - miro ajuste dentro de muestra. Ahora: - ajusto un modelo, - lo comparo con otros, - y pregunto: **¿predice bien fuera de muestra?** --- class: inverse, center, middle #
# Ejemplo en R ## Predicción de `mpg` con la base `Auto` --- # Contexto del ejemplo Vamos a usar la base `Auto` del paquete `ISLR`. ### Objetivo: predecir el consumo de combustible (`mpg`) a partir de variables del carro. -- ### La idea será esta: 1. separar **train** y **test**, 2. dentro de **train** crear una **validación interna**, 3. comparar dos modelos, 4. escoger uno, 5. evaluarlo una sola vez en **test**. --- # Cargar y preparar datos ```r library(tidyverse) library(rsample) library(yardstick) library(ISLR) data(Auto) Auto <- na.omit(Auto) glimpse(Auto) ``` ``` ## Rows: 392 ## Columns: 9 ## $ mpg <dbl> 18, 15, 18, 16, 17, 15, 14, 14, 14, 15, 15, 14, 15, 14, 2… ## $ cylinders <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 4, 6, 6, 6, 4, … ## $ displacement <dbl> 307, 350, 318, 304, 302, 429, 454, 440, 455, 390, 383, 34… ## $ horsepower <dbl> 130, 165, 150, 150, 140, 198, 220, 215, 225, 190, 170, 16… ## $ weight <dbl> 3504, 3693, 3436, 3433, 3449, 4341, 4354, 4312, 4425, 385… ## $ acceleration <dbl> 12.0, 11.5, 11.0, 12.0, 10.5, 10.0, 9.0, 8.5, 10.0, 8.5, … ## $ year <dbl> 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 70, 7… ## $ origin <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 1, 3, … ## $ name <fct> chevrolet chevelle malibu, buick skylark 320, plymouth sa… ``` --- # Partición inicial: train y test ```r set.seed(123) #Semilla split_test <- initial_split(Auto, prop = 0.80) train <- training(split_test) test <- testing(split_test) nrow(train) ``` ``` ## [1] 313 ``` ```r nrow(test) ``` ``` ## [1] 79 ``` -- ### Importante: - `initial_split()` parte la base aleatoriamente. - Aquí dejamos 80% para entrenar y 20% para test. - Desde este momento, el `test` se guarda. --- # Visualmente: ¿cómo quedaron los tamaños? ```r tibble( conjunto = c("train", "test"), n = c(nrow(train), nrow(test)), porcentaje = round(c(nrow(train), nrow(test)) / nrow(Auto) * 100, 1) ) ``` ``` ## # A tibble: 2 × 3 ## conjunto n porcentaje ## <chr> <int> <dbl> ## 1 train 313 79.8 ## 2 test 79 20.2 ``` --- # Validación interna dentro de train Ahora tomamos el conjunto `train` y lo partimos otra vez: - una parte para ajustar modelos, - otra para compararlos. ```r set.seed(123) split_val <- initial_split(train, prop = 0.75) subtrain <- training(split_val) validation <- testing(split_val) tibble( conjunto = c("subtrain", "validation", "test"), n = c(nrow(subtrain), nrow(validation), nrow(test)) ) ``` ``` ## # A tibble: 3 × 2 ## conjunto n ## <chr> <int> ## 1 subtrain 234 ## 2 validation 79 ## 3 test 79 ``` -- ### Lectura conceptual - `subtrain`: aquí se ajustan los modelos. - `validation`: aquí se comparan. - `test`: sigue guardado. --- # ¿Por qué no ajustar todo de una vez sobre train? Porque si quiero comparar alternativas, necesito un espacio intermedio. -- ### Por ejemplo, para decidir entre: - modelo simple, - modelo múltiple, - transformación, - interacción, - un método más flexible. -- ### Esa comparación no debería hacerse mirando el test. --- # Dos modelos candidatos Vamos a comparar dos regresiones: ### Modelo 1 `mpg ~ horsepower` ### Modelo 2 `mpg ~ horsepower + weight + displacement` -- La idea no es que estos sean "los únicos correctos", sino mostrar el flujo de validación. --- # Ajustar los modelos en subtrain ```r modelo_1 <- lm(mpg ~ horsepower, data = subtrain) modelo_2 <- lm(mpg ~ horsepower + weight + displacement, data = subtrain) summary(modelo_1) ``` ``` ## ## Call: ## lm(formula = mpg ~ horsepower, data = subtrain) ## ## Residuals: ## Min 1Q Median 3Q Max ## -13.9936 -3.4686 -0.5186 2.7549 16.4881 ## ## Coefficients: ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 40.496280 0.939934 43.08 <2e-16 *** ## horsepower -0.159759 0.008468 -18.87 <2e-16 *** ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## ## Residual standard error: 5.079 on 232 degrees of freedom ## Multiple R-squared: 0.6054, Adjusted R-squared: 0.6037 ## F-statistic: 355.9 on 1 and 232 DF, p-value: < 2.2e-16 ``` --- # Segundo modelo ```r summary(modelo_2) ``` ``` ## ## Call: ## lm(formula = mpg ~ horsepower + weight + displacement, data = subtrain) ## ## Residuals: ## Min 1Q Median 3Q Max ## -11.9864 -2.9523 -0.5611 2.8007 15.8406 ## ## Coefficients: ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 45.274108 1.648997 27.456 < 2e-16 *** ## horsepower -0.033944 0.016824 -2.018 0.0448 * ## weight -0.005447 0.001036 -5.255 3.38e-07 *** ## displacement -0.009490 0.009018 -1.052 0.2937 ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## ## Residual standard error: 4.398 on 230 degrees of freedom ## Multiple R-squared: 0.7067, Adjusted R-squared: 0.7028 ## F-statistic: 184.7 on 3 and 230 DF, p-value: < 2.2e-16 ``` -- ### Qué remarcar oralmente - Aquí todavía estamos en lógica de regresión clásica. - La diferencia es que ahora no me voy a quedar solo con el resumen del modelo. - Voy a mirar cómo predice en datos no usados para ajustarlo. --- # Predicciones sobre validation ```r pred_1_val <- predict(modelo_1, newdata = validation) pred_2_val <- predict(modelo_2, newdata = validation) ``` --- # Comparación en validation ```r tibble( modelo = c("Modelo 1", "Modelo 2"), RMSE = c( rmse_vec(validation$mpg, pred_1_val), rmse_vec(validation$mpg, pred_2_val) ), MAE = c( mae_vec(validation$mpg, pred_1_val), mae_vec(validation$mpg, pred_2_val) ) ) ``` ``` ## # A tibble: 2 × 3 ## modelo RMSE MAE ## <chr> <dbl> <dbl> ## 1 Modelo 1 4.56 3.64 ## 2 Modelo 2 3.93 3.04 ``` -- - **RMSE** y **MAE** miden error de predicción. - En regresión, **más pequeño es mejor**. - Aquí estoy comparando modelos en datos no vistos por ellos. --- # ¿Qué están midiendo RMSE y MAE? Cuando hacemos predicción en regresión, el modelo entrega un valor estimado de \(Y\). Entonces, para cada observación, aparece una diferencia entre: - el valor **real** - y el valor **predicho** -- A esa diferencia la llamamos **error de predicción**: $$ \text{Error} = y_i - \hat{y}_i $$ -- ### Idea clave Si el error es pequeño, el modelo estuvo cerca. Si el error es grande, el modelo se equivocó bastante. -- ### Entonces, ¿qué hacen RMSE y MAE? Ambas métricas resumen, en un solo número, **qué tan lejos estuvieron las predicciones de los valores reales**. --- # Intuición de MAE y RMSE ### MAE: error absoluto medio Mira, en promedio, **cuánto se equivoca** el modelo. $$ MAE = \frac{1}{n}\sum |y_i - \hat{y}_i| $$ -- Si el MAE fuera 2, por ejemplo, significa que el modelo se equivoca, en promedio, en unas **2 unidades de la variable respuesta**. -- ### RMSE: raíz del error cuadrático medio También mide error de predicción, pero **castiga más fuerte los errores grandes**. $$ RMSE = \sqrt{\frac{1}{n}\sum (y_i - \hat{y}_i)^2} $$ --- ### Regla práctica para interpretar - **MAE** = lectura más directa y fácil de explicar. - **RMSE** = útil cuando queremos penalizar más los errores grandes. - En ambos casos: .orange[**más pequeño = mejor capacidad predictiva**] --- # Ejemplo simple: ¿cómo se calculan? Supongamos estas tres observaciones: | Caso | Valor real \(y\) | Predicción `\(\hat{y}\)` | Error `\(y - \hat{y}\)` | Error absoluto | Error al cuadrado | |------|------------------|------------------------|------------------------|----------------|-------------------| | 1 | 20 | 18 | 2 | 2 | 4 | | 2 | 25 | 27 | -2 | 2 | 4 | | 3 | 30 | 24 | 6 | 6 | 36 | -- ### Cálculo del MAE $$ MAE = \frac{2 + 2 + 6}{3} = 3.33 $$ ### Cálculo del RMSE $$ RMSE = \sqrt{\frac{4 + 4 + 36}{3}} = \sqrt{14.67} \approx 3.83 $$ --- ### ¿Qué quiero que noten? - Ambos resumen el error de predicción. - Pero el **RMSE sube más** cuando aparece un error grande. - Aquí el error de 6 pesa bastante más en RMSE que en MAE. -- .center[ .orange[Si me preocupan mucho los errores grandes, RMSE me da una señal más dura.] ] --- # ¿Qué modelo elegir? ### Regla simple: me quedo con el que tenga menor error en validation. -- ### Pero ojo: no significa que "ese sea el verdadero modelo del mundo". Significa algo más modesto y más útil en este contexto: .orange[es el que funcionó mejor para predecir en esta validación interna.] --- # Una vez elegido, ¿qué hago? Después de escoger el modelo ganador: ### 1. lo vuelvo a ajustar usando **todo el train** ### 2. recién al final lo evalúo en **test** -- Eso se hace porque ya tomé la decisión y ahora quiero aprovechar mejor los datos de entrenamiento. --- # Reentrenar el modelo elegido en train completo ```r modelo_final <- lm(mpg ~ horsepower + weight + displacement, data = train) summary(modelo_final) ``` ``` ## ## Call: ## lm(formula = mpg ~ horsepower + weight + displacement, data = train) ## ## Residuals: ## Min 1Q Median 3Q Max ## -11.5633 -2.7492 -0.5385 2.3325 16.0114 ## ## Coefficients: ## Estimate Std. Error t value Pr(>|t|) ## (Intercept) 45.3475152 1.3288633 34.125 < 2e-16 *** ## horsepower -0.0415852 0.0145674 -2.855 0.0046 ** ## weight -0.0054818 0.0007918 -6.923 2.56e-11 *** ## displacement -0.0056882 0.0073171 -0.777 0.4375 ## --- ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 ## ## Residual standard error: 4.279 on 309 degrees of freedom ## Multiple R-squared: 0.7126, Adjusted R-squared: 0.7098 ## F-statistic: 255.4 on 3 and 309 DF, p-value: < 2.2e-16 ``` --- # Evaluación final en test ```r pred_test <- predict(modelo_final, newdata = test) tibble( RMSE_test = rmse_vec(test$mpg, pred_test), MAE_test = mae_vec(test$mpg, pred_test) ) ``` ``` ## # A tibble: 1 × 2 ## RMSE_test MAE_test ## <dbl> <dbl> ## 1 4.11 3.14 ``` -- ### Interpretación Ese resultado en test es nuestra estimación más honesta del desempeño final del modelo sobre datos nuevos. --- # ¿Y si el test sale peor que validation? Eso puede pasar, y de hecho es normal. -- ### ¿Por qué? Porque el modelo puede haberse visto bien en validation pero no tan bien en otro subconjunto no observado. -- ### Esto nos deja una enseñanza: una sola partición puede depender bastante del azar. .gray[Por eso, en el siguiente tema, veremos validación cruzada.] --- # Resumen del flujo completo .center[.font120[ 1. Separar **train** y **test** 2. Guardar **test** 3. Dividir **train** en **subtrain + validation** 4. Ajustar varios modelos en **subtrain** 5. Compararlos en **validation** 6. Elegir uno 7. Reentrenar en **train** completo 8. Evaluar una sola vez en **test** ]] --- class: center, middle, inverse #
# Mensaje importante .center[.font150[ Un buen desempeño dentro de muestra no garantiza un buen desempeño fuera de muestra. ]] --- # Qué deben llevarse de este tema - La partición train/test cambia la lógica de evaluación. - El conjunto de test debe reservarse hasta el final. - La validación interna sirve para tomar decisiones sin contaminar el test. - En regresión, la comparación se hace con errores de predicción. - Una sola partición puede ser inestable. -- .center[ .orange[Lo natural después de esto es aprender validación cruzada.] ] --- # Ejercicio sugerido Usando la misma base `Auto`: 1. Haga una partición train/test. 2. Cree una validación interna dentro de train. 3. Compare estos modelos: - `mpg ~ horsepower` - `mpg ~ weight` - `mpg ~ horsepower + weight` - `mpg ~ horsepower + weight + displacement` 4. Elija el mejor según RMSE en validation. 5. Evalúe el ganador en test. --- # Pregunta para cerrar .center[.font150[ Si yo pruebo 20 modelos distintos mirando siempre el test... ¿ese test sigue siendo un examen final limpio? ]] -- .center[ .orange[No. Ya lo convertí en parte del proceso de selección.] ] --- class: center, middle, inverse #
# Validación cruzada ## ¿Por qué es clave en regresión? --- # Punto de partida En el tema anterior vimos esto: - separar **train** y **test**, - y dentro de `train` hacer una validación interna. -- ### Pero aparece un problema: si esa validación interna depende de **una sola partición**, el resultado puede cambiar bastante por azar. -- .center[ .orange[Un modelo puede verse “mejor” solo porque cayó bien parado en esa división.] ] --- # La motivación real de la validación cruzada ### Pregunta: ¿Qué pasa si en vez de depender de una sola validación, repito el proceso varias veces sobre distintas particiones del train? -- ### Respuesta: obtengo una evaluación mucho más **estable**, menos dependiente de un solo corte aleatorio, y más útil para comparar modelos de forma justa. --- # Idea intuitiva La validación cruzada toma el conjunto de entrenamiento y lo divide en varias partes llamadas **folds**. -- Luego: - entrena el modelo en varias de esas partes, - valida en la parte que quedó por fuera, - repite el proceso rotando esa parte de validación, - y al final promedia el desempeño. -- .center[ .orange[En lugar de una sola validación, tengo varias mini-validaciones.] ] --- # Idea intuitiva .pull-left[ ### Validación interna <img src="img/validacion1.jpg" width="100%"> ] .pull-right[ ### Validación cruzada <img src="img/validacion2.jpg" width="100%"> ] --- # ¿Qué significa “k-fold cross-validation”? ### Se divide el train en \(k\) partes similares Por ejemplo: - \(k = 5\) - \(k = 10\) -- ### En cada iteración: - se usan \(k-1\) folds para entrenar, - y 1 fold para validar. Eso se repite hasta que **cada fold haya sido usado una vez como validación**. --- # Esquema visual de 5-fold CV .center[.font120[ Fold 1: **Validación** | Entrena | Entrena | Entrena | Entrena Fold 2: Entrena | **Validación** | Entrena | Entrena | Entrena Fold 3: Entrena | Entrena | **Validación** | Entrena | Entrena Fold 4: Entrena | Entrena | Entrena | **Validación** | Entrena Fold 5: Entrena | Entrena | Entrena | Entrena | **Validación** ]] -- ### Al final promedio las métricas de las 5 iteraciones. --- # ¿Por qué esto es mejor que una sola partición? ### Porque reduce la dependencia del azar Con una sola validación puede pasar que: - el subconjunto elegido sea muy fácil, - o muy difícil, - o poco representativo. -- Con validación cruzada: - el modelo se prueba varias veces, - en distintas porciones, - y la comparación se vuelve más confiable. --- # ¿Qué problema ayuda a controlar? ### El sobreajuste Un modelo sobreajustado aprende demasiado bien los detalles del conjunto con el que fue entrenado, pero luego falla al salir de esa muestra. -- La validación cruzada ayuda a detectar esto porque obliga al modelo a rendir bien en **múltiples subconjuntos no vistos**. -- .center[ .orange[No elimina mágicamente el sobreajuste, pero sí ayuda a verlo con más honestidad.] ] --- # ¿Por qué también sirve para comparar modelos? Porque todos los modelos se evalúan bajo el **mismo esquema de particiones**. -- Entonces la comparación es más justa. ### Ejemplo: si quiero comparar - un modelo lineal simple, - uno polinómico, - uno más flexible, puedo mirar quién tiene mejor desempeño promedio en los mismos folds. --- # Mensaje importante .center[ .orange[.font150[La validación cruzada no reemplaza el test final.]] ] -- ### El flujo correcto sigue siendo: 1. separar **train** y **test**, 2. dentro de `train` hacer **cross-validation**, 3. escoger el mejor modelo, 4. reentrenarlo con todo `train`, 5. evaluarlo una sola vez en `test`. --- # Entonces, ¿qué promediamos? En regresión, normalmente promediamos métricas como: - **RMSE** - **MAE** -- En cada fold sale un valor distinto. Al final calculamos, por ejemplo: - RMSE promedio - MAE promedio -- ### Lectura si un modelo tiene menor RMSE promedio, en principio está prediciendo mejor a través de los distintos folds. --- # No confundir estos dos k ### En validación cruzada: \(k\) = número de folds ### En k-NN: \(k\) = número de vecinos -- .center[ .gray[Son ideas diferentes, aunque usen la misma letra.] ] --- # ¿Qué valores de k se usan mucho? En la práctica, los más comunes son: - **5-fold CV** - **10-fold CV** -- ### Intuición rápida - si \(k\) es pequeño, cada validación deja más datos por fuera; - si \(k\) es grande, cada entrenamiento usa más datos. --- # Caso límite: LOOCV Existe una variante llamada **LOOCV** (Leave-One-Out Cross-Validation). -- ### ¿Qué hace? - deja una sola observación para validar, - y entrena con todas las demás, - repitiendo esto para cada observación. -- ### Idea general Es conceptualmente elegante, pero puede ser más costosa y a veces más inestable. Para fines prácticos, muchas veces se prefiere **5-fold o 10-fold CV**. --- # Caso límite: LOOCV .center[ <img src="img/validacion3.jpg" width="60%"> ] --- # Conexión con el tema anterior Antes hacíamos: - `subtrain` - `validation` como una sola división interna. -- Ahora hacemos algo más robusto: - varias divisiones internas, - varias validaciones, - y un promedio final. -- .center[ .orange[La validación cruzada es, en el fondo, una forma más sólida de validación interna.] ] --- class: center, middle, inverse #
# Validación cruzada en R ## Ejemplo con la base Auto --- # Punto de partida del ejemplo Seguimos con la idea de predecir `mpg`. Y seguimos respetando la lógica correcta: - `test` ya quedó guardado, - la validación cruzada se hace **solo sobre train**. --- # Crear folds en R .pull-left[ ```r set.seed(123) cv_folds <- vfold_cv(train, v = 10) cv_folds ``` ``` ## # 10-fold cross-validation ## # A tibble: 10 × 2 ## splits id ## <list> <chr> ## 1 <split [281/32]> Fold01 ## 2 <split [281/32]> Fold02 ## 3 <split [281/32]> Fold03 ## 4 <split [282/31]> Fold04 ## 5 <split [282/31]> Fold05 ## 6 <split [282/31]> Fold06 ## 7 <split [282/31]> Fold07 ## 8 <split [282/31]> Fold08 ## 9 <split [282/31]> Fold09 ## 10 <split [282/31]> Fold10 ``` ] .pull-right[ ### Qué dice esto - `v = 10` significa **10-fold cross-validation**. - El objeto guarda 10 particiones del conjunto `train`. - En cada una hay una parte para análisis y otra para evaluación. ] --- # ¿Qué está haciendo la validación cruzada? Partamos de una idea simple: ya habíamos separado la base original en dos partes: - **train** = para trabajar el modelo - **test** = para evaluarlo al final -- ### Hasta ahí vamos bien Ahora, dentro de `train`, la validación cruzada hace otra división. Pero no divide una sola vez. --- ### Lo que hace es esto: parte el conjunto `train` en varios pedacitos del mismo tamaño, que se llaman **folds**. Si usamos 10-fold CV, entonces: - `train` se divide en **10 partes** - en cada vuelta, se deja **1 parte para validar** - y las otras **9 partes se usan para entrenar** -- .center[ .orange[O sea: el modelo se entrena varias veces, no una sola.] ] --- # ¿Cómo pensar esos 10 folds? Imagina que el conjunto `train` se partió así: .center[.font120[ Fold 1 | Fold 2 | Fold 3 | Fold 4 | Fold 5 | Fold 6 | Fold 7 | Fold 8 | Fold 9 | Fold 10 ]] -- ### Primera vuelta - se entrena con Fold 2 hasta Fold 10 - se valida con Fold 1 ### Segunda vuelta - se entrena con Fold 1 y Fold 3 hasta Fold 10 - se valida con Fold 2 --- ### Y así sucesivamente... hasta que cada fold haya servido **una vez como validación**. -- ### Entonces, al final no tengo una sola medida de error, sino varias medidas de error, una por cada vuelta, y luego saco un promedio. -- .center[ .orange[Eso hace la evaluación más estable y menos dependiente del azar.] ] --- # Ver la lógica de un fold ```r cv_folds$splits[[1]] ``` ``` ## <Analysis/Assess/Total> ## <281/32/313> ``` -- ### Conceptualmente En cada fold, `rsample` separa: - **analysis(split)** = datos para entrenar - **assessment(split)** = datos para validar --- # Función para evaluar un modelo en un fold ```r evaluar_fold <- function(split, formula_modelo) { datos_entrena <- analysis(split) datos_valida <- assessment(split) modelo <- lm(formula_modelo, data = datos_entrena) pred <- predict(modelo, newdata = datos_valida) tibble( rmse = rmse_vec(datos_valida$mpg, pred), mae = mae_vec(datos_valida$mpg, pred) ) } ``` -- ### Idea Esta función hace exactamente lo que haríamos a mano: 1. entrena, 2. predice, 3. calcula métricas. --- # Aplicar CV a un modelo simple ```r resultados_m1 <- map2_dfr( cv_folds$splits, cv_folds$id, ~ evaluar_fold(.x, mpg ~ horsepower) %>% mutate(fold = .y) ) resultados_m1 ``` ``` ## # A tibble: 10 × 3 ## rmse mae fold ## <dbl> <dbl> <chr> ## 1 4.21 3.24 Fold01 ## 2 3.08 2.33 Fold02 ## 3 4.97 3.80 Fold03 ## 4 6.20 4.82 Fold04 ## 5 5.26 4.08 Fold05 ## 6 4.60 3.57 Fold06 ## 7 5.39 4.47 Fold07 ## 8 4.98 4.14 Fold08 ## 9 4.99 3.85 Fold09 ## 10 5.52 4.27 Fold10 ``` --- # ¿Qué está haciendo este código? .pull-left[ ### Paso a paso - `cv_folds$splits` contiene las 10 particiones de la validación cruzada. - `cv_folds$id` contiene el nombre de cada partición: Fold01, Fold02, etc. - `map2_dfr()` recorre ambas cosas al mismo tiempo. - En cada vuelta, toma un fold y le aplica la función `evaluar_fold()`. ] .pull-right[ ### ¿Qué hace `evaluar_fold()`? En cada fold: - entrena el modelo con la parte de entrenamiento, - valida con la parte que quedó por fuera, - calcula métricas de error, - y devuelve el `rmse` y el `mae`. Luego `mutate(fold = .y)` agrega el nombre del fold para saber de cuál vuelta salió cada resultado. ] -- ### Entonces, ¿qué es `resultados_m1`? Es una tabla que reúne el desempeño del mismo modelo en los 10 folds. Cada fila muestra cómo le fue al modelo `mpg ~ horsepower` en una partición distinta de la validación cruzada. --- # ¿Cómo leer la salida? Si la tabla muestra algo como esto: - Fold01 \(\rightarrow\) RMSE = 4.21 - Fold02 \(\rightarrow\) RMSE = 3.08 - Fold03 \(\rightarrow\) RMSE = 4.97 - ... - Fold10 \(\rightarrow\) RMSE = 5.52 -- ### Eso significa: el modelo se entrenó 10 veces, y en cada vuelta fue evaluado sobre un subconjunto distinto. Por eso: - el error **no sale igual** en todos los folds, - porque cada validación ocurre sobre datos diferentes. -- ### La idea no es escoger “el mejor fold” La idea es mirar el comportamiento del modelo **en conjunto** y luego sacar un promedio de RMSE y MAE. --- # ¿Qué significa esta tabla? .pull-left[ ``` ## # A tibble: 10 × 3 ## rmse mae fold ## <dbl> <dbl> <chr> ## 1 4.21 3.24 Fold01 ## 2 3.08 2.33 Fold02 ## 3 4.97 3.80 Fold03 ## 4 6.20 4.82 Fold04 ## 5 5.26 4.08 Fold05 ## 6 4.60 3.57 Fold06 ## 7 5.39 4.47 Fold07 ## 8 4.98 4.14 Fold08 ## 9 4.99 3.85 Fold09 ## 10 5.52 4.27 Fold10 ``` ] .pull-right[ Cada fila representa un fold. Por tanto: - hay un RMSE por fold, - hay un MAE por fold, - y no tienen por qué ser idénticos. ### Eso es completamente normal porque el modelo está siendo probado sobre subconjuntos distintos. ] --- # Resumen promedio del modelo 1 ```r resultados_m1 %>% summarise( RMSE_promedio = mean(rmse), MAE_promedio = mean(mae) ) ``` ``` ## # A tibble: 1 × 2 ## RMSE_promedio MAE_promedio ## <dbl> <dbl> ## 1 4.92 3.86 ``` -- ### Esa es la idea central de CV: no me quedo con una sola validación, sino con el promedio de varias. --- # Comparar varios modelos con CV Ahora comparemos tres candidatos: 1. `mpg ~ horsepower` 2. `mpg ~ horsepower + weight` 3. `mpg ~ horsepower + weight + displacement` --- # Modelo 2 ```r resultados_m2 <- map2_dfr( cv_folds$splits, cv_folds$id, ~ evaluar_fold(.x, mpg ~ horsepower + weight) %>% mutate(fold = .y) ) resultados_m2 %>% summarise( RMSE_promedio = mean(rmse), MAE_promedio = mean(mae) ) ``` ``` ## # A tibble: 1 × 2 ## RMSE_promedio MAE_promedio ## <dbl> <dbl> ## 1 4.22 3.34 ``` --- # Modelo 3 ```r resultados_m3 <- map2_dfr( cv_folds$splits, cv_folds$id, ~ evaluar_fold(.x, mpg ~ horsepower + weight + displacement) %>% mutate(fold = .y) ) resultados_m3 %>% summarise( RMSE_promedio = mean(rmse), MAE_promedio = mean(mae) ) ``` ``` ## # A tibble: 1 × 2 ## RMSE_promedio MAE_promedio ## <dbl> <dbl> ## 1 4.23 3.34 ``` --- # Tabla comparativa final ```r comparacion_cv <- tibble( modelo = c( "Modelo 1: horsepower", "Modelo 2: horsepower + weight", "Modelo 3: horsepower + weight + displacement" ), RMSE = c( mean(resultados_m1$rmse), mean(resultados_m2$rmse), mean(resultados_m3$rmse) ), MAE = c( mean(resultados_m1$mae), mean(resultados_m2$mae), mean(resultados_m3$mae) ) ) comparacion_cv ``` --- # Tabla comparativa final ``` ## # A tibble: 3 × 3 ## modelo RMSE MAE ## <chr> <dbl> <dbl> ## 1 Modelo 1: horsepower 4.92 3.86 ## 2 Modelo 2: horsepower + weight 4.22 3.34 ## 3 Modelo 3: horsepower + weight + displacement 4.23 3.34 ``` ### Interpretación - cada número ya resume varios folds, - por eso la comparación es más robusta, - y el modelo con menor error promedio queda mejor posicionado. --- # Una lectura correcta de esta tabla No significa: - “este modelo es perfecto” - o “este modelo explica la realidad completamente” -- Sí significa algo más prudente: .orange[entre estas alternativas, este modelo mostró mejor capacidad predictiva promedio dentro del train.] --- # Después de elegir el mejor, ¿qué sigue? ### Paso 1 ajustar ese modelo con **todo el train** ### Paso 2 evaluarlo una sola vez en **test** --- # Después de elegir el mejor, ¿qué sigue? ```r modelo_final <- lm(mpg ~ horsepower + weight + displacement, data = train) pred_test <- predict(modelo_final, newdata = test) tibble( RMSE_test = rmse_vec(test$mpg, pred_test), MAE_test = mae_vec(test$mpg, pred_test) ) ``` ``` ## # A tibble: 1 × 2 ## RMSE_test MAE_test ## <dbl> <dbl> ## 1 4.11 3.14 ``` --- # Advertencia metodológica importante La validación cruzada debe hacerse sin filtrar información del futuro fold de validación. -- ### Por ejemplo, está mal: - escalar usando todo `train` antes de armar folds, - imputar usando toda la data, - seleccionar variables con toda la muestra. -- .center[ .orange[Todo lo que se “aprende” del dato debe hacerse dentro de cada fold de entrenamiento.] ] --- # Ventajas de la validación cruzada - Usa mejor los datos que una sola validación. - Reduce la dependencia de una partición afortunada o desafortunada. - Da comparaciones más justas entre modelos. - Ayuda a detectar sobreajuste. - Es un estándar real en modelación predictiva. --- # Limitaciones Tampoco hay que venderla como magia. ### La validación cruzada: - no garantiza que el modelo sea bueno, - no corrige automáticamente datos malos, - no evita por sí sola el leakage, - y no reemplaza el criterio sustantivo. -- .center[ .gray[Es una herramienta de evaluación, no un sustituto del pensamiento estadístico.] ] --- # Qué deben llevarse de este tema - La validación cruzada repite la validación varias veces. - La forma más común es **k-fold CV**. - En regresión solemos comparar modelos con RMSE y MAE promedio. - Su utilidad principal es dar una comparación más estable y más justa. - Aun usando CV, el conjunto de test se reserva para el final. --- # Ejercicio sugerido Con la base `Auto` y usando `train`: 1. construya una validación cruzada de 5 folds, 2. compare estos modelos: - `mpg ~ horsepower` - `mpg ~ weight` - `mpg ~ horsepower + weight` - `mpg ~ horsepower + weight + displacement` 3. calcule RMSE promedio para cada uno, 4. escoja el mejor, 5. evalúelo en `test`. --- class: center, middle # Cierre ## Si una sola partición puede engañarme, ## la validación cruzada me da una evaluación más confiable. .center[ .orange[Ahora sí estamos listos para entrar a métodos más flexibles.] ] --- class: center, middle, inverse #
# k-NN para regresión ## intuición, escala de variables y elección de \(k\) --- # ¿Qué idea hay detrás de k-NN? k-NN significa **k-nearest neighbors** o **k vecinos más cercanos**. -- ### La lógica es muy intuitiva: si quiero predecir \(Y\) para una nueva observación, busco en la base los casos más parecidos a ella y uso esa información para predecir. -- .center[ .orange[En regresión, la predicción suele ser el promedio de los vecinos más cercanos.] ] --- # ¿Por qué este método es interesante? Porque no arranca imponiendo una forma funcional rígida como: - una recta, - una parábola, - o una relación predefinida. -- ### Más bien hace esto: - mira dónde cae una nueva observación, - busca observaciones similares, - y predice usando su comportamiento local. -- ### Traducción simple .orange[k-NN aprende por cercanía.] --- # Diferencia con la regresión lineal .pull-left[ ### Regresión lineal - Supone una relación funcional. - Entrega coeficientes. - Tiene una interpretación paramétrica. ] .pull-right[ ### k-NN - No entrega betas interpretables. - No impone una forma rígida. - Predice usando vecinos cercanos. ] -- .center[ .gray[k-NN suele ser más flexible, pero menos interpretable.] ] --- # ¿Cómo predice en regresión? Supongamos que quiero predecir `mpg` para un carro nuevo. -- ### Paso 1 Busco los \(k\) carros más cercanos en las variables explicativas. ### Paso 2 Miro el valor de `mpg` de esos vecinos. ### Paso 3 Promedio esos valores. --- ### Ejemplo simple Si los 3 vecinos más cercanos tienen: - 20 mpg - 22 mpg - 24 mpg entonces la predicción sería: `$$\hat{y} = \frac{20+22+24}{3} = 22$$` --- # Intuición visual .center[.font140[ Nueva observación `\(\downarrow\)` busco casos parecidos `\(\downarrow\)` miro sus valores de \(Y\) `\(\downarrow\)` promedio `\(\downarrow\)` obtengo la predicción ]] --- # Entonces, ¿qué significa “cercano”? Ahí está el corazón del método. -- ### “Cercano” se define con una distancia Usualmente, una distancia entre observaciones usando las variables predictoras. Por ejemplo: - `horsepower` - `weight` -- ### Idea Dos carros serán “vecinos” si sus valores en esas variables son parecidos. --- # Problema clave: la escala de las variables k-NN depende totalmente de distancias. Entonces, si una variable está en una escala mucho más grande que otra, puede dominar la distancia y sesgar la idea de cercanía. -- ### Ejemplo típico - `horsepower` puede estar entre 50 y 200 - `weight` puede estar entre 1500 y 5000 -- .center[ .orange[Si no escalamos, weight puede mandar mucho más que horsepower.] ] --- # ¿Por qué escalar importa tanto? Porque k-NN no “entiende” por sí solo cuál variable debería pesar más. -- ### Si no escalas: - una variable con valores grandes domina la distancia, - aunque conceptualmente no sea la más importante. ### Si escalas: - pones las variables en una escala comparable, - y la cercanía se vuelve más justa. --- # Ejemplo conceptual Supón dos variables: - `horsepower` - `weight` y quieres comparar dos carros respecto a uno nuevo. -- Si uno difiere en: - 10 unidades de `horsepower` - y 300 unidades de `weight` sin escalar, el método puede sentir que el cambio en `weight` es muchísimo más importante, solo porque el número 300 es más grande. -- .center[ .orange[Por eso, en k-NN, escalar no es un lujo: es casi obligatorio.] ] --- # Regla práctica ### En k-NN, casi siempre debes: - centrar variables, - escalar variables, - y hacerlo usando solo la información del entrenamiento. -- .gray[ No se debe escalar con toda la base antes de dividir, porque eso mete información del test en el proceso. ] --- # Segundo elemento clave: el valor de \(k\) \(k\) es el número de vecinos que uso para predecir. -- ### Y ese número cambia mucho el comportamiento del modelo - \(k\) pequeño = modelo muy sensible a lo local - \(k\) grande = modelo más suave --- # ¿Qué pasa si \(k = 1\)? El modelo usa solo el vecino más cercano. -- ### Ventaja captura patrones muy locales. ### Problema puede ser demasiado sensible al ruido. -- .center[ .orange[Con \(k=1\), el modelo puede “memorizar” demasiado.] ] --- # ¿Qué pasa si \(k\) es muy grande? Si tomo demasiados vecinos: - la predicción se vuelve muy promediada, - pierde detalle local, - y puede suavizar demasiado la relación. -- ### Traducción simple el modelo deja de captar matices importantes. --- # El dilema de \(k\) ### \(k\) pequeño - menor sesgo - mayor varianza - más riesgo de sobreajuste ### \(k\) grande - mayor sesgo - menor varianza - más riesgo de subajuste -- .center[ .orange[Elegir \(k\) es un problema clásico de balance sesgo-varianza.] ] --- # Entonces, ¿cómo se elige \(k\)? No se escoge “a ojo”. -- ### Lo correcto es: probar distintos valores de \(k\) y compararlos con **validación cruzada**. -- ### Idea - pruebo `$$(k = 1, 2, 3, \dots)$$` - calculo error promedio por CV - me quedo con el valor de `\((k)\)` que mejor predice --- class: center, middle, inverse #
# k-NN en R ## ejemplo con la base Auto --- # Contexto del ejemplo Seguimos con la base `Auto` y con la lógica del curso: - ya tenemos `train` - ya tenemos `test` -- ### Para que el ejemplo sea claro, vamos a predecir `mpg` usando: - `horsepower` - `weight` --- # Ver rangos muy diferentes ```r train %>% select(horsepower, weight) %>% summary() ``` ``` ## horsepower weight ## Min. : 46.0 Min. :1613 ## 1st Qu.: 75.0 1st Qu.:2215 ## Median : 95.0 Median :2807 ## Mean :104.5 Mean :2968 ## 3rd Qu.:125.0 3rd Qu.:3620 ## Max. :230.0 Max. :5140 ``` -- ### Idea Aquí se ve clarísimo que `weight` y `horsepower` no están en la misma escala. Por eso no deberíamos aplicar k-NN sin estandarizar. --- # Cargar caret ```r library(caret) ``` -- ### ¿Por qué usar `caret`? Porque nos permite: - entrenar k-NN fácilmente, - hacer validación cruzada, - probar varios valores de `\((k)\)`, - y además incorporar el escalamiento. --- # Definir validación cruzada ```r set.seed(123) control_cv <- trainControl( method = "cv", number = 10 ) ``` -- ### Lectura - `method = "cv"` activa validación cruzada - `number = 10` indica 10 folds --- # Ajustar k-NN con varios valores de \(k\) ```r set.seed(123) modelo_knn <- train( mpg ~ horsepower + weight, data = train, method = "knn", trControl = control_cv, tuneGrid = data.frame(k = 1:25), preProcess = c("center", "scale"), metric = "RMSE" ) modelo_knn ``` ``` ## k-Nearest Neighbors ## ## 313 samples ## 2 predictor ## ## Pre-processing: centered (2), scaled (2) ## Resampling: Cross-Validated (10 fold) ## Summary of sample sizes: 282, 281, 280, 282, 281, 283, ... ## Resampling results across tuning parameters: ## ## k RMSE Rsquared MAE ## 1 5.429188 0.5952089 3.962203 ## 2 4.633199 0.6854968 3.393393 ## 3 4.356963 0.7231496 3.193570 ## 4 4.287206 0.7326906 3.153485 ## 5 4.147036 0.7468356 3.051524 ## 6 4.076712 0.7564598 3.006112 ## 7 4.098168 0.7538050 3.008589 ## 8 4.104290 0.7523001 3.012000 ## 9 4.102725 0.7525487 3.011009 ## 10 4.094261 0.7541968 3.019796 ## 11 4.036977 0.7602733 2.984338 ## 12 4.016921 0.7610244 2.954844 ## 13 3.994284 0.7624164 2.951047 ## 14 3.963282 0.7661304 2.922696 ## 15 3.975082 0.7644402 2.939765 ## 16 3.966511 0.7656920 2.940723 ## 17 3.956810 0.7663348 2.934685 ## 18 3.946464 0.7670500 2.925428 ## 19 3.927425 0.7690090 2.927169 ## 20 3.933044 0.7682313 2.928954 ## 21 3.927472 0.7687026 2.931426 ## 22 3.906329 0.7711939 2.909914 ## 23 3.885618 0.7734947 2.887043 ## 24 3.884709 0.7732590 2.890493 ## 25 3.879527 0.7737519 2.886353 ## ## RMSE was used to select the optimal model using the smallest value. ## The final value used for the model was k = 25. ``` --- ### Qué está pasando aquí - `method = "knn"` usa k-NN - `tuneGrid` prueba muchos valores de `\((k)\)` - `preProcess = c("center", "scale")` estandariza - `metric = "RMSE"` hace que comparemos por error de predicción --- # Ajustar k-NN con varios valores de \(k\) ``` ## k-Nearest Neighbors ## ## 313 samples ## 2 predictor ## ## Pre-processing: centered (2), scaled (2) ## Resampling: Cross-Validated (10 fold) ## Summary of sample sizes: 282, 281, 280, 282, 281, 283, ... ## Resampling results across tuning parameters: ## ## k RMSE Rsquared MAE ## 1 5.429188 0.5952089 3.962203 ## 2 4.633199 0.6854968 3.393393 ## 3 4.356963 0.7231496 3.193570 ## 4 4.287206 0.7326906 3.153485 ## 5 4.147036 0.7468356 3.051524 ## 6 4.076712 0.7564598 3.006112 ## 7 4.098168 0.7538050 3.008589 ## 8 4.104290 0.7523001 3.012000 ## 9 4.102725 0.7525487 3.011009 ## 10 4.094261 0.7541968 3.019796 ## 11 4.036977 0.7602733 2.984338 ## 12 4.016921 0.7610244 2.954844 ## 13 3.994284 0.7624164 2.951047 ## 14 3.963282 0.7661304 2.922696 ## 15 3.975082 0.7644402 2.939765 ## 16 3.966511 0.7656920 2.940723 ## 17 3.956810 0.7663348 2.934685 ## 18 3.946464 0.7670500 2.925428 ## 19 3.927425 0.7690090 2.927169 ## 20 3.933044 0.7682313 2.928954 ## 21 3.927472 0.7687026 2.931426 ## 22 3.906329 0.7711939 2.909914 ## 23 3.885618 0.7734947 2.887043 ## 24 3.884709 0.7732590 2.890493 ## 25 3.879527 0.7737519 2.886353 ## ## RMSE was used to select the optimal model using the smallest value. ## The final value used for the model was k = 25. ``` --- # ¿Qué devuelve el modelo? El objeto muestra, para cada valor de \(k\): - el RMSE promedio en validación cruzada, - el mejor valor encontrado, - y el modelo final asociado. -- .center[ .orange[El valor “ganador” de \(k\) es el que tuvo mejor desempeño promedio en CV.] ] --- # Ver resultados del tuning ```r modelo_knn$results ``` ``` ## k RMSE Rsquared MAE RMSESD RsquaredSD MAESD ## 1 1 5.429188 0.5952089 3.962203 0.9798625 0.11629253 0.6276689 ## 2 2 4.633199 0.6854968 3.393393 0.7358681 0.07259946 0.4542012 ## 3 3 4.356963 0.7231496 3.193570 0.6281341 0.05852560 0.4306981 ## 4 4 4.287206 0.7326906 3.153485 0.6761782 0.06742568 0.4830759 ## 5 5 4.147036 0.7468356 3.051524 0.6994823 0.06994104 0.5022906 ## 6 6 4.076712 0.7564598 3.006112 0.6171540 0.05939299 0.4626311 ## 7 7 4.098168 0.7538050 3.008589 0.6105490 0.05836110 0.4489594 ## 8 8 4.104290 0.7523001 3.012000 0.6236834 0.06255403 0.4848797 ## 9 9 4.102725 0.7525487 3.011009 0.5523699 0.05333250 0.4221378 ## 10 10 4.094261 0.7541968 3.019796 0.5538563 0.05490349 0.4387022 ## 11 11 4.036977 0.7602733 2.984338 0.5272457 0.05221558 0.4118864 ## 12 12 4.016921 0.7610244 2.954844 0.4646133 0.04887367 0.3703407 ## 13 13 3.994284 0.7624164 2.951047 0.4682482 0.04808194 0.3645242 ## 14 14 3.963282 0.7661304 2.922696 0.4726810 0.04762913 0.3836345 ## 15 15 3.975082 0.7644402 2.939765 0.4608589 0.04753852 0.3614476 ## 16 16 3.966511 0.7656920 2.940723 0.4666538 0.04785751 0.3626194 ## 17 17 3.956810 0.7663348 2.934685 0.4622764 0.04692856 0.3563964 ## 18 18 3.946464 0.7670500 2.925428 0.4674117 0.04699771 0.3550892 ## 19 19 3.927425 0.7690090 2.927169 0.4526749 0.04461173 0.3351959 ## 20 20 3.933044 0.7682313 2.928954 0.4713372 0.04458900 0.3572151 ## 21 21 3.927472 0.7687026 2.931426 0.4704213 0.04392743 0.3748488 ## 22 22 3.906329 0.7711939 2.909914 0.4667414 0.04296628 0.3721819 ## 23 23 3.885618 0.7734947 2.887043 0.4417683 0.03968874 0.3533143 ## 24 24 3.884709 0.7732590 2.890493 0.4578367 0.04140560 0.3644287 ## 25 25 3.879527 0.7737519 2.886353 0.4676291 0.04219224 0.3667476 ``` --- ### Cómo leer esta tabla Cada fila corresponde a un valor de `\((k)\)`. - `RMSE` más pequeño = mejor - `MAE` también ayuda - `Rsquared` puede verse, pero aquí priorizamos error predictivo --- # Gráfico del tuning ```r plot(modelo_knn) ``` <img src="Class_2_files/figure-html/unnamed-chunk-29-1.png" style="display: block; margin: auto;" /> --- # Gráfico del tuning ### Qué suele pasar - con `\((k)\)` muy pequeño, el error puede ser inestable; - luego mejora; - y si `\((k)\)` se hace demasiado grande, vuelve a empeorar. -- ### Ese gráfico ayuda a mostrar que .orange[ni muy pequeño ni exageradamente grande.] --- # Mejor valor de \(k\) ```r modelo_knn$bestTune ``` ``` ## k ## 25 25 ``` -- ### Interpretación Ese es el número de vecinos que, según la validación cruzada sobre `train`, ofreció el mejor balance predictivo. --- # Predicción en test Una vez elegido \(k\), evaluamos en el conjunto de test. ```r pred_knn <- predict(modelo_knn, newdata = test) tibble( RMSE_test = rmse_vec(test$mpg, pred_knn), MAE_test = mae_vec(test$mpg, pred_knn) ) ``` ``` ## # A tibble: 1 × 2 ## RMSE_test MAE_test ## <dbl> <dbl> ## 1 3.84 3.00 ``` -- ### Esto ya es evaluación fuera de muestra real El test no participó en la elección de \(k\). --- # Comparación con regresión lineal Ahora comparemos k-NN con un modelo lineal sencillo usando las mismas variables. ```r modelo_lm <- lm(mpg ~ horsepower + weight, data = train) pred_lm <- predict(modelo_lm, newdata = test) tibble( modelo = c("Regresión lineal", "k-NN"), RMSE = c( rmse_vec(test$mpg, pred_lm), rmse_vec(test$mpg, pred_knn) ), MAE = c( mae_vec(test$mpg, pred_lm), mae_vec(test$mpg, pred_knn) ) ) ``` ``` ## # A tibble: 2 × 3 ## modelo RMSE MAE ## <chr> <dbl> <dbl> ## 1 Regresión lineal 4.11 3.14 ## 2 k-NN 3.84 3.00 ``` --- # Mensaje metodológico No siempre gana el método más sofisticado. A veces un modelo lineal compite muy bien. .center[ ### Por eso hay que comparar con evidencia, no con intuición solamente.] --- # ¿Qué ventajas tiene k-NN? - Es muy intuitivo. - Puede capturar relaciones no lineales. - No obliga a imponer una forma funcional rígida. - Funciona bien cuando la cercanía entre casos sí contiene información útil. -- # ¿Qué debilidades tiene? - Depende mucho de la escala de las variables. - Puede volverse sensible al ruido si \(k\) es muy pequeño. - Puede perder estructura si \(k\) es muy grande. - No es tan interpretable como la regresión lineal. - Puede sufrir cuando hay muchas variables. -- .center[ .gray[En alta dimensión, la noción de “vecino cercano” empieza a deteriorarse.] ] --- # Advertencia importante k-NN puede parecer fácil, pero tiene una trampa: ### si no escalas bien, todo el método puede quedar mal planteado desde el inicio. -- Y otra trampa: ### si eliges \(k\) mirando directamente el test, contaminas la evaluación. --- # Qué deben llevarse de este tema - k-NN predice usando observaciones cercanas. - En regresión, la predicción suele ser el promedio de los vecinos. - La escala de las variables es crucial. - El valor de \(k\) controla la flexibilidad del modelo. - \(k\) se elige con validación cruzada. - Luego, el desempeño final se verifica en test. --- # Ejercicio sugerido Usando la base `Auto` y el conjunto `train`: 1. ajuste un modelo k-NN para predecir `mpg`, 2. use como predictores: - `horsepower` - `weight` - `displacement` 3. pruebe valores de \(k\) entre 1 y 30, 4. elija el mejor con validación cruzada, 5. evalúe el modelo final en `test`, 6. compárelo contra una regresión lineal con las mismas variables. --- class: center, middle # Cierre ## k-NN no aprende una ecuación: ## aprende quién se parece a quién. .center[ .orange[Y justamente por eso, la distancia y la escala lo son casi todo.] ] --- class: center, middle, inverse #
# k-NN y variables categóricas ## qué problema aparece y cómo resolverlo --- # Aquí aparece una diferencia importante con LM En regresión lineal, si una variable es categórica, por ejemplo: - tipo de crucero - ciudad - marca - segmento R normalmente la transforma internamente y el modelo la puede usar. -- ### En k-NN no pasa así de simple k-NN necesita medir **distancias** entre observaciones. Y ahí surge la pregunta clave: .center[ .orange[¿cómo mido distancia entre categorías?] ] --- # ¿Por qué es un problema? Supongamos una variable como: - `Cruise_line = A, B, C` Si yo la dejo como texto, k-NN no sabe calcular distancias con eso. -- Y si por error la convierto en números así: - A = 1 - B = 2 - C = 3 aparece otro problema. --- ### Porque el modelo empezaría a creer que: - B está “más cerca” de A que de C, - y que C está “más lejos” de A, aunque esas categorías no tengan un orden real. -- .center[ .orange[Eso sería una codificación engañosa.] ] --- # Lo que NO deberíamos hacer ### Para variables nominales No conviene asignar números arbitrarios como: - rojo = 1 - azul = 2 - verde = 3 si esas categorías no tienen jerarquía natural. -- ### ¿Por qué? Porque k-NN usa distancias numéricas, y entonces esa codificación mete una estructura falsa. -- ### Traducción simple El algoritmo empieza a ver una geometría que no existe. --- # Entonces, ¿qué se hace? Depende del tipo de variable categórica. ### Caso 1: categórica nominal No tiene orden natural. Ejemplos: - marca - ciudad - línea de crucero - color -- ### Tratamiento usual: convertirla en **variables dummy** o **one-hot encoding**. --- # ¿Qué es una variable dummy? Si tengo una variable: `Cruise_line = Carnival, Royal, Princess` puedo transformarla en columnas como: - `Cruise_line_Carnival` - `Cruise_line_Royal` - `Cruise_line_Princess` donde cada una toma 0 o 1. -- ### Ejemplo Si una observación pertenece a `Royal`, entonces: - Carnival = 0 - Royal = 1 - Princess = 0 -- .center[ .orange[Así la categoría se vuelve usable dentro de una distancia numérica.] ] --- # ¿Y si la variable es ordinal? Hay variables categóricas que sí tienen orden real. Por ejemplo: - bajo - medio - alto -- ### En ese caso sí puede tener sentido codificarlas respetando el orden, siempre que ese orden sea sustantivamente válido. -- ### Pero ojo aunque exista orden, no siempre es obvio que la distancia entre categorías sea uniforme. .gray[ Ir de bajo a medio no necesariamente “vale lo mismo” que ir de medio a alto. ] --- # Intuición de la distancia con dummies Supongamos dos barcos: ### Barco 1 - Carnival = 1 - Royal = 0 - Princess = 0 ### Barco 2 - Carnival = 0 - Royal = 1 - Princess = 0 --- # Intuición de la distancia con dummies .center[ Ahí la diferencia entre categorías sí puede entrar en la distancia, ### porque están representadas como patrones de 0 y 1. ] -- ### Pero esto trae una nueva advertencia: si meto muchas dummies, la distancia también puede cambiar bastante. --- # Advertencia importante Las variables categóricas con muchas categorías pueden complicar k-NN. Por ejemplo: - ciudad con 50 niveles, - marca con 80 niveles, - nombre de empresa con 200 niveles. -- ### ¿Qué pasa? Después de crear dummies: - aparecen muchísimas columnas, - la dimensión del problema crece, - y la noción de “vecino cercano” puede deteriorarse. -- .center[ .orange[No toda variable categórica conviene meterla automáticamente en k-NN.] ] --- # Recomendación práctica ### Para una primera implementación de k-NN: lo más limpio es empezar con **predictores numéricos**. -- ### Luego, como extensión: incorporar variables categóricas importantes mediante **dummies**. -- ### ¿Por qué así? Porque primero los estudiantes entienden: - distancia, - escalamiento, - elección de \(k\), y después sí añaden la complejidad de categorías. --- # Comparación rápida: LM vs k-NN con categóricas .pull-left[ ### LM - trabaja bien con factores, - R hace gran parte del trabajo, - la interpretación sigue siendo relativamente clara. ] .pull-right[ ### k-NN - necesita predictores numéricos, - las categorías suelen pasar por dummies, - la distancia puede cambiar bastante según la codificación. ] -- .center[ .gray[La misma variable categórica no se “siente” igual en ambos métodos.] ] --- class: center, middle, inverse #
# Ejemplo en R ## base de cruceros para k-NN con variables categóricas --- # Contexto del ejemplo Ahora vamos a trabajar con una base distinta, más útil para mostrar qué pasa cuando en k-NN aparecen variables categóricas. -- ### Idea del ejercicio Queremos predecir el tamaño de la tripulación de un crucero a partir de características del barco. --- ### Variable respuesta `crew` -- ### ¿Por qué esta base sirve mucho aquí? Porque mezcla: - variables numéricas, - y una variable categórica importante: `Cruise_line` --- # Cargar la base ```r library(tidyverse) cruise <- read_csv("Datos/cruise_ship_info.csv") %>% mutate( Cruise_line = factor(Cruise_line) ) ``` ``` ## Rows: 158 Columns: 9 ## ── Column specification ──────────────────────────────────────────────────────── ## Delimiter: "," ## chr (2): Ship_name, Cruise_line ## dbl (7): Age, Tonnage, passengers, length, cabins, passenger_density, crew ## ## ℹ Use `spec()` to retrieve the full column specification for this data. ## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message. ``` ```r glimpse(cruise) ``` ``` ## Rows: 158 ## Columns: 9 ## $ Ship_name <chr> "Journey", "Quest", "Celebration", "Conquest", "Dest… ## $ Cruise_line <fct> Azamara, Azamara, Carnival, Carnival, Carnival, Carn… ## $ Age <dbl> 6, 6, 26, 11, 17, 22, 15, 23, 19, 6, 10, 28, 18, 17,… ## $ Tonnage <dbl> 30.277, 30.277, 47.262, 110.000, 101.353, 70.367, 70… ## $ passengers <dbl> 6.94, 6.94, 14.86, 29.74, 26.42, 20.52, 20.52, 20.56… ## $ length <dbl> 5.94, 5.94, 7.22, 9.53, 8.92, 8.55, 8.55, 8.55, 8.55… ## $ cabins <dbl> 3.55, 3.55, 7.43, 14.88, 13.21, 10.20, 10.20, 10.22,… ## $ passenger_density <dbl> 42.64, 42.64, 31.80, 36.99, 38.36, 34.29, 34.29, 34.… ## $ crew <dbl> 3.55, 3.55, 6.70, 19.10, 10.00, 9.20, 9.20, 9.20, 9.… ``` --- ### Qué decir - La base está en la carpeta `Datos`. - Cada fila representa un crucero. - Cada columna describe alguna característica del barco. --- # Ver las primeras observaciones ```r head(cruise) ``` ``` ## # A tibble: 6 × 9 ## Ship_name Cruise_line Age Tonnage passengers length cabins passenger_density ## <chr> <fct> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> ## 1 Journey Azamara 6 30.3 6.94 5.94 3.55 42.6 ## 2 Quest Azamara 6 30.3 6.94 5.94 3.55 42.6 ## 3 Celebrat… Carnival 26 47.3 14.9 7.22 7.43 31.8 ## 4 Conquest Carnival 11 110 29.7 9.53 14.9 37.0 ## 5 Destiny Carnival 17 101. 26.4 8.92 13.2 38.4 ## 6 Ecstasy Carnival 22 70.4 20.5 8.55 10.2 34.3 ## # ℹ 1 more variable: crew <dbl> ``` -- ### Objetivo de este vistazo No es memorizar la tabla completa, sino reconocer qué tipo de variables tenemos y cuál queremos predecir. --- # ¿Qué significa cada variable? ### Variables principales de la base - `Ship_name`: nombre del barco - `Cruise_line`: línea o empresa del crucero - `Age`: antigüedad del barco - `Tonnage`: tonelaje del crucero - `passengers`: número de pasajeros - `length`: longitud del barco - `cabins`: número de cabinas - `passenger_density`: densidad de pasajeros - `crew`: número de tripulantes -- .center[ .orange[En este ejemplo, `crew` será la variable respuesta.] ] --- # ¿Cuáles son numéricas y cuáles categóricas? ### Categóricas - `Ship_name` - `Cruise_line` ### Numéricas - `Age` - `Tonnage` - `passengers` - `length` - `cabins` - `passenger_density` - `crew` -- ### Ojo con esto Para k-NN, esta distinción importa mucho, porque el método trabaja con distancias. --- # Nuestra pregunta de modelación .center[.font140[ ¿Cómo predecir `crew` usando información del crucero? ]] -- ### Y aquí aparece el punto clave Si usamos solo variables numéricas, el proceso es más directo. Pero si además queremos meter una variable como `Cruise_line`, tenemos que convertirla a una forma numérica usable por k-NN. -- .center[ .orange[Ahí es donde entran las dummies.] ] --- # Selección inicial de variables Para este ejemplo, vamos a trabajar con: ### Variable respuesta - `crew` ### Predictores numéricos - `Tonnage` - `passengers` - `length` - `cabins` ### Predictor categórico - `Cruise_line` --- ### ¿Por qué no usamos `Ship_name`? Porque tiene demasiados niveles y para una primera explicación complica mucho la distancia. .gray[ Pedagógicamente, `Cruise_line` es mejor para mostrar el tratamiento de categorías. ] --- # Crear una base de trabajo ```r datos_modelo <- cruise %>% select(crew, Tonnage, passengers, length, cabins, Cruise_line) glimpse(datos_modelo) ``` ``` ## Rows: 158 ## Columns: 6 ## $ crew <dbl> 3.55, 3.55, 6.70, 19.10, 10.00, 9.20, 9.20, 9.20, 9.20, 11… ## $ Tonnage <dbl> 30.277, 30.277, 47.262, 110.000, 101.353, 70.367, 70.367, … ## $ passengers <dbl> 6.94, 6.94, 14.86, 29.74, 26.42, 20.52, 20.52, 20.56, 20.5… ## $ length <dbl> 5.94, 5.94, 7.22, 9.53, 8.92, 8.55, 8.55, 8.55, 8.55, 9.51… ## $ cabins <dbl> 3.55, 3.55, 7.43, 14.88, 13.21, 10.20, 10.20, 10.22, 10.20… ## $ Cruise_line <fct> Azamara, Azamara, Carnival, Carnival, Carnival, Carnival, … ``` -- ### Qué hicimos aquí Nos quedamos solo con las variables que vamos a usar en el ejemplo, para que el flujo del modelo sea más claro. --- # Separar train y test ```r library(rsample) set.seed(123) split <- initial_split(datos_modelo, prop = 0.80) train <- training(split) test <- testing(split) dim(train) ``` ``` ## [1] 126 6 ``` ```r dim(test) ``` ``` ## [1] 32 6 ``` --- ### Importante Desde este momento: - `train` se usa para aprender y transformar, - `test` se guarda para el final. -- .center[ .orange[Las dummies y el escalamiento deben construirse a partir de `train`.] ] --- # Idea general del flujo Si tengo una variable respuesta continua, por ejemplo `crew`, y varios predictores, algunos numéricos y otros categóricos, el flujo para k-NN sería: 1. separar `train` y `test`, 2. crear dummies usando solo `train`, 3. aplicar esa misma transformación a `test`, 4. asegurar que `train` y `test` tengan las mismas columnas, 5. escalar usando solo `train`, 6. entrenar k-NN con validación cruzada. --- # Supongamos este escenario Tenemos en `train` una mezcla de variables como: - numéricas: `Tonnage`, `passengers`, `length`, `cabins` - categórica: `Cruise_line` Y queremos predecir `crew`. --- # Paso 1: separar predictores y respuesta ```r library(caret) library(yardstick) library(dplyr) x_train_raw <- train %>% select(-crew) x_test_raw <- test %>% select(-crew) y_train <- train$crew y_test <- test$crew ``` -- ### ¿Qué hicimos? - dejamos por un lado los predictores, - y por otro lado la variable respuesta. --- # Paso 2: crear dummies con train ```r dummies <- dummyVars(~ ., data = x_train_raw, fullRank = FALSE) x_train_dummy <- predict(dummies, newdata = x_train_raw) %>% as.data.frame() x_test_dummy <- predict(dummies, newdata = x_test_raw) %>% as.data.frame() ``` -- ### Qué hace esto Toma las variables categóricas y las convierte en columnas numéricas 0/1. ### Importante La transformación se aprende con `train` y luego se aplica a `test`. .orange[No se debe construir esto usando toda la base.] --- # Paso 3: asegurar mismas columnas en train y test ```r faltantes_en_test <- setdiff(names(x_train_dummy), names(x_test_dummy)) for (col in faltantes_en_test) { x_test_dummy[[col]] <- 0 } x_test_dummy <- x_test_dummy[, names(x_train_dummy), drop = FALSE] names(x_train_dummy) ``` ``` ## [1] "Tonnage" "passengers" ## [3] "length" "cabins" ## [5] "Cruise_line.Azamara" "Cruise_line.Carnival" ## [7] "Cruise_line.Celebrity" "Cruise_line.Costa" ## [9] "Cruise_line.Crystal" "Cruise_line.Cunard" ## [11] "Cruise_line.Disney" "Cruise_line.Holland_American" ## [13] "Cruise_line.MSC" "Cruise_line.Norwegian" ## [15] "Cruise_line.Oceania" "Cruise_line.Orient" ## [17] "Cruise_line.P&O" "Cruise_line.Princess" ## [19] "Cruise_line.Regent_Seven_Seas" "Cruise_line.Royal_Caribbean" ## [21] "Cruise_line.Seabourn" "Cruise_line.Silversea" ## [23] "Cruise_line.Star" "Cruise_line.Windstar" ``` ```r names(x_test_dummy) ``` ``` ## [1] "Tonnage" "passengers" ## [3] "length" "cabins" ## [5] "Cruise_line.Azamara" "Cruise_line.Carnival" ## [7] "Cruise_line.Celebrity" "Cruise_line.Costa" ## [9] "Cruise_line.Crystal" "Cruise_line.Cunard" ## [11] "Cruise_line.Disney" "Cruise_line.Holland_American" ## [13] "Cruise_line.MSC" "Cruise_line.Norwegian" ## [15] "Cruise_line.Oceania" "Cruise_line.Orient" ## [17] "Cruise_line.P&O" "Cruise_line.Princess" ## [19] "Cruise_line.Regent_Seven_Seas" "Cruise_line.Royal_Caribbean" ## [21] "Cruise_line.Seabourn" "Cruise_line.Silversea" ## [23] "Cruise_line.Star" "Cruise_line.Windstar" ``` --- ### ¿Por qué hacemos esto? Porque k-NN necesita que train y test tengan exactamente las mismas variables transformadas. A veces, después de crear dummies, alguna categoría no aparece en test y toca crear esa columna manualmente en cero. --- # Paso 4: centrar y escalar ```r pre_knn <- preProcess(x_train_dummy, method = c("center", "scale")) x_train_knn <- predict(pre_knn, x_train_dummy) x_test_knn <- predict(pre_knn, x_test_dummy) ``` -- ### Idea clave Después de crear dummies, seguimos teniendo el problema general de k-NN: la distancia depende de la escala. Por eso escalamos usando solo `train`. --- # Paso 5: ajustar k-NN con CV ```r set.seed(123) control_cv <- trainControl( method = "cv", number = 10 ) modelo_knn_cat <- train( x = x_train_knn, y = y_train, method = "knn", trControl = control_cv, tuneGrid = data.frame(k = 1:25), metric = "RMSE" ) modelo_knn_cat ``` ``` ## k-Nearest Neighbors ## ## 126 samples ## 24 predictor ## ## No pre-processing ## Resampling: Cross-Validated (10 fold) ## Summary of sample sizes: 112, 114, 114, 114, 114, 114, ... ## Resampling results across tuning parameters: ## ## k RMSE Rsquared MAE ## 1 1.157020 0.8822341 0.6497935 ## 2 1.302949 0.8591763 0.8498745 ## 3 1.459826 0.8322564 1.0110372 ## 4 1.562720 0.7958216 1.1546421 ## 5 1.713146 0.7526916 1.2091194 ## 6 1.699803 0.7759454 1.2527820 ## 7 1.791655 0.7563745 1.3562030 ## 8 1.855499 0.7381997 1.4280509 ## 9 1.880954 0.7470177 1.4600504 ## 10 1.896232 0.7452536 1.4757381 ## 11 1.971039 0.7311871 1.5548031 ## 12 1.994245 0.7461185 1.5914663 ## 13 1.993481 0.7572985 1.6021354 ## 14 1.986432 0.7614260 1.6009936 ## 15 1.944475 0.7862759 1.5839868 ## 16 1.950332 0.7849756 1.5853631 ## 17 1.921623 0.8112299 1.5465430 ## 18 1.936016 0.8185571 1.5522492 ## 19 1.919435 0.8300959 1.5261822 ## 20 1.941458 0.8369109 1.5366002 ## 21 1.986628 0.8354261 1.5750816 ## 22 2.004599 0.8370150 1.5825747 ## 23 2.006157 0.8435816 1.5876886 ## 24 2.056367 0.8340046 1.6224543 ## 25 2.095390 0.8296362 1.6412597 ## ## RMSE was used to select the optimal model using the smallest value. ## The final value used for the model was k = 1. ``` --- ### Qué está pasando aquí - el modelo ya recibe todo en formato numérico, - puede calcular distancias, - prueba varios valores de \(k\), - y elige el mejor con validación cruzada. --- # Ver resultados del tuning ```r modelo_knn_cat$results ``` ``` ## k RMSE Rsquared MAE RMSESD RsquaredSD MAESD ## 1 1 1.157020 0.8822341 0.6497935 0.6149182 0.09167395 0.3283064 ## 2 2 1.302949 0.8591763 0.8498745 0.5152669 0.07025798 0.2545297 ## 3 3 1.459826 0.8322564 1.0110372 0.6032374 0.09177235 0.3565983 ## 4 4 1.562720 0.7958216 1.1546421 0.6589850 0.11949770 0.3779979 ## 5 5 1.713146 0.7526916 1.2091194 0.7104014 0.14095801 0.4058160 ## 6 6 1.699803 0.7759454 1.2527820 0.5934526 0.09286670 0.3495369 ## 7 7 1.791655 0.7563745 1.3562030 0.6026452 0.10320489 0.3658043 ## 8 8 1.855499 0.7381997 1.4280509 0.6409223 0.12087927 0.4015788 ## 9 9 1.880954 0.7470177 1.4600504 0.6894324 0.15332953 0.4322862 ## 10 10 1.896232 0.7452536 1.4757381 0.7004926 0.16748810 0.4465344 ## 11 11 1.971039 0.7311871 1.5548031 0.6682906 0.17459415 0.4398146 ## 12 12 1.994245 0.7461185 1.5914663 0.6706709 0.17572566 0.4295166 ## 13 13 1.993481 0.7572985 1.6021354 0.6643012 0.17672216 0.4285221 ## 14 14 1.986432 0.7614260 1.6009936 0.6511601 0.16602175 0.4206705 ## 15 15 1.944475 0.7862759 1.5839868 0.6207471 0.15418766 0.4031635 ## 16 16 1.950332 0.7849756 1.5853631 0.6281410 0.15851715 0.4007538 ## 17 17 1.921623 0.8112299 1.5465430 0.6128142 0.14575651 0.3963329 ## 18 18 1.936016 0.8185571 1.5522492 0.6101094 0.15133830 0.4011319 ## 19 19 1.919435 0.8300959 1.5261822 0.6071822 0.14980215 0.3969748 ## 20 20 1.941458 0.8369109 1.5366002 0.5905246 0.14487931 0.4001845 ## 21 21 1.986628 0.8354261 1.5750816 0.5842178 0.14379194 0.4055154 ## 22 22 2.004599 0.8370150 1.5825747 0.5708111 0.13679943 0.3964767 ## 23 23 2.006157 0.8435816 1.5876886 0.5577053 0.12964470 0.3872558 ## 24 24 2.056367 0.8340046 1.6224543 0.5605742 0.14288728 0.3964666 ## 25 25 2.095390 0.8296362 1.6412597 0.5672315 0.13837459 0.4018035 ``` --- ### Cómo leer esta tabla Cada fila representa un valor distinto de \(k\). - `RMSE` más pequeño = mejor desempeño predictivo - `MAE` también ayuda a comparar - el mejor \(k\) será el que minimice el error promedio en CV --- # Mejor valor de \(k\) ```r modelo_knn_cat$bestTune ``` ``` ## k ## 1 1 ``` -- ### Interpretación Ese es el número de vecinos que mejor funcionó según la validación cruzada en `train`. --- # Gráfico del tuning ```r plot(modelo_knn_cat) ``` <img src="Class_2_files/figure-html/unnamed-chunk-44-1.png" style="display: block; margin: auto;" /> --- # Paso 6: evaluar en test ```r pred_knn_cat <- predict(modelo_knn_cat, newdata = x_test_knn) tibble( RMSE_test = rmse_vec(y_test, pred_knn_cat), MAE_test = mae_vec(y_test, pred_knn_cat) ) ``` ``` ## # A tibble: 1 × 2 ## RMSE_test MAE_test ## <dbl> <dbl> ## 1 2.31 1.28 ``` -- ### Ahora sí La evaluación final se hace en datos no vistos, después de haber hecho correctamente: - dummies, - alineación de columnas, - escalamiento, - tuning con CV. --- # Qué deben llevarse de esta parte - k-NN trabaja con distancias. - Las variables categóricas no entran naturalmente como texto. - No conviene asignar números arbitrarios a categorías nominales. - Lo usual es transformarlas en **dummies**. - Esa transformación debe aprenderse con `train`. - Luego se verifica que `train` y `test` tengan las mismas columnas. - Después se escala y se ajusta el modelo. --- class: center, middle, inverse # Cierre ## En k-NN, una variable categórica no es un detalle menor. ## Cambia la forma en que construimos la distancia. .center[ .orange[Y si la distancia cambia, cambia todo el modelo.] ] --- # Ejercicio en clase: comparar LM vs k-NN Trabajen con la base `cruise_ship_info.csv` para predecir la variable `crew`. ### Objetivo Construir y comparar: - un modelo de **regresión lineal múltiple** - un modelo **k-NN para regresión** usando la misma base de datos. -- ### Instrucciones 1. Carguen la base desde la carpeta `Datos`. 2. Seleccionen estas variables: - `crew` - `Tonnage` - `passengers` - `length` - `cabins` - `Cruise_line` 3. Hagan una partición **train/test**. --- ### Parte A. Modelo lineal 4. Ajusten un modelo de regresión lineal múltiple para predecir `crew`. 5. Obtengan predicciones sobre `test`. 6. Calculen **RMSE** y **MAE** para el modelo lineal. -- ### Parte B. Modelo k-NN 7. Separen predictores y variable respuesta. 8. Transformen `Cruise_line` en **dummies** usando solo `train`. 9. Apliquen esa misma transformación a `test`. 10. Verifiquen que `train` y `test` tengan exactamente las mismas columnas. 11. Centren y escalen los predictores usando solo `train`. 12. Ajusten un modelo **k-NN** con validación cruzada. 13. Prueben varios valores de \(k\) y seleccionen el mejor. 14. Evalúen el modelo final en `test` con **RMSE** y **MAE**. --- ### Preguntas para discutir - ¿Cuál modelo predijo mejor? - ¿Cuál fue más fácil de interpretar? - ¿Por qué el tratamiento de `Cruise_line` fue distinto en LM y en k-NN? - ¿Por qué el escalamiento era clave para k-NN pero no necesariamente para LM? --- class: inverse, center, middle background-color: #122140 .pull-left[ .center[ <br><br> # Gracias!!! <br> ### ¿Preguntas? <br> <img src="img/qr-code.png" width="49%" style="display: block; margin: auto;" /> ] ] .pull-right[ <br> <br> <img style="border-radius: 50%;" src="img/avatar.png" width="150px" /> ### [www.joaquibarandica.com](https://www.joaquibarandica.com)
orlando.joaqui@correounivalle.edu.co <img src="img/Logo.jpg" width="120%"> ] <br><br><br>