PAYATHON 2026

ggplot2 折线图如何只标注数值变化点?

数据阿明

结论

先筛出 value 与上一行不同的数据,再将筛选结果传给 geom_text_repel()。这样会标注第一个数据点作为起始值,并标注之后每次发生变化的点。连续重复的值只会在该段的起点显示标签。

修改代码

保留原有的 ref 和 dt 数据准备代码。生成 dt 后添加筛选,并调整绘图代码:

library(dplyr)
library(ggplot2)
library(ggrepel)

# 保留每段连续相同 value 的第一个点。
# 第一行作为起始值保留,因为它没有前一个数据点可比较。
label_dt <- dt %>%
  mutate(prev_value = lag(value)) %>%
  filter(is.na(prev_value) | value != prev_value)

g <- ggplot(dt, aes(x = year, y = value)) +
  geom_line() +
  scale_y_continuous(
    breaks = ref$value,
    labels = ref$label
  ) +
  geom_text_repel(
    data = label_dt,
    aes(label = label)
  )

lag(value) 会取当前行前一行的值,filter() 则保留值不同的行。geom_text_repel() 用筛选后的数据绘制标签,geom_line() 仍用完整的 dt 绘制折线。

只标注变化后的点

如果不需要标注第一行作为起始值,可以只保留有前一行且值发生变化的数据:

label_dt <- dt %>%
  mutate(prev_value = lag(value)) %>%
  filter(!is.na(prev_value), value != prev_value)

注意事项

原代码直接将 dt$label 和 dt$value 用作 y 轴的 labels 和 breaks。由于 dt 中同一个 value 可能重复出现,而 y 轴刻度通常按唯一取值设置,使用 ref$value 和 ref$label 来设置刻度和值对应的文本更合适。

筛选时会按照 dt 当前的行顺序比较相邻值。如果数据还没有按时间排序,应先按 year 排序,再进行筛选。

备注:内容仅供参考。