mcpField
S
seaborn-statistical-plots

seaborn-statistical-plots

github.com/jaechang-hits数据与数据库38477 次安装6 天前更新
代码仓库

关于 seaborn-statistical-plots

seaborn-statistical-plots 是使用 Seaborn 的技能。Seaborn 是基于 matplotlib 的 Python 统计可视化库,帮助分析人员和研究者从表格数据创建分布、关系、回归及矩阵图,并支持分面图。

主要功能

  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 将坐标轴级函数(可嵌入自定义布局)与图形级函数(内置分面功能)分开,让你既能快速进行探索性分析,也能构建结构化的多面板图表。

适用场景

  • 比较不同实验条件(处理组与对照组、细胞系、时间点)下的基因表达、蛋白质丰度或测量值分布
  • 生成分组箱线图、小提琴图或条带图,同时展示汇总统计信息和单个数据点
  • 将多基因或多特征数据集中的成对相关性可视化为带注释的热图
  • 绘制连续变量之间的回归拟合及置信带(例如,细胞活力与药物浓度之间的关系)
  • 通过一次调用,按多个样本子集、组织类型或实验批次对单一图表类型进行分面
  • 使用 pairplot 快速探索新数据集,一次查看所有成对关系
  • 需要对图表元素进行像素级控制、构建复杂的混合类型布局,或绘制非统计类自定义图表时,直接使用 matplotlib
  • 输出需要具备交互性(悬停提示、缩放、平移)或嵌入 Web 应用时,使用 plotly

前置条件

  • Python 包:seaborn>=0.13、matplotlib、pandas、numpy
  • 数据要求:使用长格式(整洁格式)的 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")

核心概念

Figure-Level 与 Axes-Level 函数

Seaborn 有两类返回类型和可组合性各不相同的函数:

特性Axes-LevelFigure-Level
示例scatterplot、histplot、boxplot、heatmap、regplotrelplot、displot、catplot、lmplot
返回值matplotlib.axes.AxesFacetGrid / JointGrid / PairGrid
分面手动(自行创建子图)内置(col=、row= 参数)
尺寸设置在父级 figure 上设置 figsize=为每个分面面板设置 height= + aspect=
放置方式ax= 参数无法放入已有的坐标轴
保存plt.savefig(...)g.savefig(...)
适用场景在同一 figure 中组合不同类型的图快速查看多面板探索性视图
# 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 倍数变化与 -log10 p 值的关系(火山图样式),标注显著性,按调控状态着色,并标记排名靠前的结果。

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仅限 Figure-Level(relplot、displot、catplot、lmplot)None分类列名为每个唯一值创建一个子图面板
col_wrap仅限 Figure-LevelNoneint列数达到 N 个面板后,将后续列换到新的一行
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. 优先使用带命名列的长格式 DataFrame: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. 混合类型的自定义布局使用坐标轴级函数:图级函数(relplot、catplot)会创建自己的图形,无法放入现有的 Axes 中。组合不同类型的图表(例如,散点图 + 小提琴图 + 热力图)时,请分配一个 plt.subplots() 网格,并使用带有 ax= 的坐标轴级函数。

  4. 使用适合色觉障碍者的调色板:sns.set_palette("colorblind") 或 palette="colorblind" 会生成一组对常见色觉障碍读者而言易于区分的颜色。对于发散型数据,使用 "RdBu_r" 或 "coolwarm",并设置 center=0。

  5. 在汇总图上叠加单个数据点:小提琴图和柱状图会隐藏分布形状和样本量。叠加 stripplot 或 swarmplot,并设置 alpha=0.4 和较小的 size,可以呈现数据密度,同时不遮挡汇总统计量。

  6. 使用 height 和 aspect 调整图级图表大小,而非 figsize:图级函数会忽略 figsize。使用 height=(每个面板的高度,单位为英寸)和 aspect=(每个面板的宽高比)。对于坐标轴级函数,在调用 plt.subplots() 时设置 figsize。

  7. 反模式 — 对图级网格调用 plt.savefig():图级函数会返回 FacetGrid/JointGrid 对象。请使用 g.savefig("out.png", dpi=300, bbox_inches="tight") 保存,而不是使用 plt.savefig(),后者可能会保存出空白图形。

常见用法

用法:使用自定义调色板和 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")

故障排查

问题原因解决方案
图例放在绘图区域外,保存的文件中被裁剪图级函数默认会将图例放在外部在 savefig() 中添加 bbox_inches="tight":g.savefig("out.png", dpi=300, bbox_inches="tight")
出现 TypeError: FacetGrid.savefig() 或保存的图像为空白对拥有独立图像的图级网格调用了 plt.savefig()改用 g.savefig(...),而不是 plt.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 中,图级函数不接受 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"
图级函数忽略 ax= 参数这是坐标轴级别的区别:图级函数会创建自己的图像使用对应的坐标轴级函数(scatterplot、histplot 等)并传入 ax=

相关技能

  • matplotlib-scientific-plotting — 底层图像构建、自定义注释、非统计图表类型,以及混合 seaborn 与原始 matplotlib 的多面板布局
  • plotly-interactive-plots — 支持悬停、缩放和 HTML/Dash 导出的交互式图表
  • pydeseq2-differential-expression — 生成用于火山图式散点图的 log2FC 和 p 值
  • scikit-image-processing — 生成细胞形态测量值,并使用 seaborn 分类图/分布图进行可视化
  • scientific-visualization — 编码前选择合适图表类型和配色方案的决策指南

参考资料

评论

支持 Markdown