Une sĂ©rie temporelle, ou sĂ©rie chronologique, est une suite de valeurs numĂ©riques reprĂ©sentant lâĂ©volution dâune quantitĂ© spĂ©cifique au cours du temps. De telles suites de variables alĂ©atoires peuvent ĂȘtre exprimĂ©es mathĂ©matiquement afin dâen analyser le comportement, gĂ©nĂ©ralement pour comprendre son Ă©volution passĂ©e et pour en prĂ©voir le comportement futur. Une telle transposition mathĂ©matique utilise le plus souvent des concepts de probabilitĂ©s et de statistique.
Présentation
Il y a 3 ans, lâĂ©quipe de Facebook Core Data Science team Ă sorti en open source un puissant outil de prĂ©vision pour les sĂ©ries temporelles appelĂ© Prophet.
Prophet se base sur un modĂšle additif qui sâadapte par rapport Ă la saisonnalitĂ© des donnĂ©es mais pas que, il est assez robuste pour des jeux de donnĂ©es qui reprĂ©sentent des donnĂ©es manquantes ou comportant des valeurs aberrantes (ou outliers).
Pour les non statisticiens ou data scientistes, il est assez dĂ©licat de partir sur une connaissance des principes dâanalyse et de prĂ©vision temporelles mais Prophet est venu rĂ©pondre Ă ce besoin de la plus simple des façon.
Lâobjectif de lâarticle est de faire une introduction de Prophet sous Python đ. Les donnĂ©es et le code seront disponibles sur le Github de Ingeniance ainsi que sur Google Colaboratory en fin du blog.
Pourquoi Prophet?
A travers son article scientifique, Prophet vient rĂ©pondre Ă une problĂ©matique de scalabilitĂ© sous 3 volets: Le grand nombre de personnes qui travaillent sur les prĂ©visions temporelles et qui ont une faible connaissance mathĂ©matiques du domaine Le grand nombre de problĂšmes de prĂ©visions qui peuvent parfois venir dâun caractĂšre idiomatique Le grand nombre de sujets sur lesquels il est possible dâappliquer des prĂ©visions temporelles qui demandent des configurations diffĂ©rentes Prophet vient rĂ©pondre Ă ce problĂšme avec:
Une intégration sous R et Python
Prophet propose sa bibliothĂšque sous CRAN pour les utilisateurs de R ainsi que sur PyPi pour les adeptes de Python.
Une saisonnalité additive et multiplicative
La saisonnalité est une caractéristique commune des séries chronologiques. Il peut apparaßtre sous deux formes:
- Additive
- Multiplicative
Dans le premier cas, lâamplitude de la variation saisonniĂšre est indĂ©pendante du niveau, tandis que dans le second, elle est liĂ©e. Comme le montre la figure suivante:
Par défaut, Prophet adapte son modÚle à travers un modÚle additif, mais supporte également le modÚle multiplicatif.(Documentation)
# ModĂšle additif
additiveModel = Prophet()
additiveModel = Prophet(seasonality_mode='additive')
# ModĂšle multiplicatif
MultiplicativeModel = Prophet(seasonality_mode='multiplicative')
Un support des données mensuelles et sous quotidiennes
Prophet supporte les sĂ©ries non quotidiennes de type mensuel (si on a un enregistrement par mois) ou sous quotidien (si on a un enregistrement toutes les 6 ou 12 heures par exemple). Prophet sâadapte automatiquement Ă ce type de donnĂ©es sans lui indiquer ceci en analysant lâĂ©cart entre les lignes du jeu de donnĂ©es dâentrĂ©e.(Documentation)
Un support des événements spéciaux, jours fériés et vacances
Un point trĂšs fort de Prophet est la possibilitĂ© de personnaliser une saisonnalitĂ© dĂ©pendamment de plusieurs Ă©vĂ©nements spĂ©ciaux, fĂȘtes et vacances. Il est possible dâutiliser ses fonctions pour crĂ©er de façon automatique une liste dâĂ©vĂšnements par pays, rĂ©gion, dĂ©partement et annĂ©e et les passer au modĂšle pour apprentissage. Si tout de mĂȘme vous nâĂȘtes pas satisfaits par le nombre de pays assez restreint, il est possible dâutiliser la bibliothĂšque holidays ou mĂȘme en important son propre jeu de donnĂ©es de ces Ă©vĂ©nements. Prophet sâadaptera automatiquement pour en tenir compte.
Sous Python, ces Ă©vĂ©nements sont disponibles pour nâimporte quelle date de nâimporte quelle annĂ©e.
Sous R, ces Ă©vĂ©nements sont disponibles entre 1995 et jusquâen 2044 et pouvant ĂȘtre Ă©tendues en lançant un script qui vient avec Prophet pour en gĂ©nĂ©rer dâavantage.
Une gestion des valeurs aberrantes
Prophet peut gérer les individus aberrants de façon automatique en ne tenant pas compte de leurs présence lors du calcul des prévisions. Or, des fois il trouvera des difficultés pour le faire et si un grand nombre de valeurs aberrantes est présent à un certain moment, ceci peut biaiser les prédictions futures. Pour ceci, Facebook Prophet demande à les supprimer de la série du moment que Prophet supporte les valeurs manquantes qui seront remplacés par les prévisions de son modÚle.(Documentation)
Une gestion des changements de tendance
Prophet propose une gestion des changements sur la courbe, en positionnant sur les 80% de la courbe et de façon uniforme des points de changement (25 point pour ĂȘtre prĂ©cis) et rĂ©duira ce nombre de points au fur et Ă mesure quâil paramĂštre son modĂšle pour ne garder que le minimum. Il est mĂȘme possible dâindiquer de façon manuelle les points de changement sur la courbe. La flexibilitĂ© de la courbe de prĂ©diction est automatiquement initialisĂ©e, mais on peut Ă©galement la modifier en spĂ©cifiant le paramĂštre de flexibilitĂ© Ă la crĂ©ation du modĂšle.(Documentation)
Et bien plusâŠ
Outre les forces dĂ©jĂ citĂ©es, lâarticle scientifique de Facebook Data Science team et la documentation officielle peuvent vous convaincre dâavantage dâutiliser Prophet et commencer Ă faire vos prĂ©visions temporelles.
POC
Lâexemple sur lequel on va travailler est un exemple dans le domaine du retail, qui est lâexemple Retail Sales. Ce jeu de donnĂ©es concerne le nombre mensuel des ventes rĂ©alisĂ©s entre Janvier 1992 et Mai 2016 ce qui Ă©quivaut Ă 293 lignes de donnĂ©es reprĂ©sentĂ©es sur 2 colonnes:
- Une colonne de type datetime dont le label est ds
- Une colonne de type entier dont le label est y
Si les colonnes du jeu de donnĂ©es ne sont pas [âdsâ, âyâ], il est nĂ©cessaire dâenlever les colonnes non pertinentes et de renommer les entĂȘtes de colonnes.
On commence par installer et importer les bibliothÚques nécessaires.
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.dates as dates
from datetime import datetime
# Facebook Prophet
!pip install fbprophet
from fbprophet import Prophet
from fbprophet.diagnostics import cross_validation
from fbprophet.diagnostics import performance_metrics
from fbprophet.plot import plot_cross_validation_metric
from fbprophet import hdays
!pip install holidays
import holidays
On importe par la suite le jeu de donnĂ©es, on reformate le type des colonnes et on mets lâindex sur la colonnes ds.
RetailSalesDataframe = pd.read_csv("./RetailSales.csv", delimiter=",")
RetailSalesDataframe["ds"] = pd.to_datetime(RetailSalesDataframe["ds"], infer_datetime_format=True)
RetailSalesDataframe["y"] = pd.to_numeric(RetailSalesDataframe["y"])
indexedDataframe = RetailSalesDataframe.set_index(["ds"])
Pour visualiser et comprendre les donnĂ©es, on calcule la moyenne mobile et lâĂ©cart type et on les affiche.
Roolmean = indexedDataframe.rolling(window = 12).mean()
Roolstd = indexedDataframe.rolling(window = 12).std()
orig = plt.plot(indexedDataframe, color = "blue", label = "Original")
mean = plt.plot(Roolmean, color = "red", label = "Moyenne mobile")
std = plt.plot(Roolstd, color = "black", label = "Ecart Type")
plt.legend(loc = "best")
plt.title("Tendance des ventes par date avec la moyenne mobile et la
variance")
plt.rcParams["figure.figsize"] = [15,9]
plt.show(block = False)
On remarque que la courbe prĂ©sente une tendance Ă la hausse durant toute la pĂ©riode Ă©tudiĂ©e (sauf en 2008 pĂ©riode de la crise financiĂšre) mais Ă©galement un effet saisonnier avec une hausse pour certaines pĂ©riodes de lâannĂ©e et une forte baisse pour dâautres. Pour mieux voir ceci, on affiche lâallure de la courbe pendant les annĂ©es de 1992 jusquâĂ 1995.
ye1 = plt.plot(indexedDataframe[0:11], color = "blue", label = "1992")
ye2 = plt.plot(indexedDataframe[12:23], color = "orange", label = "1993")
ye3 = plt.plot(indexedDataframe[24:35], color = "green", label = "1994")
ye4 = plt.plot(indexedDataframe[36:47], color = "red", label = "1995")
plt.legend(loc = "best")
plt.title("Tendance des ventes par date sur 4 années")
plt.show(block = False)
image
Courbe dont lâallure est non stationnaire et suit une tendance saisonniĂšre avec peu de ventes sur les 1 ers mois de lâannĂ©e. Les ventes augmentent de façon significative pendant lâĂ©tĂ© et retombent aprĂšs le mois dâAoĂ»t et reprennent leurs hausses pendant la pĂ©riode de noĂ«l. Pour comprendre lâĂ©volution des ventes en 2008, on analysera les quelques annĂ©es qui ont suivi 2008.
crisis08 = plt.plot(indexedDataframe.loc['2008-01-01':'2008-12-30'], color = "blue", label = "2008")
crisis09 = plt.plot(indexedDataframe.loc['2009-01-01':'2009-12-30'], color = "red", label = "2009")
crisis09 = plt.plot(indexedDataframe.loc['2010-01-01':'2010-12-30'], color = "green", label = "2010")
plt.legend(loc = "best")
plt.title("Tendance des ventes par date sur la période aprÚs crise de 2008")
plt.show(block = False)
image
Le graphe prĂ©cĂ©dent relate lâimpact de la crise financiĂšre de 2008 sur le nombre de ventes. En effet, les ventes ont baissĂ© durant lâannĂ©e 2009 pour reprendre la tendance positive progressive Ă partir de 2010. Passons aux prĂ©dictions, on commence par crĂ©er un objet Prophet en lui passant en paramĂštre le type de saisonnalitĂ© car on est face Ă une saisonnalitĂ© multiplicative.
prophetModel = Prophet(seasonality_mode="multiplicative")
On entraßne le modÚle sur notre jeu de données.
prophetModel = prophetModel.fit(RetailSalesDataframe)
Une fois que câest fait, on lance les prĂ©visions sur la frĂ©quence ainsi que le nombre de pĂ©riode voulus, pour mon cas, jâai choisi une prĂ©vision sur 60 mois (5ans) et donc jusquâen Avril 2021:
future = prophetModel.make_future_dataframe(periods=60, freq='M')
Lâargument freq peut ĂȘtre nâimporte quel argument de type pd.date_range, comme D pour Daily ou M pour Monthly.
Pour voir les ventes prévus dans 5ans (à partir de 2016):
forecast = prophetModel.predict(future)
forecast[['ds', 'yhat', 'yhat_lower', 'yhat_upper']].tail()
Image
Le yhat ou y prevu est le résultat prévu avec un degré de certitude de 95% que notre résultat sera forcement entre le yhat_lower et le yhat_upper. Enfin on affiche le graphe qui résume ces résultats:
fig1 = prophetModel.plot(forecast)
Image
fig2 = prophetModel.plot_components(forecast)
Image
LâĂ©valuation du modĂšle
Pour diagnostiquer nos rĂ©sultats, il faudra bien comparer sur les donnĂ©es en entrĂ©es Ă celles que Prophet Ă prĂ©vu. Pour ceci, Prophet propose dâeffectuer une validation croisĂ©e sur notre modĂšle sur un intervalle de temps. La pĂ©riode dâentrainement est 3 fois lâhorizon et les coupures ont lieux chaque mi-horizon mais câest possible de personnaliser ces paramĂštres lors de lâappel. Pour mon cas, jâai donnĂ© 21 ans comme pĂ©riode dâentrainement initial, une pĂ©riode dâun mois et 6 mois dâhorizon.
df_cv = cross_validation(prophetModel, initial = '7665 days', period =
'30 days', horizon = '180 days')
df_p = performance_metrics(df_cv)
df_p
Image
Lâerreur absolue moyenne en pourcentage (Mean Absolute Percentage Error, alias MAPE) est la moyenne des Ă©carts en valeur absolue par rapport aux valeurs observĂ©es. Câest donc un pourcentage et par consĂ©quent un indicateur pratique de comparaison.
fig = plot_cross_validation_metric(df_cv, metric='mape')
Image
Pour notre cas, le MAPE ne dépasse pas les 2.5% et donc on peut juger que nos prédictions sont excellentes pour avoir une vision des ventes sur la période des 5 années à venir.
Pour récompenser vos efforts pour arriver à la fin, un bonus relatif à expérimentation des jours fériés vous attends au niveau du code sur le repository Github et le notebook Google Collaboratory.
Conclusion
Lâanalyse des sĂ©ries temporelles a depuis toujours Ă©tĂ© une branche trĂšs Ă©tudiĂ©e en analyse de donnĂ©es et en machine learning et la crĂ©ation de telles librairies par lâune des plus grandes entreprises du monde en est la preuve. Ces librairies viennent cacher la complexitĂ© dâune connaissance poussĂ©e des statistiques en simplifiant la rĂ©alisation des prĂ©dictions, leur visualisation et leur exportation de la plus simple des façons.