首页
/ Data-Science-For-Beginners 第10课实战:用直方图与密度曲线可视化数据分布(Matplotlib + Seaborn 详解)

Data-Science-For-Beginners 第10课实战:用直方图与密度曲线可视化数据分布(Matplotlib + Seaborn 详解)

2026-09-05 15:13:37作者:庞队千Virginia

本文基于 Data-Science-For-Beginners 仓库 10-visualization-distributions 一课展开:以密歇根州鸟类数据集(birds.csv)为例,系统讲解如何用 Matplotlib 直方图刻画数值型分布、用分组叠加直方图刻画分类变量分布、再用 Seaborn 的 KDE 密度曲线做平滑可视化。读完本文,你将能独立完成从 bins 调参、数据过滤去偏到 kdeplot 分组密度绘图的完整分布分析流程,并理解每个关键参数的作用。

1. 课程背景:birds 数据集

本课承接第 9 课(用散点图发现 Minnesota 鸟类数据中的异常值、对比不同目鸟类的最大体长)。分布(distribution)回答的是"数据沿某个坐标轴如何组织"的问题,例如:400 多种鸟的最大翼展或最大体重的整体分布是什么样的?

课程使用仓库根目录下的 birds.csv,共 443 条鸟类记录、13 个字段:名称、学名、类别(Category)、目(Order)、科(Family)、属(Genus)、保护状态(ConservationStatus)以及体长/体重/翼展的上下限(MinLength、MaxLength、MinBodyMass、MaxBodyMass、MinWingspan、MaxWingspan)。在课程文件夹下的 notebook.ipynb 中导入 Pandas 与 Matplotlib 并加载数据(代码从课程文件夹运行,故数据路径为 ../../data/birds.csv):

import pandas as pd
import matplotlib.pyplot as plt
birds = pd.read_csv('../../data/birds.csv')
birds.head()

前 5 行如下(与原课程文档一致):

Name ScientificName Category Order Family Genus ConservationStatus MinLength MaxLength MinBodyMass MaxBodyMass MinWingspan MaxWingspan
0 Black-bellied whistling-duck Dendrocygna autumnalis Ducks/Geese/Waterfowl Anseriformes Anatidae Dendrocygna LC 47 56 652 1020 76 94
1 Fulvous whistling-duck Dendrocygna bicolor Ducks/Geese/Waterfowl Anseriformes Anatidae Dendrocygna LC 45 53 712 1050 85 93
2 Snow goose Anser caerulescens Ducks/Geese/Waterfowl Anseriformes Anatidae Anser LC 64 79 2050 4050 135 165
3 Ross's goose Anser rossii Ducks/Geese/Waterfowl Anseriformes Anatidae Anser LC 57.3 64 1066 1567 113 116
4 Greater white-fronted goose Anser albifrons Ducks/Geese/Waterfowl Anseriformes Anatidae Anser LC 64 81 1930 3310 130 165

2. 快速窥探分布:散点图的局限

课程先给出一个快速概览手段——散点图:

birds.plot(kind='scatter', x='MaxLength', y='Order', figsize=(12,8))
plt.title('Max Length per Order')
plt.ylabel('Order')
plt.xlabel('Max Length')
plt.show()

它能给出"各目鸟类最大体长的整体布局",但并不是展示真实分布的最优方式——点的密集程度依赖肉眼估算。展示真实分布的任务通常由直方图(Histogram)完成。

3. 直方图:bins 参数、过滤与 2D 直方图

直方图类似条形图,但通过柱子的起伏(rise and fall)呈现分布形态;它要求数值型数据。构建方式是把数据数组划分成若干更小的"箱"(bins),展示各区间内取值数量。

3.1 基础直方图与 bins 调参

展示整个数据集的 MaxBodyMass 分布:

birds['MaxBodyMass'].plot(kind='hist', bins=10, figsize=(12,12))
plt.show()

可以看到,数据集中 400 多种鸟的 MaxBodyMass 大多落在 2000 以下的区间(左侧偏态明显,由大型雁形目物种把整体范围拉高所致)。

bins 提高到 30,可以获得更细粒度的分布:

birds['MaxBodyMass'].plot(kind='hist', bins=30, figsize=(12,12))
plt.show()

MaxBodyMass 分布(bins=30)

bins 是直方图最核心的参数:过小会掩盖分布细节(如 10 箱时只能看到"大部分集中在低端"),过大会引入噪声。课程建议尝试其他过滤器与数据点;若要查看完整分布,可去掉 ['MaxBodyMass'] 的列过滤。

3.2 用过滤消除偏态

上面的图偏态(skewed)明显,是因为少数超重型物种撑大了横轴范围。只筛选体重在 1g 到 60g 之间的小鸟、并使用 40 个 bins,可以得到一个"不那么偏态"的图:

filteredBirds = birds[(birds['MaxBodyMass'] > 1) & (birds['MaxBodyMass'] < 60)]
filteredBirds['MaxBodyMass'].plot(kind='hist', bins=40, figsize=(12,12))
plt.show()

这里 filteredBirds 是一个用布尔掩码过滤出的子 DataFrame,后续文本数据与 KDE 部分都复用它。

3.3 2D 直方图:观察两个分布的关系

Matplotlib 还提供内置的 2D 直方图来比较两个变量的联合分布,并用更亮的颜色标示"汇聚点"(convergence):

x = filteredBirds['MaxBodyMass']
y = filteredBirds['MaxLength']

fig, ax = plt.subplots(tight_layout=True)
hist = ax.hist2d(x, y)

MaxBodyMass 与 MaxLength 的 2D 直方图

从图中可以看出:体重与体长之间存在沿预期方向的相关性,并且有一个特别强的汇聚点——这正是用颜色密度代替散点堆叠的价值所在。

4. 文本(分类)数据的分布:保护状态 × 最小翼展

直方图默认面向数值数据,那么"按文本维度看分布"怎么办?birds 数据集中每个物种带有 IUCN 红色名录(IUCN Red List Categories)风格的保护状态缩写:

缩写 含义
CR Critically Endangered(极危)
EN Endangered(濒危)
EX Extinct(灭绝)
LC Least Concern(无危)
NT Near Threatened(近危)
VU Vulnerable(易危)

课程把过滤后的 filteredBirds 按 6 种保护状态分别取出 MinWingspan,用 6 条不同颜色、半透明(alpha=0.5)、相同分箱(bins=20)的直方图叠加,形成带标签的分类分布对比:

x1 = filteredBirds.loc[filteredBirds.ConservationStatus=='EX', 'MinWingspan']
x2 = filteredBirds.loc[filteredBirds.ConservationStatus=='CR', 'MinWingspan']
x3 = filteredBirds.loc[filteredBirds.ConservationStatus=='EN', 'MinWingspan']
x4 = filteredBirds.loc[filteredBirds.ConservationStatus=='NT', 'MinWingspan']
x5 = filteredBirds.loc[filteredBirds.ConservationStatus=='VU', 'MinWingspan']
x6 = filteredBirds.loc[filteredBirds.ConservationStatus=='LC', 'MinWingspan']

kwargs = dict(alpha=0.5, bins=20)

plt.hist(x1, **kwargs, color='red',    label='Extinct')
plt.hist(x2, **kwargs, color='orange', label='Critically Endangered')
plt.hist(x3, **kwargs, color='yellow', label='Endangered')
plt.hist(x4, **kwargs, color='green',  label='Near Threatened')
plt.hist(x5, **kwargs, color='blue',   label='Vulnerable')
plt.hist(x6, **kwargs, color='gray',   label='Least Concern')

plt.gca().set(title='Conservation Status', ylabel='Min Wingspan')
plt.legend()

这里的技术要点:用 .loc[条件, 列] 对分类字段做子集提取;用 kwargs 字典统一传递 alpha(透明度,使叠加时彼此可见)与 bins;最后用 plt.legend() 显示标签。

课程结论:最小翼展与保护状态之间似乎没有明显的相关性,并鼓励读者用同样方法测试数据集的其他字段、尝试不同过滤器,看能否找到真正的相关性。

5. 密度图(KDE):用 Seaborn 画平滑分布

5.1 从直方图到密度曲线

前面看到的直方图都是"阶梯状"的,不会形成平滑弧线。要展示更平滑的密度图,可以引入 Seaborn:

import seaborn as sns
sns.kdeplot(filteredBirds['MinWingspan'])
plt.show()

MinWingspan 的 KDE 密度曲线

曲线形态与前面最小翼展直方图对应,只是更平滑。课程引用了 Seaborn 官方文档对 KDE 的定位:相对于直方图,KDE 能产生更不杂乱、更易于解读的图(尤其是绘制多个分布时),但当底层分布有界或不平滑时,KDE 也可能引入失真——"与直方图一样,表现质量也依赖于好的平滑参数选择"。换句话说,离群值仍然会让你的图"表现糟糕"。

5.2 bw_adjust:控制平滑程度

把第 3 节那张"锯齿状"的 MaxBodyMass 直方图用 KDE 重画:

sns.kdeplot(filteredBirds['MaxBodyMass'])
plt.show()

如果想要"平滑但不过度平滑"的曲线,调整 bw_adjust 参数(带宽调整因子,取值越小带宽越窄、曲线越贴近原始波动):

sns.kdeplot(filteredBirds['MaxBodyMass'], bw_adjust=0.2)
plt.show()

课程要求读者阅读该类型图的全部可用参数并动手实验。

5.3 分组密度:按鸟类目展示体重密度

只需几行代码,就能展示按鸟类目(Order)分组的最大体重密度,填充色块 + 各自归一化,适合对比多个子群体的形状:

sns.kdeplot(
   data=filteredBirds, x="MaxBodyMass", hue="Order",
   fill=True, common_norm=False, palette="crest",
   alpha=.5, linewidth=0,
)

按 Order 分组填充的 MaxBodyMass 密度图

关键参数含义:hue 指定分组字段;fill=True 填充曲线下区域;common_norm=False 表示各组独立归一化(比较"形状"而非"总量");palette/alpha/linewidth 分别控制配色板、透明度与轮廓线宽。

5.4 双变量多密度叠加

还可以在同一张图里映射多个变量的密度。例如把体长(MinLength × MaxLength)与保护状态叠加:

sns.kdeplot(data=filteredBirds, x="MinLength", y="MaxLength", hue="ConservationStatus")

课程由此引出进一步思考:按体长聚类的 "Vulnerable"(易危)鸟群聚集是否有生物学意义?这正是一个从图表走向研究问题的入口。

6. 仓库实现佐证:solution notebook 中的真实运行证据

以上全部代码在仓库内都有可直接运行的完整实现:solution/notebook.ipynb 依次保存了"基础直方图 → 调整 bins → 过滤后新直方图 → MaxBodyMass×MaxLength 的 2D 直方图 → 保护状态叠加直方图 → MinWingspan KDE → MaxBodyMass KDE → bw_adjust 实验 → MinLength×MaxLength 双变量 KDE(hue=ConservationStatus)"全部单元格的执行输出,可与本文各节逐一对应复现。

从该 notebook 的运行记录还能得到两条实操层面的事实(均来自其保存的 stderr/警告输出,可推断为当时的运行环境特性):

  1. 在较早的 Matplotlib/Seaborn 组合下执行 kdeplot 会触发 FutureWarning: Support for multi-dimensional indexing (e.g. obj[:, None]) is deprecated...,属于版本过渡期告警,不影响出图;
  2. 双变量 KDE(MinLength×MaxLength)运行时会提示 UserWarning: Dataset has 0 variance; skipping density estimate.——说明某个(小样本)分组在某个方向上方差为 0 而被跳过。可以推断:分组 KDE 对样本量极少的类别并不稳健,这也是课程建议"研究 VU 聚集是否有意义"背后的方法论提醒。

另请注意:课程 README 中 2D 直方图小节还演示了 from matplotlib import colors / PercentFormatter 的可选导入(见 solution notebook 对应单元格),用于自定义 2D 直方图的颜色标尺。

7. 关键参数速查

参数 所属函数 作用与取值说明
bins plot(kind='hist') / plt.hist 分箱数量;本课用了 10、30、40、20,越大粒度越细但越易引入噪声
figsize plot 画布尺寸(英寸),本课统一使用 (12,12) 或默认
alpha plt.hist 柱条透明度(0~1);叠加多个直方图时 0.5 可保证层次可见
label / plt.legend() plt.hist 图例文字与显示,用于区分分类分组
color plt.hist 每条直方图的颜色,本课按 6 种保护状态一一指定
bw_adjust sns.kdeplot 带宽调整因子(正数);越小越"欠平滑",越大越"过平滑"
hue sns.kdeplot 按指定列分组绘制多条密度曲线
fill / common_norm sns.kdeplot 是否填充;是否共享归一化基准(False 便于比较形状)
palette / linewidth sns.kdeplot 分组配色板;曲线轮廓线宽(0 即无轮廓)

8. 作业、挑战与 R 语言版本

  • 课后作业assignment.md 要求换一个数据集(例如来自 Kaggle 的数据源)自建 notebook 讲一个"故事",且必须使用直方图;评分标准(Rubric)为:优秀 = 有完整注释(含数据来源)且至少使用 5 个直方图发现数据事实;及格 = 注释不完整或有 bug;待改进 = 无注释且含 bug。
  • 课堂挑战:在网络上搜索直方图的良好使用案例,思考它们如何用直方图展示分布、应用于哪些领域。
  • 课后自修:阅读 Seaborn kdeplot 文档,理解"一个或多个维度上的连续概率密度曲线"这一概念。
  • R 语言版本:仓库同时提供等价的 R/ggplot2 实现,见 R 版课程,其中用 geom_histogram(bins=10)coord_flip() 完成同样的分布可视化流程,便于与 Python 版本对照学习。

9. 小结

本课在 Data-Science-For-Beginners 的"3-Data-Visualization"模块中承担"从量(quantity)走向分布(distribution)"的转折:

  1. 散点图能给分布一个粗略轮廓,但刻画真实分布应使用直方图;
  2. bins、布尔掩码过滤是控制直方图粒度与偏态的两个基本杠杆;hist2d 用颜色密度揭示双变量联合分布的汇聚点;
  3. 分类(文本)字段通过 .loc 逐类提取 + 半透明叠加直方图,即可在一张图中对比多组分布;
  4. Seaborn kdeplot 以平滑曲线替代阶梯,bw_adjust 控制平滑强度,hue/fill/common_norm 组合支持分组密度对比,直至双变量多密度叠加——为第 11 课"比例可视化"与后续生命周期分析打好分布直觉基础。
登录后查看全文
热门项目推荐
相关项目推荐