Data-Science-For-Beginners 第10课实战:用直方图与密度曲线可视化数据分布(Matplotlib + Seaborn 详解)
本文基于 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()
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)
从图中可以看出:体重与体长之间存在沿预期方向的相关性,并且有一个特别强的汇聚点——这正是用颜色密度代替散点堆叠的价值所在。
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()
曲线形态与前面最小翼展直方图对应,只是更平滑。课程引用了 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,
)
关键参数含义: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/警告输出,可推断为当时的运行环境特性):
- 在较早的 Matplotlib/Seaborn 组合下执行
kdeplot会触发FutureWarning: Support for multi-dimensional indexing (e.g. obj[:, None]) is deprecated...,属于版本过渡期告警,不影响出图; - 双变量 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)"的转折:
- 散点图能给分布一个粗略轮廓,但刻画真实分布应使用直方图;
bins、布尔掩码过滤是控制直方图粒度与偏态的两个基本杠杆;hist2d用颜色密度揭示双变量联合分布的汇聚点;- 分类(文本)字段通过
.loc逐类提取 + 半透明叠加直方图,即可在一张图中对比多组分布; - Seaborn
kdeplot以平滑曲线替代阶梯,bw_adjust控制平滑强度,hue/fill/common_norm组合支持分组密度对比,直至双变量多密度叠加——为第 11 课"比例可视化"与后续生命周期分析打好分布直觉基础。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00



