You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在ggplot2中调整指定facet行高并保持y轴相对刻度?

解决ggplot2分栏子图底行空白与y轴刻度对齐问题

问题描述

使用ggplot2的facet_grid()生成3列×2行共6幅子图,要求每列子图拥有独立的y轴范围。目前将每列子图保存为独立绘图后用patchwork拼接,但底行子图存在大量空白。需要修剪底行子图,仅显示y轴相关区域,关键是保持底行与上方子图的y轴相对刻度一致(例如顶行子图高度为10单位,底行子图相对高度为2单位,各图y轴范围按比例调整)。尝试拆分所有6幅子图拼接时,因facet标签保留问题导致相对图高难以计算,寻求更简便的实现方法。

解决方案

方法1:基于patchwork的拆分与比例布局

将每列的上下子图分开绘制,手动设置对应比例的y轴范围,再通过patchwork的plot_layout指定行高比例,最后拼接三列。这种方法能严格控制y轴刻度的相对一致性。

修改后的代码示例

library(tidyverse)
library(patchwork)

set.seed(42)

A1 <- sample(seq(0, 14.5, by = 0.1), 2500, replace=TRUE)
B1 <- sample(seq(0, 19.5, by = 0.1), 2500, replace=TRUE)
C1 <- sample(seq(0, 29.5, by = 0.1), 2500, replace=TRUE)

A2 <- sample(seq(0, 2.9, by = 0.01), 2500, replace=TRUE)
B2 <- sample(seq(0, 3.9, by = 0.01), 2500, replace=TRUE)
C2 <- sample(seq(0, 4.9, by = 0.01), 2500, replace=TRUE)

df <- data.frame(
  Position = rep(seq(1:2500), times = 6),
  Signal = c(A1, A2, B1, B2, C1, C2),
  Sample = factor(rep(c("A", "B", "C"), each = 5000), levels = c("A", "B", "C")),
  Group = factor(rep(c("Treatment", "Control"), each = 2500, times = 3), levels = c("Treatment", "Control"))
)

fillscale <- c("#0496A3", "#7D3B49", "#869D53")

# A列:顶行+底行,高度比10:2
PlotA_top <- ggplot(df %>% filter(Sample == "A", Group == "Treatment")) +
  geom_area(aes(x=Position, y=Signal, fill=Sample), col=NA, linewidth=0) +
  scale_fill_manual(values=fillscale[[1]]) +
  scale_x_continuous(breaks = c(1, 1000, 1500, 2500),
                     labels = c("label 1", "ON", "OFF", "label 2")) +
  ylim(0,15) +
  labs(y="Signal", x="") +
  theme_bw() +
  theme(
    legend.position = "none",
    axis.title.x = element_blank(),
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    axis.title.y = element_text(size = 10),
    axis.text.y = element_text(size = 10),
    plot.margin = margin(t = 0.194, r = 0.40, b = 0, l = 0.194, unit = "cm")
  ) +
  ggtitle("A")

PlotA_bottom <- ggplot(df %>% filter(Sample == "A", Group == "Control")) +
  geom_area(aes(x=Position, y=Signal, fill=Sample), col=NA, linewidth=0) +
  scale_fill_manual(values=fillscale[[1]]) +
  scale_x_continuous(breaks = c(1, 1000, 1500, 2500),
                     labels = c("label 1", "ON", "OFF", "label 2")) +
  ylim(0,3) + # 对应顶行0-15的1/5比例
  labs(y="", x="") +
  theme_bw() +
  theme(
    legend.position = "none",
    axis.title.y = element_blank(),
    axis.text.y = element_text(size = 10),
    axis.text.x = element_text(size = 10),
    plot.margin = margin(t = 0, r = 0.40, b = 0.194, l = 0.194, unit = "cm")
  )

col_A <- PlotA_top / PlotA_bottom + plot_layout(heights = c(10, 2))

# B列:顶行+底行,高度比10:2
PlotB_top <- ggplot(df %>% filter(Sample == "B", Group == "Treatment")) +
  geom_area(aes(x=Position, y=Signal, fill=Sample), col=NA, linewidth=0) +
  scale_fill_manual(values=fillscale[[2]]) +
  scale_x_continuous(breaks = c(1, 1000, 1500, 2500),
                     labels = c("label 1", "ON", "OFF", "label 2")) +
  ylim(0,20) +
  labs(y="", x="") +
  theme_bw() +
  theme(
    legend.position = "none",
    axis.title.x = element_blank(),
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    axis.title.y = element_blank(),
    axis.text.y = element_text(size = 10),
    plot.margin = margin(t = 0.194, r = 0.40, b = 0, l = 0, unit = "cm")
  ) +
  ggtitle("B")

PlotB_bottom <- ggplot(df %>% filter(Sample == "B", Group == "Control")) +
  geom_area(aes(x=Position, y=Signal, fill=Sample), col=NA, linewidth=0) +
  scale_fill_manual(values=fillscale[[2]]) +
  scale_x_continuous(breaks = c(1, 1000, 1500, 2500),
                     labels = c("label 1", "ON", "OFF", "label 2")) +
  ylim(0,4) + # 对应顶行0-20的1/5比例
  labs(y="", x="") +
  theme_bw() +
  theme(
    legend.position = "none",
    axis.title.y = element_blank(),
    axis.text.y = element_text(size = 10),
    axis.text.x = element_text(size = 10),
    plot.margin = margin(t = 0, r = 0.40, b = 0.194, l = 0, unit = "cm")
  )

col_B <- PlotB_top / PlotB_bottom + plot_layout(heights = c(10, 2))

# C列:顶行+底行,高度比10:2
PlotC_top <- ggplot(df %>% filter(Sample == "C", Group == "Treatment")) +
  geom_area(aes(x=Position, y=Signal, fill=Sample), col=NA, linewidth=0) +
  scale_fill_manual(values=fillscale[[3]]) +
  scale_x_continuous(breaks = c(1, 1000, 1500, 2500),
                     labels = c("label 1", "ON", "OFF", "label 2")) +
  ylim(0,30) +
  labs(y="", x="") +
  theme_bw() +
  theme(
    legend.position = "none",
    axis.title.x = element_blank(),
    axis.text.x = element_blank(),
    axis.ticks.x = element_blank(),
    axis.title.y = element_blank(),
    axis.text.y = element_text(size = 10),
    plot.margin = margin(t = 0.194, r = 0.194, b = 0, l = 0, unit = "cm")
  ) +
  ggtitle("C")

PlotC_bottom <- ggplot(df %>% filter(Sample == "C", Group == "Control")) +
  geom_area(aes(x=Position, y=Signal, fill=Sample), col=NA, linewidth=0) +
  scale_fill_manual(values=fillscale[[3]]) +
  scale_x_continuous(breaks = c(1, 1000, 1500, 2500),
                     labels = c("label 1", "ON", "OFF", "label 2")) +
  ylim(0,6) + # 对应顶行0-30的1/5比例
  labs(y="", x="") +
  theme_bw() +
  theme(
    legend.position = "none",
    axis.title.y = element_blank(),
    axis.text.y = element_text(size = 10),
    axis.text.x = element_text(size = 10),
    plot.margin = margin(t = 0, r = 0.194, b = 0.194, l = 0, unit = "cm")
  )

col_C <- PlotC_top / PlotC_bottom + plot_layout(heights = c(10, 2))

# 拼接三列
combined_plot <- col_A | col_B | col_C

ggsave("~/Desktop/CombinedPlot_Fixed.pdf", combined_plot, device = "pdf", width = 7.5, height = 2.6)

方法2:使用ggh4x包的facet_grid2(更简便)

ggh4x扩展包的facet_grid2()支持设置行高比例,同时允许每列独立y轴,无需拆分子图即可实现需求。

代码示例

library(tidyverse)
library(ggh4x) # 先执行 install.packages("ggh4x") 安装包

set.seed(42)

# 数据部分与原代码一致
A1 <- sample(seq(0, 14.5, by = 0.1), 2500, replace=TRUE)
B1 <- sample(seq(0, 19.5, by = 0.1), 2500, replace=TRUE)
C1 <- sample(seq(0, 29.5, by = 0.1), 2500, replace=TRUE)

A2 <- sample(seq(0, 2.9, by = 0.01), 2500, replace=TRUE)
B2 <- sample(seq(0, 3.9, by = 0.01), 2500, replace=TRUE)
C2 <- sample(seq(0, 4.9, by = 0.01), 2500, replace=TRUE)

df <- data.frame(
  Position = rep(seq(1:2500), times = 6),
  Signal = c(A1, A2, B1, B2, C1, C2),
  Sample = factor(rep(c("A", "B", "C"), each = 5000), levels = c("A", "B", "C")),
  Group = factor(rep(c("Treatment", "Control"), each = 2500, times = 3), levels = c("Treatment", "Control"))
)

fillscale <- c("#0496A3", "#7D3B49", "#869D53")

ggplot(df) +
  geom_area(aes(x=Position, y=Signal, fill=Sample), col=NA, linewidth=0) +
  facet_grid2(Group~Sample, 
              scales = "free_y", # 每列独立y轴
              row_heights = c(10, 2)) # 设置顶行:底行高度比为10:2
  scale_fill_manual(values=fillscale) +
  scale_x_continuous(breaks = c(1, 1000, 1500, 2500),
                     labels = c("label 1", "ON", "OFF", "label 2")) +
  labs(y="Signal", x="") +
  theme_bw() +
  theme(
    legend.position = "none",
    axis.title.y = element_text(size = 10),
    axis.text.x = element_text(size = 10),
    axis.text.y = element_text(size = 10),
    strip.text.y = element_blank(),
    strip.background.y = element_blank()
  )

ggsave("~/Desktop/CombinedPlot_ggh4x.pdf", device = "pdf", width = 7.5, height = 2.6)

关键说明

  • 方法1通过手动拆分和比例设置,能完全控制y轴刻度的相对一致性,适合对精度要求高的场景;
  • 方法2使用ggh4x简化了代码,自动处理行高和独立轴,若无需严格固定比例,可直接让scales="free_y"自动适配数据范围,减少手动调整。

内容的提问来源于stack exchange,提问作者JVGen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 11:43:10