Para explicar cómo podemos hacer predicción sobre series temporales, utilizaremos un famoso conjunto de datos sobre la evolución del número de pasajeros en una famosa aerolínea americana desde 1949 a 1960.
import seaborn as sns
total_data = sns.load_dataset("flights")
total_data.head()
El conjunto de datos en crudo nos serviría para realizar un proceso de Machine Learning usual como los que hemos visto en pasados módulos. En esta ocasión, necesitamos aplicar una transformación del mismo para generar una serie temporal con dos dimensiones: la temporal y la del dato que queremos analizar y predecir. En este caso, la dimensión temporal estará compuesta por el mes (month
) y el año (year
) y el dato que observaremos a lo largo del tiempo será el número de pasajeros (passengers
).
import pandas as pd
total_data["month"] = pd.to_datetime(total_data.month, format = "%b").dt.month
total_data["date"] = pd.to_datetime(total_data[["year", "month"]].assign(day = 1))
total_data = total_data.set_index("date")
ts = total_data["passengers"]
ts.head()
A continuación visualizaremos la serie temporal para llevar a cabo un análisis visual de la misma:
import matplotlib.pyplot as plt
fig, axis = plt.subplots(figsize = (10, 5))
sns.lineplot(data = ts)
plt.tight_layout()
plt.show()
Para analizar una serie temporal, como vimos en la teoría, debemos estudiar varios parámetros:
A través de un análisis visual podríamos ser capaces de estimar estas métricas a ojo, pero siempre es mejor orientar el análisis a los datos matemáticos. Para la labor de realizar predicciones sobre series temporales y analizarlas, nos apoyaremos en la librería statsmodels
:
La descomposición de una serie temporal es un proceso estadístico que separa de una serie de tiempo en varios elementos diferenciados. Cada uno de estos componentes representa una parte de la estructura subyacente de la serie temporal. La descomposición de una serie de tiempo puede ser muy útil para entender mejor los datos y tomar decisiones informadas al construir modelos de pronóstico.
Utilizamos la función seasonal_decompose
de la biblioteca statsmodels
para descomponer la serie de tiempo en sus componentes de tendencia, estacionalidad y residuos.
from statsmodels.tsa.seasonal import seasonal_decompose
decomposition = seasonal_decompose(ts, period = 12)
decomposition
La tendencia se refiere a la dirección general en la que se mueven los datos. Para acceder a su información recurrimos al componente trend
del resultado decomposition
.
trend = decomposition.trend
fig, axis = plt.subplots(figsize = (10, 5))
sns.lineplot(data = ts)
sns.lineplot(data = trend)
plt.tight_layout()
plt.show()
Se confirma lo observado: una clara tendencia positiva a lo largo de los años.
La estacionalidad se refiere a los patrones repetitivos en los datos. Para acceder a su información recurrimos al componente seasonal
del resultado decomposition
.
seasonal = decomposition.seasonal
fig, axis = plt.subplots(figsize = (10, 5))
sns.lineplot(data = ts)
sns.lineplot(data = seasonal)
plt.tight_layout()
plt.show()
Para evaluar la estacionalidad de la serie temporal podemos aplicar la llamada Prueba de Dickey-Fuller (Dickey-Fuller test), que es un contraste de hipótesis en el que la hipótesis nula es que la serie es estacionaria, y la alternativa, que es no estacionaria:
from statsmodels.tsa.stattools import adfuller
def test_stationarity(timeseries):
print("Resultados de la prueba de Dickey-Fuller:")
dftest = adfuller(timeseries, autolag = "AIC")
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
return dfoutput
test_stationarity(ts)
Aquí podemos ver que el p-value
es mayor a 0.05, esto significa que nuestra hipótesis nula será rechazada y tomaremos esta serie como no estacionaria.
La variabilidad implica el estudio de los residuos: que es cómo fluctúan los datos una vez se ha estudiado la tendencia y la estacionalidad. Para acceder a su información recurrimos al componente resid
del resultado decomposition
.
residual = decomposition.resid
fig, axis = plt.subplots(figsize = (10, 5))
sns.lineplot(data = ts)
sns.lineplot(data = residual)
plt.tight_layout()
plt.show()
Se confirma en parte lo observado, ya que la carga de residuos se hace más notable al inicio y al final del periodo estudiado.
La autocorrelación es la correlación de una serie de tiempo con una copia retrasada de sí misma. Este gráfico nos ayuda a ver si los valores en la serie de tiempo están correlacionados con los valores anteriores.
from statsmodels.graphics.tsaplots import plot_acf
plot_acf(ts)
plt.tight_layout()
plt.show()
Se aprecia una alta correlación entre los puntos y sus copias retrasadas, que disminuye a lo largo del tiempo.
Un modelo consta de tres hiperparámetros:
p
: El orden del componente autorregresivo (AR).d
: El grado de diferenciación necesario para hacer la serie de tiempo estacionaria.q
: El orden del componente de media móvil (MA).El estudio de estos hiperparámetros escapa nuestra función, ya que es un análisis puramente matemático-estadístico. Hoy en día existen herramientas que nos facilitan la vida estimando de forma interna los hiperparámetros más apropiados y generando el mejor modelo posible, como el paquete pmdarima
y su función auto_arima
. Lo único que tenemos que considerar es que para optimizar al máximo sus resultados, debemos transformar la serie en estacionaria, y como en el caso de esta serie no lo es, debemos transformarla:
ts_stationary = ts.diff().dropna()
test_stationarity(ts_stationary)
Ahora la serie si que lo es, y podemos aplicar el método ARIMA automático:
from pmdarima import auto_arima
model = auto_arima(ts_stationary, seasonal = True, trace = True, m = 12)
Como podemos ver, la función hace una búsqueda en el espacio de soluciones posible para estimar los mejores parámetros. En este caso tendríamos un . El modelo que devuelve esta función es totalmente usable, como cualquier otro que hayamos visto, y su función summary()
devuelve información estadística y sobre su rendimiento que tiene gran valor:
model.summary()
Una vez se ha entrenado el modelo, se puede utilizar para predecir a futuro (predeciremos los siguientes 10
meses)
forecast = model.predict(10)
forecast
import matplotlib.pyplot as plt
fig, axis = plt.subplots(figsize = (10, 5))
sns.lineplot(data = ts_stationary)
sns.lineplot(data = forecast, c = "green")
plt.tight_layout()
plt.show()
Ahora nuestro modelo es capaz de realizar predicciones a futuro sobre nuestra serie estacionaria.