20  Échelles de Likert

Les échelles de Likert tirent leur nom du psychologue américain Rensis Likert qui les a développées. Elles sont le plus souvent utilisées pour des variables d’opinion. Elles sont codées sous forme de variable catégorielle et chaque item est codé selon une graduation comprenant en général cinq ou sept choix de réponse, par exemple : Tout à fait d’accord, D’accord, Ni en désaccord ni d’accord, Pas d’accord, Pas du tout d’accord.

Pour les échelles à nombre impair de choix, le niveau central permet d’exprimer une absence d’avis, ce qui rend inutile une modalité « Ne sait pas ». Les échelles à nombre pair de modalités voient l’omission de la modalité neutre et sont dites « à choix forcé ».

20.1 Exemple de données

Générons un jeu de données qui nous servira pour les différents exemples.

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(labelled)
Warning: le package 'labelled' a été compilé avec la version R 4.4.2
niveaux <- c(
  "Pas du tout d'accord",
  "Plutôt pas d'accord",
  "Ni d'accord, ni pas d'accord",
  "Plutôt d'accord",
  "Tout à fait d'accord"
)
set.seed(42)
df <-
  tibble(
    groupe = sample(c("A", "B"), 150, replace = TRUE),
    q1 = sample(niveaux, 150, replace = TRUE),
    q2 = sample(niveaux, 150, replace = TRUE, prob = 5:1),
    q3 = sample(niveaux, 150, replace = TRUE, prob = 1:5),
    q4 = sample(niveaux, 150, replace = TRUE, prob = 1:5),
    q5 = sample(c(niveaux, NA), 150, replace = TRUE),
    q6 = sample(niveaux, 150, replace = TRUE, prob = c(1, 0, 1, 1, 0))
  ) |> 
  mutate(across(q1:q6, ~ factor(.x, levels = niveaux))) |> 
  set_variable_labels(
    q1 = "Première question",
    q2 = "Seconde question",
    q3 = "Troisième question",
    q4 = "Quatrième question",
    q5 = "Cinquième question",
    q6 = "Sixième question"
  )

20.2 Tableau de fréquence

On peut tout à fait réaliser un tableau de fréquence classique avec gtsummary::tbl_summary().

library(gtsummary)
Warning: le package 'gtsummary' a été compilé avec la version R 4.4.2
df |> 
  tbl_summary(include = q1:q6)
Characteristic N = 1501
Première question
    Pas du tout d'accord 39 (26%)
    Plutôt pas d'accord 32 (21%)
    Ni d'accord, ni pas d'accord 25 (17%)
    Plutôt d'accord 30 (20%)
    Tout à fait d'accord 24 (16%)
Seconde question
    Pas du tout d'accord 56 (37%)
    Plutôt pas d'accord 44 (29%)
    Ni d'accord, ni pas d'accord 19 (13%)
    Plutôt d'accord 26 (17%)
    Tout à fait d'accord 5 (3.3%)
Troisième question
    Pas du tout d'accord 8 (5.3%)
    Plutôt pas d'accord 17 (11%)
    Ni d'accord, ni pas d'accord 29 (19%)
    Plutôt d'accord 43 (29%)
    Tout à fait d'accord 53 (35%)
Quatrième question
    Pas du tout d'accord 11 (7.3%)
    Plutôt pas d'accord 19 (13%)
    Ni d'accord, ni pas d'accord 31 (21%)
    Plutôt d'accord 40 (27%)
    Tout à fait d'accord 49 (33%)
Cinquième question
    Pas du tout d'accord 33 (26%)
    Plutôt pas d'accord 25 (20%)
    Ni d'accord, ni pas d'accord 28 (22%)
    Plutôt d'accord 25 (20%)
    Tout à fait d'accord 16 (13%)
    Unknown 23
Sixième question
    Pas du tout d'accord 50 (33%)
    Plutôt pas d'accord 0 (0%)
    Ni d'accord, ni pas d'accord 50 (33%)
    Plutôt d'accord 50 (33%)
    Tout à fait d'accord 0 (0%)
1 n (%)

Cependant, cela produit un tableau inutilement long, d’autant plus que les variables q1 à q6 ont les mêmes modalités de réponse. La fonction gtsummary::tbl_likert() offre un affichage plus compact.

df |> 
  tbl_likert(
    include = q1:q6
  )
Characteristic Pas du tout d’accord Plutôt pas d’accord Ni d’accord, ni pas d’accord Plutôt d’accord Tout à fait d’accord
Première question 39 (26%) 32 (21%) 25 (17%) 30 (20%) 24 (16%)
Seconde question 56 (37%) 44 (29%) 19 (13%) 26 (17%) 5 (3.3%)
Troisième question 8 (5.3%) 17 (11%) 29 (19%) 43 (29%) 53 (35%)
Quatrième question 11 (7.3%) 19 (13%) 31 (21%) 40 (27%) 49 (33%)
Cinquième question 33 (26%) 25 (20%) 28 (22%) 25 (20%) 16 (13%)
Sixième question 50 (33%) 0 (0%) 50 (33%) 50 (33%) 0 (0%)

On peut utiliser add_n() pour ajouter les effectifs totaux.

df |> 
  tbl_likert(
    include = q1:q6,
    statistic = ~ "{p}%"
  ) |> 
  add_n()
Characteristic N Pas du tout d’accord Plutôt pas d’accord Ni d’accord, ni pas d’accord Plutôt d’accord Tout à fait d’accord
Première question 150 26% 21% 17% 20% 16%
Seconde question 150 37% 29% 13% 17% 3.3%
Troisième question 150 5.3% 11% 19% 29% 35%
Quatrième question 150 7.3% 13% 21% 27% 33%
Cinquième question 127 26% 20% 22% 20% 13%
Sixième question 150 33% 0% 33% 33% 0%
Astuce

Dans certains contextes, il est envisageable de traiter notre variable ordinale comme un score numérique. Ici, nous allons attribuer les valeurs -2, -1, 0, +1 et +2 à nos 5 modalités. Dès lors, nous pourrions être intéressé de rajouter à notre tableau le score moyen. Cela est possible en quelques étapes :

  1. transformer nos facteurs en scores : les fonctions as.integer() ou unclass() permettent de transformer un facteur en valeurs numériques (1 pour la première modalité, 2 pour la seconde, etc.). Dans le cas présent, il est préférable d’utiliser unclass() qui préserve les étiquettes de variables ce qui n’est pas le cas de as.integer(). Il ne faut pas oublier de retirer 3 pour obtenir des scores allant de -2 à +2. La fonction dplyr::across() permet d’effectuer l’opération sur plusieurs variables en même temps.

  2. calculer / générer un tableau statistique avec une colonne par statistique, ce qui se fait avec gtsummary::tbl_wide_summary() qui est très similaire à gtsummary::tbl_summary().

  3. mettre les deux tableaux dans une liste et les fusionner avec gtsummary::tbl_merge().

list(
  df |> tbl_likert(include = q1:q6),
  tbl_wide_summary(
    df|> mutate(across(q1:q6, \(x) unclass(x) - 3)),
    statistic = c("{mean}", "{sd}"),
    type = ~ "continuous",
    include = q1:q6,
    digits = ~ 1
  )
) |>
  tbl_merge(tab_spanner = FALSE)
Characteristic Pas du tout d’accord Plutôt pas d’accord Ni d’accord, ni pas d’accord Plutôt d’accord Tout à fait d’accord Mean SD
Première question 39 (26%) 32 (21%) 25 (17%) 30 (20%) 24 (16%) -0.2 1.4
Seconde question 56 (37%) 44 (29%) 19 (13%) 26 (17%) 5 (3.3%) -0.8 1.2
Troisième question 8 (5.3%) 17 (11%) 29 (19%) 43 (29%) 53 (35%) 0.8 1.2
Quatrième question 11 (7.3%) 19 (13%) 31 (21%) 40 (27%) 49 (33%) 0.6 1.3
Cinquième question 33 (26%) 25 (20%) 28 (22%) 25 (20%) 16 (13%) -0.3 1.4
Sixième question 50 (33%) 0 (0%) 50 (33%) 50 (33%) 0 (0%) -0.3 1.3

20.3 Représentations graphiques

Le package ggstats propose une fonction ggstats::gglikert() pour représenter des données de Likert sous la forme d’un diagramme en barres centré sur la modalité centrale.

library(ggstats)
Warning: le package 'ggstats' a été compilé avec la version R 4.4.2
gglikert(df, include = q1:q6)

Par défaut, les pourcentages totaux ne prennent pas en compte la modalité centrale (lorsque le nombre de modalité est impair). On peut inclure la modalité centrale avec totals_include_center = TRUE, auquel cas la modalité centrale seront comptabilisée pour moitié de chaque côté. Le paramètre sort permet de trier les modalités (voir l’aide de ggstats::gglikert() pour plus de détails sur les différentes méthodes de tri).

df |> 
  gglikert(
    include = q1:q6,
    totals_include_center = TRUE,
    sort = "ascending"
  ) +
  guides(
    fill = guide_legend(nrow = 2)
  )

Il est possible de séparer les résultats par sous-groupe avec des facettes.

df |> 
  gglikert(
    include = q1:q6,
    facet_cols = vars(groupe)
  )

df |> 
  gglikert(
    include = q1:q6,
    y = "groupe",
    facet_rows = vars(.question),
    facet_label_wrap = 15
  )

Une représentation alternative consiste à réaliser un graphique en barres classiques, ce que l’on peut aisément obtenir avec ggstats::gglikert_stacked().

df |>
  gglikert_stacked(
    include = q1:q6,
    sort = "ascending",
    add_median_line = TRUE
  )

Il est à noter que les graphiques en barres divergentes ne font pas consensus. Voir par exemple The case against diverging stacked bars de Lisa Charlotte Muth et Gregor Aisch. À l’inverse, Naomi Robbins et Richard Heiberger recommandent plutôt ce type de représentation dans leur article Plotting Likert and Other Rating Scales dans JSM en 2011. On pourra également se référer à ces billets du site Data Revelations : How to visualize Likert scale data in Tableau, Got Likert data? Put the Neutrals off to one side ou encore Rethinking the divergent stacked bar chart — placing the stronger views in the center.