Package gganimate

Pascale Caissy & Jacob Lessard-Lord

2020-03-16


1 Introduction à gganimate

R est un logiciel très puissant permettant la création de graphiques, ceux-ci étant à la base de la présentation de données statistiques. Un package très utilisé pour la création de graphiques est ggplot2, sa popularité s’expliquant par sa facilité d’utilisation et la répétitivité de ses commandes. Une extension de ggplot2 très appréciée des fans de visualisation graphique est le package gganimate.

Le package gganimate a d’abord été développé par David Robinson au début de 2016. Cependant, cette version n’a jamais été publiée sur les serveurs du CRAN. À l’automne 2017, Thomas Lin Pedersen a pris en charge la maintenance du package gganimate. Des changements majeurs ont ensuite été apportés au package, ce qui a mené à une nouvelle version de gganimate à l’été 2018. Le package gganimate est maintenant disponible sur les serveurs du CRAN. La dernière version (1.0.5) a été déposée le 9 février 2020.

Ce package permet d’ajouter des animations aux graphiques statiques produits à l’aide de ggplot2. Une animation est en fait une image générée à partir d’une série d’images (tableaux ou frames en anglais) superposées rapidement pour donner l’illusion d’un mouvement continu. Ces animations peuvent entre autres être visualisées sur des pages html ou enregistrées sous forme de gif. Ainsi, ce package permet d’attirer l’attention sur les graphiques d’une présentation ou d’aider à représenter une multitude de données dans un même graphique. Ici, nous nous amuserons à créer des graphiques animés à l’aide de gganimate en utilisant deux jeux de données du R de base, soit quakes et WorldPhones.

2 Présentation et préparation des deux jeux de données

2.1 quakes

Le jeu de données quakes du package datasets du R de base contient les données de 1000 séismes de magnitude 4.0 et plus s’étant produits près des îles Fidji depuis 1964. Chaque observation inclut la latitude (lat), la longitude (long), la profondeur (depth), la magnitude (mag) et le nombre de stations à proximité (stations) ayant rapporté une activité sismique.

Ajoutons à ce jeu de données une variable factorielle prénommée region. Ce facteur déterminera si chaque observation a été observée à l’ouest ou à l’est de la longitude 175°. Créons également un facteur nommé mag_catego. Ce dernier servira à créer des classes de magnitude du séisme arrondie à l’entier inférieur (4, 5 ou 6).

quakes$region <- factor(quakes$long >= 175, labels = c("Ouest", "Est"))
quakes$mag_catego <- factor(floor(quakes$mag))
str(quakes)
'data.frame':   1000 obs. of  7 variables:
 $ lat       : num  -20.4 -20.6 -26 -18 -20.4 ...
 $ long      : num  182 181 184 182 182 ...
 $ depth     : int  562 650 42 626 649 195 82 194 211 622 ...
 $ mag       : num  4.8 4.2 5.4 4.1 4 4 4.8 4.4 4.7 4.3 ...
 $ stations  : int  41 15 43 19 11 12 43 15 35 19 ...
 $ region    : Factor w/ 2 levels "Ouest","Est": 2 2 2 2 2 2 1 2 2 2 ...
 $ mag_catego: Factor w/ 3 levels "4","5","6": 1 1 2 1 1 1 1 1 1 1 ...

2.2 WorldPhones

Le jeu de données WorldPhones du package datasets du R de base contient les données du nombre de lignes téléphoniques enregistrées entre 1951 et 1961 sur chaque continent sous forme de matrice. Dans cette matrice, le nom des colonnes indique le nom du continent où les données ont été enregistrées alors que le nom des rangées représente l’année à laquelle le nombre de lignes téléphoniques a été calculé.

Transformons d’abord cette matrice en dataframe avec le nom des rangées, donc l’année, inclus dans une colonne à part (Year).

WorldPhones <- data.frame(WorldPhones)
WorldPhones$Year <- as.numeric(rownames(WorldPhones))
head(WorldPhones, n = 3)
     N.Amer Europe Asia S.Amer Oceania Africa Mid.Amer Year
1951  45939  21574 2876   1815    1646     89      555 1951
1956  60423  29990 4708   2568    2366   1411      733 1956
1957  64721  32510 5230   2695    2526   1546      773 1957

Nous pouvons constater que le dataframe WorldPhones a une mise en forme large. Afin de pouvoir utiliser ce dataframe pour produire un graphique animé plus tard, transférons-le dans une mise en forme longue. Pour ce faire, nous utiliserons la fonction pivot_longer du package tidyr.

library(tidyr)
WorldPhones_long <- pivot_longer(
  data = WorldPhones, 
  cols = c(N.Amer, Europe, Asia, S.Amer, Oceania, Africa, Mid.Amer), 
  names_to = "Region", 
  values_to = "Nbr.Phones")

head(WorldPhones_long, n = 5)
# A tibble: 5 x 3
   Year Region  Nbr.Phones
  <dbl> <chr>        <dbl>
1  1951 N.Amer       45939
2  1951 Europe       21574
3  1951 Asia          2876
4  1951 S.Amer        1815
5  1951 Oceania       1646

3 Exploration de gganimate

3.1 Installation de gganimate

Une fois le package installé dans notre librairie à l’aide de la fonction install.packages, chargeons-le dans notre espace de travail. À noter que gganimate charge automatiquement le package ggplot2 à notre session.

library(gganimate)
Loading required package: ggplot2

NOTE: L’installation du package gifski avec la fonction install.packages peut être nécessaire pour le bon fonctionnement des animations. Le package gifski permet d’assembler les différents tableaux produits en un GIF. Par la suite, même si l’installation en soi est requise, vous n’aurez pas besoin de le charger dans votre espace de travail.

3.2 Fonctions de base

Tableau 1. Liste des fonctions les plus couramment utilisées du package gganimate avec un hyperlien vers la documentation pertinente pour chacune d’entre elle. Comme il n’existe pas de documentation spécifique pour expliquer la fonction générique des fonctions de type fonction_* (sauf enter/exit_*), les hyperliens mènent à un exemple de fonction.
Fonction Possibilités de fonctions (*) Rôle S’intègre directement aux commandes ggplot2
transition_* states / time / reveal / events / filter / layers / components / manual / null Contrôle les données à animer Oui
view_* follow / step / step_manual / zoom / zoom_manual / static Contrôle le mouvement du plan de vue Oui
shadow_* wake / trail / mark / null Contrôle la persistance des données Oui
enter/exit_* manual / appear / disappear / fade / grow / shrink / recolour / recolor / fly / drift / reset Contrôlent la façon dont les données apparaissent et disparaissent Oui
animate - Contrôle la visualisation des tableaux (frames) de l’animation et leur nombre Non
anim_save - Permet la sauvegarde de l’objet animé dans un fichier Non

NOTE: L’ajout de la fonction view_* ou transition_* à une série de commandes suivant la syntaxe de ggplot2 permet d’animer n’importe quel graphique.

NOTE 2: La description exhaustive de chaque fonction du package gganimate est disponible sur le site web de gganimate.

3.3 Premier exemple avec quakes

Explorons maintenant le package gganimate. Nous débuterons en créant un graphique statique avec ggplot2 à partir du jeu de données quakes. Nous voulons mettre sous forme d’un nuage de points la latitude et la longitude où un séisme a été enregistré. Ces points seront colorés selon un gradient de la profondeur du séisme.

p <- ggplot(data = quakes) +
  geom_point(mapping = aes(x = long, 
                           y = lat,
                           colour = depth,
                           group = region)) +
  ggtitle("1000 séismes près de Fidji") +
  xlab("longitude") + 
  ylab("latitude") +
  labs(colour = "profondeur") +
  scale_color_gradient(low = "paleturquoise", 
                       high = "#008080") +
  theme_classic() +
  coord_quickmap()

p

Nous allons maintenant ajouter un peu de mouvement. Pour faciliter la visualisation du gradient de couleur, nous voulons que chaque point apparaisse graduellement en fonction de la profondeur à laquelle les séismes ont été enregistrés. Nous nous attarderons au rôle de chacune des fonctions plus bas.

panim <- p + 
  transition_time(depth) +
  shadow_mark(past = TRUE, 
              future = FALSE) + 
  enter_fade() +
  exit_fade()
 
animate(panim, nframes = 100, end_pause = 15, rewind = TRUE) 

Nous constatons que la syntaxe s’intègre très bien à celle de ggplot2. Explorons chacune des fonctions ci-haut, en les appliquant une à une au graphique p.

panim1 <- p + 
  transition_time(depth)

panim1

transion_time permet que chaque état soit défini comme une période de temps et chaque transition se fasse selon la durée entre les différents états. Bien que la profondeur (depth) ne soit pas une mesure de temps, nous voulions ici que chaque point apparaisse graduellement dans le temps selon la profondeur. Avec cette seule fonction, le graphique est un peu chaotique: les points disparaissent les uns après les autres. Nous aurons donc besoin d’une fonction de type shadow_* pour la suite.

panim2 <- p + 
  transition_time(depth) +
  shadow_mark(past = TRUE, 
              future = FALSE)
 
panim2

shadow_mark permet d’afficher les données des animations passées et futures à l’aide de ses arguments logiques past et future. En donnant la valeur TRUE à past et la valeur FALSE à future (qui sont d’ailleurs les valeurs par défaut), les données des animations passées restent visibles jusqu’à la fin de l’animation, tandis que les données des animations futures ne sont pas affichées. Toutefois, la transition entre les points est un peu abrupte. Tentons d’intégrer une fonction de type enter/exit_*.

panim3 <- p + 
  transition_time(depth) +
  shadow_mark(past = TRUE, 
              future = FALSE) + 
  enter_fade() +
  exit_fade()

enter_fade et exit_fade nous permettent de faire apparaître et disparaître les points de façon graduelle. Cependant, l’animation se réinitialise lorsque l’intégralité des données ont apparu. Il nous faudra donc contrôler l’enchaînement des tableaux avec animate.

panim <- p + 
  transition_time(depth) +
  shadow_mark(past = TRUE, 
              future = FALSE) + 
  enter_fade() +
  exit_fade()
 
animate(panim, nframes = 100, end_pause = 15, rewind = TRUE) 

animate nous permet de contrôler la visualisation des tableaux (frames) de l’animation et leur nombre (argument nframes). L’argument rewind permet de “rembobiner” notre animation panim en faisant rejouer les différents tableaux dans l’ordre inverse. De plus, nous avons pu ajouter une pause à la fin de l’animation (argument end_pause) d’une durée de 15 tableaux afin d’augmenter la période de temps où on peut voir tous les points de l’animation.

3.3.1 Sous-exemples

Nous pourrions aussi changer l’animation en modifiant le type de transition. Par exemple, nous pourrions vouloir voir apparaître les séismes s’étant produits à l’est et à l’ouest de la longitude 175° en alternance. Nous pourrions alors utiliser la fonction transition_states au lieu de transition_times.

p2 <- p + 
  transition_states(region, 
                    wrap = FALSE, 
                    transition_length = 3, 
                    state_length = 0.5) +
  shadow_mark(past = FALSE, 
              future = FALSE) + 
  enter_fade() +
  exit_fade()
 
animate(p2, nframes = 100, rewind = TRUE) 

  • Avec la fonction transition_states, les données sont séparées en différents états. Ici, les données ont été séparées selon la région en deux états : est et ouest. Nous avons pu définir le temps entre les transitions, donc à quelle vitesse les points d’une région s’effacent, avec l’argument transition_length. Nous avons également raccourci le temps entre lequel on voit les points d’une région individuelle en modifiant la valeur de l’argument state_length.

  • Avec la fonction shadow_mark, nous avons empêché que les points d’une seule région apparaissent et disparaissent en mettant les deux arguments past et future à FALSE. Finalement, avec la fonction animate, nous avons retiré la pause (argument end_pause) à la fin de l’animation pour que le tout soit fluide.

3.3.2 Pour s’amuser encore un peu avec quakes

Une autre fonction intéressante du package gganimate est transition_layers. Cette dernière permet d’ajouter graduellement chaque couche du graphique. Par exemple, il est possible de faire apparaître des diagrammes en boîte, puis d’ajouter les points qui les constituent par la suite.

bp <- ggplot(data = quakes, 
             mapping = aes(x = mag_catego, y = stations)) +
  geom_boxplot() +
  geom_jitter(mapping = aes(colour = region),
              alpha = 0.4) +
  ggtitle("Nombre de stations ayant rapporté des séismes\nd'une certaine classe de magnitudes") +
  theme(plot.title = element_text(hjust = 0.5)) +
  xlab("Classe de magnitude") +
  ylab("Nombre de stations") +
  labs(colour = "Région")+
  theme_classic()+
  scale_color_manual(values = c("darkcyan", "#FF69B4"))
  

bpanim <- bp + 
  transition_layers(layer_length = 0.5, 
                    transition_length = 1) +
  enter_grow() + 
  enter_fade()

animate(bpanim, rewind = TRUE)

  • transition_layers permet de faire apparaître les diagrammes en boîte, puis les points qui les constituent. Dans cet exemple, geom_boxplot est une couche et geom_jitter est une autre couche. L’argument layer_length permet de déterminer le temps de pause avant l’ajout de la prochaine couche et l’argument transition_length permet de contrôler la durée de l’entrée d’une nouvelle couche.

  • enter_grow et enter_fade permettent de contrôler l’apparition des différentes couches. Comme dans cet exemple, il est possible de combiner plusieurs fonctions de type enter_* afin d’obtenir l’animation souhaitée.

  • animate permet ici d’utiliser l’argument rewind afin de rendre l’animation plus intéressante visuellement.

3.4 Deuxième exemple avec WorldPhones

Nous voulons ici que le nombre de lignes téléphoniques change en fonction des années et des régions. Nous utiliserons alors un diagramme à barres.

WP <- ggplot(data = WorldPhones_long) +
  geom_col(mapping = aes(x = Region, y = Nbr.Phones), 
           fill = "darkcyan") +
  theme_classic() +
  xlab("Région") +
  ylab("Nombre de téléphones (en milliers)") +
  transition_states(Year,
                    transition_length = 2,
                    state_length = 1, 
                    wrap = TRUE) +
  ggtitle("Année : {closest_state}")

WP

  • Cet exemple fait appel à la fonction transition_states pour animer le graphique. Chaque état représente une année différente. Encore une fois, nous avons pu définir le temps entre les transitions, donc la vitesse à laquelle on change d’année avec l’argument transition_length. Aussi, nous avons laissé la valeur par défaut pour l’argument state_length, qui permet de contrôler le temps que les données de chaque année restent visibles. En donnant la valeur TRUE à l’argument logique wrap, on ajoute une transition entre le dernier état et le premier état, ce qui permet d’avoir une animation plus fluide lorsqu’elle se termine.

  • Il est possible d’afficher de façon interactive l’année représentée à chaque état de l’animation grâce à la variable closest_state. Cette variable permet de définir le nom de l’état le plus près du tableau représenté (frame).

3.4.1 Sous-exemple

En reprenant l’exemple précédent, nous allons mettre en couleur les nouvelles données qui apparaissent passé 1951. Cela nécessitera la superposition d’un graphique de l’état initial, soit l’année 1951, sur les données des années qui suivent, celles-ci affichées d’une différente couleur.

WP2 <- ggplot(data = WorldPhones_long) +
  geom_col(mapping = aes(x = Region, y = Nbr.Phones), 
           fill = "cyan3") +
  geom_col(data = WorldPhones_long[WorldPhones_long$Year == 1951,], 
           mapping = aes(x = Region, y = Nbr.Phones), 
           fill = "darkcyan", 
           show.legend = TRUE) +
  theme_classic() +
  xlab("Région") +
  ylab("Nombre de téléphones (en milliers)") +
  transition_states(Year,
                    transition_length = 2,
                    state_length = 1, 
                    wrap = TRUE) +
  ggtitle("Année : {closest_state}") +
  shadow_mark()

WP2

  • Nous avons d’abord eu à définir dans ggplot l’argument data comme étant WorldPhones_long.
  • Nous avons ensuite généré le graphique à animer en premier. gganimate le reconnaitra, car il reprend l’argument data de la fonction ggplot.
  • Nous superposons ensuite le deuxième graphique, qui utilise seulement les données de 1951 dans WorldPhones_long, soit des données différentes que ce qui a été entré dans ggplot. Ce graphique demeurera alors statique, gganimate ne le reconnaissant pas.
  • Les arguments fill dans geom_col pour les deux graphiques ne retournent pas les mêmes teintes.
  • Pour l’animation, nous avons simplement repris notre usage précédent de transition_states et closest_states, mais avons ajouté la fonction shadow_mark pour que le graphique statique apparaisse par-dessus le graphique animé durant toute la durée de l’animation.

3.4.2 Pour s’amuser encore un peu avec WorldPhones

Les diagrammes à barres peuvent être intéressants pour comparer les données d’une seule année à la fois entre elles, mais ne permettent pas de comparer la progression du nombre de lignes téléphoniques par année sur un seul plan de vue. Nous pourrions alors créer un graphique à lignes avec geom_line. Pour l’animation, nous utiliserons pour la première fois une fonction de type view_* ainsi que la fonction transition_reveal.

WP3 <- ggplot(data = WorldPhones_long, aes(x = Year, y = Nbr.Phones, group=Region, color=Region)) +
    geom_line() +
    geom_point() +
    ggtitle("Nombre de téléphones (en milliers) entre 1951 et 1961") +
    ylab("Nombre de téléphones") +
    xlab("Année")+
    theme_classic()+
    view_follow(fixed_x = TRUE, 
                fixed_y = FALSE) +
    transition_reveal(Year)
    

WP3 <- animate(WP3, end_pause = 15)

WP3

  • transition_reveal calcule des états intermédiaires entre les années (Year) pour tracer une ligne en continu.
  • view_follow initie un mouvement du plan de vue selon la progression des axes. Ici, nous ne voulions pas que le plan de vue suive l’axe des abscisses. Nous avons donc donné la valeur FALSE à l’argument fixed_x.

3.5 Sauvegarder une animation (anim_save)

Les animations produites par gganimate peuvent soit être visualisées en RStudio (fenêtre Viewer), intégrées directement dans des documents HTML avec la syntaxe R Markdown ou sauvegardées à l’aide de la fonction anim_save en fournissant une chaîne de caractères pour le nom de fichier (argument filename) et en désignation quelle animation enregistrer (argument animation). Sauvegardées ainsi, ces animations peuvent ensuite être implémentées dans d’autres documents.

anim_save(filename = "WP3.gif", animation = WP3)

4 Appréciation du package

Le package gganimate permet d’ajouter un peu de vie aux figures graphiques classiques. Ainsi, quelqu’un qui adore la visualisation de données graphiques pourra facilement y trouver son compte pour rendre ses graphiques plus interactifs et attirer l’œil de son audience.

4.1 Points forts

  • La syntaxe est compatible avec ggplot2, donc facile à intégrer rapidement dans une série de commandes.
  • Animer un graphique ggplot2 est très simple, et nécessite dans bien des cas seulement l’ajout d’une fonction ou deux.
  • Un nombre limité de fonctions permet d’arriver à une panoplie de résultats.
  • Qui n’aime pas les animations, sincèrement?

4.2 Limitations

  • On ne peut pas superposer deux types de transition, ce qui peut limiter les possibilités de création ou demander de passer par des chemins plus complexes pour arriver à un résultat donné.
  • Les graphiques sont lents à produire selon la puissance de l’ordinateur. Cela rend l’exploration du package plus difficile.
  • La combinaison de certaines fonctions peut produire des résultats inattendus. Animer un graphique exactement comme on l’imagine peut alors devenir complexe.
  • Les tailles des graphiques sont difficiles à contrôler, surtout lorsqu’on les intègre à des documents en utilisant R Markdown.
  • La documentation R des différentes fonctions manque de clarté, car elle ne donne pas d’exemples visuels de ses commandes. Il faut souvent faire rouler le code qui est fourni, et celui-ci ne fonctionne pas toujours. Il faut alors avoir recours à des forums ou à des pages webs afin de mieux comprendre l’intégration des différentes fonctions.