Visualiser une analyse géométrique des données avec ggplot2 (R/RStudio)

https://xkcd.com/388

Ce tutoriel a pour objectif de montrer comment réaliser, avec R et RStudio, des graphiques et des tableaux statistiques sur-mesure, permettant de visualiser et de présenter les résultats d’une analyse géométrique des données.

Il a aussi pour objectif – secondaire ici mais loin d’être trivial – de fournir un exemple de script de programmation statistique structuré, commenté et reproductible.

Depuis un tutoriel ancien toujours disponible sur ce site, des nouveaux packages et nouvelles fonctionnalités ont été développés, qui facilitent et améliorent la production de graphiques. Par exemple :

  • FactoMineR : ce package parmi les plus utilisés pour réaliser des analyses géométriques a très nettement amélioré ses fonctionnalités graphiques, qui permettent désormais (notamment) de sélectionner des modalités, et de limiter le chevauchement des labels (voir la vidéo réalisée par François Husson, qui explique comment paramétrer les graphiques avec les fonctions de FactoMineR) ;
  • factoextra : ce package permet de générer avec peu de code des graphiques élégants et bien structurés (de type ggplot) ;
  • GDAtools : le package de Nicolas Robette comporte aussi des fonctions permettant de réaliser et paramétrer finement des graphiques de type ggplot : ggcloud_variables, ggcloud_indiv, ggadd_ellipses, ggadd_interaction, ggadd_supvar.

Toutefois, ces packages ne permettent pas de tout paramétrer, et je me suis souvent heurté au problème de ne pas pouvoir sélectionner comme je l’aurais souhaité les modalités à représenter, les couleurs ou encore les symboles de mes graphiques.

Par ailleurs, le package ggplot2 permet, à moindre coût en termes de code, de réaliser des graphiques non seulement élégants mais infiniment paramétrables.

J’ai donc peu à peu mis en place les routines que je vous présente ici, afin d’automatiser autant que faire se peut le code et minimiser le travail d’écriture, tout en permettant d’adapter le code à une diversité de données d’enquête, et aux objectifs d’administration de la preuve.

Sommaire

Une famille de méthodes graphiques

On désigne par analyse géométrique des données la famille de méthodes incluant notamment l’analyse en composantes principales (ACP), l’analyse des correspondances multiples (ACM), les classifications (ascendantes hiérarchies notamment, CAH), ou encore l’analyse factorielle de tableaux multiples (AFM).

Ces méthodes ont en commun de fournir la meilleure description possible des corrélations dans un jeu de données.  De nombreux manuels présentent ces méthodes, avec un degré varié de technicité (notamment, Volle, 1997 ; Cibois, 2000 ; Le Roux et Rouanet, 2014).

Un parti-pris graphique

Cette vaste famille de méthodes est souvent présentée de façon graphique. Dans les enseignements en sciences sociales, qu’elles soient juste commentées ou que l’on enseigne leurs principes et la façon d’en réaliser à l’aide d’un logiciel, il est fréquent que seul le fameux graphique en croix soit présenté. Il en va de même dans la plupart des publications scientifiques : généralement on publie un ou plusieurs plans factoriels où sont projetées les modalités actives – et le cas échéant illustratives ou supplémentaires -, plus rarement on publie aussi ces mêmes plans où sont projetés les nuages des individus, et encore plus rarement les tableaux présentant les principaux résultats statistiques permettant d’interpréter les axes (fréquences, contributions, coordonnées, cosinus carrés).

Ce parti-pris graphique n’est pas un problème en soi : c’est même la force et l’intérêt des méthodes d’analyse géométrique que de permettre une visualisation synthétique des corrélations à l’intérieur d’un jeu de données. Mais à deux conditions : 1/ que le graphique soit démonstratif et 2/ que l’on oublie jamais qu’une lecture uniquement graphique des résultats peut conduire à de sérieuses erreurs d’interprétation (Cibois, 1997).

Quelques règles pratiques

Avant de montrer, à partir d’un exemple simple, comment mettre en place les routines qui vous permettront d’être efficaces dans la création de vos graphiques et tableaux, on énoncera quelques « règles pratiques ». Ces règles, relatives aux graphiques et aux tableaux, n’ont pas vocation à faire office de tables de la loi. Je les ai réfléchies, au fil du temps, à force de chercher à interpréter des résultats d’AGD publiés par d’autres, de regarder des graphiques plus ou moins réussis… et d’essayer d’en faire moi-même avec plus ou moins de bonheur…

Bref, ce sont des règles essentiellement pratiques, dont on verra ensuite la traduction technique avec R/RStudio.

Les règles d’un graphique démonstratif ?

On pourrait dire schématiquement que pour qu’un graphique soit démonstratif, i.e utile à l’administration de la preuve, il faut qu’il soit lisible, structuré et esthétique.

  1. On doit pouvoir sélectionner quelles modalités représenter. La question de savoir quelles modalités sont éligibles à l’interprétation est l’objet de débats : ce n’est pas ici notre problème. Mais quel que soit le seuil que vous choisissiez… tout n’est pas bon à représenter !
  2. Les noms des variables et les libellés des modalités doivent être présentés en langage naturel et non en code hiéroglyphique compréhensible uniquement (et encore…) par les concepteurs de l’enquête ou les chercheurs qui ont mené l’analyse. Pratiquement, cela nécessite de travailler le matériau avec lequel graphiques et tableaux sont réalisés afin de renommer – si besoin – variables et modalités.
  3. Les libellés des modalités ne doivent pas se chevaucher. Ici, une bonne nouvelle : un package « magique » dans R (ggrepel associé à ggplot2) s’en charge. On verra comment l’utiliser.
  4. Les labels des axes doivent être clairement présentés, et indiquer le pourcentage d’inertie conservé par chaque axe.
  5. Une légende permettant de déchiffrer les symboles et couleurs éventuels doit être positionnée de façon à ne pas empiéter sur le graphique.
  6. Les types de variables (actives ou illustratives) doivent être clairement distingués, par des symboles ou des couleurs distincts.
  7. Les variables aussi doivent être distinguées entre elles, par un jeu de symboles adéquat.
  8. Enfin, l’esthétisme doit être pris en considération (notamment en vue d’une publication) : les gammes de couleur utilisées doivent être harmonieuses, et permettre de distinguer ce qui doit l’être ; on doit pouvoir aisément passer de la couleur au noir et blanc (ou nuances de gris).

C’est tout ? Non… Des graphiques démonstratifs ne suffisent pas à asseoir statistiquement l’argumentation : il faut aussi présenter lisiblement les principaux résultats statistiques de l’analyse géométrique. Pour un exemple de publication présentant les graphiques et statistiques d’une ACM, voir Denord et al. (2011).

Quels résultats statistiques présenter, et comment ?

Les informations qu’il s’agit de présenter diffèrent selon le type de méthode mise en œuvre. Pour une ACP, point de contributions pour les modalités actives, mais plutôt un tableau présentant les corrélations des variables aux axes. Pour une AFM, point de contributions non plus, puisque l’analyse consiste en une ACP d’ACM partielles : on privilégiera donc les coordonnées, et les v.test, par exemple.

Cela étant dit, si l’on s’en tient à l’ACM, méthode de la famille des analyses géométriques la plus couramment utilisée en sciences sociales, il convient sans doute de présenter :

  1. pour chaque modalité active : effectif brut (information importante : des modalités à petits effectifs peuvent parfois peser artificiellement lourd sur les axes : c’est le fameux effet « distinction » de l’ACM…) et fréquence, puis les coordonnées, contributions et cosinus carrés sur chacun des axes interprétés ;
  2. on peut aussi éventuellement sommer les contributions par variable, ou par groupe de variables, afin de voir quelles sont les variables ou les grandes thématiques qui structurent chaque axe ;
  3. pour chaque modalité illustrative : effectif brut, fréquence, puis coordonnées et cosinus carrés sur chacun des axes interprétés.

Je montrerai dans ce tutoriel comment générer directement dans R, un tableau comportant toutes les informations nécessaires. Je ne montrerai en revanche pas comment achever sa mise en forme afin de le publier. Les utilisateurs de LibreOffice Calc (ou son avatar non libre nommé Excel) exporteront le tableau en format .odt ou .xlsx afin de le manipuler à leur guise avant publication. Les utilisateurs de LateX pourront utiliser la fonction xtable pour générer un code LateX, ou bien passer par la macro CalctoLatex (ou Excel2Latex). Les utilisateurs de RMarkdown pourront utiliser knitr::kable et ses multiples réglages (kableExtra::kable_styling).

Une bonne nouvelle, tout de même : structurer le tableau des résultats et rassembler les informations utiles à la réalisation des graphiques vont de pair. En faisant l’un, on se simplifie la vie pour l’autre…

Objet, données et script

L’objet sociologique que j’ai  choisi pour ce tutoriel est l’intérim. Je tenterai une très modeste description du monde du travail intérimaire, sans autre ambition sociologique que de mettre en œuvre une « vraie » analyse, à partir de « vraies » données.

Les données utilisées dans ce tutoriel sont celles du fichier détail de l’Enquête emploi en continu de 2018. Elles sont librement téléchargeable sur le site de l’Insee.

Ce fichier détail comporte un nombre restreint de variables (par rapport au fichier production-recherche accessible via Quetelet-Progedo). L’analyse se limitera donc à une série assez frustre d’indicateurs, qui ne permettent sans doute pas de dire quoi que soit de très élaboré sur l’intérim et les intérimaires. On ne s’attardera donc pas sur l’interprétation des axes et plans factoriels, pour se concentrer sur les problèmes concrets d’une recherche sur des données d’enquête, non simulées.

Le script intégral est téléchargeable ici. Il permet de générer les données utilisées pour le tutoriel, et les analyses, à partir du jeu de données « brut » disponible sur le site de l’Insee.

Afin de simplifier la suite du tutoriel, on ne présentera pas les recodages et sélections de variables. Celles et ceux qui veulent sauter cette étape, peuvent télécharger ici le jeu de données déjà recodé, et prêt-à-jouer (ce jeu de données comporte 4689 individus travaillant en tant qu’intérimaires et 13 variables).

Avant de commencer…

Une petite mise en garde : le script présenté ci-dessous n’est pas d’une grande complexité. J’ai essayé de l’écrire de la façon la plus simple et explicite possible. Ce n’est toutefois pas un script « niveau débutant »… et il faudra peut-être à celles et ceux qui débutent dans R un peu de patience pour pouvoir se l’approprier.

Le script est rédigé en mode Tidyverse. Pour celles et ceux qui ne sont pas familiers de cette façon de coder dans R, reportez-vous au manuel de Julien Barnier, en ligne ici : Introduction à R et au Tidyverse.

Il s’agit d’un script réplicable dans la mesure où si vous disposez des données (au bon endroit dans votre ordinateur), et si les packages convoqués en début de script sont bien installés, vous n’avez rien à modifier pour pouvoir l’exécuter de bout en bout.


Préambules

On part du principe que vous disposez d’une installation fonctionnelle de R et RStudio sur votre ordinateur. Si cela n’était pas le cas, rendez-vous ici.

Chargement des packages

Les packages utilisés dans ce tutoriel sont les suivants :

On exécute aussi en début de script l’option scipen = 999 afin de désactiver l’écriture scientifique, qui gène un peu la lisibilité des sorties statistiques.

Pour une présentation synthétique des packages les plus usuels en sciences sociales, voir ici.

# 1. PACKAGES ET OPTIONS ----

library(tidyverse) # pour la manipulation des données
library(questionr) # pour la description des variables
library(FactoMineR) # pour l'analyse géométrique des données
library(RColorBrewer) # pour les palettes de couleurs
library(ggrepel) # pour que les libellés des points ne se chevauchent pas (crucial !)
library(explor) # pour une visualisation exploratoire des résultats de l'analyse

options(scipen=999) # pour désactiver l'écriture scientifique des nombres

Chargement des données

Si vous suivez le tutoriel à partir du script intégral, les instructions qui suivent sont inutiles : exécutez juste l’ensemble des instructions numérotées de 1 à 3 dans le script.

Si vous utilisez uniquement les données recodées et prêtes-à-jouer, voici la marche à suivre :

  1. Téléchargez les ici (si ce n’est déjà fait) ;
  2. enregistrez-les dans un dossier « data », au même niveau dans votre ordinateur que le script sur lequel vous écrivez (ou du projet RStudio que vous avez créé) ;
  3. ne les ouvrez avec LibreOffice Calc (ou Excel) SOUS AUCUN PRETEXTE ! C’est une très mauvaise habitude, et vous risquez de les corrompre (et de devoir les télécharger à nouveau, etc.) ;
  4. suivez les instructions ci-dessous.
# chargement des données (à adapter en fonction de l'endroit où vous les avez stockées)

d <- read_csv2("ee18_extraits_recode.csv")

Réalisation de l’ACM, stockage et visualisation des premiers résultats

C’est la partie « facile » de notre petite affaire… même si le diable sociologique se cache presque tout entier dans cette première étape de sélection des variables à faire entrer dans l’analyse. Une phrase de Jean-Paul Benzécri, créateur de l’analyse factoriel, est à réciter comme un mantra :

All in all, doing a data analysis, in good mathematics,
is simply searching eigenvectors (valeurs propres) ; all the science (or the
art) of it is just to find the right matrix to diagonalize.

Autrement dit : diagonaliser des matrices et chercher des valeurs propres est chose aisée (d’autant plus que c’est R qui le fera pour vous…). Mais savoir quelle matrice diagonaliser – et donc quelles variables et quels individus sélectionner – c’est tout un art !

# 4. REALISATION DE L'ACM ----

# 4.1. Sélection des variables de l'analyse ----

d_acm <- d %>% 
  select("age", "genre", "diplome", "nationalite", "gsp", "sociopro", "menage", "position", "choix", horaires", "heures_plus", "public_prive", "secteur") %>% 
  mutate_all(factor) %>% # car la fonction MCA ne mange que du "factor"
  drop_na() # enlever toutes les NA (57 individus)

str(d_acm)

Nota bene concernant les NA : les cases vides sont un problème en analyse géométrique, surtout lorsqu’elles sont peu nombreuses, et potentiellement redondantes (ce sont parfois les mêmes individus qui ne répondent pas). En effet, l’analyse les considère comme des modalités comme les autres. C’est formidable si la non-réponse est un objet d’étude, mais potentiellement ennuyeux sinon. Une façon expéditive de régler le problème est d’enlever les individus qui ont des cases vides pour les variables actives. Attention à ne pas en abuser… au risque de déformer fortement votre échantillon. D’autres solutions sont possibles : ventiler les cases vides de façon aléatoire (argument level.ventil de la fonction MCA), les remplir en « imputant » les données manquantes à partir de ce que l’on sait par ailleurs des corrélations dans le jeu de données (package missMDA développé par François Husson et l’équipe de FactoMineR), ou encore faire de l’analyse spécifique. Ici comme toujours, il n’y a pas de solution technique miracle : la question essentielle est celle de la délimitation de votre objet et de votre population d’intérêt…

Réaliser l’ACM

# 4.2. Réalisation de l'analyse et première visualisation des résultats ----

res_acm <- MCA(d_acm, quali.sup = 5:7)

L’argument quali.sup = 5:7 permet de sélectionner les variables illustratives (ou supplémentaires). Ici, ce sont les variables de rang 5, 6 et 7 dans le jeu de données d_acm.  C’est à dire :

  • gsp et sociopro : deux codages différents, plus ou moins agrégés, de la profession ;
  • menage : le type de ménage dans lequel vit l’enquêté.e

On a stocké les résultats de l’analyse dans l’objet res_acm. Cet objet (une liste) se subdivise en plusieurs sous objets, dont certains vont constituer le matériau de base de notre travail de mise en forme et de visualisation. Par défaut, la fonction MCA stocke les résultats des cinq premiers axes. Si vous en voulez plus, il faut paramétrer l’argument npc.

  • res_acm$var : liste comportant les informations relatives aux variables actives
    • res_acm$var$coord : tableau comportant les coordonnées de chaque modalité active ;
    • res_acm$var$contrib : tableau comportant les contributions de chaque modalité active ;
    • res_acm$var$cos2 : tableau comportant les coordonnées de chaque modalité active ;
    • res_acm$var$v.test : tableau comportant les v-tests de chaque modalité active.
  • res_acm$ind : liste comportant les informations relatives aux variables actives
    • res_acm$ind$coord : les coordonnées des individus sur chaque axe ;
    • Les contributions et cosinus carrés des individus sont aussi disponibles, mais on les utilisera pas.
  • res_acm$quali.sup : liste comportant les informations relatives aux variables illustrative
    • res_acm$quali.sup$coord : tableau comportant les coordonnées de chaque modalité illustrative ;
    • res_acm$quali.sup$contrib : tableau comportant les contributions de chaque modalité illustrative ;
    • res_acm$quali.sup$cos2 : tableau comportant les coordonnées de chaque modalité illustrative ;
    • res_acm$quali.sup$v.test : tableau comportant les v-tests de chaque modalité illustrative.

Premières visualisations

Prenez le temps, toujours, de regarder les premières sorties graphiques (même « moches » et peu lisibles) : elles fournissent des indications précieuses sur le caractère plus ou moins pertinent de votre analyse (si votre nuage des individus se scinde en deux patates bien disjointes, par exemple, c’est que vous avez un problème…).

On peut à cette fin utiliser le package factoextra, afin de produire rapidement les premiers graphiques exploratoires, ou encore le package explor avec la simple commande suivante :

explor(res_acm)

Description statistique des axes

Avant d’aller plus loin dans l’interprétation de l’ACM, il faut examiner les axes, et déterminer quels sont ceux que l’on retiendra pour l’analyse. Là encore, plusieurs critères sont possibles, certains préconisant même d’interpréter tous axes… pourvu qu’ils soient interprétables.

L’enjeu ici est de montrer :

  • Comment structurer un tableau exportable comportant les informations relatives à l’inertie conservée dans les différents axes factoriels ;
  • Comment réaliser un diagramme des sauts de variance.
# 4.3. Description statistique des axes ----

variances <- as.data.frame(res_acm$eig) %>%
  rownames_to_column() %>% # récupérer les noms de lignes (dim 1, dim 2, etc) dans une colonne distincte
  slice(1:10) %>% # conserver que les infos des 10 premiers axes
  mutate(Axes = str_replace_all(rowname, "dim", "Axe")) %>% # créer une nouvelle variable à partir de rowname, qui prend les valeurs "Axe 1, Axe 2, etc" au lieu de "dim 1, dim 2, etc."
  select(-rowname) %>%# on enlève cette colonne dont on n'a plus besoin
  rename(`Valeurs propres` = eigenvalue) %>%
  rename(`% de variance` = `percentage of variance`) %>% # on renomme les autres colonnes
  rename(`% cumulé de variance` = `cumulative percentage of variance`) %>% 
  mutate(Axes = fct_relevel(Axes, paste("Axe", 1:10))) # pour que l'ordre de 1 à 10 soit bien respecté dans les graphiques

On obtient le tableau suivant :

Valeurs propres % de variance % cumulé de variance Axes
0,22 6,3 6,3 Axe 1
0,16 4,6 11,0 Axe 2
0,14 4,1 15,1 Axe 3
0,14 3,9 19,0 Axe 4
0,13 3,8 22,8 Axe 5
0,13 3,6 26,4 Axe 6
0,12 3,5 29,8 Axe 7
0,12 3,3 33,1 Axe 8
0,11 3,2 36,4 Axe 9
0,11 3,2 39,5 Axe 10

Pour générer les diagrammes des sauts d’inertie, il suffit de prendre l’information contenue dans la deuxième ou dans la troisième colonne de ce tableau.

Aparté concernant les taux de variance modifiés de Benzécri : la démarche graphique serait la même, il faudrait juste calculer les taux modifiés et utiliser cette information comme input de la fonction graphique ci-dessous. Le package GDAtools développé par Nicolas Robette propose une fonction modif.rate qui permet de calculer aisément les taux de variance modifiés.

# Diagrammes des sauts de variance ----

ggplot(variances, aes(x = Axes)) + # initialiser du graphique et de l'axe horizontal
  geom_bar(aes(y = `% de variance`),   # indiquer le type de graphique (barplot) et la variable à représenter sur l'axe vertical
               stat = "identity", 
               fill = "red") + # parce que j'aime bien le rouge
  xlab("") + # on enlève le label de l'axe des x, pas très utile
  ylab("% de variance") + # renommer proprement le label de l'axe des y
  theme_minimal() # un des thèmes possibles dans ggplot, que j'aime bien car il est... minimaliste !

ggplot(variances, aes(x = Axes)) +
  geom_bar(aes(y = `% cumulé de variance`), 
           stat = "identity", 
           fill = "blue") +
  xlab("") +
  ylab("% cumulé de variance") +
  theme_minimal()
Diagramme des sauts d’inertie
Diagramme des sauts d’inertie cumulée

Enfin, il faut définir un seuil de contribution, permettant de déterminer quelles sont les modalités actives éligibles à l’interprétation, et qui devront donc être représentées graphiquement.

Ce seuil est bien évidemment arbitraire, on ne discutera pas ici de ce point. Le seuil ici retenu est la contribution moyenne (1 / nombre de modalités actives). Le principe est simple : une modalité est dite « contributive » si elle « pèse » plus lourd sur un axe que son poids moyen dans l’ensemble du nuage d’origine.

# Seuil de contribution ----

seuil <- 100 / nrow(res_acm$var$coord)

Mise en forme des résultats statistiques

L’objectif est de parvenir à créer un grand tableau qui contient autant de lignes que de modalités actives et/ou supplémentaires. Chacune de ces modalités est décrite par sa fréquence dans le jeu de données, et par les indicateurs usuels de l’AGD pour les premiers axes factoriels (contribution, coordonnée, cosinus carré, v.test).

Ce tableau peut être exporté dans un tableur afin de servir de support à l’interprétation des axes. Rappelons que l’interprétation statistique des axes doit impérativement précéder l’interprétation graphique, faute de quoi on s’expose à commettre de grosses erreurs d’interprétation.

Ce tableau peut aussi, en étant légèrement mis en forme, être utilisé dans une annexe de mémoire, de thèse, ou dans une publication. Pour la raison évoquée dans le paragraphe précédent et en introduction de cet article, cela devrait même être obligatoire…

Fréquences des modalités actives et supplémentaires

Première étape : récupérer les fréquences de chaque modalité. Ce faisant on récupère aussi la liste de toutes les modalités du jeu de données, ainsi que les variables auxquelles elles appartiennent. Ce sera très utile plus bas pour dessiner des symboles différents par variable…

# 5.1. Fréquences modalités actives et supplémentaires ----

frequences <- gather(d_acm, variables, modalites) %>% # étendre le jeu de données par variable et modalité
  count(variables, modalites) %>% # compter le nombre de couples "variable/modalité" unique (donc le nombre d'individus par modalité du jeu de données)
  group_by(variables) %>% 
  mutate(pourcentage = round(100 * n / nrow(d_acm), 1)) %>% # calculer des pourcentages pour chaque groupe de variable
  ungroup() %>% 
  select(variables, modalites, n, pourcentage)

On obtient un le tableau suivant (extrait des premières lignes) :

variables modalites n pourcentage
age +50a 786 17
age 15-29a 1859 40,1
age 30-39a 1116 24,1
age 40-49a 871 18,8
genre Femmes 1355 29,3
genre Hommes 3277 70,7

Indicateurs statistiques des modalités actives

Deuxième étape : récupérer les indicateurs statistiques des modalités actives. On procède pas à pas : d’abord les coordonnées, puis les contributions, les cosinus carrés et les v.tests.

# 5.2. Modalités actives ----

# Coordonnées (modalités actives) ----
coordonnees <- as.data.frame(round(res_acm$var$coord, 2)) %>% # récupérer les coordonnées des modalités actives et arrondir à deux décimales (c'est bien suffisant)
  rename_all(tolower) %>% # tout en minuscules
  rename_all(~ str_replace(., " ", "")) %>% # renommer les variables en supprimant les espaces : par exemple : "dim 1" devient "dim1" 
  rename_all(~ str_c(., "coord", sep = "_")) %>% # ajouter le suffixe _coord à chaque nom de variable. On obtient ainsi par exemple "dim1_coord"
  mutate(modalites = rownames(.)) # récupérer les noms des modalités, stockées dans le nom des lignes de res_acm$var$coord

# Contributions (modalités actives) ----
contributions <- as.data.frame(round(res_acm$var$contrib, 2))  %>% 
    rename_all(tolower) %>% 
    rename_all(~ str_replace(., " ", "")) %>% 
    rename_all(~ str_c(., "contrib", sep = "_")) %>% # idem sauf qu'ici on obtient "dim1_contrib"
    mutate(modalites = rownames(.))
  
# Cosinus carrés (modalités actives) ----
cos2 <- as.data.frame(round(res_acm$var$cos2, 2)) %>% 
    rename_all(tolower) %>%
    rename_all(~ str_replace(., " ", "")) %>% 
    rename_all(~ str_c(., "cos2", sep = "_")) %>% # idem avec "cos2" 
    mutate(modalites = rownames(.))

# vtest (modalités actives) ----
vtest <- as.data.frame(round(res_acm$var$v.test, 2)) %>% 
  rename_all(tolower) %>%
  rename_all(~ str_replace(., " ", "")) %>% 
  rename_all(~ str_c(., "vtest", sep = "_")) %>% # idem avec vtest
  mutate(modalites = rownames(.))

Enfin, on assemble ces cinq tableaux, afin d’obtenir un grand tableau (resultats_actives) comportant toutes les informations dont on a besoin pour décrire les axes selon les modalités actives. On en profite pour faire un peu de « cosmétique » en renommant proprement les noms des variables (ce sera très utile par la suite pour construire la légende des graphiques).

resultats_actives <- frequences %>% 
  right_join(coordonnees) %>% 
  right_join(contributions) %>% 
  right_join(cos2) %>% 
  right_join(vtest) %>% # fusionner les jeux de données ; la clé de fusion (implicite) est la variable "modalites", qui est commune à tous. 
  mutate(type = "Variable active") %>% # ajout d'une colonne contenant la chaîne de caractères "Variable active" (pour pouvoir distinguer plus tard avec les variables supplémentaires)
  select(type, variables, modalites, n, pourcentage,
         contains("dim1"), contains("dim2"),
         contains("dim3"), contains("dim4")) %>% # conserver et réorganiser les variables pertinentes axe par axe
  mutate(variables = fct_recode(variables, # renommer les noms des variables actives afin qu'elles soient lisibles et "publiables"
                                "Age" = "age",
                                "Genre" = "genre", 
                                "Diplôme" = "diplome", 
                                "Nationalité" = "nationalite",
                                "Position dans l'emploi" = "position", 
                                "Choix du contrat" = "choix", 
                                "Horaire de travail" = "horaires", 
                                "Souhaite travailler plus" = "heures_plus", 
                                "Public/Privé" = "public_prive", 
                                "Secteur d'activité" = "secteur"))

On obtient le tableau suivant (extraits) :

type variables modalites n pourcentage dim1_coord dim1_contrib dim1_cos2
Variable active Age +50a 786 17 -0,23 0,41 0,01
Variable active Age 15-29a 1859 40,1 0,2 0,69 0,03
Variable active Age 30-39a 1116 24,1 -0,06 0,04 0
Variable active Age 40-49a 871 18,8 -0,13 0,15 0
Variable active Genre Femmes 1355 29,3 0,92 11,24 0,35
Variable active Genre Hommes 3277 70,7 -0,38 4,65 0,35

Indicateurs statistiques des modalités illustratives

On procède de la même façon pour les modalités illustratives. On assemble les résultats dans un objet resultats_sup.

# 5.3. Modalités supplémentaires ----

# Coordonnées (modalités supplémentaires) ----
coordonnees_sup <- as.data.frame(round(res_acm$quali.sup$coord, 2)) %>% # la démarche est la même que supra, mais avec le sous-objet quali.sup qui stocke les informations sur les variables qualitatives supplémentaires
  rename_all(tolower) %>%
  rename_all(~ str_replace(., " ", "")) %>% 
  rename_all(~ str_c(., "coord", sep = "_")) %>% 
  mutate(modalites = rownames(.))
  
# Cosinus carrés (modalités supplémentaires) ----
cos2_sup <- as.data.frame(round(res_acm$quali.sup$cos2, 2)) %>% 
  rename_all(tolower) %>%
  rename_all(~ str_replace(., " ", "")) %>% 
  rename_all(~ str_c(., "cos2", sep = "_")) %>% 
  mutate(modalites = rownames(.))

# vtest (modalités supplémentaires) ----
vtest_sup <- as.data.frame(round(res_acm$quali.sup$v.test, 2)) %>% 
  rename_all(tolower) %>%
  rename_all(~ str_replace(., " ", "")) %>% 
  rename_all(~ str_c(., "vtest", sep = "_")) %>% 
  mutate(modalites = rownames(.))

# Assemblage du tableau des résultats (modalités actives) ----
  
resultats_sup <- frequences %>% 
  right_join(coordonnees_sup) %>% 
  right_join(cos2_sup) %>% 
  right_join(vtest_sup) %>% 
  mutate(type = "Variable supplémentaire") %>% # comme supra pour le tableau des résultats des modalités actives : on distingue ici le type de variable.
  select(type, variables, modalites, n, pourcentage,
         contains("dim1"), contains("dim2"),
         contains("dim3"), contains("dim4")) %>% 
  mutate(variables = fct_recode(variables,
                                "Groupe socio-professionnel" = "gsp",
                                "Catégorie socio-professionnelle" = "sociopro", 
                                "Type de ménage" = "menage"))

Assemblage du tableau complet des résultats

Afin d’obtenir un tableau comportant toutes les informations pour toutes les modalités actives et illustratives, on colle les lignes des tableaux resultats_actives, resultats_sup.

On a donc dans un seul et même objet intitulé resultats_complet  toutes les informations nécessaires pour 1/ interpréter correctement les axes et 2/ faire de beaux graphiques !

resultats_complet <- bind_rows(resultats_actives, resultats_sup)

Visualisations graphiques

Avant de se lancer dans la réalisation des graphiques, un point d’étape s’impose. De quoi dispose-t-on ?

  • Des résultats de l’ACM stockés dans un objet res_acm, qui se subdivise en sous-objet permettant de décrire les axes, les individus, les variables actives et les variables illustratives ;
  • un tableau synthétique comportant les fréquences, coordonnées, contributions, cosinus carrés et v.test des modalités actives : resultats_actives ;
  • la même chose (sauf les contributions) pour les modalités illustratives : resultats_sup ;
  • un grand tableau qui amalgame toutes ces informations : resultats_complet

Ce sont ces informations qui nous serviront à réaliser nos graphiques.

On peut représenter les résultats d’une analyse géométrique de deux façons complémentaires :

  • le nuage des modalités : on projette les modalités actives et éventuellement supplémentaires pertinentes sur le plan formé par la confrontation de deux axes ;
  • le nuage des individus : on projette les individus sur ce même plan.

L’information la plus importante ce sont donc les coordonnées des individus ou des modalités sur les axes, qui permettent de positionner des points sur un plan.

On peut ensuite modifier ces graphiques de diverses façons :

  • faire varier la taille des points en fonction du poids des individus ou des modalités dans l’inertie conservée dans le plan factoriel, ou bien en fonction de la fréquence de chaque point.
  • « habiller » le nuage des individus en fonction d’une variable catégorielle, afin de visualiser comment se répartissent et se distinguent des individus dont les propriétés sociales diffèrent ;
  • tracer des ellipses de concentration, qui permettent de mieux visualiser la forme de sous-nuages de points, et donc de mener une analyse structurale de l’information géométrique contenue dans ces nuages.

Le nuage des modalités actives

Commençons par le plus simple : sélectionner les modalités qui passent le seuil de la contribution moyenne, et les projeter dans un plan factoriel…

# 6.1. Nuage des modalités actives ----

# Nuage des modalités actives simple ----

resultats_actives %>% 
  filter(dim1_contrib > seuil |
           dim2_contrib > seuil) %>% # on part du tableau de résultat des modalités actives et on filtre uniquement celles dont la contribution dépasse le seuil pour l'un ou l'autre des deux axes (| est l'opérateur logique OU).
  
  ggplot(aes(x = dim1_coord, y = dim2_coord, # initialisation du graphique
                  label = modalites, # les labels des points sont les modalités
                  shape = variables)) +  # les formes des points dépendent des variables : à chaque variable son symbole
  
  geom_point() + # tracer les points
  geom_text_repel(segment.alpha = 0.5) + # tracer les labels, en utilisant cette fonction du package ggrepel qui permet de s'assurer qu'il n'y a pas de chevauchement. segment.alpha : transparence du petit tiret qui relie si besoin le libellé au point
  coord_fixed() + # pour que les échelles des axes soient identiques
  
  geom_hline(yintercept = 0, colour = "darkgrey", linetype="longdash") + # ligne horizontale y = 0
  geom_vline(xintercept = 0, colour = "darkgrey", linetype="longdash") + # ligne verticale x = 0
  
  xlab(paste0("Axe 1 (", round(variances[1, 2], 1), " %)")) + # label de l'axe horizontal, qui intègre automatiquement le pourcentage de variance
  ylab(paste0("Axe 2 (", round(variances[2, 2], 1), " %)")) + # idem pour l'axe vertical
  
  scale_shape_manual(name="", values = 0:20) + # sélection des types de symboles 
  
  guides(shape=guide_legend(title="")) + # paramètres de la légende : pas de titre
  
  theme_minimal() + # mise en forme globale du graphique ; theme_minimal est la plus "sobre" mais d'autres sont possibles... 
 
  theme(legend.position="bottom") # pour que la légende se place sous le graphique.
Nuage des modalités actives – Premier plan factoriel

Il n’est pas inintéressant de faire figurer sur ce graphique une indication de l’importance de chaque modalité. On peut ainsi moduler la taille des symboles par l’effectif de chaque modalité

# Nuage des modalités actives : points proportionnels aux effectifs ----

resultats_actives %>% 
  filter(dim1_contrib > seuil |
           dim2_contrib > seuil) %>% 
  
  ggplot(aes(x = dim1_coord, y = dim2_coord, 
             label = modalites,
             shape = variables, 
             size = n)) + # il suffit d'ajouter ce paramètre : la taille des points s'ajuste à la variable n (effectifs)
  
  geom_point() +
  geom_text_repel(size = 3, segment.alpha = 0.5) +
  coord_fixed() +

  geom_hline(yintercept = 0, colour = "darkgrey", linetype="longdash") +
  geom_vline(xintercept = 0, colour = "darkgrey", linetype="longdash") +
  
  xlab(paste0("Axe 1 (", round(variances[1, 2], 1), " %)")) +
  ylab(paste0("Axe 2 (", round(variances[2, 2], 1), " %)")) +
  
  scale_shape_manual(name="", values = 0:20) +
  guides(shape=guide_legend(title=""), size = FALSE) + # paramètres de la légende : pour ne pas que les échelles de taille s'affichent dans la légende
  
  theme_minimal() +
  theme(legend.position="bottom")
Nuage des modalités actives (symboles proportionnels aux effectifs) – Premier plan factoriel

On pourrait multiplier les types de graphiques différents, en modifiant juste le paramètre « size » :

  • points proportionnels aux contributions sur l’axe 1 ou l’axe 2, ou à la moyenne des contributions sur les deux axes
  • points proportionnels aux cos2, etc.

On peut aussi colorer les symboles et les labels en fonction d’un gradient de couleurs proportionnel à une variable numérique (ici les effectifs). Il suffit de jouer sur le paramètre « color ».


resultats_actives %>% 
  filter(dim1_contrib > seuil |
           dim2_contrib > seuil) %>% 
  
  ggplot(aes(x = dim1_coord, y = dim2_coord, 
             label = modalites,
             shape = variables, 
             color = n)) + # il suffit de remplacer size par color
  
  geom_point() +
  geom_text_repel(size = 3, segment.alpha = 0.5) +
  coord_fixed() +

  geom_hline(yintercept = 0, colour = "darkgrey", linetype="longdash") +
  geom_vline(xintercept = 0, colour = "darkgrey", linetype="longdash") +
  
  xlab(paste0("Axe 1 (", round(variances[1, 2], 1), " %)")) +
  ylab(paste0("Axe 2 (", round(variances[2, 2], 1), " %)")) +
  
  scale_shape_manual(name="", values = 0:20) +
  scale_color_gradient(name="", 
                       # low = "#FFCC33", high = "#FF3300" # enlever le commentaire pour un exemple de modification de couleur du gradient
                       ) +
  guides(shape=guide_legend(title=""), size = FALSE) + # paramètres de la légende : pour ne pas que les échelles de taille s'affichent dans la légende
  
  theme_minimal() +
  theme(legend.position="bottom")
Nuage des modalités actives (couleurs proportionnelles aux effectifs) – Premier plan factoriel

Les nuages des modalités actives et illustratives

On peut aussi représenter sur un même graphique les modalités actives et illustratives. J’ai arbitrairement décidé de ne représenter que les modalités illustratives dont la coordonnée dépasse 0.3 en valeur absolue.

L’enjeu important ici est de bien différencier les deux types de variables. On fait le choix ici de les distinguer par la couleur. C’est l’argument colour = type qui permet de le faire (pour rappel, type est le nom de la colonne du tableau resultats_complet qui distingue entre variables actives et variables supplémentaires).

Il faut aussi évidemment paramétrer la légende afin qu’elle soit lisible et permette bien de distinguer entre les deux types de variables (en plus des symboles des modalités actives et supplémentaires).

# 6.2. Nuage des modalités actives et supplémentaires ----

resultats_complet %>% 
  filter(dim1_contrib > seuil |
           dim2_contrib > seuil |
           is.na(dim2_contrib) & dim1_coord > 0.29 |
           is.na(dim2_contrib) & dim1_coord < -0.31) %>% # on part du tableau complet et on sélectionne, en plus des modalités actives qui passent le seuil, les modalités supplémentaires dont les coordonnées s'écartent de +/- 0.3 du barycentre (c'est parfaitement arbitraire...).
  
  ggplot(aes(x = dim1_coord, y = dim2_coord, 
             label = modalites,
             shape = variables,
             colour = type, # on distingue par des couleurs différentes les variables actives et supplémentaires
             size = n)) + 
  
  geom_point() +
  geom_text_repel(size = 3, segment.alpha = 0.5) +
  coord_fixed() +

  geom_hline(yintercept = 0, colour = "darkgrey", linetype="longdash") +
  geom_vline(xintercept = 0, colour = "darkgrey", linetype="longdash") +
  
  xlab(paste0("Axe 1 (", round(variances[1, 2], 1), " %)")) +
  ylab(paste0("Axe 2 (", round(variances[2, 2], 1), " %)")) +
  
  scale_shape_manual(name="", values = 0:20) +
  scale_color_manual(values = c("black", "darkgrey")) + # paramètres de couleur ; tout est possible ici... à vous de tester les lignes ci-dessous par exemple :
  # scale_color_brewer(palette = "Set1") +
  # scale_color_grey() +
  # scale_color_brewer(palette = "Accent")
  
  guides(shape = guide_legend(title="Nom des variables (actives et supplémentaires)", # titre de la légende des noms de variable
                              title.position = "top"), 
         colour = guide_legend(title= "Type de variable", # titre de la légende distinguant actives et supplémentaires
                               title.position = "top",
                               nrow = 2),
         size = FALSE) + # toujours pas de légende pour les tailles de point
  
  theme_minimal() +
  theme(legend.position="bottom")
Nuage des modalités actives et supplémentaires (symboles proportionnels aux effectifs) – Premier plan factoriel

Le nuage des individus et les ellipses de concentration

L’un des intérêts des méthodes d’analyse géométrique est de pouvoir raisonner sur des variables et sur des individus. Il est donc important de pouvoir bien représenter le nuage des individus, et de maîtriser les outils graphiques permettant d’en analyser la structure.

Commençons par le plus simple : le nuage des individus dans son plus simple appareil… Il faut d’abord récupérer les coordonnées des individus, stockées dans res_acm_ind_coord

# 6.3. Nuage des individus ----

indiv12 <- as.data.frame(res_acm$ind$coord[,1:2]) # récupérer les coordonnées des individus sur les deux premiers axes.

ggplot(indiv12, aes(x = `Dim 1`, y = `Dim 2`)) +  # initialisation du graphique   
  geom_point(alpha = 0.6, # alpha : permet une certaine transparence des points, pour mieux voir les superpositions. On pourrait aussi changer la taille des points avec l'argument size = 1 (par exemple).
             colour = "#E41A1C") + # couleur hexadécimale tout à fait arbitraire... les goûts et les couleurs...  
  coord_fixed() +
  
  geom_hline(yintercept = 0, colour = "darkgrey", linetype="longdash") +
  geom_vline(xintercept = 0, colour = "darkgrey", linetype="longdash") +
  
  xlab(paste0("Axe 1 (", round(variances[1, 2], 1), " %)")) +
  ylab(paste0("Axe 2 (", round(variances[2, 2], 1), " %)")) +
  
  guides(colour = FALSE) + # pas de légende pour le paramètre de couluer
  
  theme_minimal()
Nuage des individus – Premier plan factoriel

Mais ce nuage peut aussi être « habillé », selon les modalités d’une variable dont on aimerait bien savoir comment elle structure l’espace social représenté. Autrement dit, on aimerait pouvoir visualiser la façon dont ce nuage est structuré par le genre.

Une première étape consiste à coller ensemble les informations dont on a besoin pour faire le graphique : les coordonnées des individus et leur genre…

Ensuite, ce n’est que l’affaire d’un paramètre : colour = genre.

# 6.4. Habillage du nuage des individus : points habillés selon le genre ----

indiv12_genre <- d_acm %>% 
  select(genre) %>% 
  bind_cols(indiv12) # pour habiller le nuage de points, on a besoin d'adjoindre aux coordonnées l'information avec laquelle on veut l'habiller. Ici le genre. On s'autorise cette manipulation uniquement car l'ordre des lignes n'a pas été modifié... 

ggplot(indiv12_genre, aes(x = `Dim 1`, y = `Dim 2`, 
                    colour = genre)) + # ce paramètre permet de colorier les points en fonction de la variable "genre"
  
  geom_point(alpha = 0.6) +
  coord_fixed() +
  
  geom_hline(yintercept = 0, colour = "darkgrey", linetype="longdash") +
  geom_vline(xintercept = 0, colour = "darkgrey", linetype="longdash") +
  
  xlab(paste0("Axe 1 (", round(variances[1, 2], 1), " %)")) +
  ylab(paste0("Axe 2 (", round(variances[2, 2], 1), " %)")) +
  
  scale_color_brewer(palette = "Set2") + # idem ici : tout est possible, à partir de RColorBrewer notamment. Voir display.brewer.all()
  
  guides(colour = guide_legend(title="")) + # pour enlever le titre de la légende
  
  theme_minimal()
Nuage des individus selon le genre – Premier plan factoriel

Enfin, on peut tracer très facilement des ellipses de concentration, afin de visualiser de façon synthétique la forme de ces nuages, et leur intersection éventuelle.

C’est la fonction stat_ellipse()qui s’en charge. Par défaut, l’ellipse englobe 95% des points de chaque sous-nuage (c’est modifiable dans la fonction avec le paramètre level = …).

# 6.5. Habillage du nuage des individus : points habillés et ellipses de concentration----

ggplot(indiv12_genre, aes(x = `Dim 1`, y = `Dim 2`, 
                          colour = genre)) + 
  
  geom_point(alpha = 0.6) +
  coord_fixed() +

  stat_ellipse() + # unique ajout par rapport au graphique précédent, permettant de tracer les ellipses
  
  geom_hline(yintercept = 0, colour = "darkgrey", linetype="longdash") +
  geom_vline(xintercept = 0, colour = "darkgrey", linetype="longdash") +
  
  xlab(paste0("Axe 1 (", round(variances[1, 2], 1), " %)")) +
  ylab(paste0("Axe 2 (", round(variances[2, 2], 1), " %)")) +
  
  scale_color_brewer(palette = "Set2") +
  
  guides(colour = guide_legend(title="")) +
  
  theme_minimal()
Nuage des individus selon le genre (ellipses de concentration) – Premier plan factoriel

Références

Cibois Philippe, 1997, « Les pièges de l’analyse des correspondances », Histoire & Mesure, 12(3), p. 299‑320.

Cibois Philippe, 2000, L’analyse factorielle. Analyse en composantes principales et analyse des correspondances, Paris, PUF.

Denord François, Lagneau-Ymonet Paul, Thine Sylvain, 2011, « Le champ du pouvoir en France », Actes de la recherche en sciences sociales, 190(5), p. 24‑57.

Volle Michel, 1997, Analyse des données, Paris, Economica.

Le Roux Brigitte, Rouanet Henry, 2014, Analyse géométrique des données multidimensionnelles, Paris, Dunod.

Remerciements

Cet article a fait l’objet d’une relecture attentive de la part de Gabriel Alcaras, Sofia Aouani, Samuel Coavoux et Pierre Mercklé. Qu’ils en soient ici bien amicalement remerciés. Les erreurs et autres inexactitudes qui y demeureraient sont bien évidemment de ma seule responsabilité…


Vous aimerez aussi...

2 réponses

  1. Pierre Bataille dit :

    Bonjour,

    merci bcp pour ce très clair exposé! Je travaille aussi à visualiser des ACM avec ggplot2 depuis quelques temps et je dois dire que si j’étais tombé sur un billet comme le votre plus tôt, j’aurai gagné bcp de temps.

    Je m’interroge néanmoins sur une chose: les échelles des axes (ordonnées, abscisses) ne sont pas les même dans vos figures. Or, j’ai l’impression que cela peut fausser la lecture. Je crois aussi que dans les ouvrages de Leroux et Rouanet et dans d’autres packages R qui permettent de visualiser les des ACM (comme soc.ca), les échelles des x et des y sont les mêmes – dans ggplot2, il suffit d’ajouter « coord_fix() » à l’équation.

    Qu’en pensez vous? Est-ce aussi nécessaire que l’idée que je m’en fais?

    Meilleures salutations
    Pierre Bataille

    • Anton Perdoncin dit :

      Bonjour. Merci à vous ! En effet, c’est important, et c’est un oubli de ma part. J’ajoute l’argument coord_fixed() et une petite ligne pour expliquer.

Répondre à Pierre Bataille Annuler la réponse

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.