☰
K-Modes聚类入门:分类数据聚类的原理、实战与避坑指南
2026/10/21 1:28:11 网站建设 项目流程

简介:kmodes 是一个针对分类数据聚类的 Python 库,实现了 k-Modes 和 k-Prototypes 两种算法。它主要面向需要处理非数值型或混合型数据的开发者、数据分析师以及 scikit-learn 用户,弥补传统 k-means 难以直接处理分类变量的不足。库内部使用汉明距离衡量分类属性差异,并在 k-prototypes 中结合欧氏距离处理数值属性,适用于市场细分、客户分群、文本聚类、生物信息学等场景。压缩包共 30 个文件,以 20 个 Python 源码文件为主体,涵盖 kmodes 与 kprototypes 核心实现、测试用例和性能基准示例;另有 README、配置文件及示例数据集,便于快速理解和使用。资源包整体仅 36KB,结构清晰,适合直接阅读源码学习聚类细节。目前已有 2008 人学习下载。通过此包可以掌握算法完整实现,参考测试与持续集成配置,深入理解参数初始化、距离计算及聚类中心更新等关键环节,进而迁移到自己的项目中应用。

1. 先搞懂一件事:聚类是什么,为什么一到分类数据就卡壳

当你第一次听到“聚类”这个词,可能和我当年一样,以为把一堆数据自动分成几组是很简单的事。一上手就翻车:想给用户分群,手头数据却是“学历:本科/硕士”“地区:北京/上海”“购买渠道:线上/线下”这类满屏文字,最常见的k-means直接罢工——它要算点与点之间的距离,可“本科”和“硕士”之间的距离该怎么量?“红色”和“蓝色”求个平均能得到“紫色”吗?k-modes就是专治这个问题的聚类算法。标题里的k模式处理“全是分类数据”的情况,k原型处理“分类+数值混合数据”的情况。这篇入门文会从零带你跑通第一个例子,读完你就能拿自己的数据试一把。

2. 从k-means到k-modes:换一种“距离”,一切就通了

2.1 先吐槽k-means:为什么分类数据会让它崩溃

k-means的思路很直白:把每个样本看成坐标系里的一个点。比如身高165厘米、体重55公斤,就是一个坐标为(165, 55)的点。聚类时,算法不断计算“样本点到中心点的距离”,把点分给最近的簇。这里用的欧氏距离,天然要求每个维度上的值可以加减乘除,是有“数量含义”的连续数值。

但分类数据没有这种含义。以“城市”为例,北京、上海、广州之间的“距离”是多少?你可能说地理上有距离,但那不是“类别”含义上的距离。再看“颜色偏好:红、蓝、绿”,你能说红和蓝相差1、蓝和绿相差2吗?这些数没法比较。更麻烦的是,就算你把文字硬编码成0、1、2,再丢给k-means,这个问题也并没有消失——编码成1和编码成2之间的人为差距,会被当成真实的几何距离参与计算,聚类结果自然随缘。

很多新手卡在这一步,还以为是自己的数据不够“干净”。我当年也一样,试过把分类变量直接转成数字,再套k-means,出来的分组怎么看都不对劲。后来才明白:不是算法不行,是算法和你手里数据的类型根本不匹配。分类数据需要的是“是否相同”的比较,而不是“相差多少”的比较。

2.2 k-modes的核心:把“距离”换成“不匹配次数”

k-modes的想法非常朴素,朴素到像作弊:它完全不计算数值距离。两个样本之间的距离,等于它们在所有属性上“不一致”的个数。

举个例子。

样本1:颜色偏好=红,所在地区=城市,购买渠道=线上
样本2:颜色偏好=红,所在地区=城市,购买渠道=线下

对比一下,前两个属性一致,第三个不一致,那么这两个样本之间的距离就是1。如果三个属性全部不同,距离就是3。

这种距离叫“简单匹配距离”,本质上是数一数两组属性有多少个地方对不上。它天然适合分类数据,因为只判断“相等”还是“不相等”,不需要比较谁大谁小。

那簇中心怎么定义?k-means的簇中心是“均值”,取所有样本坐标的平均值。k-modes没有“平均值”,它取的是“模式”,也就是每个属性上出现次数最多的那个值。

比如一个簇里有5个人,其中4个人的地区都是“城市”,1个人是“乡镇”,那这个簇在“地区”这个属性上的模式就是“城市”。这就是为什么算法叫k-modes,mode的英文意思就是“众数、出现最多的值”。

算法的迭代流程和k-means很像,循环做两件事:第一步,把每个样本分给“模式距离最近”的簇;第二步,重新计算每个簇的模式。重复到模式不再变化,或者达到最大迭代次数,就收敛了。它要优化的目标也被换成“代价”(cost):所有样本到各自簇中心模式的不匹配次数总和。代价越小,说明分组越“整齐”。

2.3 k-prototypes:一半数值一半分类,怎么办

现实中的数据很少是“纯文本”或者“纯数字”,更常见的是混合体。举个例子,一个用户画像表里有“年龄”(数值)、有“职业类型”(分类)、有“收入等级”(分类)。这种情况,k-modes处理不了年龄,因为它不想把年龄离散化;k-means处理不了职业,因为职业没法算距离。

k-prototypes就是为这种场景设计的,它把两种距离放在一起算:数值部分用欧氏距离,分类部分用不匹配次数,然后把两部分加权求和。

总距离 = 数值距离 + gamma × 分类不匹配数

这里的gamma是一个权重参数。gamma越大,分类属性对聚类结果的影响力就越大;gamma越小,数值属性的主导作用越强。实际使用中,如果你发现分类属性不重要,可以调小gamma;如果分类属性是核心业务字段,就调大。这个值没有万能默认,需要你用业务经验和多次实验去感受。

动手时记住一条:数值部分的列,最好先做标准化处理。因为欧氏距离对量级敏感,如果“年龄”是0到100,“月薪”是5000到30000,月薪会直接压倒年龄。用标准化把数值列缩放到相近范围,k-prototypes的结果才合理。

3. 跑通第一个例子:用kmodes给你的分类数据分个组

3.1 安装:一行命令搞定

打开终端或命令行,执行:

pip install kmodes

这条命令会安装kmodes库和它依赖的几个底层包。装完后,建议立刻用一行命令确认安装成功:

python -c "from kmodes.kmodes import KModes; print('ok')"

如果输出ok,说明安装没问题。这里有个新手容易踩的坑:如果你电脑上有多个Python环境,直接敲pip可能装到别处去。入门阶段,我建议用下面这条更稳的命令:

python -m pip install kmodes

这样pip会明确装到当前python所在的环境里,避免装完了却import不到。

3.2 第一个例子:给一份小型“用户画像”数据聚类

我们构造一份非常小的数据,只有10条记录、3个分类属性:颜色偏好、所在地区、购买渠道。这份数据小到你可以手算验证结果,跑通了再换自己的数据。

from kmodes.kmodes import KModes # 构造10条样本,每条有3个分类属性 data = [ ['红', '城市', '线上'], ['蓝', '乡镇', '线下'], ['绿', '城市', '线上'], ['红', '城市', '线上'], ['蓝', '乡镇', '线下'], ['绿', '乡镇', '线上'], ['红', '城市', '线下'], ['蓝', '城市', '线上'], ['绿', '乡镇', '线下'], ['红', '乡镇', '线上'], ] # 创建K-Modes聚类器:分成2个簇 km = KModes(n_clusters=2, init='Cao', n_init=5, random_state=0) # 训练并直接得到每个样本的簇标签 clusters = km.fit_predict(data) print('每个样本的簇标签:', clusters)

运行后你会看到类似这样的输出:

每个样本的簇标签: [0 0 1 0 0 1 0 1 1 0]

我来解释这段代码在做什么。KModes(n_clusters=2)表示我们希望算法把这10个人分成2组;init='Cao'是初始化方法,比完全随机初始化更稳定,也是新手最省心的选择;n_init=5的意思是算法会从5个不同的初始点开始跑,最后选代价最小的那一次结果,用来避免运气不好陷入很差的解;random_state=0则是固定随机种子,让你这次跑和下次跑的结果完全一致。

fit_predict是一个很常用的方法,它一次完成两件事:先训练模型,再返回每个样本的簇标签。返回结果里,标签0和1只是编号,没有大小含义。比如上面结果里,第一行数据的标签是0,意思是样本0被分到了第0个簇。

3.3 看结果:簇中心长什么样

拿到标签只是第一步,更关键的是看看每个簇的“画像”是什么样的。kmodes把每个簇的模式存在cluster_centroids_属性里,你可以在上面的代码后面追加两行:

print('每个簇的模式(中心):', km.cluster_centroids_) print('聚类代价(不匹配总数):', km.cost_)

输出大概是这样:

每个簇的模式(中心): [['红', '城市', '线上'], ['蓝', '乡镇', '线下']] 聚类代价(不匹配总数): 5

这个输出非常直观:第0个簇最典型的人是“红、城市、线上”,第1个簇最典型的人是“蓝、乡镇、线下”。你可以把这个描述直接写进业务报告里,比如“第一组用户偏好红色、来自城市、习惯线上购买”。这就是聚类落地的基本方式。

代价cost_是5,意思是全部10条样本和各自簇中心模式加起来的“不匹配次数”总共有5次。这个数字单独看没有绝对意义,但它非常有用:可以用来比较不同簇数的好坏。

那么n_clusters到底怎么选?新手最常用的办法是看代价曲线:从2跑到6,画出k值和cost_的对应关系,找一个“下降到拐点不再明显变快”的位置。代码可以这样写:

for k in range(2, 7): km = KModes(n_clusters=k, init='Cao', n_init=5, random_state=0) km.fit_predict(data) print(k, km.cost_)

cost_会随着k增大而下降,因为簇越多,每个簇内部越“整齐”。簇数从2涨到3时,如果cost_下降非常猛,从3到4时下降很少,那3可能就是合理的簇数。这外号叫“肘部法”,因为它看的是曲线形状。

我把K-Modes最常用的几个参数整理成了一个表,方便你对照调参:

参数作用入门建议
n_clusters你想分成几组从2开始试,用肘部法选
init初始化方式:Cao/Huang/random优先用Cao,最稳定
n_init从几个初始点跑取最优5到10都够用
max_iter最大迭代轮数默认即可,一般不用调
random_state固定随机种子固定一个数,方便复现

4. 新手避坑:五个最容易翻车的细节

4.1 坑一:拿着连续数值硬跑K-Modes

现象:把“年龄”“月薪”“产品价格”这种连续数值原封不动喂给KModes,结果聚类结果非常不稳定,换一次随机种子分组就变样。

原因:K-Modes只看属性“是否相等”。连续数值几乎每个样本都不一样,不匹配次数会很高,导致所有样本看起来都很“远”,簇的结构无法形成。它本来就不擅长处理这类数据。

解决:连续数值要么先离散化(比如把年龄切成“青年、中年、老年”),要么改用K-Prototypes。K-Prototypes能同时处理数值列和分类列,比硬编码分类列再丢给K-Modes靠谱得多。

4.2 坑二:数据里有缺失值直接报错

现象:运行fit_predict时,报ValueError,提示数据里有NaN,或者直接返回一个包含NaN的糟糕结果。

原因:分类数据里的缺失值没法判断“是否相等”。NaN和NaN不应该算相等,和任何值不匹配,算法内部会卡住。

解决:训练之前先检查缺失值。入门阶段最简单的做法是删除缺失样本,或者用该列出现次数最多的值去填充。先让自己跑通,再去认真考虑填充策略。

4.3 坑三:n_clusters拍脑袋乱定

现象:随手写n_clusters=5,看完结果觉得“这也太散了”,但又说不出哪里不对。

原因:簇数需要结合业务和数据分布来定,拍脑袋定的簇数往往会切出一些语义不清的簇。

解决:先跑几个不同的k值,打印cost_看趋势。如果业务上有明确分组需求(比如分高、中、低三档),就按业务来;没有的话,用肘部法找一个“性价比最高”的值。

4.4 坑四:不固定随机种子,复现不了结果

现象:代码没变,只是重新运行了一次,聚类结果和上次完全不一样,比如样本归属变了,连簇的画像都换了。

原因:K-Modes的初始化带随机性,初始化点不同,最终结果可能不同,这很常见,尤其是在数据本身可分性不明显的时候。

解决:创建模型时固定random_state。我在前面代码里写random_state=0,就是为了让结果可复现。排查问题、写文档、跟同事讨论时,这一步能帮你省掉大量“为什么我不一样”的困惑。

4.5 坑五:把cluster_centroids_当成了平均值

现象:打印出cluster_centroids_,看到里面是“红”“城市”“线上”这种字符串,以为算错了。

原因:K-Modes的簇中心不是平均值,而是众数组合。它表示的是这个簇里最具代表性的属性值组合,字符串本来就是正常的。

解决:读懂簇中心时,别把它当作“中间值”,要把它当作“这个簇里占比最高的特征组合”。你完全可以把它转成可读的画像描述。这也是k-modes相对k-means的优势:聚类结果天然可以当作文本报告去解释,不用再想办法解释抽象的数字中心点。

5. 下一步:从跑通到会用,建议你这样补

到这里,你已经能跑通K-Modes,也能用K-Prototypes处理混合数据了。下一步我建议你按这个顺序继续补,每一步都紧接着上一步,大约一个周末就能走完。

第一,补聚类结果的评估方法。K-Modes没有sklearn那种“轮廓系数”可以直接用,但你可以用cost_曲线对比不同k值,也可以跑多次看标签稳定性:同一份数据,不同随机种子下分组越稳定,说明聚类越可靠。

第二,做一个真实的模拟项目X。找一份带混合字段的表格数据,比如模拟的会员信息表,里面同时有年龄、城市、消费等级、注册渠道等。先用K-Modes对纯分类部分聚类,再用K-Prototypes对所有列聚类,对比两种做法哪边分组更符合业务直觉。这一步会让你真正理解两种算法的适用边界。

第三,养成一个固定习惯。我自己的习惯是:每次拿到数据,先看列类型,分类列和数值列分开放;训练前打印一遍缺失值数量;建模时固定随机种子;跑完聚类立刻把cluster_centroids_转回原始列名的表格,逐簇读一遍画像。

结尾想分享一个当年的教训。我第一次用K-Modes时,忘记固定随机种子,连续跑三次得到三个不同结果,还以为是数据有问题,急得满屋子找原因。从那以后,我每次跑聚类都会强制在模型前面写一行random_state=0,也养成了先看数据和类型、再选算法的习惯。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询