前言
此前分别介绍了 Figure-level 的关系图 relplot 与分类图 catplot 的使用方法,接下来将系统讲解分布图 displot。与分类图不同,在实际应用中分布图极少单独出现,我们往往会将多种图形叠加使用,例如直方图叠加密度曲线、密度曲线叠加地毯图等,这种图形叠加的需求需要我们回到 Axes-level 接口。
因此,与上一篇聚焦 catplot 不同,本文在介绍分布图时,首先介绍 displot 这一 Figure-level 接口,再重点介绍 histplot / kdeplot / ecdfplot 等 Axes-level 函数。,
环境:Seaborn 0.13.2 | Matplotlib 3.10.6 | Pandas 2.3.3 | NumPy 2.4.6
一、分布图的两种层级:为何需要两套接口
1. Figure-level:displot 为分布图的 Figure-level 入口,能够一键分面、全局布局,但难以实现复杂的图形叠加。
2. Axes-level:histplot / kdeplot / ecdfplot 为各具体分布图的 Axes-level 入口,能够对每个 Axes 精细控制、实现图形叠加、嵌入复杂的子图等。
精确控制坐标轴、共享刻度、复杂组合等需求,需要我们回到 Axes-level 接口。
二、Figure-level 入口:sns.displot
displot 的核心参数
sns.displot(
data=df, # 数据
kind='hist', # 'hist'/ 'kde'/ 'ecdf'
x='数值变量', # 横轴(单变量分布图只关注一个数值变量的分布形态)
# y='数值变量', # 纵轴(双变量时使用)
hue='颜色分组特征', # 颜色区分(所有 kind 均支持)
col='按列分面特征', # 按列分面
col_wrap=…, # 一行最多子图列数(仅在与 col 配合时使用)
row='按行分面特征', # 按行分面
)
与关系图和分类图不同,单变量分布图只关注一个数值变量的分布形态,因此通常只需指定 x,无需指定 y。y 参数仅用于双变量分布时。
displot 的 hue 分组映射、分面逻辑也与 relplot / catplot 完全一致,但不支持 style / size 参数,因分布图仅依赖颜色和位置
kind 参数值
| 直方图 | 'hist' | histplot | 看分布形状,直观但受分箱影响 |
| 核密度估计图(密度曲线图) | 'kde' | kdeplot | 看分布形状,平滑,强调趋势,弱化噪声 |
| 经验分布函数图(累积分布图) | 'ecdf' | ecdfplot | 看累积分布,展示累积概率,抗异常值,适合小样本与统计推断 |
示例
使用 peguins 数据集,横轴为 bill_length_mm,按 'species' 分组分别绘制直方图、核密度估计图、经验分布函数图
import seaborn as sns
import matplotlib.pyplot as plt
df = sns.load_dataset('penguins')
# 为展示三种 kind 的差异,以下示例分别绘制;实际可根据需要复用同一 FacetGrid
g1 = sns.displot(data=df, kind='hist', x='bill_length_mm', hue='species') # 直方图
g2 = sns.displot(data=df, kind='kde', x='bill_length_mm', hue='species') # 核密度估计图
g3 = sns.displot(data=df, kind='ecdf', x='bill_length_mm', hue='species') # 经验分布函数图
plt.show()
绘图结果如下:

直方图

核密度估计图

经验分布函数图
displot 的适用边界
displot 适合快速对比多组分布,不适合用来精细控制叠加层的场景
三、布局控制:复用 FacetGrid 的调整方法
FacetGrid布局调整 g.figure.subplots_adjust
使用 Seaborn 绘制含中文、外置图例的图形时,在拿到 FacetGrid 对象 g 后,不要使用 plt.tight_layout()、g.tight_layout() 或 bbox_inches='tight'(保存时),应通过 figure.subplots_adjust() 手动控制画布边距,保证标题、轴标签和图例不被裁剪。
g.figure.subplots_adjust(
top=0.92, # 顶部留白,用于总标题 suptitle
bottom=0.08, # 底部留白,用于 x 轴标签
left=0.10, # 左侧留白,用于 y 轴标签
right=0.82, # 右侧留白,用于外置图例(关键)
hspace=0.25, # 行间距(子图纵向)
wspace=0.20 # 列间距(子图横向)
)
四、Axes-level 入口:sns.histplot / sns.kdeplot / sns.ecdfplot
1. 直方图 sns.histplot
直方图用箱子近似分布形状,是对连续变量的离散化估计
把 x 轴切成若干区间(bin)→ 统计每个区间内的观测数 → 用高度代表频数 / 密度
常用参数
sns.histplot(
data=df, # 数据
x='数值变量', # 横轴(单变量分布)
# y='数值变量', # 纵轴(双变量直方图时使用)
hue='颜色分组特征',
bins='auto', # 箱子数量(默认 'auto',由算法自动估算;也可传入整数或序列)
binwidth=None, # 箱子宽度(与 bins 互斥,更直观)
stat='count', # 柱子高度:'count'(频数,默认)、'density'(密度)、'probability'(概率)
multiple='layer', # hue 分组展示方式:'layer'(叠放,默认)、'dodge'(并排)、'stack'(堆叠)、'fill'(填满)
element='bars', # 柱子样式:'bars'(矩形,默认)、'step'(阶梯线)、'poly'(多边形)
cumulative=False, # 是否累积:False 普通直方图,True 累积分布(类似 ECDF)
kde=False, # 是否叠加核密度估计曲线(默认 False;叠加时建议配合 stat='density')
# 以下为常用美化参数,按需使用
# alpha=0.7, # 指定柱子透明度(重叠严重时降低,论文常设为 0.6~0.8)
# edgecolor='white', # 指定柱子边缘颜色(常用白色 'white' 增强边界感)
# linewidth=1, # 指定柱子边缘线宽
)
bins:箱子数量
默认自动估算,也可手动指定:
对同一份数据使用不同 bins,可能得到完全不同的统计结论
例如:
使用 penguins 数据集,横轴 'bill_length_mm'(喙长 / mm),对 'species' 为 'Gentoo' 的企鹅绘制直方图,一行三列三幅子图,分别设置分箱数为 2、10、30,观察绘制的图形
import seaborn as sns
import matplotlib.pyplot as plt
df = sns.load_dataset('penguins')
gentoo = df[df["species"] == "Gentoo"] # Gentoo 企鹅的数据
fig, axes = plt.subplots(1, 3, figsize=(15, 4), dpi=100)
# 1. bins 过少,过于平滑
sns.histplot(
data=gentoo,
x='bill_length_mm', # 观察 bill_length 分布情况
bins=2,
ax=axes[0],
)
axes[0].set_title('bins=2')
# 2. bins 适中,接近真实分布
sns.histplot(
data=gentoo,
x='bill_length_mm',
bins=10,
ax=axes[1],
)
axes[1].set_title('bins=10')
# 3. bins 过多,噪声影响明显
sns.histplot(
data=gentoo,
x='bill_length_mm',
bins=30,
ax=axes[2],
)
axes[2].set_title('bins=30')
plt.show()
绘图结果如下:

可见 bins 过少则过于平滑;bins 适中,更接近真实分布;bins 过多,噪声影响明显
binwidth:箱子宽度
binwidth 和 bins不能同时生效
binwidth 直接指定每一个箱子有多宽,表示“每 N 个单位统计一次”,更直观,推荐优先使用
例如:
使用 penguins 数据集,横轴 'bill_length_mm'(喙长 / mm),按 'species' 分组绘制直方图,看喙长每 2mm 有多少企鹅
import seaborn as sns
import matplotlib.pyplot as plt
df = sns.load_dataset('penguins')
sns.histplot(
data=df,
x='bill_length_mm', # 观察 bill_length 分布情况
hue='species', # 分组
binwidth=2, # 设置每 2mm 一个分箱
)
plt.show()
绘图结果如下:

stat:决定比较基准,柱子高度的含义
| stat 参数值 | 柱高含义 | 柱高计算公式 |
| 'count'(默认) | 原始频数,反映各区间内的观测数量 | 柱高 = 落在该区间的个体数 |
| 'density' | 密度,所有柱子面积之和等于 1 | 柱高 = 该区间数量 / (该组总数量 × 箱子宽度) |
| 'probability' | 概率,所有柱子高度之和等于 1 | 柱高 = 该区间数量 / 该组总数量 |
只要分箱宽度相等,stat='count'、'density' 与 'probability' 所呈现的分布形状就完全一样,区别仅在于纵轴的尺度。
当分箱宽度不相等时,stat='count'、'probability' 分布形状仍然一致,而 'density' 分布形状完全不同。'density' 在高度计算中除以了箱子宽度,因此其高度的大小真正代表密度的大小,其余两种面积的大小代表绝对、相对数量的大小。
例如:
使用 penguins 数据集,横轴 'bill_length_mm'(喙长 / mm),按 'species' 分组绘制直方图,一行三列子图,分别指定 stat='count'、'density'、'probability',透明度为 0.7,观察绘制的图形
以下对比等宽/不等宽分箱时图像差异
(1)默认等宽分箱
# 等宽
import seaborn as sns
import matplotlib.pyplot as plt
df = sns.load_dataset('penguins')
fig, axes = plt.subplots(1, 3, figsize=(15, 4), dpi=100)
# 1. 频数
sns.histplot(
data=df,
x='bill_length_mm', # 观察 bill_length 分布情况
hue='species',
stat='count',
alpha=0.7,
ax=axes[0],
)
axes[0].set_title('stat=\\'count\\'')
# 2. 密度
sns.histplot(
data=df,
x='bill_length_mm',
hue='species',
stat='density',
alpha=0.7,
ax=axes[1],
)
axes[1].set_title('stat=\\'density\\'')
# 3. 概率
sns.histplot(
data=df,
x='bill_length_mm',
hue='species',
stat='probability',
alpha=0.7,
ax=axes[2],
)
axes[2].set_title('stat=\\'probability\\'')
plt.subplots_adjust(wspace=0.4)
plt.show()
绘图结果如下:

可见三幅图形状相同,仅尺度不同(纵轴含义不同)
(2)人为指定的不等宽分箱 bins = [30, 38, 43, 48, 60]
# 不等宽(bins=[30, 38, 43, 48, 60])
import seaborn as sns
import matplotlib.pyplot as plt
df = sns.load_dataset('penguins')
bins=[30, 38, 43, 48, 60]
fig, axes = plt.subplots(1, 3, figsize=(15, 4), dpi=100, sharey=False)
# 1. 频数
sns.histplot(
data=df,
x='bill_length_mm', # 观察 bill_length 分布情况
bins=bins,
hue='species',
stat='count',
alpha=0.7,
ax=axes[0],
)
axes[0].set_title('stat=\\'count\\'')
# 2. 密度
sns.histplot(
data=df,
x='bill_length_mm',
bins=bins,
hue='species',
stat='density',
alpha=0.7,
ax=axes[1],
)
axes[1].set_title('stat=\\'density\\'')
# 3. 概率
sns.histplot(
data=df,
x='bill_length_mm',
bins=bins,
hue='species',
stat='probability',
alpha=0.7,
ax=axes[2],
)
axes[2].set_title('stat=\\'probability\\'')
plt.subplots_adjust(wspace=0.4)
plt.show()
绘图结果如下:

可见在区间不等宽情景下,密度图与另外两幅图有了明显形状差别
multiple:决定同一 bin 下不同 hue 取值的摆放方式
| multiple 参数值 | 空间分配方式 | 纵轴含义 | 使用场景 |
| 'layer'(默认) | 叠放(透明叠加) | 各组独立计数 / 密度 | 看分布重叠,适合探索 |
| 'dodge' | 并排(左右分开) | 各组独立计数 / 密度 | 比较各组在同一 bin 内的高度,适合汇报 |
| 'stack' | 堆叠(从下往上累加) | 高度为累计值 | 看总量 + 各组贡献占比 |
| 'fill' | 堆叠后归一化(填满至 1) | 高度为组内占比 | 看比例,忽略总量差异 |
四种方式的差异,仅在于几何位置的摆放,是在 stat 基础上进行的,不改变 stat 的数学定义
例如:
使用 penguins 数据集,横轴 'bill_length_mm'(喙长 / mm),按 'species' 分组绘制直方图,二行二列子图,stat='density',分别指定 multiple='layer'、'dodge'、'stack'、'fill',透明度为 0.5,观察绘制的图形
import seaborn as sns
import matplotlib.pyplot as plt
df = sns.load_dataset('penguins')
fig, axes = plt.subplots(2, 2, figsize=(12, 12))
axs = axes.flatten()
multiples = ['layer', 'dodge', 'stack', 'fill']
# 分别绘制四幅子图
for i in range(4):
sns.histplot(
data=df,
x='bill_length_mm',
hue='species',
stat='density',
multiple=multiples[i], # 方法 i
alpha=0.5,
ax=axs[i], # 子图 i
)
axs[i].set_title(f'multiple=\\'{multiples[i]}\\'')
plt.subplots_adjust(wspace=0.2, hspace=0.2)
plt.show()
绘图结果如下:

直方图使用小结
1. 想看分布形状:先用 `binwidth` 控制粒度,再用 `stat='density'` 统一尺度
2. 多组对比:
看重叠 → multiple='layer'
看高度 → multiple='dodge'
看占比 → multiple='fill'
3. 论文作图:
透明度 alpha=0.7
边缘 edgecolor='white'
避免 cumulative=True(除非专门展示累积分布)
2. 核密度估计图 sns.kdeplot
平滑的密度曲线,看“趋势”而非“细节”
常用参数
示例
注意:尾部外推的风险
3. 经验分布函数图 sns.ecdfplot
累积概率,不受异常值影响,论文级图表
常用参数
示例
注意:在数据量少、有极端值时用它替代直方图,效果更好
五、sns.histplot / sns.kdeplot / sns.ecdfplot 统一图例控制的一种方案
若要绘制单子图或共享图例的多子图,可参考以下做法;若无需进行该操作则可以跳过。
在 histplot 中,若指定了 hue 参数,则 Seaborn 会自动为每一个子图生成图例。
在绘图时显式设置 legend=False 来关闭 Axes 内置的图例,随后手动构造 labels 与 handles 传递给 fig.legend() 生成图例
1. labels 构造方法
labels 需要为列表,从原数据、我们指定为 hue 的特征来提取:
例如:
在 peguins 数据集中指定 'species' 为 hue,则用 df.unique() 方法去重提取各类别名称,再转为列表
labels = df['species'].unique().tolist() # 需要转为列表类型
2. handles 构造方法
handles 需要为列表,因此使用列表生成式构造
以矩形 Patch 构造 handles 作为柱体图例
from matplotlib.patches import Patch
palette = sns.color_palette() # 调取当前调色板,保证和图上绘制的颜色一致
handles = [ # 列表生成式,构造矩形 Patch
Patch(
facecolor=palette[i], # facecolor 从调色板获取颜色
linewidth=1
)
for i in range(len(labels))
]
以线条 Line2D 构造 handles 作为线形图例
palette = sns.color_palette() # 调取当前调色板,保证和图上绘制的颜色一致
handles = [ # 列表生成式构造 Line2D
Line2D(
[0], [0],
color=palette[i], # color 从调色板获取颜色
lw=4, # lw 控制线宽,单位 pt;KDE 常用 2,折线图可酌情调整
solid_capstyle='butt' # 平切防止画出框外
)
for i in range(len(labels))
]
3. 统一图例构造 fig.legend
例如:
以基于 peguins 数据集绘制的图像为例
(1)单子图,KDE(线形)
# 单子图
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
df = sns.load_dataset('penguins')
fig, ax =plt.subplots(figsize=(10, 6), dpi=100)
sns.kdeplot(
data=df,
x='bill_length_mm',
hue='species',
legend=False, # 关闭子图图例
)
palette = sns.color_palette()
labels = df['species'].unique().tolist()
handles = [ # 列表生成式构造 Line2D
Line2D(
[0], [0],
color=palette[i], lw=2,
solid_capstyle='butt' # 平切防止画出框外
)
for i in range(len(labels))
]
fig.legend( # 例如图例外置右侧中部
handles=handles, # 传入 handles
labels=labels, # 传入 labels
title='species',
bbox_to_anchor=(0.82, 0.5), # 图例对齐水平方向 0.82,配合 plt.subplots_adjust(right=0.8, …) 使用
loc='upper left'
)
plt.subplots_adjust(right=0.8, wspace=0.2, hspace=0.2)
plt.show()
绘图结果如下:

(2)多子图,直方图(柱体)
# 多子图统一图例
import seaborn as sns
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
df = sns.load_dataset('penguins')
fig, axes = plt.subplots(2, 2, figsize=(12, 12))
axs = axes.flatten()
multiples = ['layer', 'dodge', 'stack', 'fill']
# 分别绘制四幅子图
for i in range(4):
sns.histplot(
data=df,
x='bill_length_mm',
hue='species',
stat='density',
multiple=multiples[i], # 方法 i
alpha=0.5,
ax=axs[i], # 子图 i
legend=False # 关闭子图图例
)
axs[i].set_title(f'multiple=\\'{multiples[i]}\\'')
# 构造图例
palette = sns.color_palette()
labels = df['species'].unique().tolist()
handles=[
Patch(
facecolor=palette[i],
linewidth=1
)
for i in range(len(labels))
]
fig.legend( # 例如图例外置右侧中部
handles=handles, # 传入 handles
labels=labels, # 传入 labels
title='species',
bbox_to_anchor=(0.82, 0.5), # 图例对齐水平方向 0.82,配合 plt.subplots_adjust(right=0.8, …) 使用
bbox_transform=fig.transFigure, # 根据整个画布对齐(否则对齐当前活动子图)
loc='upper left'
)
plt.subplots_adjust(right=0.8, wspace=0.2, hspace=0.2)
plt.show()
绘图结果如下:

六、实战核心:图形叠加的艺术
1. 直方图 + KDE:最常用的组合
七、分布图选型速查表
|
看大致分布形状 |
直方图 |
bins |
最直观 |
|
看平滑趋势 |
KDE |
bw_adjust |
不适合小样本 |
|
看累积概率 |
ECDF |
stat |
抗异常值 |
|
对比多组分布 |
叠放直方图 / 多 KDE |
hue, alpha |
避免过于花哨 |
|
展示原始数据 |
直方图 + Rug |
rug=True |
诚实的体现 |
八、总结:从“画分布”到“理解分布”
displot 能够高效地探索分布
histplot/ kdeplot 能够准确地表达分布
需要在于根据数据特点,在 displot 的便捷与 Axes-level 的灵活之间做出正确取舍。





