Nous allons ici nous pencher sur un type graphe unique (ou du moins présentant moins de différentes formes que ceux présentés jusqu’ici). Il permet de montrer à la fois comment les effectifs de différentes catégories se comparent entre eux dans un état et comment elles se transforment, se recombinent, pour former d’autres catégories dans un autre état voir plusieurs autres états. Il s’agit du diagramme de Sankey inventé en 1898 par un ingénieur irlandais du même nom. Les transitions sont marquées par des flèches ou des bandes dont la largeurs reflètent l’importance des effectifs déplacés d’un état à l’autre. Le résultat illustre bien les relations en cascade entre variables qualitatives. La logique qui préside à l’ordre de présentation des états peut être soit chronologique, soit guider par les comparaisons que l’on veut réaliser. Il y a dans ce dernier cas une réflexion à mener afin que le message porté par le graphe corresponde en toute rigueur à celui que l’on désir transmettre. Il sera, par ailleurs, pour que l’ensemble reste lisible, important de veiller à ce que le nombre de catégories par état ainsi que le nombre de croisements de lignes lors des transferts ne soient pas trop nombreux.

Illustrons cela à partir de quelques exemples composés à partir de ggplot et de ses dérivées. Nous utiliserons ici un package particulier ggsankey. Attention, celui-ci (au moment où j’écris ce poste) n’est pas disponible sur le CRAN. Il faudra le charger à partir de github via devtools.

devtools::install_github(“davidsjoberg/ggsankey”)

Une fois celui-ci installé, on peut appeler les packages dans notre environnement de travail.

library(tidyverse)
library(ggsankey)

Avant d’utiliser un jeu de données réelles pour illustrer la construction d’un diagramme de Sankey, travaillons à partir d’éléments définis nous-même. Commençons donc par les établir. Considérons un système avec deux états nommés A et B. Au sein de l’état A, il y a trois catégories dont les effectifs se répartissent de la manière suivante: 20% de “chi”, 50% de “fou” et 30% de “mi”. Au sein de l’état B, il y a quatre catégories qui se répartissent comme suit: 10% de “tra”, 20% de “lala”, 40% de “la” et 30% de “lère”. On a donc:

dat_sim<-data.frame(Etat=c(rep('A',3),rep('B',4)),
                    cat=c('chi','fou','mi','tra','lala','la','lère'),
                    eff=c(0.2,0.5,0.3,0.3,0.4,0.1,0.2))
dat_sim
##   Etat  cat eff
## 1    A  chi 0.2
## 2    A  fou 0.5
## 3    A   mi 0.3
## 4    B  tra 0.3
## 5    B lala 0.4
## 6    B   la 0.1
## 7    B lère 0.2

L’ensemble peut être représenté par deux diagrammes à bâtons empilés mis en parallèle.

ggplot(data=dat_sim,
       aes(x=Etat,y=eff,fill=cat))+
  geom_bar(stat='identity',position = 'fill',width=0.5,colour='black')+
  geom_text(aes(label=cat,y=c(0.9,0.6,0.15,0.15,0.70,0.95,0.40)))+
  theme_minimal()+
  theme(axis.title= element_blank(),
        legend.position = 'none')

La représentation est intéressante, mais elle ne dit rien sur le passage des individus de l’état A à l’état B. Le diagramme de Sankey nous permet d’intégrer cette information. Ajoutons les éléments suivants. Sur une centaine d’individu au totale. Parmi ceux appartement à la catégorie “chi” dans l’état A, 10 passent à la catégorie “tra” dans l’état B, 5 à la catégorie “lala”, 5 à la catégorie “la”. Pour l’instant, considérons que les autres ne bougent pas.

dat_sank1<-data.frame(x=factor(c(rep('A',100),rep('B',100))),
                     node=c(rep('chi',20),rep('fou',50),rep('mi',30),
                            rep('tra',30),rep('lala',40),rep('la',10),
                            rep('lère',20)),
                     next_x=c(rep(2,20),rep(NA,180)),
                     next_node=c(rep('tra',10),rep('lala',5),rep('la',5),
                                 rep(NA,180)))

Établissons un graphe de base à partir de là, en utilisant uniquement le geom_sankey() sans mise en forme.

ggplot(dat_sank1, 
       aes(x = x, next_x = next_x, node = node, next_node = next_node,
           fill = factor(node))) +
  geom_sankey()

Le résultat est peu convainquant. Mais, il peut facilement être amélioré. Ajoutons de la transparence sur les flux et entourons les nœuds d’un liseré noir.

ggplot(dat_sank1, 
       aes(x = x, next_x = next_x, node = node, next_node = next_node,
           fill = factor(node))) +
  geom_sankey(flow.alpha = 0.6,node.color = "black")

Ajoutons des étiquettes pour le nom des nœuds en utilisant le geom_sankey_label() et un titre à l’ensemble. Passons aussi par un thème dédié (le theme_sankey()).

ggplot(dat_sank1, 
       aes(x = x, next_x = next_x, node = node, next_node = next_node,
           fill = factor(node),label = node)) +
  geom_sankey(flow.alpha = 0.6,node.color = "black")+
  geom_sankey_label(size = 3, color = "white", fill = "gray40") +
  labs(title='Transferts de chi')+
  theme_sankey(base_size = 18) +
  theme(plot.title = element_text(hjust=0.5),
        axis.title = element_blank(),
        legend.position = 'none')

Le résultat est bien plus convaincant. Voyons ce qu’il en est si on intègre le devenir des catégories ‘fou’ et ‘mi’. Ceux-ci se composent comme suit: sur les 50 ‘fou’, 20 deviennent ‘tra’, 10 ‘lala’, 5 ‘la’ et 15 ‘lère’; sur les 30 ‘mi’, 25 deviennent ‘lala’ et 5 ‘lère’.

dat_sank<-data.frame(x=factor(c(rep('A',100),rep('B',100))),
                     node=c(rep('chi',20),rep('fou',50),rep('mi',30),
                            rep('tra',30),rep('lala',40),rep('la',10),
                            rep('lère',20)),
                     next_x=c(rep(2,100),rep(NA,100)),
                     next_node=c(rep('tra',10),rep('lala',5),rep('la',5),
                                 rep('tra',20),rep('lala',10),rep('la',5),
                                 rep('lère',15),
                                 rep('lala',25),rep('lère',5),
                                 rep(NA,100)))
ggplot(dat_sank, 
       aes(x = x, next_x = next_x, node = node, next_node = next_node,
           fill = factor(node),label = node)) +
  geom_sankey(flow.alpha = 0.6,
              node.color = "black")+
  geom_sankey_label(size = 3, color = "white", fill = "gray40") +
  labs(title='Devenir de chi, fou, mi')+
  theme_sankey(base_size = 18) +
  theme(plot.title = element_text(hjust=0.5),
        axis.title = element_blank(),
        legend.position = 'none')

Voyons maintenant comment faire de même à partir de données réelles. Reprenons les données d’admissions à Berkley pour l’automne 1973 que nous avions utilisées pour illustrer le diagramme en mosaïque (GT 6) et le paradoxe de Simpson. Celles-ci sont issues d’un tableau sur la page wikipédia (US) sur ce dernier. Nous les avons scraper à l’aide du code suivant:

library(rvest,quietly=TRUE)
url<-"https://en.wikipedia.org/wiki/Simpson%27s_paradox"
dat<-url %>% 
    read_html() %>%
    html_nodes(xpath='//*[@id="mw-content-text"]/div[1]/table[2]') %>%
    html_table() %>% 
    as.data.frame()
dat[1:8,]
##   Department        All    All.1        Men    Men.1      Women  Women.1
## 1 Department Applicants Admitted Applicants Admitted Applicants Admitted
## 2          A        933      64%        825      62%        108      82%
## 3          B        585      63%        560      63%         25      68%
## 4          C        918      35%        325      37%        593      34%
## 5          D        792      34%        417      33%        375      35%
## 6          E        584      25%        191      28%        393      24%
## 7          F        714       6%        373       6%        341       7%
## 8      Total       4526      39%       2691      45%       1835      30%

Puis, nous avons créé une data frame correspondant à ces informations en retenu une répartition individuelle.

dat2<-data.frame(sexe=c(rep('homme',2691),rep('femme',1835)),
                 dep=c(rep('A',825),rep('B',560),rep('C',325),
                       rep('D',417),rep('E',191),rep('F',373),
                       rep('A',108),rep('B',25),rep('C',593),
                       rep('D',375),rep('E',393),rep('F',341)),
                 statut=c(rep('admis',round(825*0.62)-1),rep('rejeté',round(825*0.38)),
                       rep('admis',round(560*0.63)),rep('rejeté',round(560*0.37)),
                       rep('admis',round(325*0.37)),rep('rejeté',round(325*0.63)),
                       rep('admis',round(417*0.33)),rep('rejeté',round(417*0.67)),
                       rep('admis',round(191*0.28)),rep('rejeté',round(191*0.72)),
                       rep('admis',round(373*0.06)),rep('rejeté',round(373*0.94)),
                       rep('admis',round(108*0.82)),rep('rejeté',round(108*0.18)),
                       rep('admis',round(25*0.68)),rep('rejeté',round(25*0.32)),
                       rep('admis',round(593*0.34)),rep('rejeté',round(593*0.66)),
                       rep('admis',round(375*0.35)),rep('rejeté',round(375*0.65)),
                       rep('admis',round(393*0.24)),rep('rejeté',round(393*0.76)),
                       rep('admis',round(341*0.07)),rep('rejeté',round(341*0.93))))
glimpse(dat2)
## Rows: 4,526
## Columns: 3
## $ sexe   <chr> "homme", "homme", "homme", "homme", "homme", "homme", "homme", …
## $ dep    <chr> "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A"…
## $ statut <chr> "admis", "admis", "admis", "admis", "admis", "admis", "admis", …

Les données articulées de la sorte ne permettent pas de réaliser directement un diagramme de Sankey. Le package prévoit une fonction pour réaliser les transformations nécessaires sans efforts. Il s’agit de make_long() dans la laquelle on indique le nom de la data frame et celui des variables à re-coder en suivant l’ordre des états qu’elles marquent. Choisissons ici pour commencer de mettre la variable sexe au milieu.

dat_l<-make_long(dat2,dep,sexe,statut)

Cela permet de bien voir comment cette éléments impactes les deux autres, autrement-dit comment les candidatures dans les différents départements se répartissent en fonction du sexe des individus et comment la réussite ou l’échec des candidatures se répartissent en fonction du sexe.

ggplot(dat_l, 
       aes(x = x, next_x = next_x, node = node, next_node = next_node,
           fill = factor(node),label = node)) +
  geom_sankey(flow.alpha = 0.6,
              node.color = "black")+
  geom_sankey_label(size = 3, color = "white", fill = "gray40") +
  labs(title='Admissions à Berkley 1973',
       caption="Source: wikipedia US - Simpson's paradox")+
  theme_sankey(base_size = 18) +
  theme(plot.title = element_text(hjust=0.5),
        plot.caption = element_text(hjust=1,size=6,face='italic'),
        axis.title = element_blank(),
        legend.position = 'none')

On voit que certains département attirent plus de femmes que d’homme et que globalement elles sont en proportion plus souvent rejetées. Cela pourrait être le fruit d’un traitement inégalitaire. Néanmoins, les choses sont plus compliquées. Les femmes sont plus nombreuses à postuler dans les départements les plus sélectifs. Le problème est donc plus structurelle. Les choses sont plus claires si on place la variable sexe en premier.

dat_l2<-make_long(dat2,sexe,dep,statut)

Le diagramme est alors plus parlant.

ggplot(dat_l2, 
       aes(x = x, next_x = next_x, node = node, next_node = next_node,
           fill = factor(node),label = node)) +
  geom_sankey(flow.alpha = 0.6,
              node.color = "black")+
  geom_sankey_label(size = 3, color = "white", fill = "gray40") +
  labs(title='Admissions à Berkley 1973',
       caption="Source: wikipedia US - Simpson's paradox")+
  theme_sankey(base_size = 18) +
  theme(plot.title = element_text(hjust=0.5),
        plot.caption = element_text(hjust=1,size=6,face='italic'),
        axis.title = element_blank(),
        legend.position = 'none')

On voit clairement que les femmes s’orientent principalement sur les départements les plus sélectifs et donc sont plus fréquemment rejetées que les hommes.

Il y a bien d’autres manières de réaliser un diagramme de Sankey en mobilisant d’autres packages (comme plotly, networkd3,PantaRhei) ou d’autres logiciels (Tableau, Python, Java, même Excel…), certains sites permettent de le faire en ligne (comme Sankeymatic). Nous reviendrons dessus avec des applications plus variées. Ce type de diagramme marquant les liens entre variables qualitatives me donne pas mal d’idées.