4.1 Graphique en barres

Le graphique en barres (on dit aussi graphique en bâtons) compare les effectifs pour différents niveaux (ou modalités) d’une variable qualitative ou facteur. La différence avec l’histogramme est donc subtile et tient au fait que, pour l’histogramme, nous partons d’une variable quantitative qui est découpée en classes.

4.1.1 Effectifs par facteur

La question du nombre et/ou de l’intervalle des classes ne se pose pas dans le cas du graphique en barres. Par défaut, les barres seront séparées les unes des autres par un petit espace vide pour bien indiquer visuellement qu’il n’y a pas continuité entre les classes (dans l’histogramme, les barres sont accolées les unes aux autres pour matérialiser justement cette continuité).

La formule que vous utiliserez, ici encore, ne fait appel qu’à une seule variable et s’écrira donc :

\[\sim variable \ facteur\]

Exemple d'un graphique en barres montrant le dénombrement des niveaux d'une variable facteur, avec les éléments importants du graphique mis en évidence en couleurs.

Figure 4.1: Exemple d’un graphique en barres montrant le dénombrement des niveaux d’une variable facteur, avec les éléments importants du graphique mis en évidence en couleurs.

Outre les barres elles-mêmes, prêtez toujours attention aux éléments suivants du graphique (ici mis en évidence en couleurs) :

  • les axes avec les graduations (en rouge)
  • les niveaux de la variable facteur (en rouge également)
  • le label des axes (en bleu)

Les instructions dans R pour produire un graphique en barres à l’aide de la fonction chart() sont les suivantes. Nous partons d’un jeu de données zooplankton que nous importons et dont nous extrayons un sous-ensemble à l’aide de la fonction sfilter()(vous étudierez en détail les fonctions de remaniement de tableaux dans les deux prochains modules) avant de réaliser notre graphique à l’aide de chart() :

# Importation du jeu de données
(zooplankton <- read("zooplankton", package = "data.io", lang = "FR"))
# # A data.table: 1262 x 20
# # Language:     FR
#      ecd  area perimeter feret major minor  mean  mode   min   max std_dev range
#    <dbl> <dbl>     <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl> <dbl>
#  1 0.770 0.465      4.45 1.32  1.16  0.509 0.363 0.036 0.004 0.908   0.231 0.904
#  2 0.700 0.385      2.32 0.728 0.713 0.688 0.361 0.492 0.024 0.676   0.183 0.652
#  3 0.815 0.521      4.15 1.33  1.11  0.598 0.308 0.032 0.008 0.696   0.204 0.688
#  4 0.785 0.484      4.44 1.78  1.56  0.394 0.332 0.036 0.004 0.728   0.218 0.724
#  5 0.361 0.103      1.71 0.739 0.694 0.188 0.153 0.016 0.008 0.452   0.110 0.444
#  6 0.832 0.544      5.27 1.66  1.36  0.511 0.371 0.02  0.004 0.844   0.268 0.84 
#  7 1.23  1.20      15.7  3.92  1.37  1.11  0.217 0.012 0.004 0.784   0.214 0.78 
#  8 0.620 0.302      3.98 1.19  1.04  0.370 0.316 0.012 0.004 0.756   0.246 0.752
#  9 1.19  1.12      15.3  3.85  1.34  1.06  0.176 0.012 0.004 0.728   0.172 0.724
# 10 1.04  0.856      7.60 1.89  1.66  0.656 0.404 0.044 0.004 0.88    0.264 0.876
# # … with 1,252 more rows, and 8 more variables: size <dbl>, aspect <dbl>,
# #   elongation <dbl>, compactness <dbl>, transparency <dbl>, circularity <dbl>,
# #   density <dbl>, class <fct>
# Réduction du jeu de données à quatre classes seulement
(copepoda <- sfilter(zooplankton,
  class %in% c("Calanoïde", "Cyclopoïde",  "Harpacticoïde",  "Poecilostomatoïde")))
# # A data.table: 535 x 20
# # Language:     FR
#      ecd  area perimeter feret major minor  mean  mode   min   max std_dev range
#    <dbl> <dbl>     <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>   <dbl> <dbl>
#  1 0.770 0.465      4.45 1.32  1.16  0.509 0.363 0.036 0.004 0.908   0.231 0.904
#  2 0.815 0.521      4.15 1.33  1.11  0.598 0.308 0.032 0.008 0.696   0.204 0.688
#  3 0.785 0.484      4.44 1.78  1.56  0.394 0.332 0.036 0.004 0.728   0.218 0.724
#  4 0.361 0.103      1.71 0.739 0.694 0.188 0.153 0.016 0.008 0.452   0.110 0.444
#  5 0.832 0.544      5.27 1.66  1.36  0.511 0.371 0.02  0.004 0.844   0.268 0.84 
#  6 1.23  1.20      15.7  3.92  1.37  1.11  0.217 0.012 0.004 0.784   0.214 0.78 
#  7 0.620 0.302      3.98 1.19  1.04  0.370 0.316 0.012 0.004 0.756   0.246 0.752
#  8 1.19  1.12      15.3  3.85  1.34  1.06  0.176 0.012 0.004 0.728   0.172 0.724
#  9 1.04  0.856      7.60 1.89  1.66  0.656 0.404 0.044 0.004 0.88    0.264 0.876
# 10 0.725 0.412      7.14 1.90  0.802 0.655 0.209 0.008 0.004 0.732   0.202 0.728
# # … with 525 more rows, and 8 more variables: size <dbl>, aspect <dbl>,
# #   elongation <dbl>, compactness <dbl>, transparency <dbl>, circularity <dbl>,
# #   density <dbl>, class <fct>
# Réalisation du graphique
chart(data = copepoda, ~ class) +
  geom_bar() +
  xlab("Classe") +
  ylab("Effectifs")
Abondances de quatres types de copépodes dans un échantillon de zooplancton.

Figure 4.2: Abondances de quatres types de copépodes dans un échantillon de zooplancton.

La fonction geom_bar() se charge d’ajouter les barres verticales dans le graphique. La hauteur de ces barres correspond au nombre d’observations rencontrées dans le jeu de données pour chaque niveau (ou classe, ou groupe) de la variable facteur représentée. Avec cet exemple, vous voyez également au passage comment réduire le nombre de niveaux dans un jeu de données avec filter().

4.1.2 Effectifs par deux facteurs

Reprenons maintenant le jeu de données biometry.

# Importation des données `biometry`
(biometry <- read("biometry", package = "BioDataScience", lang = "FR"))
# # A data.table: 395 x 7
# # Language:     FR
#    gender day_birth  weight height wrist year_measure   age
#    <fct>  <date>      <dbl>  <dbl> <dbl>        <dbl> <dbl>
#  1 H      1995-03-11     69    182  15           2013    18
#  2 H      1998-04-03     74    190  16           2013    15
#  3 H      1967-04-04     83    185  17.5         2013    46
#  4 H      1994-02-10     60    175  15           2013    19
#  5 F      1990-12-02     48    167  14           2013    23
#  6 F      1994-07-15     52    179  14           2013    19
#  7 F      1971-03-03     72    167  15.5         2013    42
#  8 F      1997-06-24     74    180  16           2013    16
#  9 H      1972-10-26    110    189  19           2013    41
# 10 H      1945-03-15     82    160  18           2013    68
# # … with 385 more rows

Nous voulons représenter des barres pour les effectifs d’hommes et de femmes dans ce jeu de données (variable gender), mais en les séparant par années (variable year_measure). C’est faisable, mais notez que, si gender est déjà une variable facteur <fct>, year_measure est encodé comme variable quantitative numérique (c’est un “double” indiqué <dbl>, c’est-à-dire un nombre décimal par opposition à entier <int>). Or nous devons absolument utiliser des variables facteur ici. Nous allons donc effectuer la conversion avec la fonction as.factor() avant de réaliser notre graphique en barres. Nous en profitons pour indiquer un label() en français pour cette variable.

# Conversion de la variable year_measure de numérique à facteur
biometry$year_measure <- as.factor(biometry$year_measure)
label(biometry$year_measure) <- "Année de la mesure"

Notez bien comment on se réfère à la variable year_measure à l’intérieur du jeu de données biometry avec biometry$year_measure. Et cette notation peut aussi bien être utilisée pour récupérer la colonne year_measure dans un argument d’une fonction (à droite), que comme résultat de l’assignation (à gauche de l’opérateur d’assignation <-). Ainsi l’instruction qui transforme en facteur remplace la version dans le jeu de données biometry. Maintenant, considérons que nous nous intéressons aux mesures antérieures à 2017 (cela nous permettra d’illustrer des points importants relatifs à l’utilisation de variables facteurs).

Les variables facteurs sont encodées dans R comme des entiers 1, 2, 3, … pour les différents niveaux avec en plus, un attribut levels qui y associe une description textuelle à chacun des niveaux. Cela peut être perturbant quand la description textuelle est constituée d’un nombre comme ici pour year_measure. Mais les calculs sont prohibés sur les variables facteurs.

tail(biometry)$year_measure
# Année de la mesure 
# [1] 2017 2017 2017 2017 2017 2017
# Levels: 2013 2014 2016 2017

Nous utilisons head() et tail() pour extraire les quelques lignes de début ou de fin d’un tableau. C’est utile pour en réduire la taille à l’impression. Faites très attention : lorsque vous imprimez le contenu d’une variable facteur, R substitue automatiquement les niveaux 1, 2, 3 … par le contenu textuel comme indiqué dans la dernière ligne Levels: ..., et l’imprime sans mettre le texte entre guillemets. Cela peut renforcer la fausse impression que c’est bien des valeurs numériques.

Vous commencez à comprendre que si vous effectuez maintenant la comparaison year_measure < 2017 lorsque cette variable est encodée comme facteur, ça ne fonctionnera pas comme vous le souhaitez ! Si on est chanceux, un message d’erreur ou d’avis (“warning”) est imprimé. C’est le cas de sfilter() que nous avons déjà utilisé.

biometry2 <- sfilter(biometry, year_measure < 2017) 
# Warning in Ops.factor(year_measure, 2017): '<' n'est pas pertinent pour des
# variables facteurs

Notez au passage que sfilter() n’utilise pas la notation biometry$year_measure, mais prend un premier argument qui est le jeu de données biometry, et la suite se réfère aux variables de ce jeu de données telles que year_measure en priorité aux autres variables disponibles dans l’environnement utilisateur de R. C’est donc une approche bien plus puissante que la notation traditionnelle. Aussi sfilter() renvoie tout le tableau remanié. Donc, nous devons l’affecter simplement à une variable qui contient ce tableau (biometry2 ici).

Vous pouvez aussi utiliser la fonction filter() qui s’emploie de manière similaire, mais attention : avec des tableaux tels que ceux utilisés dans SciViews::R, la fonction ne renvoie pas directement le résultat, mais crée un objet list qui contient les instructions à exécuter. Il faut collecter les résultats à l’aide de collect_dtx(), ou à l’aide de l’opérateur d’assignation alternative %<-% pour obtenir véritablement le résultat du calcul. Donc, nous obtiendrons le même résultat à l’aide de filter(), si nous pensons bien à utiliser %<-% à la place de <-. Donc, l’instruction suivante correspond à la précédente :

biometry2 %<-% filter(biometry, year_measure < 2017) 
# Warning in Ops.factor(year_measure, 2017): '<' n'est pas pertinent pour des
# variables facteurs

Mais revenons à notre variable facteur. Dans d’autres cas, le résultat peut être dramatique, car le calcul est appliqué à l’encodage des niveaux de la variable facteur. Or, avec quatre niveaux, les encodages sont 1, 2, 3 et 4, … et ils sont bien évidemment tous inférieurs à 2017 (pour rappel, “2013”, “2014”, “2016” et “2017” sont les libellés textuels des niveaux de la variable facteur) !

De manière générale, n’effectuez jamais de calcul sur des variables facteurs. Tranformez-les toujours avant. Si les libellés contiennent des valeurs numériques sur lesquelles vous voulez faire des calculs, utilisez as.numeric(as.character(VARFACT)), et une fois le calcul réalisé, retransformez en facteur avec as.factor().

Le calcul explicite et sûr est donc le suivant :

# Transforme de manière sûre factor -> numeric (double)
biometry$year_measure <- as.numeric(as.character(biometry$year_measure))
# Filtre les données sur une copie du tableau
biometry2 <- sfilter(biometry, year_measure < 2017)
# Retransforme en variable factor
biometry2$year_measure <- as.factor(biometry2$year_measure)
# Vérification
tail(biometry2)$year_measure
# [1] 2016 2016 2016 2016 2016 2016
# Levels: 2013 2014 2016

Naturellement ici, la bonne stratégie est d’effectuer le calcul sur le tableau de départ avant de transformer en facteur, mais nous sommes partis de la variable facteur à titre d’illustration d’un cas qui peut se rencontrer en pratique. À présent que nous avons notre jeu de données sans les individus de 2017, et les variables correctement encodées, nous pouvons aborder différentes représentations pour observer des dénombrements tenant compte de plusieurs variables facteurs. Par défaut, l’argument position = a pour valeur "stack" (donc, lorsque cet argument n’est pas précisé dans geom_bar(), les barres sont empilées par rapport à la seconde variable facteur).

a <- chart(data = biometry2, ~ gender) +
  geom_bar() +
  ylab("Effectifs")

b <- chart(data = biometry2, ~ gender %fill=% year_measure) +
  geom_bar() +
  ylab("Effectifs") +
  scale_fill_viridis_d()

combine_charts(list(a, b), common.legend = TRUE)
Dénombrement des hommes (H) et des femmes (F) dans l'étude sur l'obésité en Hainaut, (A) graphique utilisant un seul facteur. (B) graphique prenant en compte les années de mesure.

Figure 4.3: Dénombrement des hommes (H) et des femmes (F) dans l’étude sur l’obésité en Hainaut, (A) graphique utilisant un seul facteur. (B) graphique prenant en compte les années de mesure.

Il existe d’autres options en utilisant les valeurs "dodge" ou "fill" pour l’argument position =.

a <- chart(data = biometry2, ~ gender %fill=% year_measure) +
  geom_bar(position = "stack") +
  ylab("Effectifs") +
  scale_fill_viridis_d()

b <- chart(data = biometry2, ~ gender %fill=% year_measure) +
  geom_bar(position = "dodge") +
  ylab("Effectifs") +
  scale_fill_viridis_d()

c <- chart(data = biometry2, ~ gender %fill=% year_measure) +
  geom_bar(position = "fill") +
  ylab("Fractions") +
  scale_fill_viridis_d()

combine_charts(list(a, b, c), common.legend = TRUE)
Dénombrement des hommes (H) et des femmes (F) dans l'étude sur l'obésité en Hainaut en tenant compte des années de mesure (différentes présentations).

Figure 4.4: Dénombrement des hommes (H) et des femmes (F) dans l’étude sur l’obésité en Hainaut en tenant compte des années de mesure (différentes présentations).

Soyez vigilant à la différence entre l’argument position = "stack" et position = "fill" qui malgré un rendu semblable ont l’axe des ordonnées qui diffèrent (dans le cas de "fill", il s’agit de la fraction par rapport au total qui est représentée, et non pas des effectifs absolus dénombrés).

Pièges et astuces
Réordonner la variable facteur par fréquence

Vous pouvez avoir le souhait d’ordonner votre variable facteur afin d’améliorer le rendu visuel de votre graphique. Pour cela, vous pouvez employer la fonction fct_infreq().

chart(data = copepoda, ~ fct_infreq(class)) +
  geom_bar() +
  labs(x = "Classe", y = "Effectifs")
Dénombrement des classes de copépodes du jeu de données zooplankton.

Figure 4.5: Dénombrement des classes de copépodes du jeu de données zooplankton.

Rotation des axes du graphique en barres

Lorsque les niveaux dans la variable étudiée sont trop nombreux, les légendes en abscisse risquent de se chevaucher, comme dans la Fig. 4.6

chart(data = zooplankton, ~ class) +
  geom_bar() +
  ylab("Effectifs")
Dénombrement des classes du jeu de données zooplankton.

Figure 4.6: Dénombrement des classes du jeu de données zooplankton.

Dans ce cas, il est possible de réaliser un graphique en barres horizontales qui laisse plus de place pour le libellé sur l’axe. Il existe deux manières de le faire : préférentiellement en utilisant l’argument orientation = "y" de geom_bar() (mais alors il faut utiliser aes(y = ...) à la place de la formule). Une seconde option consiste à réaliser le graphique en barres verticales (tout le code reste identique), mais de rajouter coord_flip() tout à la fin. Utilisons successivement ces deux approches.

chart(data = zooplankton, aes(y = class)) +
  geom_bar(orientation = "y") +
  xlab("Effectifs")
Dénombrement des classes du jeu de données zooplankton (version avec barres horizontales en utilisant `orientation = "y"`).

Figure 4.7: Dénombrement des classes du jeu de données zooplankton (version avec barres horizontales en utilisant orientation = "y").

Pourquoi ne peut-on pas utiliser de formule dans ce cas ? En fait, il faudrait écrire class ~, … seulement voilà, une formule ne peut pas avoir un membre de droite vide. Donc, on est dans une impasse et on doit utiliser la forme explicite aes() pour “aesthetics” en anglais qui indique quelle variable est utilisée pour quoi dans le graphique en indiquant y =.

Avec la fonction coord_flip() ajoutée à votre graphique, vous pouvez effectuer une rotation des axes (l’axe X devient Y et inversement) pour obtenir un graphique en barres horizontales. De plus, l’œil humain perçoit plus distinctement les différences de longueurs horizontales que verticales. Donc, de ce point de vue, le graphe en barres horizontales est considéré comme meilleur que le graphe en barres verticales.

chart(data = zooplankton, ~ class) +
  geom_bar() +
  ylab("Effectifs") +
  coord_flip()
Dénombrement des classes du jeu de données zooplankton (version avec barres horizontales en utilisant `coord_flip()`).

Figure 4.8: Dénombrement des classes du jeu de données zooplankton (version avec barres horizontales en utilisant coord_flip()).

Avec coord_flip(), pensez toujours que vos axes finaux X et Y sont inversés avant l’utilisation de cette instruction. Ainsi, si vous voulez changer le libellé de l’axes X sur le graphique final, mais avant d’avoir indiqué coord_flip(), gardez bien à l’esprit que c’est en fait le libellé de l’axe Y que vous devez indiquer avec, par exemple ylab() comme c’est le cas dans la figure précédente pour le libellé “Effectifs”.

Pour en savoir plus

4.1.3 Valeurs moyennes

Le graphique en barres peut être aussi employé pour résumer des données numériques via la moyenne. Il ne s’agit plus de dénombrer les effectifs d’une variable facteur, mais de résumer des données numériques en fonction d’une variable facteur. On peut exprimer cette relation dans R sous la forme de :

\[y \sim x\]

que l’on peut lire :

\[y \ en \ fonction \ de \ x\]

Avec y une variable numérique et x une variable facteur. Considérez l’échantillon suivant :

1, 71, 55, 68, 78, 60, 83, 120, 82 ,53, 26

Calculez la moyenne sur base de la formule de la moyenne

\[\overline{y} = \sum_{i = 1}^n \frac{y_i}{n}\]

# Création du vecteur
x <- c(1, 71, 55, 68, 78, 60, 83, 120, 82, 53, 26)
# Calcul  de la moyenne
mean(x)
# [1] 63.36364

Les instructions pour produire ce graphe en barres à l’aide de chart() sont :

chart(data = copepoda, size ~ class) +
  stat_summary(geom = "col", fun = "mean") +
  xlab("Classe")
Exemple de graphique en barres représentant les moyennes de tailles par groupe zooplanctonique.

Figure 4.9: Exemple de graphique en barres représentant les moyennes de tailles par groupe zooplanctonique.

Ici, nous faisons appel à une autre famille de fonctions : celles qui effectuent des calculs sur les données avant de les représenter graphiquement. Leurs noms commencent toujours par stat_.

Le graphe en barres pour représenter les moyennes est très répandu dans le domaine scientifique malgré le grand nombre d’arguments en sa défaveur et que vous pouvez lire dans la section pour en savoir plus ci-dessous. L’un des arguments le plus important est la faible information qu’il véhicule puisque l’ensemble des données n’est plus représenté que par une valeur (la moyenne) pour chaque niveau de la variable facteur. Pour un petit nombre d’observations, il vaut mieux toutes les représenter à l’aide d’un nuage de points. Si le nombre d’observations devient très grand (dizaines ou plus), le graphique en boites de dispersion est plus indiqué (voir plus loin dans ce module). Le graphique en violon (cf. module précédent) est également utilisable s’il y a encore plus de données.

Pour en savoir plus
  • Beware of dynamite. Démonstration de l’impact d’un graphe en barres pour représenter la moyenne (et l’écart type) = graphique en “dynamite”.

  • Dynamite plots: unmitigated evil? Une autre comparaison du graphe en dynamite avec des représentations alternatives qui montre que le premier peut avoir quand même quelques avantages dans des situations particulières.