Una serie temporal (time series) es una sucesión de datos ordenados cronológicamente, espaciados a intervalos iguales o desiguales. El proceso de forecasting consiste en predecir el valor futuro de una serie temporal, bien modelando la serie únicamente en función de su comportamiento pasado (autorregresivo) o empleando otras variables externas.
Ingeniería de características para series temporales
El objetivo de la ingeniería de características es proporcionar relaciones sólidas e idealmente sencillas entre las nuevas características de entrada y las características de salida para que el algoritmo las modele. En efecto, estamos desplazando la complejidad. La complejidad reside en las relaciones entre los datos de entrada y de salida.
En el caso de las series temporales, no existe el concepto de variables de entrada y salida. Podemos apoyarnos en la capacidad de modelos sofisticados para descifrar la complejidad del problema. Podemos facilitar el trabajo a estos modelos (e incluso utilizar modelos más sencillos) si podemos exponer mejor la relación inherente entre entradas y los resultados de los datos. La dificultad estriba en que no conocemos la relación funcional inherente subyacente entre los datos de entrada y de salida que intentamos exponer. Si lo supiéramos, probablemente no necesitaríamos aprendizaje automático. En su lugar, la única información que tenemos es el rendimiento de los modelos desarrollados sobre los conjuntos de datos de aprendizaje supervisado o las visiones del problema que creamos. En efecto, la mejor estrategia por defecto es utilizar todos los conocimientos disponibles para crear muchos conjuntos de datos y utilizar el rendimiento del modelo.
Datos
En esta seccion, utilizaremos como ejemplo el conjunto de datos Temperaturas mínimas diarias. Este conjunto de datos describe las temperaturas mínimas diarias a lo largo de 10 años (1981-1990) en la ciudad de Melbourne Australia.
Empecemos por algunas de las funciones más sencillas que podemos utilizar. Se trata de características de la fecha/hora de cada observación. De hecho, estas pueden ser bastante complejas. Dos características con las que podemos empezar son el mes y el día enteros de cada observación.
from pandas import read_csvfrom pandas import DataFramefrom pandas import concatseries = read_csv('daily-minimum-temperatures.csv', header=0, index_col=0,parse_dates=True).squeeze("columns")dataframe = DataFrame()dataframe['month'] = [series.index[i].month for i inrange(len(series))]dataframe['day'] = [series.index[i].day for i inrange(len(series))]dataframe['temperature'] = [series[i] for i inrange(len(series))]print(dataframe.head(5))
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\3700144450.py:9: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
t-2 t-1 t t+1
0 NaN NaN NaN 20.7
1 NaN NaN 20.7 17.9
2 NaN 20.7 17.9 18.8
3 20.7 17.9 18.8 14.6
4 17.9 18.8 14.6 15.8
Estadísticas de ventana móvil
Podemos calcular estadísticas resumidas de los valores de la ventana móvil e incluirlas como características en nuestro conjunto de datos. Quizá la más útil sea la media de los valores anteriores, también llamada media móvil.
Podemos calcular la media de los valores actuales y anteriores y utilizarla para predecir el valor siguiente. Para los datos de temperatura, tendríamos que esperar 3 pasos de tiempo antes de tener 2 valores de los que sacar la media antes de poder utilizar ese valor para predecir un tercer valor.
mean(t-1,t) t+1
0 NaN 20.7
1 NaN 17.9
2 19.30 18.8
3 18.35 14.6
4 16.70 15.8
Expansión de estadísticas de ventanas
Otro tipo de ventana que puede resultar útil incluye todos los datos anteriores de la serie. Esto se denomina ventana expansiva y puede ayudar a seguir los límites de los datos observables. Al igual que la función rolling() en DataFrame, Pandas proporciona una función expanding() que recoge conjuntos de todos los valores anteriores para cada paso de tiempo.
min mean max t+1
0 20.7 20.700000 20.7 17.9
1 17.9 19.300000 20.7 18.8
2 17.9 19.133333 20.7 14.6
3 14.6 18.000000 20.7 15.8
4 14.6 17.560000 20.7 15.8
Visualización de series temporales
La visualización desempeña un papel importante en el análisis y la previsión de series temporales. Los gráficos de los datos pueden proporcionar valiosos diagnósticos para identificar estructuras temporales como tendencias, ciclos y estacionalidad, que pueden influir en la elección del modelo, y estacionalidad que pueden influir en la elección del modelo. El problema es que muchos principiantes de la previsión de series temporales se detienen en los gráficos de líneas. En este tutorial, echaremos un vistazo a 6 tipos de visualizaciones que puede utilizar con sus propios datos de series temporales. Son los siguientes
Gráficos de líneas.
Histogramas y gráficos de densidad.
Gráficos de cajas y bigotes.
Mapas de calor.
Lag Plots o gráficos de dispersión.
Gráficos de autocorrelación.
La atención se centra en las series temporales univariantes, pero las técnicas son igualmente aplicables a las series temporales multivariantes, cuando se dispone de más de una observación en cada paso temporal. multivariadas, cuando se tiene más de una observación en cada paso temporal. A continuación, echemos un vistazo al conjunto de datos que utilizaremos para demostrar la visualización de series temporales en este material.
Gráfico de líneas
La primera visualización de series temporales, y quizás la más popular, es el gráfico de líneas. En este gráfico, el tiempo se muestra en el eje x con los valores de observación a lo largo del eje y. A continuación se muestra un ejemplo de visualización de la Serie Pandas del conjunto de datos de Temperaturas Mínimas Diarias directamente como un gráfico de líneas.
# create a line plotfrom pandas import read_csvfrom matplotlib import pyplotfrom skforecast.plot import set_dark_themeseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")set_dark_theme()series.plot()pyplot.show()
# create a dot plotseries.plot(style='k.')pyplot.show()
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\2150578903.py:8: FutureWarning:
'Y' is deprecated and will be removed in a future version, please use 'YE' instead.
Histograma y gráficos de densidad
Otra visualización importante es la de la distribución de las propias observaciones. Es decir, un gráfico de los valores sin la ordenación temporal. Algunos métodos de previsión lineal de series temporales suponen que la distribución de las observaciones es correcta (es decir, una curva de campana o una distribución normal). Esto puede comprobarse explícitamente con herramientas como las pruebas de hipótesis estadísticas. Pero los gráficos pueden proporcionar una primera comprobación útil de la distribución de las observaciones, tanto en las observaciones brutas como después de realizar cualquier tipo de transformación de los datos.
# create a density plotfrom pandas import read_csvfrom matplotlib import pyplotseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")series.plot(kind='kde')pyplot.show()pyplot.close()
Gráficos de caja y bigotes por intervalo
Los histogramas y los gráficos de densidad proporcionan una visión de la distribución de todas las observaciones, pero podemos estar interesados en la distribución de los valores por intervalo de tiempo. Otro tipo de gráfico útil para resumir la distribución de las observaciones es el gráfico de cajas y bigotes. Este gráfico dibuja una caja alrededor de los percentiles 25 y 75 de los datos que captura el 50% medio de las observaciones. Se traza una línea en el percentil 50 (la mediana) y los bigotes se dibujan por encima y por debajo de la caja para resumir la extensión general de las observaciones. Se dibujan puntos para los valores atípicos fuera de los bigotes o de la extensión de los datos.
# create a boxplot of yearly datafrom pandas import read_csvfrom pandas import DataFramefrom pandas import Grouperfrom matplotlib import pyplotseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")groups = series.groupby(Grouper(freq='Y'))years = DataFrame()for name, group in groups: years[name.year] = group.valuesyears.boxplot()pyplot.show()pyplot.close()
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\1192400507.py:8: FutureWarning:
'Y' is deprecated and will be removed in a future version, please use 'YE' instead.
# create a boxplot of monthly datafrom pandas import read_csvfrom pandas import DataFramefrom pandas import Grouperfrom matplotlib import pyplotfrom pandas import concatseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")one_year = series['1990']groups = one_year.groupby(Grouper(freq='M'))months = concat([DataFrame(x[1].values) for x in groups], axis=1)months = DataFrame(months)months.columns =range(1,13)months.boxplot()pyplot.show()pyplot.close()
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\842634312.py:10: FutureWarning:
'M' is deprecated and will be removed in a future version, please use 'ME' instead.
Mapas de calor
Una matriz de números puede trazarse como una superficie, en la que a los valores de cada celda de la matriz se les asigna un color único. Esto se denomina mapa de calor, ya que los valores más grandes se pueden dibujar con colores más cálidos (amarillos y rojos) y los valores más pequeños se pueden dibujar con colores más fríos (azules y verdes). Al igual que los gráficos de caja y bigotes, podemos comparar observaciones entre intervalos utilizando un mapa de calor.
# create a heat map of yearly datafrom pandas import read_csvfrom pandas import DataFramefrom pandas import Grouperfrom matplotlib import pyplotseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")groups = series.groupby(Grouper(freq='Y'))years = DataFrame()for name, group in groups: years[name.year] = group.valuesyears = years.Tpyplot.matshow(years, interpolation=None, aspect='auto')pyplot.show()pyplot.close()
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\3935000226.py:8: FutureWarning:
'Y' is deprecated and will be removed in a future version, please use 'YE' instead.
# create a heat map of monthly datafrom pandas import read_csvfrom pandas import DataFramefrom pandas import Grouperfrom matplotlib import pyplotfrom pandas import concatseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")one_year = series['1990']groups = one_year.groupby(Grouper(freq='M'))months = concat([DataFrame(x[1].values) for x in groups], axis=1)months = DataFrame(months)months.columns =range(1,13)pyplot.matshow(months, interpolation=None, aspect='auto')pyplot.show()pyplot.close()
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\192252240.py:10: FutureWarning:
'M' is deprecated and will be removed in a future version, please use 'ME' instead.
Gráficos de dispersión de retrasos
La modelización de series temporales presupone una relación entre una observación y la observación anterior. Las observaciones anteriores en una serie temporal se denominan retardos, con la observación en el paso de tiempo anterior denominada lag1, la observación en dos pasos de tiempo hace lag=2, y así sucesivamente. Un tipo de gráfico útil para explorar la relación entre cada observación y un retardo de esa observación se llama gráfico de dispersión. Pandas tiene una función incorporada para exactamente esto llamado el gráfico de retardo. Se traza la observación en el tiempo t en el eje x y la observación en el siguiente paso de tiempo (t + 1) en el eje y.
Podemos cuantificar la fuerza y el tipo de relación entre las observaciones y sus retardos. En estadística, esto se denomina correlación y, cuando se calcula en función de los valores de retardo de las series temporales, se denomina autocorrelación. Un valor de correlación calculado entre dos grupos de números, como las observaciones y sus valores de retardo = 1, da como resultado un número comprendido entre -1 y 1. El signo de este número indica una correlación negativa o positiva, respectivamente. Un valor cercano a cero indica una correlación débil, mientras que un valor cercano a -1 o 1 indica una correlación fuerte.
Es posible que tenga observaciones con una frecuencia incorrecta. Tal vez sean demasiado granulares o no lo suficientemente granulares. La librería Pandas en Python proporciona la capacidad de cambiar la frecuencia de sus datos de series temporales. En este tutorial, descubrirá cómo utilizar Pandas en Python tanto para aumentar como para disminuir la frecuencia de muestreo de los datos de series temporales.
Conjunto de datos
En esta lección, utilizaremos como ejemplo el conjunto de datos Ventas de champú. Este conjunto de datos describe el número mensual de ventas de champú durante un periodo de 3 años.
Remuestreo de datos
Las observaciones de las ventas de champú son mensuales. Imaginemos que quisiéramos obtener información sobre las ventas diarias. Tendríamos que remuestrear la frecuencia de mensual a diaria y utilizar un esquema de interpolación para obtener la nueva frecuencia diaria. La librería Pandas proporciona una función llamada resample() en los objetos Series y DataFrame. Esta función se puede utilizar para agrupar registros cuando se reduce el muestreo y para hacer espacio para nuevas observaciones cuando se aumenta el muestreo.
Month
1901-01-01 266.0
1901-01-02 NaN
1901-01-03 NaN
1901-01-04 NaN
1901-01-05 NaN
1901-01-06 NaN
1901-01-07 NaN
1901-01-08 NaN
1901-01-09 NaN
1901-01-10 NaN
1901-01-11 NaN
1901-01-12 NaN
1901-01-13 NaN
1901-01-14 NaN
1901-01-15 NaN
1901-01-16 NaN
1901-01-17 NaN
1901-01-18 NaN
1901-01-19 NaN
1901-01-20 NaN
1901-01-21 NaN
1901-01-22 NaN
1901-01-23 NaN
1901-01-24 NaN
1901-01-25 NaN
1901-01-26 NaN
1901-01-27 NaN
1901-01-28 NaN
1901-01-29 NaN
1901-01-30 NaN
1901-01-31 NaN
1901-02-01 145.9
Freq: D, Name: Sales, dtype: float64
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\1241687185.py:7: FutureWarning:
The argument 'date_parser' is deprecated and will be removed in a future version. Please use 'date_format' instead, or read your data in as 'object' dtype and then call 'to_datetime'.
Un buen punto de partida es utilizar una interpolación lineal. Esto dibuja una línea recta entre los datos disponibles, en este caso el primero del mes, y rellena los valores con la frecuencia elegida a partir de esta línea.
# upsample to daily intervals with linear interpolationfrom pandas import read_csvfrom datetime import datetimefrom matplotlib import pyplotdef parser(x):return datetime.strptime('190'+x, '%Y-%m')series = read_csv('monthly-shampoo-sales.csv', header=0, index_col=0, parse_dates=True,date_parser=parser).squeeze("columns")upsampled = series.resample('D').mean()interpolated = upsampled.interpolate(method='linear')print(interpolated.head(32))interpolated.plot()pyplot.show()pyplot.close()
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\2755792297.py:7: FutureWarning:
The argument 'date_parser' is deprecated and will be removed in a future version. Please use 'date_format' instead, or read your data in as 'object' dtype and then call 'to_datetime'.
Otro método común de interpolación es utilizar un polinomio o un spline para conectar los valores. Esto crea más curvas y puede parecer más natural en muchos conjuntos de datos. El uso de una interpolación spline requiere que especifique el orden (número de términos en el polinomio); en este caso, un orden de 2 está bien.
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\3377822813.py:7: FutureWarning:
The argument 'date_parser' is deprecated and will be removed in a future version. Please use 'date_format' instead, or read your data in as 'object' dtype and then call 'to_datetime'.
Los datos de ventas son mensuales, pero quizá preferiríamos que los datos fueran trimestrales. El año puede dividirse en 4 trimestres comerciales, de 3 meses cada uno. En lugar de crear nuevas filas entre las observaciones existentes, la función resample() de Pandas agrupará todas las observaciones por la nueva frecuencia.
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\3452663358.py:7: FutureWarning:
The argument 'date_parser' is deprecated and will be removed in a future version. Please use 'date_format' instead, or read your data in as 'object' dtype and then call 'to_datetime'.
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\3452663358.py:8: FutureWarning:
'Q' is deprecated and will be removed in a future version, please use 'QE' instead.
Quizás queramos ir más allá y convertir los datos mensuales en datos anuales, y quizás utilizarlos más tarde para modelizar el año siguiente. Podemos reducir la muestra de los datos utilizando el alias A para la frecuencia de fin de año y esta vez utilizar la suma para calcular las ventas totales de cada año.
series = read_csv('monthly-shampoo-sales.csv', header=0, index_col=0, parse_dates=True,date_parser=parser).squeeze("columns")resample = series.resample('Y')yearly_mean_sales = resample.sum()print(yearly_mean_sales.head())yearly_mean_sales.plot()pyplot.show()pyplot.close()
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\867565110.py:1: FutureWarning:
The argument 'date_parser' is deprecated and will be removed in a future version. Please use 'date_format' instead, or read your data in as 'object' dtype and then call 'to_datetime'.
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\867565110.py:2: FutureWarning:
'Y' is deprecated and will be removed in a future version, please use 'YE' instead.
Transformaciones de potencia
El objetivo de las transformaciones de datos es eliminar el ruido y mejorar la señal en la predicción de series temporales. Puede resultar muy difícil seleccionar una buena, o incluso la mejor, transformación para un determinado problema de predicción. Hay muchas transformaciones entre las que elegir y cada una tiene una intuición matemática diferente. En este tutorial, descubrirá cómo explorar diferentes transformadas basadas en potencia para la predicción de series temporales con Python.
Conjunto de datos de pasajeros de líneas aéreas
En esta seccion, utilizaremos como ejemplo el conjunto de datos Pasajeros de líneas aéreas. Este conjunto de datos describe el número total de pasajeros de líneas aéreas a lo largo del tiempo.
# load and plot a time seriesfrom pandas import read_csvfrom matplotlib import pyplotseries = read_csv('monthly-airline-passengers.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")pyplot.figure(1)# line plotpyplot.subplot(211)pyplot.plot(series)# histogrampyplot.subplot(212)pyplot.hist(series)pyplot.show()pyplot.close()
Transformación de raíz cuadrada
Una serie temporal con una tendencia de crecimiento cuadrática puede convertirse en lineal sacando la raíz cuadrada. Vamos a demostrarlo con un ejemplo rápido. Consideremos una serie de números del 1 al 99 al cuadrado. El gráfico de líneas de esta serie mostrará una tendencia de crecimiento cuadrática y un histograma de los valores mostrará una distribución exponencial con una cola larga. El siguiente fragmento de código crea y representa gráficamente esta serie.
# contrive a quadratic time seriesfrom matplotlib import pyplotseries = [i**2for i inrange(1,100)]pyplot.figure(1)# line plotpyplot.subplot(211)pyplot.plot(series)# histogrampyplot.subplot(212)pyplot.hist(series)pyplot.show()pyplot.close()
Si observa una estructura como ésta en su propia serie temporal, es posible que tenga una tendencia de crecimiento cuadrática. El siguiente ejemplo realiza una transformación sqrt() en la serie temporal y representa gráficamente el resultado.
# square root transform a contrived quadratic time seriesfrom matplotlib import pyplotfrom numpy import sqrtseries = [i**2for i inrange(1,100)]# sqrt transformtransform = series = sqrt(series)pyplot.figure(1)# line plotpyplot.subplot(211)pyplot.plot(transform)# histogrampyplot.subplot(212)pyplot.hist(transform)pyplot.show()pyplot.close()
Es posible que el conjunto de datos de pasajeros de líneas aéreas muestre un crecimiento cuadrático. Si este es el caso, podríamos esperar que una transformación de raíz cuadrada redujera la tendencia de crecimiento a lineal y cambiara la distribución de las observaciones para que fuera quizás casi gaussiana. El siguiente ejemplo realiza una raíz cuadrada del conjunto de datos y representa gráficamente los resultados.
Una clase de tendencias más extremas son las exponenciales, a menudo representadas gráficamente como un palo de hockey. Las series temporales con una distribución exponencial pueden hacerse lineales tomando el logaritmo de los valores. Esto se denomina transformación logarítmica. Como en el caso anterior del cuadrado y la raíz cuadrada, podemos demostrarlo con un ejemplo rápido. El código siguiente crea una distribución exponencial elevando los números del 1 al 99 al valor e, que es la base de los logaritmos naturales o número de Euler.
# create and plot an exponential time seriesfrom matplotlib import pyplotfrom math import expseries = [exp(i) for i inrange(1,100)]pyplot.figure(1)# line plotpyplot.subplot(211)pyplot.plot(series)# histogrampyplot.subplot(212)pyplot.hist(series)pyplot.show()pyplot.close()
De nuevo, podemos volver a transformar esta serie en lineal tomando el logaritmo natural de los valores. Esto haría que la serie fuera lineal y la distribución uniforme. El ejemplo siguiente lo demuestra para completar.
# log transform a contrived exponential time seriesfrom matplotlib import pyplotfrom math import expfrom numpy import logseries = [exp(i) for i inrange(1,100)]transform = log(series)pyplot.figure(1)# line plotpyplot.subplot(211)pyplot.plot(transform)# histogrampyplot.subplot(212)pyplot.hist(transform)pyplot.show()
Nuestro conjunto de datos Pasajeros de líneas aéreas tiene una distribución de este tipo, pero quizá no tan extrema. El siguiente ejemplo muestra una transformación logarítmica del conjunto de datos Pasajeros de líneas aéreas.
La transformada de raíz cuadrada y la transformada logarítmica pertenecen a una clase de transformadas denominadas transformadas de potencia. La transformación de Box-Cox2 es un método de transformación de datos configurable que admite la raíz cuadrada y la transformación logarítmica, así como un conjunto de transformaciones relacionadas.
Podemos establecer el parámetro lambda en Ninguno (el valor predeterminado) y dejar que la función y un valor ajustado estadísticamente. El siguiente ejemplo demuestra este uso, devolviendo tanto el conjunto de datos transformado como el valor lambda elegido.
El suavizado de medias móviles es una técnica ingenua y eficaz de previsión de series temporales. Se puede utilizar para la preparación de datos, ingeniería de características, e incluso directamente para hacer predicciones. En este tutorial, descubrirá cómo utilizar el suavizado de medias móviles para la previsión de series temporales con Python.
La media móvil puede utilizarse como técnica de preparación de datos para crear una versión suavizada del conjunto de datos original. La suavización es útil como técnica de preparación de datos, ya que puede reducir la variación aleatoria de las observaciones y exponer mejor la estructura de los procesos causales subyacentes. subyacentes.
# moving average smoothing as data preparationfrom pandas import read_csvfrom matplotlib import pyplotseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")# tail-rolling average transformrolling = series.rolling(window=3)rolling_mean = rolling.mean()print(rolling_mean.head(10))# plot original and transformed datasetseries.plot()rolling_mean.plot(color='red')pyplot.show()pyplot.close()# zoomed plot original and transformed datasetseries[:100].plot()rolling_mean[:100].plot(color='red')pyplot.show()pyplot.close()
Date
1959-01-01 NaN
1959-01-02 NaN
1959-01-03 32.333333
1959-01-04 31.000000
1959-01-05 35.000000
1959-01-06 34.666667
1959-01-07 39.333333
1959-01-08 39.000000
1959-01-09 42.000000
1959-01-10 36.000000
Name: Births, dtype: float64
Uso de la media móvil como ingeniería
La media móvil puede utilizarse como fuente de nueva información al modelizar una previsión de series temporales como un problema de aprendizaje supervisado. En este caso, la media móvil se calcula y se añade como una nueva característica de entrada utilizada para predecir el siguiente paso temporal. En primer lugar, hay que desplazar una copia de la serie un paso adelante. Esto representará la entrada a nuestro problema de predicción, o una versión lag=1 de la serie. Se trata de una visión estándar de aprendizaje supervisado del problema de las series temporales.
mean t t+1
0 NaN NaN 35
1 NaN 35.0 32
2 NaN 32.0 30
3 NaN 30.0 31
4 32.333333 31.0 44
5 31.000000 44.0 29
6 35.000000 29.0 45
7 34.666667 45.0 43
8 39.333333 43.0 38
9 39.000000 38.0 27
La media móvil como predicción
El valor de la media móvil también puede utilizarse directamente para hacer predicciones. Es un modelo ingenuo y supone que los componentes de tendencia y estacionalidad de la serie temporal ya se han eliminado o ajustado. El modelo de media móvil para predicciones puede utilizarse fácilmente de forma progresiva. A medida que se disponga de nuevas observaciones (por ejemplo, diariamente), se puede actualizar el modelo y hacer una predicción para el día siguiente. Podemos implementar esto manualmente en Python.
# moving average smoothing as a forecast model#!pip install scikit-learnfrom math import sqrtfrom pandas import read_csvfrom numpy import meanfrom sklearn.metrics import mean_squared_errorfrom matplotlib import pyplotseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")# prepare situationX = series.valuesXwindow =3history = [X[i] for i inrange(window)]historytest = [X[i] for i inrange(window, len(X))]testpredictions =list()# walk forward over time steps in testfor t inrange(len(test)): length =len(history) yhat = mean([history[i] for i inrange(length-window,length)]) obs = test[t] predictions.append(yhat) history.append(obs)#print('predicted=%f, expected=%f' % (yhat, obs))rmse = sqrt(mean_squared_error(test, predictions))print('Test RMSE: %.3f'% rmse)# plotpyplot.plot(test)pyplot.plot(predictions, color='red')pyplot.show()# zoom plotpyplot.plot(test[:100])pyplot.plot(predictions[:100], color='red')pyplot.show()
Test RMSE: 7.834
Introducción al ruido blanco
El ruido blanco es un concepto importante en la previsión de series temporales. Si una serie temporal es ruido blanco, se trata de una secuencia de números aleatorios y no puede predecirse. Si la serie de errores de previsión no es ruido blanco, sugiere que se podrían introducir mejoras en el modelo de predicción. En este tutorial, descubrirá las series temporales de ruido blanco con Python.
En esta sección, crearemos una serie de ruido blanco gaussiano en Python y realizaremos algunas comprobaciones. Es útil crear y revisar una serie temporal de ruido blanco en la práctica. Proporcionará el marco de referencia y gráficos de ejemplo y pruebas estadísticas para utilizar y comparar en sus propios proyectos de series temporales para comprobar si son ruido blanco. En primer lugar, podemos crear una lista de 1.000 variables aleatorias gaussianas utilizando la función gauss() del módulo aleatorio1. Extraeremos variables de una distribución gaussiana con una media (mu) de 0,0 y una desviación típica (sigma) de 1,0. Una vez creada, podemos envolver la lista en una Serie Pandas por conveniencia.
## 10.4 Example of White Noise Time Seriesfrom random import gaussfrom random import seedfrom pandas import Seriesfrom pandas.plotting import autocorrelation_plotfrom matplotlib import pyplot# seed random number generatorseed(1)# create white noise seriesseries = [gauss(0.0, 1.0) for i inrange(1000)]series = Series(series)# summary statsprint(series.describe())# line plotseries.plot()pyplot.show()pyplot.close()# histogram plotseries.hist()pyplot.show()pyplot.close()# autocorrelationautocorrelation_plot(series)pyplot.show()pyplot.close()
count 1000.000000
mean -0.013222
std 1.003685
min -2.961214
25% -0.684192
50% -0.010934
75% 0.703915
max 2.737260
dtype: float64
Introducción al paseo aleatorio
¿Cómo saber si un problema de series temporales es predecible? Se trata de una pregunta difícil en la previsión de series temporales. Existe una herramienta llamada paseo aleatorio que puede ayudarte a entender la predictibilidad de tu problema de previsión de series temporales. En este tutorial, descubrirá el paseo aleatorio y sus propiedades en Python.
Series aleatorias
La biblioteca estándar de Python contiene el módulo random1 que proporciona acceso a un conjunto de funciones para generar números aleatorios. La función randrange()2 puede utilizarse para generar un número entero aleatorio entre 0 y un límite superior. Podemos utilizar la función randrange() para generar una lista de 1.000 números enteros aleatorios entre 0 y 10.
#!pip install statsmodels# 11.1 Random Series# create and plot a random seriesfrom random import seedfrom random import randrangefrom matplotlib import pyplotseed(1)series = [randrange(10) for i inrange(1000)]pyplot.plot(series)pyplot.show()pyplot.close()
Paseo aleatorio
Un paseo aleatorio es diferente de una lista de números aleatorios porque el siguiente valor de la secuencia es una modificación del valor anterior de la secuencia. El proceso utilizado para generar la serie fuerza la dependencia de un paso temporal al siguiente. Esta dependencia proporciona cierta coherencia de un paso a otro en lugar de los grandes saltos que proporciona una serie de números aleatorios independientes. Es esta dependencia la que da nombre al proceso como paseo aleatorio o paseo del borracho.
# create and plot a random walkfrom random import seedfrom random import randomfrom matplotlib import pyplotseed(1)random_walk =list()random_walk.append(-1if random() <0.5else1)for i inrange(1,1000): movement =-1if random() <0.5else1 value = random_walk[i-1] + movement random_walk.append(value)pyplot.plot(random_walk)pyplot.show()pyplot.close()
Recorrido aleatorio y autocorrelación
Podemos calcular la correlación entre cada observación y las observaciones de los pasos anteriores. Un gráfico de estas correlaciones se denomina gráfico de autocorrelación o correlograma3. Dada la forma en que se construye el paseo aleatorio, cabría esperar una fuerte autocorrelación con la observación anterior y una caída lineal a partir de ahí con los valores de retardo anteriores. Podemos utilizar la función autocorrelation plot() en Pandas para trazar el correlograma del paseo aleatorio.
# plot the autocorrelation of a random walkfrom random import seedfrom random import randomfrom matplotlib import pyplotfrom pandas.plotting import autocorrelation_plotseed(1)random_walk =list()random_walk.append(-1if random() <0.5else1)for i inrange(1000): movement =-1if random() <0.5else1 value = random_walk[i-1] + movement random_walk.append(value)autocorrelation_plot(random_walk)pyplot.show()pyplot.close()
Recorrido aleatorio y estacionariedad
Una serie temporal estacionaria es aquella en la que los valores no son una función del tiempo (la estacionariedad se trata con más detalle en el capítulo 15). Dada la forma en que se construye el paseo aleatorio y los resultados de revisar la autocorrelación, sabemos que las observaciones en un paseo aleatorio dependen del tiempo. La observación actual es un paso aleatorio desde la observación anterior.
# calculate the stationarity of a random walkfrom random import seedfrom random import randomfrom statsmodels.tsa.stattools import adfuller# generate random walkseed(1)random_walk =list()random_walk.append(-1if random() <0.5else1)for i inrange(1,1000): movement =-1if random() <0.5else1 value = random_walk[i-1] + movement random_walk.append(value)print()# statistical testresult = adfuller(random_walk)print('ADF Statistic: %f'% result[0])print('p-value: %f'% result[1])print('Critical Values:')for key, value in result[4].items():print('\t%s: %.3f'% (key, value))
Podemos hacer que el paseo aleatorio sea estacionario tomando la primera diferencia. Es decir, sustituyendo cada observación por la diferencia entre ésta y el valor anterior. Dada la forma en que se construyó este paseo aleatorio, esperaríamos que el resultado fuera una serie temporal de valores -1 y 1. Esto es exactamente lo que vemos. Esto es exactamente lo que vemos.
# calculate and plot a differenced random walkfrom random import seedfrom random import randomfrom matplotlib import pyplot# create random walkseed(1)random_walk =list()random_walk.append(-1if random() <0.5else1)for i inrange(1000): movement =-1if random() <0.5else1 value = random_walk[i-1] + movement random_walk.append(value)# take differencediff =list()for i inrange(1, len(random_walk)): value = random_walk[i] - random_walk[i -1] diff.append(value)# line plotpyplot.plot(diff)pyplot.show()pyplot.close()autocorrelation_plot(diff)pyplot.show()
Predecir un paseo aleatorio
Un paseo aleatorio es impredecible; no se puede predecir razonablemente. Dada la forma en que se construye el paseo aleatorio, podemos esperar que la mejor predicción que podríamos hacer sería utilizar la observación en el paso de tiempo anterior como lo que sucederá en el siguiente paso de tiempo. Simplemente porque sabemos que el siguiente paso temporal será una función del paso temporal anterior. Esto suele denominarse previsión ingenua o modelo de persistencia.
# persistence forecasts for a random walkfrom random import seedfrom random import randomfrom sklearn.metrics import mean_squared_errorfrom math import sqrt# generate the random walkseed(1)random_walk =list()random_walk.append(-1if random() <0.5else1)for i inrange(1, 1000): movement =-1if random() <0.5else1 value = random_walk[i-1] + movement random_walk.append(value)# prepare datasettrain_size =int(len(random_walk) *0.66)train, test = random_walk[0:train_size], random_walk[train_size:]# persistencepredictions =list()history = train[-1]for i inrange(len(test)): yhat = history predictions.append(yhat) history = test[i]rmse = sqrt(mean_squared_error(test, predictions))print('Persistence RMSE: %.3f'% rmse)
Persistence RMSE: 1.000
Otro error que cometen los principiantes del paseo aleatorio es suponer que si se conoce el rango de error (varianza), entonces podemos hacer predicciones utilizando un proceso del tipo de generación del paseo aleatorio. Es decir, si sabemos que el error es -1 o 1, ¿por qué no hacer predicciones añadiendo un -1 o 1 elegido al azar al valor anterior.
# random predictions for a random walkfrom random import seedfrom random import randomfrom sklearn.metrics import mean_squared_errorfrom math import sqrt# generate the random walkseed(1)random_walk =list()random_walk.append(-1if random() <0.5else1)for i inrange(1000): movement =-1if random() <0.5else1 value = random_walk[i-1] + movement random_walk.append(value)# prepare datasettrain_size =int(len(random_walk) *0.66)train, test = random_walk[0:train_size], random_walk[train_size:]# random predictionpredictions =list()history = train[-1]for i inrange(len(test)): yhat = history + (-1if random() <0.5else1) predictions.append(yhat) history = test[i]rmse = sqrt(mean_squared_error(test, predictions))print('Random RMSE: %.3f'% rmse)
Random RMSE: 9.827
Descomponer datos de series temporales
La descomposición de series temporales consiste en considerar una serie como una combinación de componentes de nivel, tendencia, estacionalidad y ruido. La descomposición proporciona un modelo abstracto útil para pensar en las series temporales en general y para comprender mejor los problemas durante el análisis y la previsión de series temporales. En este tutorial, descubrirá la descomposición de series temporales y cómo dividir automáticamente una serie temporal en sus componentes con Python.
Descomposición automática de series temporales
Existen métodos para descomponer automáticamente una serie temporal. La biblioteca Statsmodels proporciona una implementación del método de descomposición ingenuo, o clásico, en una función denominada decompose() estacional. Requiere que se especifique si el modelo es aditivo o multiplicativo.
Descomposición aditiva
Podemos crear una serie temporal compuesta por una tendencia lineal creciente de 1 a 99 y algo de ruido aleatorio y descomponerla como un modelo aditivo. Dado que la serie temporal es artificial y se proporciona como una matriz de números, debemos especificar la frecuencia de las observaciones (argumento freq=1).
# additive decompose a contrived additive time seriesfrom random import randrangefrom matplotlib import pyplotfrom statsmodels.tsa.seasonal import seasonal_decomposeseries = [i+randrange(10) for i inrange(1,100)]result = seasonal_decompose(series, model='additive', period=1)result.plot()pyplot.show()pyplot.close()
Descomposición multiplicativa
Podemos concebir una serie temporal cuadrática como un cuadrado del paso temporal de 1 a 99, y luego descomponerla asumiendo un modelo multiplicativo.
# multiplicative decompose a contrived multiplicative time seriesfrom matplotlib import pyplotfrom statsmodels.tsa.seasonal import seasonal_decomposeseries = [i**2.0for i inrange(1,100)]result = seasonal_decompose(series, model='multiplicative', period=1)result.plot()pyplot.show()pyplot.close()
Airline Passengers Dataset
En esta lección, utilizaremos como ejemplo el conjunto de datos Pasajeros de líneas aéreas. Este conjunto de datos describe el número total de pasajeros de líneas aéreas a lo largo del tiempo.
Nuestro conjunto de datos de series temporales puede contener una tendencia. Una tendencia es un aumento o disminución continuado de la serie a lo largo del tiempo. Puede ser beneficioso identificar, modelar e incluso eliminar la información de tendencias de su conjunto de datos de series temporales. En este tutorial, descubrirá cómo modelar y eliminar la información de tendencias de los datos de series temporales en Python.
Desfase por diferenciación
Tal vez el método más sencillo para detraer la tendencia de una serie temporal sea la diferenciación. Específicamente, se construye una nueva serie en la que el valor en el paso temporal actual se calcula como la diferencia entre la observación original y la observación en el paso temporal anterior.
# detrend a time series using differencingfrom pandas import read_csvfrom datetime import datetimefrom matplotlib import pyplotdef parser(x):return datetime.strptime('190'+x, '%Y-%m')series = read_csv('monthly-shampoo-sales.csv', header=0, index_col=0, parse_dates=True,date_parser=parser).squeeze("columns")X = series.valuesdiff =list()for i inrange(1, len(X)): value = X[i] - X[i -1] diff.append(value)pyplot.plot(diff)pyplot.show()
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\1556502088.py:7: FutureWarning:
The argument 'date_parser' is deprecated and will be removed in a future version. Please use 'date_format' instead, or read your data in as 'object' dtype and then call 'to_datetime'.
Detrend por ajuste de modelos
Una tendencia suele visualizarse fácilmente como una línea que atraviesa las observaciones. Las tendencias lineales pueden resumirse mediante un modelo lineal, y las tendencias no lineales pueden resumirse mejor utilizando un polinomio u otro método de ajuste de curvas. Debido a la naturaleza subjetiva y específica de la identificación de tendencias, este enfoque puede ayudar a identificar si una tendencia está presente. Incluso puede ser útil ajustar un modelo lineal a una tendencia que es claramente superlineal o exponencial.
# use a linear model to detrend a time seriesfrom pandas import read_csvfrom datetime import datetimefrom sklearn.linear_model import LinearRegressionfrom matplotlib import pyplotimport numpydef parser(x):return datetime.strptime('190'+x, '%Y-%m')series = read_csv('monthly-shampoo-sales.csv', header=0, index_col=0, parse_dates=True,date_parser=parser).squeeze("columns")# fit linear modelX = [i for i inrange(0, len(series))]X = numpy.reshape(X, (len(X), 1))y = series.valuesmodel = LinearRegression()model.fit(X, y)# calculate trendtrend = model.predict(X)# plot trendpyplot.plot(y)pyplot.plot(trend)pyplot.show()# detrenddetrended = [y[i]-trend[i] for i inrange(0, len(series))]# plot detrendedpyplot.plot(detrended)pyplot.show()pyplot.close()
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\926202335.py:9: FutureWarning:
The argument 'date_parser' is deprecated and will be removed in a future version. Please use 'date_format' instead, or read your data in as 'object' dtype and then call 'to_datetime'.
Utilizar y eliminar la estacionalidad
Los conjuntos de datos de series temporales pueden contener un componente estacional. Se trata de un ciclo que se repite a lo largo del tiempo, por ejemplo mensual o anualmente. Este ciclo repetitivo puede oscurecer la señal que deseamos modelizar al hacer previsiones y, a su vez, puede proporcionar una señal fuerte a nuestros modelos predictivos. En este tutorial, descubrirá cómo identificar y corregir la estacionalidad en los datos de series temporales con Python.
Ajuste estacional con diferenciación
Una forma sencilla de corregir un componente estacional es utilizar la diferenciación. Si existe un componente estacional a nivel de una semana, podemos eliminarlo en una observación de hoy restando el valor de la semana pasada. En el caso del conjunto de datos de las temperaturas mínimas diarias, parece que tenemos un componente estacional cada año que muestra el paso del verano al invierno.
# deseasonalize a time series using differencingfrom pandas import read_csvfrom matplotlib import pyplotseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")X = series.valuesdiff =list()days_in_year =365for i inrange(days_in_year, len(X)): value = X[i] - X[i - days_in_year] diff.append(value)pyplot.plot(diff)pyplot.show()pyplot.close()
Otra opción es considerar que la temperatura en cualquier periodo del año es probablemente estable. Quizá a lo largo de varias semanas. Podemos abreviar esta idea y considerar que todas las temperaturas dentro de un mes natural son estables. Un modelo mejorado puede consistir en restar la temperatura media del mismo mes natural del año anterior, en lugar de la del mismo día. Podemos empezar o remuestreando el conjunto de datos a una temperatura mínima media mensual.
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\3501869392.py:6: FutureWarning:
'M' is deprecated and will be removed in a future version, please use 'ME' instead.
Podemos probar el mismo método de diferenciación en los datos mensuales y confirmar que el conjunto de datos desestacionalizados elimina efectivamente los ciclos anuales.
# deseasonalize monthly data by differencingfrom pandas import read_csvfrom matplotlib import pyplotseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")resample = series.resample('M')monthly_mean = resample.mean()X = series.valuesdiff =list()months_in_year =12for i inrange(months_in_year, len(monthly_mean)): value = monthly_mean[i] - monthly_mean[i - months_in_year] diff.append(value)pyplot.plot(diff)pyplot.show()pyplot.close()
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\966464281.py:6: FutureWarning:
'M' is deprecated and will be removed in a future version, please use 'ME' instead.
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\966464281.py:12: FutureWarning:
Series.__getitem__ treating keys as positions is deprecated. In a future version, integer keys will always be treated as labels (consistent with DataFrame behavior). To access a value by position, use `ser.iloc[pos]`
A continuación, podemos utilizar las temperaturas mínimas medias mensuales del mismo mes del año anterior para ajustar el conjunto de datos de temperaturas mínimas diarias. Una vez más, nos limitamos a omitir el primer año de datos, pero la corrección utilizando los datos mensuales en lugar de los diarios puede ser un enfoque más estable.
# deseasonalize a time series using month-based differencingfrom pandas import read_csvfrom matplotlib import pyplotseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")X = series.valuesdiff =list()days_in_year =365for i inrange(days_in_year, len(X)): month_str =str(series.index[i].year-1)+'-'+str(series.index[i].month) month_mean_last_year = series[month_str].mean() value = X[i] - month_mean_last_year diff.append(value)pyplot.plot(diff)pyplot.show()pyplot.close()
Ajuste estacional mediante modelización
Podemos modelizar directamente el componente estacional y, a continuación, sustraerlo de las observaciones. El componente estacional de una serie temporal dada es probablemente una onda sinusoidal con un periodo y una amplitud generalmente fijos. Puede aproximarse fácilmente mediante un método de ajuste de curvas.
# model seasonality with a polynomial modelfrom pandas import read_csvfrom matplotlib import pyplotfrom numpy import polyfitseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")# fit polynomial: x^2*b1 + x*b2 + ... + bnX = [i%365for i inrange(0, len(series))]y = series.valuesdegree =4coef = polyfit(X, y, degree)print('Coefficients: %s'% coef)# create curvecurve =list()for i inrange(len(X)): value = coef[-1]for d inrange(degree): value += X[i]**(degree-d) * coef[d] curve.append(value)# plot curve over original datapyplot.plot(series.values)pyplot.plot(curve, color='red', linewidth=3)pyplot.show()pyplot.close()
La curva parece ajustarse bien a la estructura estacional del conjunto de datos. Ahora podemos utilizar este modelo para crear una versión desestacionalizada del conjunto de datos. El ejemplo completo figura a continuación.
# deseasonalize by differencing with a polynomial modelfrom pandas import read_csvfrom matplotlib import pyplotfrom numpy import polyfitseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")# fit polynomial: x^2*b1 + x*b2 + ... + bnX = [i%365for i inrange(0, len(series))]y = series.valuesdegree =4coef = polyfit(X, y, degree)# create curvecurve =list()for i inrange(len(X)): value = coef[-1]for d inrange(degree): value += X[i]**(degree-d) * coef[d] curve.append(value)# create seasonally adjustedvalues = series.valuesdiff =list()for i inrange(len(values)): value = values[i] - curve[i] diff.append(value)pyplot.plot(diff)pyplot.show()pyplot.close()
Estacionariedad de los datos de series temporales
Las series temporales son diferentes de los problemas más tradicionales de modelización predictiva de clasificación y regresión. La estructura temporal añade un orden a las observaciones. Este orden impuesto significa que deben manejarse de forma específica supuestos importantes sobre la coherencia de dichas observaciones. de forma específica. Por ejemplo, al modelizar, hay que suponer que los estadísticos de resumen de las observaciones son coherentes. En terminología de series temporales, nos referimos a esta expectativa como que las series temporales son estacionarias. Estas suposiciones pueden ser fácilmente violadas en series de tiempo por la adición de una tendencia, estacionalidad, y otras estructuras dependientes del tiempo. En este tutorial, descubrirá cómo comprobar si su serie temporal es estacionaria con Python.
Series temporales estacionarias
Las observaciones de una serie temporal estacionaria no dependen del tiempo. Las series temporales son estacionarias si no tienen tendencia ni efectos estacionales. Los estadísticos de resumen calculados sobre las series temporales son consistentes a lo largo del tiempo, como la media o la varianza de las observaciones. Cuando una serie temporal es estacionaria, puede ser más fácil de modelizar. Los métodos de modelización estadística presuponen o exigen que las series temporales sean estacionarias para ser eficaces. A continuación se muestra un ejemplo del conjunto de datos Nacimientos Femeninos Diarios que es estacionario.
# load time series datafrom pandas import read_csvfrom matplotlib import pyplotseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")series.plot()pyplot.show()pyplot.close()
Series temporales no estacionarias
Las observaciones de una serie temporal no estacionaria muestran efectos estacionales, tendencias y otras estructuras que dependen del índice temporal. Los estadísticos de resumen, como la media y la varianza, cambian con el tiempo, lo que proporciona una deriva en los conceptos que un modelo puede intentar captar. Los métodos clásicos de análisis y previsión de series temporales tratan de hacer estacionarios los datos de series temporales no estacionarios identificando y eliminando las tendencias y suprimiendo los efectos estacionales. A continuación se muestra un ejemplo de un conjunto de datos de pasajeros de líneas aéreas que no es estacionario y muestra componentes de tendencia y estacionales.
# load time series datafrom pandas import read_csvfrom matplotlib import pyplotseries = read_csv('monthly-airline-passengers.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")series.plot()pyplot.show()pyplot.close()
Estadísticas resumen
Una comprobación rápida y sucia para ver si su serie temporal es no estacionaria es revisar los estadísticos de resumen. Puede dividir su serie temporal en dos (o más) particiones y comparar la media y la varianza de cada grupo. Si difieren y la diferencia es estadísticamente significativa, es probable que la serie temporal no sea estacionaria.
Conjunto de datos de nacimientos diarios
Como estamos viendo la media y la varianza, suponemos que los datos se ajustan a una distribución gaussiana (también llamada curva de campana o normal). También podemos comprobarlo rápidamente observando un histograma de nuestras observaciones.
# plot a histogram of a time seriesfrom pandas import read_csvfrom matplotlib import pyplotseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")series.hist()pyplot.show()pyplot.close()# calculate statistics of partitioned time series datafrom pandas import read_csvseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")X = series.valuessplit =int(len(X) /2)X1, X2 = X[0:split], X[split:]mean1, mean2 = X1.mean(), X2.mean()var1, var2 = X1.var(), X2.var()print('mean1=%f, mean2=%f'% (mean1, mean2))print('variance1=%f, variance2=%f'% (var1, var2))
Repasando de nuevo el gráfico de la serie temporal, podemos ver que hay un componente de estacionalidad obvio, y parece que el componente de estacionalidad está creciendo. Esto puede sugerir un crecimiento exponencial de una temporada a otra. Se puede utilizar una transformación logarítmica para aplanar el cambio exponencial y volver a una relación lineal. A continuación se muestra el mismo histograma con una transformación logarítmica de la serie temporal.
Las pruebas estadísticas se basan en hipótesis sólidas sobre los datos. Sólo pueden utilizarse para informar del grado en que puede rechazarse (o no rechazarse) una hipótesis nula. El resultado debe interpretarse para que un problema determinado tenga sentido. No obstante, pueden proporcionar una comprobación rápida y una prueba confirmatoria de que su serie temporal es estacionaria o no estacionaria.
La prueba Dickey-Fuller aumentada es un tipo de prueba estadística denominada prueba de raíz unitaria1. La intuición que subyace a una prueba de raíz unitaria es que determina hasta qué punto una serie temporal está definida por una tendencia.
Existen varias pruebas de raíz unitaria y la de Dickey-Fuller aumentada puede ser una de las más utilizadas. Utiliza un modelo autorregresivo y optimiza un criterio de información a través de múltiples valores de retardo diferentes. La hipótesis nula de la prueba es que la serie temporal puede ser representada por una raíz unitaria, que no es estacionaria (tiene alguna estructura dependiente del tiempo). La hipótesis alternativa (que rechaza la hipótesis nula) es que la serie temporal es estacionaria.
# calculate stationarity test of time series datafrom pandas import read_csvfrom statsmodels.tsa.stattools import adfullerseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")X = series.valuesresult = adfuller(X)print('ADF Statistic: %f'% result[0])print('p-value: %f'% result[1])print('Critical Values:')for key, value in result[4].items():print('\t%s: %.3f'% (key, value))
El objetivo de la previsión de series temporales es realizar predicciones precisas sobre el futuro. Los métodos rápidos y potentes en los que confiamos en el aprendizaje automático, como el uso de divisiones entrenamiento-prueba y la validación cruzada k-fold, no funcionan en el caso de los datos de series temporales. Esto se debe a que ignoran los componentes temporales inherentes al problema. En este tutorial, descubrirá cómo evaluar modelos de aprendizaje automático en datos de series temporales con Python. En el campo de la previsión de series temporales, esto se denomina backtesting o hindcasting.
Data de entrenamiento-prueba
Puede dividir su conjunto de datos en subconjuntos de entrenamiento y de prueba. Su modelo puede prepararse en el conjunto de datos de entrenamiento y las predicciones pueden hacerse y evaluarse para el conjunto de datos de prueba. Para ello, seleccione un punto de división arbitrario en la lista ordenada de observaciones y cree dos nuevos conjuntos de datos. Dependiendo de la cantidad de datos de que disponga y de la cantidad de datos necesaria, puede utilizar divisiones de 50-50, 70-30 y 90-10. Es muy sencillo dividir datos en Python.
Después de cargar el conjunto de datos como una Serie Pandas, podemos extraer el array NumPy de valores de datos. El punto de división se puede calcular como un índice específico en el array. Todos los registros hasta el punto de división se toman como el conjunto de datos de entrenamiento y todos los registros desde el punto de división hasta el final de la lista de observaciones se toman como el conjunto de prueba. A continuación se muestra un ejemplo en Python con una división de 66-34
# calculate a train-test split of a time series datasetfrom pandas import read_csvseries = read_csv('sunspots.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")X = series.valuestrain_size =int(len(X) *0.66)train, test = X[0:train_size], X[train_size:len(X)]print('Observations: %d'% (len(X)))print('Training Observations: %d'% (len(train)))print('Testing Observations: %d'% (len(test)))
Observations: 2820
Training Observations: 1861
Testing Observations: 959
# plot train-test split of time series datafrom pandas import read_csvfrom matplotlib import pyplotseries = read_csv('sunspots.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")X = series.valuestrain_size =int(len(X) *0.66)train, test = X[0:train_size], X[train_size:len(X)]print('Observations: %d'% (len(X)))print('Training Observations: %d'% (len(train)))print('Testing Observations: %d'% (len(test)))pyplot.plot(train)pyplot.plot([Nonefor i in train] + [x for x in test])pyplot.show()
Observations: 2820
Training Observations: 1861
Testing Observations: 959
Múltiples divisiones entrenamiento-prueba
Podemos repetir varias veces el proceso de dividir las series temporales en conjuntos de entrenamiento y de prueba. Esto requerirá el entrenamiento y la evaluación de múltiples modelos, pero este gasto computacional adicional proporcionará una estimación más robusta del rendimiento esperado del método y la configuración elegidos en datos no vistos. Podríamos hacerlo manualmente repitiendo el proceso descrito en la sección anterior con diferentes puntos de división.
# calculate repeated train-test splits of time series datafrom pandas import read_csvfrom sklearn.model_selection import TimeSeriesSplitfrom matplotlib import pyplotseries = read_csv('sunspots.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")X = series.valuessplits = TimeSeriesSplit(n_splits=3)pyplot.figure(1)index =1for train_index, test_index in splits.split(X): train = X[train_index] test = X[test_index]#print('Observations: %d' % (len(train) + len(test)))#print('Training Observations: %d' % (len(train)))#print('Testing Observations: %d' % (len(test))) pyplot.subplot(310+ index) pyplot.plot(train) pyplot.plot([Nonefor i in train] + [x for x in test]) index +=1pyplot.show()
Validación progresiva
En la práctica, es muy probable que volvamos a entrenar nuestro modelo a medida que dispongamos de nuevos datos. Esto daría al modelo la mejor oportunidad de hacer buenas previsiones en cada paso temporal. Podemos evaluar nuestros modelos de aprendizaje automático bajo este supuesto. Dado que esta metodología implica moverse a lo largo de la serie temporal paso a paso, a menudo se denomina prueba de avance o validación de avance.
# walk forward evaluation model for time series datafrom pandas import read_csvseries = read_csv('sunspots.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")X = series.valuesn_train =500n_records =len(X)for i inrange(n_train, n_records): train, test = X[0:i], X[i:i+1]print('train=%d, test=%d'% (len(train), len(test)))
train=2819, test=1
Medidas de rendimiento de las predicciones
Las medidas de rendimiento de la predicción de series temporales proporcionan un resumen de la habilidad y la capacidad del modelo de predicción que realizó las predicciones. Hay muchas medidas de rendimiento entre las que elegir. Puede resultar confuso saber qué medida utilizar y cómo interpretar los resultados. En este tutorial, descubrirá las medidas de rendimiento para evaluar las predicciones de series temporales con Python. Las series temporales generalmente se centran en la predicción de valores reales, llamados problemas de regresión. Por lo tanto, las medidas de rendimiento de este tutorial se centrarán en métodos para evaluar predicciones de valores reales.
Establecer una línea de base es esencial en cualquier problema de previsión de series temporales. Una línea de base en el rendimiento le da una idea de lo bien que todos los demás modelos funcionarán realmente en su problema. En este tutorial, descubrirá cómo desarrollar un pronóstico de persistencia que puede utilizar para calcular un nivel de referencia de rendimiento en un conjunto de datos de series temporales con Python.
Un modelo de persistencia supone que el valor futuro de una serie temporal se calcula bajo el supuesto de que nada cambia entre el tiempo actual y el tiempo previsto.
# evaluate a persistence forecast modelfrom pandas import read_csvfrom datetime import datetimefrom pandas import DataFramefrom pandas import concatfrom matplotlib import pyplotfrom sklearn.metrics import mean_squared_errorfrom math import sqrt# load datasetdef parser(x):return datetime.strptime('190'+x, '%Y-%m')series = read_csv('monthly-shampoo-sales.csv', header=0, index_col=0, parse_dates=True,date_parser=parser).squeeze("columns")# create lagged datasetvalues = DataFrame(series.values)dataframe = concat([values.shift(1), values], axis=1)dataframe.columns = ['t', 't+1']print(dataframe.head(5))# split into train and test setsX = dataframe.valuestrain_size =int(len(X) *0.66)train, test = X[1:train_size], X[train_size:]train_X, train_y = train[:,0], train[:,1]test_X, test_y = test[:,0], test[:,1]# persistence modeldef model_persistence(x):return x# walk-forward validationpredictions =list()for x in test_X: yhat = model_persistence(x) predictions.append(yhat)rmse = sqrt(mean_squared_error(test_y, predictions))print('Test RMSE: %.3f'% rmse)# plot predictions and expected resultspyplot.plot(train_y)pyplot.plot([Nonefor i in train_y] + [x for x in test_y])pyplot.plot([Nonefor i in train_y] + [x for x in predictions])pyplot.show()
t t+1
0 NaN 266.0
1 266.0 145.9
2 145.9 183.1
3 183.1 119.3
4 119.3 180.3
Test RMSE: 133.156
C:\Users\WINDOWS 11\AppData\Local\Temp\ipykernel_3536\1367142013.py:12: FutureWarning:
The argument 'date_parser' is deprecated and will be removed in a future version. Please use 'date_format' instead, or read your data in as 'object' dtype and then call 'to_datetime'.
Visualizar los errores de las predicciones
Los errores de predicción en problemas de regresión de series temporales se denominan residuos o errores residuales. Una exploración cuidadosa de los errores residuales en su problema de predicción de series temporales puede decirle mucho sobre su modelo de predicción e incluso sugerirle mejoras. En este tutorial, descubrirá cómo visualizar los errores residuales de las predicciones de series temporales.
Modelo de prediccion de persistencia
La previsión más sencilla que podemos hacer es prever que lo que ocurrió en el paso temporal anterior será lo mismo que ocurrirá en el paso temporal siguiente. Esto se denomina previsión ingenua o modelo de previsión de persistencia. Podemos implementar el modelo de persistencia en Python.
# calculate residuals from a persistence forecastfrom pandas import read_csvfrom pandas import DataFramefrom pandas import concatseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")# create lagged datasetvalues = DataFrame(series.values)dataframe = concat([values.shift(1), values], axis=1)dataframe.columns = ['t', 't+1']# split into train and test setsX = dataframe.valuestrain_size =int(len(X) *0.66)train, test = X[1:train_size], X[train_size:]train_X, train_y = train[:,0], train[:,1]test_X, test_y = test[:,0], test[:,1]# persistence modelpredictions = [x for x in test_X]# calculate residualsresiduals = [test_y[i]-predictions[i] for i inrange(len(predictions))]residuals = DataFrame(residuals)print(residuals.head())
0
0 9.0
1 -10.0
2 3.0
3 -6.0
4 30.0
Gráfico de líneas residuales
El primer gráfico consiste en observar los errores de previsión residuales a lo largo del tiempo en forma de línea. Esperamos que el gráfico sea aleatorio alrededor del valor 0 y que no muestre ninguna tendencia o estructura cíclica. La matriz de errores residuales se puede envolver en un Pandas DataFrame y trazar directamente. El siguiente código proporciona un ejemplo.
# line plot of residual errorsfrom pandas import read_csvfrom pandas import DataFramefrom pandas import concatfrom matplotlib import pyplotseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")# create lagged datasetvalues = DataFrame(series.values)dataframe = concat([values.shift(1), values], axis=1)dataframe.columns = ['t', 't+1']# split into train and test setsX = dataframe.valuestrain_size =int(len(X) *0.66)train, test = X[1:train_size], X[train_size:]train_X, train_y = train[:,0], train[:,1]test_X, test_y = test[:,0], test[:,1]# persistence modelpredictions = [x for x in test_X]# calculate residualsresiduals = [test_y[i]-predictions[i] for i inrange(len(predictions))]residuals = DataFrame(residuals)# plot residualsresiduals.plot()pyplot.show()
Estadísticas resumen de los residuos
Podemos calcular estadísticas de resumen sobre los errores residuales. En primer lugar, nos interesa el valor medio de los errores residuales. Un valor cercano a cero sugiere que no hay sesgo en las previsiones, mientras que los valores positivos y negativos sugieren un sesgo positivo o negativo en las previsiones realizadas. Es útil conocer un sesgo en las previsiones, ya que puede corregirse directamente en las previsiones antes de su utilización o evaluación.
A continuación se muestra un ejemplo de cálculo de estadísticas resumidas de la distribución de errores residuales. Se incluyen la media y la desviación típica de la distribución, así como los percentiles y los errores mínimo y máximo observados.
# summary statistics of residual errorsfrom pandas import read_csvfrom pandas import DataFramefrom pandas import concatseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")# create lagged datasetvalues = DataFrame(series.values)dataframe = concat([values.shift(1), values], axis=1)dataframe.columns = ['t', 't+1']# split into train and test setsX = dataframe.valuestrain_size =int(len(X) *0.66)train, test = X[1:train_size], X[train_size:]train_X, train_y = train[:,0], train[:,1]test_X, test_y = test[:,0], test[:,1]# persistence modelpredictions = [x for x in test_X]# calculate residualsresiduals = [test_y[i]-predictions[i] for i inrange(len(predictions))]residuals = DataFrame(residuals)# summary statisticsprint(residuals.describe())
0
count 125.000000
mean 0.064000
std 9.187776
min -28.000000
25% -6.000000
50% -1.000000
75% 5.000000
max 30.000000
Histograma y gráficos de densidad de residuos
Los gráficos pueden utilizarse para comprender mejor la distribución de los errores más allá de las estadísticas resumidas. Es de esperar que los errores de previsión se distribuyan normalmente en torno a una media cero. Los gráficos pueden ayudar a descubrir los sesgos de esta distribución. Podemos utilizar tanto histogramas como diagramas de densidad para comprender mejor la distribución de los errores residuales.
# density plots of residual errorsfrom pandas import read_csvfrom pandas import DataFramefrom pandas import concatfrom matplotlib import pyplotseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")# create lagged datasetvalues = DataFrame(series.values)dataframe = concat([values.shift(1), values], axis=1)dataframe.columns = ['t', 't+1']# split into train and test setsX = dataframe.valuestrain_size =int(len(X) *0.66)train, test = X[1:train_size], X[train_size:]train_X, train_y = train[:,0], train[:,1]test_X, test_y = test[:,0], test[:,1]# persistence modelpredictions = [x for x in test_X]# calculate residualsresiduals = [test_y[i]-predictions[i] for i inrange(len(predictions))]residuals = DataFrame(residuals)# histogram plotresiduals.hist()pyplot.show()# density plotresiduals.plot(kind='kde')pyplot.show()
Gráfico Q-Q residual
Un gráfico Q-Q, o gráfico de cuantiles, compara dos distribuciones y puede utilizarse para ver lo similares o diferentes que son. Podemos crear un gráfico Q-Q utilizando la función qqplot()1 de la biblioteca Statsmodels. El gráfico Q-Q puede utilizarse para comprobar rápidamente la normalidad de la distribución de los errores residuales. Los valores se ordenan y se comparan con una distribución idealizada de Gauss. La comparación se muestra como un gráfico de dispersión (teórica en el eje x y observada en el eje y) en el que una coincidencia entre las dos distribuciones se muestra como una línea diagonal desde la parte inferior izquierda hasta la parte superior derecha del gráfico. El gráfico es útil para detectar desviaciones evidentes de esta expectativa. A continuación se muestra un ejemplo de gráfico Q-Q de los errores residuales. El eje x muestra los cuantiles teóricos y el eje y muestra los cuantiles de la muestra.
# qq plot of residual errorsfrom pandas import read_csvfrom pandas import DataFramefrom pandas import concatfrom matplotlib import pyplotimport numpyfrom statsmodels.graphics.gofplots import qqplotseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")# create lagged datasetvalues = DataFrame(series.values)dataframe = concat([values.shift(1), values], axis=1)dataframe.columns = ['t', 't+1']# split into train and test setsX = dataframe.valuestrain_size =int(len(X) *0.66)train, test = X[1:train_size], X[train_size:]train_X, train_y = train[:,0], train[:,1]test_X, test_y = test[:,0], test[:,1]# persistence modelpredictions = [x for x in test_X]# calculate residualsresiduals = [test_y[i]-predictions[i] for i inrange(len(predictions))]residuals = numpy.array(residuals)qqplot(residuals, line='r')pyplot.show()
Gráfico de autocorrelación de residuos
La autocorrelación calcula la fuerza de la relación entre una observación y las observaciones en pasos de tiempo anteriores. Podemos calcular la autocorrelación de la serie temporal del error residual y representar gráficamente los resultados. Esto se denomina gráfico de autocorrelación. No es de esperar que exista correlación entre los residuos. Los resultados de la autocorrelación estarían por debajo del umbral de significación (líneas horizontales discontinuas en el gráfico).
Una autocorrelación significativa en el gráfico de residuos sugiere que el modelo podría estar haciendo un mejor trabajo a la hora de incorporar la relación entre las observaciones y las observaciones retardadas, lo que se denomina autoregresión. Pandas proporciona una función integrada para calcular un gráfico de autocorrelación, llamada autocorrelation plot().
# autoregression plot of residual errorsfrom pandas import read_csvfrom pandas import DataFramefrom pandas import concatfrom matplotlib import pyplotfrom pandas.plotting import autocorrelation_plotseries = read_csv('daily-total-female-births.csv', header=0, index_col=0, parse_dates=True).squeeze("columns")# create lagged datasetvalues = DataFrame(series.values)dataframe = concat([values.shift(1), values], axis=1)dataframe.columns = ['t', 't+1']# split into train and test setsX = dataframe.valuestrain_size =int(len(X) *0.66)train, test = X[1:train_size], X[train_size:]train_X, train_y = train[:,0], train[:,1]test_X, test_y = test[:,0], test[:,1]# persistence modelpredictions = [x for x in test_X]# calculate residualsresiduals = [test_y[i]-predictions[i] for i inrange(len(predictions))]residuals = DataFrame(residuals)autocorrelation_plot(residuals)pyplot.show()
Modelos de autorregresión
La autorregresión es un modelo de series temporales que utiliza observaciones de pasos temporales anteriores como entrada de una ecuación de regresión para predecir el valor en el siguiente paso temporal. Se trata de una idea muy simple que puede dar lugar a previsiones precisas en una serie de problemas de series temporales. En este tutorial, descubrirá cómo implementar un modelo autorregresivo para la predicción de series temporales con Python.
Comprobación de la autocorrelación
Podemos realizar una comprobación visual rápida para ver si existe autocorrelación en nuestro conjunto de datos de series temporales. Podemos trazar la observación en el paso de tiempo actual (t) con la observación en el paso de tiempo anterior (t-1) como un gráfico de dispersión. Esto podría hacerse manualmente creando primero una versión con retardo del conjunto de datos de series temporales y utilizando una función de gráfico de dispersión incorporada en la biblioteca Pandas. Pero hay una manera más fácil. Pandas proporciona un gráfico integrado para hacer exactamente esto, llamado la función lag plot()1. A continuación se muestra un ejemplo de creación de un gráfico de retardo del conjunto de datos de Temperaturas Mínimas Diarias.
# lag plot of time seriesfrom pandas import read_csvfrom matplotlib import pyplotfrom pandas.plotting import lag_plotseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")lag_plot(series)pyplot.show()
La correlación puede calcularse fácilmente utilizando la función corr()2 en el DataFrame del conjunto de datos retardados. El siguiente ejemplo crea una versión retardada del conjunto de datos Temperaturas mínimas diarias y calcula una matriz de correlación de cada columna con otras columnas, incluida ella misma.
Podemos trazar el coeficiente de correlación de cada variable de retardo. Esto puede dar rápidamente una idea de qué variables de retardo pueden ser buenas candidatas para su uso en un modelo predictivo y de cómo cambia con el tiempo la relación entre la observación y sus valores históricos. Podríamos calcular manualmente los valores de correlación para cada variable de retardo y representar gráficamente el resultado. Afortunadamente, Pandas proporciona un gráfico integrado llamado autocorrelation plot() function.
# autocorrelation plot of time seriesfrom pandas import read_csvfrom matplotlib import pyplotfrom pandas.plotting import autocorrelation_plotseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")autocorrelation_plot(series)pyplot.show()
Supongamos que queremos desarrollar un modelo para predecir los últimos 7 días de temperaturas mínimas en el conjunto de datos dadas todas las observaciones anteriores. El modelo más sencillo que podríamos utilizar para hacer predicciones sería persistir la última observación. Podemos llamarlo modelo de persistencia y proporciona una línea de base de rendimiento para el problema que podemos utilizar para la comparación con un modelo de autoregresión.
Podemos desarrollar un arnés de prueba para el problema dividiendo las observaciones en conjuntos de entrenamiento y de prueba, con sólo las 7 últimas observaciones del conjunto de datos asignadas al conjunto de prueba como datos no vistos que deseamos predecir. Las predicciones se realizan utilizando un modelo de validación walk-forward, de forma que podamos persistir en las observaciones más recientes para el día siguiente. Esto significa que no estamos haciendo una predicción de 7 días, sino 7 predicciones de 1 día.
# evaluate a persistence modelfrom pandas import read_csvfrom pandas import DataFramefrom pandas import concatfrom matplotlib import pyplotfrom sklearn.metrics import mean_squared_errorfrom math import sqrt# load datasetseries = read_csv('daily-min-temperatures (1).csv', header=0, index_col=0,parse_dates=True).squeeze("columns")# create lagged datasetvalues = DataFrame(series.values)dataframe = concat([values.shift(1), values], axis=1)dataframe.columns = ['t', 't+1']# split into train and test setsX = dataframe.valuestrain, test = X[1:len(X)-7], X[len(X)-7:]train_X, train_y = train[:,0], train[:,1]test_X, test_y = test[:,0], test[:,1]# persistence modeldef model_persistence(x):return x# walk-forward validationpredictions =list()for x in test_X: yhat = model_persistence(x) predictions.append(yhat)rmse = sqrt(mean_squared_error(test_y, predictions))print('Test RMSE: %.3f'% rmse)# plot predictions vs expectedpyplot.plot(test_y)pyplot.plot(predictions, color='red')pyplot.show()
Test RMSE: 1.850
Modelo de autorregresión
Un modelo de autoregresión es un modelo de regresión lineal que utiliza variables retardadas como variables de entrada. Podríamos calcular el modelo de regresión lineal manualmente utilizando la clase LinearRegession en scikit-learn y especificar manualmente las variables de entrada retardadas a utilizar. Alternativamente, la biblioteca Statsmodels proporciona un modelo de autoregresión que selecciona automáticamente un valor de retardo apropiado utilizando pruebas estadísticas y entrena un modelo de regresión lineal. Se proporciona en la clase AR. Podemos utilizar este modelo creando primero el modelo AR() y luego llamando a fit() para entrenarlo en nuestro conjunto de datos. Esto devuelve un objeto AR Result. Una vez hecho esto, podemos utilizar el modelo para hacer una predicción llamando a la función predict() para un número de observaciones en el futuro. Esto crea 1 predicción de 7 días, que es diferente del ejemplo de persistencia anterior. A continuación se muestra el ejemplo completo.
A partir de esta seccion se describe cómo utilizar modelos de regresión de Scikit-learn para realizar forecasting de series temporales. En concreto, se hace uso de Skforecast, una librería que contiene las clases y funciones necesarias para adaptar cualquier modelo de regresión de Scikit-learn a problemas de forecasting.
Skforecast: forecasting series temporales con Python, Machine Learning y Scikit-learn por Joaquín Amat Rodrigo y Javier Escobar Ortiz.
C:\Users\WINDOWS 11\Documents\.virtualenvs\r-tensorflow\lib\site-packages\tqdm\auto.py:21: TqdmWarning:
IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
Datos
Se dispone de una serie temporal con el gasto mensual (millones de dólares) en fármacos con corticoides que tuvo el sistema de salud Australiano entre 1991 y 2008. Se pretende crear un modelo autoregresivo capaz de predecir el futuro gasto mensual. Los datos empleados en los ejemplos de este documento se han obtenido del magnífico libro Forecasting: Principles and Practice by Rob J Hyndman and George Athanasopoulos.
# Descarga de datos# ==============================================================================datos = fetch_dataset(name='h2o_exog', raw=True)# Preparación del dato# ==============================================================================datos['fecha'] = pd.to_datetime(datos['fecha'], format='%Y-%m-%d')datos = datos.set_index('fecha')datos = datos.asfreq('MS')datos = datos.sort_index()datos.head()print(f'Número de filas con missing values: {datos.isnull().any(axis=1).mean()}')# Verificar que un índice temporal está completo# ==============================================================================fecha_inicio = datos.index.min()fecha_fin = datos.index.max()date_range_completo = pd.date_range(start=fecha_inicio, end=fecha_fin, freq=datos.index.freq)print(f"Índice completo: {(datos.index == date_range_completo).all()}")# Separación datos train-test# ==============================================================================steps =36datos_train = datos[:-steps]datos_test = datos[-steps:]print(f"Fechas train : {datos_train.index.min()} --- {datos_train.index.max()} (n={len(datos_train)})")print(f"Fechas test : {datos_test.index.min()} --- {datos_test.index.max()} (n={len(datos_test)})")set_dark_theme()fig, ax = plt.subplots(figsize=(8, 4))datos_train['y'].plot(ax=ax, label='train', fontsize=12)datos_test['y'].plot(ax=ax, label='test', fontsize=12)ax.legend();fig
h2o_exog
--------
Monthly expenditure ($AUD) on corticosteroid drugs that the Australian health
system had between 1991 and 2008. Two additional variables (exog_1, exog_2) are
simulated.
Hyndman R (2023). fpp3: Data for Forecasting: Principles and Practice (3rd
Edition). http://pkg.robjhyndman.com/fpp3package/,
https://github.com/robjhyndman/fpp3package, http://OTexts.com/fpp3.
Shape of the dataset: (195, 4)
Número de filas con missing values: 0.0
Índice completo: True
Fechas train : 1992-04-01 00:00:00 --- 2005-06-01 00:00:00 (n=159)
Fechas test : 2005-07-01 00:00:00 --- 2008-06-01 00:00:00 (n=36)
Forecasting autorregresivo recursivo
Se crea y entrena un modelo ForecasterRecursive a partir de un regresor RandomForestRegressor y una ventana temporal de 6 lags. Esto último significa que, el modelo, utiliza como predictores los 6 meses anteriores.
# Crear y entrenar forecaster# ==============================================================================forecaster = ForecasterRecursive( regressor = RandomForestRegressor(random_state=123), lags =6 )forecaster.fit(y=datos_train['y'])forecaster# Predicciones# ==============================================================================steps =36predicciones = forecaster.predict(steps=steps)predicciones.head(5)# Gráfico de predicciones vs valores reales# ==============================================================================fig, ax = plt.subplots(figsize=(8, 4))datos_train['y'].plot(ax=ax, label='train')datos_test['y'].plot(ax=ax, label='test')predicciones.plot(ax=ax, label='predicciones')ax.legend();plt.show()# Error test# ==============================================================================error_mse = mean_squared_error( y_true = datos_test['y'], y_pred = predicciones )print(f"Error de test (mse): {error_mse}")
Error de test (mse): 0.07326833976120374
Ajuste de hiperparámetros (tuning)
El ForecasterRecursive entrenado ha utilizado una ventana temporal de 6 lags y un modelo Random Forest con los hiperparámetros por defecto. Sin embargo, no hay ninguna razón por la que estos valores sean los más adecuados. La librería Skforecast proporciona varias estrategias de búsqueda para encontrar la mejor combinación de hiperparámetros y lags. En este caso, se utiliza la función grid_search_forecaster, que compara los resultados obtenidos con cada combinación de hiperparámetros y lags, e identifica la mejor.
# Búsqueda de hiperparámetros: grid search# ==============================================================================forecaster = ForecasterRecursive( regressor = RandomForestRegressor(random_state=123), lags =12# Este valor será remplazado en el grid search )# Particiones de entrenamiento y validacióncv = TimeSeriesFold( steps =36, initial_train_size =int(len(datos_train) *0.5), refit =False, fixed_train_size =False, )# Valores candidatos de lagslags_grid = [10, 20]# Valores candidatos de hiperparámetros del regresorparam_grid = {'n_estimators': [100, 250],'max_depth': [3, 5, 10]}resultados_grid = grid_search_forecaster( forecaster = forecaster, y = datos_train['y'], cv = cv, param_grid = param_grid, lags_grid = lags_grid, metric ='mean_squared_error', return_best =True, n_jobs =1, verbose =False )# Resultados de la búsqueda de hiperparámetros# ==============================================================================resultados_grid# Crear y entrenar forecaster con mejores hiperparámetros# ==============================================================================regressor = RandomForestRegressor(n_estimators=250, max_depth=3, random_state=123)forecaster = ForecasterRecursive( regressor = regressor, lags =20 )forecaster.fit(y=datos_train['y'])# Predicciones# ==============================================================================predicciones = forecaster.predict(steps=steps)# Gráfico de predicciones vs valores reales# ==============================================================================fig, ax = plt.subplots(figsize=(8, 4))datos_train['y'].plot(ax=ax, label='train')datos_test['y'].plot(ax=ax, label='test')predicciones.plot(ax=ax, label='predicciones')ax.legend();plt.show()# Error de test# ==============================================================================error_mse = mean_squared_error( y_true = datos_test['y'], y_pred = predicciones )print(f"Error de test (mse) {error_mse}")
`Forecaster` refitted using the best-found lags and parameters, and the whole data set:
Lags: [ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20]
Parameters: {'max_depth': 3, 'n_estimators': 250}
Backtesting metric: 0.02177319540541341
Error de test (mse) 0.004356831371529945
Backtesting
Para obtener una estimación robusta de la capacidad predictiva del modelo, se lleva a cabo un proceso de backtesting. El backtesting consiste en evaluar el comportamiento de un modelo predictivo al aplicarlo de forma retrospectiva sobre datos históricos. Por lo tanto, es una estrategia de validación que permite cuantificar la capacidad predictiva de un modelo.
# Backtesting partitions using TimeSeriesFold# ==============================================================================cv = TimeSeriesFold( steps =12*3, initial_train_size =len(datos['y']) -12*9, # Last 9 years are separated for the backtest window_size =20, fixed_train_size =False, refit =True,)cv.split(X=datos['y'], as_pandas=True)# Backtesting# ==============================================================================cv = TimeSeriesFold( steps =12*3, initial_train_size =len(datos) -12*9, fixed_train_size =False, refit =True, )metrica, predicciones_backtest = backtesting_forecaster( forecaster = forecaster, y = datos['y'], cv = cv, metric ='mean_squared_error', verbose =True, n_jobs =1 )metrica# Gráfico de predicciones de backtest vs valores reales# ==============================================================================fig, ax = plt.subplots(figsize=(8, 4))datos.loc[predicciones_backtest.index, 'y'].plot(ax=ax, label='test')predicciones_backtest.plot(ax=ax, label='predicciones')ax.legend();plt.show()
Information of folds
--------------------
Number of observations used for initial training: 87
Number of observations used for backtesting: 108
Number of folds: 3
Number skipped folds: 0
Number of steps per fold: 36
Number of steps to exclude between last observed data (last window) and predictions (gap): 0
Fold: 0
Training: 1992-04-01 00:00:00 -- 1999-06-01 00:00:00 (n=87)
Validation: 1999-07-01 00:00:00 -- 2002-06-01 00:00:00 (n=36)
Fold: 1
Training: 1992-04-01 00:00:00 -- 2002-06-01 00:00:00 (n=123)
Validation: 2002-07-01 00:00:00 -- 2005-06-01 00:00:00 (n=36)
Fold: 2
Training: 1992-04-01 00:00:00 -- 2005-06-01 00:00:00 (n=159)
Validation: 2005-07-01 00:00:00 -- 2008-06-01 00:00:00 (n=36)
Information of folds
--------------------
Number of observations used for initial training: 87
Number of observations used for backtesting: 108
Number of folds: 3
Number skipped folds: 0
Number of steps per fold: 36
Number of steps to exclude between last observed data (last window) and predictions (gap): 0
Fold: 0
Training: 1992-04-01 00:00:00 -- 1999-06-01 00:00:00 (n=87)
Validation: 1999-07-01 00:00:00 -- 2002-06-01 00:00:00 (n=36)
Fold: 1
Training: 1992-04-01 00:00:00 -- 2002-06-01 00:00:00 (n=123)
Validation: 2002-07-01 00:00:00 -- 2005-06-01 00:00:00 (n=36)
Fold: 2
Training: 1992-04-01 00:00:00 -- 2005-06-01 00:00:00 (n=159)
Validation: 2005-07-01 00:00:00 -- 2008-06-01 00:00:00 (n=36)
Debido a la naturaleza compleja de muchos de los actuales modelos de machine learning, a menudo funcionan como cajas negras, lo que dificulta entender por qué han hecho una predicción u otra. Las técnicas de explicabilidad pretenden desmitificar estos modelos, proporcionando información sobre su funcionamiento interno y ayudando a generar confianza, mejorar la transparencia y cumplir los requisitos normativos en diversos ámbitos. Mejorar la explicabilidad de los modelos no sólo ayuda a comprender su comportamiento, sino también a identificar sesgos, mejorar su rendimiento y permitir a las partes interesadas tomar decisiones más informadas basadas en los conocimientos del machine learning.
# Importancia predictores# ==============================================================================importancia = forecaster.get_feature_importances()importancia.head(10)
feature
importance
11
lag_12
0.815564
1
lag_2
0.086286
13
lag_14
0.019047
9
lag_10
0.013819
2
lag_3
0.012943
14
lag_15
0.009637
0
lag_1
0.009141
10
lag_11
0.008130
7
lag_8
0.007377
8
lag_9
0.005268
Shap values
Los valores SHAP (SHapley Additive exPlanations) son un método muy utilizado para explicar los modelos de machine learning, ya que ayudan a comprender cómo influyen las variables y los valores en las predicciones de forma visual y cuantitativa.
Se puede obtener un análisis SHAP a partir de modelos skforecast con sólo dos elementos:
El regresor interno del forecaster.
Las matrices de entrenamiento creadas a partir de la serie temporal y variables exógenas, utilizadas para ajustar el pronosticador.
Aprovechando estos dos componentes, los usuarios pueden crear explicaciones interpretables para sus modelos de skforecast. Estas explicaciones pueden utilizarse para verificar la fiabilidad del modelo, identificar los factores más significativos que contribuyen a las predicciones y comprender mejor la relación subyacente entre las variables de entrada y la variable objetivo.
# Matrices de entrenamiento utilizadas por el forecaster para entrenar el regresor# ==============================================================================X_train, y_train = forecaster.create_train_X_y(y=datos_train['y'])# Crear SHAP explainer (para modelos basados en árboles)# ==============================================================================explainer = shap.TreeExplainer(forecaster.regressor)# Se selecciona una muestra del 50% de los datos para acelerar el cálculorng = np.random.default_rng(seed=785412)sample = rng.choice(X_train.index, size=int(len(X_train) *0.5), replace=False)X_train_sample = X_train.loc[sample, :]shap_values = explainer.shap_values(X_train_sample)# Shap summary plot (top 10)# ==============================================================================shap.initjs()shap.summary_plot(shap_values, X_train_sample, max_display=15, show=False)fig, ax = plt.gcf(), plt.gca()ax.set_title("SHAP Summary plot")ax.tick_params(labelsize=8)fig.set_size_inches(8, 4)plt.show()
# Backtesting indicando que se devuelvan los predictores# ==============================================================================cv = TimeSeriesFold( steps =12*3, initial_train_size =len(datos) -12*9, fixed_train_size =False, refit =True, )_, predicciones_backtest = backtesting_forecaster( forecaster = forecaster, y = datos['y'], cv = cv, return_predictors =True, metric ='mean_squared_error', n_jobs =1 )predicciones_backtest.head()# Waterfall para una predicción concreta# ==============================================================================iloc_predicted_date = predicciones_backtest.index.get_loc('2005-04-01')shap_values_single = explainer(predicciones_backtest.iloc[:, 2:])shap.plots.waterfall(shap_values_single[iloc_predicted_date], show=False)fig, ax = plt.gcf(), plt.gca()fig.set_size_inches(7, 4)plt.show()
En el ejemplo anterior, se han utilizado como predictores únicamente lags de la propia variable predicha. En ciertos escenarios, es posible disponer de información sobre otras variables, cuyo valor a futuro se conoce, y pueden servir como predictoreres adicionales en el modelo.
Siguiendo con el ejemplo anterior, se simula una nueva variable cuyo comportamiento está correlacionado con la serie temporal modelada y que, por lo tanto, se quiere incorporar como predictor. Esto mísmo es aplicable a múltiples variables exógenas.
En determinados escenarios, puede ser interesante incorporar otras características de la serie temporal además de los lags, por ejemplo, la media movil de los últimos n valores puede servir para capturar la tendencia de la serie. El argumento window_features permite incorporar al modelo predictores adicionales, creados a partir de los valores pasados de la serie temporal.
La clase RollingFeatures disponible en skforecast permite crear algunos de los predictores más utilizados:
‘mean’: la media de los n valores anteriores.
‘std’: la desviación estándar de los n valores anteriores.
‘min’: el mínimo de los n valores anteriores.
‘max’: el máximo de los n valores anteriores.
‘sum’: la suma de los n valores anteriores.
‘median’: la mediana de los n valores anteriores.
‘ratio_min_max’: la relación entre el mínimo y el máximo de los n valores anteriores.
‘coef_variation’: el coeficiente de variación de los n valores anteriores.
El usuario puede especificar un tamaño de ventana diferente para cada uno de ellos o el mismo para todos.
Para conseguir predicciones de varios steps a futuro, los modelos ForecasterRecursive siguen una estrategia de predicción recursiva en la que, cada nueva predicción, se basa en la predicción anterior. Una alternativa es entrenar un modelo para cada uno de los steps que se desea predecir. Esta estrategia, normalmente conocida como direct multi-step forecasting, es computacionalmente más costosa que la recursiva puesto que requiere entrenar varios modelos. Sin embargo, en algunos escenarios, consigue mejores resultados. Este tipo de modelos pueden obtenerse con la clase ForecasterDirect y pueden incluir también window features y variables exógenas.
# Error test# ==============================================================================error_mse = mean_squared_error(y_true = datos_test['y'], y_pred = predicciones)print(f"Error de test (mse) {error_mse}")
Error de test (mse) 0.011792965469623133
Forecasting probabilístico
El forecasting probabilístico es una familia de técnicas que permite predecir la distribución esperada del de la serie en lugar de un único valor. Este tipo de pronóstico proporciona mucha más información, ya que permite la creación de intervalos de predicción.
Un intervalo de predicción define el espacio dentro del cual es de esperar que se encuentre el verdadero valor de yy con una determinada probabilidad. Por ejemplo, es de esperar que el intervalo de predicción (1, 99) contenga el verdadero valor de la predicción con un 98% de probabilidad.
Skforecast implementa varios métodos para el pronóstico probabilístico:
Fechas train : 1992-04-01 00:00:00 --- 2005-06-01 00:00:00 (n=159)
Fechas test : 2005-07-01 00:00:00 --- 2008-06-01 00:00:00 (n=36)
Error de test (mse): 0.010465086161791183
Cobertura del intervalo predicho: 83.33 %
Métrica custom
En los procesos de backtesting (backtesting_forecaster) y optimización de hiperparámetros (grid_search_forecaster), además de las métricas mean_squared_error, mean_absolute_error y mean_absolute_percentage_error, el usuario puede utilizar cualquier función que desee siempre y cuando cumpla lo siguiente:
Tiene como argumentos:
y_true: verdaderos valores de la serie.
y_pred: valores predichos.
Devuelve un valor numérico (float o int).
El modelo es mejor cuanto menor es la métrica. Esto únicamente es necesario si se quiere que la función grid_search_forecaster reentrene automáticamente el mejor modelo encontrado.
Gracias a esta flexibilidad, es posible evaluar la capacidad predictiva del modelo con métricas aplicables a escenarios muy diversos. Por ejemplo:
Considerar únicamente determinados meses, días u horas.
Considerar únicamente fechas que sean festivos.
Considerar únicamente el último step del horizonte predicho.
Véase un ejemplo en el que se quiere predecir un horizonte de 12 meses, pero únicamente considerar los últimos 3 meses de cada año para calcular la métrica de interés.
# Métrica custom # ==============================================================================def custom_metric(y_true, y_pred):''' Calcular el mean_absolute_error utilizando únicamente las predicciones de los últimos 3 meses del año. ''' mask = y_true.index.month.isin([10, 11, 12]) metric = mean_absolute_error(y_true[mask], y_pred[mask])return metric# Backtesting # ==============================================================================metrica, predicciones_backtest = backtesting_forecaster( forecaster = forecaster, y = datos['y'], cv = cv, metric = custom_metric, verbose =True )metrica
Information of folds
--------------------
Number of observations used for initial training: 79
Number of observations used for backtesting: 116
Number of folds: 4
Number skipped folds: 0
Number of steps per fold: 36
Number of steps to exclude between last observed data (last window) and predictions (gap): 0
Last fold only includes 8 observations.
Fold: 0
Training: 1992-04-01 00:00:00 -- 1998-10-01 00:00:00 (n=79)
Validation: 1998-11-01 00:00:00 -- 2001-10-01 00:00:00 (n=36)
Fold: 1
Training: No training in this fold
Validation: 2001-11-01 00:00:00 -- 2004-10-01 00:00:00 (n=36)
Fold: 2
Training: No training in this fold
Validation: 2004-11-01 00:00:00 -- 2007-10-01 00:00:00 (n=36)
Fold: 3
Training: No training in this fold
Validation: 2007-11-01 00:00:00 -- 2008-06-01 00:00:00 (n=8)
Los modelos generados con Skforecast se pueden cargar y guardar usando las librerías Pickle o Joblib. Para facilitar el proceso, dos funciones están disponibles: save_forecaster y load_forecaster. A continuación, se muestra un sencillo ejemplo. Para más información cosultar: skforecast save and load forecaster.
En los proyectos relacionados con forecasting es frecuente que, como resultado de la etapa de experimentación y desarrollo, se genere un modelo. Para que este modelo consiga un impacto real en el negocio, se tiene que poder poner en producción y generar predicciones cada cierto tiempo, con las que tomar decisiones. Esta necesidad ha guiado en gran medida el desarrollo de la librería Skforecast.
Supóngase un caso de uso en el que se han de generar predicciones de forma semanal, por ejemplo, cada lunes el modelo tiene que predecir el resto de la semana. Una forma de conseguir este comportamiento es reentrenando el modelo semanalmente justo antes de que se ejecute la primera predicción y llamar a continuación al método predict del objeto forecaster.