Gemius Ranking + gganimate()

Le CIM et GEMIUS mettent à disposition un ranking des vues par devices, os, browsers et résolution. Nous nous attarderons sur ces données et laisserons de côté le browserID. Bien que ces données soient disponibles sous forme de graphiques dynamiques (je vous invite d’ailleurs à consulter la page ici), l’objectif est plutôt de transformer ceux-ci en animation sympa à partager.

Si vous souhaitez avoir le détail de la méthode de mesure, c’est ici.

Extract des données.

ici pas de tour de magie, le site permets de sortir l’ensemble des données en .xlsx ou en .csv. A vous de choisir. La bonne pratique consiste à créer 2 répertoires dès le départ dans votre projet Rstudio : data et script. J’y ai sauvegardé les fichiers extraits. Ha oui j’oubliais, charger les librairies nécessaires. Bon, commençons par ça.

library(readr)
library(tidyverse)
## ── Attaching packages ───────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──
## ✔ ggplot2 3.1.0     ✔ purrr   0.3.0
## ✔ tibble  2.0.1     ✔ dplyr   0.7.8
## ✔ tidyr   0.8.2     ✔ stringr 1.3.1
## ✔ ggplot2 3.1.0     ✔ forcats 0.3.0
## Warning: package 'tibble' was built under R version 3.5.2
## Warning: package 'purrr' was built under R version 3.5.2
## ── Conflicts ──────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
library(janitor)
library(bbplot)
library(gganimate)
## Warning: package 'gganimate' was built under R version 3.5.2

Approche.

Quelle approche pour cet article? Et bien simplement :

  • Sauvegarder les df dans une liste
  • créer une fonction ggplot avec un thème spécifique
  • Ajouter la couche gganimate
  • Sauver les animations sur le disque

Liste des .csv.

Commençons par sauvegarder dans une liste le chemin de chaque fichier. Nous utilisons la fonction list.files() qui produit un vecteur avec le chemin de chaque fichier. La fonction as.list produit une liste. L’argument full.names remonte le chemin complet et pas uniquement le nom du fichier.

gemius_files <- as.list(list.files(path = "../post/data/gemius", full.names = TRUE))

gemius_files
## [[1]]
## [1] "../post/data/gemius/browsers_groups_luxembourg_pc.csv"
## 
## [[2]]
## [1] "../post/data/gemius/browsers_groups_luxembourg_phone.csv"
## 
## [[3]]
## [1] "../post/data/gemius/browsers_groups_luxembourg.csv"
## 
## [[4]]
## [1] "../post/data/gemius/browsers_types_luxembourg.csv"
## 
## [[5]]
## [1] "../post/data/gemius/devices_luxembourg.csv"
## 
## [[6]]
## [1] "../post/data/gemius/devices_telephone.csv"
## 
## [[7]]
## [1] "../post/data/gemius/operating_systems_families_apple_os.csv"
## 
## [[8]]
## [1] "../post/data/gemius/operating_systems_families_luxembourg_pc.csv"
## 
## [[9]]
## [1] "../post/data/gemius/operating_systems_families_luxembourg_phone.csv"
## 
## [[10]]
## [1] "../post/data/gemius/operating_systems_families_luxembourg.csv"
## 
## [[11]]
## [1] "../post/data/gemius/operating_systems_types_luxembourg_pc.csv"
## 
## [[12]]
## [1] "../post/data/gemius/operating_systems_types_luxembourg_phone_android.csv"
## 
## [[13]]
## [1] "../post/data/gemius/operating_systems_types_luxembourg.csv"
## 
## [[14]]
## [1] "../post/data/gemius/resolution_luxembourg.csv"

Attribuer un nom à la liste.

Nous allons attribuer un nom à chaque membre de la liste. Nous nous en servirons pour l’enregistrement des data.frame dans l’environnement global.

gemius_files <-
gemius_files %>% 
set_names(c("browser_group_lux_pc",
            "browser_group_lux_phone",
            "browser_group_lux",
            "browser_types_lux",
            "devices_lux",
            "mobile_by_country",
            "apple_os_by_country",
            "os_family_lux_pc",
            "os_family_lux_phone",
            "os_family_lux",
            "os_type_lux_pc",
            "os_type_lux_phone_android",
            "os_type_lux",
            "res_lux"))

Créons aussi une liste pour les noms des graphiques.

graph_names <- as.list(c("Views by Browser Group (PC) - Luxembourg",
                         "Views by Browser Group (Phone) - Luxembourg",
                         "Views by Browser Group (PC, Tablet, Mobile) - Luxembourg",
                         "Views by Browser Type (PC, Tablet, Mobile) - Luxembourg",
                         "Views by Device Type (PC, Tablet, Mobile) - Luxembourg",
                         "Views by Mobile - Luxembourg/Belgium/Portugual",
                         "Views by Apple OS (PC, Tablet, Mobile) - Luxembourg/Belgium/Portugual",
                         "Views by OS Family (PC) - Luxembourg",
                         "Views by OS Family (Phone) - Luxembourg",
                         "Views by OS Family (PC, Tablet, Mobile) - Luxembourg",
                         "Views by OS Type (PC) - Luxembourg",
                         "Views by OS Type (Android Phone)",
                         "Views by OS Type (PC, Tablet, Mobile) - Luxembourg",
                         "Views by Resolutions (PC, Tablet, Mobile) - Luxembourg"))

Chargement des fichiers csv.

Certains arguments sont nécessaires à la fonction read_csv pour correctement lire les fichiers. Nous devons skipper la première ligne et transformer la colonne des dates en classe date. Nous allons créer une fonction avec ces arguments avec la fonction partial du package purrr.

read_gemius <- partial(read_csv, col_types = cols(`Time period` = col_date(format = "%m.%Y")), 
    skip = 1)

Nous pouvons maintenant mapper la fonction pour lire l’ensemble du dossier. Nous en profitons pour “nettoyer” les noms des colonnes avec clean_names du package janitor et de transformer l’ensemble en tidy data. Nous transformons aussi les valeurs de la colonne “value” en type numérique.

map(gemius_files, read_gemius) %>% 
  map(clean_names) %>% #cleaning names
  map(gather, "type", "value",  -c("time_period")) %>% #tidy transformation
  map(mutate, value = as.numeric(value)) %>%  #value as numeric value
  list2env(envir = .GlobalEnv)
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## <environment: R_GlobalEnv>

Nous allons aussi enregistrer l’ensemble des données dans une seule table.

gemius_data <- 
map(gemius_files, read_gemius) %>% 
  map(clean_names) %>% #cleaning names
  map(gather, "type", "value",  -c("time_period")) %>% #tidy transformation
  map(mutate, value = as.numeric(value)) %>% #value as numeric value
  bind_rows(.id = "df") 
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion
## Warning in evalq(as.numeric(value), <environment>): NAs introduced by
## coercion

Vérifions que tout est bien enregistré dans l’Environnement Global.

ls()
##  [1] "apple_os_by_country"       "browser_group_lux"        
##  [3] "browser_group_lux_pc"      "browser_group_lux_phone"  
##  [5] "browser_types_lux"         "devices_lux"              
##  [7] "gemius_data"               "gemius_files"             
##  [9] "graph_names"               "mobile_by_country"        
## [11] "os_family_lux"             "os_family_lux_pc"         
## [13] "os_family_lux_phone"       "os_type_lux"              
## [15] "os_type_lux_pc"            "os_type_lux_phone_android"
## [17] "read_gemius"               "res_lux"

Vérifions le contenu de devices_lux et de gemius_data

glimpse(devices_lux)
## Observations: 141
## Variables: 3
## $ time_period <date> 2019-01-01, 2018-12-01, 2018-11-01, 2018-10-01, 201…
## $ type        <chr> "telephone", "telephone", "telephone", "telephone", …
## $ value       <dbl> 62.79, 66.02, 63.84, 64.80, 62.06, 64.06, 64.26, 61.…
glimpse(gemius_data)
## Observations: 2,679
## Variables: 4
## $ df          <chr> "browser_group_lux_pc", "browser_group_lux_pc", "bro…
## $ time_period <date> 2019-01-01, 2018-12-01, 2018-11-01, 2018-10-01, 201…
## $ type        <chr> "microsoft_edge", "microsoft_edge", "microsoft_edge"…
## $ value       <dbl> 8.50, 8.40, 8.00, 8.00, 7.65, 7.62, 7.32, 7.45, 7.57…

Parfait.

ggplot template.

Le but ici est de paramétrer le style du graphique pour les animations. Avec theme() nous pouvons modifier l’aspect du graphique à notre guise. Observer la fonction labs(), nous nommons le graphique grâce au vecteur graph_names créé plus tôt.

devices_lux %>%
  ggplot() +
  aes(time_period, value, col = type) +
  geom_vline(xintercept = as.Date("2019-01-01"), col = "white", linetype = 3) +
  geom_line(size = 1.5) +
  geom_point(size = 3) +
  scale_y_continuous(labels = scales::percent_format(scale = 1)) +
  scale_x_date(date_breaks = "year", date_labels = "%Y", limits = c(as.Date("2015-01-01"), as.Date("2019-01-01"))) +
  labs(title = graph_names[5],
       subtitle = "rankingGemius | 03.2015 - 01.2019",
       caption = "Data source : Gemius - © DARP - www.davidsolito.com - 2019") +
  theme(legend.position = "top",
            rect = element_blank(),
            plot.margin = unit(c(1.5, 1.5, 1.5, 1.5), "cm"), 
            plot.background = element_rect(fill = "#292a2d"),
            legend.title = element_blank(),
            legend.text = element_text(colour = "white"),
            axis.line = element_blank(),
            panel.grid.minor = element_blank(),
            panel.grid.major = element_line(linetype = 3),
            title = element_text(colour = "#ed4d83", size = 14),
            axis.text = element_text(colour = "white"),
            axis.title = element_blank()) 

Dès que l’aspect convient, nous pouvons enregistrer le theme dans une fonction avec as_mapper.

draw <- as_mapper(~ ggplot(.x) +
                    aes(time_period, value, col = type) +
                    geom_line(size = .5) +
                    geom_point(size = 1) +
                    scale_y_continuous(labels = scales::number_format(suffix = "%")) +
                    scale_x_date(date_breaks = "year", date_labels = "%Y", limits = c(as.Date("2015-01-01"), as.Date("2019-01-01"))) +
                    labs(title = .y,
                         subtitle = "rankingGemius | 03.2015 - 01.2019",
                         caption = "Data source : Gemius - © DARP - www.davidsolito.com - 2019") +
                    theme(legend.position = "top",
                          plot.margin = unit(c(1.5, 1.5, 1.5, 1.5), "cm"),
                          rect = element_blank(),
                          legend.title = element_blank(),
                          legend.text = element_text(color = "white"),
                          plot.background = element_rect(fill = "#292a2d"),
                          axis.line = element_blank(),
                          panel.grid.minor = element_blank(),
                          panel.grid.major = element_line(linetype = 3),
                          title = element_text(colour = "#ed4d83", size = 14),
                          axis.text = element_text(colour = "white"),
                          axis.title = element_blank())) 

Dessiner les graphiques.

Dessinons maintenant chaque graphique avec notre fonction draw et map2. Pourquoi map2? Parce que nous avons dans notre fonction draw attribuée .y pour le titre des graphiques.

gemius_data %>% 
  group_by(df) %>% 
  nest() %>% 
  mutate(plot = map2(data, graph_names, draw)) %>% 
  pluck(3)
## [[1]]

## 
## [[2]]

## 
## [[3]]

## 
## [[4]]

## 
## [[5]]

## 
## [[6]]

## 
## [[7]]

## 
## [[8]]

## 
## [[9]]

## 
## [[10]]

## 
## [[11]]

## 
## [[12]]

## 
## [[13]]

## 
## [[14]]

Adapter le template pour les animations

Pour les animations, nous souhaitons ajouter un label qui suivra la progression du graphique. Enregistrons cette extension dans un vecteur my_label. Pour la position (x, y), nous utilisons chaque enregistrement dans la table, la date pour les x et la valeur pour y.

my_label <- geom_label(aes(col = type, x = time_period, y = value, label = paste(type,": ", "\n", round(value, 1), "%")),
             label.size = NA,
             fill = NA,
             hjust = -0.05, 
             vjust = 0.5,
             size = 5)



draw2 <- as_mapper(~ ggplot(.x) +
                    aes(time_period, value, col = type) +
                    geom_line(size = .5) +
                    geom_point(size = 1) +
                    scale_y_continuous(labels = scales::number_format(suffix = "%")) +
                    scale_x_date(date_breaks = "year", date_labels = "%Y", limits = c(as.Date("2015-01-01"), as.Date("2020-01-01"))) +

                    labs(title = .y,
                         subtitle = "rankingGemius | 03.2015 - 01.2019",
                         caption = "Data source : Gemius - © DARP - www.davidsolito.com - 2019") +
                    my_label +
                    theme(legend.position = "none",
                          plot.margin = unit(c(1.5, 1.5, 1.5, 1.5), "cm"),
                          rect = element_blank(),
                          legend.title = element_blank(),
                          legend.text = element_text(color = "white"),
                          plot.background = element_rect(fill = "#292a2d"),
                          axis.line = element_blank(),
                          panel.grid.minor = element_blank(),
                          panel.grid.major = element_line(linetype = 3),
                          title = element_text(colour = "#ed4d83", size = 14),
                          axis.text = element_text(colour = "white"),
                          axis.title = element_blank())) 

Voyons ce que cela donne.

gemius_data %>% 
  group_by(df) %>% 
  nest() %>% 
  mutate(plot = map2(data, graph_names, draw2)) %>% 
  pluck(3, 2)

BAM!

Extension gganimate()

Nous allons ajouter la fonction transition_reveal() du package gganimate pour animer chaque ligne du graphique.

draw3 <- as_mapper(~ ggplot(.x) +
                    aes(time_period, value, col = type) +
                    geom_line(size = 1) +
                    geom_point(size = 2) +
                    scale_y_continuous(labels = scales::number_format(suffix = "%")) +
                    scale_x_date(date_breaks = "year", date_labels = "%Y", limits = c(as.Date("2015-01-01"), as.Date("2020-01-01"))) +
                    labs(title = .y,
                         subtitle = "rankingGemius | 03.2015 - 01.2019",
                         caption = "Data source : Gemius - © DARP - www.davidsolito.com - 2019") +
                    my_label +
                    theme(legend.position = "none",
                          plot.margin = unit(c(1.5, 1.5, 1.5, 1.5), "cm"),
                          rect = element_blank(),
                          legend.title = element_blank(),
                          legend.text = element_text(color = "white"),
                          plot.background = element_rect(fill = "#292a2d"),
                          axis.line = element_blank(),
                          panel.grid.minor = element_blank(),
                          panel.grid.major = element_line(linetype = 3),
                          title = element_text(colour = "#ed4d83", size = 14),
                          axis.text = element_text(colour = "white"),
                          axis.title = element_blank()) +
                     transition_reveal(time_period, keep_last = FALSE)) #Extension gganimate

Pour animer le graphique, nous devons utiliser la fonction animate. Nous allons l’enregistrer en incluant les arguments avec partial.

please_anim <- partial(animate, device = "png", nframes = 100, fps = 10, width = 800, height = 600)

Testons sur un graphique.

gemius_data %>% 
  group_by(df) %>% 
  nest() %>% 
  mutate(plot = map2(data, graph_names, draw3)) %>% 
  pluck(3,5) %>% 
  please_anim()

Maintenant que tout est en place, sauvons les animations dans une nouvelle table avant de sauver les fichiers sur le disque.

gemius_plot <-
gemius_data %>% 
  group_by(df) %>% 
  nest() %>% 
  mutate(plot = map2(data, graph_names, draw))


gemius_anim <-
gemius_data %>% 
  group_by(df) %>% 
  nest() %>% 
  mutate(plot = map2(data, graph_names, draw3)) %>% 
  mutate(anim = map(plot, please_anim))

Sauvons le tout sur le disque.

gemius_anim %>%
  pull(anim) %>%
  set_names(gemius_anim$df) %>% 
  iwalk(~ anim_save(.x, filename = paste0(.y, ".gif")))

gemius_plot %>%
  pull(plot) %>%
  set_names(gemius_plot$df) %>% 
  iwalk(~ ggsave(.x, filename = paste0(.y, ".jpg"), 
                device = "jpg", width = 30, height = 21, 
                units = c("cm"),
                dpi = 300))

Appendix