首页
/ ML-For-Beginners 第5周第1课实战:聚类前先用散点图、相关热力图与 KDE 分布图看清数据结构

ML-For-Beginners 第5周第1课实战:聚类前先用散点图、相关热力图与 KDE 分布图看清数据结构

2026-09-05 11:10:27作者:沈韬淼Beryl

本篇技术指南围绕 ML-For-Beginners 仓库中「5-Clustering(聚类)」模块的开篇课程《Introduction to clustering》及其配套作业 assignment.md 展开:聚类是无监督学习,算法本身不依赖标签,但在选算法、定参数之前,你必须先"看懂"数据的结构。本文完整复现了课程在 Nigerian 音乐数据集(530 首歌、16 个字段)上的全部探索步骤——数据加载与完整性校验、柱状图筛选流派、相关系数热力图、KDE 联合分布与 FacetGrid 散点图——并解释每个可视化结论如何指向后续的 K-Means 聚类选型。读完后,你将能独立完成作业所要求的"用散点图研究聚类数据"任务:在一个 notebook 中产出至少 5 张有注释的散点图,并说明你从中发现了什么。

为什么聚类之前必须先做可视化

课程开篇给出聚类的定义:聚类属于无监督学习,它假定数据集没有标签、或输入不与预定义输出配对,算法通过发现数据中的模式来给出分组。与有监督任务不同,聚类没有标准答案可以对照,因此"数据探索阶段"承担了绝大部分判断责任:

  • 点与点之间是松散聚集还是彼此独立?——决定"是否值得聚类";
  • 数据是否落在近似平面(欧氏几何)上?——决定距离度量与算法类型;
  • 密度是否均匀?——决定 K-Means 类算法是否会失效。

课程给出了聚类的三类典型生产场景:市场细分(例如判断哪些年龄段购买哪些商品)、异常检测(例如从信用卡交易中发现欺诈)、医学影像中的肿瘤定位。课程还提到一个容易被忽视的用途:为数据点分配 cluster id 之后,可以只引用 cluster id 而不暴露更具辨识度的原始字段,从而在保留数据价值的前提下保护隐私。

课程附注:cluster analysis(聚类分析)最早起源于 20 世纪 30 年代的人类学与社会心理学领域。

本课在整个仓库中的位置是 12 周、26 节课路线图的第 5 周(第 5 章"Clustering")第 1 课,配套材料包括:

  • 主练习文件:notebook.ipynb(标题为 "Nigerian Music scraped from Spotify - an analysis",需要自行填充代码);
  • 完成后的参考解答:solution/notebook.ipynb,同目录下还有 JuliaR 子目录提供其他语言版本的解答;
  • 作业说明:assignment.md
  • 数据集:nigerian-songs.csv(530 行歌曲数据)。

聚类算法速览:本课可视化要回答的选型问题

课程正文附了一张源自 Scikit-learn 文档的算法与适用场景对照表,完整保留如下——它是"为什么先可视化"的直接答案:只有先看清数据的簇数、密度、几何形态,才能对号入座:

方法 适用场景
K-Means 通用、归纳式(inductive)
Affinity propagation 数量多、大小不均的簇、归纳式
Mean-shift 数量多、大小不均的簇、归纳式
Spectral clustering 数量少、大小均等的簇、传递式(transductive)
Ward hierarchical clustering 数量多、受约束的簇、传递式
Agglomerative clustering 数量多、受约束、非欧氏距离、传递式
DBSCAN 非平坦几何、大小不均的簇、传递式
OPTICS 非平坦几何、密度可变的不均簇、传递式
Gaussian mixtures 平坦几何、归纳式
BIRCH 含异常值的大数据集、归纳式

课程用四组术语解释了上表中的关键列,理解它们才能读懂"适用场景":

  • Transductive(传递式)vs. Inductive(归纳式):传递式推理是从观测到的训练样本映射到特定的测试样本;归纳式推理先从训练样本学出一般规则,再套用到测试样本。课程给的例子是补全部分打标的音乐数据:归纳法会先训出"records/cds"两类再贴标签,遇到实际上是"cassettes"的数据就会出错;传递法则先把相似项聚成组再整体贴标签,对未知类别更稳健。
  • Non-flat(非平坦)vs. flat(平坦)几何:flat 指欧氏几何(中学里的"平面几何"),两点距离是线段长度;non-flat 指非欧氏几何,距离沿曲线度量。如果你的数据可视化之后"不存在平面之上",就需要专门处理非欧氏结构的算法。
  • Distances(距离):簇由其距离矩阵定义。欧氏簇以质心(centroid,中心点)为参考,距离即点到质心的距离;非欧氏情形使用 clustroid(簇内离其他点最近的那个点)作为参考。
  • Constrained(受约束聚类):把"半监督"引入无监督方法,用 must-link / cannot-link 规则约束点的配对关系。课程举例:给算法附加"该物品必须由塑料制成""必须能发出音乐"这类规则后,它就不会把"饼干"也分进"音乐器材"簇里。
  • Density(密度):噪声多的数据密度不均匀,同一算法在不同密度区域表现差异很大,需要匹配密度型方法(如 DBSCAN、OPTICS、Mean-shift)。

课程随后将主流算法归为五大家族,这也是作业 Challenge 要求你整理成图的骨架:

  1. Hierarchical clustering(层次聚类):对象按"与邻近对象而非远处对象的距离"归类,簇由成员间的距离自底向上合并而成。Scikit-learn 的 agglomerative clustering 属于此族。
  2. Centroid clustering(质心聚类):需要先给定簇数 k,算法确定每个簇的中心点并围绕其收集数据;K-Means 是最流行的版本(取"最近均值"得名,最小化点到簇的平方距离)。
  3. Distribution-based clustering(基于分布的聚类):以统计建模为中心,计算数据点属于某个簇的概率并据此分配,Gaussian mixture 属于此族。
  4. Density-based clustering(基于密度的聚类):按点与点的密度聚集情况分配簇,远离群体的点视为离群值或噪声;DBSCAN、Mean-shift、OPTICS 属于此族。
  5. Grid-based clustering(基于网格的聚类):对多维数据集建立网格,数据被划分到网格单元中,单元即簇。

平坦与非平坦几何对比信息图,解释欧氏/非欧氏距离在聚类中的含义

环境与数据准备

练习的第一步是准备环境并加载数据。课程给出的安装与加载代码如下(注意数据文件位于 5-Clustering/data/ 目录下,README 中写的 ../data/nigerian-songs.csv 是相对于 notebook 所在目录的路径):

!pip install seaborn

import matplotlib.pyplot as plt
import pandas as pd

df = pd.read_csv("../data/nigerian-songs.csv")
df.head()

df.head() 的前 5 行如下(来自 nigerian-songs.csv,共 16 列):

name album artist artist_top_genre release_date length popularity danceability acousticness energy instrumentalness liveness loudness speechiness tempo time_signature
0 Sparky Mandy & The Jungle Cruel Santino alternative r&b 2019 144000 48 0.666 0.851 0.42 0.534 0.11 -6.699 0.0829 133.015 5
1 shuga rush EVERYTHING YOU HEARD IS TRUE Odunsi (The Engine) afropop 2020 89488 30 0.71 0.0822 0.683 0.000169 0.101 -5.64 0.36 129.993 3
2 LITT! LITT! AYLØ indie r&b 2018 207758 40 0.836 0.272 0.564 0.000537 0.11 -7.127 0.0424 130.005 4
3 Confident / Feeling Cool Enjoy Your Life Lady Donli nigerian pop 2019 175135 14 0.894 0.798 0.611 0.000187 0.0964 -4.961 0.113 111.087 4
4 wanted you rare. Odunsi (The Engine) afropop 2018 152049 25 0.702 0.116 0.833 0.91 0.348 -6.044 0.0447 105.115 4

其中 length 单位是毫秒,popularity 是 Spotify 给出的 0–100 热度分,danceabilityenergy 等音频特征均为 0–1 区间的连续值,loudness 为分贝(可为负),tempo 是 BPM。

三步数据体检:info、isnull、describe

课程要求按固定顺序做三项完整性检查。第一步 df.info() 确认行列结构与类型:

df.info()

预期输出(530 行 × 16 列,float64 占 8 列、int64 占 4 列、object 占 4 列):

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 530 entries, 0 to 529
Data columns (total 16 columns):
 #   Column            Non-Null Count  Dtype
---  ------            --------------  -----
 0   name              530 non-null    object
 1   album             530 non-null    object
 2   artist            530 non-null    object
 3   artist_top_genre  530 non-null    object
 4   release_date      530 non-null    int64
 5   length            530 non-null    int64
 6   popularity        530 non-null    int64
 7   danceability      530 non-null    float64
 8   acousticness      530 non-null    float64
 9   energy            530 non-null    float64
10  instrumentalness  530 non-null    float64
11  liveness          530 non-null    float64
12  loudness          530 non-null    float64
13  speechiness       530 non-null    float64
14  tempo             530 non-null    float64
15  time_signature    530 non-null    int64
dtypes: float64(8), int64(4), object(4)
memory usage: 66.4+ KB

第二步 df.isnull().sum() 确认每列空值数均为 0(课程原文结论:"Looking good"):

df.isnull().sum()
name                0
album               0
artist              0
artist_top_genre    0
release_date        0
length              0
popularity          0
danceability        0
acousticness        0
energy              0
instrumentalness    0
liveness            0
loudness            0
speechiness         0
tempo               0
time_signature      0
dtype: int64

第三步 df.describe() 给出 12 个数值列的统计画像,是后续"挑哪些列做散点图"的直接依据:

df.describe()
release_date length popularity danceability acousticness energy instrumentalness liveness loudness speechiness tempo time_signature
count 530 530 530 530 530 530 530 530 530 530 530 530
mean 2015.390566 222298.1698 17.507547 0.741619 0.265412 0.760623 0.016305 0.147308 -4.953011 0.130748 116.487864 3.986792
std 3.131688 39696.82226 18.992212 0.117522 0.208342 0.148533 0.090321 0.123588 2.464186 0.092939 23.518601 0.333701
min 1998 89488 0 0.255 0.000665 0.111 0 0.0283 -19.362 0.0278 61.695 3
25% 2014 199305 0 0.681 0.089525 0.669 0 0.07565 -6.29875 0.0591 102.96125 4
50% 2016 218509 13 0.761 0.2205 0.7845 0.000004 0.1035 -4.5585 0.09795 112.7145 4
75% 2017 242098.5 31 0.8295 0.403 0.87575 0.000234 0.164 -3.331 0.177 125.03925 4
max 2020 511738 73 0.966 0.954 0.995 0.91 0.811 0.582 0.514 206.007 5

这里有一个值得注意的细节:popularity 的 25% 分位数为 0,意味着约四分之一的歌曲没有任何热度排名。课程的处理策略是把这类记录视为噪声,后面统一过滤。

课程的思考题:既然聚类是无监督方法、不需要标签,为什么数据里还带着 artistartist_top_genre 这些"标签"?答案是——数据探索阶段它们非常有用,但聚类算法本身并不依赖它们;你完全可以删掉表头、只用列号引用数据。

用柱状图确定分析范围:流派过滤与噪声剔除

第一步可视化是"哪个流派最多"。课程代码如下:

import seaborn as sns

top = df['artist_top_genre'].value_counts()
plt.figure(figsize=(10,7))
sns.barplot(x=top[:5].index,y=top[:5].values)
plt.xticks(rotation=45)
plt.title('Top genres',color = 'blue')

数据集中最主流的五个流派柱状图

柱状图显示存在一个 Missing 流派——这是 Spotify 未能分类的记录。课程的处理是直接过滤掉,然后重新画全流派柱状图:

df = df[df['artist_top_genre'] != 'Missing']
top = df['artist_top_genre'].value_counts()
plt.figure(figsize=(10,7))
sns.barplot(x=top.index,y=top.values)
plt.xticks(rotation=45)
plt.title('Top genres',color = 'blue')

过滤 Missing 后全部流派的分布柱状图,前三大流派明显占优

过滤后结论很清晰:afro dancehallafropopnigerian pop 三大流派占据绝大多数样本。课程据此把数据集收敛到这三大流派,同时剔除 popularity == 0(无热度排名、视为噪声)的记录:

df = df[(df['artist_top_genre'] == 'afro dancehall') | (df['artist_top_genre'] == 'afropop') | (df['artist_top_genre'] == 'nigerian pop')]
df = df[(df['popularity'] > 0)]
top = df['artist_top_genre'].value_counts()
plt.figure(figsize=(10,7))
sns.barplot(x=top.index,y=top.values)
plt.xticks(rotation=45)
plt.title('Top genres',color = 'blue')

这一步的意义在于"降维前的定界":把分析锁定在数据量足够、语义一致的子集上,后续的散点图与聚类才不会被长尾流派稀释。

相关系数热力图:弱相关数据对聚类意味着什么

定界之后,课程用一个快速测试检查数据中是否存在强线性相关:

corrmat = df.corr(numeric_only=True)
f, ax = plt.subplots(figsize=(12, 9))
sns.heatmap(corrmat, vmax=.8, square=True)

过滤后数据集的数值列相关系数热力图

课程对热力图的判读是:唯一强的相关是 energyloudness 之间——音乐响往往能量高,这并不意外;除此之外各特征间相关性都相当弱。这个结论直接影响下一步:如果特征间几乎没有线性结构,"哪个特征能预测哪个"的故事讲不通,数据只能交给聚类算法去发现组内结构——而弱相关、松散对齐的数据恰恰是聚类难度最高的情形。课程同时提示:相关不等于因果,这里只有相关性的证据。

KDE 联合分布:三大流派真的不同吗

课程的第二个探索练习(Exercise - data distribution)要回答的问题是:这三大流派在"流行度 × 舞动性"的感知上是否显著不同?使用的是 Seaborn 的 jointplotkind="kde" 会叠加核密度估计(KDE,Kernel Density Estimate)曲面,用连续概率密度曲线来刻画分布,便于在多个分布并存时做解读:

sns.set_theme(style="ticks")

g = sns.jointplot(
    data=df,
    x="popularity", y="danceability", hue="artist_top_genre",
    kind="kde",
)

popularity 与 danceability 维度上按流派着色的 KDE 联合分布图,可见绕同一收敛点的同心环

图中可以看到三个流派都绕着一个共同的收敛点画出一圈圈等高线——三大流派在流行度与舞动性上只是"松散对齐",而非各自占据不同的角落。课程的判断是:在这种松散对齐的数据上确定簇,将是一个挑战。

FacetGrid 散点图:聚类的"侦察兵"

KDE 图给出的是密度视图,作业要求的武器则是散点图本身。课程用 FacetGrid 在同一坐标轴上按流派着色绘制:

sns.FacetGrid(df, hue="artist_top_genre", height=5) \
   .map(plt.scatter, "popularity", "danceability") \
   .add_legend()

FacetGrid 散点图:三大流派在 popularity-danceability 平面上的分布呈现相似收敛模式

同一坐标轴上的散点图呈现出与 KDE 图一致的收敛模式:所有流派向同一区域聚拢,而不是分成三个可分离的团块。课程在此点明本课的方法论主旨:

对聚类而言,散点图可以展示数据的簇结构,因此熟练掌握这类可视化非常有价值。 下一课将把这份过滤后的数据交给 K-Means 聚类,去发现数据中那些以有趣方式相互重叠的群体。

这句话也解释了为什么"散点图"是散点图库(matplotlib 的 scatter/plot、Seaborn 的 scatterplot/FacetGrid、以及其他第三方绘图库)值得系统调研的原因——它是聚类前侦察的主力图形,也是聚类结果复核的主力图形。

作业要求与评分标准(完整继承)

assignment.md 布置了一项研究性任务,核心指令是:

本节课你使用了几种可视化技术来把握"为聚类而绘图"的方法。散点图对发现对象群体尤其有用。请研究创建散点图的不同方式与不同绘图库,并在一个 notebook 中记录你的工作。数据可以用本课的、其他课的、或你自己来源的数据(但须在 notebook 中注明出处)。用散点图绘制一些数据,并解释你的发现。

作业附带的评分标准(Rubric)完整如下,可作为自查清单:

评价维度 优秀(Exemplary) 合格(Adequate) 需改进(Needs Improvement)
(评分项) 提交一个包含五张注释完善散点图的 notebook 散点图少于五张,且注释不够完善 提交的 notebook 不完整

对照本课内容,一份"优秀"档作业可以按下面这张图形式谱来组织(全部有课程与仓库数据支撑,可直接落进 notebook.ipynb):

图形 代码要点 在聚类工作流中的角色
流派柱状图(过滤前) sns.barplot + value_counts() 识别长尾与 Missing 类别,界定分析范围
流派柱状图(过滤后) 过滤 Missing、三大流派、popularity > 0 后重绘 剔除噪声,确认聚类子集
相关系数热力图 df.corr(numeric_only=True) + sns.heatmap 评估特征线性结构,预判聚类难度
KDE 联合分布 sns.jointplot(..., kind="kde") 用连续密度曲线观察多分布重叠
FacetGrid 散点图 sns.FacetGrid + plt.scatter 直接侦察簇的收敛/分离形态

若想让散点图更"可解释",可参考课程提示:换用 energyloudnessspeechiness 等列或更换流派组合,并结合 df.describe() 中各列的 spread 判断哪些维度值得上图。另外,作业允许的"其他课数据"在本仓库中现成可用,例如 2-Regression/data/US-pumpkins.csv4-Classification/data/cuisines.csv7-TimeSeries/data/energy.csv,均可复用上述散点图模板。

本课挑战与通向 K-Means 的路径

课程末尾的 🚀Challenge 为下一课做铺垫:

为下一节课做准备:把你可能在生产环境中发现并使用的各种聚类算法画成一张图。这些聚类分别试图解决什么问题?

结合本课的算法速览表,这张图可以直接按"簇数已知/未知、密度均匀/不均、几何平坦/非平坦、是否含异常值"四个维度对 K-Means、DBSCAN、OPTICS、Mean-shift、Gaussian mixtures、BIRCH 等方法分箱。

至此,本课的可视化工作已经为 2-K-Means 一课铺平了道路:带着三大流派、popularity > 0 的干净子集,以及对"数据松散对齐、相关弱"的预判进入 K-Means,你才能判断它给出的簇究竟是数据结构的反映,还是对噪声的强行拟合。若需要其他语言的完整解答,可参考 solution/notebook.ipynb 及同目录下的 Julia、R 版本;想检验学习效果,仓库根目录提供了配套的测验应用 quiz-app

登录后查看全文
热门项目推荐
相关项目推荐