Claude Media
Claude Codeでmatplotlibの論文図を投稿規定のdpiとフォントに揃える

Claude Codeでmatplotlibの論文図を投稿規定のdpiとフォントに揃える

生データから図を再生成するスクリプトをClaude Codeに書かせ、dpi・図の寸法・フォント埋め込み(Type 42)を投稿規定に合わせて検証させる手順です。

論文の図を投稿規定に合わせて作り直す作業は、規定の値を savefig の引数と rcParams に落とせば、スクリプトで再現できます。Claude Codeには、その値を CLAUDE.md で渡し、生成した図の画素数とフォント埋め込みを実際に測らせる、という分担が向いています。

この記事の対象は、すでにあるグラフを「規定どおりのファイル」に作り直す工程です。集計そのものはClaude Codeでpandasのデータ処理を書く実践手順が扱っています。ここでは、集計済みのデータから図を出力する最後の一段に絞ります。

規定の項目はmatplotlibのどこに対応するか

投稿規定に並ぶ項目は、matplotlibでは次の置き場所に落ちます。規定の数値そのものは雑誌ごとに違うので、表の「規定から読む値」の欄を自分の投稿先の値で埋めてください。

規定の項目規定から読む値matplotlibでの指定
図の幅・高さ規定から読む値mmやinchmatplotlibでの指定plt.subplots(figsize=(幅inch, 高さinch))
解像度規定から読む値dpimatplotlibでの指定fig.savefig(..., dpi=...)
ファイル形式規定から読む値PDF / EPS / PNG / SVGなどmatplotlibでの指定savefig の拡張子か format=
フォントの種類・サイズ規定から読む値例: sans-serifの指定、pt数matplotlibでの指定font.family / font.size
フォントの埋め込み規定から読む値必須かどうかmatplotlibでの指定pdf.fonttype / ps.fonttype
余白規定から読む値トリミングの可否matplotlibでの指定bbox_inches / pad_inches

savefig の dpi は既定で rcParams["savefig.dpi"] を参照し、その既定値は 'figure' です。'figure' のときは図自体のdpiが使われます。図自体のdpiの既定は figure.dpi の100です。つまり何も指定しないと、保存時の解像度は100 dpiのままです。

規定をCLAUDE.mdに書いて渡す

Claudeは CLAUDE.md を、強制される設定ではなくコンテキストとして読みます。そのため、検証できるくらい具体的に書くほうが守られやすくなります。公式のメモリ解説も、「Use 2-space indentation」のように確認できる書き方を勧めています。図の規定も同じ考え方で、値を数字で書きます。

次は、図の入稿規定を書いた CLAUDE.md の断片の例です。数値は仮置きなので、投稿先の規定で置き換えてください。

## 論文の図(figures/)
- 図は `figures/make_figures.py` だけで生成する。手作業の修正はしない
- 入力は `data/` の CSV。図のコードで集計し直さず、集計済みの `data/summary.csv` を読む
- 出力は `figures/out/` に PDF と PNG の 2 形式
- 図の幅は 3.3 inch(1 段組)または 6.9 inch(2 段組)。高さは幅の 0.75 倍を上限にする
- PNG は 600 dpi。PDF はベクターのまま
- フォントは sans-serif、本文 8 pt、軸ラベル 8 pt、目盛り 7 pt
- PDF のフォントは Type 42 で埋め込む(`pdf.fonttype = 42`)
- 図を作り直したら `python figures/check_figures.py` を実行し、全項目が OK になるまで直す

最後の行が重要です。公式のベストプラクティスは、Claudeに「実行できる確認手段」を与えることが、人が張り付く作業と任せきりの作業の分かれ目だと説明しています。確認手段は、テストでもビルドの終了コードでも、出力を固定値と突き合わせるスクリプトでも構いません。図の規定では、寸法・画素数・フォントを測る check_figures.py がこれに当たります。

200行を超える CLAUDE.md は遵守率が下がるとされているので、図の規定が長くなるなら、パスを限定したルールファイルに切り出す方法もあります。

図を再生成するスクリプトの形

Claudeに最初に頼むのは、図ごとの関数と、規定を一か所に集めた設定です。規定を rcParams にまとめると、図が増えても値が散らばりません。次の例は、頼んだときに期待する形の一例です。

# figures/make_figures.py
import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
 
COL1, COL2 = 3.3, 6.9      # 図の幅(inch)。規定の値に置き換える
PNG_DPI = 600              # 規定の解像度
 
mpl.rcParams.update({
    "font.family": "sans-serif",
    "font.size": 8,
    "axes.labelsize": 8,
    "xtick.labelsize": 7,
    "ytick.labelsize": 7,
    "pdf.fonttype": 42,    # TrueType として埋め込む
    "ps.fonttype": 42,
    "savefig.bbox": "tight",
})
 
def save(fig, name):
    fig.savefig(f"figures/out/{name}.pdf")
    fig.savefig(f"figures/out/{name}.png", dpi=PNG_DPI)
 
def fig_dose_response(df):
    fig, ax = plt.subplots(figsize=(COL1, COL1 * 0.75))
    ax.errorbar(df["dose"], df["mean"], yerr=df["sd"], marker="o")
    ax.set_xlabel("Dose (mg/kg)")
    ax.set_ylabel("Response")
    save(fig, "fig1_dose_response")
 
if __name__ == "__main__":
    fig_dose_response(pd.read_csv("data/summary.csv"))

pdf.fonttype と ps.fonttype の既定値は、どちらも3(Type 3)です。42にするとTrueType(PSではType 42)になります。この違いは次の節で扱います。

dpiと寸法の関係を先に押さえる

ラスター形式(PNGなど)の画素数は、図の寸法(inch)にdpiを掛けた値です。幅3.3 inchの図を600 dpiで保存すれば、幅はおよそ1,980画素になります。規定が「幅85 mmで600 dpi以上」のように寸法とdpiを別々に指定しているなら、figsize をinchに直してから dpi を渡す、という順で値が決まります。

ここで注意したいのは、savefig に bbox_inches="tight" を使うと、余白が詰められて出力の寸法が figsize とずれる点です。公式の savefig の説明では、'tight' は図のbboxを内容に合わせて自動で求めて保存する指定です。規定の寸法にぴったり合わせたいときは tight をやめ、constrained_layout などの配置で収めたほうが、寸法の検証が単純になります。余白を詰めたい図だけ tight を使い、検証スクリプトで出力寸法を測り直す運用でも構いません。

PDFやSVGはベクター形式なので、dpiを指定しても曲線の精細さは変わりません。dpiが効くのは、PDF内に画像を貼り込んだ部分(ヒートマップを imshow で描いた図など)と、PNGのようなラスター出力です。「PDFにも600 dpiを指定する」と規定にあっても、折れ線の図ならベクターのままで足ります。規定の文言が何を求めているかは、図の種類ごとに投稿先へ確かめる箇所です。

フォントの埋め込みはType 3かType 42か

投稿規定で最もつまずきやすいのがフォントです。公式のフォント解説によれば、matplotlibがPDFとPostScriptで扱うフォント仕様は、Type 3とTrueType(PDF)/ Type 42(PS)、およびTeXを使う場合のType 1です。

違いを公式の比較表から抜き出すと、次のとおりです。

項目Type 3TrueType(PDF)/ Type 42(PS)
位置づけType 3Adobeが導入した古い形式。PostScript言語の全体を使え、任意のコードを埋め込めるTrueType(PDF)/ Type 42(PS)Appleが導入した新しい形式で、現在も広く使われる
フォントヒンティングType 3対応しないTrueType(PDF)/ Type 42(PS)対応する(仮想マシン経由)

既定の3のままでも文字は図に埋め込まれます。Type 3とType 42では、ヒンティングへの対応が違います(上の表)。どちらを求められるかは、投稿先の規定の文面に従います。Claudeに「Type 42にして」と頼むときは、この表のどちらの列なのかを CLAUDE.md に書いておくと迷いません。

埋め込みはサブセット化されます。PDFとPSではフォント全体を埋め込むとファイルが大きくなりやすく、とくにCJKのようにグリフ数の多いフォントでそうなります。そのためmatplotlibは実際に使った文字だけを埋め込みます。使った文字の分だけが埋め込まれるので、日本語の図でもフォント全体の大きさにはなりません。

ベクター文字のままファイルを軽くしたいときの別手段が、14コアフォントです。PDFとPSの仕様には準拠ビューアーが必ず備える14のコアフォントがあり、文書をそれだけに限れば、フォント情報を埋め込まずにベクター文字が得られます。ただし投稿規定が「すべてのフォントを埋め込むこと」を求めるなら、この方式は合いません。使うなら次のように指定します。

mpl.rcParams["pdf.use14corefonts"] = True   # PDF バックエンド
mpl.rcParams["ps.useafm"] = True            # PS バックエンド

SVGの場合は svg.fonttype で決まり、既定は 'path' です。文字をパスとして書き出すので、見た目は閲覧環境によらず同じですが、後から文字を編集することはできません。'none' にすると文字列として残り、ファイルは小さくなりますが、見え方はビューアーとインストール済みのフォントで変わります。投稿先がSVGで文字の再編集を求めるか、見た目の固定を求めるかで選びます。

日本語を含む図はフォントを重ねて指定する

和文を含む図では、font.family に和文フォントを足します。matplotlib 3.6以降は、Agg・SVG・PDF・PSの各バックエンドで、1つのテキストの中でも複数のフォントを順に試すフォールバックが働きます。公式の例では、family にフォント名のリストを渡し、各グリフはリストの中で最初にそのグリフを持つフォントで描かれます。

mpl.rcParams["font.family"] = ["DejaVu Sans", "Noto Sans CJK JP"]

欧文はリスト先頭のフォントで、和文は2番目で描かれるので、英数字の見た目を保ったまま日本語を混在させられます。

ここで1つ確認が要ります。フォント解説は、matplotlibのOpenTypeフォントへの対応を「限定的」と説明しています。和文フォントはOpenType形式のものが多く、pdf.fonttype = 42 を指定しても、選んだフォントが規定の種類で埋め込まれるとは限りません。TrueTypeアウトラインの和文フォントを選ぶか、出力したPDFを次の節の pdffonts で調べて、種類の列を確かめます。

リストの後ろのフォントが手元に無いと、そのグリフは描けません。Claudeに頼むときは「使うフォントの名前を font_manager で一覧にして、無ければ報告して」と一言添えると、警告を握りつぶさずに済みます。

Claudeに検証させる反復を回す

規定を満たしたかどうかを、Claudeの申告ではなく測定値で判定します。次のような check_figures.py を一緒に書かせ、図を作り直すたびに実行させます。

# figures/check_figures.py
import sys
from pathlib import Path
from PIL import Image
 
EXPECT_W_IN = {"fig1_dose_response": 3.3}
PNG_DPI = 600
ok = True
for name, w_in in EXPECT_W_IN.items():
    png = Image.open(f"figures/out/{name}.png")
    want = round(w_in * PNG_DPI)
    good = abs(png.size[0] - want) <= 2
    print(("OK " if good else "NG "), name, "width px", png.size[0], "want", want)
    ok &= good
    if not Path(f"figures/out/{name}.pdf").exists():
        print("NG ", name, "PDF が無い"); ok = False
sys.exit(0 if ok else 1)

bbox_inches="tight" を有効にしていると幅がずれるため、このスクリプトはNGを返します。Claudeはその出力を読み、tight を外すか、期待値の側を実測に合わせるかを選びます。どちらを選ぶかは規定の文言次第なので、NGのまま黙って期待値を書き換えさせないよう、CLAUDE.md に「期待値は規定の値から導き、実測に合わせて変えない」と書き足しておきます。

PDFのフォントは、手元にpopplerの pdffonts があれば、次のコマンドで埋め込みの有無と種類を一覧にできます。

python figures/make_figures.py
python figures/check_figures.py
pdffonts figures/out/fig1_dose_response.pdf

pdffonts の出力は環境によって変わります。Claudeに実行させ、種類の列(TrueTypeかType 3か)と、埋め込みの列がすべてyesになっているかを報告させます。pdffonts が無い環境では、ほかのPDF解析ツールを使うよう頼んでも構いません。

切り分け

図が規定に合わないときの切り分け

  1. QPNG の幅(画素)が figsize × dpi と一致していますか?
    答えがいいえ

    bbox_inches="tight" が効いていないか、savefig の dpi が既定の 'figure'(100 dpi)のままでないかを見ます。

    答えがはい
  2. QPDF のフォントが規定の種類で埋め込まれていますか?
    答えがいいえ

    pdf.fonttype を設定した後に図を作っているか、スクリプトの先頭で rcParams.update が実行されているかを確かめます。

    答えがはい
  3. Q和文が四角(豆腐)になっていませんか?
    答えがなっていません

    font.family のリストにある和文フォントが、実行環境にインストールされているかを調べます。

    答えがはい

規定を満たした

出力一式を投稿システムに入稿する前に、図と本文の用語の表記ゆれだけを人が確認します。

作り直しで踏みやすい落とし穴

落とし穴

rcParams の設定より前に図を作っている

rcParams は図を作る時点の値が使われます。plt.subplots を呼んだ後に font.size を変えても、すでにある図の文字には反映されません。設定は import 直後にまとめて書くよう、CLAUDE.md で指定します。

  • 規定の値をClaudeに推測させない: 投稿先の規定ページの数値は、自分で CLAUDE.md に写します。「論文なら300 dpi」のような一般論をClaudeが補うと、投稿先とずれることがあります。
  • PNGの解像度だけを検証して満足しない: 画素数が合っていても、PDF内のフォントがType 3のままなら、Type 42を求める規定には通りません。
  • 元の図を直接上書きさせない: 再生成は figures/out/ に出し、元の図は残します。作り直しの前後を並べて見比べられます。
  • グラフの見た目の相談は別の工程にする: 凡例の位置や配色といった見た目の調整は、入稿仕様とは別の作業です。基本の頼み方はClaudeにグラフを作らせる基本の頼み方にまとめています。

CSVの集計と、ブラウザ上でのグラフ化までClaude.aiで済ませる流れは、Claude CSV分析の使い方が扱っています。論文図のように寸法とフォントまで規定される場合は、この記事のようにスクリプトとして残しておくほうが、再投稿のときに値を直しやすくなります。

まとめ

規定を CLAUDE.md に数字で書き、rcParams と savefig に集め、check_figures.py で画素数とフォントを測る、という3点で、図の入稿仕様は再現可能な作業になります。dpiは保存時の引数で、寸法は figsize で、フォントの種類は pdf.fonttype で決まります。この3つが別々の場所にあることを、最初にClaudeへ伝えておくと、直す場所を取り違えません。

この記事を共有:XはてブLinkedIn