Décomposer une série temporelle sous R

Tendance, saisonnalité et résidus ... Isoler ces 3 composantes pour mieux comprendre votre série de données temporelles

La temporalité inhérente aux time series en font un objet d'analyse à part qu'il est parfois difficile d'appréhender. Nous allons voir dans cet article ce qui constitue bien souvent une première approche d'analyse, à savoir la décomposition de la série selon les 3 composantes qui la définissent: la tendance, la saisonnalité et les résidus.

Données de travail

Nous allons travailler avec un dataset regroupant le nombre moyen de visiteurs quotidiens d'un site internet. Nous disposons dans cette série de données des observations mensuelles allant de janvier 2008 à décembre 2022. Voici à quoi ressemble le dataset :

periode;visiteurs
01/01/2008;50
01/02/2008;25
01/03/2008;3
01/04/2008;12
01/05/2008;57
01/06/2008;82
01/07/2008;108
...

Voici par ailleurs le graphique correspondant à la série :

R timeserie serie temporelle decompose trend tendance saisonnalite arima

Nous pouvons constater sur le graphique ci-dessus qu'une stratégie SEO a été mise en place fin 2016. Cela semble avoir eu un impact sur le nombre de visites puisque la tendance haussière s'est accélérée à partir de 2017. Nous reviendrons sur ce point.

Pour information, ce dataset est disponible ici.

Importons les données dans un dataframe df_visites :


df_visites <- read.table("visiteursSite.csv", header = TRUE,
                     sep = ";",
                     quote = "\"",
                     fill = TRUE,
                     comment.char = "")
str(df_visites)
'data.frame':	180 obs. of  2 variables:
 $ periode  : chr  "01/01/2008" "01/02/2008" "01/03/2008" "01/04/2008" ...
 $ visiteurs: int  50 25 3 12 57 82 108 101 85 71 ...

La période a été importée en chaine de caractères, il nous faut la convertir en format date pour la suite :


df_visites$periode <- as.Date(df_visites$periode, format="%d/%m/%Y")
str(df_visites)
'data.frame':	180 obs. of  2 variables:
 $ periode  : Date, format: "2008-01-01" "2008-02-01" "2008-03-01" ...
 $ visiteurs: int  50 25 3 12 57 82 108 101 85 71 ...

Time serie additive ou multiplicative

Nous l'avons déjà évoqué, une série temporelle est la résultante d'une combinaison de 3 composantes : la tendance, la saisonnalité et les résidus, comprenez par la, la variation résiduelle aléatoire inexpliquée par les 2 premières composantes. Décomposer une série temporelle, c'est à dire isoler chacune des composantes, revient à lui appliquer un modèle. On peut en distinguer deux, applicables selon la nature de la série, additive ou multiplicative.

Soit la série temporelle \(X_{(t)}\) composée d'une tendance \(T_{(t)}\), d'une saisonnalité \(S_{(t)}\) et d'une partie non expliquée \(\epsilon_{(t)}\) :

Un modèle additif s'écrira : $${X_{(t)} = T_{(t)} + S_{(t)} + \epsilon_{(t)}}$$

Un modèle multiplicatif s'écrira : $${X_{(t)} = T_{(t)}.(1 + S_{(t)}).(1 + \epsilon_{(t)})}$$

Comment identifier la nature d'une série temporelle ?

La saisonnalité d'une série temporelle décrit un comportement de la variable d'intérêt répétitif sur une période donnée. Etudier la nature d'une série temporelle va revenir à analyser la variation au cours du temps de la saisonnalité. Si celle-ci varie, la série temporelle pourra être considérée de nature multiplicative. Dans le cas contraire, si la saisonnalité reste constante, une nature additive sera à privilégier.

Les données sur lesquelles nous travaillons dans le cadre de cet article laissent apparaitre un changement de nature fin 2016 suite à l'adoption d'une stratégie SEO visant à augmenter la visibilité du site internet. La série, avant cette date, peut être considérée comme étant de nature additive. La tendance suit en effet un canal haussier constant, la saisonnalité, bien visible sur une période de 12 mois ne semble subir aucune variation notable entre chaque période.
La série post évènement, au contraire, connait une accélération à la hausse. La saisonnalité suit le mouvement. On constate en effet une amplitude de plus en plus forte avec le temps. Nous allons considérer que les données, à partir de 2017, suivent une série de nature multiplicative.

R timeserie serie temporelle decompose trend tendance saisonnalite arima

Nous allons à présent isoler la série antérieure au 1er décembre 2016 dans un premier dataframe df_visitesAv puis la série postérieure à cette date dans un second dataframe df_visitesAp :


df_visitesAv <- df_visites[df_visites$periode<='2016-12-01',]
df_visitesAp <- df_visites[df_visites$periode>'2016-12-01',]

Nous considérons donc que nous disposons d'une série additive df_visitesAv et d'une série multiplicative df_visitesAp. Si la logique de décomposition reste la même, les opérations sous-jacentes diffèrent. Il faut donc dans la plupart des cas, avoir évalué la nature de la série pour pouvoir la décomposer de façon efficace.

Méthodes de décomposition

La décomposition d'une série temporelle peut se résumer aux étapes suivantes :

  • estimer la tendance,
  • éliminer celle-ci de la série,
  • estimer la saisonnalité,
  • l'éliminer également pour obtenir ainsi les résidus

Nous allons évoquer dans cet article deux méthodes de décomposition. Une première méthode simple via la moyenne mobile, puis une seconde méthode dite de régression locale (LOESS).

Décomposition manuelle via la moyenne mobile

La décomposition d'une série temporelle peut s'effectuer directement via la fonction decompose(...). Avant cependant d'illustrer son utilisation, procédons à une rapide décomposition manuelle sur notre série additive:

Commençons par isoler la tendance. Pour cela, nous allons tout simplement dresser une moyenne mobile de la série sur une période couvrant une saison. En ce qui nous concerne, la saison a une durée de 12 mois. La librairie zoo va nous permettre de tracer facilement, via la fonction rollmean, la moyenne mobile de la série :


require(zoo)

#Calcul du trend de Visiteurs
trendVisiteurs <- rollmean(df_visitesAv$visiteurs, 12, na.pad = TRUE, align = "right")
plot(trendVisiteurs, type = "S")
R timeserie serie temporelle decompose trend tendance saisonnalite arima

La tendance étant isolée, nous allons isoler la saisonnalité. Cela va s'effectuer en deux étapes. La soustraction, tout d'abord de la tendance à la série. Cette opération, appelée "detrend" n'est pertinente que pour les séries de nature additive.
La seconde étape va consister à standardiser la saisonnalité. Concrètement, nous allons représenter notre série sans tendance sous la forme d'une matrice de dimension $${N * M}$$, ou N représente la période couverte par une saison et M, le nombre de saisons. La moyenne inter-saisons par période va nous donner une saison type qu'il conviendra de répéter sur le nombre de saisons pour ainsi, obtenir une saisonnalité moyenne représentant notre série :


require(zoo)

#Calcul du trend de Visiteurs
#On isole la saisonnalite via une soustraction du trend aux donnees originales
detrendVisiteurs <- df_visitesAv$visiteurs - trendVisiteurs

#Standardisation de la saisonnalite
mVisiteurs <- t(matrix(data=detrendVisiteurs, nrow=12))
saisonnaliteVisiteurs <- colMeans(mVisiteurs, na.rm=T)
plot(rep(saisonnaliteVisiteurs, 12), type = "S")
R timeserie serie temporelle decompose trend tendance saisonnalite arima

Nous avons désormais la tendance et la saisonnalité de notre série. Il ne nous reste plus qu'à les soustraire à celle-ci pour obtenir les résidus. Il est important, en effet, de les analyser car l'identification d'un motif ou d'une structure dans ces derniers sera synonyme de modèle non adapté. Il pourra s'agit, par exemple, d'une méprise dans la nature même de la série.


#Determination des residus
randomVisiteurs <- df_visitesAv$visiteurs - trendVisiteurs - saisonnaliteVisiteurs
plot(randomVisiteurs, type = "S")
R timeserie serie temporelle decompose trend tendance saisonnalite arima

La fonction decompose()

Ce que nous venons d'implémenter manuellement, pour illustrer la décomposition d'une série temporelle, peut être réalisé très rapidement via la fonction decompose(). Ci-dessous, voici le rendu de cette fonction pour notre série :


tsVisiteurs <- ts(df_visitesAv$visiteurs, frequency=12)
decomposition <- decompose(tsVisiteurs, 'additive')
plot(decomposition)
R timeserie serie temporelle decompose trend tendance saisonnalite arima

Comme vous pouvez le constater dans le code ci-dessus, il est nécessaire de convertir nos données en objet TimeSerie (ts) d'une part et, d'autre part, de connaitre au préalable la nature de la série.
De la même façon, la décomposition de la série multiplicative post 2016 pourra s'effectuer comme ceci :


tsVisiteurs <- ts(df_visitesAp$visiteurs, frequency=12)
decomposition <- decompose(tsVisiteurs, 'multiplicative')
plot(decomposition)

Notons enfin qu'il est possible d'accéder, depuis l'objet décomposé, aux 3 composantes. Dans notre cas, il s'agira des objets decomposition$trend, decomposition$seasonal et decomposition$random pour, respectivement, la tendance, la saisonnalité et les résidus.

Décomposition par régression locale ou LOESS

La méthode de régression LOESS ( pour Locally Estimated Scatterplot Smoothing) est non paramétrique. L'ajustement de la série se fait localement sur la base des points de voisinage, d'où son nom, régression locale. La décomposition que nous allons utiliser se nomme STL (pour Seasonal-Trend decomposition using LOESS) et, comme son nom l'indique, elle exploite la régression LOESS pour lisser des estimations des 3 composantes de notre série.


tsVisiteurs <- ts(df_visitesAv$visiteurs, frequency=12)
decompLOESS <-stl(tsVisiteurs, s.window="periodic", robust=TRUE)  
plot(decompLOESS) 
R timeserie serie temporelle decompose trend tendance saisonnalite arima

Dans le code ci-dessus, vous constatez que nous avons été obligé de convertir notre série en objet Time Serie (ts), afin de spécifier une fréquence. Par ailleurs, nous avons, lors de l'appel à la fonction STL, demandé une application robuste de la régression. Cela signifie la prise en compte d'une pondération de voisinage plutôt que de considérer chaque point comme étant d'égale importance. La robustesse peut être nécessaire pour gommer les outliers.

Crédits

Miniature issue de Image de vecstock sur Freepik


Retrouvez dans la rubrique "Nos datasets" toutes les données dont vous aurez besoin pour tester et pratiquer !