Magíster en Data Science - Universidad del Desarrollo
Overview
Resultado de aprendizaje esperado:
Identificar datos de series temporales, sus particularidades y riesgos, en el contexto de posibles aplicaciones profesionales.
Bibliografía recomendada:
Stock & Watson, C.14 link ; Wooldridge, c.12 link, Gujarati, c.12 link
Paquetes que usaremos
# Paquetes y settingsfrom dateutil.parser import parse import matplotlib as mplimport matplotlib.pyplot as pltimport seaborn as snsimport numpy as npimport pandas as pd# setting de graficosplt.figure(figsize=(5,3), dpi=200, facecolor='w', edgecolor='k')
<Figure size 1000x600 with 0 Axes>
<Figure size 1000x600 with 0 Axes>
Las series de tiempo
Y otros tipos de datos:
Recordemos
Los datos que analizamos con modelos se pueden categorizar en tres grandes tipos:
Corte transversal
Series de tiempo
Panel
Corte transversal
La unidad de observación son individuos separados unos de otros.
Cada fila reprsenta un individuo (personas, familias, empresas) en un momento del tiempo específico (mismo año, mismo mes, etc). (usualmente)
Estos modelos representan una correlación marginal en las observaciones entre y y x’s (escalada por la varianza de x)
y para que podamos interpretarlas causalmente tenemos varias condiciones o supuestos que de se deben cumplir.
Uno de estos, es el supuesto de exogeneidad: \[ E[u_{i}|X_{i}]=0 \]
Muestras en corte transversal
Sin embargo, esta forma de ver este supuesto es una simplificación
Como estamos en una muestra aleatoria, no tenemos que verificar que los efectos cruzados tambien sean exogenos:
\[ E[u_{i}|X_{j}]=0 \]
Esto se daba por cumplido, como consecuencia de que era una muestra aletaoria.
Lo cual, generalente, ocurre en corte transversal por lo cual cada observación es i.i.d.
Series de tiempo y procesos estocásticos
En serie de tiempo, nuestro universo es un proceso estocástico
En cada momento se observa un posible resultado (o realización) del proceso estocásticos.
Estimamos modelos de la forma: \[ y_{t}=\beta_{0}+\beta_{1}x_{1t}+\dots+\beta_{1}x_{kt}+u_{t} \]
La imposibilidad de la muestra aleatoria en series de tiempo
Esto tiene vaias implicancias:
NO SON INDEPENDIENTES ya que por construcción, viene del mismo proceso.
Por lo tanto, el supuesto de exogeneidad \(E[u_{t}|X_{t}]=0\) NO ES SUFICIENTE
Requerimos su versión más exigente:
(Exogeneidad estricta): \[E[u_{t}|X_{s}]=0 \qquad \forall s \]
Este supuesto, generalmente NO SE CUMPLE.
Si se cumpliese, seguiruíamos operando como siempre con modelos de regresión multiple estándar.
Trabajando con series de tiempo:
¿Qué hacer entonces?
Reconocer los datos como procesos estocásticos e incluir sus particularidades en la modelación. De eso se tratarán nuestras dos sesiones
Peculiaridades de los procesos estocasticos y su exploración en la data (Sesion 5)
Transformaciones (lags, diferencias y logs)
Dependencia temporal, inercia, estacionaeriedad y estacionalidad.
Modelos específicos para series de tiempo (Sesión 6)
Cambio en supuestos de modelamiento
Procesos Auto regresivos y medias móviles
ARIMA, SARIMA
VAR
El lenguaje de las series de tiempo
Notación
Variables de series de tiempo se denominan con sub-índice “t” para indicar el perído en el tiempo: \(y_t\)
El total de periodos se suele referir como T.
Transformaciones temporales
Dado que una serie de tiempo tiene un orden específico, que en si mismo es importante.
En base a este orden se suelen crear nuevos indicadores o variables.
Revisemos los más comunes:
Rezagos
Diferencias
Tazas de crecimiento
Rezagos o lags
Un rezago es el valor de la variable en períodos anteriores:
Primer rezago: \(y_{t-1}\) es el valor 1 período anterior
Segundo rezago: \(y_{t-2}\) es el valor 2 períodos atrás
j-ésimo rezago: \(y_{t-j}\) es el valor j períodos atrás
Ejemplo rezago
Calculemos el primer y segundo rezago en los datos de amazon:
# Calcular el primer rezago (Lag 1) para la columna 'open'amazon_weekly['open_lag1'] = amazon_weekly['open'].shift(1)# Calcular el segundo rezago (Lag 2) para la columna 'open'amazon_weekly['open_lag2'] = amazon_weekly['open'].shift(2)# Ver los primeros registros del DataFrame con las columnas de rezagoamazon_weekly[['date', 'open', 'open_lag1', 'open_lag2']].head()
date
open
open_lag1
open_lag2
0
2009-11-30
7.1810
NaN
NaN
1
2009-12-07
6.9000
7.181
NaN
2
2009-12-14
6.6250
6.900
7.181
3
2009-12-21
6.5240
6.625
6.900
4
2009-12-28
6.9875
6.524
6.625
Diferencias
Una diferencia corresponde al cambio en una variable entre dos periodos específicos y se usa la notación \(\Delta\)
Primera diferencia: \[\Delta y_t = y_t- y_{t-1}\]
Ejemplo: Calculemos la primera y segunda diferencia en los datos de Amazon:
# Calcular la primera diferencia para la columna 'open'amazon_weekly['open_diff1'] = amazon_weekly['open'] - amazon_weekly['open'].shift(1)# Calcular la segunda diferencia para la columna 'open'amazon_weekly['open_diff2'] = amazon_weekly['open_diff1'] - amazon_weekly['open_diff1'].shift(1)# Ver los primeros registros del DataFrame con las columnas de diferenciaprint(amazon_weekly[['date', 'open', 'open_diff1', 'open_diff2']].head())
date open open_diff1 open_diff2
0 2009-11-30 7.1810 NaN NaN
1 2009-12-07 6.9000 -0.2810 NaN
2 2009-12-14 6.6250 -0.2750 0.0060
3 2009-12-21 6.5240 -0.1010 0.1740
4 2009-12-28 6.9875 0.4635 0.5645
Tasas de crecimiento
Si calculamos la primera diferencia el logaritmo natural, podemos obtener incorporar la tasa de crecimiento en una regresión:
Primera diferencia en logs: \[ \Delta ln(y_{t})=ln(y_{t})-ln(y_{t-1}) \]
Cambio porcentual de \(y_t\) entre \(t -1\) y \(t\approx 100\times \Delta ln(y_{t})\)
Ejemplo Tasa de crecimiento
Calculemos la primera diferencia en logaritmo natural y la taza de crecimiento para la serie de Amazon, en su precio de apertura:
import numpy as np# Calcular el logaritmo natural de la columna 'open' con NumPyamazon_weekly['open_ln'] = np.log(amazon_weekly['open'])# Calcular la primera diferencia en los logaritmos naturales ('open_ln_diff1')amazon_weekly['open_ln_diff1'] = amazon_weekly['open_ln'] - amazon_weekly['open_ln'].shift(1)# Calcular el cambio porcentual entre t-1 y t para 'open' ('open_percent_change')amazon_weekly['open_percent_change'] = (amazon_weekly['open'] - amazon_weekly['open'].shift(1)) / amazon_weekly['open'].shift(1) *100# Ver los primeros registros del DataFrame con las columnas calculadasprint(amazon_weekly[['date', 'open', 'open_ln', 'open_ln_diff1', 'open_percent_change']].head())
date open open_ln open_ln_diff1 open_percent_change
0 2009-11-30 7.1810 1.971439 NaN NaN
1 2009-12-07 6.9000 1.931521 -0.039917 -3.913106
2 2009-12-14 6.6250 1.890850 -0.040671 -3.985509
3 2009-12-21 6.5240 1.875488 -0.015363 -1.524526
4 2009-12-28 6.9875 1.944123 0.068635 7.104537
Patrones de dependencia intertemporal
Primera aproximación a series de tiempo desde la intuiciónn - Con las series Tendremos dos casos: - La serie es estacionaria: bien comportada y el pasado describe bien el futuro. - Tenemos particularidades temporales actuando en el proceso estocástico:
* Efecto rezagados (variable independiente rezagada)
* Retroalimentación entre variables
* Inercia o auto-dependencia la variable depende de si misma en el pasado
Estacionareidad
Entenderemos estacionareidad como el futuro se parece al pasado, al menos en un sentido probabilistico.
En una serie estacionaria:
Nunca se aleja mucho de la media (media constante)
El efecto de los errores caen a lo largo del tiempo (varianza constante)
Lo que ocurre recientemente es relativamente más importante que lo que pasó en momentos más alejados (covarianza constante)
Si las series son estacionarias, no necesitamos muestreo aleatorio y podemos seguir usando OLS como siempre.
(En términos técnicos: Podemos reemplazar el supuesto de exogeneidad extricta por exogeneidad debil SI la serie es estacionaria.)
Estacionareidad
Condiciones matemáticas:
La media es constante a lo largo del tiempo, es decir, \(\mu_t = \mu\) para todo \(t\).
La varianza es constante a lo largo del tiempo, es decir, \(\sigma^2_t = \sigma^2\) para todo \(t\).
La covarianza no depende del tiempo, lo que implica que \(Cov(X_t, X_{t+k}) = Cov(X_s, X_{s+k})\) para cualquier par de instantes \(t\) y \(s\), y para cualquier desfase (lag) \(k\).
Ejemplo estacionaereidad
Hay muchos ejemplos de tipos de no estacionareidad.
Ilustremos los más tipicos con unos ejemplos ficticios.
x = np.linspace(0, np.pi*10, 360)y = np.sin(x)
Podemos generar los diferentes tipos con algunas manipulaciones algebráicas.
import matplotlib as pltimport matplotlib.pyplot as pltfig, axs = plt.subplots(2, 2, figsize=(18, 18))axs[0][0].plot(x, y)axs[0][0].set_title('Serie Estacionaria')axs[0][0].set_xlabel('tiempo')axs[0][0].set_ylabel('Amplitud')axs[0][1].plot(x, y+x/10)axs[0][1].set_title('Media cambiante')axs[0][1].set_xlabel('time')axs[0][1].set_ylabel('Amplitud')axs[1][0].plot(x, y*x/10)axs[1][0].set_title('Varianza cambiante')axs[1][0].set_xlabel('time')axs[1][0].set_ylabel('Amplitud')axs[1][1].plot(np.sin(x+x*x/30))axs[1][1].set_title('Co-variance cambiante')axs[1][1].set_xlabel('time')axs[1][1].set_ylabel('Amplitud')plt.tight_layout()
Ausencia de estacionaeridad:
Revisaremos los casos más típicos en los cuales no hay estacionaeridad:
Tendencias
Estacionalidad
Quiebre estructural
Ausencia de estacionareidad 1: Tendencias
Un claro ejemplo de series no estacionarias es cuando hay tendencias.
Podemos identificar la tendencia a través de una media movil
Revisemoslo con los datos de pasajeros de aerolineas.
import pandas as pd# Cargar datosairline = pd.read_csv('data_sesion5/international-airline-passengers.csv', sep=';')# Nota: si no les reconoce bien la dependencia de la carpeta, pueden usar también#airline= pd.read_csv("https://github.com/melanieoyarzun/taller_seriestiempo_IDS/blob/8c0b9774be8d4103da3801d3069d82b4fe006461/Data/international-airline-passengers.csv?raw=true", sep=';')airline['Month'] = pd.to_datetime(airline['Month']+'-01')airline.set_index('Month', inplace=True)airline.head()
Passengers
Month
1949-01-01
112
1949-02-01
118
1949-03-01
132
1949-04-01
129
1949-05-01
121
Ausencia de estacionareidad 1: Tendencias
Ejemplo Aerolinea
Con un grafico rápido, identificamos que está todo bien y que efectivamente se observa que la serie no es estacionaria.
. . .
import matplotlib as mplimport matplotlib.pyplot as pltfig, ax = plt.subplots(1, 1)ax.plot(airline.index, airline['Passengers'])ax.set_xlabel('Año')ax.set_ylabel('Pasajeros')
Text(0, 0.5, 'Pasajeros')
Ausencia de estacionareidad 1: Tendencias
Ejemplo Aerolinea
Podemos identificar la tendencia en los datos, al calcular la media movil.
Definimos una función para calcular la media móvil:
. . .
def running_average(x, order): current = x[:order].sum() running = []for i inrange(order, x.shape[0]): current += x[i] current -= x[i-order] running.append(current/order)return np.array(running)
Esta función es autoexplicativa.
Simplemente corre en el dataset paso a paso y calcula la media en una ventana específica.
Ausencia de estacionareidad 1: Tendencias
Ejemplo Aerolinea
Ahora podemos agregar esta línea de tendencia al gráfico anterior.
Acá usamos una descomposición multiplicativa, pero este mismo proneso se podría haber hecho siguiendo una descomposición aditiva, con pequeños cambios al codigo.
Ausencia de estacionareidad 3: Quiebre estructural
Otro tipo de no estacionaeridad se presenta cuando la función de regresión poblacional cambia en el transcurso de la las observaciones.
Esto puede ocurrir por varios motivos, por ejemplo cambios en una politica económica, cambios en la estructura de la economía, un nuevo invento o disrupción tecnológica, etc.
Si ocurren tales “cambios estructurales” o “rupturas”, entonces un modelo de regresión que no tenga en cuenta esos cambios puede proporcionar una base engañosa para la inferencia ya la predicción.
Ausencia de estacionareidad 3: Quiebre estructural
Identificación
Las estrategias para identificar un cambio estructurale son varias revisaremos dos:
Contrastes de hipótesis comparando cambios en los coeficientes de regresión mediante estadístico F o Test de Chow.
La segunda es biuscar potenciales cambios estructurales desde la predicción: se simula que la mmuestra termina antes de lo que realmente lo hace y se comparan las predicciones.
Los cambios estructurales se detectan cuando la capacidad de predicción es sustancialmente peor de lo esperado.
Ausencia de estacionareidad 3: Quiebre estructural
Ejemplo datos de amazon
Podemos utilizar datos de acciones y verificar si hay un quiebre estructural antes y después de la crisis financiera de 2008.
Para hacerlo, necesitaremos datos históricos de acciones y realizaremos análisis de series temporales.
. . .
# Importar las bibliotecas necesariasimport numpy as npimport pandas as pdimport statsmodels.api as smimport matplotlib.pyplot as plt# Obtener datos históricos de acciones de Amazonfrom yahoo_fin.stock_info import get_dataamazon_subprime= get_data("amzn", start_date="12/04/2000", end_date="25/09/2015", index_as_date =False, interval="1wk")amazon_subprime.head()
date
open
high
low
close
adjclose
volume
ticker
0
2000-12-04
1.259375
1.381250
1.006250
1.171875
1.171875
1013370000
AMZN
1
2000-12-11
1.143750
1.375000
1.087500
1.143750
1.143750
804546000
AMZN
2
2000-12-18
1.037500
1.059375
0.743750
0.778125
0.778125
1390856000
AMZN
3
2000-12-25
0.815625
0.925000
0.750000
0.778125
0.778125
678338000
AMZN
4
2001-01-01
0.790625
0.893750
0.678125
0.728125
0.728125
866064000
AMZN
Ausencia de estacionareidad 3: Quiebre estructural
Ejemplo datos de amazon
Graficamos los datos
import seaborn as sns# Crea el scatterplotsns.scatterplot(data=amazon_subprime, y="open", x="date")plt.xlabel("Fecha")plt.ylabel("Precio de Apertura")plt.title("Precios de Apertura de Acciones de Amazon (2000-2015)")plt.show()
Ausencia de estacionareidad 3: Quiebre estructural
Ejemplo datos de amazon
Podriamos ajustar un modelo a todos los datos o separar anres y después de la crisis subprime.
Calculamos los modelos:
# Dividir los datos en tres conjuntos: antes de 2008, después de 2008 y todos los datosdata_before_2008 = amazon_subprime[amazon_subprime['date'] <'2008-01-01']data_after_2008 = amazon_subprime[amazon_subprime['date'] >='2008-01-01']# Ajustar modelos de regresión lineal para cada conjunto de datosmodel_all_data = sm.OLS(amazon_subprime['open'], sm.add_constant(np.arange(len(amazon_subprime)))).fit()model_before_2008 = sm.OLS(data_before_2008['open'], sm.add_constant(np.arange(len(data_before_2008)))).fit()model_after_2008 = sm.OLS(data_after_2008['open'], sm.add_constant(np.arange(len(data_after_2008)))).fit()
Ausencia de estacionareidad 3: Quiebre estructural
Ejemplo datos de amazon
Podriamos ajustar un modelo a todos los datos o separar anres y después de la crisis subprime.
# Graficar los datos y las líneas de regresiónplt.figure(figsize=(12, 6))# Datos de todos los datos (en negro)plt.scatter(amazon_subprime['date'], amazon_subprime['open'], color='lightgray', label='Todos los datos')# Línea de regresión para todos los datos (en negro)plt.plot(amazon_subprime['date'], model_all_data.predict(sm.add_constant(np.arange(len(amazon_subprime)))), color='black', linewidth=2)# Datos hasta 2008 (en azul)plt.scatter(data_before_2008['date'], data_before_2008['open'], color='lightblue', label='Hasta 2008')# Línea de regresión hasta 2008 (en azul)plt.plot(data_before_2008['date'], model_before_2008.predict(sm.add_constant(np.arange(len(data_before_2008)))), color='blue', linewidth=2)# Datos después de 2008 (en rojo)plt.scatter(data_after_2008['date'], data_after_2008['open'], color='lightcoral', label='Después de 2008')# Línea de regresión después de 2008 (en rojo)plt.plot(data_after_2008['date'], model_after_2008.predict(sm.add_constant(np.arange(len(data_after_2008)))), color='red', linewidth=2)plt.xlabel("Fecha")plt.ylabel("Precio de Apertura")plt.title("Líneas de Regresión para Precios de Apertura de Acciones de Amazon")plt.legend()plt.show()
Ausencia de estacionareidad 3: Quiebre estructural
Ejemplo datos de amazon
Claramente, los modelos por separados parecen que explican mejor.
Al parecer hay quiebre estructural.
Revisemos usando el test de Chow:
# Dividir los datos en dos conjuntos: antes de 2008 y después de 2008data_before_2008 = amazon_subprime[amazon_subprime['date'] <'2008-01-01']data_after_2008 = amazon_subprime[amazon_subprime['date'] >='2008-01-01']# Ajustar modelos de regresión lineal para cada conjunto de datosmodel_before_2008 = sm.OLS(data_before_2008['open'], sm.add_constant(np.arange(len(data_before_2008)))).fit()model_after_2008 = sm.OLS(data_after_2008['open'], sm.add_constant(np.arange(len(data_after_2008)))).fit()# Calcular SSR para cada modelossr_before_2008 = np.sum(model_before_2008.resid **2)ssr_after_2008 = np.sum(model_after_2008.resid **2)# Combinar todos los datos en un solo modeloall_data = amazon_subprime['open']model_all_data = sm.OLS(all_data, sm.add_constant(np.arange(len(amazon_subprime)))).fit()# Calcular SSR para el modelo completossr_all_data = np.sum(model_all_data.resid **2)# Calcular el estadístico F para el Test de Chowk =2# Número de coeficientes (incluyendo el intercepto)N =len(amazon_subprime)f_statistic = ((ssr_all_data - (ssr_before_2008 + ssr_after_2008)) / k) / ((ssr_before_2008 + ssr_after_2008) / (N -2* k))# Calcular el valor p asociado al estadístico Ffrom scipy.stats import fp_value =1- f.cdf(f_statistic, k, N -2* k)print("Valor p del Test de Chow:", p_value)
Valor p del Test de Chow: 1.1102230246251565e-16
Podemos usar paquetes que tengan el test directamente programado. Por ejemplo chowtest
Ausencia de estacionareidad 3: Quiebre estructural
Ejemplo datos de amazon
Podemos ver los tres modelos en una tabla integrada:
from stargazer.stargazer import Stargazer# Crear una lista de modelos que deseas incluir en la tablamodelos = [model_before_2008, model_after_2008, model_all_data]# Crear una lista de etiquetas para los modelosetiquetas = ["Modelo Antes de 2008", "Modelo Después de 2008", "Modelo Completo"]# Configurar Stargazerstargazer = Stargazer(modelos)stargazer.custom_columns(etiquetas, [1, 1, 1]) # Asignar etiquetas a las columnas# Imprimir la tablastargazer