如何调整 ggplot2 森林图的分面标签间距和顶部注释位置?
可以将分面标签改为 plotmath 表达式,用 ~ 控制 APOE 与后续文字的间距。三处顶部注释统一使用 y = Inf,配合负值 vjust 和 clip = "off",再在分面标签下方预留空间,就不用根据蛋白数量反复计算注释高度。
输入中没有实际效果图和包版本,无法判断文字重叠是否与特定字体或富文本渲染环境有关。下面的修改避开了 HTML 空格实体,具体留白仍需根据最终导出的图片调整。
为什么原来的注释位置不一致?
原代码给 P trend 设置的是:
y = length(levels(dat_sub$Assay)) + 1.2
另外两处使用的是:
y = length(levels(dat_sub$Assay)) + 0.3
这两个值本来就不在同一高度。此外,离散 y 轴受类别顺序、未使用的 factor levels 和 expand 影响,类别数量加上一个偏移量不一定等于面板顶部。
coord_cartesian(clip = "off") 允许文字画出面板边界,但不会自动为文字腾出空间。仅增加 plot.margin 也不能直接拉开分面标签与绘图区之间的距离。
1. 使用 plotmath 设置分面标签
在原函数的数据预处理部分,将 Carrier_comparison 的 recode() 替换为:
Carrier_comparison = recode(
Carrier_comparison,
"e2vse3" = "italic(APOE)~'ε2 effects'",
"e4vse3" = "italic(APOE)~'ε4 effects'"
)
然后修改 facet_grid():
facet_grid(
. ~ Carrier_comparison,
scales = "free",
space = "free",
labeller = label_parsed
)
其中,italic(APOE) 设置斜体,~ 提供表达式间距,带引号的 ε2 effects 和 ε4 effects 保持普通文字形式。这个方案不需要   或 element_markdown()。
在原来的 theme() 中,将整个 strip.text 设置替换为:
strip.text = element_text(
size = 16,
colour = "black",
hjust = 0.5,
vjust = 0.5,
margin = margin(t = 12, b = 36, unit = "pt")
),
strip.background = element_blank()
b = 36 在分面标题下方预留空间,供面板上方的注释使用。这是一个布局起点,仍需根据字体和图片尺寸调整。原来的 vjust = 3.5 也应移除,改用正常的对齐值和明确的边距。
如果其他分面变量也需要富文本,可以将上述设置写为 strip.text.x,只调整当前列分面的标签。
2. 用统一的顶部锚点替换三个 annotate()
删除原有的三个顶部 annotate(),在同一位置换成以下图层。变量 max_abs_limit 沿用原函数中的定义:
annotate(
"text",
x = c(-0.5, 0.35) * max_abs_limit,
y = Inf,
label = c("Lower levels", "Higher levels"),
hjust = 0.5,
vjust = -0.8,
size = 5,
family = "Arial"
) +
annotate(
"text",
x = 0.85 * max_abs_limit,
y = Inf,
label = "bolditalic('P')*bold(' trend')",
parse = TRUE,
hjust = 0.5,
vjust = -0.8,
size = 5,
family = "Arial"
) +
coord_cartesian(clip = "off")
y = Inf 将注释锚定在各自面板的上边界。这里的负值 vjust 会将文字向上移出面板;数值越负,通常上移越多。
这些注释图层没有指定 Carrier_comparison,因此会出现在每个分面中,适合给两个比较面板添加相同的说明。
示例将 Higher levels 的 x 坐标从 0.5 调到 0.35,为右侧的 P trend 留出更多横向空间。P trend 使用 x = 0.85 * max_abs_limit 和居中对齐,与原代码中 P 值列的中心一致。
普通文字和 plotmath 表达式的字形高度可能不同。相同的 y 和 vjust 提供了统一的定位规则;如果导出后仍有轻微视觉偏差,可以只微调 P trend 的 vjust。
3. 让每个趋势 P 值只绘制一次
提供的数据中,同一个 Ancestry、Carrier_comparison、Assay 的趋势 P 值在三个年龄组中重复出现。原来的 geom_text() 会在同一位置重复绘制这些标签。
在循环中得到 dat_sub 后,增加:
trend_labels <- dat_sub %>%
distinct(
Carrier_comparison,
Assay,
QM_P_trend_uncorrected_label
)
再将原来的 P 值 geom_text() 替换为:
geom_text(
data = trend_labels,
aes(
x = 0.85 * max_abs_limit,
y = Assay,
label = QM_P_trend_uncorrected_label
),
inherit.aes = FALSE,
hjust = 0.5,
vjust = 0.5,
family = "Arial",
size = 4,
colour = "black"
)
这种去重方式适用于题目中每个比较和蛋白只有一个趋势 P 值的情况。如果实际数据中同一组合存在不同的趋势检验结果,应先明确需要展示哪一个,不能仅靠排版将它们合并。
4. 导出时检查留白和字体
可以先查看单个 ancestry 的图,再用 cowplot::plot_grid() 合并,这样比较容易发现布局问题。最终仍应检查 ggsave() 导出的文件,因为面板宽度、字体和输出尺寸都会影响文字间距。
如果顶部注释与分面标题相碰,优先增大 strip.text 的底部边距;如果注释离面板太近,再将 vjust 调得更负。若水平方向拥挤,可以调整注释的 x 坐标或增加导出宽度。单独增加 plot.margin 不能解决同一行文字之间的横向重叠。
原代码还有一处独立的 P 值格式问题:应先比较数值,再格式化显示。将:
sprintf("%.3f", QM_P_trend_uncorrected) < 0.001
改为:
QM_P_trend_uncorrected < 0.001
否则会先发生舍入,并让字符结果参与比较,可能使接近阈值的 P 值显示不符合预期。
备注:内容仅供参考。
