Series temporales y combinación de clasificadores

Esta práctica está dividida en dos partes:

  • En el primer ejercicio veremos como descomponer y componer series temporales para realizar predicciones a futuro.
  • En el segundo ejercicio estudiaremos diferentes métodos de combinación de clasificadores.
In [262]:
import pickle

import scipy.stats
import numpy as np
import pandas as pd
import matplotlib as mpl
from sklearn import svm
from sklearn import ensemble
from matplotlib import pyplot as plt
from sklearn.decomposition import PCA
from sklearn.metrics import accuracy_score
from sklearn.preprocessing import OneHotEncoder
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LinearRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split
!pip install statsmodels
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.graphics.tsaplots import plot_pacf, plot_acf

%matplotlib inline
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: statsmodels in ./.local/lib/python3.5/site-packages (0.11.1)
Requirement already satisfied: numpy>=1.14 in /usr/local/lib/python3.5/dist-packages (from statsmodels) (1.14.3)
Requirement already satisfied: scipy>=1.0 in ./.local/lib/python3.5/site-packages (from statsmodels) (1.4.1)
Requirement already satisfied: pandas>=0.21 in ./.local/lib/python3.5/site-packages (from statsmodels) (0.24.2)
Requirement already satisfied: patsy>=0.5 in ./.local/lib/python3.5/site-packages (from statsmodels) (0.5.1)
Requirement already satisfied: python-dateutil>=2.5.0 in /usr/local/lib/python3.5/dist-packages (from pandas>=0.21->statsmodels) (2.6.1)
Requirement already satisfied: pytz>=2011k in /usr/local/lib/python3.5/dist-packages (from pandas>=0.21->statsmodels) (2017.2)
Requirement already satisfied: six in /usr/local/lib/python3.5/dist-packages (from patsy>=0.5->statsmodels) (1.11.0)
WARNING: You are using pip version 20.0.2; however, version 20.1.1 is available.
You should consider upgrading via the '/usr/bin/python3 -m pip install --upgrade pip' command.

1. Series temporales

En este primer ejercicio trabajaremos las series temporales. Para ello usaremos el dataset AirPassangers que contiene información del número de vuelos que se realizaron a lo largo de muchos años.

Empezaremos leyendo los datos y observando gráficamente su distribución. Como se puede apreciar es un claro caso de serie temporal, con heterocedasticidad, tendencia, periodo y ruido. A lo largo de este ejercicio trataremos cada uno de estos puntos.

In [263]:
data = pd.read_csv('AirPassengers.csv', parse_dates=['Month'], index_col='Month', header=0)
data.head()
Out[263]:
Passengers
Month
1949-01-01112
1949-02-01118
1949-03-01132
1949-04-01129
1949-05-01121
In [264]:
data.plot(figsize=(15, 5))
Out[264]:
<matplotlib.axes._subplots.AxesSubplot at 0x7ff985d21a20>

Antes de empezar a tratar las diferentes componenetes de una serie temporal, eliminaremos del dataset original un par de años de datos. Así cuando hagamos una predicción a futuro podremos comprobar si se ajusta a los datos reales.

In [265]:
TEST_SIZE = 24
train, test = data.iloc[:-TEST_SIZE], data.iloc[-TEST_SIZE:]
x_train, x_test = np.array(range(train.shape[0])), np.array(range(train.shape[0], data.shape[0]))
train.shape, x_train.shape, test.shape, x_test.shape
Out[265]:
((120, 1), (120,), (24, 1), (24,))
In [266]:
fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(x_train, train)
ax.plot(x_test, test)
Out[266]:
[<matplotlib.lines.Line2D at 0x7ff9ccd10fd0>]

1.1 Componentes de la serie temporal

1.1.a. Heterocedasticidad

Se dice que una serie temporal tiene heterocedasticidad cuando la variancia varía con el tiempo (https://es.wikipedia.org/wiki/Heterocedasticidad). En nuestro caso, observamos que tenemos heterocedasticidad, ya que la amplitud de onda varía con el tiempo. En este primer apartado debéis eliminar la heterocedasticidad de la serie temporal. Es decir, que la diferencia entre el mínimo y el máximo de la estacionalidad (anual) sea más o menos la misma a lo largo del tiempo.

Implementación: Transformación de los datos para eliminar la heterocedasticidad. Para hacerlo debéis transformar los datos aplicando la función que consideréis que elimina mejor la heterocedasticidad en el dataset, provad con las tres funciones siguientes: - exponencial - logarítmica - raiz cuadrada y decidid cual de las tres funciona mejor para eliminar la heterocedasticidad. Estas funciones ya están implementadas en numpy. Mostrad gráficamente el dataset transformado.
In [267]:
train_log = np.log(train)
test_log = np.log(test)

print('logarítmica')
fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(x_train, train_log)
ax.plot(x_test, test_log, label='test')
ax.set_title('logarítmica')
plt.legend([l1, l2],["train", 'test'])
logarítmica
Out[267]:
<matplotlib.legend.Legend at 0x7ff9ccb0edd8>
In [268]:
print('exponencial')
train_exp = np.exp(train)
fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(x_train, train_exp)
ax.set_title('exponencial')
exponencial
Out[268]:
<matplotlib.text.Text at 0x7ff9cca66278>
In [269]:
print('raiz cuadrada')
train_sqrt = np.sqrt(train)
fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(x_train, train_sqrt)
ax.set_title('raiz cuadrada')
raiz cuadrada
Out[269]:
<matplotlib.text.Text at 0x7ff9cca49e48>

Con el resultado obtenido, considero que la mejor opción para eliminarla heterocedasticidad en el dataset es la función logaritmica.

1.1.b. Tendencia

La tendencia es el comportamiento que tienen los datos a largo plazo (https://miro.medium.com/max/1872/1*rDQL2fAp_X_dgAHNZuwRfw.png). En nuestra serie temporal tenemos una tendencia lineal creciente. En este apartado debéis eliminar la tendencia, quedando una serie temporal con tendencia constante.

Implementación: Eliminar la tendencia de los datos. Como observando la serie podemos apreciar que tenemos una tendencia lineal, podemos ajustar una regresión lineal (usando scikit-learn) y sustraerla a los datos originales (sin heterocedasticidad). Graficar los datos sin tendencia.
In [270]:
model = LinearRegression()
model.fit(x_train.reshape(-1, 1), train_log.values)
train_log_trend = model.predict(x_train.reshape(-1, 1))



fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(x_train, train_log)
ax.plot(x_test, test_log)
ax.plot(x_train, train_log_trend)
plt.legend([l1, l2],["train", 'test'])

train_log_trend = (train_log - model.predict(x_train.reshape(-1, 1))).values.squeeze()


test_log_trend = (test_log - model.predict(x_test.reshape(-1, 1))).values.squeeze()


fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(x_train, train_log_trend)
ax.plot(x_test, test_log_trend)

plt.legend([l1, l2],["train", 'test'])
Out[270]:
<matplotlib.legend.Legend at 0x7ff99305ecc0>

1.1.c. Estacionalidad

Definimos estacionalidad como la variación cíclica que se produce en los datos (https://es.wikipedia.org/wiki/Estacionalidad). En este apartado se debe encontrar y eliminar la estacionalidad.

1.1.c.a. Encontrar el periodo de la estacionalidad

Implementación: En primer lugar tenéis que encontrar el ciclo, es decir, cada cuando se repiten los datos. Para encontrarlo podéis usar la autocorrelación (numpy te permite obtener los coeficientes de correlación). La idea es que se tiene que comparar: - La serie original con la serie empezando en el segundo punto (es decir, serie[1:]) - La serie original con la serie empezando en el tercer punto (es decir, serie[2:]) - ... En el momento en que vuelva a empezar el ciclo, la serie será muy parecida a la serie original, y, por lo tanto, la correlación será muy elevada. El punto donde la correlación sea máxima, será el ciclo de la serie. Mostrar gráficamente los coeficientes de los 22 primeros valores de autocorrelación y determinar su valor máximo, esta será nuestra estacionalidad. NOTA: Si usais numpy para encontrar los coeficientes de correlación, las dos series que comparéis deben de tener la misma longitud. Para representar la serie original podéis eliminar los valores del final para que tenga la misma longitud que la serie "movida". Por ejemplo, en el primer caso podéis comparar serie[1:] con serie[:-1].
In [271]:
corr_coefficients = []

for i in range(1, 24):
    corr_coefficients.append(np.corrcoef(train_log_trend[i:], train_log_trend[:-i])[0,1])

fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(range(len(corr_coefficients)), corr_coefficients)
Out[271]:
[<matplotlib.lines.Line2D at 0x7ff9ccc6f8d0>]

1.1.c.b. Aplicar un modelo SARIMA

Para tratar la estacionalidad aplicaremos un modelo SARIMA. Las siglas corresponden a "stationality ARIMA", es decir, un modelo ARIMA con estacionalidad (la que acabamos de encontrar).

El modelo ARIMA nos va a permitir tratar el ruido que queda al eliminar la heterocedasticidad, tendencia y estacionalidad. Para hacerlo tiene en cuenta las siguientes componentes:

  • AR: auto-regressive, se denomina p. Tiene en cuenta la correlación con sus lags, es decir, mira si las observaciones pasadas afectan para calcular el siguiente punto.
  • I: integrated, se denomina d. Es el orden de diferenciación, en nuestro caso no es necesario, puesto que la serie ya no tiene heterocedasticidad ni tendencia.
  • MA: moving-average, se denomina q. Tiene en cuenta la correlación con los lags de los errores, es decir, una vez se ha aplicado el modelo, mira los errores del modelo versus los datos reales.

Para más información sobre los ARIMA podéis consultar este link: https://otexts.com/fpp2/arima.html

Los modelos SARIMA dependen de varios parámetros (p,d,q)(P,D,Q)s, donde los primeros (minúsculas) corresponden a AR, I, MA de la serie normal, y los segundos (mayúsculas) a AR, I, MA con estacionalidad.

Para encontrar estos parámetros tenemos que mirar los gráficos PACF (partial autocorrelation function) y ACF (autocorrelation function).

El gráfico PACF nos determina el parámetro p, es decir, la AR.

In [272]:
# Supongamos que los datos sin heterocedasticidad ni tendencia se llaman train_log_trend
plot_pacf(train_log_trend)
plt.show()

Para leer este gráfico, simplemente nos tenemos que fijar en los valores que salen fuera del intervalo de confianza (zona azul).

Nada mas empezar hay dos valores fuera del intervalo. De todos modos el primero no se debe tener en cuenta, puesto que mira la correlación de un valor consigo mismo, y éste siempre será 1. Si no tenemos en cuenta este primer valor, solo hay un valor fuera del intervalo de confianza, con lo cual p = 1.

Cuando se repite el ciclo, es decir, a partir del valor 11, vuelven a haber dos valores fuera del intervalo, con lo cual P = 2.

Veamos ahora el gráfico ACF, este determinará el valor de q.

In [273]:
# Supongamos que los datos sin heterocedasticidad ni tendencia se llaman train_log_trend
plot_acf(train_log_trend)
plt.show()

Este gráfico se lee igual que el anterior. Nada más empezar hay un valor fuera del intervalo, con lo cual q = 1. Cuando se repite el ciclo, es decir, a partir del valor 11, hay tres valores fuera del intervalo, con lo cual Q = 3.

Como los datos no tienen ni tendencia ni heterocedasticidad, d = D = 0.

Como hemos visto en el apartado anterior, el ciclo es 12, con lo cual s = 12.

Implementación: Aplica un modelo SARIMA a los datos sin heterocedasticidad ni tendencia. Puedes usar SARIMAX (de statsmodels.tsa.statespace.sarimax) con los parámetros que acabamos de ver. Mostrar gráficamente el resultado del modelo junto con la serie original para comparar si se ajusta bien.
In [274]:
import warnings
warnings.filterwarnings("ignore") # specify to ignore warning messages
import statsmodels.api as sm
p=1
q=1
d=0
P=2
D=0
Q=3
m=12

#SARIMA(p,d,q)(P,D,Q)m

mod = sm.tsa.statespace.SARIMAX(train_log_trend,
                                order=(p,d,q),
                                seasonal_order=(P,D,Q, m))
results = mod.fit()
print('----Resultado del modelo----')
print(results.summary())

results.plot_diagnostics(figsize=(15, 12))

plt.show()
----Resultado del modelo----
                                         SARIMAX Results
==================================================================================================
Dep. Variable:                                          y   No. Observations:                  120
Model:             SARIMAX(1, 0, 1)x(2, 0, [1, 2, 3], 12)   Log Likelihood                 211.429
Date:                                    Sun, 07 Jun 2020   AIC                           -406.857
Time:                                            10:42:50   BIC                           -384.557
Sample:                                                 0   HQIC                          -397.801
                                                    - 120
Covariance Type:                                      opg
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
ar.L1          0.8580      0.078     11.035      0.000       0.706       1.010
ma.L1         -0.1886      0.122     -1.552      0.121      -0.427       0.050
ar.S.L12       0.1227      1.369      0.090      0.929      -2.560       2.805
ar.S.L24       0.8562      1.347      0.636      0.525      -1.783       3.496
ma.S.L12       0.3052      1.391      0.219      0.826      -2.420       3.031
ma.S.L24      -0.4774      0.787     -0.607      0.544      -2.019       1.065
ma.S.L36      -0.0355      0.190     -0.187      0.852      -0.407       0.336
sigma2         0.0013      0.000      7.820      0.000       0.001       0.002
===================================================================================
Ljung-Box (Q):                       38.61   Jarque-Bera (JB):                 3.61
Prob(Q):                              0.53   Prob(JB):                         0.16
Heteroskedasticity (H):               0.51   Skew:                             0.06
Prob(H) (two-sided):                  0.04   Kurtosis:                         3.84
===================================================================================

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