kmodes与k-prototypes:分类及混合数据聚类实战指南

发布时间:2026/10/25 3:05:13
kmodes与k-prototypes:分类及混合数据聚类实战指南

简介:本资源是面向数据科学学习者与Python开发者的k-模式与k-原型聚类算法实践包,专为解决分类变量及混合型数据(数值+类别)的无监督聚类难题而设计,弥补scikit-learn原生库对非数值特征支持的不足。资源共30个文件,含20个核心Python源码(如kmodes.py、kprototypes.py及多个benchmark与示例脚本)、2个CSV数据集(soybean.csv、stocks.csv)、2个Markdown文档、1个详细README.rst说明文件,以及测试配置(.coveragerc)、CI配置(.travis.yml)和许可证等工程化文件,整体仅36KB,轻量易读、结构清晰,便于快速理解算法实现逻辑与调用方式。已有2009人学习下载,适合中高级Python用户深入掌握聚类原理、复现论文实验、拓展scikit-learn生态能力。读者可直接运行examples目录下的iris.py、soybean.py等案例,结合源码注释与benchmark对比,掌握初始化策略、距离度量机制及混合数据聚类评估方法。

1. kmodes 是什么:当数据全是“选项题”,别再硬套 K-means 这把尺子

你有没有试过用 K-means 做聚类,结果模型跑通了,但分出来的组怎么看都像乱点鸳鸯谱?比如分析用户填写的问卷——职业是“教师/程序员/自由职业”,城市是“北京/成都/昆明”,偏好标签是“早起型/夜猫子/不定时”……这些数据没有大小之分,没有距离可算,“教师”和“程序员”的差距,没法用 3.2 或 5.7 这样的数字来衡量。这时候硬套 K-means,就像拿卷尺去量一首诗的长短——工具没错,但对象根本不是它该量的东西。kmodes 就是专为这类“纯分类数据”设计的聚类算法:它不比谁离中心点更近,而是比谁和中心点“选得更一致”。它把每个簇的“中心”定义成一组高频出现的类别值(比如“程序员+北京+夜猫子”),然后看每个样本和这个组合匹配了几项。匹配越多,就越属于这个簇。这不是玄学,是面对真实业务数据时,一种更诚实的建模选择。


2. 为什么需要 kmodes:K-means 在分类数据上会“翻车”的三个真相

2.1 K-means 的底层逻辑,天生排斥“选项型”数据

K-means 的核心是“最小化平方误差”,也就是让每个点到它所属簇中心的欧氏距离之和最小。这个“距离”计算依赖数值的连续性与可加性。举个例子:假设你有两个样本,职业分别是“医生”和“律师”,K-means 会强行给它们分配数字编码(比如医生=1,律师=2),然后算出中心点可能是 1.5——可世界上根本没有“1.5号职业”。更糟的是,如果你把“医生”编成 1、“教师”编成 100,算法就会误以为“医生”和“教师”天差地别,哪怕在业务意义上它们同属“知识服务行业”。这种编码强加的虚假数值关系,会让聚类结果完全失真。kmodes 则彻底绕开数值距离,只统计“类别是否一致”:医生 vs 医生 → 匹配;医生 vs 律师 → 不匹配。简单、干净、无歧义。

2.2 kmodes 的“模式中心”到底是什么?一个超市购物篮的比喻

想象你是一家连锁超市的数据分析师,手上有 10 万条顾客购物小票,每张小票是一行记录,列是商品品类:[牛奶, 面包, 啤酒, 尿布, 洗发水],值只有“买了”或“没买”(即 1 或 0)。K-means 会试图算出一个“平均购物篮”,比如 [0.63, 0.71, 0.22, 0.45, 0.58]——这毫无业务意义。而 kmodes 的“模式中心”(mode)是实实在在存在的组合:比如它可能发现一个典型模式是 [1, 1, 0, 1, 0],对应“买了牛奶、面包、尿布,没买啤酒和洗发水”。这个组合一定在原始数据中真实出现过(哪怕只出现一次),它是所有属于该簇的顾客里,各品类购买频率最高的那个取值。所以 kmodes 的中心不是“数学平均”,而是“众数代表”,是数据自己投票选出来的最典型画像。

2.3 算法流程:四步走完,比 K-means 更“直觉”

kmodes 的迭代过程非常贴近人的直觉判断,共四步:

  1. 随机选 k 个样本作为初始模式中心(不是随机数,是真实存在的完整记录);
  2. 分配:对每个样本,计算它和每个中心“匹配的字段数”,把它分给匹配最多的那个中心;
  3. 更新:对每个簇,重新计算它的新中心——对每一列,选出该簇内出现频次最高的类别值;
  4. 收敛判断:如果所有中心都没变,算法结束;否则回到第 2 步。

提示:这和 K-means 的“算均值”更新完全不同。kmodes 的更新永远产生一个真实存在的、由原始数据投票产生的新中心,不会生成任何“不存在的中间态”。

下面这段 Python 代码展示了 kmodes 如何用kmodes库完成一次标准聚类,关键参数含义已注释:

from kmodes.kmodes import KModes import pandas as pd # 假设 df 是一个纯分类数据的 DataFrame,例如: # job city habit # 0 teacher beijing early # 1 coder chengdu night # 2 teacher kunming night # 初始化 kmodes 模型 km = KModes( n_clusters=3, # 想分成几组?这里设为 3 init='Huang', # 初始化策略:'Huang'(推荐)比随机更稳,'Cao' 更快但略不稳定 n_init=10, # 多运行 10 次,取最优结果(防陷入局部最优) verbose=1 # 打印每次迭代的损失值(匹配错误数),方便观察收敛 ) # 拟合数据(注意:kmodes 只接受字符串或整数类别,不能有浮点数!) clusters = km.fit_predict(df) # 查看每个簇的“模式中心” print("各簇中心(模式):") print(km.cluster_centroids_) # 输出示例(三列:job/city/habit): # [['teacher' 'beijing' 'early'] # ['coder' 'chengdu' 'night'] # ['freelancer' 'kunming' 'irregular']]

这段代码背后,km.fit_predict(df)就是在执行上面说的四步循环。cluster_centroids_属性返回的就是那几个最具代表性的“模式”,它们不是抽象坐标,而是你能直接读得懂的业务组合。这也是为什么很多做用户分群、问卷分析、商品搭配的开发者,在第一次看到cluster_centroids_的输出时,会脱口而出:“啊,这不就是我们运营天天说的‘北上广早起白领’‘新一线夜猫程序员’吗?”


3. kmodes 和 k-prototypes:当你的数据里既有“选项题”,又有“填空题”

3.1 一个现实困境:数据从来不是非黑即白

实际项目中,纯分类数据少之又少。更多时候,你的表格像这样:

user_idjobcityageincomehabit
1teacherbeijing3212000early
2codershenzhen2825000night
3freelancerchengdu358000irregular

这里job,city,habit是分类变量(选项题),而age,income是数值变量(填空题)。K-means 能处理后两者,但搞不定前三者;kmodes 能搞定前三者,但无法消化后两者。硬把age编码成类别(如“青年/中年/老年”)会丢失精度;硬把job数值化又会引入虚假距离。这就是 k-prototypes 登场的时刻——它不是 kmodes 的升级版,而是它的“混血兄弟”,一个能同时驾驭两种数据类型的统一框架。

3.2 k-prototypes 的核心思想:给不同数据类型配不同的“尺子”

k-prototypes 把总损失函数拆成两部分:

  • 分类部分:用 kmodes 的“不匹配数”来度量(比如 job 不同扣 1 分,city 不同再扣 1 分);
  • 数值部分:用 K-means 的“欧氏距离平方”来度量(比如 age 差 5 岁,扣 25 分);
  • 总损失 = 分类损失 + γ × 数值损失

其中γ(gamma)是一个超参数,它决定了你有多看重数值差异。γ 越大,算法越倾向于让数值相近的点归为一簇;γ 越小,则更尊重分类一致性。这个设计非常务实:它不强迫你做非此即彼的选择,而是让你用一个参数,去调节两种数据类型在聚类中的“话语权”。

3.3 代码实战:如何用 k-prototypes 处理混合数据

继续上面的用户表,我们需要告诉算法哪几列是分类的,哪几列是数值的。k-prototypes库通过categorical参数实现这一点:

from kmodes.kprototypes import KPrototypes import numpy as np # 数据准备:注意,数值列必须是 float,分类列必须是 string 或 int # df_numeric 是 age 和 income 列组成的数组 # df_cat 是 job, city, habit 列组成的数组(需转为 string) # 合并成一个 numpy 数组(k-prototypes 要求输入为 array) X = np.column_stack([ df_cat.values.astype(str), # 分类列转字符串 df_numeric.values # 数值列保持 float ]) # 指定分类列的索引位置(0,1,2 是 job/city/habit;3,4 是 age/income) categorical_columns = [0, 1, 2] # 初始化 k-prototypes kp = KPrototypes( n_clusters=3, init='Huang', # 同 kmodes,初始化策略 n_init=10, verbose=1, gamma=0.5 # 关键!平衡分类与数值损失的权重 ) # 拟合 clusters = kp.fit_predict(X, categorical=categorical_columns) # 查看中心:分类部分是 mode,数值部分是 mean print("簇中心(分类列在前,数值列在后):") print(kp.cluster_centroids_) # 输出示例(前3列是模式,后2列是均值): # [('teacher', 'beijing', 'early', 31.2, 11500.0), # ('coder', 'shenzhen', 'night', 27.8, 24200.0), # ('freelancer', 'chengdu', 'irregular', 34.5, 7800.0)]

可以看到,cluster_centroids_返回的是混合元组:前半部分是字符串(模式),后半部分是浮点数(均值)。这正是 k-prototypes 的“双模态”本质——它没有抹平差异,而是为每种数据类型保留了最自然的表达方式。


4. 易混概念辨析:kmodes、k-prototypes、scikit-learn 里的聚类器,到底谁是谁?

4.1 kmodes 不是 scikit-learn 的官方模块,但它和 sklearn 兼容性极好

这是初学者最容易踩的第一个坑。你在pip install scikit-learn时,并不会自动装上kmodes。它是一个独立的第三方库(pip install kmodes),由社区维护,但 API 设计高度模仿 sklearn:fit(),fit_predict(),predict()方法名、参数风格、返回结构都和sklearn.cluster.KMeans几乎一致。这意味着你写惯了 sklearn 的人,几乎不用学习成本就能上手 kmodes;而且你可以放心地把它塞进sklearn.pipeline.Pipeline、和sklearn.model_selection.GridSearchCV一起用(只需稍作适配)。它不是 sklearn 的“补丁”,而是一个“平行宇宙里的 sklearn 兼容实现”。

4.2 kmodes ≠ K-modes 的学术论文实现,而是工业级落地版本

学术论文(如 Huang 1997, 1998)提出了 kmodes 的理论框架,但原始实现往往只支持单机小数据、缺少健壮性检查、不提供n_init多次重启等工程优化。而kmodes这个 Python 库,是在论文基础上做了大量生产环境打磨:支持稀疏矩阵、内置多种初始化策略(Huang/Cao)、自动处理缺失值(默认丢弃)、提供详细的收敛日志。它解决的不是“能不能跑”,而是“能不能在百万行问卷数据上稳定跑出可解释结果”。

4.3 k-prototypes 不是“kmodes + K-means 简单拼接”,而是有理论保障的统一框架

有人以为 k-prototypes 就是先用 kmodes 聚类分类列,再用 K-means 聚类数值列,最后合并结果。这是完全错误的。真正的 k-prototypes 是在每一次迭代中,同步更新分类中心(mode)和数值中心(mean),并用同一个损失函数驱动整个过程。它的目标函数是联合优化的,不是分步优化。这就保证了最终的簇,是分类一致性和数值相似性共同作用下的帕累托最优解,而不是两个独立算法的妥协产物。

4.4 它们和 “One-Hot + K-means” 的本质区别:维度灾难与语义失真

另一个常见误区,是把分类变量做 One-Hot 编码(比如 job 变成 job_teacher=1, job_coder=0, job_freelancer=0),然后喂给 K-means。这看似“解决了数值化问题”,实则埋下两大隐患:

  • 维度爆炸:一个有 100 个城市的字段,One-Hot 后变成 100 列,数据变得极其稀疏,K-means 的距离计算失去意义;
  • 语义断裂:One-Hot 向量之间天然正交,任意两个不同城市的向量距离都是 √2,算法无法感知“北京和天津地理邻近”这类潜在关系(虽然 kmodes 也不感知,但它至少不伪造这种虚假的“等距”关系)。

kmodes 绕开了编码环节,直接在原始语义层面操作,是更本源、更轻量的解法。

对比维度K-means(数值)kmodes(纯分类)k-prototypes(混合)One-Hot + K-means
输入数据要求全数值全字符串/整数类别混合,需指定分类列索引全数值(经编码)
中心点类型浮点数向量字符串元组混合元组(str+float)高维浮点向量
距离/不匹配定义欧氏距离不匹配字段数分类不匹配 + γ×数值距离欧氏距离(高维)
是否引入虚假数值关系是(编码强加)否否(数值部分原生处理)是(One-Hot 强加)
典型适用场景用户消费金额分层问卷多选题聚类用户全维度画像分群小规模、低基数分类

5. 什么时候该用 kmodes/k-prototypes?一个电商用户分群的真实决策树

5.1 场景还原:某电商平台要做精细化运营,手头有一份 50 万用户的脱敏行为表

字段包括:device_type(iOS/Android/H5)、acquisition_channel(微信/抖音/搜索/应用商店)、preferred_category(美妆/数码/家居/母婴)、avg_order_value(数值)、purchase_frequency(数值)、first_purchase_month(2023-01/2023-02/…)。运营同学想回答:“哪些用户群体值得重点投放?他们的共性行为路径是什么?”

这时,机械地套用 K-means 会出大问题:first_purchase_month是时间戳,但作为字符串处理时,K-means 会把它当成普通类别;若转成月份序号(1,2,3…),又丢失了“2023-01 和 2023-12 时间跨度大”的语义。而 kmodes/k-prototypes 的解法很清晰:

  • 第一步:识别数据类型
    device_type,acquisition_channel,preferred_category,first_purchase_month→ 分类变量(共 4 列)
    avg_order_value,purchase_frequency→ 数值变量(共 2 列)

  • 第二步:选择算法与参数
    因为存在明确的混合结构,首选k-prototypes。γ参数需调优:由于运营更关注“渠道+品类”这类强业务信号,数值指标只是辅助,故初始γ设为 0.1(弱化数值影响);后续根据轮廓系数(silhouette score)和业务可解释性微调。

  • 第三步:预处理要点(血泪经验)

    注意:first_purchase_month必须转为字符串,不能是 datetime 类型,否则k-prototypes会报错;
    注意:所有分类列若有缺失值(NaN),k-prototypes默认会丢弃整行——50 万行数据可能因此锐减到 30 万。稳妥做法是先用'unknown'填充;
    注意:acquisition_channel若有几十个渠道,其中 20 个只占 0.1% 流量,建议先做“长尾合并”(把小渠道统归为'other'),避免模式中心被噪声主导。

5.2 一次成功的聚类输出与业务解读

运行后,得到 4 个簇,其cluster_centroids_如下:

簇IDdevice_typeacquisition_channelpreferred_categoryfirst_purchase_monthavg_order_valuepurchase_frequency
0iOS微信美妆2023-08285.62.1
1Android抖音数码2023-031240.30.8
2H5搜索家居2022-11412.03.5
3iOS应用商店母婴2023-06678.21.4

这个结果可以直接交给运营团队:“看,这是四类高价值用户画像。第 0 类是‘微信引流的美妆 iOS 用户’,她们复购率高、客单价适中,适合推新品试用装;第 1 类是‘抖音引流的数码 Android 用户’,单次消费高但频次低,适合推以旧换新和配件套装……”——每一条结论,都锚定在真实、可验证的字段组合上,没有“平均用户”这种虚概念。

5.3 什么时候坚决别用?三个明确的“停用红线”

kmodes/k-prototypes 强大,但绝不万能。以下情况,请立刻切换思路:

  • 红线一:分类变量基数(unique count)超过 1000
    比如user_id作为一列放进聚类,或者product_sku_id有 5 万个取值。kmodes 的模式中心会变成“每个簇只包含 1~2 个用户”,完全丧失聚合意义。此时应先做 Embedding(如用 item2vec 对 sku 降维),再用 K-means。

  • 红线二:存在强序数关系(ordinal)却未告知算法
    比如满意度评分是['非常不满意','不满意','一般','满意','非常满意']。它本质是有序的,但 kmodes 当作无序类别处理,会丢失“非常满意”和“满意”比“非常不满意”更接近这一信息。正确做法是:要么用序数编码(1~5)后改用 K-means,要么用专门的序数聚类方法。

  • 红线三:你需要概率化的簇归属,而非硬划分
    kmodes/k-prototypes 输出的是0/1/2/3这样的硬标签,告诉你“用户 A 属于簇 2”。但如果你需要知道“用户 A 有 70% 概率属于簇 2,20% 属于簇 0”,那就得换用Gaussian Mixture Model (GMM)或Latent Class Analysis (LCA)等软聚类模型。

从那以后我每次拿到新数据,第一件事不是画分布图,而是打开df.dtypes和df.nunique(),对着每一列问:这是数字?是选项?还是藏着顺序的“伪数字”?只要发现两列以上是字符串且 unique 值合理(<500),我就立刻pip install kmodes,把KModes或KPrototypes加进 baseline pipeline。它不一定是最炫的模型,但往往是第一个让业务方点头说“这个分组,我看得懂”的模型。希望帮到你。

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