关于 seaborn-statistical-plots
seaborn-statistical-plots 是使用 Seaborn 的技能。Seaborn 是基于 matplotlib 的 Python 统计可视化库,帮助分析人员和研究者从表格数据创建分布、关系、回归及矩阵图,并支持分面图。
主要功能
- 01分布图与分类数据图
- 02关系图与回归图
- 03热图与聚类矩阵
- 04成对关系图与联合图
- 05按数据子集创建分面图
适用场景
- 01比较不同实验条件下的测量值
- 02展示分组分布与单个观测值
- 03探索多个特征间的相关性
- 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-Level | Figure-Level |
|---|---|---|
| 示例 | scatterplot、histplot、boxplot、heatmap、regplot | relplot、displot、catplot、lmplot |
| 返回值 | matplotlib.axes.Axes | FacetGrid / 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 | 列名(分类或连续) | 使用颜色编码变量;自动显示图例 |
style | scatterplot、lineplot | None | 分类列名 | 使用标记形状或线条虚线样式编码变量 |
size | scatterplot、lineplot | None | 分类或连续列 | 使用点或线条的大小编码变量 |
col / row | 仅限 Figure-Level(relplot、displot、catplot、lmplot) | None | 分类列名 | 为每个唯一值创建一个子图面板 |
col_wrap | 仅限 Figure-Level | None | int | 列数达到 N 个面板后,将后续列换到新的一行 |
estimator | barplot、pointplot | "mean" | "mean"、"median"、任意可调用对象 | 在每个类别内应用的聚合函数 |
errorbar | barplot、lineplot、pointplot | ("ci", 95) | "ci"、"sd"、"se"、"pi"、None | 估计值周围显示的误差条类型 |
stat | histplot | "count" | "count"、"frequency"、"density"、"probability" | 应用于直方图柱高的归一化方式 |
bw_adjust | kdeplot、violinplot | 1.0 | 0.1–3.0 | KDE 带宽乘数;值越小曲线越尖锐,值越大曲线越平滑 |
multiple | histplot、kdeplot | "layer" | "layer"、"stack"、"dodge"、"fill" | 重叠的 hue 分组的绘制方式 |
inner | violinplot | "box" | "box"、"quart"、"point"、"stick"、None | 小提琴图内部的标注方式 |
standard_scale | clustermap | None | 0(行)、1(列) | 聚类前执行 Z 分数归一化所沿的轴 |
dodge | boxplot、violinplot、stripplot | 不固定 | True、False | 沿坐标轴分开显示按 hue 分组的元素 |
context | set_theme() | "notebook" | "paper"、"notebook"、"talk"、"poster" | 根据输出媒介调整字体和线宽 |
最佳实践
-
优先使用带命名列的长格式 DataFrame:Seaborn 的语义映射(
hue、style、size)会直接从列名中读取变量名称。传入原始数组会丢失坐标轴标签和图例。使用pd.melt()将宽格式数据转换为长格式。 -
在脚本开头调用一次
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}) -
混合类型的自定义布局使用坐标轴级函数:图级函数(
relplot、catplot)会创建自己的图形,无法放入现有的Axes中。组合不同类型的图表(例如,散点图 + 小提琴图 + 热力图)时,请分配一个plt.subplots()网格,并使用带有ax=的坐标轴级函数。 -
使用适合色觉障碍者的调色板:
sns.set_palette("colorblind")或palette="colorblind"会生成一组对常见色觉障碍读者而言易于区分的颜色。对于发散型数据,使用"RdBu_r"或"coolwarm",并设置center=0。 -
在汇总图上叠加单个数据点:小提琴图和柱状图会隐藏分布形状和样本量。叠加
stripplot或swarmplot,并设置alpha=0.4和较小的size,可以呈现数据密度,同时不遮挡汇总统计量。 -
使用
height和aspect调整图级图表大小,而非figsize:图级函数会忽略figsize。使用height=(每个面板的高度,单位为英寸)和aspect=(每个面板的宽高比)。对于坐标轴级函数,在调用plt.subplots()时设置figsize。 -
反模式 — 对图级网格调用
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 — 编码前选择合适图表类型和配色方案的决策指南
参考资料
- Seaborn 官方文档 — API 参考、教程和示例库
- Seaborn 示例库 — 所有图表类型的可视化索引
- Seaborn GitHub — 源代码和问题跟踪器
- Waskom ML(2021)。“seaborn:统计数据可视化。” Journal of Open Source Software,6(60),3021。https://doi.org/10.21105/joss.03021