scientific-figure-assembly について
論文用の複数パネル図を作成する研究者向けのスキルです。R の patchwork または cowplot で図をまとめ、パネルラベルを付ける方法を案内し、整った配置と 300 DPI 以上の出力を重視します。既存の PNG/JPG 向けには旧式の Python 手順もあります。
主な機能
- 01複数の図をパネル形式に配置
- 02A、B、C などのラベルを追加
- 03縦、横、グリッドのレイアウトに対応
- 04patchwork または cowplot による R での組み立て
- 05300 DPI 以上での出力準備
活用シーン
- 01メタ分析のフォレストプロットをまとめる
- 02複数の結果図を並べて比較する
- 03学術誌への投稿図を整えてラベル付けする
- 04既存の PNG/JPG を複数パネル図に組み立てる
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ htlin222/ dotfiles --skill scientific-figure-assemblyインストール後、Claude Code で/scientific-figure-assemblyを入力して読み込まれたか確認します。
同梱ファイルscripts/assemble_figures.py
詳細
科学論文用図の組み立て(R ベース)
R パッケージ(patchwork、cowplot)を使用して、300 DPI の解像度で、プロフェッショナルなパネルラベル(A、B、C、D)付きの、出版可能な複数パネル図を作成します。
⚠️ 重要:このワークフローでは、図の組み立てに R を使用します。メタアナリシスのプロジェクトでは、すべての図を R で作成し、組み立ててください。
使用する場面
- 複数のプロットを1つの複数パネル図にまとめて出版する場合
- 既存の図にパネルラベル(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)
# Create or load individual plots
p1 <- ggplot(data1, aes(x, y)) + geom_point() + ggtitle("A. First Panel")
p2 <- ggplot(data2, aes(x, y)) + geom_line() + ggtitle("B. Second Panel")
p3 <- ggplot(data3, aes(x, y)) + geom_bar(stat="identity") + ggtitle("C. Third Panel")
# Combine vertically
combined <- p1 / p2 / p3
# Or combine horizontally
combined <- p1 | p2 | p3
# Or grid layout (2 columns)
combined <- (p1 | p2) / p3
# Export at 300 DPI
ggsave("figures/figure1_combined.png",
plot = combined,
width = 10, height = 12, dpi = 300)
方法2:cowplot(あらゆる R プロット向け)
柔軟性が高く、base R のプロットと ggplot2 の両方で使用できます。
library(ggplot2)
library(cowplot)
# Create individual plots
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")
# Combine with automatic panel labels
combined <- plot_grid(
p1, p2, p3,
labels = c("A", "B", "C"),
label_size = 18,
ncol = 1, # Vertical stack
rel_heights = c(1, 1, 1) # Equal heights
)
# Export
ggsave("figures/figure1_combined.png",
plot = combined,
width = 10, height = 12, dpi = 300)
従来の Python スクリプトテンプレート(非推奨)
⚠️ メタアナリシスのプロジェクトでは、代わりに上記の R の方法を使用してください。
既存の PNG ファイルを扱うために Python がどうしても必要な場合:
#!/usr/bin/env python3
"""Legacy: Assemble multi-panel scientific figure from PNG files."""
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):
"""
Add panel label (A, B, C) to image.
Args:
img: PIL Image object
label: Label text (e.g., 'A', 'B', 'C')
position: 'top-left', 'top-right', 'bottom-left', 'bottom-right'
font_size: Font size in pixels (80 works well for 3000px wide images)
offset: (x, y) offset from corner in pixels
bg_color: Background color for label box
text_color: Label text color
border: Whether to draw border around label box
"""
draw = ImageDraw.Draw(img)
# Try system fonts (macOS, then 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"Warning: Using default font for label {label}")
# Calculate label position
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]
# Draw background box
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
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):
"""
Stack images vertically with panel labels.
Args:
input_files: List of paths to input images
output_file: Path for output image
labels: List of labels (default: A, B, C, ...)
spacing: Vertical spacing between panels in pixels
dpi: Output resolution
"""
if labels is None:
labels = [chr(65 + i) for i in range(len(input_files))] # A, B, C, ...
# Load all images
images = [Image.open(f) for f in input_files]
# Add labels
labeled = [add_panel_label(img, label)
for img, label in zip(images, labels)]
# Calculate dimensions
max_width = max(img.width for img in labeled)
total_height = sum(img.height for img in labeled) + spacing * (len(labeled) - 1)
# Create combined image
combined = Image.new('RGB', (max_width, total_height), 'white')
# Paste images
y_offset = 0
for img in labeled:
combined.paste(img, (0, y_offset))
y_offset += img.height + spacing
# Save with specified DPI
combined.save(output_file, dpi=(dpi, dpi))
print(f"✅ Created {output_file}")
print(f" Dimensions: {combined.width}×{combined.height} px at {dpi} DPI")
return output_file
def assemble_horizontal(input_files, output_file, labels=None,
spacing=40, dpi=300):
"""Stack images horizontally with panel labels."""
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"✅ Created {output_file}")
print(f" Dimensions: {combined.width}×{combined.height} px at {dpi} DPI")
return output_file
def assemble_grid(input_files, output_file, rows, cols,
labels=None, spacing=40, dpi=300):
"""
Arrange images in a grid with panel labels.
Args:
rows: Number of rows
cols: Number of columns
Other args same as 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)]
# Calculate cell dimensions (use max from each row/col)
cell_width = max(img.width for img in labeled)
cell_height = max(img.height for img in labeled)
# Total dimensions
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')
# Place images
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"✅ Created {output_file}")
print(f" Dimensions: {combined.width}×{combined.height} px at {dpi} DPI")
return output_file
if __name__ == '__main__':
import sys
# Example usage
if len(sys.argv) < 3:
print("Usage: python assemble_figures.py <output> <layout> <input1> <input2> ...")
print(" layout: vertical, horizontal, or grid:RxC (e.g., 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"Unknown layout: {layout}")
sys.exit(1)
一般的なレイアウト
縦(最も一般的)
プロットを上下に積み重ねます。推移や関連する結果を示すのに適しています。
例:3つのフォレストプロット(pCR、EFS、OS)を縦に積み重ねる
- パネル A:pCR のフォレストプロット
- パネル B:EFS のフォレストプロット
- パネル C:OS のフォレストプロット
横
プロットを横並びに配置します。比較に適しています。
例:出版バイアスを示す2つのファンネルプロット
- パネル A:pCR のファンネルプロット
- パネル B:EFS のファンネルプロット
グリッド(2x2、2x3 など)
行と列に配置します。体系的な比較に適しています。
例:サブグループ解析の2x2グリッド
- パネル A:年齢のサブグループ
- パネル B:性別のサブグループ
- パネル C:病期のサブグループ
- パネル D:組織型のサブグループ
R ワークフロー(推奨)
完全な例:メタアナリシスのフォレストプロット
#!/usr/bin/env Rscript
# assemble_forest_plots.R
# Combine multiple forest plots into a single figure
library(meta)
library(metafor)
library(patchwork)
# Set working directory
setwd("/Users/htlin/meta-pipe/06_analysis")
# Load extraction data
data <- read.csv("../05_extraction/extraction.csv")
# --- Create individual forest plots ---
# Plot 1: Pathologic complete response
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"
)
# Save as ggplot-compatible object
p1 <- forest(res_pcr, layout = "RevMan5") +
ggtitle("A. Pathologic Complete Response")
# Plot 2: Event-free survival
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. Event-Free Survival")
# Plot 3: Overall survival
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. Overall Survival")
# --- Combine with patchwork ---
combined <- p1 / p2 / p3 +
plot_annotation(
title = "Figure 1. Efficacy Outcomes with ICI vs Control",
theme = theme(plot.title = element_text(size = 16, face = "bold"))
)
# Export at 300 DPI
ggsave("../07_manuscript/figures/figure1_efficacy.png",
plot = combined,
width = 10,
height = 14,
dpi = 300,
bg = "white")
cat("✅ Created figure1_efficacy.png\n")
cat(" Dimensions: 3000×4200 px at 300 DPI\n")
より細かく制御するために cowplot を使用
library(cowplot)
# Combine with explicit panel labels and alignment
combined <- plot_grid(
p1, p2, p3,
labels = c("A", "B", "C"),
label_size = 18,
label_fontface = "bold",
ncol = 1,
align = "v", # Vertical alignment
axis = "l", # Align left axis
rel_heights = c(1, 1, 1)
)
# Add overall title
title <- ggdraw() +
draw_label(
"Figure 1. Efficacy Outcomes with ICI vs Control",
fontface = "bold",
size = 16,
x = 0.5,
hjust = 0.5
)
# Combine title and plots
final <- plot_grid(
title,
combined,
ncol = 1,
rel_heights = c(0.1, 1)
)
# Export
ggsave("../07_manuscript/figures/figure1_efficacy.png",
plot = final,
width = 10, height = 14, dpi = 300, bg = "white")
グリッドレイアウト(2x2 または 2x3)
library(patchwork)
# 2x2 grid
combined <- (p1 | p2) / (p3 | p4) +
plot_annotation(tag_levels = "A")
# 2x3 grid
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:入力ファイルを確認
# Check that all files exist and are PNG/JPG
ls -lh path/to/plots/*.png
ステップ2:組み立てスクリプトを作成
このスキルで提供されている Python テンプレートを使用します。
ステップ3:組み立てを実行
# Using uv (recommended for dependency management)
uv run python assemble_figures.py Figure1_Efficacy.png vertical \
forest_plot_pCR.png \
forest_plot_EFS.png \
forest_plot_OS.png
# Or with system Python (requires PIL/Pillow)
python assemble_figures.py Figure1.png grid:2x2 \
plot1.png plot2.png plot3.png plot4.png
ステップ4:出力を確認
# Check dimensions and file size
ls -lh Figure1_Efficacy.png
# Verify DPI (should show 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(1段組)または183mm(2段組)
- フォント:Arial、Helvetica、または類似のサンセリフ体
- ラベル:最終サイズで太字、8~10pt
Lancet、JAMA、NEJM
- 解像度:最低300 DPI
- 形式:TIFF、EPS、または PNG
- 幅:段幅に収める(通常3~4インチ)
- ラベル:明瞭でコントラストが高いこと
- グレースケール:白黒でも読み取れること
品質チェックリスト
提出前に確認してください:
- すべての図が最低300 DPIである
- パネルラベル(A、B、C)が見やすく、正しい順序で並んでいる
- ラベルが重要なデータを隠していない
- すべてのパネルが適切に整列している
- パネル間の間隔が均一である
- ファイルサイズが適切である(PNGの場合は10 MB未満)
- 最終的なジャーナル掲載サイズで印刷しても図が読みやすい
- 必要に応じて、グレースケールでも配色を判別できる
よくある問題と解決策
問題:ラベルが小さすぎる
解決策:font_size パラメーターを大きくします(2倍にしてみてください)
問題:ラベルがデータを隠している
解決策:position を別の隅に変更するか、offset を調整します
問題:DPIが低すぎる
解決策:まず入力プロットを高解像度で再生成してから、再度組み立てます
問題:間隔が不均一
解決策:組み立てる前に入力画像をトリミングし、余分な余白を削除します
問題:ファイルサイズが大きすぎる
解決策:PNGを圧縮するか、JPEGに変換します(品質が低下する場合があります)
使用例
メタ解析の図(R)
# Figure 1: Efficacy outcomes (3 vertical panels)
library(patchwork)
combined <- p_pcr / p_efs / p_os +
plot_annotation(
title = "Figure 1. Efficacy Outcomes",
tag_levels = "A"
)
ggsave("07_manuscript/figures/figure1_efficacy.png",
width = 10, height = 14, dpi = 300)
# Figure 2: Safety + Bias (2 vertical panels)
combined <- p_safety / p_funnel +
plot_annotation(tag_levels = "A")
ggsave("07_manuscript/figures/figure2_safety.png",
width = 10, height = 10, dpi = 300)
# Figure 3: Subgroup analysis (2x2 grid)
combined <- (p_age | p_sex) / (p_stage | p_histology) +
plot_annotation(
title = "Figure 3. Subgroup Analyses",
tag_levels = "A"
)
ggsave("07_manuscript/figures/figure3_subgroups.png",
width = 14, height = 12, dpi = 300)
レガシーなPythonの例(非推奨)
# Figure 1: Efficacy outcomes (3 vertical panels)
uv run python assemble.py Figure1_Efficacy.png vertical \
forest_plot_pCR.png \
forest_plot_EFS.png \
forest_plot_OS.png
# Figure 2: Safety + Bias (2 vertical panels)
uv run python assemble.py Figure2_Safety.png vertical \
forest_plot_SAE.png \
funnel_plot_pCR.png
依存関係
Rパッケージ(推奨)
# Install from CRAN
install.packages(c("patchwork", "cowplot", "ggplot2"))
# For meta-analysis plots
install.packages(c("meta", "metafor"))
Python(レガシー — PNGの組み立て専用)
# Install using uv (if needed for legacy workflows)
uv add Pillow
# Or using pip
pip install Pillow
出力例
Rの出力
✅ Created figure1_efficacy.png
Dimensions: 3000×4200 px at 300 DPI
Size: 2.3 MB
出力ファイルには次の内容が含まれます:
- patchwork/cowplotによって自動的に追加される、プロフェッショナルなパネルラベル(A、B、C)
- パネル間の均一な間隔
- 論文掲載に適した300 DPIの解像度
- 比較しやすいように整列した軸
- 論文掲載に適したテーマ
Pythonの出力(レガシー)
✅ Created Figure1_Efficacy.png
Dimensions: 3000×6080 px at 300 DPI
出力ファイルには次の内容が含まれます:
- 左上隅に配置された、プロフェッショナルなパネルラベル(A、B、C)
- パネル間の均一な間隔
- 論文掲載に適した300 DPIの解像度
- 視認性を最大限に高める、ラベルの周囲に黒い枠線を付けた白い背景
関連スキル
/meta-manuscript-assembly- メタ解析の論文原稿を準備する一連の作業/plot-publication- 個別の論文掲載用プロットを作成する/figure-legends- 包括的な図の凡例を作成する
プロのヒント
Rのワークフローに関するヒント
- 最初から最後までRで作業する:最良の結果を得るには、プロットの生成と組み立ての両方をRで行います
- ggplot2にはpatchworkを使う:構文が最も簡単です(縦に並べる場合は
p1 / p2) - 異なる種類のプロットにはcowplotを使う:base Rとggplot2の両方で動作します
- テーマをグローバルに設定する:
theme_set(theme_minimal())を使って一貫性を保ちます - 最後に一度だけエクスポートする:すべてのプロットを作成してから結合し、エクスポートします(より高速です)
- 整列を確認する:cowplotでは
align = "v"とaxis = "l"を使います - サイズを統一する:読みやすいテキストにするため、テーマで
base_sizeを設定します
一般的なヒント
- まず高品質な入力を生成する:組み立てても、低品質な元のプロットは改善されません
- ラベルを体系的に付ける:上から下、または左から右にA-B-Cの順にします
- 印刷プレビューで確認する:最終的な印刷サイズでラベルが読みやすいことを確認します
- Rスクリプトを保存する:再現性を確保するためにRコードを保存します
- 図をバージョン管理する:Rスクリプトと最終的なPNGファイルの両方をコミットします
- 異なる画面でテストする:ノートPCの画面と印刷ページで読みやすさを確認します
Rパッケージのリソース
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/