科学图表装配Skill scientific-figure-assembly

这个技能用于帮助科研人员使用R语言装配多面板科学图表,添加标准面板标签(如A、B、C),并确保图形达到出版质量(300 DPI),适用于科学论文和期刊投稿。关键词:R语言,数据可视化,科学图表,面板装配,出版质量。

科研绘图 1 次安装 4 次浏览 更新于 3/12/2026

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等期刊准备图表

快速开始

告诉我:

  1. 图表对象:R图表对象(ggplot、森林图等)或PNG/JPG文件路径
  2. 布局:垂直(堆叠)、水平(并排)或网格(2x2、2x3等)
  3. 输出名称:最终图表的名称
  4. 标签:使用哪些面板标签(默认: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工作流程提示

  1. 全程在R中工作:在R中生成图表并装配以获得最佳结果
  2. 使用patchwork用于ggplot2:最简单语法(p1 / p2 用于垂直)
  3. 使用cowplot用于混合图表:适用于基础R和ggplot2
  4. 全局设置主题:使用theme_set(theme_minimal())以获得一致性
  5. 最后导出一次:创建所有图表,组合,然后导出(更快)
  6. 检查对齐:在cowplot中使用align = "v"axis = "l"
  7. 一致的尺寸:在主题中设置base_size以获得可读文本

通用提示

  1. 首先生成高质量输入:装配不会改善低质量源图表
  2. 系统标签:A-B-C从上到下或从左到右
  3. 在打印预览中检查:确保标签在最终打印尺寸可读
  4. 保存R脚本:保存R代码以复现性
  5. 版本控制图表:提交R脚本和最终PNG文件
  6. 在不同屏幕上测试:在笔记本电脑和打印页面上检查可读性

R包资源

当需要帮助时: