AutoEl objetivo es modelar la relación entre horsepower y
mpg comparando tres paradigmas: bases de
funciones, regresión local y polinomio
global de grado 2. La comparación se realiza mediante 10
repeticiones de validación cruzada.
data(Auto)
hp <- Auto$horsepower
mpg <- Auto$mpg
n <- nrow(Auto)
cat("Observaciones totales :", n, "\n")## Observaciones totales : 392
## Rango de horsepower : 46 230
## Rango de mpg : 9 46.6
ggplot(Auto, aes(x = horsepower, y = mpg)) +
geom_point(alpha = 0.45, color = "#2C7BB6", size = 1.8) +
geom_smooth(method = "loess", se = TRUE,
color = "#D7191C", fill = "#FDAE61", alpha = 0.25) +
labs(
title = "Relación entre Horsepower y MPG",
subtitle = "Dataset Auto — ISLR2 | n = 392 vehículos",
x = "Horsepower", y = "Miles per Gallon (mpg)"
) +
theme_minimal(base_size = 13)# Error Cuadrático Medio
ecm <- function(y_real, y_pred) {
mean((y_real - y_pred)^2, na.rm = TRUE)
}Para obtener distribuciones robustas del ECM de prueba (punto 6), los puntos (1)–(5) se ejecutan 10 veces, cada una con una partición aleatoria distinta.
# ── Almacenamiento de resultados ─────────────────────────────────────────────
resultados <- data.frame(
repeticion = integer(),
paradigma = character(),
ECM_prueba = numeric(),
stringsAsFactors = FALSE
)
resumen_rep <- data.frame(
rep = 1:10,
k_optimo = NA_integer_,
mejor_basis = NA_character_,
mejor_local = NA_integer_,
stringsAsFactors = FALSE
)
# ── Loop principal ────────────────────────────────────────────────────────────
for (rep in 1:10) {
set.seed(rep * 42) # Semilla reproducible distinta por iteración
# ── PUNTO (1): Separación 90% / 10% ─────────────────────────────────────
idx_test <- sample(seq_len(n), size = round(0.10 * n))
idx_train <- setdiff(seq_len(n), idx_test)
df_train <- data.frame(hp = hp[idx_train], mpg = mpg[idx_train])
df_test <- data.frame(hp = hp[idx_test], mpg = mpg[idx_test])
n_train <- nrow(df_train)
# Nodos de frontera (5 unidades fuera del rango → ajuste estable)
boundary <- range(df_train$hp) + c(-5, 5)
# Asignación aleatoria de folds
folds <- sample(1:10, n_train, replace = TRUE)
# ── PUNTO (2): Knots óptimos — CV-10 sobre k = 1, …, 10 ─────────────────
ecm_por_knot <- numeric(10)
for (k in 1:10) {
knot_pos <- seq(min(df_train$hp), max(df_train$hp),
length.out = k + 2)[2:(k + 1)]
errores_folds <- numeric(10)
for (fold in 1:10) {
idx_val <- which(folds == fold)
idx_tr <- which(folds != fold)
if (length(idx_val) == 0) next
X_tr <- bs(df_train$hp[idx_tr], knots = knot_pos,
Boundary.knots = boundary)
X_val <- bs(df_train$hp[idx_val], knots = knot_pos,
Boundary.knots = boundary)
fit <- lm(df_train$mpg[idx_tr] ~ X_tr)
pred <- cbind(1, X_val) %*% coef(fit)
errores_folds[fold] <- ecm(df_train$mpg[idx_val], pred)
}
ecm_por_knot[k] <- mean(errores_folds, na.rm = TRUE)
}
k_optimo <- which.min(ecm_por_knot)
knot_opt <- seq(min(df_train$hp), max(df_train$hp),
length.out = k_optimo + 2)[2:(k_optimo + 1)]
resumen_rep$k_optimo[rep] <- k_optimo
# ── PUNTO (3): Comparar modelos de bases — CV-10 ──────────────────────────
ecm_p2 <- ecm_ss <- ecm_rs <- numeric(10)
for (fold in 1:10) {
idx_val <- which(folds == fold)
idx_tr <- which(folds != fold)
if (length(idx_val) == 0) next
df_tr <- df_train[idx_tr, ]
df_val <- df_train[idx_val, ]
# Polinomio global grado 2
fit_p2 <- lm(mpg ~ poly(hp, 2), data = df_tr)
ecm_p2[fold] <- ecm(df_val$mpg, predict(fit_p2, newdata = df_val))
# Smoothing spline (GCV automático)
fit_ss <- smooth.spline(df_tr$hp, df_tr$mpg, cv = FALSE)
ecm_ss[fold] <- ecm(df_val$mpg, predict(fit_ss, x = df_val$hp)$y)
# Regression spline óptimo
X_tr <- bs(df_tr$hp, knots = knot_opt, Boundary.knots = boundary)
X_val <- bs(df_val$hp, knots = knot_opt, Boundary.knots = boundary)
fit_rs <- lm(df_tr$mpg ~ X_tr)
pred_rs <- cbind(1, X_val) %*% coef(fit_rs)
ecm_rs[fold] <- ecm(df_val$mpg, pred_rs)
}
ecm_cv_basis <- c(
`Polinomio grado 2` = mean(ecm_p2, na.rm = TRUE),
`Smoothing spline` = mean(ecm_ss, na.rm = TRUE),
`Regression spline` = mean(ecm_rs, na.rm = TRUE)
)
mejor_basis <- names(which.min(ecm_cv_basis))
resumen_rep$mejor_basis[rep] <- mejor_basis
# ── PUNTO (4): Regresión local — loess grado 1 vs grado 2 ────────────────
ecm_l1 <- ecm_l2 <- numeric(10)
for (fold in 1:10) {
idx_val <- which(folds == fold)
idx_tr <- which(folds != fold)
if (length(idx_val) == 0) next
df_tr <- df_train[idx_tr, ]
df_val <- df_train[idx_val, ]
fit_l1 <- loess(mpg ~ hp, data = df_tr, degree = 1)
ecm_l1[fold] <- ecm(df_val$mpg, predict(fit_l1, newdata = df_val))
fit_l2 <- loess(mpg ~ hp, data = df_tr, degree = 2)
ecm_l2[fold] <- ecm(df_val$mpg, predict(fit_l2, newdata = df_val))
}
mejor_local <- which.min(c(
Degree1 = mean(ecm_l1, na.rm = TRUE),
Degree2 = mean(ecm_l2, na.rm = TRUE)
))
resumen_rep$mejor_local[rep] <- mejor_local
# ── PUNTO (5): ECM de PRUEBA para los 3 paradigmas ───────────────────────
# Paradigma 1: Mejor modelo de bases
if (mejor_basis == "Polinomio grado 2") {
fit_b <- lm(mpg ~ poly(hp, 2), data = df_train)
pred_b <- predict(fit_b, newdata = df_test)
} else if (mejor_basis == "Smoothing spline") {
fit_b <- smooth.spline(df_train$hp, df_train$mpg, cv = FALSE)
pred_b <- predict(fit_b, x = df_test$hp)$y
} else {
X_full <- bs(df_train$hp, knots = knot_opt, Boundary.knots = boundary)
X_tst <- bs(df_test$hp, knots = knot_opt, Boundary.knots = boundary)
fit_b <- lm(df_train$mpg ~ X_full)
pred_b <- cbind(1, X_tst) %*% coef(fit_b)
}
ecm_test_basis <- ecm(df_test$mpg, pred_b)
# Paradigma 2: Mejor regresión local
fit_l <- loess(mpg ~ hp, data = df_train, degree = mejor_local)
ecm_test_local <- ecm(df_test$mpg, predict(fit_l, newdata = df_test))
# Paradigma 3: Polinomio global grado 2
fit_pg <- lm(mpg ~ poly(hp, 2), data = df_train)
ecm_test_poly2 <- ecm(df_test$mpg, predict(fit_pg, newdata = df_test))
resultados <- rbind(resultados, data.frame(
repeticion = rep,
paradigma = c("Bases de funciones",
"Regresión local",
"Polinomio global grado 2"),
ECM_prueba = c(ecm_test_basis, ecm_test_local, ecm_test_poly2),
stringsAsFactors = FALSE
))
}
cat("✔ Loop de 10 repeticiones completado.\n")## ✔ Loop de 10 repeticiones completado.
En cada una de las 10 repeticiones se usó una semilla
rep × 42 distinta para garantizar reproducibilidad. El 10 %
de los 392 vehículos (≈39) quedó como conjunto de prueba y el 90 %
restante (≈353) como conjunto de entrenamiento.
## Número óptimo de knots por repetición:
knitr::kable(resumen_rep[, c("rep", "k_optimo")],
col.names = c("Repetición", "k óptimo"),
align = "cc")| Repetición | k óptimo |
|---|---|
| 1 | 4 |
| 2 | 4 |
| 3 | 4 |
| 4 | 4 |
| 5 | 6 |
| 6 | 3 |
| 7 | 4 |
| 8 | 4 |
| 9 | 4 |
| 10 | 4 |
ggplot(resumen_rep, aes(x = factor(rep), y = k_optimo)) +
geom_col(fill = "#2C7BB6", alpha = 0.85, width = 0.6) +
geom_hline(yintercept = median(resumen_rep$k_optimo),
linetype = "dashed", color = "#D7191C", linewidth = 0.8) +
annotate("text", x = 10.5, y = median(resumen_rep$k_optimo) + 0.3,
label = paste("Mediana =", median(resumen_rep$k_optimo)),
color = "#D7191C", size = 3.5) +
scale_y_continuous(breaks = 1:10) +
labs(title = "Número óptimo de knots por repetición (CV-10)",
x = "Repetición", y = "k óptimo") +
theme_minimal(base_size = 13)Interpretación: El número de knots que minimiza el
ECM de validación cruzada indica la complejidad óptima del
regression spline. Un k mayor aumenta la
flexibilidad del ajuste, pero puede llevar a sobreajuste.
## Mejor modelo de bases por repetición:
knitr::kable(resumen_rep[, c("rep", "k_optimo", "mejor_basis")],
col.names = c("Repetición", "k óptimo", "Mejor modelo"),
align = "ccl")| Repetición | k óptimo | Mejor modelo |
|---|---|---|
| 1 | 4 | Regression spline |
| 2 | 4 | Regression spline |
| 3 | 4 | Regression spline |
| 4 | 4 | Regression spline |
| 5 | 6 | Smoothing spline |
| 6 | 3 | Regression spline |
| 7 | 4 | Regression spline |
| 8 | 4 | Smoothing spline |
| 9 | 4 | Regression spline |
| 10 | 4 | Regression spline |
##
## Frequencia de selección por modelo:
##
## Regression spline Smoothing spline
## 8 2
Interpretación: Se comparan tres modelos mediante
CV-10. El smoothing spline usualmente tiene ventaja
porque su parámetro de suavizado se optimiza automáticamente mediante
GCV, mientras que el regression spline usa el k
encontrado en el punto anterior.
## Grado de loess óptimo por repetición:
knitr::kable(
data.frame(
Repetición = resumen_rep$rep,
`Grado óptimo` = ifelse(resumen_rep$mejor_local == 1,
"Grado 1 (localmente lineal)",
"Grado 2 (localmente cuadrático)")
),
align = "cl"
)| Repetición | Grado.óptimo |
|---|---|
| 1 | Grado 2 (localmente cuadrático) |
| 2 | Grado 2 (localmente cuadrático) |
| 3 | Grado 2 (localmente cuadrático) |
| 4 | Grado 2 (localmente cuadrático) |
| 5 | Grado 2 (localmente cuadrático) |
| 6 | Grado 2 (localmente cuadrático) |
| 7 | Grado 2 (localmente cuadrático) |
| 8 | Grado 2 (localmente cuadrático) |
| 9 | Grado 2 (localmente cuadrático) |
| 10 | Grado 1 (localmente lineal) |
Interpretación: loess con grado 1
ajusta localmente una recta; con grado 2 ajusta una parábola. El ancho
de banda óptimo es el que provee loess() por defecto. El
grado que produce menor ECM de CV-10 es el seleccionado.
resumen_ecm <- resultados %>%
group_by(paradigma) %>%
summarise(
Media = round(mean(ECM_prueba), 3),
Mediana = round(median(ECM_prueba), 3),
DE = round(sd(ECM_prueba), 3),
Mínimo = round(min(ECM_prueba), 3),
Máximo = round(max(ECM_prueba), 3),
.groups = "drop"
)
knitr::kable(resumen_ecm,
caption = "Resumen del ECM de prueba por paradigma (10 repeticiones)")| paradigma | Media | Mediana | DE | Mínimo | Máximo |
|---|---|---|---|---|---|
| Bases de funciones | 18.252 | 15.831 | 6.874 | 11.915 | 33.876 |
| Polinomio global grado 2 | 18.124 | 15.773 | 6.213 | 12.261 | 33.363 |
| Regresión local | 17.968 | 15.273 | 6.732 | 12.274 | 34.462 |
paleta <- c(
"Bases de funciones" = "#2C7BB6",
"Regresión local" = "#1A9641",
"Polinomio global grado 2" = "#D7191C"
)
ggplot(resultados, aes(x = paradigma, y = ECM_prueba, fill = paradigma)) +
geom_boxplot(alpha = 0.7, outlier.shape = 21, outlier.size = 2.5,
width = 0.5) +
geom_jitter(aes(color = paradigma), width = 0.12, alpha = 0.9,
size = 2.5) +
scale_fill_manual(values = paleta) +
scale_color_manual(values = paleta) +
labs(
title = "Distribución del ECM de prueba por paradigma",
subtitle = "10 repeticiones con partición aleatoria 90/10",
x = NULL,
y = "ECM de prueba"
) +
theme_minimal(base_size = 13) +
theme(legend.position = "none",
axis.text.x = element_text(size = 11))ggplot(resultados, aes(x = repeticion, y = ECM_prueba,
color = paradigma, group = paradigma)) +
geom_line(linewidth = 1.1, alpha = 0.9) +
geom_point(size = 3) +
scale_color_manual(values = paleta) +
scale_x_continuous(breaks = 1:10) +
labs(
title = "Evolución del ECM de prueba a lo largo de las 10 repeticiones",
x = "Repetición",
y = "ECM de prueba",
color = "Paradigma"
) +
theme_minimal(base_size = 13) +
theme(legend.position = "bottom")ganador <- resumen_ecm$paradigma[which.min(resumen_ecm$Media)]
cat("Paradigma con menor ECM promedio de prueba:", ganador, "\n")## Paradigma con menor ECM promedio de prueba: Regresión local
## ECM promedio : 17.968
Respuesta al punto (6): Con base en las distribuciones del ECM de prueba obtenidas en las 10 repeticiones, el paradigma de Regresión local produce el menor error de predicción promedio. Dado que las distribuciones de los tres paradigmas se superponen parcialmente, se recomienda también considerar la variabilidad (desviación estándar) al seleccionar el modelo final.
Para la \(i\)-ésima unidad estadística se tienen \(n_i\) observaciones \(\{(t_{ij},\, x_{ij})\}_{j=1}^{n_i}\), donde \(t_{ij} \in \mathcal{T} \subset \mathbb{R}\) es el punto de evaluación y \(x_{ij} \in \mathbb{R}\) es el valor observado. Sea \(K_h(u) = \frac{1}{h}K\!\left(\frac{u}{h}\right)\) un kernel simétrico con ancho de banda \(h > 0\).
El estimador de Nadaraya–Watson para la función suavizada \(\hat{x}_i(t)\) de la \(i\)-ésima unidad estadística en el punto \(t\) es:
\[ \hat{x}_i(t) \;=\; \frac{\displaystyle\sum_{j=1}^{n_i} K_h\!\left(t - t_{ij}\right)\, x_{ij}} {\displaystyle\sum_{j=1}^{n_i} K_h\!\left(t - t_{ij}\right)} \]
Interpretación: \(\hat{x}_i(t)\) es un promedio ponderado de las observaciones \(x_{ij}\) pertenecientes a la unidad \(i\). El peso de cada observación es proporcional al kernel evaluado en la distancia \(|t - t_{ij}|\): observaciones con \(t_{ij}\) cercano a \(t\) reciben mayor peso, logrando suavizar localmente la curva.
Extendiendo el estimador anterior a todas las \(n\) unidades estadísticas y sus observaciones, el estimador de Nadaraya–Watson para la función media en \(t\) es:
\[ \hat{\mu}(t) \;=\; \frac{\displaystyle\sum_{i=1}^{n} \sum_{j=1}^{n_i} K_h\!\left(t - t_{ij}\right)\, x_{ij}} {\displaystyle\sum_{i=1}^{n} \sum_{j=1}^{n_i} K_h\!\left(t - t_{ij}\right)} \]
Interpretación: Este estimador utiliza todos los datos discretizados de todas las \(n\) unidades de manera conjunta. En cada punto \(t\), las observaciones \(x_{ij}\) cuyo \(t_{ij}\) es cercano a \(t\) reciben mayor peso, independientemente del individuo \(i\) al que pertenezcan. De esta forma, la estimación de \(\mu(t)\) aprovecha toda la información disponible de la muestra funcional.
Caso particular: Si \(n_i = m\) para todo \(i\) y los puntos de evaluación son comunes (\(t_{ij} = t_j\)), el estimador se simplifica a:
\[ \hat{\mu}(t) \;=\; \frac{\displaystyle\sum_{j=1}^{m} K_h\!\left(t - t_j\right)\,\bar{x}_j} {\displaystyle\sum_{j=1}^{m} K_h\!\left(t - t_j\right)}, \qquad \bar{x}_j = \frac{1}{n}\sum_{i=1}^{n} x_{ij} \]
lo que resalta su interpretación como un suavizado de los promedios muestrales punto a punto: \(\hat{\mu}(t)\) es el estimador de Nadaraya–Watson aplicado a los promedios \(\bar{x}_j\) evaluados en los puntos \(t_j\).
Fin del Taller 2