pythonic生物人

万字详解!时隔7年,ggplot2迎来v4.0.0大版本更新!

R ggplot2优雅、自由,你几乎可以用它绘制任何publication-quality plots👉可视化三剑客:ggplot2 + Matplotlib + Seaborn

《保姆级R可视化教程》部分案例
《保姆级R可视化教程》部分案例

为持续满足用户日益增长的可视化需求,ggplot2也保持着活跃的版本迭代节奏。自2018-07-03 ggplot2 v3.0.0大版本更新后,历经近20个小版本更新,时隔7年,2025-09-11终于迎来ggplot2 v4.0.0大版本更新。

ggplot2过去7年,版本更新时间轴
ggplot2过去7年,版本更新时间轴

下面学习ggplot2 v4.0.0重大改变,无论你是基于ggplot2进行二次开发,还是仅仅调用ggplot2绘图,都有参考价值。

1 ggplot2底层大升级

ggplot2 v4.0.0换了一个更强大、更严谨的新底层—S7 (替代之前S3、S4的不足):

  • ✅对普通ggplot2画图用户,几乎无感,以前怎么写代码现在还怎么写。但是,S7会“more strictly enforcing types for certain arguments”,例如,如果你输错了参数类型(比如该填数字的地方你填了文字),它会立刻、明确地报错,这帮你快速定位bug。
element_text(hjust = "foo")
#> Error: <ggplot2::element_text> object properties are invalid:
#> - @hjust must be <NULL>, <integer>, or <double>, not <character>
  • ✅对ggplot2扩展包开发者,功能更强大了
Image
  • ✅平稳过渡,团队保证了现有代码(基于旧S3系统开发的众多扩展包)不会瘫痪,并会引导大家逐步转向S7新标准。

2 ggplot2主题(theme)升级

theme介绍见教程👉《保姆级R可视化教程》来了!章节“11 详解ggplot2—绘图主题(theme)”:

Image
  • ✅新增ink / paper / accent三个参数,分别控制整张图的前景色、背景色和强调色。
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  geom_smooth(method = "lm", formula = y ~ x) +
# ink / paper / accent三个参数,分别控制整张图的前景色、背景色和强调色
  theme_gray(paper = "cornsilk", ink = "navy", accent = "tomato")
Image
  • ✅新增theme(geom)参数用于集中管理geoms的默认样式;通过element_geom()函数可配置该参数中的属性;from_theme()函数允许在 aes()内部直接调用主题中定义的默认值。
ggplot(mpg, aes(class, displ)) +
  geom_boxplot(aes(colour = from_theme(accent))) +
  theme(
    geom = element_geom(accent = "tomato", paper = "cornsilk")
  )
Image
  • ✅element_geom()的另一个重要设计区别在于对“线”的用途区分,
ggplot(faithful, aes(waiting)) +
  geom_histogram(bins = 30, colour = "black") +
  geom_freqpoly(bins = 30) +
  theme(geom = element_geom(
    bordertype = "dashed",
    borderwidth = 0.2,
    linewidth = 2,
    linetype = "solid"
  ))
Image
  • ✅配色方案(palettes)现在也可通过theme统一管理。相关主题设置遵循 palette.{美学属性}.{数据类型}的命名格式(如 palette.colour.continuous),其中数据类型分为离散(discrete)与连续(continuous)。这一改进使配色方案能够与主题的其他样式设置协同工作,实现整体视觉风格的一致性。
ggplot(mpg, aes(displ, hwy, shape = drv, colour = cty)) +
  geom_point() +
  theme(
    palette.colour.continuous = c("chartreuse", "forestgreen"),
    palette.shape.discrete = c("triangle", "triangle open", "triangle down open")
  )
Image
  • ✅新增theme设置快捷函数,为提升主题代码的可读性和编写效率。这些函数通过自动添加对应前缀,将参数传递至 theme()中,例如:
theme_sub_axis()
theme_sub_axis_x()
theme_sub_axis_bottom()
theme_sub_axis_top()
theme_sub_axis_y()
theme_sub_axis_left()
theme_sub_axis_right()
theme_sub_panel()
theme_sub_plot()
theme_sub_strip()
  • theme间距与边距设置引入了spacing(间距)和 margins(边距)作为新的根元素
p <- ggplot(penguins, aes(bill_dep, bill_len, colour = species)) +
  geom_point(na.rm = TRUE)

p + theme(
  spacing = unit(1, "cm"),  #spacing
  margins = margin_auto(1, unit = "cm"), # margins
  axis.ticks.length.x = rel(2)
)
Image
  • ✅新增了通过 panel.widths和 panel.heights参数精确控制面板尺寸的功能,与 ggsave(width, height)(控制整个图形输出尺寸)形成互补。
p1 <- p + facet_grid(~ island) +
  labs(title = "Separate units (per panel)") +
# Using the new shortcut for panels
  theme_sub_panel(
    widths = unit(c(2, 3, 4), "cm"), #widths
    heights = unit(3, "cm") # heights
  )

p2 <- p + facet_grid(~ island) +
  labs(title = "Single unit (all panels)") +
  theme_sub_panel(
    widths = unit(9, "cm"), #widths
    heights = unit(3, "cm") # heights
  )

p1 / p2
Image

更多关于Themes的更新,

Image

3 ggplot2文本(labels)升级

labels介绍见教程👉《保姆级R可视化教程》来了!章节“12 详解ggplot2—标题、文本(title、labels)”:

  • ✅变量labels自动识别机制,多个主流R包(如gt、Hmisc、labelled等)长期采用非正式约定,用变量的label属性存储人类可读的标签。如今ggplot2正式加入这一生态,自动将变量的label属性作为默认图形标签使用。这意味着:若数据框的某列包含label属性(如:attr(df$age, "label") <- "患者年龄";绘图时无需手动指定,ggplot2会自动将其作为轴标题或图例标题。
df <- penguins

# Manually set label attributes.
# Other packages may offer better tooling than this.
attr(df$species, "label") <- "Penguin Species"
attr(df$bill_dep, "label") <- "Bill depth (mm)"
attr(df$bill_len, "label") <- "Bill length (mm)"
attr(df$body_mass, "label") <- "Body mass (g)"

ggplot(df, aes(bill_dep, bill_len, colour = sqrt(body_mass))) +
  geom_point(na.rm = TRUE)
Image
  • ✅ 新增labs(dictionary)参数极大地提升了为变量设置标签的效率和准确性,通过setNames() or dplyr::pull()设置。
dict <- tibble::tribble(
  ~var,    ~label,
"species",  "Penguin Species",
"bill_dep", "Bill depth (mm)",
"bill_len", "Bill length (mm)",
"body_mass", "Body mass (g)"
)

ggplot(penguins, aes(bill_dep, bill_len, colour = body_mass)) +
  geom_point(na.rm = TRUE) +
# Or:
# labs(dictionary = dplyr::pull(dict, label, name = var)) #d通过plyr::pull设置
  labs(dictionary = setNames(dict$label, dict$var)) #通过setNames设置
Image

4 ggplot2分面(facet)升级

facet介绍见教程👉《保姆级R可视化教程》来了!章节“9 详解ggplot2—分面(facet)”:

Image
  • ✅facet_wrap()现已简化为单参数dir控制8种布局。新参数采用双字母代码组合(t: 顶, r: 右, b: 底, l: 左),同时定义起始位置和填充方向:首字母决定起始角落(如 br和 rb均从右下角开始);字母顺序决定填充方向,首字母为 b(底部)→ 从下到上填充;首字母为 r(右侧)→ 从右到左填充。
p <- ggplot(mpg, aes(displ, hwy)) +
  geom_point()

p1 <- p + 
  facet_wrap(~ vctrs::vec_group_id(class), dir = "br") +
  labs(title = "dir = 'br'")

p2 <- p +
  facet_wrap(~ vctrs::vec_group_id(class), dir = "rb") +
  labs(title = "dir = 'rb'")

p1 | p2
Image
8种布局选项对应参数:
8种布局选项对应参数:
  • ✅facet_wrap()函数本次新增了space参数,用于在特定条件下根据数据范围按比例分配面板空间。该功能原为 facet_grid()独有,但由于 facet_wrap()不共享坐标轴,一直未能实现。现在发现了一种特殊场景可支持此功能:

当分面只有一行时:所有面板共享同一 y 轴范围,可按 x 轴数据比例调整列宽度;

当分面只有一列时:所有面板共享同一 x 轴范围,可按 y 轴数据比例调整行高度。

ggplot(penguins, aes(bill_dep, bill_len, colour = species)) +
  geom_point(na.rm = TRUE) +
  facet_wrap(~ island, scales = "free_x", space = "free_x")
Image
  • ✅新增 layer(layout)参数,用于指导facet系统处理数据的方式,该参数支持以下模式:
Image
ggplot(penguins, aes(bill_dep, bill_len)) +
# Repeat within every row
  geom_point(na.rm = TRUE, colour = "grey", layout = "fixed_rows") +
# Use facetting variables (default)
  geom_point(na.rm = TRUE, layout = NULL) +
# Pick particular panel
  annotate(
"text", x = I(0.5), y = I(0.5),
    label = "Panel 6", layout = 6
  ) +
  facet_grid(island ~ species)
Image

5 离散位置标度(Discrete scales)升级

scales介绍见教程👉《保姆级R可视化教程》来了!章节“10 详解ggplot2—数据标度(scale)”:

Image
  • ✅离散位置标度现支持通过 palette参数自定义映射规则,打破原有固定整数序列模式,可自由调整轴上的分组间距与排布结构。例如,
ggplot(ToothGrowth, aes(interaction(dose, supp, sep = "\n"), len)) +
  geom_boxplot() +
  scale_x_discrete(
    palette = scales::pal_manual(c(1:3, 5:7))
  )
Image

✅新增continuous.limits参数,允许直接声明离散标度的连续轴范围,轻松实现多图表或分面板的坐标轴同步,确保不同水平数量的图表中元素(如条形宽度)保持一致。

p1 <- ggplot(mpg, aes(class)) +
  geom_bar() +
  facet_wrap(~ drv, ncol = 1, scales = "free_x")

p2 <- p1 + scale_x_discrete(continuous.limits = c(1, 5))

(p1 + labs(title = "Free limits")) | 
(p2 + labs(title = "Fixed limits"))
Image
  • ✅使用次要分隔线在视觉上区分层级,以此作为主要分隔线(如中心线)的替代方案。
p1 <- ggplot(mpg, aes(drv, hwy, colour = factor(year))) +
  geom_point(position = "jitterdodge") +
  guides(colour = "none") +
  scale_x_discrete(
    minor_breaks = scales::breaks_width(1, offset = 0.5),
# To show that the minor axis ticks take on these values
    guide = guide_axis(minor.ticks = TRUE)
  )

p2 <- p1 + 
  theme(panel.grid.major.x = element_blank()) +
  theme_sub_axis_bottom(ticks = element_blank(), minor.ticks = element_line())

p1 | p2
Image
  • ✅现在可以通过dup_axis()函数来添加次要轴,其主要作用是提供标注信息(例如显示各组的统计摘要),而非用于对齐不同量纲的数据集
ggplot(mpg, aes(class, cty)) +
  geom_boxplot() +
  scale_x_discrete(
    sec.axis = dup_axis(
      name = "Counts",
# You can use numeric input for breaks
      breaks = seq_len(length(unique(mpg$class))),
# Watch out for the order of `table()` and your levels!
      labels = paste0("n = ", table(mpg$class))
    )
  )
Image

6 位置(position)升级

position介绍见教程👉《保姆级R可视化教程》来了!章节“7 详解ggplot2—位置调整(position)”:

Image
  • ✅位置调整函数(如 position_nudge())首次可以声明自己的美学参数(aesthetics)。你可以在 aes()内部通过映射数据来动态控制位置调整的参数,而不再仅限于使用静态的数值参数。
# Taken from:
# https://ourworldindata.org/grapher/share-electricity-coal?tab=table&tableFilter=continents
coal <- tibble::tribble(
  ~continent,  ~pct_1985, ~pct_2024,
"Africa",        53.87, 24.68,
"Asia",          32.60, 51.19,
"Europe",        32.84, 12.91,
"North America", 48.93, 13.79,
"South America",  2.91,  3.31,
"Oceania",       58.75, 39.26
) |>
  dplyr::mutate(pp_difference = pct_2024 - pct_1985)

ggplot(coal, aes(pp_difference, continent)) +
  geom_col() +
  geom_text(
    aes(nudge_x = sign(pp_difference) * 3, 
        label = pp_difference)
  ) +
  labs(x = "Change in electricity generated by coal (pp)")
Image
sports <- c("water polo", "swimming", "gymnastics", "field", "netball")
p <- ggridges::Aus_athletes |>
  dplyr::filter(sport %in% sports) |>
  ggplot(aes(height, sport, fill = sex)) +
  geom_boxplot(position = position_dodge(preserve = "single")) #当某个运动项目下只有单一性别的数据时(例如水球可能只有男性运动员,无板篮球只有女性运动员),其对应的箱线图宽度不会异常变宽,而是与其他包含两种性别的箱线图保持一致,从而维持图形的美观和一致性

p
Image
p + aes(order = sex)
Image

通过将美学映射应用于位置调整,所有正确的分组都能被放置在恰当的位置,从而带来一种心灵上的舒缓与秩序感。


7 统计变换方法(stat)升级

stat介绍见教程👉《保姆级R可视化教程》来了!章节“6 详解ggplot2—统计变换方法(stat)”:

Image
  • ✅新增stat_manual(),提供了一种创建 ggplot2 扩展的新方式。传统方法通常需要定义正式的类(class)和构造函数(constructor),这有一定的复杂性。而 stat_manual()旨在规避这种复杂性,让扩展变得更灵活和直接。
make_centroids <- function(df) {
  transform(
    df,
    xend = mean(x, na.rm = TRUE),
    yend = mean(y, na.rm = TRUE)
  )
}

make_hull <- function(df) {
  df <- df[complete.cases(df), , drop = FALSE]
  hull <- chull(df$x, df$y)
  df[hull, , drop = FALSE]
}

ggplot(penguins, aes(bill_len, bill_dep, colour = species)) + 
  geom_point(na.rm = TRUE) + 
# As a stat, provide a geom
  stat_manual(
    geom = "segment", # function creates new xend/yend for segment
    fun = make_centroids,
    linewidth = 0.2,
    na.rm = TRUE
  ) +
# As a geom, provide the stat
  geom_polygon(
    stat = "manual",
    fun = make_hull,
    fill = NA,
    linetype = "dotted"
  )
Image
  • ✅新增stat_connect()的stat方法,专门用于以阶梯状方式(stairstep fashion)连接观测点。它的设计目的是将连接逻辑(如何画线)与几何表现(画成什么样子)分离开来
eco <- economics |>
  dplyr::mutate(year = lubridate::year(date)) |>
  dplyr::summarise(
    min = min(unemploy),
    max = max(unemploy),
    mid = median(unemploy),
    .by = year
  )

ggplot(eco, aes(year, y = mid, ymin = min, ymax = max)) +
  geom_line(stat = "connect") +
  geom_ribbon(stat = "connect", alpha = 0.4)
Image
  • ✅stat_connect()不仅限于生成阶梯状连接,其核心优势在于允许通过自定义的 2 列数值矩阵来定义任意连接模式,从而极大地扩展了其应用场景和创意可能性。
x <- seq(0, 1, length.out = 20)[-1]
smooth <- cbind(x, scales::rescale(plogis(x, location = 0.5, scale = 0.1)))
zigzag <- cbind(c(0.4, 0.6, 1), c(0.75, 0.25, 1))

ggplot(head(eco, 10), aes(year, y = mid)) +
  geom_point() +
  stat_connect(aes(colour = "smooth"), connection = smooth) +
  stat_connect(aes(colour = "zigzag"), connection = zigzag)
Image

8 坐标系(coord)升级

coord介绍见教程👉《保姆级R可视化教程》来了!章节“8 详解ggplot2—坐标系统(coord)”:

Image
  • ✅坐标系(coord)现在新增了 reverse参数,这为翻转图表方向提供了一种更直接和通用的方法。
world <- sf::st_as_sf(maps::map('world', plot = FALSE, fill = TRUE))

ggplot(world) +
  geom_sf() +
  coord_sf(reverse = "y")
Image

coord_radial(reverse = )接受的参数不是常见的 "x" 或 "y" 方向,而是 "none"(不反转)、"theta"(反转角度方向)、"r"(反转半径方向)以及 "thetar"(同时反转角度和半径方向)。

Image

9 Styling升级

  • ✅现在用户还可以对geom_boxplot()的每个组成部分进行独立样式控制。新增的定制化支持涵盖:中位数线(median line)、箱体(box)、须线(whiskers)以及箱须连接线(staples)。
Image
  • ✅geom_violin()分位数计算优化,现在分位数直接基于原始数据计算,不再使用密度估计数据,结果更准确。系统默认会算出25%、50%、75%三个分位数,但默认不显示出来。如果你需要显示分位线,可以手动设置线型参数。和箱线图类似,现在可以用 quantile.colour、quantile.linetype 等参数来自定义分位线的颜色、线型和粗细。
ggplot(mpg, aes(class, hwy, fill = class)) +
  geom_violin(
    quantiles = c(0.1, 0.9),
    quantile.linetype = 1
  ) +
  guides(fill = "none")
Image
  • ✅标签图层样式升级,geom_label()新增 linetype和 linewidth美学属性(支持数据映射),其中 linewidth替代原有 label.size参数;同时新增 border.colour和 text.colour参数,可分别独立设置边框与文字颜色(优先级高于全局 colour设置)。
ggplot(mtcars) +
  aes(
    wt, mpg,
    label = rownames(mtcars),
    colour = factor(cyl),
    linetype = factor(vs),
    linewidth = factor(am)
  ) +
  geom_label(text.colour = "black") +
  scale_linewidth_manual(values = c(0.3, 0.6))
Image
  • ✅ggplot2的geom_area()和geom_ribbon()现在支持在同一个图形分组内使用渐变色填充了。这意味着你可以在一个面积区域或带状区域内呈现颜色渐变效果。要使用这个功能,需要R版本在4.1.0以上,并且使用兼容的图形设备。
ggplot(economics, aes(date, unemploy)) +
  geom_area(aes(fill = uempmed))
Image

https://ggplot2.tidyverse.org/index.html


推荐👉:《保姆级R可视化教程》来了!,数百张图,旨在引导如何系统学习R语言可视化,部分展示如下:
图片
图片
图片
复现效果图-abcd图❤️
复现效果图-b图❤️
图片
❤️复现效果图-ABCD图❤️
图片
❤️复现效果图-bcd图❤️
图片
图片
❤️复现效果图-b图❤️
图片
图片
图片
图片
图片
图片
图片
图片
图片
图片

图片

图片
图片
图片

(加入学习,收费,备注:299)

图片