name: 科学图表装配 description: 使用R装配多面板科学图表,添加面板标签(A、B、C),达到出版质量(300 DPI)。适用于将单个图表组合成期刊就绪的图形。 allowed-tools: Bash(Rscript *), Write, Read
科学图表装配(基于R)
使用R包(patchwork、cowplot)创建出版就绪的多面板图表,带有专业的面板标签(A、B、C、D),分辨率300 DPI。
⚠️ 重要:此工作流程使用R进行图表装配。对于元分析项目,所有图表应在R中生成和装配。
何时使用
- 将多个图表合并为单个多面板图表以供发表
- 向现有图表添加面板标签(A、B、C)
- 确保图表满足期刊要求(最小300 DPI)
- 为手稿创建一致的图表布局
- 为Nature、Science、Cell、JAMA、Lancet等期刊准备图表
快速开始
告诉我:
- 图表对象:R图表对象(ggplot、森林图等)或PNG/JPG文件路径
- 布局:垂直(堆叠)、水平(并排)或网格(2x2、2x3等)
- 输出名称:最终图表的名称
- 标签:使用哪些面板标签(默认:A、B、C、D…)
我将使用patchwork或cowplot创建一个R脚本,以装配图表,并带有适当的间距和标签。
R包方法(推荐)
方法1:patchwork(用于ggplot2对象)
最简单的组合ggplot2对象的方法:
library(ggplot2)
library(patchwork)
# 创建或加载单个图表
p1 <- ggplot(data1, aes(x, y)) + geom_point() + ggtitle("A. 第一面板")
p2 <- ggplot(data2, aes(x, y)) + geom_line() + ggtitle("B. 第二面板")
p3 <- ggplot(data3, aes(x, y)) + geom_bar(stat="identity") + ggtitle("C. 第三面板")
# 垂直组合
combined <- p1 / p2 / p3
# 或水平组合
combined <- p1 | p2 | p3
# 或网格布局(2列)
combined <- (p1 | p2) / p3
# 以300 DPI导出
ggsave("figures/figure1_combined.png",
plot = combined,
width = 10, height = 12, dpi = 300)
方法2:cowplot(用于任何R图表)
更灵活,适用于基础R图表和ggplot2:
library(ggplot2)
library(cowplot)
# 创建单个图表
p1 <- ggplot(data1, aes(x, y)) + geom_point()
p2 <- ggplot(data2, aes(x, y)) + geom_line()
p3 <- ggplot(data3, aes(x, y)) + geom_bar(stat="identity")
# 组合并自动添加面板标签
combined <- plot_grid(
p1, p2, p3,
labels = c("A", "B", "C"),
label_size = 18,
ncol = 1, # 垂直堆叠
rel_heights = c(1, 1, 1) # 等高
)
# 导出
ggsave("figures/figure1_combined.png",
plot = combined,
width = 10, height = 12, dpi = 300)
遗留Python脚本模板(不推荐)
⚠️ 对于元分析项目,请使用上述R方法。
如果绝对需要Python处理现有PNG文件:
#!/usr/bin/env python3
"""遗留:从PNG文件装配多面板科学图表。"""
from PIL import Image, ImageDraw, ImageFont
from pathlib import Path
def add_panel_label(img, label, position='top-left',
font_size=80, offset=(40, 40),
bg_color='white', text_color='black',
border=True):
"""
向图像添加面板标签(A、B、C)。
参数:
img: PIL Image对象
label: 标签文本(例如,'A'、'B'、'C')
position: 'top-left'、'top-right'、'bottom-left'、'bottom-right'
font_size: 字体大小像素(80适用于3000px宽的图像)
offset: (x, y) 从角落的偏移像素
bg_color: 标签框的背景颜色
text_color: 标签文本颜色
border: 是否绘制标签框边框
"""
draw = ImageDraw.Draw(img)
# 尝试系统字体(macOS,然后Linux)
try:
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size)
except:
try:
font = ImageFont.truetype(
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", font_size
)
except:
font = ImageFont.load_default()
print(f"警告:使用默认字体用于标签 {label}")
# 计算标签位置
x, y = offset
if 'right' in position:
bbox = draw.textbbox((0, 0), label, font=font)
text_width = bbox[2] - bbox[0]
x = img.width - text_width - offset[0]
if 'bottom' in position:
bbox = draw.textbbox((0, 0), label, font=font)
text_height = bbox[3] - bbox[1]
y = img.height - text_height - offset[1]
# 绘制背景框
bbox = draw.textbbox((x, y), label, font=font)
padding = 10
draw.rectangle(
[bbox[0] - padding, bbox[1] - padding,
bbox[2] + padding, bbox[3] + padding],
fill=bg_color,
outline='black' if border else None,
width=2 if border else 0
)
# 绘制文本
draw.text((x, y), label, fill=text_color, font=font)
return img
def assemble_vertical(input_files, output_file, labels=None,
spacing=40, dpi=300):
"""
垂直堆叠图像并添加面板标签。
参数:
input_files: 输入图像路径列表
output_file: 输出图像路径
labels: 标签列表(默认:A、B、C、...)
spacing: 面板间垂直间距像素
dpi: 输出分辨率
"""
if labels is None:
labels = [chr(65 + i) for i in range(len(input_files))] # A、B、C、...
# 加载所有图像
images = [Image.open(f) for f in input_files]
# 添加标签
labeled = [add_panel_label(img, label)
for img, label in zip(images, labels)]
# 计算尺寸
max_width = max(img.width for img in labeled)
total_height = sum(img.height for img in labeled) + spacing * (len(labeled) - 1)
# 创建组合图像
combined = Image.new('RGB', (max_width, total_height), 'white')
# 粘贴图像
y_offset = 0
for img in labeled:
combined.paste(img, (0, y_offset))
y_offset += img.height + spacing
# 以指定DPI保存
combined.save(output_file, dpi=(dpi, dpi))
print(f"✅ 创建 {output_file}")
print(f" 尺寸:{combined.width}×{combined.height} px 在 {dpi} DPI")
return output_file
def assemble_horizontal(input_files, output_file, labels=None,
spacing=40, dpi=300):
"""水平堆叠图像并添加面板标签。"""
if labels is None:
labels = [chr(65 + i) for i in range(len(input_files))]
images = [Image.open(f) for f in input_files]
labeled = [add_panel_label(img, label)
for img, label in zip(images, labels)]
max_height = max(img.height for img in labeled)
total_width = sum(img.width for img in labeled) + spacing * (len(labeled) - 1)
combined = Image.new('RGB', (total_width, max_height), 'white')
x_offset = 0
for img in labeled:
combined.paste(img, (x_offset, 0))
x_offset += img.width + spacing
combined.save(output_file, dpi=(dpi, dpi))
print(f"✅ 创建 {output_file}")
print(f" 尺寸:{combined.width}×{combined.height} px 在 {dpi} DPI")
return output_file
def assemble_grid(input_files, output_file, rows, cols,
labels=None, spacing=40, dpi=300):
"""
在网格中排列图像并添加面板标签。
参数:
rows: 行数
cols: 列数
其他参数同assemble_vertical
"""
if labels is None:
labels = [chr(65 + i) for i in range(len(input_files))]
images = [Image.open(f) for f in input_files]
labeled = [add_panel_label(img, label)
for img, label in zip(images, labels)]
# 计算单元格尺寸(使用每行/列的最大值)
cell_width = max(img.width for img in labeled)
cell_height = max(img.height for img in labeled)
# 总尺寸
total_width = cell_width * cols + spacing * (cols - 1)
total_height = cell_height * rows + spacing * (rows - 1)
combined = Image.new('RGB', (total_width, total_height), 'white')
# 放置图像
for idx, img in enumerate(labeled):
if idx >= rows * cols:
break
row = idx // cols
col = idx % cols
x = col * (cell_width + spacing)
y = row * (cell_height + spacing)
combined.paste(img, (x, y))
combined.save(output_file, dpi=(dpi, dpi))
print(f"✅ 创建 {output_file}")
print(f" 尺寸:{combined.width}×{combined.height} px 在 {dpi} DPI")
return output_file
if __name__ == '__main__':
import sys
# 示例用法
if len(sys.argv) < 3:
print("用法:python assemble_figures.py <输出> <布局> <输入1> <输入2> ...")
print(" 布局:vertical、horizontal 或 grid:RxC(例如,grid:2x2)")
sys.exit(1)
output = sys.argv[1]
layout = sys.argv[2]
inputs = sys.argv[3:]
if layout == 'vertical':
assemble_vertical(inputs, output)
elif layout == 'horizontal':
assemble_horizontal(inputs, output)
elif layout.startswith('grid:'):
rows, cols = map(int, layout.split(':')[1].split('x'))
assemble_grid(inputs, output, rows, cols)
else:
print(f"未知布局:{layout}")
sys.exit(1)
常见布局
垂直(最常用)
将图表堆叠在彼此上方 - 适合展示进展或相关结果。
示例:三个森林图(pCR、EFS、OS)垂直堆叠
- 面板A:pCR森林图
- 面板B:EFS森林图
- 面板C:OS森林图
水平
将图表并排放置 - 适合比较。
示例:两个漏斗图展示发表偏倚
- 面板A:pCR漏斗图
- 面板B:EFS漏斗图
网格(2x2、2x3等)
在行和列中排列 - 适合系统比较。
示例:2x2网格的子组分析
- 面板A:年龄子组
- 面板B:性别子组
- 面板C:阶段子组
- 面板D:组织学子组
R工作流程(推荐)
完整示例:元分析森林图
#!/usr/bin/env Rscript
# assemble_forest_plots.R
# 将多个森林图组合成单个图表
library(meta)
library(metafor)
library(patchwork)
# 设置工作目录
setwd("/Users/htlin/meta-pipe/06_analysis")
# 加载提取数据
data <- read.csv("../05_extraction/extraction.csv")
# --- 创建单个森林图 ---
# 图表1:病理完全反应
res_pcr <- metabin(
event.e = events_pcr_ici,
n.e = total_ici,
event.c = events_pcr_control,
n.c = total_control,
data = data,
studlab = study_id,
sm = "RR",
method = "MH"
)
# 保存为ggplot兼容对象
p1 <- forest(res_pcr, layout = "RevMan5") +
ggtitle("A. 病理完全反应")
# 图表2:无事件生存
res_efs <- metagen(
TE = log_hr_efs,
seTE = se_log_hr_efs,
data = data,
studlab = study_id,
sm = "HR"
)
p2 <- forest(res_efs) +
ggtitle("B. 无事件生存")
# 图表3:总生存
res_os <- metagen(
TE = log_hr_os,
seTE = se_log_hr_os,
data = data,
studlab = study_id,
sm = "HR"
)
p3 <- forest(res_os) +
ggtitle("C. 总生存")
# --- 使用patchwork组合 ---
combined <- p1 / p2 / p3 +
plot_annotation(
title = "图1. ICI与对照的疗效结果",
theme = theme(plot.title = element_text(size = 16, face = "bold"))
)
# 以300 DPI导出
ggsave("../07_manuscript/figures/figure1_efficacy.png",
plot = combined,
width = 10,
height = 14,
dpi = 300,
bg = "white")
cat("✅ 创建 figure1_efficacy.png
")
cat(" 尺寸:3000×4200 px 在 300 DPI
")
使用cowplot进行更多控制
library(cowplot)
# 组合并明确面板标签和对齐
combined <- plot_grid(
p1, p2, p3,
labels = c("A", "B", "C"),
label_size = 18,
label_fontface = "bold",
ncol = 1,
align = "v", # 垂直对齐
axis = "l", # 对齐左轴
rel_heights = c(1, 1, 1)
)
# 添加总标题
title <- ggdraw() +
draw_label(
"图1. ICI与对照的疗效结果",
fontface = "bold",
size = 16,
x = 0.5,
hjust = 0.5
)
# 组合标题和图表
final <- plot_grid(
title,
combined,
ncol = 1,
rel_heights = c(0.1, 1)
)
# 导出
ggsave("../07_manuscript/figures/figure1_efficacy.png",
plot = final,
width = 10, height = 14, dpi = 300, bg = "white")
网格布局(2x2或2x3)
library(patchwork)
# 2x2网格
combined <- (p1 | p2) / (p3 | p4) +
plot_annotation(tag_levels = "A")
# 2x3网格
combined <- (p1 | p2 | p3) / (p4 | p5 | p6) +
plot_annotation(tag_levels = "A")
ggsave("figure_grid.png", width = 14, height = 10, dpi = 300)
Python工作流程(遗留 - 仅用于PNG文件)
⚠️ 仅在已有PNG文件且无法在R中重新生成时使用。
步骤1:验证输入文件
# 检查所有文件存在且为PNG/JPG
ls -lh path/to/plots/*.png
步骤2:创建装配脚本
使用此技能中提供的Python模板。
步骤3:运行装配
# 使用uv(推荐用于依赖管理)
uv run python assemble_figures.py Figure1_Efficacy.png vertical \
forest_plot_pCR.png \
forest_plot_EFS.png \
forest_plot_OS.png
# 或使用系统Python(需要PIL/Pillow)
python assemble_figures.py Figure1.png grid:2x2 \
plot1.png plot2.png plot3.png plot4.png
步骤4:验证输出
# 检查尺寸和文件大小
ls -lh Figure1_Efficacy.png
# 验证DPI(应显示300x300)
file Figure1_Efficacy.png
定制选项
字体大小调整
对于不同图像尺寸:
- 3000px宽图像:
font_size=80(默认) - 1500px宽图像:
font_size=40 - 6000px宽图像:
font_size=160
标签位置
position='top-left'(默认)position='top-right'position='bottom-left'position='bottom-right'
面板间间距
- 默认:
spacing=40像素 - 紧间距:
spacing=20 - 松间距:
spacing=80
标签样式
- 白色背景带黑色边框(默认,最佳可见性)
- 透明背景:
bg_color=None, border=False - 自定义颜色:
bg_color='#f0f0f0', text_color='#333333'
期刊要求
Nature、Science、Cell
- 分辨率:300-600 DPI
- 格式:TIFF或高质量PDF首选,PNG可接受
- 宽度:最终尺寸89mm(单列)或183mm(双列)
- 字体:Arial、Helvetica或类似无衬线字体
- 标签:粗体,最终尺寸8-10pt
Lancet、JAMA、NEJM
- 分辨率:300 DPI最小
- 格式:TIFF、EPS或PNG
- 宽度:适合列宽(通常3-4英寸)
- 标签:清晰,高对比度
- 灰度:必须在黑白中可读
质量检查清单
提交前:
- [ ] 所有图表至少300 DPI
- [ ] 面板标签(A、B、C)可见且顺序正确
- [ ] 标签不遮挡重要数据
- [ ] 所有面板正确对齐
- [ ] 面板间间距一致
- [ ] 文件大小合理(PNG小于10 MB)
- [ ] 图表在最终期刊尺寸打印时可读
- [ ] 颜色方案在灰度下工作(如要求)
常见问题与解决方案
问题:标签太小
解决方案:增加font_size参数(尝试加倍)
问题:标签遮挡数据
解决方案:更改position到不同角落或调整offset
问题:DPI太低 解决方案:首先以更高分辨率重新生成输入图表,然后重新装配
问题:间距不均匀 解决方案:在装配前裁剪输入图像以去除多余空白
问题:文件太大 解决方案:使用PNG压缩或转换为JPEG(可能损失质量)
示例用例
元分析图表(R)
# 图1:疗效结果(3个垂直面板)
library(patchwork)
combined <- p_pcr / p_efs / p_os +
plot_annotation(
title = "图1. 疗效结果",
tag_levels = "A"
)
ggsave("07_manuscript/figures/figure1_efficacy.png",
width = 10, height = 14, dpi = 300)
# 图2:安全性 + 偏倚(2个垂直面板)
combined <- p_safety / p_funnel +
plot_annotation(tag_levels = "A")
ggsave("07_manuscript/figures/figure2_safety.png",
width = 10, height = 10, dpi = 300)
# 图3:子组分析(2x2网格)
combined <- (p_age | p_sex) / (p_stage | p_histology) +
plot_annotation(
title = "图3. 子组分析",
tag_levels = "A"
)
ggsave("07_manuscript/figures/figure3_subgroups.png",
width = 14, height = 12, dpi = 300)
遗留Python示例(不推荐)
# 图1:疗效结果(3个垂直面板)
uv run python assemble.py Figure1_Efficacy.png vertical \
forest_plot_pCR.png \
forest_plot_EFS.png \
forest_plot_OS.png
# 图2:安全性 + 偏倚(2个垂直面板)
uv run python assemble.py Figure2_Safety.png vertical \
forest_plot_SAE.png \
funnel_plot_pCR.png
依赖
R包(推荐)
# 从CRAN安装
install.packages(c("patchwork", "cowplot", "ggplot2"))
# 用于元分析图表
install.packages(c("meta", "metafor"))
Python(遗留 - 仅用于PNG装配)
# 使用uv安装(如需要用于遗留工作流程)
uv add Pillow
# 或使用pip
pip install Pillow
输出示例
R输出
✅ 创建 figure1_efficacy.png
尺寸:3000×4200 px 在 300 DPI
大小:2.3 MB
输出文件将具有:
- 由patchwork/cowplot自动添加的专业面板标签(A、B、C)
- 面板间一致的间距
- 300 DPI分辨率适合发表
- 对齐的轴以便于比较
- 出版就绪的主题
Python输出(遗留)
✅ 创建 Figure1_Efficacy.png
尺寸:3000×6080 px 在 300 DPI
输出文件将具有:
- 专业面板标签(A、B、C)在左上角
- 面板间一致的间距
- 300 DPI分辨率适合发表
- 白色背景,标签带黑色边框以获得最大可见性
相关技能
/meta-manuscript-assembly- 完整元分析手稿准备/plot-publication- 创建单个出版就绪图表/figure-legends- 生成全面的图例
专业提示
R工作流程提示
- 全程在R中工作:在R中生成图表并装配以获得最佳结果
- 使用patchwork用于ggplot2:最简单语法(
p1 / p2用于垂直) - 使用cowplot用于混合图表:适用于基础R和ggplot2
- 全局设置主题:使用
theme_set(theme_minimal())以获得一致性 - 最后导出一次:创建所有图表,组合,然后导出(更快)
- 检查对齐:在cowplot中使用
align = "v"和axis = "l" - 一致的尺寸:在主题中设置
base_size以获得可读文本
通用提示
- 首先生成高质量输入:装配不会改善低质量源图表
- 系统标签:A-B-C从上到下或从左到右
- 在打印预览中检查:确保标签在最终打印尺寸可读
- 保存R脚本:保存R代码以复现性
- 版本控制图表:提交R脚本和最终PNG文件
- 在不同屏幕上测试:在笔记本电脑和打印页面上检查可读性
R包资源
当需要帮助时:
- CRAN:https://cran.r-project.org/(patchwork、cowplot文档)
- Tidyverse:https://www.tidyverse.org/(ggplot2参考)
- R-universe:https://r-universe.dev/search/(搜索所有R包)
- patchwork指南:https://patchwork.data-imaginist.com/
- cowplot指南:https://wilkelab.org/cowplot/