Sesión 6: Modelando series de tiempo

Curso: Análisis de datos

Phd (c) Melanie Oyarzún - melanie.oyarzun@udd.cl

Magíster en Data Science - Universidad del Desarrollo

Overview

Resultado de aprendizaje esperado:

Modelar series temporales, eligiendo entre alternativas.

Bibliografía recomendada:

Stock & Watson, C.15 link ; Wooldridge, c.12 link, Gujarati, c.12 link

Paquetes que usaremos

# Paquetes y settings

from dateutil.parser import parse 
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import pandas as pd

# importamos librerías

import sklearn
from sklearn.linear_model import LinearRegression

import scipy
from scipy import stats

import statsmodels
import statsmodels.api as sm

# setting de graficos
plt.figure(figsize=(5,3), dpi= 200, facecolor='w', edgecolor='k')
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>

Aproximaciones al modelar series de tiempo

  • Como vimos, las series de tiempo tienen varias características que las hacen especiales.

    • No es posible el muestreo aleatorio
    • Pueden ser estacionarias o tener patrones de dependencia temporal.
  • Hay dos consecuencias de esto:

    1. Nuestros supuestos y propiedades estadísticas cambian al caso con muestra aletaoria.
    2. Tenemos diferentes tipos de modelos posibles, considerando los elementos temporales.

Tipos de modelos:

Modelo estático

Se modela la relación contemporánea entre dos variables, i.e., relación en el mismo momento en el tiempo

\[y_{t}=\beta_{0}+\beta_{1}z_{t}+u_{t}\]

Ejemplos:

  • Modelo Simple: \[ inflación_{t}=\beta_{0}+\beta_{1}desempleo_{t}+u_{t}\]

  • Modelo múltiple: \[ homicidio_{t}=\beta_{0}+\beta_{1}condena_{t}+\beta_{2}desmepleo_{t}+\beta_{3}hombres_{t}+u_{t} \]

Tipos de modelos:

Modelo dinámico

Se incluyen efectos temporales, que se piensan que tienen que ver con tendencias, inercia o estacionalidades

  • Un modelo con inercia en la inflación (por conceptos): \[ inflación_{t}=\beta_{0}+\beta_{1}desempleo_{t}+\beta_{2} inflación_{t-1} +u_{t}\]

  • Hay modelos conocidos:

    • Ejemplo: ARIMA de primer órden \[ \Delta inflacion_t = \phi \cdot \Delta inflacion_{t-1} + \varepsilon_t\]

Supuestos no cumplidos y opciones

Para poder interpretar causalmente los coeficientes en un modelo de regresión lineal multiple en series de tiempo necesitamos que se cumplan los siguientes supuestos de Gauss Markov (adaptados de ST):

  1. Modelo lineal en parámetros: \(y_{t}=\beta_{0}+\beta_{1}x_{1t}+\dots+\beta_{1}x_{kt}+u_{t}\)

  2. Media condicionada nula o exogeneidad (que ahora es estricta): \(E[u_{t}|X_{js}]=0 \quad \forall s\)

  3. No hay colinealidad perfecta.

  4. Homoscedasticidad \(Var[u_{t}|X_{js}]=\sigma \quad \forall s\)

  5. No hay correlación serial: \(Cov[u_{t}, u_{s}|X_{js}]=\sigma \quad \forall s\ne t\)

Vemos que hay dos elementos diferentes a corte transversal: (por que no hay Muestreo Aletorio) - cambia la exogeneidad a uno más estricto - y aparece un nuevo supuesto, correlación serial.

Supuestos no cumplidos y opciones

Cada uno de estos supuestos tiene diferentes implicancias:

  • 1-3 es que el estimador de MCO \(\hat{\beta}\) es insesgado.

  • 4-5 que tiene minima varianza de los estimadores lineales (eficiente)

  • En total, 1-5 -> Los estimadores de MCO son los mejores estimadores lineales insesgados (MELI)

Resumen de correlación serial:

  • Los datos de series temporales, generalmente están relacionadas con sus valores pasados.

  • Razones: inercia, reacciones rezagadas, entre otras.

  • Matemáticamente: Autocovarianza \[ Cov[u_{t}, u_{s}|X_{js}] \neq \sigma \quad \forall s\ne t\]

Correlación Serial y Auto-correlación

  • Cuando una variable depende de sus propios valores en el pasado se denomina autocorrelacion

  • Si los valores de una variable X en el presente están correlacionados con valores pasados de otra variable, Y, se conoce como correlación serial

  • A veces se usan los términos como sinónimos.

  • Podemos identificar la correlación serial de tres maneras:

    • Graficamente en los residuos
    • Estadísticamente mediante la prueba de WHite o de Breaush y Pagan.

¿Y si la serie es estacionaria?

  • Cuando la serie es estacionaria, podemos reemplazar el supuesto de exogeneidad extrica de vuelta por el de exogeneidad debil.

  • Podemos seguir trabajando como de construmbre

  • Así que un primer paso, es ver si la serie es estacionaria.

¿Y si la serie no es estacionaria?

  • Cuando tenermos patrones de dependencia intertemporal, estos supuestos ya no se cunplen.

  • Muy comun en ST:

    • Efecto rezagado (variable independiente rezagada)
    • Retroalimentación entre variables
    • Auto-dependencia, la variable depende de si misma en el pasado.

Objetivos diferentes de los modelos

  • Los modelos de regresión se pueden usar con dos objetivos principales: predicción y explicación.

  • En series de tiempo, como no hay muestreo aleatorio, nos enfocaremos en la predicciónn.

    • Un modelo de regresión puede ser útil para la predicción, aún cuando ninguno de sus coeficientes tenga interpretación causal.

    • Desde el punto de vista de la predicción, lo que es importante es que el modelo entregue una predicción lo más precisa posible.

  • La idea base es aprovechar las peculiaridades de las series temporales: la dependencia temporal y tendencias, para identificar patrones en la data que enriquezcan la predicción aun cuando no tengan valor explicativo. Estos elementos los incluiremos DENTRO de la regresión, para que represente de mejor manera el fenómeno a predecir.

Modelos explicativos en series temporales:

  • Los modelos que se centran en la explicación: Se basen en modelos teoricos estructurales que se corroboran en la data

  • Se usan otras aproximaciones empíricas como Causalidad de Granger

    • un concepto estadístico que evalúa si una serie temporal puede predecir otra serie temporal.
    • Ayuda a determinar si un conjunto de datos precedentes es útil para predecir un conjunto de datos posterior.
  • Un Concepto relacionado es la cointegración:

    • La cointegración identifica relaciones estadísticas a largo plazo entre series temporales, lo que puede ser relevante al evaluar la causalidad de Granger.
    • A pesar de tendencias o patrones a largo plazo, existe una combinación lineal que es estacionaria.
  • Volveremos a esta clase de modelos, al final de la sesión.

Modelando las series temporales:

Modelando series de Tiempo

  • Para modelar, entonces este tipo de datos recurriremos a una familia de modelos que se llaman auto-regresivos.

  • Esto quiere decir, que incluye rezagos como regresores para poder considerar la interdependencia temporal de los datos en el modelamiento.

  • Empezaremos con el caso más simple:

    • las Medias Móviles (MA),
    • luego Auto-regresivos (AR)
    • Los combinaremos en ARIMA y SARIMA
    • Mencionaremos algunos otros modelos muy usados, que nacen de esta base.

MEDIAS MOVILES (MA)

  • Empezamos por definir un modelo de media movil o moving average

  • Este es un modelo autorregresivo de “memoria corta”.

  • Matemáticamente, un modelo MA de primer orden o MA(1) es:

    \[ x_t=\epsilon_t + \theta \epsilon_{t-1} \]

  • Decimos que es de primer orden, porque tiene 1 rezago estocástico.

  • SI agregamos más lags, tiene mayor “memoria”

MEDIAS MOVILES (MA)

Ejemplos:

  • \(\Delta ddaheladost = \varepsilon_t - 0.5\varepsilon_{t-1}\), donde \(\varepsilon_t = \Delta temperaturat\)
  • \(\Delta preciopetroleot = \varepsilon_t + 0.5\varepsilon_{t-1}\), donde \(\varepsilon_t = \Delta hurac\'ant\)

Auto regresivo (AR)

  • Ahora definimos el modelo autorregresivo.

  • La función es similar a la MA pero usa posiciones pasivas en lugar de los valores estocásticos brutos

  • Su ecuación es: \[X_t = \alpha_0 + \rho X(t-1) + \varepsilon(t)\]

  • Ejemplo: \[\Delta preciopetroleot = \alpha_0 + 0.5\Delta preciopertoleot-1 + \varepsilon_t\] donde \(\varepsilon_t \sim \text{iid}(0, \sigma^2)\)

Medias móviles y Modelos autorregresivos

  • Estos modelos lo que hacen es modelar la “memoria” temporal del proceso estocástico.
    • MA(1) : memoria “corta”, impacto del shock no persiste mucho
    • AR(1) : memoria “larga”, impacto del shock persiste (en teoría, persiste infinitamente)
import numpy as np
import matplotlib.pyplot as plt

# Parámetros del modelo MA(1)
theta = 0.7
sigma = 1
n = 100  # Longitud de la serie de tiempo

# Simulación de la serie de tiempo MA(1)
ma_series = [np.random.normal(scale=sigma)]
for _ in range(1, n):
    ma_series.append(np.random.normal(scale=sigma) + theta * ma_series[-1])

# Parámetros del modelo AR(1)
phi = 0.7

# Simulación de la serie de tiempo AR(1)
ar_series = [np.random.normal(scale=sigma)]
for _ in range(1, n):
    ar_series.append(phi * ar_series[-1] + np.random.normal(scale=sigma))

# Crear gráficos
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
plt.plot(ma_series, label=f'MA(1) (theta={theta})')
plt.title('Serie de Tiempo MA(1)')
plt.legend()
plt.text(90, -6, 'Valores MA(1) se construyen con:\nX[t] = ε[t] + theta * ε[t-1]')

plt.subplot(2, 1, 2)
plt.plot(ar_series, label=f'AR(1) (phi={phi})', color='red')
plt.title('Serie de Tiempo AR(1)')
plt.legend()
plt.text(90, -5, 'Valores AR(1) se construyen con:\nX[t] = phi * X[t-1] + ε[t]')

plt.tight_layout()
plt.show()

AR vs AM

Podemos enr en el grafico de AUTO CORRELACION PARCIAL como se correlacionan los valores de la serie.

import numpy as np
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf


# Parámetros del modelo MA(1)
theta_ma = 0.5
sigma_ma = 1
n = 100

# Simulación de la serie de tiempo MA(1)
ma_series = [np.random.normal(scale=sigma_ma)]
for _ in range(1, n):
    ma_series.append(np.random.normal(scale=sigma_ma) + theta_ma * ma_series[-1])

# Parámetros del modelo AR(1)
rho_ar = 0.5
sigma_ar = 1

# Simulación de la serie de tiempo AR(1)
ar_series = [np.random.normal(scale=sigma_ar)]
for _ in range(1, n):
    ar_series.append(rho_ar * ar_series[-1] + np.random.normal(scale=sigma_ar))

# Graficar la ACF para MA(1)
plt.figure(figsize=(12, 6))
plt.subplot(2, 1, 1)
plot_acf(np.array(ma_series), ax=plt.gca(), title="ACF para MA(1)")

# Graficar la ACF para AR(1)
plt.subplot(2, 1, 2)
plot_acf(np.array(ar_series), ax=plt.gca(), title="ACF para AR(1)")

plt.tight_layout()
plt.show()

ARIMA y SARIMA

  • ARIMA que significa Autoregressive Integrated Moving-Average, es un modelamiento de serie de tiempo que combina modelos auto regresivos (AR) y medias moviles (MA), con la opcion de incluir raices unitarias.

  • Estos modelos tienen 3 parámetros:

    • p, que son cuantos autorregresores tiene el modelo AR,
    • q que indica cuantos parametros tiene la media movil y
    • Finalmente, la combinación de ambos modelamientos es \[ x_t=\epsilon_t + \phi x_{t-1} + \theta \epsilon_{t-1} \]
  • Si el modelo de los datos es estacionario, entonces no es necesario diferenciar y se usa d=0. Si no lo es, requerimos diferenciar con d>1.

  • Adicionalmente existe la clase SARIMA, o Seasonal-ARIMA que le agrega coeficientes que controlan por la estacionalidad.

ARIMA

Definimos unas funciones auxiliares, para diferenciar e integrar:

def differentiate(values, d=1):
    # First value is required so that we can recover the original values with np.cumsum
    x = np.concatenate([[values[0]], values[1:]-values[:-1]])

    if d == 1:
        return x
    else:    
        return differentiate(x, d - 1)
def integrate(values, d=1):
    x = np.cumsum(values)
    
    if d == 1:
        return x
    else:
        
        return integrate(x, d-1)

La clase de ARIMA es una variante de la definida en https://www.ritchievink.com/blog/2018/09/26/algorithm-breakdown-ar-ma-and-arima-models.

ARIMA

class ARIMA(LinearRegression):
    def __init__(self, q, d, p):
        """
        An ARIMA model.
        :param q: (int) Order of the MA model.
        :param p: (int) Order of the AR model.
        :param d: (int) Number of times the data needs to be differenced.
        """
        super().__init__(True)
        self.p = p
        self.d = d
        self.q = q
        self.ar = None
        self.resid = None
        
    def prepare_features(self, x):
        if self.d > 0:
            x = differentiate(x, self.d)
                    
        ar_features = None
        ma_features = None
        
        # Determine the features and the epsilon terms for the MA process
        if self.q > 0:
            if self.ar is None:
                self.ar = ARIMA(0, 0, self.p)
                self.ar.fit_predict(x)
            eps = self.ar.resid
            eps[0] = 0
            
            # prepend with zeros as there are no residuals_t-k in the first X_t
            ma_features = rolling(np.r_[np.zeros(self.q), eps], self.q)
            
        # Determine the features for the AR process
        if self.p > 0:
            # prepend with zeros as there are no X_t-k in the first X_t
            ar_features = rolling(np.r_[np.zeros(self.p), x], self.p)
                                
        if ar_features is not None and ma_features is not None:
            n = min(len(ar_features), len(ma_features)) 
            ar_features = ar_features[:n]
            ma_features = ma_features[:n]
            features = np.hstack((ar_features, ma_features))
        elif ma_features is not None: 
            n = len(ma_features)
            features = ma_features[:n]
        else:
            n = len(ar_features)
            features = ar_features[:n]
        
        return features, x[:n]
    
    def fit(self, x):
        features, x = self.prepare_features(x)
        super().fit(features, x)
        return features
            
    def fit_predict(self, x): 
        """
        Fit and transform input
        :param x: (array) with time series.
        """
        features = self.fit(x)
        return self.predict(x, prepared=(features))
    
    def predict(self, x, **kwargs):
        """
        :param x: (array)
        :kwargs:
            prepared: (tpl) containing the features, eps and x
        """
        features = kwargs.get('prepared', None)
        if features is None:
            features, x = self.prepare_features(x)
        
        y = super().predict(features)
        self.resid = x - y

        return self.return_output(y)
    
    def return_output(self, x):
        if self.d > 0:
            x = integrate(x, self.d) 
        return x
    
    def forecast(self, x, n):
        """
        Forecast the time series.
        
        :param x: (array) Current time steps.
        :param n: (int) Number of time steps in the future.
        """
        features, x = self.prepare_features(x)
        y = super().predict(features)
        
        # Append n time steps as zeros. Because the epsilon terms are unknown
        y = np.r_[y, np.zeros(n)]
        for i in range(n):
            feat = np.r_[y[-(self.p + n) + i: -n + i], np.zeros(self.q)]
            y[x.shape[0] + i] = super().predict(feat[None, :])
        return self.return_output(y)

ARIMA

  • Tambien se puede importar directamente desde statsmodels donde debemos proveer p, d y q.
from statsmodels.tsa.arima.model import ARIMA
import statsmodels.api as sm

ARIMA

  • Estimemos el modelo con
    • p=1 (coeficientes AR),
    • d=1 (orden de diferenciacion),
    • q=1 (coeficientes MA).
    • Notar que d realiza la diferenciación y le saca la tendencia a la serie, segun los resultados del ADF.
  • Pasemos a un ejemplo real, volvamos a la serie de GDP que trabajamos al principio:
#Importamos desde la carpeta
GDP = pd.read_csv('data_sesion5/GDP.csv', parse_dates=['DATE'])
GDP.set_index('DATE', inplace=True)

ARIMA

Ajustemos un modelo ARIMA de 1er orden, en la primera diferencia.

modelo1_GDP = ARIMA(GDP, order=(1,1,1))
modelo1_fit=modelo1_GDP.fit()
modelo1_fit.summary()
SARIMAX Results
Dep. Variable: GDP No. Observations: 290
Model: ARIMA(1, 1, 1) Log Likelihood -1607.431
Date: Wed, 08 Nov 2023 AIC 3220.861
Time: 10:43:50 BIC 3231.861
Sample: 01-01-1947 HQIC 3225.269
- 04-01-2019
Covariance Type: opg
coef std err z P>|z| [0.025 0.975]
ar.L1 0.9416 0.024 38.775 0.000 0.894 0.989
ma.L1 -0.5952 0.050 -11.800 0.000 -0.694 -0.496
sigma2 3955.1632 221.551 17.852 0.000 3520.931 4389.395
Ljung-Box (L1) (Q): 0.06 Jarque-Bera (JB): 135.32
Prob(Q): 0.81 Prob(JB): 0.00
Heteroskedasticity (H): 4.53 Skew: -0.42
Prob(H) (two-sided): 0.00 Kurtosis: 6.25


Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).

ARIMA

Adicionalmente, está la estimacion con autoarima, que elije automaticamente p, q y d (minimizando AIC).

#!pip install pmdarima
from pmdarima.arima import auto_arima
modelo2_GDP = auto_arima(GDP, start_P=0, start_Q=0)
modelo2_GDP.summary()
SARIMAX Results
Dep. Variable: y No. Observations: 290
Model: SARIMAX(2, 2, 1) Log Likelihood -1592.341
Date: Wed, 08 Nov 2023 AIC 3192.681
Time: 10:43:52 BIC 3207.333
Sample: 01-01-1947 HQIC 3198.553
- 04-01-2019
Covariance Type: opg
coef std err z P>|z| [0.025 0.975]
ar.L1 0.2925 0.046 6.353 0.000 0.202 0.383
ar.L2 0.1677 0.051 3.297 0.001 0.068 0.267
ma.L1 -0.9765 0.014 -68.045 0.000 -1.005 -0.948
sigma2 3688.7172 195.218 18.895 0.000 3306.098 4071.337
Ljung-Box (L1) (Q): 0.04 Jarque-Bera (JB): 367.68
Prob(Q): 0.85 Prob(JB): 0.00
Heteroskedasticity (H): 4.81 Skew: -0.85
Prob(H) (two-sided): 0.00 Kurtosis: 8.26


Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).

Podemos ver que el algoritmo eligio un (2,2,1) para (p,d,q).

Eligiendo un modelo

  • Generalmente, la eleccion de modelos y la longitud de sus rezagos se hace mediante AIC o BIC.

  • AIC (Akaike Information Criterion) \[ BIC(p) = log\left( \frac{SSR(p)}{T} \right) + (p + 1) \frac{log(T)}{T}\]

    • Menor AIC indica un mejor modelo
  • BIC (Bayesian Information Criterion)

    \[ AIC(p) = log\left( \frac{SSR(p)}{T} \right) + (p + 1) \frac{2}{T}\]

    • Muy similar a AIC
    • Menor BIC indicates a better model

AIC vs BIC

  • La diferencia entre ambos, es como penalizan la complejidad del modelo.
  • BIC favorece modelos más simples que AIC.
  • Generalmente, AIC se prefiere para modelos predictivos y BIC para explicativos.

Ejemplo

  • En nuestro ejemplo anterior, teniamos estimados dos modelos por ARIMA: modelo 1 (1,0,1) y modelo 2(2,2,1).
  • SI tuvieramos que elegir uno de estos, deberiamos compara el AIC y/o BIC de ambos.
modelo1_fit.summary()
SARIMAX Results
Dep. Variable: GDP No. Observations: 290
Model: ARIMA(1, 1, 1) Log Likelihood -1607.431
Date: Wed, 08 Nov 2023 AIC 3220.861
Time: 10:43:52 BIC 3231.861
Sample: 01-01-1947 HQIC 3225.269
- 04-01-2019
Covariance Type: opg
coef std err z P>|z| [0.025 0.975]
ar.L1 0.9416 0.024 38.775 0.000 0.894 0.989
ma.L1 -0.5952 0.050 -11.800 0.000 -0.694 -0.496
sigma2 3955.1632 221.551 17.852 0.000 3520.931 4389.395
Ljung-Box (L1) (Q): 0.06 Jarque-Bera (JB): 135.32
Prob(Q): 0.81 Prob(JB): 0.00
Heteroskedasticity (H): 4.53 Skew: -0.42
Prob(H) (two-sided): 0.00 Kurtosis: 6.25


Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
modelo2_GDP.summary()
SARIMAX Results
Dep. Variable: y No. Observations: 290
Model: SARIMAX(2, 2, 1) Log Likelihood -1592.341
Date: Wed, 08 Nov 2023 AIC 3192.681
Time: 10:43:52 BIC 3207.333
Sample: 01-01-1947 HQIC 3198.553
- 04-01-2019
Covariance Type: opg
coef std err z P>|z| [0.025 0.975]
ar.L1 0.2925 0.046 6.353 0.000 0.202 0.383
ar.L2 0.1677 0.051 3.297 0.001 0.068 0.267
ma.L1 -0.9765 0.014 -68.045 0.000 -1.005 -0.948
sigma2 3688.7172 195.218 18.895 0.000 3306.098 4071.337
Ljung-Box (L1) (Q): 0.04 Jarque-Bera (JB): 367.68
Prob(Q): 0.85 Prob(JB): 0.00
Heteroskedasticity (H): 4.81 Skew: -0.85
Prob(H) (two-sided): 0.00 Kurtosis: 8.26


Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
print("AIC Modelo 1:", modelo1_fit.aic)
print("AIC Modelo 2:", modelo2_GDP.aic())

print("BIC Modelo 1:", modelo1_fit.bic)
print("BIC Modelo 2:", modelo2_GDP.bic())
AIC Modelo 1: 3220.8613349084044
AIC Modelo 2: 3192.681051377708
BIC Modelo 1: 3231.8606149727416
BIC Modelo 2: 3207.3328932982517
  • Podemos ver que el modelo 2, elegido por AUTOARIMA, es el de menor AIC y BIC.

Modelamiento y predicción

  • Con los elementos que hemos revisado, vamos a realizar un modelamiento con el 80% de la muestra y revisar su ajuste con el 20% restante.

  • Luego con ese modelo, vamos a generar una predicción fuera de la muestra.

# Proporción entrenamiento y chequeo de ajuste

train_size = 0.8
split_idx = round(len(GDP)* train_size)
split_idx

# Split
train = GDP.iloc[:split_idx]
test = GDP.iloc[split_idx:]

# Visualizamos la division
fig,ax= plt.subplots(figsize=(12,8))
kws = dict(marker='o')
plt.plot(train, label='Train', **kws)
plt.plot(test, label='Test', **kws)
plt.legend(loc="lower right")

Modelamiento y predicción

  • Ajustamos el modelo a los datos de entrenamiento.
  • La opcion summary nos provee toda la información de nuestro modelo.
model=auto_arima(train, start_p=0, start_q=0)

model.summary()
SARIMAX Results
Dep. Variable: y No. Observations: 232
Model: SARIMAX(2, 2, 1) Log Likelihood -1237.743
Date: Wed, 08 Nov 2023 AIC 2483.486
Time: 10:43:54 BIC 2497.238
Sample: 01-01-1947 HQIC 2489.033
- 10-01-2004
Covariance Type: opg
coef std err z P>|z| [0.025 0.975]
ar.L1 0.2536 0.066 3.818 0.000 0.123 0.384
ar.L2 0.1840 0.053 3.492 0.000 0.081 0.287
ma.L1 -0.9661 0.019 -51.604 0.000 -1.003 -0.929
sigma2 2745.0843 194.607 14.106 0.000 2363.661 3126.508
Ljung-Box (L1) (Q): 0.00 Jarque-Bera (JB): 22.21
Prob(Q): 0.96 Prob(JB): 0.00
Heteroskedasticity (H): 2.73 Skew: 0.01
Prob(H) (two-sided): 0.00 Kurtosis: 4.52


Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).

Modelamiento y predicción

También podemos revisar lo graficos del modelo.

model.plot_diagnostics()

Valores Ajustados- Predichos

  • Usando el modelo que obtuvimos con los datos de entrenamientom vamos a revisar su ajuste prediciendo los datos de prueba.
  • No confundir, valores ajustados con proyección fuera de la muestra.
  • La idea es comrbobar que tan bien el modelo predice los datos ya observados.
#total de valores a predecir en muestra de prueba
len(test)
58
prediccion = pd.DataFrame( model.predict(n_periods=len(test)), index=test.index )
prediccion.columns = ['predicted GDP']
prediccion.head()
predicted GDP
DATE
2005-01-01 14725.301327
2005-04-01 14834.990121
2005-07-01 14937.802087
2005-10-01 15037.814690
2006-01-01 15135.852037

Valores Ajustados- Predichos

  • Revisemos como se compara la predicción con los datos reales
plt.subplots(figsize=(12,8))
plt.plot(train, label="training")
plt.plot(test, label="test")
plt.plot(prediccion, label="Predicho")
plt.legend(loc="lower right")
plt.show
<function matplotlib.pyplot.show(close=None, block=None)>

Valores Ajustados- Predichos

  • Podemos ver que el modelo no ajusta tan bien, porque está incluido el dato del 2009, donde ocurre la crisis SubPrime.
  • Veamos que ocurre si ampliamos el rango de entrenamiento para que lo incluya en el modelo y la predicción se haga sobre un menor rango.
# Proporción entrenamiento y chequeo de ajuste

train_size = 0.9
split_idx = round(len(GDP)* train_size)
split_idx

# Split
train = GDP.iloc[:split_idx]
test = GDP.iloc[split_idx:]

# Visualizamos la division
kws = dict(marker='o')
plt.subplots(figsize=(12,8))
plt.plot(train, label='Train', **kws)
plt.plot(test, label='Test', **kws)
plt.legend(loc="lower right")

Valores Ajustados- Predichos

Usemos un modelo autoregresivo:

model=auto_arima(train, start_p=0, start_q=0)

model.summary()
SARIMAX Results
Dep. Variable: y No. Observations: 261
Model: SARIMAX(2, 2, 1) Log Likelihood -1429.762
Date: Wed, 08 Nov 2023 AIC 2867.525
Time: 10:43:57 BIC 2881.752
Sample: 01-01-1947 HQIC 2873.245
- 01-01-2012
Covariance Type: opg
coef std err z P>|z| [0.025 0.975]
ar.L1 0.3138 0.052 6.026 0.000 0.212 0.416
ar.L2 0.1815 0.053 3.409 0.001 0.077 0.286
ma.L1 -0.9786 0.014 -71.619 0.000 -1.005 -0.952
sigma2 3622.7638 208.829 17.348 0.000 3213.467 4032.061
Ljung-Box (L1) (Q): 0.00 Jarque-Bera (JB): 385.11
Prob(Q): 1.00 Prob(JB): 0.00
Heteroskedasticity (H): 5.61 Skew: -0.94
Prob(H) (two-sided): 0.00 Kurtosis: 8.67


Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).

Valores Ajustados- Predichos

prediccion = pd.DataFrame( model.predict(n_periods=len(test)), index=test.index )
prediccion.columns = ['predicted GDP']
prediccion.head()
predicted GDP
DATE
2012-04-01 16238.065497
2012-07-01 16330.940263
2012-10-01 16415.841822
2013-01-01 16495.378984
2013-04-01 16571.785899

Valores Ajustados- Predichos

plt.subplots(figsize=(12,8))
plt.plot(train, label="training")
plt.plot(test, label="test")
plt.plot(prediccion, label="Predicho")
plt.legend(loc="lower right")
plt.show
<function matplotlib.pyplot.show(close=None, block=None)>

  • Si nos fijamos, ahora la predicción es mucho mejor. - - Podemos seguir refinando la elección de tiempo, especialmente al considerar la posibilidad de quiebres estructurales.

Predicciones fuera de muestra

  • Los modelos ARIMA son muy buenos creando predicciones fuera de muestra en un paso, es decir, el siguiente periodo después de los datos.
model = ARIMA(GDP, order=(2,2,1))
results = model.fit()
print(results.summary())
                               SARIMAX Results                                
==============================================================================
Dep. Variable:                    GDP   No. Observations:                  290
Model:                 ARIMA(2, 2, 1)   Log Likelihood               -1592.341
Date:                Wed, 08 Nov 2023   AIC                           3192.681
Time:                        10:43:58   BIC                           3207.333
Sample:                    01-01-1947   HQIC                          3198.553
                         - 04-01-2019                                         
Covariance Type:                  opg                                         
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
ar.L1          0.2925      0.046      6.353      0.000       0.202       0.383
ar.L2          0.1677      0.051      3.297      0.001       0.068       0.267
ma.L1         -0.9765      0.014    -68.045      0.000      -1.005      -0.948
sigma2      3688.7172    195.218     18.895      0.000    3306.098    4071.337
===================================================================================
Ljung-Box (L1) (Q):                   0.04   Jarque-Bera (JB):               367.68
Prob(Q):                              0.85   Prob(JB):                         0.00
Heteroskedasticity (H):               4.81   Skew:                            -0.85
Prob(H) (two-sided):                  0.00   Kurtosis:                         8.26
===================================================================================

Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).

Predicciones fuera de muestra

results.forecast(steps=1)
2019-07-01    19120.265611
Freq: QS-OCT, dtype: float64

Predicciones fuera de muestra

También esta lo que se llama multi-step out of sample forecast o predicción fuera de la muestra en multiples pasos.

EN esta se va estimando paso a paso un periodo adicional, incluyendolo en en analisis, actualizando el modelo y re-estimando.

Por ejemplo, estimemos para los siguientes 20 periodos.

pred=results.get_forecast(steps=20, dynamic=False )
pred_ci= pred.conf_int()
ax = GDP['1990':].plot(label='observed')
pred.predicted_mean.plot(ax=ax, label='One-step ahead Forecast', alpha=.7)

ax.fill_between(pred_ci.index,
                pred_ci.iloc[:, 0],
                pred_ci.iloc[:, 1], color='k', alpha=.2)

ax.set_xlabel('Año')
ax.set_ylabel('GDP')
plt.legend()
plt.show()

Predicciones fuera de muestra

Finalmente, vamos a agergarle estos a un nuevo dataframe que incluya la predicción.

from pandas.tseries.offsets import DateOffset

future_dates=[GDP.index[-1]+ DateOffset(years=x)for x in range(-2,10)]
future_datest_df=pd.DataFrame(index=future_dates[1:],columns=GDP.columns)

future_datest_df
GDP
2018-04-01 NaN
2019-04-01 NaN
2020-04-01 NaN
2021-04-01 NaN
2022-04-01 NaN
2023-04-01 NaN
2024-04-01 NaN
2025-04-01 NaN
2026-04-01 NaN
2027-04-01 NaN
2028-04-01 NaN
forecast_df=pd.DataFrame(pred.predicted_mean)

NaN=np.nan

forecast_df['GDP']=NaN
forecast_df
predicted_mean GDP
2019-07-01 19120.265611 NaN
2019-10-01 19211.547973 NaN
2020-01-01 19301.388591 NaN
2020-04-01 19389.613055 NaN
2020-07-01 19477.123064 NaN
2020-10-01 19564.153101 NaN
2021-01-01 19650.922952 NaN
2021-04-01 19737.536218 NaN
2021-07-01 19824.060056 NaN
2021-10-01 19910.531481 NaN
2022-01-01 19996.972580 NaN
2022-04-01 20083.396021 NaN
2022-07-01 20169.809212 NaN
2022-10-01 20256.216443 NaN
2023-01-01 20342.620213 NaN
2023-04-01 20429.021972 NaN
2023-07-01 20515.422561 NaN
2023-10-01 20601.822471 NaN
2024-01-01 20688.221987 NaN
2024-04-01 20774.621273 NaN
GDP_forecast= pd.DataFrame(GDP['GDP'])
GDP_forecast['predicted_mean']=NaN
GDP_forecast
GDP predicted_mean
DATE
1947-01-01 2033.061 NaN
1947-04-01 2027.639 NaN
1947-07-01 2023.452 NaN
1947-10-01 2055.103 NaN
1948-01-01 2086.017 NaN
... ... ...
2018-04-01 18598.135 NaN
2018-07-01 18732.720 NaN
2018-10-01 18783.548 NaN
2019-01-01 18927.281 NaN
2019-04-01 19021.860 NaN

290 rows × 2 columns

GDP_forecast=pd.concat([GDP_forecast,forecast_df])
GDP_forecast
GDP predicted_mean
1947-01-01 2033.061 NaN
1947-04-01 2027.639 NaN
1947-07-01 2023.452 NaN
1947-10-01 2055.103 NaN
1948-01-01 2086.017 NaN
... ... ...
2023-04-01 NaN 20429.021972
2023-07-01 NaN 20515.422561
2023-10-01 NaN 20601.822471
2024-01-01 NaN 20688.221987
2024-04-01 NaN 20774.621273

310 rows × 2 columns

Predicciones fuera de muestra

Graficamente:

GDP_forecast[['GDP', 'predicted_mean']].plot(figsize=(12, 8))
<Axes: >

Modelos VAR (Vector AutoRegression)

  • Es un modelo estadístico que se utiliza para modelar y predecir múltiples series de tiempo en conjunto. Es una extensión de los modelos autorregresivos (AR) que se aplican a múltiples variables simultáneamente.

Parámetros del modelo VAR: - p: Representa la cantidad de retardos (lags) utilizados en el modelo autorregresivo. Cada serie de tiempo se regresa en el tiempo hasta p períodos anteriores. - q: Indica la cantidad de términos de media móvil utilizados en el modelo. La media móvil considera las observaciones anteriores de los errores.

  • Matemáticamente:

    $ x_t = _t + 1 x{t-1} + 2 x{t-2} + + p x{t-p} + 1 {t-1} + 2 {t-2} + + q {t-q} $

  • Donde (x_t) es una serie de tiempo en el tiempo (t), (_t) es el error en el tiempo (t), (_i) son los coeficientes autorregresivos y (_i) son los coeficientes de media móvil.

  • El modelo VAR considera múltiples series de tiempo simultáneamente.

Modelos VAR (Vector AutoRegression)

Estacionariedad y diferenciación en VAR: - Al igual que en ARIMA, la estacionariedad es un concepto importante en VAR. - Si las series de tiempo son estacionarias, no es necesario realizar diferenciación ((d=0)). - Sin embargo, si las series no son estacionarias, se requiere diferenciación ((d>0)) para convertirlas en series estacionarias antes de aplicar el modelo VAR.

Modelos VAR (Vector AutoRegression)

Ejemplo:

  • Supongamos que estamos interesados en modelar el comportamiento de dos series de tiempo:

  • el precio de una acción (S) y el volumen de transacciones (V).

  • Podríamos utilizar un modelo VAR(2) que representa la relación de cada serie con sus dos valores anteriores:

  • Para el precio de la acción (S):

  • Revsisaremos un ejemplo aplicado al final de la clase.

Granger Causality:

  • Intuicion: una ts puede ayudar a predecir otra ts
  • No es causalidad en sí, es una versión “fruna”, que si se cumple es consistente con una dirección causal, lo que puede ser bastante útil.
  • Tres pasos: 1. Mejor modelo AR para la serie 1 2. Se añaden términos de la serie 2 y sus lags y se prueban haciendo test-t para ver cada lag es por si mismo es significativo, o sea, útil para predecir la serie 1. Al final, vamos a tener algunos lags de la serie 2 en el modelo, se hace un F-test para ver si todos juntos mejoran el modelo. 3. Finalmente se concluye que si algún lag de la serie 2 pasa las pruebas, se dice que la serie 2 “Granger causes” la serie 1. Esto significa que encontramos que si conocemos información sobre los rezagos de la serie 2, vamos a poder predecir significativamente mejor la serie 1.
from statsmodels.tsa.stattools import grangercausalitytests
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
#Construyamos la serie de tiempo, solo un AR(1) simple
t1 = [0.1*np.random.normal()]
for _ in range(100):
    t1.append(0.5*t1[-1] + 0.1*np.random.normal())
# Construyamos la serie 2 que es granger caused por t1
t2 = [item + 0.1*np.random.normal() for item in t1]
#ajustar t1 y t2
t1 = t1[3:]
t2 = t2[:-3]
plt.figure(figsize=(10,4))
plt.plot(t1, color='b')
plt.plot(t2, color='r')

plt.legend(['t1', 't2'], fontsize=16)

ts_df = pd.DataFrame(columns=['t2', 't1'], data=zip(t2,t1))
ts_df
t2 t1
0 0.028738 0.274712
1 0.027427 0.115285
2 0.056531 0.203435
3 0.278024 0.072625
4 0.057692 -0.203738
... ... ...
93 0.050557 -0.052848
94 0.013699 -0.091523
95 -0.105622 0.064698
96 -0.095261 0.213272
97 -0.117295 0.081767

98 rows × 2 columns

gc_res = grangercausalitytests(ts_df, 3)

Granger Causality
number of lags (no zero) 1
ssr based F test:         F=6.5813  , p=0.0119  , df_denom=94, df_num=1
ssr based chi2 test:   chi2=6.7913  , p=0.0092  , df=1
likelihood ratio test: chi2=6.5641  , p=0.0104  , df=1
parameter F test:         F=6.5813  , p=0.0119  , df_denom=94, df_num=1

Granger Causality
number of lags (no zero) 2
ssr based F test:         F=7.1223  , p=0.0013  , df_denom=91, df_num=2
ssr based chi2 test:   chi2=15.0273 , p=0.0005  , df=2
likelihood ratio test: chi2=13.9611 , p=0.0009  , df=2
parameter F test:         F=7.1223  , p=0.0013  , df_denom=91, df_num=2

Granger Causality
number of lags (no zero) 3
ssr based F test:         F=48.7412 , p=0.0000  , df_denom=88, df_num=3
ssr based chi2 test:   chi2=157.8549, p=0.0000  , df=3
likelihood ratio test: chi2=92.9992 , p=0.0000  , df=3
parameter F test:         F=48.7412 , p=0.0000  , df_denom=88, df_num=3

No hay evidencia de Granger causality para lags 1 y 2, pero si para lag=3!!!

Resumen general Series de tiempo

Hemos visto varios elementos de estacionareidad y autocorrelación, reunamoslo en un análisis compacto.

  1. Chequeamos la estacionareidad usando media y varianza movil y el test de Dickey-Fuller aumentado.

Para esto creamos una función, que depende de una ventana para los análisis (w) y con esta, revisamos la serie de tiempo (ts)

from statsmodels import tsa
from statsmodels.tsa.stattools import adfuller

def stationarity_check(ts, w):
            
    # Calculate rolling statistics
    roll_mean = ts.rolling(window=w, center=False).mean()
    roll_std = ts.rolling(window=w, center=False).std()

    # Perform the Dickey Fuller test
    dftest = adfuller(ts) 
    
    # Plot rolling statistics:
    fig = plt.figure(figsize=(12,6))
    orig = plt.plot(ts, color='blue',label='Original')
    mean = plt.plot(roll_mean, color='red', label='Rolling Mean')
    std = plt.plot(roll_std, color='green', label = 'Rolling Std')
    plt.legend(loc='best')
    plt.title('Rolling Mean & Standard Deviation')
    plt.show(block=False)
    
    # Print Dickey-Fuller test results

    print('\nResults of Dickey-Fuller Test: \n')

    dfoutput = pd.Series(dftest[0:4], index=['Test Statistic', 'p-value', 
                                             '#Lags Used', 'Number of Observations Used'])
    for key, value in dftest[4].items():
        dfoutput['Critical Value (%s)'%key] = value
    print(dfoutput)
#ejemplo con GPD y w=10

stationarity_check(GDP, w=10)


Results of Dickey-Fuller Test: 

Test Statistic                   2.962229
p-value                          1.000000
#Lags Used                      12.000000
Number of Observations Used    277.000000
Critical Value (1%)             -3.454180
Critical Value (5%)             -2.872031
Critical Value (10%)            -2.572360
dtype: float64

Claramente la serie NO es estacionaria. Ahora necesitamos sacarle las tendencias para proceder

  1. Aplicamos una descomposición con seasonal_dceomposition() desde statsmodels.tsa.seasonal para visualizar las tendencias, la estacionalidad y residuos.
from statsmodels.tsa.seasonal import seasonal_decompose


def decomposition_plot(ts):
# Apply seasonal_decompose 
    decomposition = seasonal_decompose(np.log(ts))
    
# Get trend, seasonality, and residuals
    trend = decomposition.trend
    seasonal = decomposition.seasonal
    residual = decomposition.resid

# Plotting
    plt.figure(figsize=(12,8))
    plt.subplot(411)
    plt.plot(np.log(ts), label='Original', color='blue')
    plt.legend(loc='best')
    plt.subplot(412)
    plt.plot(trend, label='Trend', color='blue')
    plt.legend(loc='best')
    plt.subplot(413)
    plt.plot(seasonal,label='Seasonality', color='blue')
    plt.legend(loc='best')
    plt.subplot(414)
    plt.plot(residual, label='Residuals', color='blue')
    plt.legend(loc='best')
    plt.tight_layout()
decomposition_plot(GDP)

  1. Realizamos una diferenciación para eliminar las tendencias.
GDP['1 diff']= GDP['GDP']-GDP['GDP'].shift(1)
GDP
GDP 1 diff
DATE
1947-01-01 2033.061 NaN
1947-04-01 2027.639 -5.422
1947-07-01 2023.452 -4.187
1947-10-01 2055.103 31.651
1948-01-01 2086.017 30.914
... ... ...
2018-04-01 18598.135 159.881
2018-07-01 18732.720 134.585
2018-10-01 18783.548 50.828
2019-01-01 18927.281 143.733
2019-04-01 19021.860 94.579

290 rows × 2 columns

A=pd.DataFrame(GDP['1 diff'])
A=A.dropna() # ya que al diferenciar se pierden observaciones

Chequeamos si la serie diferenciada elimina la tendencia. Sino, volvemos a diferenciar.

stationarity_check(A, 10)


Results of Dickey-Fuller Test: 

Test Statistic                  -4.109942
p-value                          0.000933
#Lags Used                      11.000000
Number of Observations Used    277.000000
Critical Value (1%)             -3.454180
Critical Value (5%)             -2.872031
Critical Value (10%)            -2.572360
dtype: float64
  1. Graficamos la autocorrelacion total y parcial, para proveer informacion sobre posibles (p,d,q) para modelar.
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
def plot_acf_pacf(ts, figsize=(10,8),lags=30):
    
    fig,ax = plt.subplots(nrows=3, figsize=figsize)
    
    # Plot ts
    ts.plot(ax=ax[0])
    
    # Plot acf, pavf
    plot_acf(ts.dropna(), ax=ax[1], lags=lags)
    plot_pacf(ts.dropna(), ax=ax[2], lags=lags) 
    fig.tight_layout()
 plot_acf_pacf(GDP['GDP'], figsize=(10,8),lags=30)

  1. Proceder con la modelacion, ya sea usando ARIMA o auto_arima. Y en base a este realizar comprobaciones de ajuste o predicciones.
model=auto_arima(GDP['GDP'], start_p=0, start_q=0)

model.summary()
SARIMAX Results
Dep. Variable: y No. Observations: 290
Model: SARIMAX(2, 2, 1) Log Likelihood -1592.341
Date: Wed, 08 Nov 2023 AIC 3192.681
Time: 10:44:02 BIC 3207.333
Sample: 01-01-1947 HQIC 3198.553
- 04-01-2019
Covariance Type: opg
coef std err z P>|z| [0.025 0.975]
ar.L1 0.2925 0.046 6.353 0.000 0.202 0.383
ar.L2 0.1677 0.051 3.297 0.001 0.068 0.267
ma.L1 -0.9765 0.014 -68.045 0.000 -1.005 -0.948
sigma2 3688.7172 195.218 18.895 0.000 3306.098 4071.337
Ljung-Box (L1) (Q): 0.04 Jarque-Bera (JB): 367.68
Prob(Q): 0.85 Prob(JB): 0.00
Heteroskedasticity (H): 4.81 Skew: -0.85
Prob(H) (two-sided): 0.00 Kurtosis: 8.26


Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).

Ejemplo Modelo VAR (Vector AutoRegression)

import pandas as pd
from datetime import datetime
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
from statsmodels.tsa.api import VAR
from scipy.stats import pearsonr
def parser(s):
    return datetime.strptime(s, '%Y-%m')
ice_cream_heater_df = pd.read_csv('data_sesion5/ice_cream_vs_heater.csv', parse_dates=[0], index_col=0, squeeze=True, date_parser=parser)
ice_cream_heater_df = ice_cream_heater_df.asfreq(pd.infer_freq(ice_cream_heater_df.index))
plt.figure(figsize=(12,6))
ice_cream, = plt.plot(ice_cream_heater_df['ice cream'])
heater, = plt.plot(ice_cream_heater_df['heater'], color='red')

for year in range(2004, 2021):
    plt.axvline(datetime(year,1,1), linestyle='--', color='k', alpha=0.5)

plt.legend(['Ice Cream', 'Heater'], fontsize=16)

Normalizando

avgs = ice_cream_heater_df.mean()
devs = ice_cream_heater_df.std()
for col in ice_cream_heater_df.columns:
    ice_cream_heater_df[col] = (ice_cream_heater_df[col] - avgs.loc[col]) / devs.loc[col]
plt.figure(figsize=(12,6))
ice_cream, = plt.plot(ice_cream_heater_df['ice cream'])
heater, = plt.plot(ice_cream_heater_df['heater'], color='red')

for year in range(2004, 2021):
    plt.axvline(datetime(year,1,1), linestyle='--', color='k', alpha=0.5)
    
plt.axhline(0, linestyle='--', color='k', alpha=0.3)

plt.legend(['Ice Cream', 'Heater'], fontsize=16)

Tomando la primera diferencia para remover la tendencia

ice_cream_heater_df = ice_cream_heater_df.diff().dropna()
plt.figure(figsize=(12,6))
ice_cream, = plt.plot(ice_cream_heater_df['ice cream'])
heater, = plt.plot(ice_cream_heater_df['heater'], color='red')

for year in range(2004, 2021):
    plt.axvline(datetime(year,1,1), linestyle='--', color='k', alpha=0.5)
    
plt.axhline(0, linestyle='--', color='k', alpha=0.3)
plt.ylabel('First Difference', fontsize=18)

plt.legend(['Ice Cream', 'Heater'], fontsize=16)

plt.figure(figsize=(12,6))
ice_cream, = plt.plot(ice_cream_heater_df['ice cream'])

for year in range(2004, 2021):
    plt.axvline(datetime(year,1,1), linestyle='--', color='k', alpha=0.5)
    
plt.axhline(0, linestyle='--', color='k', alpha=0.3)
plt.ylabel('First Difference', fontsize=18)

plt.legend(['Ice Cream'], fontsize=16)

Removiendo la volatilidad incrementada

annual_volatility = ice_cream_heater_df.groupby(ice_cream_heater_df.index.year).std()
annual_volatility
heater ice cream
Month
2004 0.415481 0.184201
2005 0.474527 0.214837
2006 0.400148 0.227698
2007 0.359839 0.198947
2008 0.396182 0.183239
2009 0.499810 0.221038
2010 0.459566 0.211668
2011 0.443924 0.501018
2012 0.471104 0.389711
2013 0.503587 0.335840
2014 0.855743 0.377482
2015 0.569441 0.404340
2016 0.719843 0.593347
2017 0.830886 0.874263
2018 0.987221 0.708785
2019 0.892991 0.843598
2020 0.426657 0.369810
ice_cream_heater_df['ice_cream_annual_vol'] = ice_cream_heater_df.index.map(lambda d: annual_volatility.loc[d.year, 'ice cream'])
ice_cream_heater_df['heater_annual_vol'] = ice_cream_heater_df.index.map(lambda d: annual_volatility.loc[d.year, 'heater'])
ice_cream_heater_df
heater ice cream ice_cream_annual_vol heater_annual_vol
Month
2004-02-01 -0.918789 0.117140 0.184201 0.415481
2004-03-01 -0.408351 0.058570 0.184201 0.415481
2004-04-01 -0.102088 0.175710 0.184201 0.415481
2004-05-01 0.000000 0.117140 0.184201 0.415481
2004-06-01 0.000000 0.175710 0.184201 0.415481
... ... ... ... ...
2020-02-01 -0.714614 0.117140 0.369810 0.426657
2020-03-01 -0.918789 -0.058570 0.369810 0.426657
2020-04-01 0.000000 0.527129 0.369810 0.426657
2020-05-01 0.204175 0.995687 0.369810 0.426657
2020-06-01 -0.306263 0.234279 0.369810 0.426657

197 rows × 4 columns

ice_cream_heater_df['ice cream'] = ice_cream_heater_df['ice cream'] / ice_cream_heater_df['ice_cream_annual_vol']
ice_cream_heater_df['heater'] = ice_cream_heater_df['heater'] / ice_cream_heater_df['heater_annual_vol']
plt.figure(figsize=(12,6))
ice_cream, = plt.plot(ice_cream_heater_df['ice cream'])

for year in range(2004, 2021):
    plt.axvline(datetime(year,1,1), linestyle='--', color='k', alpha=0.5)
    
plt.axhline(0, linestyle='--', color='k', alpha=0.3)
plt.ylabel('First Difference', fontsize=18)

plt.legend(['Ice Cream'], fontsize=16)

plt.figure(figsize=(12,6))
ice_cream, = plt.plot(ice_cream_heater_df['ice cream'])
heater, = plt.plot(ice_cream_heater_df['heater'], color='red')

for year in range(2004, 2021):
    plt.axvline(datetime(year,1,1), linestyle='--', color='k', alpha=0.5)
    
plt.axhline(0, linestyle='--', color='k', alpha=0.3)
plt.ylabel('First Difference', fontsize=18)

plt.legend(['Ice Cream', 'Heater'], fontsize=16)

Removiendo Estacionalidad

month_avgs = ice_cream_heater_df.groupby(ice_cream_heater_df.index.month).mean()
print(month_avgs)
         heater  ice cream  ice_cream_annual_vol  heater_annual_vol
Month                                                              
1     -0.428360   0.130345              0.415976           0.580717
2     -1.483129   0.250194              0.402342           0.570997
3     -1.063595   0.433016              0.402342           0.570997
4     -0.442926   0.838085              0.402342           0.570997
5     -0.121663   0.967273              0.402342           0.570997
6     -0.128425   0.987311              0.402342           0.570997
7     -0.107093   0.735052              0.404376           0.580018
8      0.075720  -1.821285              0.404376           0.580018
9      0.544638  -1.307661              0.404376           0.580018
10     1.613170  -0.830889              0.404376           0.580018
11     1.183118  -0.088136              0.404376           0.580018
12     0.491641   0.088998              0.404376           0.580018
ice_cream_heater_df['ice_cream_month_avg'] = ice_cream_heater_df.index.map(lambda d: month_avgs.loc[d.month, 'ice cream'])
ice_cream_heater_df['heater_month_avg'] = ice_cream_heater_df.index.map(lambda d: month_avgs.loc[d.month, 'heater'])
ice_cream_heater_df
heater ice cream ice_cream_annual_vol heater_annual_vol ice_cream_month_avg heater_month_avg
Month
2004-02-01 -2.211387 0.635934 0.184201 0.415481 0.250194 -1.483129
2004-03-01 -0.982838 0.317967 0.184201 0.415481 0.433016 -1.063595
2004-04-01 -0.245710 0.953901 0.184201 0.415481 0.838085 -0.442926
2004-05-01 0.000000 0.635934 0.184201 0.415481 0.967273 -0.121663
2004-06-01 0.000000 0.953901 0.184201 0.415481 0.987311 -0.128425
... ... ... ... ... ... ...
2020-02-01 -1.674916 0.316756 0.369810 0.426657 0.250194 -1.483129
2020-03-01 -2.153463 -0.158378 0.369810 0.426657 0.433016 -1.063595
2020-04-01 0.000000 1.425403 0.369810 0.426657 0.838085 -0.442926
2020-05-01 0.478547 2.692427 0.369810 0.426657 0.967273 -0.121663
2020-06-01 -0.717821 0.633512 0.369810 0.426657 0.987311 -0.128425

197 rows × 6 columns

ice_cream_heater_df['ice cream'] = ice_cream_heater_df['ice cream'] - ice_cream_heater_df['ice_cream_month_avg']
ice_cream_heater_df['heater'] = ice_cream_heater_df['heater'] - ice_cream_heater_df['heater_month_avg']
ice_cream_heater_df
heater ice cream ice_cream_annual_vol heater_annual_vol ice_cream_month_avg heater_month_avg
Month
2004-02-01 -0.728257 0.385740 0.184201 0.415481 0.250194 -1.483129
2004-03-01 0.080757 -0.115049 0.184201 0.415481 0.433016 -1.063595
2004-04-01 0.197217 0.115816 0.184201 0.415481 0.838085 -0.442926
2004-05-01 0.121663 -0.331339 0.184201 0.415481 0.967273 -0.121663
2004-06-01 0.128425 -0.033411 0.184201 0.415481 0.987311 -0.128425
... ... ... ... ... ... ...
2020-02-01 -0.191787 0.066562 0.369810 0.426657 0.250194 -1.483129
2020-03-01 -1.089868 -0.591394 0.369810 0.426657 0.433016 -1.063595
2020-04-01 0.442926 0.587318 0.369810 0.426657 0.838085 -0.442926
2020-05-01 0.600210 1.725154 0.369810 0.426657 0.967273 -0.121663
2020-06-01 -0.589396 -0.353799 0.369810 0.426657 0.987311 -0.128425

197 rows × 6 columns

plt.figure(figsize=(12,6))
ice_cream, = plt.plot(ice_cream_heater_df['ice cream'])

for year in range(2004, 2021):
    plt.axvline(datetime(year,1,1), linestyle='--', color='k', alpha=0.5)
    
plt.axhline(0, linestyle='--', color='k', alpha=0.3)
plt.ylabel('First Difference', fontsize=18)

plt.legend(['Ice Cream'], fontsize=16)

plt.figure(figsize=(12,6))
ice_cream, = plt.plot(ice_cream_heater_df['ice cream'])
heater, = plt.plot(ice_cream_heater_df['heater'], color='red')

for year in range(2004, 2021):
    plt.axvline(datetime(year,1,1), linestyle='--', color='k', alpha=0.5)
    
plt.axhline(0, linestyle='--', color='k', alpha=0.3)
plt.ylabel('First Difference', fontsize=18)

plt.legend(['Ice Cream', 'Heater'], fontsize=16)

PACF - Heater

plot_pacf(ice_cream_heater_df['heater'])
plt.show()

Entonces, consideremos un AR(2)

Correlación entre “heater” y lagged “ice cream”

for lag in range(1, 14):
    heater_series = ice_cream_heater_df['heater'].iloc[lag:]
    lagged_ice_cream_series = ice_cream_heater_df['ice cream'].iloc[:-lag]
    print('Lag: %s'%lag)
    print(pearsonr(heater_series, lagged_ice_cream_series))
    print('------')
Lag: 1
PearsonRResult(statistic=-0.0315445074259157, pvalue=0.660728499174997)
------
Lag: 2
PearsonRResult(statistic=-0.09872703210944636, pvalue=0.1697084190508982)
------
Lag: 3
PearsonRResult(statistic=-0.0017838553789235184, pvalue=0.9803056762962026)
------
Lag: 4
PearsonRResult(statistic=0.060239718598255367, pvalue=0.4052979832528944)
------
Lag: 5
PearsonRResult(statistic=-0.05403486448015025, pvalue=0.45664426275062703)
------
Lag: 6
PearsonRResult(statistic=0.06461183026553831, pvalue=0.37453000434455314)
------
Lag: 7
PearsonRResult(statistic=-0.04949334618415869, pvalue=0.49768766857621993)
------
Lag: 8
PearsonRResult(statistic=0.07890837135977347, pvalue=0.2804548970433355)
------
Lag: 9
PearsonRResult(statistic=-0.05323501699600425, pvalue=0.4681034202228288)
------
Lag: 10
PearsonRResult(statistic=0.02953698906079116, pvalue=0.6882054906332707)
------
Lag: 11
PearsonRResult(statistic=-0.0597953635152257, pvalue=0.41752174204858805)
------
Lag: 12
PearsonRResult(statistic=-0.07513046035981785, pvalue=0.3094409252542959)
------
Lag: 13
PearsonRResult(statistic=0.19808902198429754, pvalue=0.007029107082073339)
------

Ajustando un VAR Model

ice_cream_heater_df = ice_cream_heater_df[['ice cream', 'heater']]
model = VAR(ice_cream_heater_df)
model_fit = model.fit(maxlags=13)
# ojo, imprime resultados para ambas variables (ice cream y heater) como variables dependientes
model_fit.summary()
  Summary of Regression Results   
==================================
Model:                         VAR
Method:                        OLS
Date:           Wed, 08, Nov, 2023
Time:                     10:44:06
--------------------------------------------------------------------
No. of Equations:         2.00000    BIC:                   -1.92349
Nobs:                     184.000    HQIC:                  -2.48459
Log likelihood:          -204.405    FPE:                  0.0571123
AIC:                     -2.86700    Det(Omega_mle):       0.0434311
--------------------------------------------------------------------
Results for equation ice cream
================================================================================
                   coefficient       std. error           t-stat            prob
--------------------------------------------------------------------------------
const                -0.016054         0.034105           -0.471           0.638
L1.ice cream         -0.287811         0.079633           -3.614           0.000
L1.heater            -0.121251         0.073673           -1.646           0.100
L2.ice cream         -0.217862         0.087025           -2.503           0.012
L2.heater            -0.081992         0.079793           -1.028           0.304
L3.ice cream         -0.122169         0.090772           -1.346           0.178
L3.heater            -0.100743         0.080730           -1.248           0.212
L4.ice cream         -0.167505         0.089113           -1.880           0.060
L4.heater            -0.130541         0.082232           -1.587           0.112
L5.ice cream         -0.206725         0.090337           -2.288           0.022
L5.heater            -0.168606         0.084980           -1.984           0.047
L6.ice cream         -0.138873         0.092630           -1.499           0.134
L6.heater            -0.115771         0.089911           -1.288           0.198
L7.ice cream         -0.180695         0.092245           -1.959           0.050
L7.heater            -0.027255         0.094850           -0.287           0.774
L8.ice cream         -0.111166         0.092679           -1.199           0.230
L8.heater             0.041803         0.094740            0.441           0.659
L9.ice cream         -0.043835         0.091389           -0.480           0.631
L9.heater             0.012616         0.094705            0.133           0.894
L10.ice cream        -0.153345         0.089841           -1.707           0.088
L10.heater            0.059215         0.094546            0.626           0.531
L11.ice cream        -0.004417         0.089771           -0.049           0.961
L11.heater            0.042153         0.093799            0.449           0.653
L12.ice cream        -0.126643         0.090094           -1.406           0.160
L12.heater           -0.021794         0.090097           -0.242           0.809
L13.ice cream        -0.081957         0.087069           -0.941           0.347
L13.heater           -0.018799         0.079199           -0.237           0.812
================================================================================

Results for equation heater
================================================================================
                   coefficient       std. error           t-stat            prob
--------------------------------------------------------------------------------
const                 0.005855         0.036062            0.162           0.871
L1.ice cream         -0.033113         0.084202           -0.393           0.694
L1.heater            -0.405367         0.077900           -5.204           0.000
L2.ice cream         -0.169804         0.092018           -1.845           0.065
L2.heater            -0.193569         0.084371           -2.294           0.022
L3.ice cream         -0.048999         0.095980           -0.511           0.610
L3.heater            -0.016958         0.085362           -0.199           0.843
L4.ice cream         -0.007633         0.094226           -0.081           0.935
L4.heater             0.009474         0.086950            0.109           0.913
L5.ice cream         -0.020253         0.095520           -0.212           0.832
L5.heater            -0.050607         0.089856           -0.563           0.573
L6.ice cream          0.040645         0.097944            0.415           0.678
L6.heater            -0.006504         0.095070           -0.068           0.945
L7.ice cream         -0.053440         0.097538           -0.548           0.584
L7.heater            -0.053795         0.100292           -0.536           0.592
L8.ice cream          0.074170         0.097997            0.757           0.449
L8.heater            -0.059001         0.100176           -0.589           0.556
L9.ice cream         -0.004720         0.096633           -0.049           0.961
L9.heater            -0.000168         0.100139           -0.002           0.999
L10.ice cream        -0.007920         0.094996           -0.083           0.934
L10.heater            0.040322         0.099970            0.403           0.687
L11.ice cream        -0.076564         0.094922           -0.807           0.420
L11.heater            0.022568         0.099181            0.228           0.820
L12.ice cream        -0.111959         0.095263           -1.175           0.240
L12.heater            0.082608         0.095266            0.867           0.386
L13.ice cream         0.203451         0.092065            2.210           0.027
L13.heater           -0.155390         0.083744           -1.856           0.064
================================================================================

Correlation matrix of residuals
             ice cream    heater
ice cream     1.000000  0.064855
heater        0.064855  1.000000

\[ \hat{h}_t = - 0.41h_{t-1} - 0.19h_{t-2} + 0.2i_{t-13} \]