Sesión 5: Introducción a las 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:

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 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

# 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>

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)
  • Es análogo a una fotografía de un grupo.

Ejemplo encuesta Casen

import pandas as pd

df_casen2020= pd.read_stata("data_sesion5/casen_2020_ingresos.dta")
df_casen2020.head(5)
folio o id_persona region comuna zona expr edad sexo tot_per ... esc2 educ o1 yaut yauth yautcor yautcorh ytrabajocor ytrabajocorh yae
0 1.101100e+11 1 5 Región de Tarapacá Iquique Urbano 67 34 Mujer 2 ... 12.0 Media humanista completa No 220000.0 300000 220000.0 300000 150000.0 150000.0 240586.0
1 1.101100e+11 2 6 Región de Tarapacá Iquique Urbano 67 4 Mujer 2 ... NaN Sin educación formal NaN 80000.0 300000 80000.0 300000 NaN 150000.0 240586.0
2 1.101100e+11 2 31 Región de Tarapacá Iquique Urbano 67 5 Mujer 3 ... NaN Básica incompleta NaN 25000.0 941583 25000.0 941583 NaN 891583.0 439170.0
3 1.101100e+11 1 32 Región de Tarapacá Iquique Urbano 67 45 Hombre 3 ... 15.0 Técnico nivel superior incompleta 889500.0 941583 889500.0 941583 889500.0 891583.0 439170.0
4 1.101100e+11 3 30 Región de Tarapacá Iquique Urbano 67 19 Mujer 3 ... NaN No sabe No 27083.0 941583 27083.0 941583 2083.0 891583.0 439170.0

5 rows × 22 columns

df_casen2020.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 185437 entries, 0 to 185436
Data columns (total 22 columns):
 #   Column        Non-Null Count   Dtype   
---  ------        --------------   -----   
 0   folio         185437 non-null  float64 
 1   o             185437 non-null  int8    
 2   id_persona    185437 non-null  int16   
 3   region        185437 non-null  category
 4   comuna        185437 non-null  category
 5   zona          185437 non-null  category
 6   expr          185437 non-null  int16   
 7   edad          185437 non-null  int16   
 8   sexo          185437 non-null  category
 9   tot_per       185437 non-null  int8    
 10  ecivil        153892 non-null  category
 11  esc           148886 non-null  float64 
 12  esc2          148886 non-null  float64 
 13  educ          185437 non-null  category
 14  o1            151315 non-null  category
 15  yaut          95399 non-null   float64 
 16  yauth         185437 non-null  int32   
 17  yautcor       102165 non-null  float64 
 18  yautcorh      185437 non-null  int32   
 19  ytrabajocor   73509 non-null   float32 
 20  ytrabajocorh  185437 non-null  float32 
 21  yae           185339 non-null  float64 
dtypes: category(7), float32(2), float64(6), int16(3), int32(2), int8(2)
memory usage: 15.6 MB
import seaborn as sns
# Crea el scatterplot
sns.scatterplot(data=df_casen2020, x="esc", y="yaut", alpha=0.5)
<Axes: xlabel='esc', ylabel='yaut'>

Series de tiempo

  • Se tienen diferentes momentos del tiempo (día, semana, mes, año, etc.) para una misma unidad de análisis
    • individuo, país, empresa, etc

Serie Temporal

  • Ejemplos típicos de series de tiempo son:
    • Datos macroeconómicos (PIB. Inglación, empleo, etc.)
    • Financieros (precios de acciones)
    • Empresariales (Ventas, costos, etc…)

Ejemplo datos de acciones

#Usemos la api de Yahoo finance: instalar: pip install yahoo_fin y pip install requests_html

from yahoo_fin.stock_info import get_data

amazon_weekly= get_data("amzn", start_date="12/04/2009", end_date="25/09/2024", index_as_date = False, interval="1wk")

amazon_weekly.head()
date open high low close adjclose volume ticker
0 2009-11-30 7.1810 7.2955 6.7555 6.8790 6.8790 627018000 AMZN
1 2009-12-07 6.9000 6.9500 6.4910 6.7075 6.7075 957260000 AMZN
2 2009-12-14 6.6250 6.6305 6.2825 6.4240 6.4240 915898000 AMZN
3 2009-12-21 6.5240 6.9850 6.5095 6.9235 6.9235 648120000 AMZN
4 2009-12-28 6.9875 7.1290 6.7260 6.7260 6.7260 572014000 AMZN
sns.scatterplot(data=amazon_weekly, y="open", x="date")
<Axes: xlabel='date', ylabel='open'>

Uso series de tiempo

La información temporal permite responder cómo una variable (o más) responde a cambios a través del tiempo.

  • ¿Cuál es el efecto causal dinámico de \(X_t\) sobre \(Y_t\)?
  • ¿Cuál es la mejor predicción del valor de Y en el futuro?

PERO su uso sin cuidado puede llevarnos a conclusiones MUY equivocadas

Panel

  • Este es la combinacion de los dos tipos anteriores
  • Para un conjunto de individuos tenemos varias observaciones en el tiempo.

Panel

  • Suele ser el tipo de datos más completo,

    • pero también más dificil de conseguir.
  • Además, enfrenta los problemas típicos de ambos tipos anteriores, dependiendo si es un panel corto o largo.

  • Una alternativa a estos son los llamados pooled cross section

  • El análisis de este tipo de datos escapa a los objetivos del taller, pero una introducción pueden revisarla en Stock y Watson Cap. 10.

Peculiaridades de las Series de tiempo

Cortes transversal vs series de tiempo

Existe una diferencia clave, que se desprende de trabajar con corte tranvseral vs series de tiempo y es el concepto de la muestra aleatoria.

Muestras en corte transversal

En corte transversal solemos trabajar con MUESTRAS

  • Estimamos modelos de la forma:

\[ y_{i}=\beta_{0}+\beta_{1}x_{1i}+\dots+\beta_{1}x_{ki}+u_{i} \]

Muestras en corte transversal

  • Estimamos modelos de la forma:

\[ y_{i}=\beta_{0}+\beta_{1}x_{1i}+\dots+\beta_{k}x_{ki}+u_{i} <-> Y= \beta_{0}+\beta_{1}X_{1}+\dots+\beta_{k}X_{k}+U \]

  • 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 rezago
amazon_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 diferencia
print(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 NumPy
amazon_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 calculadas
print(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:

  1. La media es constante a lo largo del tiempo, es decir, \(\mu_t = \mu\) para todo \(t\).
  2. La varianza es constante a lo largo del tiempo, es decir, \(\sigma^2_t = \sigma^2\) para todo \(t\).
  3. 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 plt
import matplotlib.pyplot as plt

fig, 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:

  1. Tendencias
  2. Estacionalidad
  3. 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 datos

airline = 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 mpl
import matplotlib.pyplot as plt

fig, 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 in range(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.

import numpy as np

trend = running_average(airline['Passengers'], 12)

fig, ax = plt.subplots(1, 1)
ax.plot(airline.index, airline['Passengers'])
ax.set_xlabel('Año')
ax.set_ylabel('Pasajeros')
ax.plot(airline.index[12:], trend, label='Tendencia')
ax.legend()

Ausencia de estacionareidad 1: Tendencias

Ejemplo Aerolinea

A la serie, entonces, le podemos sacar esta tendencia, al dividr.

detrended = airline.iloc[12:].values.flatten()/trend

. . .

Y graficamente:

fig, ax = plt.subplots(1, 1)
ax.plot(airline.index[12:], detrended)
ax.set_xlabel('Date')
ax.set_ylabel('Detrended value')
Text(0, 0.5, 'Detrended value')

Ausencia de estacionareidad 2: Estacionalidad

(no confundir con estacionareidad)

  • Es cuando hay un patron claro de ciclos que se repite en el tiempo.
  • Generalmente los identificamos a priori con una inspección del grafico.
  • Podemos usar una función para identificarlos.

. . .

def plot_seasons(detrended, order, plot_mean = True):
    colors = plt.rcParams['axes.prop_cycle'].by_key()['color']

    N = len(detrended)

    data = np.array([detrended[i::order] for i in range(order)])
    
    means = np.mean(data, axis=1)
    medians = np.median(data, axis=1)
    
    counts = [0]
    counts.extend([len(data[i]) for i in range(order)])
    counts = np.cumsum(counts)

    ticks = (counts[:-1]+counts[1]/2)
    
    for i in range(order):
        values = data[i, :]
        npoints = len(values)

        plt.plot(range(counts[i], counts[i+1]), values, c=colors[0])
        plt.plot(range(counts[i], counts[i+1]), np.ones(npoints)*means[i], c=colors[1])
        plt.plot(range(counts[i], counts[i+1]), np.ones(npoints)*medians[i], c=colors[2])

    plt.legend(['data', 'mean', 'median'])
    plt.xlabel('season')
    plt.ylabel('values')
    plt.xticks(ticks, np.arange(order));
    
    if plot_mean:
        plt.plot(ticks, means, c=colors[3])
    
    return means

Ausencia de estacionareidad 2: Estacionalidad

  • Acá, simplemente vamos a graficar la curva para diferentes periodos en la temporada.

  • Esto se hace yendo a traves del dataset con un stride igual al periodo estacional.

  • La figura tambien nos provee una forma arquetípica de comportamiento estacional.

  • Con esto, podememos terminar la descomposición de la data en tres componentes.

# Descomposición multiplicativa
def decomposition(data, order, plot=True):
    values = data.values.flatten()
    trend = running_average(values, order)
    detrended = values[order:]/trend
    
    season = [detrended[i::order].mean() for i in range(order)]
    seasonality = np.array(season*(detrended.shape[0]//order+1))[:detrended.shape[0]]
    residuals = values[order:]/(trend*seasonality)

    if plot:
        fig, axs = plt.subplots(4, 1, figsize=(22, 16), sharex=True)
        index = data.index

        axs[0].plot(index, values)
        axs[0].set_title('Data original')
        
        axs[1].plot(index[order:], trend)
        axs[1].set_title('Tendencia')

        axs[2].plot(index[order:], detrended)
        axs[2].set_title('Estacionalidad')

        axs[3].plot(index[order:], residuals)
        axs[3].set_title('Residuos')
        
    return values, trend, seasonality, residuals

Ausencia de estacionareidad 2: Estacionalidad

  • Con esto , el patron estacional se remueve al repetir el patron medio estacional identificado anteriormente y dividiendo, desde la data sin tendencia.

  • El resultado de esta division son simpemente los residuos.

  • Lo que nos muestra que esta descomposición es demasiado sencilla aun.

values, trend, seasonality, residuals = decomposition(airline, 12)

  • 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 necesarias
import numpy as np
import pandas as pd
import statsmodels.api as sm
import matplotlib.pyplot as plt

# Obtener datos históricos de acciones de Amazon
from yahoo_fin.stock_info import get_data
amazon_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 scatterplot
sns.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 datos
data_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 datos
model_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.

Graficamos:

# Graficar los datos y las líneas de regresión
plt.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 2008
data_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 datos
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()

# Calcular SSR para cada modelo
ssr_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 modelo
all_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 completo
ssr_all_data = np.sum(model_all_data.resid ** 2)

# Calcular el estadístico F para el Test de Chow
k = 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 F
from scipy.stats import f
p_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 tabla
modelos = [model_before_2008, model_after_2008, model_all_data]

# Crear una lista de etiquetas para los modelos
etiquetas = ["Modelo Antes de 2008", "Modelo Después de 2008", "Modelo Completo"]

# Configurar Stargazer
stargazer = Stargazer(modelos)
stargazer.custom_columns(etiquetas, [1, 1, 1])  # Asignar etiquetas a las columnas

# Imprimir la tabla
stargazer
Dependent variable: open
Modelo Antes de 2008Modelo Después de 2008Modelo Completo
(1)(2)(3)
const0.491***1.089***-3.132***
(0.063)(0.169)(0.194)
x10.007***0.048***0.025***
(0.000)(0.001)(0.000)
Observations370403773
R20.6050.9150.809
Adjusted R20.6040.9150.808
Residual Std. Error0.611 (df=368)1.701 (df=401)2.695 (df=771)
F Statistic563.964*** (df=1; 368)4302.783*** (df=1; 401)3256.333*** (df=1; 771)
Note:*p<0.1; **p<0.05; ***p<0.01