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))
Thelma Panaïotis
13 juillet 2026
dataviz, couleurs, palette qualitative, ggplot2, RColorBrewer, okabe-ito, R
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).
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.
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 :
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.
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()`).
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"
)
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.
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
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.
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.
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.
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)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.
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)
)
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.
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))
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 ?