
Calculer une moyenne standardisée avec R et en faire une représentation avec ggplot2
Dans cet article, je propose une méthode pour calculer des moyennes standardisées avec R. Il s’agit d’un procédé simple que j’ai découvert par l’intermédiaire de Pierre Marissal [1], et qui a des vertus exploratoires tout à fait intéressantes.
Principe des moyennes standardisées
Voici le type de graphique que nous allons voir comment construire dans cet article. Ce graphique est issu d’une analyse sur les inégalités des conditions de vie des étudiant-es [2]. Le graphique met en relation les points des étudiant-es et les "privations matérielles" qu’elles/ils subissent dans leur quotidien, et montre une relation manifeste. Pour éviter que cette relation soit due à l’inégale répartition des étudiants dans les différentes années, la moyenne est "standardisée" par niveau d’étude :
Voici des explications plus détaillées du principe suivi. Continuons avec les données issues de l’enquête sur les conditions de vie étudiantes. Imaginons que nous voulons investiguer la relation entre la moyenne des points obtenus par les étudiant-es pendant une année académique et, non pas les privations, mais leur origine sociale (par facilité pour la démonstration). Le calcul est simple, et montre une relation manifeste, si l’on observe les intervalles de confiance (IC- et IC+, calculés à un niveau de confiance de 0.95) :
Cependant, ce résultat doit être interprété avec prudence, et il ne faudrait pas trop rapidement conclure à une influence de l’origine sociale sur les résultats scolaires, même si c’est très tentant. Voici par exemple un élément qui pourrait interférer avec cette interprétation : on sait que les points sont plus élevés en master qu’en bachelier, soit parce que les étudiant-es qui rencontrent des difficultés scolaires ont été éliminés pendant les années de bachelier, soit parce que les enseignants sont plus cléments en master (les deux explications coexistent très certainement). De ce fait, il suffirait que les étudiant-es d’origine sociale "supérieure" soient sur-représentés en master et les étudiant-es d’origine "populaire" le soient en bachelier, pour que la relation que l’on observe soit en partie (ou totalement) due à l’inégale répartition des étudiant-es d’origines sociales différentes dans les différents niveaux d’études...
Il est ainsi possible de "standardiser" le calcul de la moyenne par le niveau d’étude, pour s’assurer de l’absence d’effet de ce dernier. Pour cela, il faut que chaque niveau d’étude ait le même poids dans le calcul de la moyenne pour chaque groupe d’origine sociale. Comment faire ? Nous calculons d’abord la moyenne des points par origine sociale et par année d’étude. Nous calculons dans le même temps le poids (la proportion) des différentes années d’études dans l’ensemble de l’échantillon : 27,3% pour les BA1, 24,6% pour les BA2-3 et 47,9% pour les MA-MS (colonne Freq/année d’étude, similaire quel que soit le milieu social). Voici le résultat :
On constate que les points sont effectivement plus bas, quelle que soit l’origine sociale, dans les années inférieures. Il est alors possible de calculer une moyenne "standardisée" en sommant, au sein de chaque groupe (d’origine sociale) la moyenne de chaque niveau d’étude pondérée par le poids de ce niveau calculé au sein de l’ensemble de l’échantillon. De ce fait, chaque niveau d’étude contribue de la même manière à la moyenne quel que soit le groupe (d’origine sociale) considéré (excusez la notation mathématique hasardeuse d’un sociologue) :
${Moyenne\:standardis\acute{e}e_{\text{groupe}}}=\sum_{groupe}{Moyenne_{\text{(groupe et année)}}}\times\:pond\acute{e}ration_{\text{(année)}}$
Par exemple, pour calculer la moyenne des étudiant-es d’origine "populaire", la moyenne standardisée = (0,27x6,98)+(0,25x10,68)+(0,48x10,63) = 9,66. Si on fait cette opération pour chaque groupe d’origine sociale, voici les résultats obtenus (la différence avec mon calcul manuel provient de l’erreur d’arrondi) :

On obtient ainsi une moyenne qui ne dépend plus de l’inégale répartition des étudiant-es des différentes origines sociales dans les niveaux d’études. Les résultats sont donc sensiblement différents du premier tableau, même si les écarts ne sont pas énormes, la répartition dans les années étant en réalité assez peu dépendante du milieu d’origine dans les données utilisées. Avant de passer à l’opérationnalisation dans R, je dois faire trois remarques :
- Même si cette moyenne parle d’avantage du lien "propre" entre origine sociale et moyenne des points, elle est une abstraction, qu’il faut interpréter comme telle. En termes interprétatifs, elle indique la moyenne qu’aurait un groupe (ici d’origine sociale) si celui ci était reparti de la même manière que dans tout l’échantillon dans la variable de standardisation (ici le niveau d’étude) ;
- Il est bien entendu possible de "standardiser" avec plus d’un facteur. On pourrait par exemple penser au domaine d’étude, puisque les manières de noter doivent varier sensiblement entre facultés, et puisqu’il est probable que l’inscription dans une faculté ne soit pas indépendante de l’origine sociale (on trouve par exemple plus d’étudiant-es provenant de milieux aisés en médecine). Mais pour la clarté de l’exercice, nous nous limiterons à un facteur ;
- Je ne sais pas s’il est correct de calculer des intervalles de confiance de manière standard sur cette moyenne "standardisée". C’est un point à éclaircir, et que je ne développe pas ici par manque d’informations.
Opérationnalisation avec R
Je présente maintenant comment j’ai opérationnalisé le calcul de cette moyenne "standardisée" avec R. J’utilise pour ce faire les données issues de l’enquête Histoire de vie - Construction des identités menée par l’INSEE. J’utilise ces données mises en formes de la manière décrite par un article précédent de ce site : vous devez donc au préalable suivre cette procédure pour avoir sous la main les données chargées dans un objet, qu’il faut nommer d pour que les opérations présentées ci-après puissent s’exécuter. Un fichier reprenant l’ensemble du script présenté dans cette page et les données utilisées est téléchargeable en fin d’article.
Imaginons que nous voulons savoir si les heures passées à lire (heures.lectures) varient selon le diplôme de la personne (nivetud). Attention : cette analyse est faite pour exemplifier la procédure, sans garantie que celle-ci soit très pertinente sur le fond. En premier lieu, la variable heures.lectures ne reprend que les personnes qui déclarent lire, alors qu’il est tout à fait probable que le taux de lecteurs/trices varie selon le niveau d’étude. En second lieu, un nombre étrangement élevé de personnes déclarent lire au moins 9h par jour, élément qui jette le doute sur la fiabilité de cette information. En troisième lieu, les effectifs sont faibles, ce qui implique que les intervalles de confiance sont nécessairement élevés. Soit, faisons comme si ces problèmes n’existaient pas pour l’exercice. Si l’on calcule les heures moyennes par niveau d’étude pour les lecteurs/trices, on voit que les écarts sont assez faibles, puisque les lecteurs/trices sans diplôme lisent autant que lecteurs/trices diplômé-es du supérieur, chose étonnante au premier abord :
A priori, la relation parmi les lecteurs/trices n’est donc pas très forte. Sauf que le niveau d’étude est corrélé à l’âge (les plus âgé-es ont fait moins d’étude), et que les heures de lectures également (les lecteurs/trices âgé-es lisent plus). De ce fait, il est possible que notre constat des heures de lectures tout aussi élevées parmi les lecteurs/trices ayant peu fait d’étude que ceux ayant un diplôme du supérieur soit provoqué par le fait que ces lecteurs/trices soient aussi plus âgé-es ! Calculons donc une moyenne standardisée par âge, afin d’avoir une première idée du phénomène, avant par exemple d’entamer des analyses mutivariées plus complexes.
Dans R, nous allons commencer par déterminer les variables retenues dans des éléments génériques. Cela nous permet de définir les variables à utiliser au tout début du script, ce qui nous évitera de changer tout le script par la suite quand nous réaliserons ce même calcul avec d’autres variables :
- La variable de groupe (les différents niveaux d’études) est définie dans la colonne GROUP_MEAN_STD ;
- La variable pour laquelle nous voulons calculer une moyenne standardisée (les heures de lecture) est stockée dans la colonne MEAN ;
- La variable de standardisation (l’âge) l’est quant à elle dans la variable STANDARD_MEAN_1 ;
- Des labels sont déterminés pour chacune des variables, pour la construction du graphique par après avec ggplot2. L’unité de la variable à partir de laquelle la moyenne est calculée est également stipulée (" h" dans Label_MEAN_STD_unit) dans ce même objectif.
# * Une variable de groupe
d$GROUP_MEAN_STD <- d$nivetud
Label_group_MEAN_STD <- "Niveau d'étude"
# Note : la variable dont il faut comparer la moyenne
d$MEAN <- d$heures.lecture
Label_MEAN_STD <- "Heures de lecture"
Label_MEAN_STD_unit <- " h"
# * Une variable de standardisation
d$STANDARD_MEAN_1 <- d$age_rec
Label_standard_MEAN_1 <- "Âge"
A l’aide du package dplyr (faisant partie du tidyverse), je calcule ensuite une moyenne standardisée des heures de lecture par niveau d’étude des répondant-es. Il s’agit de l’opérationnalisation du principe expliqué dans le point précédent, que je détaille en quatre étapes :
- Etape 1 : des moyennes par groupe (de diplôme) et par catégorie (d’âge) de la variable de standardisation sont calculées (mean_group_niv) ;
- Etape 2 : les effectifs sont calculés pour chaque catégories (d’âge) de la variable de standardisation au sein de l’ensemble de l’échantillon (n_niv) ;
- Etape 3 : grâces aux effectifs calculés lors de l’étape 2, le poids des différents niveaux de la variable de standardisation est calculé (en proportion) à travers tout l’échantillon (prop_niv) ;
- Etape 4 : les moyennes standardisées (mean_group) sont enfin calculées en sommant, pour chaque groupe (de diplôme), les poids des âges dans tout l’échantillon aux moyennes correspondant à ces âges.
library(tidyverse)
STD_MEAN <- d %>%
filter(!is.na(GROUP_MEAN_STD) & !is.na(MEAN) & !is.na(STANDARD_MEAN_1)) %>%
group_by(STANDARD_MEAN_1, GROUP_MEAN_STD) %>% # étape 1
summarise(n = n(),
mean_group_niv = mean(MEAN, na.rm = TRUE)) %>%
ungroup() %>%
group_by(STANDARD_MEAN_1) %>% # étape 2
mutate(n_niv = sum(n)) %>%
ungroup() %>% # étape 3
mutate(n_tot = sum(n),
prop_niv = n_niv / n_tot) %>%
group_by(GROUP_MEAN_STD) %>% # étape 4
summarise(n = sum(n),
mean_group = sum(prop_niv * mean_group_niv)
) %>%
mutate(Pourcentage = n/sum(n))
Le tableau ci-dessous montre le résultat. La standardisation a eu un effet, puisque l’écart entre les niveaux d’étude faibles et élevés a augmenté, signifiant que l’âge jouait bien dans le résultat obtenu précédemment :
Ce calcul a des vertus exploratoires, mais peut également servir dans le cadre de communications qui ne doivent pas être trop techniques : le coefficient d’une régression associé à une modalité est toujours moins facile à comprendre qu’une moyenne qui, même standardisée, donne une mesure facilement compréhensible.
Représentation graphique avec ggplot2
Le tableau précédent est bien triste ; les résultats seraient davantage mis en valeur dans une jolie représentation graphique. Voyons comment faire avec ggplot2. Au préalable, je réalise plusieurs opérations de préparation :
- Je transforme la variable de groupe en facteur, au cas où elle serait numérique, si les groupes ont comme nom un chiffre (1, 2, 3...) ;
- Je réalise une régression linéaire avec la moyenne standardisée en Y et les différents niveaux (d’étude) du groupe en X, et stocke les coefficients dans l’objet coef ;
- Je choisis un titre et un sous-titre pour le graphique dans label_main_MEAN_STD_manual et label_sub_MEAN_STD_manual, afin de pouvoir les changer facilement en cas de changement de variables ;
- Je calcule la moyenne générale dans mean_general, à laquelle les différents groupes seront comparés sur le graphique.
# Je transforme la variable de groupe en facteur
STD_MEAN$GROUP_MEAN_STD <- as.factor(STD_MEAN$GROUP_MEAN_STD)
# Je réalise une régression et stocke les coefficients de la régression dans un objet "coef".
# Je transforme au préalable la variable de groupe en variable numérique (normalement ça se fait correctement, puisque la variable a préalablement déjà été transformée en facteur), pour que le calcul de la régression linéaire se fasse correctement.
coef <- coef(lm(mean_group ~ as.numeric(STD_MEAN$GROUP_MEAN_STD), data = STD_MEAN))
# Je définis un Titre et Sous-titre du graphique
label_main_MEAN_STD_manual <- "Moyennes des heures passées à lire"
label_sub_MEAN_STD_manual <- "Par niveau d'étude, standardisation par âge"
# La moyenne générale, pour l'afficher comme ligne horizontale sur le graphique ggplot2.
# L'échantillon est filtré à travers une indexation de la même manière que dans le pipeline qui a calculé la moyenne standardisée (voir plus haut)
mean_general <- mean(d$MEAN[!is.na(d$GROUP_MEAN_STD) & !is.na(d$MEAN) & !is.na(d$STANDARD_MEAN_1)], na.rm = TRUE)
Je construis ensuite le graphique ggplot2 à proprement parler, à l’aide des différents éléments définis précédemment. Je ne détaille pas chaque étape dans le corps du texte, chacune des étapes étant expliquée avec suffisamment de détails dans le code. Si un élément n’est pas clair, n’hésitez pas à me contacter :
library(viridis) # Pour les palettes de couleur viridis
library(hrbrthemes) # Des thèmes pour ggplot2
bulles_STD_MEAN <- STD_MEAN %>%
ggplot(aes(x=GROUP_MEAN_STD,
y=mean_group,
colour=as.factor(GROUP_MEAN_STD),
size = Pourcentage*100)) +
geom_abline(size = 0.5, # Une ligne horizontale qui représente la moyenne générale dans l'échantillon
linetype = 2,
colour = "#db4051",
intercept = mean_general, # Est ce que cela a du sens en standardisant ?
slope = 0,
alpha = 0.8) +
geom_abline(size = 0.8, # La droite de régression qui indique la tendance
linetype = 1,
colour = "black",
intercept = coef[1], # Les coefficients sont issus de l'objet "coef" créé précédemment
slope = coef[2],
alpha = 0.1) +
geom_point(alpha=0.8, # Pour faire des bulles blanches transparentes
color = "white") +
geom_point(shape = 1, # Les bordures des bulles dans un nouveau geom_point, pour pouvoir décider de ne pas les rendre transparentes (contrairement au centre des bulles)
alpha=1,
stroke = 2) +
geom_text(aes(label = paste0(round(mean_group, digits = 1), # Les labels dans les bulles ont l'unité choisie précédemment
Label_MEAN_STD_unit)),
size = 3.5,
color="#333333") +
scale_size_area(name = "Échelle : part des\nrépondant·es interrogé·es", # Une légente indiquant ce que représente la taille des bulles en % de l'échantillon
max_size = 28,
breaks = c(50,40,30,20,10,5,2.5,1),
labels = function(x) paste0(x, "%")) + # La légende pour la taille des bulle est graduée en pourcentage
scale_y_continuous(labels = function(x) paste0(x, Label_MEAN_STD_unit), # L'axe Y est gradué avec l'unité choisie précédemment
expand = expansion(mult = c(0.15, .15))) +
scale_x_discrete(expand = expansion(mult = c(0.15, .15)),
labels = function(x) str_wrap(x, width = 10)) + # Pour forcer les labels de l'axe X à aller à la ligne si trop longs (> 10)
scale_color_viridis(discrete=TRUE, # L'échelle de couleur viridis avec l'option "D"
guide="none",
option = "D",
begin = 0,
end = 1) +
theme_ipsum() + # Un joli thème issu du package hrbrthemes()
theme(panel.grid.minor.x = element_blank(), # Quelques personnalisations du thème
panel.grid.minor.y = element_line(color="#eeeeee"),
panel.grid.major.x = element_line(color="#eeeeee"),
panel.grid.major.y = element_line(color="#eeeeee"),
axis.text.y = element_text(margin = margin(r = 0)),
legend.position="right",
legend.background = element_rect(fill="#fafafa",
size=0.5, linetype="dotted",
colour ="#6c6c6c")
) +
ylab(Label_MEAN_STD) + # A partir d'ici les titres définis précédemment
xlab(Label_group_MEAN_STD) +
ggtitle(label_main_MEAN_STD_manual) +
labs(subtitle = label_sub_MEAN_STD_manual)
bulles_STD_MEAN
Voici le résultat, qui est plutôt esthétique. Il s’agit d’un simple nuage de points, avec la moyenne en Y et les groupes (de niveau d’étude) en X. Une droite de régression (en gris clair) indique la relation tendancielle. La moyenne générale est indiquée par une ligne discontinue rouge. La taille des bulles indique la taille du groupe à l’échelle de l’échantillon (en proportion). L’idéal serait d’avoir une indication des intervalles de confiance des moyennes calculées, mais je dois encore explorer la faisabilité de ce genre de calcul pour une moyenne pondérée.