mcpField
S
seaborn-statistical-plots

seaborn-statistical-plots

github.com/jaechang-hitsデータ・データベース38477 回インストール6 日前に更新
リポジトリ

seaborn-statistical-plots について

seaborn-statistical-plotsは、matplotlibを基盤とするPython統計可視化ライブラリSeabornのスキルです。分析者や研究者向けに、表形式データから分布図、関係図、回帰図、行列図を作成し、ファセット表示にも対応します。

主な機能

  1. 01分布図とカテゴリープロット
  2. 02関係図と回帰プロット
  3. 03ヒートマップとクラスタ行列
  4. 04ペアプロットとジョイントプロット
  5. 05データ部分集合ごとのファセット表示

活用シーン

  1. 01実験条件ごとの測定値の比較
  2. 02グループ分布と個々の観測値の表示
  3. 03複数の特徴量間の相関の探索
  4. 04信頼区間付き回帰フィットの描画

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jaechang-hits/sciagent-skills --skill seaborn-statistical-plots

インストール後、Claude Code で/seaborn-statistical-plotsを入力して読み込まれたか確認します。

詳細

Seaborn — 統計プロット

概要

Seaborn は、matplotlib を基盤とする統計データ可視化用の Python ライブラリです。pandas DataFrame を直接扱い、カテゴリ変数によるグループ化を自動的に処理し、信頼区間とカーネル密度推定を計算して、最小限の設定で魅力的な出版品質の図を作成します。Seaborn は、カスタムレイアウトに埋め込める軸レベルの関数と、ファセット機能を備えた図レベルの関数を分けており、手軽な探索的解析と、構造化された複数パネルの図の両方に対応します。

使用する場面

  • 実験条件(処置群と対照群、細胞株、時点)間で、遺伝子発現、タンパク質量、測定値の分布を比較する場合
  • 要約統計量と個々のデータ点を同時に示す、グループ化された箱ひげ図、バイオリンプロット、ストリッププロットを作成する場合
  • 複数の遺伝子や特徴量を含むデータセットのペアごとの相関を、注釈付きヒートマップとして可視化する場合
  • 連続変数間(例:細胞生存率と薬物濃度)の回帰曲線を信頼区間の帯とともにプロットする場合
  • 1 回の呼び出しで、複数のサンプルサブセット、組織タイプ、実験バッチにわたって単一のプロット形式をファセット表示する場合
  • pairplot を使い、すべてのペアごとの関係を一度に確認して、新しいデータセットをすばやく探索的に解析する場合
  • 図の要素をピクセル単位で制御したい場合、複雑な異種レイアウトが必要な場合、または統計以外のカスタムプロットを作成する場合は、matplotlib を直接使用してください
  • 出力にインタラクティブ機能(ホバーツールチップ、ズーム、パン)が必要な場合、またはウェブアプリケーションに埋め込む場合は、plotly を使用してください

前提条件

  • Python パッケージ:seaborn>=0.13、matplotlib、pandas、numpy
  • データ要件:ロング形式(tidy 形式)の Pandas DataFrame。各行が観測値、各列が変数です
  • 環境:標準的な Python 環境。GPU や特別なハードウェアは不要です
pip install "seaborn>=0.13" matplotlib pandas numpy scipy

クイックスタート

import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

# Simulate gene expression across conditions
rng = np.random.default_rng(42)
df = pd.DataFrame({
    "gene":      ["BRCA1"] * 60 + ["TP53"] * 60,
    "condition": ["control", "treated"] * 60,
    "log2_expr": np.concatenate([
        rng.normal(5.2, 0.8, 60),
        rng.normal(6.1, 0.9, 60),
    ])
})

sns.set_theme(style="ticks", context="notebook")
sns.boxplot(data=df, x="gene", y="log2_expr", hue="condition", palette="Set2")
plt.ylabel("log2 Expression")
plt.title("Gene Expression by Condition")
plt.tight_layout()
plt.savefig("quickstart_boxplot.png", dpi=150)
print("Saved quickstart_boxplot.png")

コア API

1. 分布プロット

単変量の分布を可視化し、グループ間で比較します。histplot はデータをビン分割し、kdeplot は滑らかな密度推定を当てはめます。displot はファセット機能を追加する図レベルのラッパーです。

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

rng = np.random.default_rng(0)
n = 200
df = pd.DataFrame({
    "log2_tpm":  np.concatenate([rng.normal(4.5, 1.1, n), rng.normal(6.0, 1.3, n)]),
    "sample":    ["tumor"] * n + ["normal"] * n,
})

fig, axes = plt.subplots(1, 3, figsize=(15, 4))

# Histogram with density normalization and stacked hue groups
sns.histplot(data=df, x="log2_tpm", hue="sample", stat="density",
             multiple="stack", bins=30, ax=axes[0])
axes[0].set_title("Histogram (stacked)")

# KDE with fill — bandwidth controlled by bw_adjust
sns.kdeplot(data=df, x="log2_tpm", hue="sample", fill=True,
            bw_adjust=0.8, alpha=0.4, ax=axes[1])
axes[1].set_title("KDE (filled)")

# ECDF — useful for comparing cumulative distributions
sns.ecdfplot(data=df, x="log2_tpm", hue="sample", ax=axes[2])
axes[2].set_title("ECDF")

plt.tight_layout()
plt.savefig("distributions.png", dpi=150)
print("Saved distributions.png")
# Bivariate KDE: joint distribution of two continuous variables
rng = np.random.default_rng(1)
df2 = pd.DataFrame({
    "log2_rna": rng.normal(5.5, 1.2, 300),
    "log2_prot": rng.normal(4.8, 1.0, 300) + 0.6 * rng.normal(5.5, 1.2, 300),
})
sns.kdeplot(data=df2, x="log2_rna", y="log2_prot",
            fill=True, levels=8, thresh=0.05, cmap="Blues")
plt.xlabel("log2 RNA (TPM)")
plt.ylabel("log2 Protein (iBAQ)")
plt.title("RNA–Protein Correlation Density")
plt.tight_layout()
plt.savefig("bivariate_kde.png", dpi=150)
print("Saved bivariate_kde.png")
2. カテゴリプロット

カテゴリグループ間で分布や集計統計量を比較します。軸レベルの関数(boxplot、violinplot、stripplot、swarmplot、barplot)は、カスタムレイアウトに埋め込むための ax= パラメーターを受け取ります。

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

rng = np.random.default_rng(2)
conditions = ["DMSO", "Drug A 1uM", "Drug A 10uM", "Drug B 1uM", "Drug B 10uM"]
df = pd.DataFrame({
    "condition": np.repeat(conditions, 30),
    "viability": np.concatenate([
        rng.normal(100, 5, 30),
        rng.normal(92, 7, 30),
        rng.normal(65, 10, 30),
        rng.normal(88, 8, 30),
        rng.normal(45, 12, 30),
    ])
})

fig, axes = plt.subplots(1, 3, figsize=(18, 5))

# Box plot — shows quartiles and outliers
sns.boxplot(data=df, x="condition", y="viability",
            palette="husl", width=0.5, ax=axes[0])
axes[0].set_xticklabels(axes[0].get_xticklabels(), rotation=30, ha="right")
axes[0].set_title("Box Plot")

# Violin — KDE shape + inner quartile lines
sns.violinplot(data=df, x="condition", y="viability",
               inner="quart", palette="muted", ax=axes[1])
axes[1].set_xticklabels(axes[1].get_xticklabels(), rotation=30, ha="right")
axes[1].set_title("Violin Plot")

# Strip plot overlaid on box — shows all individual points
sns.boxplot(data=df, x="condition", y="viability",
            palette="pastel", width=0.5, ax=axes[2])
sns.stripplot(data=df, x="condition", y="viability",
              color="black", alpha=0.4, size=3, jitter=True, ax=axes[2])
axes[2].set_xticklabels(axes[2].get_xticklabels(), rotation=30, ha="right")
axes[2].set_title("Box + Strip")

plt.tight_layout()
plt.savefig("categorical.png", dpi=150)
print("Saved categorical.png")
# Bar plot with mean ± 95% CI and individual points (swarm)
fig, ax = plt.subplots(figsize=(8, 5))
sns.barplot(data=df, x="condition", y="viability",
            estimator="mean", errorbar="ci", palette="Set3", ax=ax)
sns.swarmplot(data=df, x="condition", y="viability",
              color="black", size=3, alpha=0.5, ax=ax)
ax.set_ylabel("Cell Viability (%)")
ax.set_xticklabels(ax.get_xticklabels(), rotation=30, ha="right")
plt.tight_layout()
plt.savefig("barswarm.png", dpi=150)
print("Saved barswarm.png")
3. 関係プロット

連続変数間の関係を可視化します。scatterplot と lineplot は軸レベルの関数で、relplot は col と row によるファセット表示に対応した図レベルのラッパーです。

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

rng = np.random.default_rng(3)
n = 150
df = pd.DataFrame({
    "molecular_weight": rng.uniform(200, 800, n),
    "logP":             rng.uniform(-2, 6, n),
    "pIC50":            rng.normal(6.5, 1.2, n),
    "target_class":     rng.choice(["kinase", "GPCR", "protease"], n),
    "pass_lipinski":    rng.choice(["yes", "no"], n, p=[0.7, 0.3]),
})

# Scatter with hue (categorical color) + size (continuous) + style (marker)
sns.scatterplot(data=df, x="molecular_weight", y="pIC50",
                hue="target_class", size="logP", style="pass_lipinski",
                sizes=(30, 120), alpha=0.7)
plt.xlabel("Molecular Weight (Da)")
plt.ylabel("pIC50")
plt.title("Compound Bioactivity by Target Class")
plt.tight_layout()
plt.savefig("relational_scatter.png", dpi=150)
print("Saved relational_scatter.png")
# Line plot with automatic mean aggregation and SD error band across replicates
timepoints = [0, 1, 2, 4, 8, 24]
groups = ["untreated", "low_dose", "high_dose"]
rows = []
for grp, base in zip(groups, [100.0, 95.0, 80.0]):
    for tp in timepoints:
        for _ in range(5):  # 5 replicates
            rows.append({"timepoint_h": tp, "group": grp,
                         "confluency": base * np.exp(-0.02 * tp * (1 + rng.normal(0, 0.1)))})
time_df = pd.DataFrame(rows)

sns.lineplot(data=time_df, x="timepoint_h", y="confluency",
             hue="group", style="group", errorbar="sd", markers=True, dashes=False)
plt.xlabel("Time (h)")
plt.ylabel("Confluency (%)")
plt.title("Cell Growth Inhibition (mean ± SD, n=5)")
plt.tight_layout()
plt.savefig("lineplot.png", dpi=150)
print("Saved lineplot.png")
4. 回帰プロット

線形(または多項式/lowess)モデルを当てはめ、信頼区間の帯とともに可視化します。regplot は軸レベルの関数で、lmplot はファセット表示に対応した図レベルの関数です。

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

rng = np.random.default_rng(4)
n = 120
tumor_size = rng.uniform(0.5, 6.0, n)
survival_months = 40 - 5 * tumor_size + rng.normal(0, 4, n)
grade = rng.choice(["low", "high"], n, p=[0.5, 0.5])
df = pd.DataFrame({"tumor_size_cm": tumor_size,
                   "survival_months": survival_months,
                   "grade": grade})

fig, axes = plt.subplots(1, 2, figsize=(13, 5))

# Linear regression with 95% CI band
sns.regplot(data=df, x="tumor_size_cm", y="survival_months",
            ci=95, scatter_kws={"alpha": 0.4, "s": 25}, ax=axes[0])
axes[0].set_title("Linear Regression (95% CI)")

# Residuals plot — check for homoscedasticity
sns.residplot(data=df, x="tumor_size_cm", y="survival_months",
              scatter_kws={"alpha": 0.4, "s": 25}, ax=axes[1])
axes[1].axhline(0, color="red", linestyle="--", linewidth=1)
axes[1].set_title("Residuals vs Fitted")

plt.tight_layout()
plt.savefig("regression.png", dpi=150)
print("Saved regression.png")
# lmplot — figure-level: separate regression lines per grade (hue) + facets
g = sns.lmplot(data=df, x="tumor_size_cm", y="survival_months",
               hue="grade", col="grade", ci=95,
               scatter_kws={"alpha": 0.4}, height=4, aspect=1.1)
g.set_axis_labels("Tumor Size (cm)", "Survival (months)")
g.set_titles("{col_name} grade")
g.savefig("lmplot_faceted.png", dpi=150)
print("Saved lmplot_faceted.png")
5. 行列プロット

矩形データを色で値を表した行列として可視化します。heatmap は軸レベルの関数で、clustermap は行と列に階層的クラスタリングを適用する図レベルの関数です。

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

rng = np.random.default_rng(5)
genes = [f"GENE{i}" for i in range(1, 9)]
samples = [f"S{i}" for i in range(1, 7)]

# Simulate log2 fold-change matrix (rows=genes, cols=samples)
lfc = pd.DataFrame(
    rng.normal(0, 1.5, size=(8, 6)),
    index=genes, columns=samples
)
# Inject a pattern: first 3 genes up in samples 1-3, down in 4-6
lfc.iloc[:3, :3] += 2.5
lfc.iloc[:3, 3:] -= 2.5

# Correlation heatmap of numeric features
df_num = pd.DataFrame(
    rng.standard_normal((80, 5)),
    columns=["GeneA", "GeneB", "GeneC", "GeneD", "GeneE"]
)
df_num["GeneB"] = df_num["GeneA"] * 0.85 + rng.normal(0, 0.3, 80)
corr = df_num.corr()

fig, axes = plt.subplots(1, 2, figsize=(14, 5))

sns.heatmap(corr, annot=True, fmt=".2f", cmap="coolwarm",
            center=0, square=True, linewidths=0.5, ax=axes[0])
axes[0].set_title("Pearson Correlation Heatmap")

sns.heatmap(lfc, cmap="RdBu_r", center=0, annot=True, fmt=".1f",
            linewidths=0.3, cbar_kws={"label": "log2FC"}, ax=axes[1])
axes[1].set_title("log2 Fold Change Matrix")

plt.tight_layout()
plt.savefig("heatmaps.png", dpi=150)
print("Saved heatmaps.png")
# Clustermap with hierarchical clustering and row/column color annotations
rng = np.random.default_rng(6)
n_genes, n_samples = 30, 16
expr = pd.DataFrame(
    rng.lognormal(mean=2.0, sigma=1.2, size=(n_genes, n_samples)),
    index=[f"GENE{i:03d}" for i in range(n_genes)],
    columns=[f"{'T' if i < 8 else 'N'}{i:02d}" for i in range(n_samples)]
)

# Column annotation colors (tumor vs normal)
col_colors = ["#D32F2F" if c.startswith("T") else "#1976D2" for c in expr.columns]

g = sns.clustermap(
    np.log2(expr + 1),
    cmap="viridis",
    standard_scale=0,          # z-score across rows (genes)
    method="ward",
    metric="euclidean",
    col_colors=col_colors,
    figsize=(12, 10),
    linewidths=0,
    cbar_pos=(0.02, 0.8, 0.03, 0.15),
    cbar_kws={"label": "Row z-score"},
)
g.ax_heatmap.set_xlabel("Sample")
g.ax_heatmap.set_ylabel("Gene")
plt.savefig("clustermap.png", dpi=150, bbox_inches="tight")
print("Saved clustermap.png")
6. 多変数グリッド

pairplot ですべてのペアごとの関係を確認するか、jointplot で周辺分布を含む二変量分布を表示します。グリッドレイアウトを完全にカスタマイズする場合は、FacetGrid を直接使用してください。

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

rng = np.random.default_rng(7)
n = 60
df = pd.DataFrame({
    "cell_area":      rng.normal(350, 60, n * 3),
    "nucleus_area":   rng.normal(90, 15, n * 3),
    "mean_intensity": rng.exponential(500, n * 3),
    "aspect_ratio":   np.abs(rng.normal(1.3, 0.3, n * 3)),
    "cell_type":      (["HeLa"] * n + ["MCF7"] * n + ["A549"] * n),
})

# Pairplot — matrix of pairwise scatter + KDE on diagonal
g = sns.pairplot(df, hue="cell_type", corner=True,
                 diag_kind="kde", plot_kws={"alpha": 0.5, "s": 20})
g.savefig("pairplot.png", dpi=150)
print("Saved pairplot.png")
# Jointplot — bivariate KDE with marginal histograms
g = sns.jointplot(data=df, x="cell_area", y="nucleus_area",
                  hue="cell_type", kind="scatter",
                  marginal_kws={"fill": True, "alpha": 0.3})
g.set_axis_labels("Cell Area (µm²)", "Nucleus Area (µm²)")
g.savefig("jointplot.png", dpi=150)
print("Saved jointplot.png")
# FacetGrid — custom layout: KDE of mean_intensity per cell type
g = sns.FacetGrid(df, col="cell_type", height=3.5, aspect=1.1,
                  sharey=False)
g.map(sns.histplot, "mean_intensity", bins=20, kde=True, color="steelblue")
g.set_axis_labels("Mean Intensity (AU)", "Count")
g.set_titles("{col_name}")
g.tight_layout()
g.savefig("facetgrid_intensity.png", dpi=150)
print("Saved facetgrid_intensity.png")

主要な概念

図レベル関数と Axes レベル関数

Seaborn には、戻り値の型と組み合わせやすさが異なる 2 つの階層の関数があります。

特徴Axes レベル図レベル
例scatterplot、histplot、boxplot、heatmap、regplotrelplot、displot、catplot、lmplot
戻り値matplotlib.axes.AxesFacetGrid / JointGrid / PairGrid
ファセット分割手動(自分でサブプロットを作成)組み込み(col=、row= パラメーター)
サイズ指定親図で figsize= を指定各ファセットパネルで height= と aspect= を指定
配置ax= パラメーター既存の Axes には配置できません
保存plt.savefig(...)g.savefig(...)
使用する場面1 つの図に異なる種類のプロットを組み合わせる場合複数パネルで手軽に探索的な表示を行う場合
# Axes-level: place in a pre-allocated subplot grid
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
sns.violinplot(data=df, x="cell_type", y="cell_area", ax=axes[0])
sns.scatterplot(data=df, x="cell_area", y="nucleus_area", hue="cell_type", ax=axes[1])
ロング形式データとワイド形式データ

Seaborn の意味マッピング(hue、size、style)には、各変数が列、各観測値が行となるロング形式(整然形式)データが必要です。一部の関数(heatmap、clustermap、lineplot)はワイド形式データも受け付けます。

# Wide-form: unsuitable for hue/style mappings
#   sample_A  sample_B  sample_C
# 0      5.1       6.2       4.8

# Long-form (preferred): melt wide → long
wide = pd.DataFrame({"sampleA": [5.1, 4.3], "sampleB": [6.2, 5.9]})
long = wide.melt(var_name="sample", value_name="log2_expr")
# → columns: sample, log2_expr

よくあるワークフロー

ワークフロー 1:有意性の閾値を付けた差次的発現の散布図

目的:log2 fold-change と -log10 p-value の関係を、調節状態ごとに色分けし、有意性の注釈と上位ヒットのラベルを付けて可視化します(ボルケーノプロット形式)。

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
n = 500
lfc   = rng.normal(0, 1.5, n)
pvals = 10 ** (-rng.exponential(1.5, n))        # skewed toward low significance
pvals = np.clip(pvals, 1e-20, 1.0)
genes = [f"GENE{i:04d}" for i in range(n)]

df_de = pd.DataFrame({"gene": genes, "log2fc": lfc, "pvalue": pvals})
df_de["neg_log10_p"] = -np.log10(df_de["pvalue"])

# Classify regulation status
lfc_thresh = 1.0
padj_thresh = 0.05
df_de["sig"] = "NS"
df_de.loc[(df_de["log2fc"] >  lfc_thresh) & (df_de["pvalue"] < padj_thresh), "sig"] = "Up"
df_de.loc[(df_de["log2fc"] < -lfc_thresh) & (df_de["pvalue"] < padj_thresh), "sig"] = "Down"

palette = {"NS": "#AAAAAA", "Up": "#D32F2F", "Down": "#1976D2"}

sns.set_theme(style="ticks", context="paper", font_scale=1.1)
fig, ax = plt.subplots(figsize=(8, 6))

sns.scatterplot(data=df_de, x="log2fc", y="neg_log10_p",
                hue="sig", palette=palette,
                alpha=0.6, s=18, linewidth=0, ax=ax)

# Threshold lines
ax.axhline(-np.log10(padj_thresh), color="black", linestyle="--", linewidth=0.8)
ax.axvline( lfc_thresh,            color="black", linestyle="--", linewidth=0.8)
ax.axvline(-lfc_thresh,            color="black", linestyle="--", linewidth=0.8)

# Label top 5 most significant genes per direction
for direction in ["Up", "Down"]:
    top = df_de[df_de["sig"] == direction].nlargest(5, "neg_log10_p")
    for _, row in top.iterrows():
        ax.text(row["log2fc"], row["neg_log10_p"] + 0.3, row["gene"],
                fontsize=6, ha="center", va="bottom",
                color=palette[direction])

# Annotation counts
n_up   = (df_de["sig"] == "Up").sum()
n_down = (df_de["sig"] == "Down").sum()
ax.set_title(f"Volcano Plot  |  Up: {n_up}  Down: {n_down}")
ax.set_xlabel("log2 Fold Change")
ax.set_ylabel("-log10 p-value")
sns.despine(trim=True)
plt.tight_layout()
plt.savefig("volcano_plot.png", dpi=300, bbox_inches="tight")
print(f"Volcano: {n_up} up, {n_down} down — saved volcano_plot.png")
ワークフロー 2:グループ化したバイオリンプロットとストリッププロットによる複数条件の比較

目的:複数の処理条件と時点にわたって、遺伝子発現(または任意の連続測定値)を比較し、全体の分布と個々の反復測定値を表示します。

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

rng = np.random.default_rng(99)
genes    = ["BRCA1", "TP53", "EGFR"]
treats   = ["DMSO", "Drug A", "Drug B"]
timepoints = ["6h", "24h", "48h"]
rows = []
for gene in genes:
    base_expr = {"BRCA1": 7.5, "TP53": 6.2, "EGFR": 8.1}[gene]
    for treat in treats:
        treat_shift = {"DMSO": 0.0, "Drug A": -0.8, "Drug B": 0.6}[treat]
        for tp in timepoints:
            tp_shift = {"6h": 0.0, "24h": 0.3, "48h": 0.6}[tp]
            for _ in range(12):
                rows.append({
                    "gene":      gene,
                    "treatment": treat,
                    "timepoint": tp,
                    "log2_expr": base_expr + treat_shift + tp_shift + rng.normal(0, 0.5),
                })
df_mc = pd.DataFrame(rows)

sns.set_theme(style="whitegrid", context="paper", font_scale=1.0)
g = sns.catplot(
    data=df_mc,
    x="timepoint", y="log2_expr",
    hue="treatment",
    col="gene",
    kind="violin",
    inner="quart",
    dodge=True,
    palette="Set2",
    height=4, aspect=0.9,
    col_order=genes,
    order=timepoints,
)

# Overlay individual points
for ax in g.axes.flat:
    gene_label = ax.get_title()
    gene_name  = gene_label.split(" = ")[-1] if " = " in gene_label else gene_label
    subset = df_mc[df_mc["gene"] == gene_name]
    sns.stripplot(
        data=subset,
        x="timepoint", y="log2_expr",
        hue="treatment",
        dodge=True,
        jitter=True,
        size=2.5,
        alpha=0.4,
        palette="dark:black",
        order=timepoints,
        legend=False,
        ax=ax,
    )

g.set_axis_labels("Timepoint", "log2 Expression")
g.set_titles("{col_name}")
g.add_legend(title="Treatment")
sns.despine(trim=True)
g.tight_layout()
g.savefig("multigroup_violin.png", dpi=300, bbox_inches="tight")
print("Saved multigroup_violin.png")
ワークフロー 3:細胞形態の特徴量をペアごとに探索

目的:pairplot を使って、複数の特徴量を持つ細胞形態データセットの特徴量間の関係を手早く調べてから、jointplot で重要なペアを詳しく確認します。

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

rng = np.random.default_rng(12)
n_per_type = 80
df_morph = pd.DataFrame({
    "cell_area_um2":    np.concatenate([rng.normal(320, 50, n_per_type),
                                        rng.normal(420, 70, n_per_type),
                                        rng.normal(280, 40, n_per_type)]),
    "nucleus_area_um2": np.concatenate([rng.normal(85, 12, n_per_type),
                                        rng.normal(110, 18, n_per_type),
                                        rng.normal(75, 10, n_per_type)]),
    "eccentricity":     np.abs(np.concatenate([rng.normal(0.6, 0.12, n_per_type),
                                               rng.normal(0.8, 0.10, n_per_type),
                                               rng.normal(0.5, 0.09, n_per_type)])),
    "mean_dapi":        np.concatenate([rng.exponential(400, n_per_type),
                                        rng.exponential(600, n_per_type),
                                        rng.exponential(350, n_per_type)]),
    "cell_line":        ["HeLa"] * n_per_type + ["MCF7"] * n_per_type + ["U2OS"] * n_per_type,
})

# 1. Pairplot survey
g = sns.pairplot(df_morph, hue="cell_line", corner=True,
                 diag_kind="kde", plot_kws={"alpha": 0.5, "s": 15},
                 palette="Dark2")
g.savefig("morphology_pairplot.png", dpi=150)
print("Saved morphology_pairplot.png")

# 2. Focused jointplot for the most informative pair
g2 = sns.jointplot(data=df_morph, x="cell_area_um2", y="nucleus_area_um2",
                   hue="cell_line", kind="scatter",
                   marginal_kws={"fill": True, "alpha": 0.25},
                   palette="Dark2", alpha=0.6)
g2.set_axis_labels("Cell Area (µm²)", "Nucleus Area (µm²)")
g2.savefig("morphology_jointplot.png", dpi=150)
print("Saved morphology_jointplot.png")

主なパラメーター

パラメーター関数デフォルト範囲 / オプション効果
hueすべてのプロット関数None列名(カテゴリまたは連続値)変数を色で表し、自動的に凡例を表示します
stylescatterplot、lineplotNoneカテゴリ列名マーカーの形状または線の破線パターンで変数を表します
sizescatterplot、lineplotNoneカテゴリ列または連続値列点または線のサイズで変数を表します
col / row図レベルのみ(relplot、displot、catplot、lmplot)Noneカテゴリ列名一意の値ごとに 1 つのサブプロットパネルを作成します
col_wrap図レベルのみNoneintN 個のパネルごとに列を新しい行に折り返します
estimatorbarplot、pointplot"mean""mean"、"median"、任意の呼び出し可能オブジェクト各カテゴリ内に適用する集計関数です
errorbarbarplot、lineplot、pointplot("ci", 95)"ci"、"sd"、"se"、"pi"、None推定値の周囲に表示するエラーバーの種類です
stathistplot"count""count"、"frequency"、"density"、"probability"ヒストグラムの棒の高さに適用する正規化方法です
bw_adjustkdeplot、violinplot1.00.1~3.0KDE の帯域幅の倍率です。値が小さいほど尖り、値が大きいほど滑らかになります
multiplehistplot、kdeplot"layer""layer"、"stack"、"dodge"、"fill"重なり合う hue グループの描画方法です
innerviolinplot"box""box"、"quart"、"point"、"stick"、Noneバイオリン本体の内部に表示する注釈です
standard_scaleclustermapNone0(行)、1(列)クラスタリング前に Z スコア正規化を行う軸です
dodgeboxplot、violinplot、stripplot可変True、False軸に沿って hue でグループ化された要素を分離します
contextset_theme()"notebook""paper"、"notebook"、"talk"、"poster"出力媒体に合わせてフォントと線の幅を調整します

ベストプラクティス

  1. 列名付きのロング形式データフレームを優先します:Seaborn のセマンティックマッピング(hue、style、size)では、列名から変数名を直接読み取ります。生の配列を渡すと、軸ラベルや凡例が失われます。pd.melt() を使ってワイド形式のデータを変換します。

  2. スクリプトの冒頭で set_theme() を一度呼び出します:これにより、以降のすべてのプロットに共通のスタイル、コンテキスト、パレットが設定され、一貫性が保たれます。sns.set_theme() でデフォルトに戻せます。

    sns.set_theme(style="ticks", context="paper", font_scale=1.1,
                  rc={"axes.spines.right": False, "axes.spines.top": False})
    
  3. 異なる種類を組み合わせたカスタムレイアウトには、Axes レベルの関数を使います:Figure レベルの関数(relplot、catplot)は独自の Figure を作成するため、既存の Axes に配置できません。異なる種類のプロット(例:散布図+バイオリンプロット+ヒートマップ)を組み合わせる場合は、plt.subplots() のグリッドを用意し、ax= を指定して Axes レベルの関数を使います。

  4. 色覚多様性に配慮したパレットを使います:sns.set_palette("colorblind") または palette="colorblind" を使うと、一般的な色覚特性の違いがある読者にも区別しやすいパレットになります。発散データには、center=0 とともに "RdBu_r" または "coolwarm" を使います。

  5. 要約プロットに個々のデータポイントを重ねます:バイオリンプロットや棒グラフでは、分布の形状やサンプル数が見えにくくなります。alpha=0.4 と小さな size を指定して stripplot または swarmplot を重ねると、要約統計量を隠さずにデータ密度を伝えられます。

  6. Figure レベルのプロットでは figsize ではなく height と aspect でサイズを指定します:Figure レベルの関数は figsize を無視します。height=(パネルあたりの高さ、インチ単位)と aspect=(パネルあたりの幅と高さの比率)を使います。Axes レベルの場合は、plt.subplots() の呼び出しで figsize を指定します。

  7. アンチパターン — Figure レベルのグリッドに plt.savefig() を呼び出す:Figure レベルの関数は FacetGrid/JointGrid オブジェクトを返します。plt.savefig() ではなく、g.savefig("out.png", dpi=300, bbox_inches="tight") で保存してください。plt.savefig() では空の Figure が保存されることがあります。

よく使うレシピ

レシピ:カスタムパレットと 300 DPI のエクスポートで、出版に適した図を作成する

使用する場面:学術誌への投稿やスライド資料向けに、複数パネルの図を作成する場合。

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

sns.set_theme(style="ticks", context="paper", font_scale=1.2,
              rc={"pdf.fonttype": 42, "ps.fonttype": 42})

rng = np.random.default_rng(7)
df = pd.DataFrame({
    "condition": np.repeat(["Control", "Treated"], 40),
    "ki67_pct":  np.concatenate([rng.normal(18, 4, 40), rng.normal(32, 6, 40)]),
    "apoptosis": np.concatenate([rng.normal(5, 1.5, 40), rng.normal(12, 2.5, 40)]),
})

custom_palette = {"Control": "#4575B4", "Treated": "#D73027"}

fig, axes = plt.subplots(1, 2, figsize=(8, 4))

# Panel A
sns.boxplot(data=df, x="condition", y="ki67_pct",
            palette=custom_palette, width=0.45, linewidth=1.2, ax=axes[0])
sns.stripplot(data=df, x="condition", y="ki67_pct",
              color="black", alpha=0.35, size=3, jitter=True, ax=axes[0])
axes[0].set_ylabel("Ki67 Positive Cells (%)")
axes[0].set_xlabel("")
axes[0].set_title("A", loc="left", fontweight="bold")

# Panel B
sns.boxplot(data=df, x="condition", y="apoptosis",
            palette=custom_palette, width=0.45, linewidth=1.2, ax=axes[1])
sns.stripplot(data=df, x="condition", y="apoptosis",
              color="black", alpha=0.35, size=3, jitter=True, ax=axes[1])
axes[1].set_ylabel("Apoptotic Cells (%)")
axes[1].set_xlabel("")
axes[1].set_title("B", loc="left", fontweight="bold")

sns.despine(trim=True)
plt.tight_layout()
plt.savefig("figure1.pdf", dpi=300, bbox_inches="tight")
plt.savefig("figure1.png", dpi=300, bbox_inches="tight")
print("Saved figure1.pdf and figure1.png at 300 DPI")
レシピ:行と列の色注釈付きクラスターヒートマップ

使用する場面:サンプルグループの注釈を付けた遺伝子発現行列を表示し、階層的クラスタリングによって共発現モジュールを明らかにする場合。

import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np
import pandas as pd

rng = np.random.default_rng(21)
n_genes, n_samples = 40, 20
conditions = ["tumor"] * 10 + ["normal"] * 10

# Simulate expression: 3 co-expression modules
expr = pd.DataFrame(
    rng.lognormal(2.5, 0.8, (n_genes, n_samples)),
    index=[f"GENE{i:03d}" for i in range(n_genes)],
    columns=[f"{c[0].upper()}{i:02d}" for i, c in enumerate(conditions)],
)
# Module 1: genes 0-13 up in tumor
expr.iloc[:14, :10]  *= 3.0
# Module 2: genes 14-27 down in tumor
expr.iloc[14:28, :10] *= 0.3
# Module 3: genes 28-39 unchanged

log_expr = np.log2(expr + 1)

# Column colors: tumor=red, normal=blue
cond_pal = {"tumor": "#C62828", "normal": "#1565C0"}
col_colors = [cond_pal[c] for c in conditions]

# Row colors: module membership
module_pal = {"up": "#EF9A9A", "down": "#90CAF9", "stable": "#C8E6C9"}
row_modules = (["up"] * 14) + (["down"] * 14) + (["stable"] * 12)
row_colors  = [module_pal[m] for m in row_modules]

g = sns.clustermap(
    log_expr,
    cmap="RdYlBu_r",
    center=log_expr.values.mean(),
    standard_scale=0,           # z-score per gene (row)
    method="ward",
    metric="euclidean",
    col_colors=col_colors,
    row_colors=row_colors,
    figsize=(14, 12),
    linewidths=0,
    cbar_pos=(0.02, 0.85, 0.03, 0.12),
    cbar_kws={"label": "Row z-score"},
    dendrogram_ratio=(0.12, 0.08),
)
g.ax_heatmap.set_xlabel("Sample", fontsize=10)
g.ax_heatmap.set_ylabel("Gene",   fontsize=10)
g.ax_heatmap.set_title("Gene Expression Clustermap", fontsize=12, pad=80)

# Manual legend for column/row annotations
legend_handles = [
    mpatches.Patch(color="#C62828", label="Tumor"),
    mpatches.Patch(color="#1565C0", label="Normal"),
    mpatches.Patch(color="#EF9A9A", label="Up in tumor"),
    mpatches.Patch(color="#90CAF9", label="Down in tumor"),
    mpatches.Patch(color="#C8E6C9", label="Stable"),
]
g.ax_heatmap.legend(handles=legend_handles, bbox_to_anchor=(1.25, 1.05),
                    loc="upper left", frameon=False, fontsize=9)

plt.savefig("clustermap_annotated.png", dpi=300, bbox_inches="tight")
print("Saved clustermap_annotated.png")

トラブルシューティング

問題原因解決策
凡例がプロットの外側に配置され、保存したファイルで切れてしまうFigure レベルの関数では、デフォルトで凡例が外側に配置されますsavefig() に bbox_inches="tight" を追加します:g.savefig("out.png", dpi=300, bbox_inches="tight")
TypeError: FacetGrid.savefig() が発生する、または空の図が保存される独自の Figure を持つ Figure レベルのグリッドに対して plt.savefig() を呼び出していますplt.savefig(...) ではなく g.savefig(...) を使用します
x 軸のカテゴリラベルが重なるラベル文字列が長く、デフォルトの回転角度では重なりますプロットの呼び出し後に plt.xticks(rotation=45, ha="right") と plt.tight_layout() を追加します
ValueError: Could not interpret value ... for parameter 'hue' が発生するデータがワイド形式です。hue マッピングにはロング形式が必要ですdf.melt(id_vars=[...], var_name="sample", value_name="expr") で変換します
KDE の帯域幅が滑らかすぎる(二峰性が失われる)デフォルトの bw_adjust=1.0 では、小規模なデータセットが過度に平滑化されますbw_adjust=0.5 に下げ、histplot でピークを確認します
clustermap が figsize を無視する古い seaborn では、Figure レベルの関数はキーワード引数として figsize を受け付けませんfigsize を直接引数として渡します:sns.clustermap(..., figsize=(12, 10))
バイオリンプロットが細い線になり、形状が表示されないKDE 推定に必要な観測数が不足していますkind="box" または kind="strip" に切り替えるか、cut=0 を指定して KDE をデータ範囲内に制限します
グループ数が多いと色を見分けられないカテゴリが 6 個を超えると、デフォルトのパレットでは色が繰り返されますsns.color_palette("husl", n_colors=N) を使用するか、最大 20 色を区別できる "tab20" を使用します
Figure レベルの関数が ax= パラメーターを無視するAxes レベルとの違いです。Figure レベルの関数は独自の Figure を作成します対応する Axes レベルの関数(scatterplot、histplot など)を ax= とともに使用します

関連スキル

  • matplotlib-scientific-plotting — seaborn と生の matplotlib を組み合わせた、低レベルの Figure 作成、カスタム注釈、統計以外のプロット形式、複数パネルのレイアウト
  • plotly-interactive-plots — ホバー、ズーム、HTML/Dash へのエクスポートに対応したインタラクティブなチャート
  • pydeseq2-differential-expression — ボルケーノ形式の散布図で使用する log2FC と p 値を生成します
  • scikit-image-processing — seaborn のカテゴリ別プロットや分布プロットで可視化する細胞形態の測定値を生成します
  • scientific-visualization — コーディング前に適切なチャート形式と配色を選ぶためのガイド

参考資料

コメント