Lissage par moyenne mobile sous R

Lisser une série temporelle sur la base d'une moyenne mobile simple ou centrée

Nous avons évoqué dans un précèdent article l'imputation des valeurs manquantes dans une série temporelle. Nous allons ici continuer sur les séries temporelles et voir comment procéder a un lissage sur la base des moyennes mobiles.

Données de travail

La série sur laquelle nous allons nous baser retrace la fréquentation, en nombre de véhicules, d'un péage d'autoroute. Cette série couvre une période journalière allant du 01 janvier 2010 au 26 octobre 2016.

Chargeons nos données dans un dataframe "df_peage", et voyons à quoi elles ressemblent :


df_peage <- read.table("peage.csv", header = TRUE,
                           sep = ",",
                           quote = "\"",
                           fill = TRUE,
                           comment.char = "",
                           encoding="UTF-8")
head(df_peage)
  jour mois annee passages
1    1    1  2010     1782
2    2    1  2010     2178
3    3    1  2010     1804
4    4    1  2010     1892
5    5    1  2010     1716
6    6    1  2010     1771

Nous constatons que la date d'observation fait l'objet de 3 variables distinctes : année, mois et jour.
A l'instar de ce que nous avions fait dans notre précèdent article sur les séries temporelles, nous allons créer une nouvelle colonne : date_jour de format Date.


df_peage['date_jour'] <- paste(sprintf("%02d", df_peage$jour), 
                          sprintf("%02d", df_peage$mois), 
                          df_peage$annee, sep='/')
df_peage[['date_jour']] <- as.Date(df_peage[['date_jour']], format='%d/%m/%Y')
head(df_peage)
  jour mois annee passages  date_jour
1    1    1  2010     1782 2010-01-01
2    2    1  2010     2178 2010-01-02
3    3    1  2010     1804 2010-01-03
4    4    1  2010     1892 2010-01-04
5    5    1  2010     1716 2010-01-05
6    6    1  2010     1771 2010-01-06

Visualisations nos données :

R timeseries serie temporelle mobile average moyenne

Moyenne mobile simple et moyenne mobile centrée

Lisser les données au moyen d'une moyenne mobile d'ordre p va consister à définir une fenêtre de p périodes afin d'effectuer la moyenne des valeurs couvertes. Cette moyenne sera ensuite affectée à la période centrale. La fenêtre va ainsi se déplacer du début à la fin de la série a lisser.
On comprend aisément qu'un ordre impair ne pose pas de problème quand à la détermination de la période qui va porter la moyenne, ceci est moins évident dans le cas d'un ordre pair. C'est ici qu'intervient la notion de moyenne centrée, puisqu'elle va consister, dans le cas d'un ordre pair, à établir, d'une part, une fenêtre de p+1 périodes et, d'autre part, à pondérer la première et la dernière période par 1/2.
Voyons tout ceci en détails.

Moyenne mobile simple

Ci-dessous une fonction lissage qui va prendre en paramètres d'entrée la série numérique a lisser (var_num), la taille de la fenêtre (alpha) ainsi que la série temporelle (var_temps).
Apres avoir calculé la demie fenêtre (division entière de la fenêtre alpha), la fonction lissage va parcourir la série numérique afin d'effectuer la moyenne des valeurs couvertes. Il s'agit donc d'une moyenne mobile simple.


lissage <- function(var_num, alpha, var_temps){

  n = length(var_num)
  
  #Initialisation des vecteurs de sortie
  valeurs_lissees <- c()
  vecteur_temps <- c()
  
  #Nous parcourons la série en prenant soin de soustraire alpha à la borne sup.
  demieFenetre <- alpha%/%2
  for (i in demieFenetre:(n-demieFenetre)){
    valeurs_lissees <- c(valeurs_lissees, 
                        (sum(var_num[(i-demieFenetre):(i+demieFenetre)], 
                        na.rm = TRUE)/alpha))
    vecteur_temps <- c(as.Date(vecteur_temps), var_temps[i])
  }
  
  return(list(valeurs_lissees, vecteur_temps))
}

Voyons le résultat obtenu pour des ordres de 5 jours, 25 jours et 155 jours.
Pour cela nous définissons notre collection d'ordres alpha et de couleurs associées.


alpha_collection <- c(5, 55, 125)
alpha_couleurs <- c(2, 3, 4)

Nous lançons ensuite la fonction lissage pour chacun des ordres à éditer. Les données propres aux moyennes mobiles seront stockées dans un dataframe mvDf.


mvDf <- data.frame()
for (alpha in alpha_collection){
  mvData <- lissage(df_peage$passages, alpha, df_peage$date_jour)
  mvData <- as.data.frame(mvData, col.names = c('mav', 'date_jour'))
  mvData['alpha'] = alpha
  mvData['couleur'] = alpha_couleurs[which(alpha == alpha_collection)]
  mvDf <- rbind(mvDf, mvData)
}

Enfin il ne nous reste plus qu'a dresser le graphique.


library(ggplot2)
theme_set(
  theme_classic() + 
    theme(legend.position = "top")
)

p <- ggplot(data = mvDf, aes(x = date_jour, y = mav))+
  geom_line(aes(group = couleur, color = as.factor(couleur)), size = 1)
p + ggtitle("Lissage par moyenne mobile ")+
  theme(plot.title = element_text(hjust = 0.5),
        legend.title=element_blank(),
        legend.background = element_rect(linetype = 1, size = 0.5, colour = 1))+
  labs(y="Entrées", x = "Jours")+
  scale_color_manual(labels = paste0("alpha = ", alpha_collection), 
                     values = alpha_couleurs)
R timeseries serie temporelle mobile average moyenne

Moyenne mobile centrée

La fonction lissage illustrée précédemment peut être plus précise sur les lissages d'ordre pair, aussi nous allons la modifier pour les prendre en compte.


lissage <- function(var_num, alpha, var_temps){

  n = length(var_num)
  
  #Initialisation des vecteurs de sortie
  valeurs_lissees <- c()
  vecteur_temps <- c()
  
  #Nous parcourons la série en prenant soin de soustraire alpha à la borne sup.
  demieFenetre <- alpha%/%2
  for (i in demieFenetre:(n-demieFenetre)){
    
    #Lissage centre
    if(alpha%%2==0){
      valeurs_lissees <- c(valeurs_lissees, 
                             (sum(var_num[(i-demieFenetre)]/2,
                                  var_num[(i-demieFenetre+1):(i+demieFenetre-1)],
                                  var_num[(i+demieFenetre)]/2,
                              na.rm = TRUE)/alpha))
    #Lissage simple
    }else{
      valeurs_lissees <- c(valeurs_lissees, 
                          (sum(var_num[(i-demieFenetre):(i+demieFenetre)], 
                          na.rm = TRUE)/alpha))
    }

    
    vecteur_temps <- c(as.Date(vecteur_temps), var_temps[i])
  }
  
  return(list(valeurs_lissees, vecteur_temps))
}

La seule différence notable réside dans le test sur la parité de l'ordre alpha. Pour un alpha pair, la calcul de la moyenne est diffèrent dans la mesure ou on attribue un poids de 1/2 au premier et dernier terme de la somme.


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