欢迎光临
我们一直在努力

【Seaborn 结构化可视化指南】P4. 分布图与图形叠加:从 displot 到 Axes-level 控制

前言

此前分别介绍了 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 参数值

图表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 的灵活之间做出正确取舍。

    赞(0)
    未经允许不得转载:171主机测评 » 【Seaborn 结构化可视化指南】P4. 分布图与图形叠加:从 displot 到 Axes-level 控制
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址