ARDL, VECM y SARIMAX
Universidad Nacional de San Antonio Abad del Cusco
Los receptores GNSS registran cada semana cómo se desplaza la corteza terrestre en tres ejes:
Se aplico el modelo SAITS para la impujtacion de datos de series temporales debiso que mostró un mejor ajuste en cuanto a los factores multivariados
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from pypots.imputation import SAITS
df = pd.read_csv('Libro1 - copia.csv', sep=';')
# Extraer fechas y convertirlas a datetime
fechas = pd.to_datetime(df['fecha'], format='%d/%m/%Y')
datos_numericos = df.drop(columns=['fecha'])
scaler = StandardScaler()
datos_escalados = scaler.fit_transform(datos_numericos)
n_pasos = datos_escalados.shape[0]
n_variables = datos_escalados.shape[1]
datos_3d = datos_escalados.reshape(1, n_pasos, n_variables)
saits_modelo = SAITS(
n_steps=n_pasos,
n_features=n_variables,
n_layers=1,
d_model=64,
d_ffn=64,
n_heads=4,
d_k=64,
d_v=64,
dropout=0.2,
epochs=150
)
dataset_para_saits = {"X": datos_3d}
saits_modelo.fit(dataset_para_saits)
resultado_imputacion = saits_modelo.impute(dataset_para_saits)
datos_imputados_escalados = resultado_imputacion.reshape(n_pasos, n_variables)
datos_finales = scaler.inverse_transform(datos_imputados_escalados)
df_imputado = pd.DataFrame(datos_finales, columns=datos_numericos.columns)
df_imputado.insert(0, 'fecha', fechas)
fig, axes = plt.subplots(3, 1, figsize=(12, 10), sharex=True)
axes[0].plot(df_imputado['fecha'], df_imputado['north'], color='royalblue', linewidth=1.5, marker='o', markersize=3)
axes[0].set_title('Series de Tiempo GPS (Imputación Deep Learning: SAITS)', fontsize=14, fontweight='bold')
axes[0].set_ylabel('North (m)', fontsize=12)
axes[0].grid(True, linestyle='--', alpha=0.6)
axes[1].plot(df_imputado['fecha'], df_imputado['east'], color='crimson', linewidth=1.5, marker='o', markersize=3)
axes[1].set_ylabel('East (m)', fontsize=12)
axes[1].grid(True, linestyle='--', alpha=0.6)
axes[2].plot(df_imputado['fecha'], df_imputado['height'], color='forestgreen', linewidth=1.5, marker='o', markersize=3)
axes[2].set_ylabel('Height (m)', fontsize=12)
axes[2].set_xlabel('Fecha', fontsize=12)
axes[2].grid(True, linestyle='--', alpha=0.6)
plt.tight_layout()
plt.show()Dado que los datos presentan una periodicidad semanal, elegir el porcentaje de división es importante para evaluar la capacidad predictiva de los modelos, es por ello que se optó por dividir la data de la siguiente manera:
library(tidyverse)
library(urca)
gnss <- read.csv("GNSS_NEU.csv", sep=";") # Datos
gnss$fecha <- as.Date(gnss$fecha)
ts_datos <- gnss %>% dplyr::select(north, east, height) %>% as.data.frame()
n_total <- nrow(ts_datos)
n_train <- floor(0.85 * n_total)
train_data <- ts_datos[1:n_train, ]
test_data <- ts_datos[(n_train + 1):n_total, ]
fecha_train <- gnss$fecha[1:n_train]
fecha_test <- gnss$fecha[(n_train + 1):n_total]
cat("Tamaño del set de Entrenamiento (Train):", nrow(train_data), "observaciones\n")Tamaño del set de Entrenamiento (Train): 326 observaciones
Tamaño del set de Prueba (Test): 58 observaciones
par(mfrow = c(3, 2), mar = c(3, 5, 3, 1))
variables <- c("north", "east", "height")
for (i in 1:3) {
var <- variables[i]
acf(train_data[[var]], lag.max = 50, main = "", col = "darkblue")
if (i == 1) title(main = "ACF", cex.main = 1.5)
mtext(toupper(var), side = 2, line = 3, font = 2, cex = 1.1)
pacf(train_data[[var]], lag.max = 50, main = "", col = "darkred")
if (i == 1) title(main = "PACF", cex.main = 1.5)
}\[H_0: \text{No es estacionario}\]
A un 5%:
Resultados de Estacionariedad (Prueba ADF)
La prueba de cointegración de Johansen (base de los modelos VECM) exige de manera estricta que todas las variables compartan el mismo orden de integración, específicamente \(I(1)\). Dado que las coordenadas GNSS presentan órdenes mixtos, el enfoque ARDL (Autoregresivo de Rezagos Distribuidos) se establece como la metodología teóricamente correcta, ya que permite modelar relaciones a largo plazo combinando variables \(I(0)\) e \(I(1)\) sin perder información espacial.
El ARDL (Autoregresivo de Rezagos Distribuidos) es un modelo econométrico avanzado que permite estimar la dinámica temporal entre variables. A diferencia de los modelos clásicos, el ARDL se destaca por dos ventajas fundamentales:
library(ARDL)
library(forecast)
# 1. Aislar la estacionalidad (Ciclo anual GNSS)
ts_referencia_train <- ts(train_data$height, frequency = 52)
terminos_fourier_train <- fourier(ts_referencia_train, K = 1)
train_data$sin_anual <- terminos_fourier_train[, 1]
train_data$cos_anual <- terminos_fourier_train[, 2]# 1. Crear los términos de Fourier
ts_referencia_train <- ts(train_data$height, frequency = 52)
terminos_fourier_train <- fourier(ts_referencia_train, K = 1)
# 2. Ajustar el modelo estacional para capturar la "Onda" a escala
modelo_estacional <- lm(train_data$height ~ terminos_fourier_train[, 1] + terminos_fourier_train[, 2])
train_data$onda_fourier <- fitted(modelo_estacional)
if (!"fecha" %in% colnames(train_data)) {
train_data$fecha <- seq(as.Date("2018-07-25"), by = "7 days", length.out = nrow(train_data))
}
ggplot(train_data, aes(x = fecha)) +
# Datos originales de fondo (tenues)
geom_line(aes(y = height, color = "Deformación Bruta"), linewidth = 0.6, alpha = 0.4) +
# La onda perfecta al frente (gruesa y llamativa)
geom_line(aes(y = onda_fourier, color = "Ciclo Anual (Fourier K=1)"), linewidth = 1.2) +
scale_color_manual(values = c("Deformación Bruta" = "#2c3e50", "Ciclo Anual (Fourier K=1)" = "#e74c3c")) +
theme_bw() +
labs(
title = "Aislamiento de la Estacionalidad (Filtro Físico)",
subtitle = "Extracción de la carga hidrológica anual mediante Serie de Fourier",
y = "Coordenada Vertical (m)",
x = "Fecha de Entrenamiento",
color = ""
) +
theme(
legend.position = "bottom",
plot.title = element_text(face = "bold", size = 14),
plot.subtitle = element_text(face = "italic", color = "gray30"),
legend.text = element_text(face = "bold", size = 11)
)Las variables de Fourier sin_anual y cos_anual están después de la barra | porque son variables “fijas” o deterministas, el modelo no les busca rezagos del pasado.
Time series regression with "ts" data:
Start = 3, End = 326
Call:
dynlm::dynlm(formula = full_formula, data = data, start = start,
end = end)
Residuals:
Min 1Q Median 3Q Max
-0.008511 -0.001981 -0.000116 0.001847 0.008385
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.0002729 0.0003797 -0.719 0.4729
L(height, 1) 0.6305792 0.0525071 12.009 < 2e-16 ***
L(height, 2) 0.2362290 0.0529271 4.463 1.12e-05 ***
east 0.3086212 0.0957503 3.223 0.0014 **
L(east, 1) -0.4805682 0.0953716 -5.039 7.89e-07 ***
north 0.0292012 0.0231634 1.261 0.2084
sin_anual 0.0006782 0.0003234 2.097 0.0368 *
cos_anual 0.0009689 0.0002411 4.019 7.32e-05 ***
---
Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Residual standard error: 0.002934 on 316 degrees of freedom
Multiple R-squared: 0.8847, Adjusted R-squared: 0.8821
F-statistic: 346.3 on 7 and 316 DF, p-value: < 2.2e-16
Bounds F-test (Wald) for no cointegration
data: d(height) ~ L(height, 1) + L(east, 1) + north + d(L(height, 1)) + d(east) + sin_anual + cos_anual
F = 9.7808, p-value = 0.0004708
alternative hypothesis: Possible cointegration
null values:
k T
2 1000
La prueba de límites (Bounds Test) de Pesaran arrojó un estadístico \(F = 9.78\), siendo altísimamente significativo (\(p < 0.001\)). Esto rechaza de manera contundente la hipótesis de no-cointegración.
Esto confirma que la Altura y el desplazamiento horizontal (Este) no se mueven de manera aislada o aleatoria. Existe una fuerza que los “amarra” en el tiempo manteniendo un equilibrio a largo plazo (lo cual valida perfectamente la física del bloque tectónico sobre el que está Wanchaq).
Al existir cointegración comprobada, el modelo ARDL queda blindado. Se garantiza al jurado que la relación encontrada representa un fenómeno geofísico real y no una simple coincidencia estadística temporal.
Errores Robustos (HAC): Para garantizar la máxima rigurosidad, los multiplicadores a largo plazo y el modelo de corrección de errores (ECM) fueron estimados utilizando la matriz de varianzas-covarianzas de Newey-West, blindando los resultados contra cualquier autocorrelación o heterocedasticidad residual.
# 2. Modelo de Corrección de Errores (ECM) con Errores Robustos (HAC)
modelo_ecm <- uecm(mejor_ardl)
ecm_robusto <- coeftest(modelo_ecm, vcov. = NeweyWest(modelo_ecm))
# 3. Multiplicadores de Largo Plazo (Impacto real de la placa tectónica)
multiplicadores_robustos <- multipliers(mejor_ardl, vcov_matrix = NeweyWest(mejor_ardl))
print(multiplicadores_robustos) Term Estimate Std. Error t value Pr(>|t|)
1 (Intercept) -0.002048677 0.002114642 -0.9688056 0.33338309
2 east -1.290972628 0.568321631 -2.2715529 0.02378563
3 north 0.219241738 0.205279630 1.0680151 0.28632893
Los multiplicadores fueron estimados aplicando la matriz de varianzas-covarianzas de Newey-West (HAC). Esto asegura que los errores estándar están blindados contra la heterocedasticidad y la autocorrelación, haciendo que los valores p sean irrefutables.
El Impacto Tectónico del Este: Se comprobó que el desplazamiento hacia el Este es el principal motor de la deformación vertical a largo plazo (\(p < 0.05\)). El coeficiente estimado es de -1.29.
Tasa de hundimiento: Por cada unidad (ej. milímetro) que la corteza se desplaza permanentemente hacia el Este, la estación Wanchaq experimenta un hundimiento geológico a largo plazo de 1.29 unidades.
Independencia de la Coordenada Norte: De manera consistente con la dinámica a corto plazo, el desplazamiento hacia el Norte no ejerce una influencia estadísticamente significativa (\(p = 0.286\)) sobre la coordenada vertical en el equilibrio de largo plazo.
# 1. Extraer métricas de precisión
valores_ajustados <- as.numeric(fitted(mejor_ardl))
filas_restantes <- nrow(train_data) - length(valores_ajustados) + 1
valores_reales_train <- train_data$height[filas_restantes:nrow(train_data)]
r_cuadrado <- summary(mejor_ardl)$adj.r.squared
# 2. Gráfica de Ajuste
datos_train_grafica <- train_data[filas_restantes:nrow(train_data), ]
datos_train_grafica$fecha <- fecha_train[filas_restantes:length(fecha_train)]
datos_train_grafica$Pronostico <- valores_ajustados
ggplot(datos_train_grafica, aes(x = fecha)) +
geom_line(aes(y = height, color = "Altura Real"), linewidth = 0.8, alpha = 0.6) +
geom_line(aes(y = Pronostico, color = "Ajuste ARDL"), linewidth = 0.8, linetype = "dashed") +
scale_color_manual(values = c("Altura Real" = "gray60", "Ajuste ARDL" = "darkblue")) +
theme_minimal() +
labs(title = "Memoria del Modelo: Ajuste en Datos de Entrenamiento (85%)",
subtitle = sprintf("Varianza explicada (R-cuadrado Ajustado): %.2f%%", r_cuadrado * 100),
y = "Desplazamiento Vertical (m)", x = "Fecha", color = "") +
theme(legend.position = "bottom", plot.title = element_text(face="bold"))Alta Fidelidad de Aprendizaje: El modelo ARDL logra un seguimiento casi perfecto (línea azul punteada) de la trayectoria real de deformación (línea gris), validando visualmente el 88.21% de varianza explicada.
Captura Multiescalar: El ajuste demuestra que el algoritmo no solo aprendió la macro-tendencia tectónica general, sino que también es capaz de replicar con precisión los micro-picos y valles (la volatilidad a corto plazo y la estacionalidad).
Ausencia de Desfase Severo: A pesar de tener rezagos temporales (memoria de 2 semanas), el modelo no muestra un retraso visual significativo (lagging) respecto a la serie original, indicando una respuesta rápida a los cambios geofísicos.
Box-Ljung test
data: residuos_ardl
X-squared = 12.582, df = 10, p-value = 0.248
Breusch-Godfrey test for serial correlation of order up to 1
data: mejor_ardl
LM test = 0.82109, df = 1, p-value = 0.3649
library(dplyr)
library(ggplot2)
library(forecast)
# 1. Proyectar la estacionalidad al futuro
terminos_fourier_test <- fourier(ts_referencia_train, K = 1, h = nrow(test_data))
test_data$sin_anual <- as.numeric(terminos_fourier_test[, 1])
test_data$cos_anual <- as.numeric(terminos_fourier_test[, 2])
# 2. EL ARREGLO: Usar bind_rows en lugar de rbind
data_predict <- bind_rows(train_data, test_data) %>%
mutate(
height_L1 = dplyr::lag(height, 1),
height_L2 = dplyr::lag(height, 2),
east_L1 = dplyr::lag(east, 1)
)
train_predict <- data_predict[1:nrow(train_data), ]
test_predict <- data_predict[(nrow(train_data) + 1):nrow(data_predict), ]
# Re-entrenamos la versión LM explícita con los coeficientes correctos
modelo_lm_predict <- lm(height ~ height_L1 + height_L2 + east + east_L1 + north + sin_anual + cos_anual,
data = train_predict)
# 3. Pronóstico y Cálculo de Error
predicciones_test <- predict(modelo_lm_predict, newdata = test_predict)
rmse_test <- sqrt(mean((test_data$height - predicciones_test)^2, na.rm = TRUE))
mae_test <- mean(abs(test_data$height - predicciones_test), na.rm = TRUE)
# 4. Gráfica Final
# --- SOLUCIÓN: Inyectar fechas al test_data ---
if (!"fecha" %in% colnames(test_data)) {
# Tomamos la última fecha de tu entrenamiento y le sumamos 7 días
ultima_fecha_train <- max(train_data$fecha)
test_data$fecha <- seq(ultima_fecha_train + 7, by = "7 days", length.out = nrow(test_data))
}
df_test_grafica <- data.frame(
fecha = test_data$fecha,
Real = test_data$height,
Pronostico = as.numeric(predicciones_test)
)
ggplot(df_test_grafica, aes(x = fecha)) +
geom_line(aes(y = Real, color = "Altura Real"), linewidth = 1) +
geom_line(aes(y = Pronostico, color = "Pronóstico ARDL"), linewidth = 1, linetype = "dashed") +
scale_color_manual(values = c("Altura Real" = "gray40", "Pronóstico ARDL" = "darkred")) +
theme_minimal() +
labs(title = "Predicción en Datos Nuevos (15%)",
subtitle = sprintf("Precisión de pronóstico: RMSE = %.5f m | MAE = %.5f m", rmse_test, mae_test),
y = "Desplazamiento Vertical (m)", x = "Fecha", color = "") +
theme(legend.position = "bottom", plot.title = element_text(face="bold"))# RESPUESTA AL IMPULSO (MULTIPLICADORES DINÁMICOS) PARA ARDL
coefs <- coef(modelo_lm_predict)
horizonte <- 20
respuestas <- numeric(horizonte)
# Simulamos un "Shock" unitario en el componente ESTE
# Recordando la estructura: Height ~ Height_L1 + Height_L2 + East + East_L1 ...
impulse_value <- 1
# Calculamos el efecto inmediato y propagado (Recursión)
for (t in 1:horizonte) {
if (t == 1) {
# Efecto inmediato: Coeficiente de 'east'
respuestas[t] <- coefs["east"] * impulse_value
} else if (t == 2) {
# Primer rezago: Coeficiente de 'east_L1' + persistencia de la altura
respuestas[t] <- (coefs["height_L1"] * respuestas[t-1]) + (coefs["east_L1"] * impulse_value)
} else {
# Efecto a largo plazo: Solo queda la persistencia (memoria) de la altura
respuestas[t] <- (coefs["height_L1"] * respuestas[t-1]) + (coefs["height_L2"] * respuestas[t-2])
}
}
# 4. Graficar el Multiplicador Dinámico
df_irf_ardl <- data.frame(Semana = 1:horizonte, Impacto = respuestas)
ggplot(df_irf_ardl, aes(x = Semana, y = Impacto)) +
geom_line(color = "blue", linewidth = 1) +
geom_point(color = "blue") +
geom_hline(yintercept = 0, linetype = "dashed", color = "red") +
theme_minimal() +
labs(title = "Respuesta Dinámica de la Altura ante un Shock en el Este (ARDL)",
subtitle = "Muestra cómo el hundimiento se estabiliza tras el movimiento horizontal",
x = "Semanas después del evento", y = "Impacto acumulado en Altura (m)")library(forecast)
# 1. Preparar las variables externas (La matriz "X" de regresores)
# Unimos el desplazamiento horizontal y las ondas estacionales de Fourier
xreg_train_sarimax <- cbind(
east = train_data$east,
north = train_data$north,
sin_anual = terminos_fourier_train[, 1],
cos_anual = terminos_fourier_train[, 2]
)
# 2. Búsqueda algorítmica del mejor modelo SARIMAX
# auto.arima evaluará decenas de modelos y elegirá el de menor criterio AICc
modelo_sarimax <- auto.arima(train_data$height,
xreg = xreg_train_sarimax,
seasonal = FALSE) # Fourier ya controla la estacionalidad
# 3. Mostrar el resumen del mejor modelo encontrado
summary(modelo_sarimax)Series: train_data$height
Regression with ARIMA(2,1,1) errors
Coefficients:
ar1 ar2 ma1 east north sin_anual cos_anual
0.5826 0.1577 -0.9252 0.2685 -0.1724 0.0074 6e-04
s.e. 0.0695 0.0622 0.0375 0.0997 0.1548 0.0010 9e-04
sigma^2 = 9.184e-06: log likelihood = 1426.87
AIC=-2837.74 AICc=-2837.28 BIC=-2807.46
Training set error measures:
ME RMSE MAE MPE MAPE MASE
Training set -8.312616e-05 0.002993094 0.002332568 -Inf Inf 0.9280259
ACF1
Training set 0.004296426
# 1. Preparar la matriz XREG para el futuro (Test Data)
# IMPORTANTE: Usamos los datos de prueba que el modelo NUNCA ha visto
xreg_test_arimax <- cbind(
east = test_data$east,
north = test_data$north,
sin_anual = test_data$sin_anual,
cos_anual = test_data$cos_anual
)
pronostico_arimax <- forecast(modelo_sarimax, xreg = xreg_test_arimax)
rmse_arimax <- sqrt(mean((test_data$height - pronostico_arimax$mean)^2, na.rm = TRUE))
mae_arimax <- mean(abs(test_data$height - pronostico_arimax$mean), na.rm = TRUE)
df_grafica_arimax <- data.frame(
fecha = test_data$fecha,
Real = test_data$height,
Pronostico = as.numeric(pronostico_arimax$mean)
)
checkresiduals(modelo_sarimax)
Ljung-Box test
data: Residuals from Regression with ARIMA(2,1,1) errors
Q* = 8.4763, df = 7, p-value = 0.2925
Model df: 3. Total lags used: 10
ggplot(df_grafica_arimax, aes(x = fecha)) +
geom_line(aes(y = Real, color = "Altura Real"), linewidth = 1) +
geom_line(aes(y = Pronostico, color = "Pronóstico ARIMAX"), linewidth = 1, linetype = "dashed") +
scale_color_manual(values = c("Altura Real" = "gray40", "Pronóstico ARIMAX" = "blue")) +
theme_minimal() +
labs(title = "Validación Externa: Pronóstico ARIMAX (Out-of-Sample)",
subtitle = sprintf("Precisión: RMSE = %.5f m | MAE = %.5f m", rmse_arimax, mae_arimax),
y = "Desplazamiento Vertical (m)", x = "Fecha", color = "") +
theme(legend.position = "bottom", plot.title = element_text(face="bold"))En la realidad, la estación Wanchaq no solo “recibe” efectos. El movimiento de la placa sudamericana es un vector tridimensional. Un empuje tectónico no ocurre solo en el plano horizontal; por la elasticidad de la corteza, un desplazamiento al Este a menudo “obliga” a un reajuste vertical (subsidencia o levantamiento). El VECM permite que las tres variables sean “endógenas”, es decir, que todas dependan de todas, capturando la cinemática real del bloque.
Dado que en el ARDL confirmamos mediante el test de Pesaran que existe cointegración, el Teorema de Granger dicta que obligatoriamente existe una representación de corrección de errores (VECM). Mientras que el ARDL es una ecuación simplificada, el VECM es la forma estructural completa.
library(vars)
library(urca)
matriz_gnss <- cbind(
height = train_data$height,
east = train_data$east,
north = train_data$north
)
seleccion_lags <- VARselect(matriz_gnss, lag.max = 10, type = "const")
#cat("\n--- REZAGOS ÓPTIMOS SUGERIDOS ---\n")
#print(seleccion_lags$selection)
K_optimo <- max(seleccion_lags$selection["SC(n)"], 2)
cat("\nRezago (K) elegido para Johansen:", K_optimo, "\n")
Rezago (K) elegido para Johansen: 2
# type = "trace": Usamos el estadístico de Traza (el más robusto)
# ecdet = "const": Asumimos intercepto pero sin tendencia en la ecuación (equivalente a tu Case 3)
prueba_johansen <- ca.jo(matriz_gnss, type = "trace", ecdet = "const", K = K_optimo)
cat("\n--- RESULTADOS DE LA PRUEBA DE JOHANSEN ---\n")
--- RESULTADOS DE LA PRUEBA DE JOHANSEN ---
######################
# Johansen-Procedure #
######################
Test type: trace statistic , without linear trend and constant in cointegration
Eigenvalues (lambda):
[1] 1.004103e-01 5.793508e-02 4.089887e-02 1.526557e-16
Values of teststatistic and critical values of test:
test 10pct 5pct 1pct
r <= 2 | 13.53 7.52 9.24 12.97
r <= 1 | 32.87 17.85 19.96 24.60
r = 0 | 67.15 32.00 34.91 41.07
Eigenvectors, normalised to first column:
(These are the cointegration relations)
height.l2 east.l2 north.l2 constant
height.l2 1.00000000 1.00000000 1.000000000 1.0000000
east.l2 13.44910793 2.06055219 -0.328190117 1.4013379
north.l2 -4.77158567 -0.21035438 0.277011525 4.9898976
constant 0.04643199 -0.01991509 0.003772574 -0.1468218
Weights W:
(This is the loading matrix)
height.l2 east.l2 north.l2 constant
height.d -0.013649914 -0.027329368 -0.044232290 9.113070e-18
east.d -0.007948208 -0.009530759 0.032242687 3.767177e-18
north.d 0.004251315 -0.011723117 0.004880814 1.320329e-18
# ==============================================================================
# CONSTRUCCIÓN DEL MODELO Y SIMULACIÓN DE CHOQUES
# ==============================================================================
# 1. Convertimos el test de Johansen en un modelo calculable
# Usamos r = 1 (asumiendo 1 relación principal de equilibrio a pesar del over-rejection)
modelo_vecm <- vec2var(prueba_johansen, r = 1)
# 2. Funciones de Impulso-Respuesta (IRF)
# Simulamos un "shock" en el Este y vemos cómo reacciona la Altura 20 periodos adelante
irf_este_altura <- irf(modelo_vecm, impulse = "east", response = "height",
n.ahead = 20, boot = TRUE)
# Al correr esto, R te abrirá una ventana con una gráfica física espectacular
plot(irf_este_altura, main = "Impacto en la Altura tras un 'Shock' hacia el Este")# ==============================================================================
# FASE 5 (VECM): PREDICCIÓN DEL FUTURO Y CÁLCULO DEL RMSE
# ==============================================================================
# 3. Le pedimos al modelo que pronostique tantos pasos adelante como tenga tu test_data
prediccion_vecm <- predict(modelo_vecm, n.ahead = nrow(test_data))
# Extraemos exclusivamente los valores pronosticados para la Altura
pronostico_altura_vecm <- prediccion_vecm$fcst$height[, "fcst"]
# Calculamos el error final (RMSE y MAE)
rmse_vecm <- sqrt(mean((test_data$height - pronostico_altura_vecm)^2, na.rm = TRUE))
mae_vecm <- mean(abs(test_data$height - pronostico_altura_vecm), na.rm = TRUE)
cat("RMSE VECM:", round(rmse_vecm, 5), "metros\n")RMSE VECM: 0.01726 metros
MAE VECM: 0.01551 metros
Portmanteau Test (asymptotic)
data: Residuals of VAR object vecm_to_var
Chi-squared = 107.93, df = 75, p-value = 0.007641
$serial
Portmanteau Test (asymptotic)
data: Residuals of VAR object vecm_to_var
Chi-squared = 107.93, df = 75, p-value = 0.007641
v_real <- as.numeric(test_data$height)
v_ardl <- as.numeric(predicciones_test)
v_arimax <- as.numeric(pronostico_arimax$mean)
v_vecm <- as.numeric(pronostico_altura_vecm)
df_integrado <- data.frame(
fecha = test_data$fecha,
Real = v_real,
ARDL = v_ardl,
ARIMAX = v_arimax,
VECM = v_vecm,
ARDL_lwr = v_ardl - (1.96 * rmse_test),
ARDL_upr = v_ardl + (1.96 * rmse_test)
)
ggplot(df_integrado, aes(x = fecha)) +
geom_ribbon(aes(ymin = ARDL_lwr, ymax = ARDL_upr, fill = "Incertidumbre ARDL (95%)"), alpha = 0.15) +
geom_line(aes(y = Real, color = "Observado (Real)"), linewidth = 1.1) +
geom_line(aes(y = ARDL, color = "ARDL (Dinámico)"), linewidth = 0.9) +
geom_line(aes(y = ARIMAX, color = "ARIMAX-Fourier"), linewidth = 0.8, linetype = "dashed") +
geom_line(aes(y = VECM, color = "VECM (Sistémico)"), linewidth = 0.8, linetype = "dotdash") +
scale_color_manual(values = c(
"Observado (Real)" = "black",
"ARDL (Dinámico)" = "blue",
"ARIMAX-Fourier" = "darkgreen",
"VECM (Sistémico)" = "darkred"
)) +
scale_fill_manual(values = c("Incertidumbre ARDL (95%)" = "dodgerblue")) +
theme_minimal() +
labs(title = "Comparación de Modelos: Predicción de Altura Wanchaq",
subtitle = "Evaluación sobre el 15% de datos finales (no vistos)",
y = "Desplazamiento Vertical (m)", x = "Año",
color = "Modelos", fill = "") +
theme(legend.position = "bottom", plot.title = element_text(face="bold", size = 14))# Función para el Nash-Sutcliffe Efficiency
f_nse <- function(real, pred) {
1 - (sum((real - pred)^2, na.rm=T) / sum((real - mean(real, na.rm=T))^2, na.rm=T))
}
# Consolidación de la tabla
tabla_final <- data.frame(
Modelo = c("ARDL", "ARIMAX-Fourier", "VECM"),
RMSE_m = c(rmse_test, rmse_arimax, rmse_vecm),
MAE_m = c(mae_test, mae_arimax, mae_vecm),
NSE = c(f_nse(test_data$height, predicciones_test),
f_nse(test_data$height, as.numeric(pronostico_arimax$mean)),
f_nse(test_data$height, as.numeric(pronostico_altura_vecm)))
)
# Redondeo para estética académica
tabla_final[, 2:3] <- round(tabla_final[, 2:3], 5)
tabla_final[, 4] <- round(tabla_final[, 4], 4)
print(tabla_final) Modelo RMSE_m MAE_m NSE
1 ARDL 0.00570 0.00460 0.5437
2 ARIMAX-Fourier 0.01500 0.01264 -2.1537
3 VECM 0.01726 0.01551 -3.1762