¿Qué es el modelo ARIMA?

Un modelo ARIMA (Autoregressive Integrated Moving Average) es una herramienta estadística utilizada para analizar y predecir series de tiempo. En términos simples, es como una “bola de cristal matemática” que usa datos pasados para estimar valores futuros, especialmente útil en finanzas para preveer precios, ventas o ingresos.

Desglose del nombre ARIMA 1. Autoregressive o autorregresivo (AR) → Usa valores pasados para predecir el futuro 2. Integrated (I) → Ajusta tendencias en los datos para hacerlos estacionarios (sin patrones cambiantes en el tiempo). 3.MAving Average o media móvil (MA)- → Suaviza fluctuaciones aleatorias a partir de errores pasados.

ARIMA combina estos tres elementos para crear una predicción más precisa.

Metodología Box-Jenkins

La metodología Box-Jenkins es un enfoque sistemático para construir modelos ARIMA con el objetivo de analizar y pronosticar series de tiempo. Fue desarrollada por George Box y Gwilym Jenkins y se basa en cuatro etapas clave:

1️⃣ Identificación 2️⃣ Estimación 3️⃣ Validación 4️⃣ Pronóstico

Se usa especialmente cuando se quiere encontrar el modelo ARIMA más adecuado para una serie de tiempo.

Paso 1. Idntificación del Modelo

Análisis exploratorio de la variable o extracción de señales

Antes de ajustar un modelo ARIMA, es necesario entender la estructura de la serie de tiempo.

1. Verificar estacionariedad (media y varianza constantes en el tiempo) 2. Diferenciar los datos por si hay una tendencia para hacer los datos estacionarios 3. Examinar los gráficos ACF y PACF para determinar los parámetro p y q

#Cargar librerías necesarias
library(readxl)  # Para leer archivos Excel
library(tseries)  # Para pruebas de estacionariedad
library(forecast)  # Para modelado ARIMA y pronósticos
library(ggplot2)  # Para visualización de datos
library(plotly)  # Para gráficos interactivos
library(timetk)   
data_col <- read_excel("IPC.xlsx", col_types = c("date", 
    "numeric"))
# Convertir el IP a una serie de tiempo Mensual
ipc_ts <- ts(data_col$IPC, start = c(2000, 1), frequency = 12)
ipc_ts
##         Jan    Feb    Mar    Apr    May    Jun    Jul    Aug    Sep    Oct
## 2000  41.54  42.30  42.87  43.18  43.37  43.42  43.48  43.58  43.84  44.04
## 2001  44.80  45.44  46.14  46.72  47.12  47.18  47.11  47.22  47.55  47.67
## 2002  48.32  49.14  49.48  49.74  50.02  50.28  50.31  50.44  50.82  51.13
## 2003  52.41  53.08  53.40  53.94  54.26  54.46  54.41  54.49  54.73  54.93
## 2004  55.81  56.29  56.92  57.14  57.32  57.53  57.48  57.65  58.09  58.13
## 2005  58.70  59.17  59.57  59.85  60.23  60.39  60.45  60.48  60.86  61.07
## 2006  61.33  61.63  61.99  62.11  62.28  62.59  62.90  63.27  63.54  63.46
## 2007  64.10  64.68  65.32  65.89  66.10  66.31  66.18  66.14  66.29  66.36
## 2008  67.37  68.49  68.96  69.32  69.92  70.67  71.11  71.40  71.24  71.58
## 2009  72.36  72.65  72.91  73.08  73.13  72.95  72.94  73.03  72.81  72.66
## 2010  73.23  73.64  73.90  74.30  74.33  74.47  74.40  74.32  73.80  73.65
## 2011  75.14  75.40  75.45  75.57  75.69  75.94  76.09  75.99  76.87  76.79
## 2012  77.45  77.58  77.48  77.47  78.13  77.98  77.69  77.90  78.50  78.43
## 2013  78.89  78.78  78.65  79.03  79.64  79.53  79.16  79.47  80.11  79.55
## 2014  80.20  80.51  80.90  81.34  81.64  81.68  81.78  82.16  82.47  82.58
## 2015  83.76  84.50  85.01  85.11  84.82  85.16  85.07  85.80  86.49  87.28
## 2016  89.83  90.72  91.65  92.41  92.69  93.25  93.56  92.98  93.22  92.92
## 2017  94.18  95.03  95.44  95.83  96.23  96.34  96.45  96.56  96.81  96.87
## 2018  97.49  98.12  98.66  99.00  99.23  99.42  99.23  99.45  99.81  99.74
## 2019 100.67 101.08 101.75 102.29 102.56 103.03 103.39 103.49 103.94 104.15
## 2020 105.14 105.72 106.33 106.39 106.11 106.00 105.92 105.99 106.21 106.26
## 2021 107.14 107.72 108.13 108.75 111.76 110.39 110.67 111.13 111.42 111.42
## 2022 114.69 116.68 117.95 119.55 120.54 121.20 122.22 123.59 124.50 125.42
## 2023 130.36 132.23 133.64 134.60 135.35 135.59 136.27 137.56 138.08 138.73
## 2024 140.71 142.02 142.75 143.64 144.03 144.04 144.36 144.63 144.97 144.83
## 2025 146.75                                                               
##         Nov    Dec
## 2000  44.19  44.32
## 2001  47.89  47.98
## 2002  51.54  51.69
## 2003  55.01  55.39
## 2004  58.35  58.35
## 2005  61.12  61.03
## 2006  63.53  63.67
## 2007  66.42  66.85
## 2008  71.76  71.93
## 2009  72.75  72.79
## 2010  74.02  74.58
## 2011  76.53  76.96
## 2012  78.14  78.42
## 2013  79.25  79.78
## 2014  82.61  82.81
## 2015  87.60  88.48
## 2016  92.82  93.02
## 2017  96.87  97.01
## 2018  99.68 100.00
## 2019 104.22 104.53
## 2020 106.22 106.47
## 2021 111.86 112.67
## 2022 126.32 128.07
## 2023 138.92 139.23
## 2024 145.05 145.56
## 2025
# Calcular estadísticas descriptivas básicas
descriptive_stats <- data.frame(
  Min = min(ipc_ts),
  Max = max(ipc_ts),
  Media = mean(ipc_ts),
  Mediana = median(ipc_ts),
  DesviacionEstandar = sd(ipc_ts),
  CoefVar = sd(ipc_ts) / mean(ipc_ts)
)
print(descriptive_stats)
##     Min    Max    Media Mediana DesviacionEstandar  CoefVar
## 1 41.54 146.75 82.90698   77.98           26.58843 0.320702
# Gráfico interactivo de la serie original
grafico_serie <- ggplot(data_col, aes(x = seq.Date(from = as.Date("2000-01-01"), by = "month", length.out = nrow(data_col)), y = ipc_ts)) +
  geom_line(color = "grey", size = 1.5) +
  geom_point(color = "grey") +
  ggtitle("Figura 1.IPC Cali") +
  xlab("Tiempo") +
  ylab("índice") +
  theme_minimal()
ggplotly(grafico_serie)
# Extraer señales:
# Descomposición de la serie temporal
stl_decomp <- stl(ipc_ts, s.window = "periodic")
plot(stl_decomp)

Graficamos serie original VS ajustada por estacionalidad

# Extraer los componentes de la descomposición
ipc_sa <- ipc_ts - stl_decomp$time.series[, "seasonal"]
library(ggplot2)
library(plotly)
library(scales)

# Comparación de la serie original con la ajustada por estacionalidad
grafico_ajustada <- ggplot() +
  geom_line(aes(x = seq_along(ipc_ts), y = ipc_ts), color = "grey", size = 1, linetype = "solid") +
  geom_line(aes(x = seq_along(ipc_sa), y = ipc_sa), color = "black", size = 1, linetype = "dashed") +
  ggtitle("Figura 2.Serie Original vs Serie Ajustada por Estacionalidad") +
  xlab("Tiempo") +
  ylab("Índice") +
  theme_minimal()
ggplotly(grafico_ajustada)
# Crear vector de fechas correctamente alineado con la serie
fechas <- seq.Date(from = as.Date("2000-01-01"), by = "month", length.out = length(ipc_ts))

# Gráfico mejorado con fechas en el eje X
grafico_ajustada <- ggplot() +
  geom_line(aes(x = fechas, y = ipc_ts), color = "grey", size = 1, linetype = "solid", name = "Serie Original") +
  geom_line(aes(x = fechas, y = ipc_sa), color = "black", size = 1, linetype = "dashed", name = "Serie Ajustada") +
  ggtitle("Figura 2. Serie Original vs Serie Ajustada por Estacionalidad") +
  xlab("Tiempo") +
  ylab("Índice") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Rotar etiquetas para mejor visualización

# Convertir a gráfico interactivo
ggplotly(grafico_ajustada)
# Comparación de la serie original con la tendencia
tendencia <- stl_decomp$time.series[, "trend"]
grafico_tendencia <- ggplot() +
  geom_line(aes(x = seq_along(ipc_ts), y = ipc_ts), color = "grey", size = 1) +
  geom_line(aes(x = seq_along(tendencia), y = tendencia), color = "purple", size = 1, linetype = "dashed") +
  ggtitle("Figura 3. Serie Original vs Tendencia") +
  xlab("Tiempo") +
  ylab("índice") +
  theme_minimal()
ggplotly(grafico_tendencia)
# Extraer la tendencia de la descomposición STL
tendencia <- stl_decomp$time.series[, "trend"]

# Gráfico interactivo de la serie original vs tendencia
grafico_tendencia <- ggplot() +
  geom_line(aes(x = fechas, y = ipc_ts), color = "grey", size = 1, linetype = "solid", name = "Serie Original") +
  geom_line(aes(x = fechas, y = tendencia), color = "purple", size = 1, linetype = "dashed", name = "Tendencia") +
  ggtitle("Figura 3. Serie Original vs Tendencia") +
  xlab("Tiempo") +
  ylab("Índice") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) # Rotar etiquetas del eje X para mejor visualización

# Convertir a gráfico interactivo
ggplotly(grafico_tendencia)
#Cálculo de la tasa de crecimiento anual correctamente alineada
tasa_crecimiento <- (ipc_ts[(13:length(ipc_ts))] / ipc_ts[1:(length(ipc_ts) - 12)] - 1) * 100
tasa_tendencia <- (tendencia[(13:length(tendencia))] / tendencia[1:(length(tendencia) - 12)] - 1) * 100

# Crear vector de fechas corregido
fechas_corregidas <- seq(from = as.Date("2001-01-01"), by = "month", length.out = length(tasa_crecimiento))

# Verificar longitudes
print(length(fechas_corregidas))
## [1] 289
print(length(tasa_crecimiento))
## [1] 289
print(length(tasa_tendencia))
## [1] 289
# Gráfico de la tasa de crecimiento anual
grafico_crecimiento <- ggplot() +
  geom_line(aes(x = fechas_corregidas, y = tasa_crecimiento), color = "grey", size = 1) +
  geom_line(aes(x = fechas_corregidas, y = tasa_tendencia), color = "green", size = 1, linetype = "dashed") +
  ggtitle("Figura 4. Tasa de Crecimiento Anual: Serie Original vs Tendencia") +
  xlab("Tiempo") +
  ylab("% de Crecimiento Anual") +
  theme_minimal()

# Convertir a gráfico interactivo
ggplotly(grafico_crecimiento)

Ahora dividimos el conjunto de datos de prueba y entrenamiento

✅ Para entrenar el modelo con datos históricos sin usar información futura. ✅ Para evaluar la precisión del modelo comparando sus predicciones con los datos reales de prueba.

💡 Esta división es clave en modelos predictivos para evitar sobreajuste y evaluar el rendimiento en datos no vistos.

# Dividir la serie en conjunto de entrenamiento y prueba 
train_size <- length(ipc_ts) - 13
train_ts <- window(ipc_ts, end = c(2023, 12))  # Entrenamiento hasta dic23
test_ts <- window(ipc_ts, start = c(2024, 1))  # Prueba desde ene24

Identificar estacionariedad

En el análisis de series de tiempo, una serie es estacionaria si su comportamiento es constante a lo largo del tiempo, es decir:

✅ Su promedio no cambia con el tiempo. ✅ Su variabilidad (qué tanto fluctúa) se mantiene estable. ✅ Su relación con valores pasados es siempre la misma.

¿Cómo saber si una serie es estacionaria?

1️⃣ Observando un gráfico Si el gráfico de la serie muestra una tendencia creciente o decreciente, o si las variaciones se hacen más grandes con el tiempo, la serie probablemente no es estacionaria.

2️⃣ Usando la prueba de Dickey-Fuller Aumentada (ADF) Es una prueba estadística que nos dice si la serie tiene una tendencia fuerte. Si el p-valor de la prueba es mayor a 0.05, significa que la serie no es estacionaria.

¿Cómo hacer una serie estacionaria?

Si encontramos que la serie no es estacionaria, podemos transformarla para que lo sea:

✅ Diferenciación: Restamos cada valor con su valor anterior. Esto elimina tendencias crecientes o decrecientes. ✅ Tomar el logaritmo: Si la variabilidad crece con el tiempo, aplicar un logaritmo estabiliza la varianza. ✅ Eliminar tendencias o ajustar estacionalidad: Si hay patrones repetitivos, podemos restarlos o modelarlos por separado.

# Prueba de estacionariedad con Augmented Dickey-Fuller (ADF)
adf_test <- adf.test(train_ts)
print(adf_test)
## 
##  Augmented Dickey-Fuller Test
## 
## data:  train_ts
## Dickey-Fuller = 1.7781, Lag order = 6, p-value = 0.99
## alternative hypothesis: stationary
# Si la serie no es estacionaria (p-valor > 0.05), aplicar diferenciación
if (adf_test$p.value > 0.05 && length(train_ts) > 1) {
  train_diff <- diff(train_ts, differences = 1)
}
 # Graficar la serie original en un gráfico separado
  p2 <- ggplot(data.frame(Tiempo = time(train_ts), Ipc = as.numeric(train_ts)), aes(x = Tiempo, y = Ipc)) +
    geom_line(color = "blue") +
    ggtitle("Serie Original") +
    xlab("Tiempo") + ylab("IPC")
  
  ggplotly(p2)  # Convertir en gráfico interactivo
  # Graficar la serie diferenciada en un gráfico separado
  p3 <- ggplot(data.frame(Tiempo = time(train_ts)[-1], IPC_Diff = as.numeric(train_diff)), aes(x = Tiempo, y = IPC_Diff)) +
    geom_line(color = "red") +
    ggtitle("Serie Estacionaria (Diferenciada)") +
    xlab("Tiempo") + ylab("IPC Diferenciado")
  
  ggplotly(p3)  # Convertir en gráfico interactivo

A continuación se muestra que el valor que puede tomar d=1:

El valor p ya es menor a 0.05.

# Segunda prueba de estacionariedad sobre la serie diferenciada
  adf_test_diff <- adf.test(train_diff)
  print(adf_test_diff)
## 
##  Augmented Dickey-Fuller Test
## 
## data:  train_diff
## Dickey-Fuller = -4.9966, Lag order = 6, p-value = 0.01
## alternative hypothesis: stationary

Identificación manual de p y q

¿Qué hacen estos gráficos?

ACF (Autocorrelation Function)

Muestra la correlación de la serie con sus rezagos.

Ayuda a determinar el parámetro q en un modelo ARIMA(p, d, q).

PACF (Partial Autocorrelation Function)

Muestra la correlación parcial entre la serie y un rezago específico, eliminando el efecto de rezagos intermedios.

Ayuda a determinar el parámetro p en un modelo ARIMA(p, d, q).

# Graficar ACF y PACF
acf_plot <- ggAcf(train_diff, lag.max = 20) + ggtitle("Autocorrelation Function (ACF)")
pacf_plot <- ggPacf(train_diff, lag.max = 20) + ggtitle("Partial Autocorrelation Function (PACF)")

ggplotly(acf_plot)
ggplotly(pacf_plot)

Modelo amnual Arima (1,1,1)

*El coeficiente AR(1) alto (0.9576) sugiere que la serie tiene una inercia fuerte, lo que implica que valores pasados afectan mucho el presente.

*El coeficiente MA(1) negativo (-0.6682) indica que los errores previos afectan en dirección opuesta al valor presente.

*El AIC y BIC pueden compararse con otros modelos (por ejemplo, ARIMA(0,1,1) o ARIMA(1,1,0)) para ver si esta especificación es la mejor.

🔹 Recomendación:

*Probar otras especificaciones como ARIMA(0,1,1) o ARIMA(1,1,2) y comparar AIC/BIC. Evaluar la serie en términos de estacionariedad para verificar si la diferenciación fue suficiente.

*Usar gráficos de autocorrelación para analizar si el orden AR y MA están bien ajustados.

# Cálculo manual de modelo ARIMA
manual_arima_model <- Arima(train_ts, order = c(1,1,1))
summary(manual_arima_model)
## Series: train_ts 
## ARIMA(1,1,1) 
## 
## Coefficients:
##          ar1      ma1
##       0.9576  -0.6682
## s.e.  0.0326   0.1192
## 
## sigma^2 = 0.1577:  log likelihood = -141.67
## AIC=289.34   AICc=289.42   BIC=300.32
## 
## Training set error measures:
##                      ME      RMSE       MAE        MPE      MAPE       MASE
## Training set 0.04456859 0.3950103 0.2637702 0.05480029 0.3301973 0.06556071
##                   ACF1
## Training set 0.1000932

Validación de residuos

1. Gráfico de Residuales (arriba) Muestra cómo se comportan los errores del modelo a lo largo del tiempo. Lo ideal: Que los residuales estén distribuidos aleatoriamente alrededor de 0, sin patrones visibles.

Problema: Hay algunos picos grandes, especialmente alrededor de 2020, lo que sugiere que el modelo no capturó bien algunos eventos extremos.

2. Autocorrelación de Residuales (ACF - abajo izquierda) Indica si los errores están correlacionados entre sí. Lo ideal: Que todas las barras estén dentro de las líneas azules (sin autocorrelación). Problema: Hay varios rezagos que sobresalen de las líneas azules, lo que indica que aún hay patrones en los datos que el modelo no ha capturado bien.

3. Histograma de Residuales (abajo derecha) Muestra si los errores siguen una distribución normal. Lo ideal: Que la forma sea similar a la curva roja (campana de Gauss). Problema: Aunque en general sigue una distribución normal, hay valores extremos en los extremos (colas), lo que sugiere que hay algunos eventos atípicos que el modelo no predijo bien.

Conclusión General 🔹 El modelo ARIMA(1,1,1) no captura completamente la estructura de los datos. 🔹 Se observa autocorrelación en los errores y presencia de eventos extremos (especialmente en 2020). 🔹 Podría mejorarse probando un modelo más complejo, incluyendo estacionalidad o cambiando el orden del ARIMA.

 checkresiduals(manual_arima_model)

## 
##  Ljung-Box test
## 
## data:  Residuals from ARIMA(1,1,1)
## Q* = 118.5, df = 22, p-value = 3.22e-15
## 
## Model df: 2.   Total lags used: 24
#Pronóstico con el modelo ARIMA manual
manual_forecast <- forecast(manual_arima_model, h = length(test_ts))

# Crear dataframe para gráfico interactivo del pronóstico manual
manual_forecast_data <- data.frame(Tiempo = time(manual_forecast$mean), 
                                   Pronostico = as.numeric(manual_forecast$mean),
                                   Observado = as.numeric(test_ts))

# Graficar pronóstico manual junto con los valores observados reales
p_manual <- ggplot(manual_forecast_data, aes(x = Tiempo)) +
  geom_line(aes(y = Pronostico, color = "Pronóstico Manual")) +
  geom_line(aes(y = Observado, color = "Observado")) +
  ggtitle("Pronóstico Manual vs Observado") +
  xlab("Tiempo") + ylab("Ingresos")

ggplotly(p_manual)
# Calcular métricas de evaluación del modelo manual
mae_manual <- mean(abs(manual_forecast$mean - test_ts), na.rm = TRUE)
rmse_manual <- sqrt(mean((manual_forecast$mean - test_ts)^2, na.rm = TRUE))

# Mostrar métricas de evaluación del modelo manual
cat("MAE Manual: ", mae_manual, "\n")
## MAE Manual:  2.361106
cat("RMSE Manual: ", rmse_manual, "\n")
## RMSE Manual:  2.414935

Las metricas de evealuación anteriores sugieren:

1. MAE (Mean Absolute Error) = 2.3611 Representa el error promedio en valores absolutos entre las predicciones y los valores reales. Indica, en promedio, cuánto se desvía la predicción del modelo con respecto a los datos reales. Interpretación: En promedio, el modelo comete un error de 2.36 unidades en cada predicción.

2. RMSE (Root Mean Squared Error) = 2.4149

Mide el error cuadrático medio y penaliza más los errores grandes.

Se interpreta de manera similar al MAE, pero al elevar los errores al cuadrado, da más peso a desviaciones grandes.

Interpretación: El error típico del modelo en cada predicción es 2.41 unidades.

# Identificación automática del mejor modelo ARIMA
auto_arima_model <- auto.arima(train_ts)  # Busca automáticamente los mejores parámetros del modelo ARIMA
print(auto_arima_model)
## Series: train_ts 
## ARIMA(1,2,2)(0,0,2)[12] 
## 
## Coefficients:
##          ar1      ma1     ma2    sma1    sma2
##       0.7560  -1.4721  0.4841  0.2621  0.1430
## s.e.  0.1453   0.1803  0.1689  0.0624  0.0612
## 
## sigma^2 = 0.1387:  log likelihood = -122.12
## AIC=256.24   AICc=256.54   BIC=278.18

Estructura del modelo ARIMA(1,2,2)(0,0,2)[12] se interpreta así:

(1,2,2):

1 → Componente AR (Auto-Regresivo) de orden 1 (usa el valor previo de la serie para predecir). 2 → Diferenciación de orden 2 (necesita restar valores pasados dos veces para hacer la serie estacionaria). 2 → Componente MA (Media Móvil) de orden 2 (usa errores pasados para mejorar la predicción).

(0,0,2)[12]:

0,0,2 → Indica que hay un componente de media móvil estacional (SMA) de orden 2. [12] → La serie tiene una estacionalidad de 12 períodos (probablemente mensual si la serie es anual). 2. Coeficientes del modelo AR(1) = 0.7560 → Indica que el valor actual depende un 75.6% del valor anterior. MA(1) = -1.4721 y MA(2) = 0.4841 → Capturan el efecto de errores pasados en la predicción. SMA(1) = 0.2621 y SMA(2) = 0.1430 → Capturan patrones de error estacionales. Errores estándar (s.e.) → Son pequeños, lo que sugiere que los coeficientes son estadísticamente significativos.

3. Métricas de Evaluación Log-likelihood = -122.12 → Cuanto más alto, mejor el ajuste del modelo. AIC = 256.24 y BIC = 278.18 → Métricas de selección de modelos (valores más bajos indican mejor ajuste con menos parámetros).

Conclusión 🔹 El modelo ARIMA(1,2,2)(0,0,2)[12] sugiere una serie de tiempo con fuerte dependencia en los valores pasados y un patrón estacional de 12 períodos. 🔹 La presencia de diferenciación de orden 2 sugiere una tendencia fuerte que tuvo que ser eliminada para hacer la serie estacionaria. 🔹 El modelo parece razonablemente ajustado, pero su desempeño debe compararse con otros modelos usando métricas como RMSE o MAE.

Forma de correr modelo manualmente

# Cargar la librería forecast si no está cargada
library(forecast)

# Ajuste del modelo ARIMA con los parámetros encontrados
darima <- Arima(train_ts, 
                order = c(1,2,2),  # (p,d,q) no estacional
                seasonal = list(order = c(0,0,2), period = 12))  # (P,D,Q) estacional con periodo 12

# Resumen del modelo ajustado
summary(darima)
## Series: train_ts 
## ARIMA(1,2,2)(0,0,2)[12] 
## 
## Coefficients:
##          ar1      ma1     ma2    sma1    sma2
##       0.7560  -1.4721  0.4841  0.2621  0.1430
## s.e.  0.1453   0.1803  0.1689  0.0624  0.0612
## 
## sigma^2 = 0.1387:  log likelihood = -122.12
## AIC=256.24   AICc=256.54   BIC=278.18
## 
## Training set error measures:
##                      ME      RMSE       MAE        MPE     MAPE       MASE
## Training set 0.01238208 0.3679304 0.2389882 0.00249301 0.295471 0.05940109
##                     ACF1
## Training set 0.006141105

Conclusión Puntos positivos: El modelo captura bien la tendencia y estacionalidad de los datos. Posibles mejoras: La ACF de los residuos muestra algunos valores fuera de las bandas, lo que indica que el modelo podría no haber eliminado toda la autocorrelación. El gran pico en 2020 puede afectar la precisión del modelo. Tal vez una intervención o modelado de valores atípicos ayude. Se podría probar un modelo ARIMA con otras combinaciones de parámetros o incluir variables exógenas si hay información adicional.

# Diagnóstico del modelo (los residuos deben ser ruido blanco)
checkresiduals(darima)  # Verificar si los residuos son aleatorios y no presentan patrones

## 
##  Ljung-Box test
## 
## data:  Residuals from ARIMA(1,2,2)(0,0,2)[12]
## Q* = 26.6, df = 19, p-value = 0.1143
## 
## Model df: 5.   Total lags used: 24
# Generar pronóstico para el conjunto de prueba
forecast_arima <- forecast(darima, h = length(test_ts))  # Predecir los valores futuros

# Crear dataframe para gráfico interactivo del pronóstico
forecast_data <- data.frame(Tiempo = time(forecast_arima$mean), 
                            Pronostico = as.numeric(forecast_arima$mean),
                            Observado = as.numeric(test_ts))

# Graficar pronóstico junto con los valores observados reales
p4 <- ggplot(forecast_data, aes(x = Tiempo)) +
  geom_line(aes(y = Pronostico, color = "Pronóstico")) +
  geom_line(aes(y = Observado, color = "Observado")) +
  ggtitle("Pronóstico vs Observado") +
  xlab("Tiempo") + ylab("IPC Cali")

ggplotly(p4)  # Convertir el gráfico en interactivo