Trop de couleurs tue la couleur

Data viz
Tutoriels R
Palettes qualitatives et limites de la perception humaine : combien de catégories peut-on vraiment distinguer sur un graphique ?
Auteur·rice

Thelma Panaïotis

Date de publication

13 juillet 2026

Mots clés

dataviz, couleurs, palette qualitative, ggplot2, RColorBrewer, okabe-ito, R

Trop de couleurs tue la couleur

Une palette qualitative avec trop de catégories, c’est un peu comme un camembert avec trop de parts : joli, mais illisible.

Cet article fait partie d’une série sur les couleurs en dataviz :

Les données utilisées dans cet article proviennent du dataset many_penguins (TidyTuesday, 14 juillet 2026), lui-même dérivé de la base AVONET (Tobias et al. 2022).

Les palettes qualitatives

Prenons un jeu de données réel avec beaucoup de catégories : many_penguins, publié cette semaine dans TidyTuesday. C’est un prolongement naturel du dataset penguins utilisé dans le premier article de cette série : là où penguins ne couvre que 3 espèces (qui au passage ne sont pas des pingouins mais des manchots !), many_penguins en couvre 18, réparties en 6 genres, issues du dataset AVONET qui recense les traits morphologiques de l’ensemble des oiseaux du monde.

93 individus, 18 espèces. C’est exactement le genre de situation où on a besoin d’une palette qualitative solide. Sans réfléchir, on laisse d’abord ggplot2 choisir sa palette par défaut.

many_penguins |>
  ggplot(aes(x = beak.width, y = wing.length, colour = species)) +
  geom_point(size = 1.5) +
  labs(x = "Largeur du bec (mm)", y = "Longueur des ailes (mm)", colour = "Espèce") +
  theme(legend.text = element_text(size = 10))
Nuage de points de la largeur du bec en fonction de la longueur des ailes chez 93 manchots, colorés par espèce avec la palette par défaut de ggplot2. Les couleurs se ressemblent beaucoup, rendant les 18 espèces difficiles à distinguer les unes des autres.
Figure 1: 18 espèces de manchots, palette par défaut de ggplot2.

Le résultat est illisible. La palette par défaut de ggplot2 répartit les teintes de façon égale sur le cercle chromatique, sans se soucier de leur ressemblance perceptuelle : certaines couleurs voisines se confondent presque, d’autres sont beaucoup plus saillantes que le reste sans raison. Ce n’est pas conçu pour ça.

C’est là qu’interviennent les palettes de RColorBrewer, conçues à l’origine pour la cartographie. Il en existe huit, chacune avec sa propre logique :

  • Set1 (9 couleurs max) : teintes très saturées, fort contraste, la plus polyvalente
  • Set2 (8 couleurs max) : teintes plus douces, adaptée aux fonds clairs
  • Set3 (12 couleurs max) : le plus grand nombre de teintes disponibles, mais des tons plus pastel donc moins contrastés
  • Dark2 (8 couleurs max) : tons foncés, bon contraste sur fond blanc
  • Paired (12 couleurs max) : des paires clair/foncé pour chaque teinte, pensée pour des catégories qui vont naturellement deux par deux, pas pour des catégories complètement indépendantes
  • Accent (8 couleurs max) : pensée pour faire ressortir une ou deux classes importantes au milieu des autres, pas pour distinguer un grand nombre de catégories à égalité
  • Pastel1 (9 couleurs max) : teintes pastel, faible contraste, à éviter dès qu’on a beaucoup de catégories
  • Pastel2 (8 couleurs max) : même logique que Pastel1, palette d’origine plus restreinte

Il existe d’autres familles de palettes qualitatives en dehors de ColorBrewer, notamment okabe-ito, conçue spécifiquement pour rester lisible en cas de daltonisme. On la retrouve dans le widget de la section suivante, et plus en détail dans le troisième article de la série.

Même la plus généreuse de ces huit palettes, Set3, plafonne à 12 couleurs. Avec 18 espèces, on est structurellement au-delà de ce que n’importe quelle palette qualitative de ColorBrewer peut proposer.

many_penguins |>
  ggplot(aes(x = beak.width, y = wing.length, colour = species)) +
  geom_point(size = 1.5) +
  scale_colour_brewer(palette = "Set3") +
  labs(x = "Largeur du bec (mm)", y = "Longueur des ailes (mm)", colour = "Espèce") +
  theme(legend.text = element_text(size = 10))
Warning in RColorBrewer::brewer.pal(n, pal): n too large, allowed maximum for palette Set3 is 12
Returning the palette you asked for with that many colors
Warning: Removed 39 rows containing missing values or values outside the scale range
(`geom_point()`).
Le même nuage de points, coloré cette fois avec la palette Set3. Les points de six espèces sur dix-huit apparaissent en gris uniforme, faute de couleur disponible dans la palette.
Figure 2: Le même graphique avec la palette Set3 (12 couleurs maximum). Les six espèces en trop n’apparaissent plus sur le graphique.

Cette fois, ce n’est plus une question de lisibilité difficile : R refuse purement et simplement d’attribuer une couleur aux espèces excédentaires. Elles disparaissent du graphique, sans point ni couleur, tout en restant listées dans la légende. Six espèces sur dix-huit s’évaporent ainsi silencieusement. Ce n’est plus une palette qui se dégrade, c’est un mur.

Il existe un cas intermédiaire souvent oublié : les variables ordinales, c’est-à-dire des catégories qui ont un ordre naturel mais pas de valeur numérique continue. Par exemple des tranches d’âge, des niveaux de satisfaction, ou faible / moyen / fort.

Ni une palette qualitative (qui ignore l’ordre) ni une palette séquentielle continue (qui suppose des intervalles réguliers) ne conviennent vraiment. La bonne réponse est une palette séquentielle discrète, comme scale_fill_brewer(palette = "Blues") avec des niveaux ordonnés, ou scale_fill_steps() pour discrétiser une variable continue.

palmerpenguins::penguins |>
  drop_na(body_mass_g) |>
  mutate(masse_cat = cut(
    body_mass_g,
    breaks = 4,
    labels = c("Légère", "Moyenne", "Lourde", "Très lourde"))
  ) |>
  ggplot(aes(x = flipper_length_mm, y = bill_length_mm, colour = masse_cat)) +
  geom_point(size = 1.5) +
  scale_colour_brewer(palette = "RdPu", direction = 1) +
  labs(
    x = "Longueur des nageoires (mm)",
    y = "Longueur du bec (mm)",
    colour = "Masse"
  )
Nuage de points de la longueur du bec en fonction de la longueur des nageoires chez les manchots, colorés selon quatre tranches de masse ordonnées, d'un rose clair pour les plus légers à un rose foncé pour les plus lourds.
Figure 3: Palette séquentielle discrète : la masse des manchots (dataset penguins, article 1) découpée en quatre tranches ordonnées.

Ni assez de couleurs disponibles, ni un moyen de contourner le problème en changeant simplement de palette. C’est cette question qu’on creuse dans la section suivante.

Combien de couleurs peut-on vraiment distinguer ?

En section précédente, R a refusé net d’attribuer une couleur à six espèces sur dix-huit avec Set3. C’est un mur facile à repérer : soit la palette a assez de couleurs, soit elle n’en a pas. Mais la vraie question est plus subtile que ce simple seuil technique : même quand une palette a assez de couleurs, à partir de combien de catégories devient-elle difficile à lire ?

Il n’existe pas de nombre magique validé scientifiquement. Le fameux « 7 plus ou moins 2 » du psychologue George Miller, souvent cité en dataviz, porte en réalité sur la mémoire à court terme en général, pas spécifiquement sur la couleur. Deux facteurs jouent en même temps : le nombre de catégories, et la qualité de la palette elle-même.

Plutôt que de vous donner un chiffre unique, essayez par vous-même

Figure 4: 18 espèces de manchots, deux palettes de votre choix, un curseur pour faire varier le nombre d’espèces affichées. Observez à quel rythme chaque palette se dégrade.

Avec des palettes qui assignent toujours une couleur, quel que soit le nombre de catégories, le mur disparaît : chaque espèce reste identifiable, même quand deux teintes se ressemblent beaucoup. C’est exactement le compromis que permet l’interactivité du widget : pas besoin de distinguer visuellement 18 teintes à l’œil nu, un survol suffit à lever le doute. La dégradation reste réelle, elle est seulement progressive plutôt que brutale : les teintes se rapprochent de plus en plus à mesure que le nombre de catégories augmente, jusqu’à devenir quasiment interchangeables au premier coup d’œil.

Faire disparaître une catégorie n’est pas un bug, c’est une conséquence du fonctionnement de RColorBrewer : la palette a un nombre de couleurs fixe, et scale_colour_brewer() ne sait pas en inventer une de plus. R signale d’ailleurs le problème par un avertissement (n too large, allowed maximum for palette...) plutôt que de masquer la limite. C’est un signal utile : il oblige à changer de stratégie plutôt que de laisser croire que tout va bien.

Dans les deux cas, qu’il s’agisse d’un mur nettement visible ou d’une dérive progressive, le message est le même : au-delà d’un certain nombre de catégories, changer de palette ne suffit plus. C’est ce qu’on regarde dans la dernière section.

Que faire quand on a trop de catégories ?

Colorer chacune des dix-huit espèces individuellement ne fonctionne pas, on l’a vu. Voici trois façons différentes de contourner le problème, parmi bien d’autres possibles.

Regrouper par une hiérarchie qui a du sens

Les 18 espèces se répartissent en seulement 6 genres. Plutôt que de regrouper les catégories rares dans un « Autres » artificiel, on peut s’appuyer sur une hiérarchie biologique qui existe déjà dans les données.

many_penguins |>
  ggplot(aes(x = beak.width, y = wing.length, colour = genus)) +
  geom_point(size = 1.5) +
  scale_colour_brewer(palette = "Dark2") +
  labs(x = "Largeur du bec (mm)", y = "Longueur des ailes (mm)", colour = "Genre") +
  theme(legend.text = element_text(size = 10))
Le même nuage de points de largeur du bec et longueur des ailes, coloré cette fois par genre plutôt que par espèce : six couleurs nettement distinctes au lieu de dix-huit.
Figure 5: Les mêmes données, colorées par genre plutôt que par espèce : 6 catégories au lieu de 18.

Six catégories, n’importe quelle palette qualitative standard s’en sort bien. Le prix à payer est net : on perd complètement la distinction entre espèces d’un même genre.

Il existe un moyen simple de récupérer ce détail sans toucher à la couleur : rendre le même graphique interactif. Le package ggiraph transforme un graphique ggplot2 en objet interactif, où survoler un point affiche l’espèce correspondante.

library(ggiraph)

p <- many_penguins |>
  drop_na(beak.width, wing.length) |>
  ggplot(aes(
    x = beak.width, y = wing.length, colour = genus,
    tooltip = species, data_id = species
  )) +
  geom_point_interactive(size = 1.5) +
  scale_colour_brewer(palette = "Dark2") +
  labs(x = "Largeur du bec (mm)", y = "Longueur des ailes (mm)", colour = "Genre") +
  theme(legend.text = element_text(size = 10))

girafe(ggobj = p, width_svg = 7, height_svg = 5)
Figure 6: Le même graphique, rendu interactif. Survolez un point pour voir l’espèce correspondante.

L’identité de chaque point reste disponible, à la demande plutôt qu’affichée en permanence, sans couleur supplémentaire. Le compromis à assumer : ça ne fonctionne qu’à l’écran, avec JavaScript actif. Imprimé ou lu hors ligne, l’interactivité disparaît.

Pour une solution qui reste utilisable même à l’impression, il faut sortir de la couleur autrement. On pourrait être tenté d’ajouter encore un canal visuel : une forme différente par espèce en plus de la couleur, une légende propre à chaque sous-graphique… Chaque tentative de ce genre revient buter sur le même mur qu’on a déjà rencontré. La couleur plafonne quelque part entre 8 et 12, scale_shape() plafonne à 6. Peu importe le canal choisi, tant qu’on essaie d’y faire tenir 18 identités distinctes, on retombe sur une limite. La vraie sortie ne consiste pas à trouver un meilleur canal, mais à faire porter l’identité par la position plutôt que par une esthétique de couleur ou de forme, qui elle n’a pas ce genre de plafond.

Facetter directement par espèce

many_penguins |>
  drop_na(beak.width, wing.length) |>
  mutate(species = fct_reorder(species, as.numeric(factor(genus)))) |>
  ggplot(aes(x = beak.width, y = wing.length, colour = genus)) +
  geom_point(size = 1.5) +
  scale_colour_brewer(palette = "Dark2") +
  scale_x_continuous(limits = c(5, 21)) +
  scale_y_continuous(limits = c(60, 190)) +
  facet_wrap(~species, ncol = 3) +
  labs(x = "Largeur du bec (mm)", y = "Longueur des ailes (mm)", colour = "Genre") +
  theme(
    strip.text = element_text(face = "italic", size = 8), 
    legend.text = element_text(size = 10)
  )
Grille de dix-huit petits graphiques, un par espèce de manchot, partageant les mêmes axes de largeur du bec et longueur des ailes. Les points sont colorés par genre pour regrouper visuellement les espèces apparentées.
Figure 7: Un panneau par espèce (18 au total), couleur par genre pour le regroupement visuel, axes partagés.

La couleur est toujours là, mais elle ne porte plus l’identité individuelle : elle confirme seulement que les espèces d’un même genre sont regroupées. C’est la position du panneau, pas la couleur du point, qui distingue les 18 espèces.

Changer complètement de type de graphique

Une dernière option, plus radicale : abandonner le nuage de points. Avec les espèces en position sur l’axe plutôt qu’en couleur, la question du nombre de couleurs disparaît complètement. Un boxplot, par exemple, se lit très bien avec 18 catégories en position, tant qu’on garde une seule couleur par genre pour le regroupement visuel.

many_penguins |>
  ggplot(aes(x = fct_rev(species), y = `hand-wing.index`, fill = genus)) +
  geom_boxplot(outlier.alpha = 0.6, box.linewidth = 0.5, alpha = 0.8) +
  scale_fill_brewer(palette = "Dark2") +
  coord_flip() +
  labs(
    x = NULL,
    y = "Indice de forme des ailes (Hand-Wing Index)",
    fill = "Genre"
  ) +
  theme(axis.text.y = element_text(size = 8), legend.text = element_text(size = 10))
Diagramme en boîtes horizontal de l'indice de forme des ailes pour dix-huit espèces de manchots, colorées par genre. Les espèces du genre Aptenodytes affichent les indices les plus élevés, signe d'ailes plus effilées.
Figure 8: Indice de forme des ailes par espèce, position plutôt que couleur. Le remplissage par genre (6 couleurs) aide à repérer les regroupements sans surcharger le graphique.

Le Hand-Wing Index mesure la forme de l’aile : plus il est élevé, plus l’aile est effilée, adaptée au vol sur de longues distances. Ici encore, la couleur ne porte plus l’information principale, elle regroupe seulement. On retrouve la hiérarchie des encodages vue dans le premier article de cette série : la position sur un axe reste l’encodage le plus précis, sans les limites perceptuelles ou techniques de la couleur.

Pour aller plus loin

Pour explorer d’autres palettes que celles de ColorBrewer, deux ressources utiles : le repo de palettes de couleurs pour R d’Emil Hvitfeldt, qui recense des dizaines de collections, et son package paletteer, qui centralise plus de 2500 palettes directement utilisables dans R. Pour tester visuellement une palette avant de l’adopter, R Graph Gallery propose un outil interactif de recherche par couleur.

Choisir une palette qualitative, c’est se heurter tôt ou tard à deux murs différents : celui, net et technique, du nombre de couleurs qu’une palette peut proposer avant de laisser des catégories sans couleur ; et celui, plus flou mais tout aussi réel, du nombre de couleurs qu’un œil humain peut suivre sans effort, même quand la palette en a suffisamment.

Dans le prochain article de la série, on s’intéresse à l’accessibilité : et si votre graphique était illisible pour 8 % de vos lecteurs ?

Envie de faire de la dataviz propre et efficace avec R ?

Références

Tobias, Joseph A., Catherine Sheard, Alex L. Pigot, et al. 2022. « AVONET: Morphological, Ecological and Geographical Data for All Birds ». Ecology Letters 25 (3): 581‑97. https://doi.org/10.1111/ele.13898.