Análisis y predicción de series temporales

Trosman, Denis

9/2/2023

Master Big Data y Business Analytics - Facultad de Estudios Estadísticos 2022-2023

Introducción

El objetivo de este trabajo es realizar un análisis de series de tiempo sobre datos de consumo de energía. La base de datos proviene de la Administración de Información de Energia de los Estados Unidos de América, o E.I.A por sus siglas en inglés (https://www.eia.gov/totalenergy/data/monthly/).

En esta se pueden observar los consumos de energía mensuales desde el año 1943, según el tipo de consumo. Para este trabajo se ha escogido el consumo total de combustibles fósiles. La unidad de medida es en Quadrillion Btu.

El consumo de energía suele seguir una estacionalidad guiada por los meses de uso, sea verano o invierno, como se notará en el siguiente análisis. Ademas de ver esto en una descomposición de la serie de datos, se observarán las funciones de autocorrelaciones y autocorrelaciones parciales. Por último, se generarán modelos de predicción y se verá cual ajusta de mejor manera a la serie temporal.

Pasos previos

Eliminamos objetos previos y notación científica.

rm(list = ls()) 
options(scipen = 999) 

Cargamos librerias.

library(readxl)
library(forecast)
library(ggplot2)
library( dplyr )
library (fUnitRoots)
library(lmtest)
library(data.table)
library(stats)

Lectura y visualización de los datos

Seteamos working directory y leemos data.

setwd("C:/Users/denis/OneDrive/Escritorio/Master/Modulo 8 - Time Series/Tarea")

data <- read.csv('energy.csv',header=TRUE)
data <- data[,2:3]

Observamos primeros datos y resumen.

YYYYMM Value
200501 8.015701
200502 7.152356
200503 7.551950
200504 6.615682
200505 6.708309
200506 6.914146
summary(data)
##      YYYYMM           Value      
##  Min.   :200501   Min.   :4.972  
##  1st Qu.:200906   1st Qu.:6.216  
##  Median :201311   Median :6.662  
##  Mean   :201340   Mean   :6.670  
##  3rd Qu.:201803   3rd Qu.:6.975  
##  Max.   :202208   Max.   :8.066

Contamos con información desde 2005-01 a 2022-08. No contenemos NAs y los valores se manejan dentro del rango de 4 a 8 Quad-BTUs.

Representación gráfica y descomposición de la misma

Creamos objeto time series.

#Columna 2; Comienzo 2005-01; Frequencia mensual.
energy <- ts(data[2],start=c(2005,1),frequency=12) 

Visualizamos serie.

autoplot(energy)+  ggtitle("Consumo de energia mensual") +
  xlab("Mes") +  ylab("energy") +
  theme_minimal()

Descomponemos serie de tiempo con la funcion decompose(). Se pueden observar tanto los datos originales como la tendencia, la estacionalidad y el error.

Como se comentó previamente, el consumo energético tiene una componente estacional, como también puede verse en el tercer plot debajo. Así mismo, cabe destacar la caída del consumo en 2020, causado principalmente por la pandemia. Debido a que la estacionalidad varía a lo largo del tiempo, tanto por la pandemia como mayores valores al comienzo de la serie, se escogerá el método multiplicativo.

energy_comp<- decompose(energy,type=c("multiplicative"))

autoplot(energy_comp) +
  theme_minimal()

Vease como en Enero hay un 14% más consumo de energía que en la media anual, y en Abril un 9% menos. Luego en Junio y Julio sube arriba de la media, como tambien en Diciembre (9%).

energy_comp$seasonal[1:12]
##  [1] 1.1444442 1.0245120 1.0239235 0.9103513 0.9219512 0.9514534 1.0233716
##  [8] 1.0316964 0.9347594 0.9530934 0.9806625 1.0997812

De la misma manera, podemos ver tanto un seasonal plot del consumo de la energía, mostrando el consumo mensual para cada año particular, como tambien la serie desestacionalizada.

ggseasonplot(energy, year.labels=TRUE, year.labels.left=TRUE) +
  ylab("Consumo") +
  ggtitle("Seasonal plot: energy") + theme_minimal()

autoplot(energy, series="Datos") +
  autolayer(trendcycle(energy_comp), series="Tendencia") +
  autolayer(seasadj(energy_comp), series="Estacionalmente ajustada") +
  xlab("Year") + ylab("Energy") +
  ggtitle("Energy") +
  scale_colour_manual(values=c("gray","blue","red"),
                      breaks=c("Datos","Estacionalmente ajustada","Tendencia")) + theme_minimal()

Separamos últimos datos para evaluar predicciones futuras.

energy_TR<-window(energy,start=c(2005,1), end=c(2020,1))
energy_Test<- window(energy,start=c(2020,2))

Modelo de suavizado exponencial más adecuado

Debido a que la serie presenta estacionalidad, no tiene sentido realizar un modelo de alisado simple ni el de alisado doble de Holt

Por esto, se comenzará con el modelo Holt-Winters, con la función hw().

\(X_t = (L_t + b_t)*S_t + Z_t\)

fit_hw <- hw(energy_TR,h=12, seasonal="multiplicative")

autoplot(fit_hw) +
  autolayer(fitted(fit_hw), series="Fitted") +
  ylab("Energy") + xlab("Fecha") + theme_minimal()

summary(fit_hw)
## 
## Forecast method: Holt-Winters' multiplicative method
## 
## Model Information:
## Holt-Winters' multiplicative method 
## 
## Call:
##  hw(y = energy_TR, h = 12, seasonal = "multiplicative") 
## 
##   Smoothing parameters:
##     alpha = 0.4305 
##     beta  = 1e-04 
##     gamma = 1e-04 
## 
##   Initial states:
##     l = 7.129 
##     b = -0.0039 
##     s = 1.099 0.9786 0.953 0.9337 1.0315 1.0198
##            0.9536 0.9285 0.9167 1.0262 1.0196 1.1398
## 
##   sigma:  0.0275
## 
##      AIC     AICc      BIC 
## 345.5990 349.3536 399.9734 
## 
## Error measures:
##                       ME      RMSE       MAE         MPE     MAPE      MASE
## Training set 0.001392319 0.1803738 0.1403972 -0.02926616 2.072251 0.6467909
##                    ACF1
## Training set 0.03717615
## 
## Forecasts:
##          Point Forecast    Lo 80    Hi 80    Lo 95    Hi 95
## Feb 2020       6.668187 6.433577 6.902798 6.309382 7.026993
## Mar 2020       6.707867 6.450874 6.964859 6.314831 7.100903
## Apr 2020       5.988145 5.741381 6.234909 5.610752 6.365538
## May 2020       6.061636 5.795412 6.327860 5.654482 6.468791
## Jun 2020       6.222248 5.933058 6.511439 5.779970 6.664527
## Jul 2020       6.649813 6.324605 6.975021 6.152450 7.147175
## Aug 2020       6.722037 6.377721 7.066352 6.195452 7.248622
## Sep 2020       6.081316 5.756315 6.406317 5.584270 6.578362
## Oct 2020       6.203087 5.858330 6.547843 5.675827 6.730346
## Nov 2020       6.366458 5.999505 6.733410 5.805252 6.927663
## Dec 2020       7.145526 6.719430 7.571621 6.493869 7.797182
## Jan 2021       7.406042 6.950107 7.861978 6.708749 8.103336

De esta manera, la expresión del modelo sería la siguiente:

\(L_t = 0.4305(x_t/S_{t-s}) + (1-0.4305)(L_{t-1} + b_{t-1})\)

\(b_t = 0.0001(L_t - L_{t-1}) + 0.9999b_{t-1}\)

\(S_t = 0.0001(x_t/L_t) + (1-0.0001)S_{t-s}\)

\(\hat{x}_{t+1} = (L_t + b_t)*S_{t-s+1}\)

Funciones de autocorrelación y autocorrelación parcial

Con nuestra serie de tiempo podemos observar las funciones de autocorrelacion, tanto totales como parciales, para poder encontrar patrones e importancias de los retardos del consumo de energía sobre el consumo actual. Con la primera podemos medir la relación lineal entre las variables de la serie separadas por k posiciones, mientras que con la segunda medimos lo mismo pero ajustando por la presencia de los demás términos de desfase más corto.

Estas pueden ser analizadas a partir de correlogramas, donde vemos la significatividad de cada retardo y los que luego serán los parámetros de nuestro modelo, como también la estacionariedad o no de nuestra serie original.

Podemos utilizar la función ggtsdisplay() para visualizar esto. Notamos que no existe estacionariedad en la serie debido a que la ACF no se reduce rápidamente, sino que muestra importancias en períodos estacionales como por ejemplo los 12, 24 y 36 meses de diferencia.

La PACF nos sirve para identificar el modelo autorregresivo AR(). En este caso notamos importancia hasta el periodo 12. Sin embargo, hay intervalos medios en donde no se cruza la banda de confianza.

ggtsdisplay(energy_TR,theme=theme_minimal())

Para transformar la serie a una que sea estacionaria, podemos probar generando diferencias y modelando la estacionalidad.

Primera diferencia

energy_dif<-diff(energy_TR) 
ggtsdisplay(energy_dif,theme=theme_minimal())

Modelamos

modelo_dif1<-arima(energy_TR,order=c(0,1,0))
summary(modelo_dif1)
## 
## Call:
## arima(x = energy_TR, order = c(0, 1, 0))
## 
## 
## sigma^2 estimated as 0.2627:  log likelihood = -135.09,  aic = 272.18
## 
## Training set error measures:
##                        ME      RMSE       MAE        MPE     MAPE      MASE
## Training set -0.004296411 0.5110949 0.3987326 -0.3385827 5.928287 0.9945856
##                     ACF1
## Training set -0.06243303

Diferencia con estacionalidad anual

energy_dif12<-diff(energy_TR,12) %>% diff() 
ggtsdisplay(energy_dif12,theme=theme_minimal())

Podríamos probar un modelo sobre esta transformación con parametros AR(2) y MA(2), siguiendo lo que muestran los gráficos de ACF y PACF.

modelo_dif12<-arima(energy_TR,order=c(2,1,2), seasonal=c(1,1,1))
summary(modelo_dif12)
## 
## Call:
## arima(x = energy_TR, order = c(2, 1, 2), seasonal = c(1, 1, 1))
## 
## Coefficients:
##           ar1      ar2      ma1      ma2    sar1     sma1
##       -0.2655  -0.0831  -0.2479  -0.1871  0.2407  -0.9995
## s.e.   0.3649   0.2270   0.3610   0.3034  0.0913   0.5040
## 
## sigma^2 estimated as 0.03379:  log likelihood = 32.45,  aic = -50.91
## 
## Training set error measures:
##                       ME     RMSE       MAE        MPE     MAPE      MASE
## Training set 0.008231354 0.177116 0.1358394 0.07979956 2.014022 0.3388333
##                    ACF1
## Training set 0.01687959

Para obtener el mejor modelo posible, sin embargo, utilizaremos la función auto_arima() del paquete forecast. Esta encuentra el mejor modelo ARIMA según los criterios de información AIC, AICc y BIC.

A este le podemos indicar la presencia de estacionalidad y luego utiliza la función nsdiffs() para determinar la D (número de diferencias estacionales a utilizar), como tambien ndiffs() para determinar la d (número de diferencias ordinarias a utilizar).

Notamos como la función escoge como mejor modelo a ARIMA(2,0,1)(2,1,1)[12]. Este no diferencia la serie para el modelo general, sino que lo hace para el modelo estacional, una vez sola. El componente AR() del modelo general tiene como parametro a p=2, y del MA() a q=1. Los mismos parametros se ven en el modelo estacional, siendo P=2 y Q=1. Este modelo muestra una mejora menor comparado con el intentado previamente.

modelo <- auto.arima(energy_TR,seasonal=TRUE)

summary(modelo)
## Series: energy_TR 
## ARIMA(2,0,1)(2,1,1)[12] 
## 
## Coefficients:
##          ar1      ar2      ma1    sar1     sar2     sma1
##       1.0556  -0.0877  -0.6532  0.1412  -0.2949  -0.8014
## s.e.  0.0293   0.0303   0.0615  0.0363   0.0306   0.0744
## 
## sigma^2 = 0.03472:  log likelihood = 39.12
## AIC=-64.24   AICc=-63.55   BIC=-42.34
## 
## Training set error measures:
##                       ME      RMSE       MAE        MPE     MAPE     MASE
## Training set -0.01782103 0.1768301 0.1351414 -0.3107178 2.005757 0.622578
##                      ACF1
## Training set 0.0006198699

Podemos visualizar sus residuos. Vemos el comportamiento de white noise por lo que funciona correctamente. Tambien podemos confirmarlo con el test de Ljung-Box. Este tiene como hipótesis nula que los residuos estan independientemente distribuidos (son white noise). Con un P-Valor de 0.3932, no podemos rechazar H0, por lo que hay evidencia suficiente para reconocer que los errores del modelo son ruido blanco.

ggtsdisplay(residuals(modelo))

checkresiduals(modelo)

## 
##  Ljung-Box test
## 
## data:  Residuals from ARIMA(2,0,1)(2,1,1)[12]
## Q* = 18.978, df = 18, p-value = 0.3932
## 
## Model df: 6.   Total lags used: 24

De la misma manera, podemos observar los valores ajustados y los originales.

cbind("Energía" = energy_TR, "Valores ajustados" =fitted(modelo)) %>%
  autoplot() + xlab("Periodo") + ylab("") +   ggtitle("Consumo de energía")

Expresión algebraica del modelo ajustado

Para escribir la la expresión algebráica del modelo necesitamos obtener previamente sus coeficientes.

coeftest(modelo)
## 
## z test of coefficients:
## 
##       Estimate Std. Error  z value  Pr(>|z|)    
## ar1   1.055580   0.029340  35.9778 < 2.2e-16 ***
## ar2  -0.087700   0.030278  -2.8965 0.0037733 ** 
## ma1  -0.653191   0.061473 -10.6256 < 2.2e-16 ***
## sar1  0.141179   0.036297   3.8896 0.0001004 ***
## sar2 -0.294897   0.030629  -9.6279 < 2.2e-16 ***
## sma1 -0.801369   0.074354 -10.7778 < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

De esta manera, el modelo sería el siguiente:

\((1-1.055B + 0.087B^2)(1-0.141B^{12} + 0.294B^{24})(1-B^{12})X_t = (1 - 0.653B)(1 - 0.801B^{12})Z_t\)

Predicciones e intervalos de confianza

Calcularemos las predicciones para un horizonte temporal de 31 meses siguientes, hasta llegar al último mes disponible en nuestra serie de tiempo original.

autoplot(forecast(modelo),h=length(energy_Test)) + theme_minimal()

Y su summary: Notamos un AIC de -64 y un RMSE de 0.176. Así mismo observamos las predicciones y sus intervalos de confianza en formato tabla.

summary(forecast(modelo,h=length(energy_Test)))
## 
## Forecast method: ARIMA(2,0,1)(2,1,1)[12]
## 
## Model Information:
## Series: energy_TR 
## ARIMA(2,0,1)(2,1,1)[12] 
## 
## Coefficients:
##          ar1      ar2      ma1    sar1     sar2     sma1
##       1.0556  -0.0877  -0.6532  0.1412  -0.2949  -0.8014
## s.e.  0.0293   0.0303   0.0615  0.0363   0.0306   0.0744
## 
## sigma^2 = 0.03472:  log likelihood = 39.12
## AIC=-64.24   AICc=-63.55   BIC=-42.34
## 
## Error measures:
##                       ME      RMSE       MAE        MPE     MAPE     MASE
## Training set -0.01782103 0.1768301 0.1351414 -0.3107178 2.005757 0.622578
##                      ACF1
## Training set 0.0006198699
## 
## Forecasts:
##          Point Forecast    Lo 80    Hi 80    Lo 95    Hi 95
## Feb 2020       6.671215 6.432336 6.910095 6.305881 7.036549
## Mar 2020       6.715430 6.457942 6.972919 6.321636 7.109225
## Apr 2020       5.887477 5.617699 6.157255 5.474887 6.300067
## May 2020       6.037646 5.757221 6.318072 5.608772 6.466520
## Jun 2020       6.182813 5.892857 6.472769 5.739364 6.626263
## Jul 2020       6.690408 6.391859 6.988957 6.233817 7.146999
## Aug 2020       6.710649 6.404321 7.016976 6.242162 7.179135
## Sep 2020       6.115691 5.802300 6.429082 5.636400 6.594981
## Oct 2020       6.172771 5.852948 6.492594 5.683643 6.661898
## Nov 2020       6.414770 6.089076 6.740463 5.916664 6.912875
## Dec 2020       7.199739 6.868677 7.530800 6.693424 7.706053
## Jan 2021       7.385090 7.049121 7.721059 6.871270 7.898910
## Feb 2021       6.578407 6.215744 6.941070 6.023761 7.133052
## Mar 2021       6.677975 6.305251 7.050699 6.107943 7.248008
## Apr 2021       5.957875 5.576909 6.338842 5.375238 6.540513
## May 2021       6.047415 5.659011 6.435819 5.453402 6.641428
## Jun 2021       6.253863 5.858670 6.649056 5.649468 6.858258
## Jul 2021       6.715792 6.314386 7.117198 6.101895 7.329689
## Aug 2021       6.737732 6.330631 7.144833 6.115125 7.360339
## Sep 2021       6.126007 5.713678 6.538336 5.495405 6.756610
## Oct 2021       6.222855 5.805721 6.639989 5.584904 6.860806
## Nov 2021       6.409712 5.988157 6.831267 5.765000 7.054424
## Dec 2021       7.236814 6.811189 7.662440 6.585876 7.887753
## Jan 2022       7.598524 7.169158 8.027891 6.941865 8.255184
## Feb 2022       6.625576 6.194034 7.057118 5.965590 7.285563
## Mar 2022       6.777176 6.342956 7.211395 6.113094 7.441257
## Apr 2022       6.015388 5.578630 6.452147 5.347424 6.683353
## May 2022       6.100087 5.660973 6.539200 5.428521 6.771652
## Jun 2022       6.276112 5.834819 6.717405 5.601213 6.951012
## Jul 2022       6.764918 6.321607 7.208230 6.086932 7.442905
## Aug 2022       6.795712 6.350531 7.240893 6.114866 7.476557

Comparación de las predicciones obtenidas

Comparamos las predicciones contra el set test reservado previamente. Notamos que la performance empeora con el test como era de esperarse.

fit_modelo<-forecast(modelo,h=length(energy_Test),seasonal=TRUE)
accuracy(fit_modelo, x = energy_Test)
##                       ME      RMSE       MAE        MPE     MAPE     MASE
## Training set -0.01782103 0.1768301 0.1351414 -0.3107178 2.005757 0.622578
## Test set     -0.18458703 0.3291021 0.2174863 -3.2363344 3.721505 1.001930
##                      ACF1 Theil's U
## Training set 0.0006198699        NA
## Test set     0.6819457206 0.7198352

Sin embargo, las predicciones son acertadas, sin contar el año 2020 donde la pandemia tiene un peso negativo.

En rojo, los valores reales, en negro las predicciones.

fit_modelo %>%
  autoplot() +
  geom_line(
    aes(
      x = as.numeric(time(energy_Test)),
      y = as.numeric(energy_Test)
    ),
    col = "red"
  )

Podemos aplicar el modelo al set de testing y ver que los fitted values son cercanos a los originales.

modelo_test <- Arima(energy_Test, model = modelo)

one_step_forecasts <- fitted(modelo_test)

ggplot() +
  geom_line(
    aes(
      x = as.numeric(time(one_step_forecasts)),
      y = as.numeric(one_step_forecasts)
    ),
    col = "black"
  ) +
  geom_line(
    aes(
      x = as.numeric(time(energy_Test)),
      y = as.numeric(energy_Test)
    ),
    col = "red"
  )