
Étiqueter la dernière valeur de chaque courbe dans ggplot2
Placez la valeur finale de chaque série au bout de sa courbe — fini la chasse à la légende
Un guide pratique pour étiqueter la dernière valeur de chaque courbe dans un graphique ggplot2 multi-courbes ou de séries temporelles. Extrayez le dernier point de chaque groupe en R base, posez une étiquette geom_text()/geom_label() au bout de la courbe, empêchez son rognage au bord du panneau, placez les valeurs sur un axe secondaire, et écartez les étiquettes de fin qui se chevauchent avec ggrepel.
- Obtenez le dernier point de chaque courbe en R base pur — sans
dplyr. - Écrivez la valeur (et le nom de la série) au bout de la courbe avec
geom_text(). - Empêchez le rognage de l’étiquette au bord du panneau — expansion d’échelle ou
coord_cartesian(clip = "off"). - Placez les valeurs sur un axe secondaire pour une échelle de droite sans légende.
- Écartez les étiquettes de fin qui se chevauchent avec
ggrepelquand deux courbes se terminent proches l’une de l’autre.
Votre graphique en courbes se termine, et le lecteur doit remonter chaque courbe jusqu’à une légende pour savoir quelle courbe correspond à quelle série — et la légende ne lui donne jamais la valeur. Placez le nombre là où l’œil se trouve déjà : au bout de la courbe. C’est la touche finale la plus efficace sur un graphique multi-courbes ou de séries temporelles, et une fois le principe compris, c’est trois lignes de code.
L’astuce a deux moitiés : trouver le dernier point de chaque courbe, puis y dessiner une étiquette. Tout le reste — empêcher l’étiquette d’être coupée, gérer deux courbes qui se terminent l’une sur l’autre — n’est que fignolage. Ce guide fait tout cela, avec du code ggplot2 réellement exécuté que vous pouvez copier et exécuter.
Les données : quelques courbes qui partagent un axe des x
Tout graphique de « dernière valeur » part de données au format long — une ligne par observation, avec une colonne indiquant à quelle série appartient la ligne. (Le format long est la forme que ggplot2 attend pour des courbes groupées ; voyez tracer plusieurs courbes pour le pourquoi.) Nous utilisons le jeu de données intégré EuStockMarkets, les cours de clôture quotidiens de quatre indices européens — le DAX allemand, le SMI suisse, le CAC français et le FTSE britannique — de 1991 à 1998. Il se présente comme une large matrice de séries temporelles, nous l’empilons donc dans un data.frame long en R base :
library(ggplot2)
eu <- as.data.frame(EuStockMarkets)
eu$time <- as.numeric(time(EuStockMarkets)) # decimal year for the x-axis
# wide -> long, base R: repeat time, stack the four price columns
stocks <- data.frame(
time = rep(eu$time, 4),
index = rep(c("DAX", "SMI", "CAC", "FTSE"), each = nrow(eu)),
price = c(eu$DAX, eu$SMI, eu$CAC, eu$FTSE)
)
head(stocks) time index price
1 1991.496 DAX 1628.75
2 1991.500 DAX 1613.63
3 1991.504 DAX 1606.51
4 1991.508 DAX 1621.04
5 1991.512 DAX 1618.16
6 1991.515 DAX 1610.61
Voici le graphique brut — quatre courbes, et une légende que vous devez sans cesse consulter :
ggplot(stocks, aes(x = time, y = price, colour = index)) +
geom_line(linewidth = 0.7) +
scale_colour_viridis_d(end = 0.9) +
labs(x = NULL, y = "Index level", colour = "Index") +
theme_minimal()
Étape 1 — obtenir le dernier point de chaque courbe
L’étiquette doit se placer à l’observation finale de chaque courbe, extrayez donc d’abord une ligne par groupe : la ligne dont le x est le plus grand (ici, le time le plus récent). En R base, scindez les données selon la colonne de groupe et prenez la ligne au x maximal de chaque morceau avec which.max() :
library(ggplot2)
eu <- as.data.frame(EuStockMarkets)
eu$time <- as.numeric(time(EuStockMarkets))
stocks <- data.frame(
time = rep(eu$time, 4),
index = rep(c("DAX", "SMI", "CAC", "FTSE"), each = nrow(eu)),
price = c(eu$DAX, eu$SMI, eu$CAC, eu$FTSE)
)
# one row per index: its last (latest-time) observation
ends <- do.call(rbind, lapply(split(stocks, stocks$index),
function(d) d[which.max(d$time), ]))
ends time index price
CAC 1998.646 CAC 3995.00
DAX 1998.646 DAX 5473.72
FTSE 1998.646 FTSE 5455.00
SMI 1998.646 SMI 7676.30
split() découpe le tableau en une liste, un élément par indice ; which.max(d$time) trouve la ligne la plus récente de chaque série ; do.call(rbind, ...) empile les quatre gagnantes dans un petit data frame. Cette table ends — quatre lignes, une par courbe — est ce que nous étiquetons. Le principe est tout à fait général : il trouve le vrai dernier point même lorsque les séries ont des longueurs différentes (une courbe qui s’arrête tôt est tout de même étiquetée à sa propre fin). Si vous préférez le tidyverse, le même résultat s’obtient avec dplyr::slice_max(order_by = time, n = 1, by = index) — mais R base garde la recette sans dépendances.
Étape 2 — dessiner l’étiquette avec geom_text()
Ajoutez maintenant une deuxième couche — un geom_text() alimenté par le petit tableau ends plutôt que par les données complètes. Faites correspondre son label à ce que vous voulez au bout de la courbe ; ici le nom de l’indice et sa valeur finale. Définissez hjust = 0 pour que le texte soit aligné à gauche et s’étende vers la droite, à l’écart de la courbe, et retirez la légende — les étiquettes l’ont remplacée :
ggplot(stocks, aes(x = time, y = price, colour = index)) +
geom_line(linewidth = 0.7) +
geom_text(
data = ends,
aes(label = paste0(index, " ", round(price))),
hjust = 0, nudge_x = 0.1, fontface = "bold"
) +
scale_colour_viridis_d(end = 0.9) +
labs(x = NULL, y = "Index level") +
theme_minimal() +
theme(legend.position = "none")
C’est toute l’idée. Comme l’étiquette hérite de colour = index depuis l’aes() du graphique, chaque étiquette s’accorde à sa courbe gratuitement. Deux choses restent grossières, et les deux étapes suivantes les corrigent : les étiquettes débordent du bord droit (elles sont rognées), et le DAX et le FTSE se terminent si près que leurs étiquettes se chevauchent.
Étape 3 — empêcher le rognage des étiquettes au bord
Une étiquette placée au dernier point dépasse la zone de tracé et se retrouve coupée. La correction propre consiste à donner plus de place au panneau sur la droite en élargissant l’échelle des x — expansion(mult = c(0.02, 0.18)) ajoute 2 % de marge à gauche et 18 % à droite, à l’intérieur du panneau, pour que le texte ait où se loger :
ggplot(stocks, aes(x = time, y = price, colour = index)) +
geom_line(linewidth = 0.7) +
geom_text(
data = ends,
aes(label = paste0(index, " ", round(price))),
hjust = 0, nudge_x = 0.1, fontface = "bold"
) +
scale_x_continuous(expand = expansion(mult = c(0.02, 0.18))) +
scale_colour_viridis_d(end = 0.9) +
labs(x = NULL, y = "Index level") +
theme_minimal() +
theme(legend.position = "none")
L’alternative — quand vous voulez que les étiquettes vivent dans la marge en dehors du panneau plutôt que de grignoter l’espace du graphique — est de désactiver le rognage et d’élargir plutôt la marge du graphique :
ggplot(stocks, aes(x = time, y = price, colour = index)) +
geom_line(linewidth = 0.7) +
geom_text(
data = ends,
aes(label = paste0(index, " ", round(price))),
hjust = 0, nudge_x = 0.1, fontface = "bold"
) +
coord_cartesian(clip = "off") +
scale_colour_viridis_d(end = 0.9) +
labs(x = NULL, y = "Index level") +
theme_minimal() +
theme(legend.position = "none",
plot.margin = margin(5.5, 60, 5.5, 5.5)) # extra right margin (pt)
coord_cartesian(clip = "off") autorise les géométries à dessiner en dehors du panneau ; la marge droite plot.margin agrandie (en points, ordre : haut, droite, bas, gauche) leur donne un endroit où se poser. Optez pour la version à expansion d’échelle par défaut — elle garde l’étiquette à l’intérieur de la zone des données — et pour clip = "off" quand vous voulez précisément les étiquettes dans la marge. Le rognage est corrigé maintenant, mais le DAX et le FTSE se terminent si près que leurs étiquettes se chevauchent encore ici — l’étape 5 (ggrepel) les écarte.
Étape 4 — les valeurs sur un axe secondaire
Une voie différente et très compacte évite complètement la géométrie de texte : afficher la valeur finale de chaque courbe comme une graduation sur un axe des y secondaire. sec_axis() reflète l’échelle principale (~ . = « mêmes valeurs »), et vous définissez ses graduations sur les derniers cours et ses étiquettes sur les noms des indices :
ggplot(stocks, aes(x = time, y = price, colour = index)) +
geom_line(linewidth = 0.7) +
scale_y_continuous(
sec.axis = sec_axis(~ ., breaks = ends$price, labels = ends$index)
) +
scale_colour_viridis_d(end = 0.9) +
labs(x = NULL, y = "Index level") +
theme_minimal() +
theme(legend.position = "none")
L’axe de droite nomme désormais chaque courbe là où elle aboutit — une échelle nette, sans légende, sans rognage à gérer. Sa seule limite est le revers d’être un axe : les graduations qui tombent trop près les unes des autres (le DAX et le FTSE, encore) se tassent, et il affiche le nom de la série, pas une étiquette libre. Quand deux fins coïncident presque, écartez-les — c’est exactement ce que fait l’étape suivante.
Étape 5 — écarter les extrémités qui se chevauchent avec ggrepel
Le DAX et le FTSE se terminent à un cheveu l’un de l’autre, si bien que leurs étiquettes geom_text() se chevauchent. Le package ggrepel (install.packages("ggrepel")) résout cela : geom_text_repel() écarte les étiquettes pour qu’aucune ne se heurte. Pour des étiquettes de fin, contraignez la répulsion à la direction verticale et poussez tout vers la droite, de sorte que les étiquettes restent dans une colonne nette à côté des extrémités des courbes :
library(ggrepel)
ggplot(stocks, aes(x = time, y = price, colour = index)) +
geom_line(linewidth = 0.7) +
geom_text_repel(
data = ends,
aes(label = paste0(index, " ", round(price))),
hjust = 0, direction = "y", nudge_x = 0.35,
segment.color = NA, fontface = "bold"
) +
scale_x_continuous(expand = expansion(mult = c(0.02, 0.18))) +
scale_colour_viridis_d(end = 0.9) +
labs(x = NULL, y = "Index level") +
theme_minimal() +
theme(legend.position = "none")
direction = "y" laisse les étiquettes se déplacer uniquement de haut en bas ; nudge_x = 0.35 les décale des extrémités des courbes ; segment.color = NA masque les petites lignes de rappel (retirez-le pour les garder). C’est la version vers laquelle se tourner pour tout graphique réel — elle se dégrade en douceur de « les étiquettes ne se chevauchent pas » à « les étiquettes ont à peine bougé » selon l’encombrement des extrémités. Utilisez plutôt geom_label_repel() pour des étiquettes encadrées sur un fond chargé.
Autres approches
| Approche | Idéale quand | Remarque |
|---|---|---|
geom_text() + expansion d’échelle |
Quelques courbes bien séparées | La plus simple ; le choix par défaut au quotidien. |
Axe de droite sec_axis() |
Vous voulez une échelle sans légende | Compact ; se tasse quand les extrémités sont proches. |
ggrepel |
Deux courbes ou plus se terminent proches | La réponse générale robuste. |
Package directlabels |
Vous voulez une seule fonction pour placer toutes les étiquettes | Package externe ; voyez ci-dessous. |
Si vous préférez ne pas gérer du tout le tableau ends, le package directlabels automatise l’étiquetage des courbes — geom_dl(aes(label = index), method = "last.points") trouve et place chaque étiquette de fin pour vous :
# illustrative — install.packages("directlabels")
library(directlabels)
ggplot(stocks, aes(time, price, colour = index)) +
geom_line() +
geom_dl(aes(label = index), method = "last.points") +
theme_minimal() + theme(legend.position = "none")C’est pratique pour un passage rapide « nommer chaque courbe », même si vous renoncez au contrôle fin (texte d’étiquette personnalisé, décalage précis) que la recette ends + ggrepel ci-dessus vous offre.
Foire aux questions
Construisez un petit data frame contenant uniquement le dernier point de chaque courbe, puis ajoutez une couche geom_text() alimentée par ce tableau. En R base, do.call(rbind, lapply(split(df, df$group), function(d) d[which.max(d$x), ])) extrait la ligne finale de chaque groupe ; ensuite geom_text(data = ends, aes(label = value), hjust = 0) l’écrit au bout de la courbe. Comme la couche de texte hérite de la correspondance colour, chaque étiquette s’accorde automatiquement à sa courbe.
Scindez les données selon la colonne de regroupement et choisissez la ligne au x maximal dans chaque morceau : do.call(rbind, lapply(split(df, df$group), function(d) d[which.max(d$x), ])). Cela gère les séries de longueurs différentes — chaque groupe est étiqueté à sa propre dernière observation. L’équivalent tidyverse est dplyr::slice_max(order_by = x, n = 1, by = group).
L’étiquette dépasse le panneau de tracé, qui la rogne. Soit vous ajoutez de la place à l’intérieur du panneau en élargissant l’échelle des x — scale_x_continuous(expand = expansion(mult = c(0.02, 0.18))) —, soit vous laissez les géométries dessiner en dehors du panneau avec coord_cartesian(clip = "off") et élargissez la marge droite plot.margin. La voie de l’expansion d’échelle est le choix par défaut le plus propre car elle garde l’étiquette dans la zone des données.
Étiquetez chaque courbe à sa fin et retirez la légende avec theme(legend.position = "none"). Les étiquettes de fin portent la même information — quelle couleur correspond à quelle série — là où le lecteur regarde déjà, de sorte que la légende devient superflue. Un axe secondaire (sec_axis(~ ., breaks = ..., labels = ...)) est une autre option sans légende qui nomme chaque courbe sur l’échelle de droite.
Utilisez ggrepel (geom_text_repel(direction = "y")) quand vous voulez un contrôle total sur le texte de l’étiquette et un évitement fiable des chevauchements — c’est la réponse générale robuste. Tournez-vous vers directlabels (geom_dl(method = "last.points")) quand vous voulez simplement nommer chaque courbe rapidement avec une seule fonction, sans texte personnalisé ni décalage. Les deux reposent sur la même idée : placer l’étiquette au dernier point de chaque courbe.
Pour aller plus loin
- Série ggplot2 — les leçons plus larges sur lesquelles ce guide s’appuie : plusieurs courbes (tracer une courbe par groupe), graphique en courbes (la base à une seule courbe et les axes de séries temporelles), et texte et annotations (
geom_text,geom_label,annotate, et ggrepel en profondeur). · Visualisation de données. - Ce billet est la touche finale ciblée — étiqueter les extrémités des courbes — que ces leçons ne couvrent pas à elles seules.
Demandez à Prova « étiquette la dernière valeur de chaque courbe dans mon propre graphique ggplot2, et écarte celles qui se chevauchent » — collez vos données et elle répond avec du code que vous pouvez exécuter. The runtime is the judge. Demander à Prova →
Citation
@online{kassambara2026,
author = {Kassambara, Alboukadel},
title = {Étiqueter la dernière valeur de chaque courbe dans ggplot2},
date = {2026-07-14},
url = {https://www.datanovia.com/blog/ggplot-line-last-value-label},
langid = {fr}
}