☰
商场客户聚类实战:KMeans与特征工程实现用户分群
2026/10/24 5:06:23 网站建设 项目流程

1. 项目概述与业务背景

1.1 为什么商场要做客户聚类

我做商场客户聚类这个项目,最早是因为一个很现实的业务问题:商场运营方手里攒了几十万条会员消费数据,却说不清楚自己的客户到底有哪几类。谁是高价值客户,谁是价格敏感型,谁只是偶尔路过随便逛逛,这些问题靠拍脑袋是回答不了的,更糟糕的是,运营方试图用同一个模板给所有客户发营销短信,结果高价值客户觉得被打扰,价格敏感客户觉得没诚意,两拨人都得罪了。

商场客户聚类就是用来解决这个问题的。它属于无监督学习,不需要事先标注任何标签,只靠数据本身把客户分成若干个组,让同一组内的客户消费行为尽可能相似,不同组之间差异尽可能大。聚完之后你会发现,原本几千几万个看起来乱糟糟的客户记录,突然就有了清晰的层次:可能是几个大类的区分,每一类下面还能细分出不同的小群体。

这个项目的价值不只在技术层面,更关键的是它的可直接落地性。聚类结果可以直接对接三大业务场景:第一个是精准营销,针对不同客户群体设计不同的优惠套餐和触达渠道;第二个是商场品类调整,看看各楼层各品类的到访转化情况,决定哪些品牌应该续约、哪些应当调整位置;第三个是会员运营策略,针对不同类型客户设计差异化的积分规则和会员等级体系。

适合来参考这个项目的人,我建议是这样三类:正在做数据挖掘课程作业、需要完整走一遍流程的同学;刚转行做数据分析师、手上拿到一堆业务数据却不知道从哪下手的从业者;以及商场零售运营相关岗位、想用数据驱动决策的业务人员。不需要你有多深的数学功底,只要你懂基本的Python和数据操作逻辑,今天这篇内容完全可以照着做下来。

1.2 项目整体的技术路线

整个项目我从头到尾走了一遍,大体上分成六个阶段。第一阶段是数据梳理,搞清楚表里有哪些字段、什么类型的值、数据量有多大;第二阶段是特征工程,决定用哪些字段参与聚类、怎么处理缺失值和异常值;第三阶段是标准化处理,这一步很多人会忽略,但它直接决定聚类效果;第四阶段是聚类模型构建,跑KMeans算法并确定最优的簇数K;第五阶段是结果分析,把每个簇的特征拉出来逐一解读;第六阶段是可视化呈现,把聚类结果用图表和画像描述展示给业务方。

在后面每个阶段里,我都会把具体操作、踩过的坑、调整的细节写清楚,让你不仅知道代码怎么写,更知道为什么这样写。到项目收尾的时候,还会单独写一节常见问题的排查记录,这些都是我在反复试错中积累下来的经验。

2. 数据准备与特征工程

2.1 原始数据长什么样

我这次用到的数据是某个模拟商场项目的会员消费记录,也就是常见的Mall Customer Segmentation Data这类数据集。初始表格里有2000条记录,每一条代表一个客户,共包含以下几个字段:客户ID、性别、年龄、年收入、消费评分。

客户ID就是唯一的身份编号,这个字段在聚类中不参与计算,只在后面对照结果时用来追溯具体客户;性别是类别型字段,分为男和女两类;年龄和年收入都是数值型字段,其中年收入一般是以千美元为单位,比如数值15代表年薪1万5千美元;消费评分是一个商场根据客户历史消费行为打出的综合分数,取值范围1到99,打分越高代表客户消费意愿和消费能力越强。

拿到原始数据之后,不要急着建模。我做的第一件事是探查数据的基本情况:整体看一遍数据的描述性统计,确认有没有空值、有没有明显的异常值。这一步用pandas的describe和info两个方法就足够。实测下来,这组模拟数据没有缺失值,类型也都规整,性别是文本型,年龄和收入是数值型,消费评分是整数型。

但模拟数据归模拟数据,真实场景下你拿到的数据往往没这么干净。缺失值、乱码文本、超出合理范围的取值都是常见情况。所以这部分的处理逻辑还是要认真对待,后面在常见问题章节我会专门展开讲几种典型脏数据的处理方法。

2.2 特征选择的关键决策

特征选择在聚类项目里的重要性,我怎么说都不为过。它决定了聚类结果到底有没有业务可解释性,甚至决定了聚类结果到底是有意义的还是统计噪音的产物。

这个项目里,我最终选定了两个核心特征参与聚类:年收入和消费评分。原因很简单:商场客户细分最关心的两个维度,一个是消费能力,一个是消费意愿。年收入代表“买不买得起”,消费评分代表“愿不愿意买”。拿着两个维度去分客户,得到的结果在业务上一定是有明显区分度的。

性别这个字段我做了对比实验。加上它跑过一次聚类,分类结果在画图的时候能清晰地看出性别差异,但客户画像会变得更碎,每簇里都出现男女比例接近的倾向,业务方的反馈是“看起来有差异,但不知道怎么用”。后来我做了简化版本,只用年龄、年收入、消费评分三个连续变量做聚类,结果稳定性反而更好。最后我采用了比较稳妥的方案:主体聚类用年收入加消费评分这两个核心维度,年龄等其他字段作为聚类完成之后的画像辅助分析维度,不参与聚类计算。

年龄这个字段也很微妙。初看它对客户分群有影响,比如年轻人和中年人的消费偏好确实不同,但在聚类实践中,年龄维度会把簇数推得很大,每个簇的业务差异反而不清晰。我的经验是:如果业务目标只是区分消费能力和消费意愿,年收入加消费评分就够了;如果还要研究各年龄段的偏好差异,可以单独做年龄维度的交叉分析,而不是强行把年龄塞进聚类模型。

2.3 数据清洗与标准化处理

数据清洗阶段,我按顺序做了几件常规但必须做的事。

第一步,检查并处理缺失值。对于缺失比例低于5%的字段,可以用均值或中位数填充,我当然更推荐中位数,因为它对离群值不敏感;对于缺失比例偏高的字段,宁可删除这一列也不要勉强填充。第二步,检查异常值。年收入如果出现负数、年龄超过100这些明显不合常理的情况,直接筛查出来单独处理。第三步,确认数据格式。性别这类文本字段要统一大小写和取值标准,比如“M”和“Male”要先统一成同一套编码。

标准化这一步是整个流程里最容易做错的地方。KMeans算法基于欧氏距离计算样本之间的相似度,它对特征的量纲非常敏感。年龄的单位是“岁”,取值范围大概在18到70之间;年收入的单位是“千美元”,取值范围大概在15到137之间;消费评分的取值范围在1到99之间。如果直接拿原始数值去做聚类,年龄和收入这两个特征会在距离计算中占据绝对主导地位,消费评分的影响几乎被淹没。

解决这个问题,有StandardScaler和MinMaxScaler两种常用方法。StandardScaler把数据转换成均值为0、方差为1的分布,适合特征本身近似正态分布的情况;MinMaxScaler把数据缩放到0到1之间,适合特征分布未知或者有明显边界的情况。我用的是StandardScaler,实测下来聚类效果稳定。给你一个可以直接用的代码参考:

from sklearn.preprocessing import StandardScaler # 选择参与聚类的特征列 features = df[['Annual Income (k$)', 'Spending Score (1-100)']] # 标准化处理 scaler = StandardScaler() scaled_features = scaler.fit_transform(features) # 查看标准化后的数据分布 print('均值:', scaled_features.mean(axis=0)) print('标准差:', scaled_features.std(axis=0))

标准化之后你会发现,每个特征的均值为0、标准差为1,此时它们对距离计算的贡献才是等权的。做完这一步才可以放心跑聚类模型。

3. 聚类算法核心原理与选型

3.1 KMeans为什么是首选

商场客户聚类这个场景,我首选的算法是KMeans。它的计算过程可以这样理解:你有一堆点散落在二维或高维空间里,算法随机指定K个中心点,然后每个点归到离自己最近的那个中心,所有点归完类之后,再把每类的中心重新计算为这类的质心,重新分配、重新计算,反复迭代,直到中心点不再移动。

KMeans在客户分群场景里优势很明显。第一,计算速度快,2000条样本跑几百次迭代只需要毫秒级别;第二,结果容易解释,每个簇的中心点就是该簇客户群的典型特征取值,直接可以写成画像描述;第三,实现成本低,Python的Scikit-learn库里一行代码就能调用,不需要额外写复杂的逻辑。

当然KMeans也有它的短板。它对异常值敏感,个别极端值会把簇中心拉偏;对初始中心点的选择敏感,不同初始点可能导致不同的聚类结果;而且它倾向于找到球形分布的簇,对形状怪异的簇划分效果一般。在商场客户聚类场景里,这些问题的影响相对可控,因为客户特征分布大体上还是比较规整的,所以KMeans依然是最合适的选择。

我还对比过另外两种常见方案。层次聚类在业务解释上做得更好,可以一次性输出从1簇到N簇的全部分层关系,但计算复杂度偏高,2000条样本构建完整层次树会明显变慢;DBSCAN可以自动发现任意形状的簇,同时还能识别离群点,但它的两个关键参数(半径eps和最小样本数min_samples)对结果影响极大,调参成本高,而且在客户群体边界模糊的情况下,簇的数量和归属不够稳定。综合比较,这个项目就用KMeans最合适。

3.2 核心参数详解:如何确定K值

确定K值,也就是决定把客户分成几类,是整个项目中最关键、也最考验经验的环节。K值太小,不同特点的客户被强行揉在一起,细分失去意义;K值太大,每一类都只剩一小撮人,业务方又没法针对这么碎的群体设计策略。

我用了两种方法交叉验证。第一种是肘部法则,这是最直观的方法:分别计算K从1到10时的簇内误差平方和,也就是SSE,然后画一条折线图。随着K增大,SSE会持续下降,但在某个点之后下降速度会明显放缓,这个拐点就是所谓的“肘部”。从我的测试数据来看,K=5和K=6附近出现了明显的拐点,说明这个范围内聚类效果的边际收益开始递减。

from sklearn.cluster import KMeans import matplotlib.pyplot as plt # 计算不同K值对应的SSE sse = [] for k in range(1, 11): kmeans = KMeans(n_clusters=k, n_init=10, random_state=42) kmeans.fit(scaled_features) sse.append(kmeans.inertia_) # 绘制肘部法则图 plt.figure(figsize=(10, 6)) plt.plot(range(1, 11), sse, marker='o') plt.xlabel('聚类数量 K') plt.ylabel('簇内误差平方和 SSE') plt.title('肘部法则确定最优K值') plt.grid(True) plt.show()

光是看肘部图还不够,我用轮廓系数再做了一次定量验证。轮廓系数综合评估簇内紧密程度和簇间分离程度,取值在-1到1之间,越接近1说明聚类效果越好。实测下来,K=5的时候平均轮廓系数最高,达到约0.55,这说明五次分的整体聚合度最好。

from sklearn.metrics import silhouette_score # 计算不同K值的轮廓系数 silhouette_scores = [] for k in range(2, 11): kmeans = KMeans(n_clusters=k, n_init=10, random_state=42) labels = kmeans.fit_predict(scaled_features) score = silhouette_score(scaled_features, labels) silhouette_scores.append(score) # 输出轮廓系数列表 for k, score in enumerate(silhouette_scores, start=2): print(f'K={k}, 轮廓系数={score:.4f}')

最后我把K=5定成了最终方案。补充说一句,如果你在业务上已经有明确的分群诉求,比如领导直接说“我就要按高中低三档分客户”,那K值可以直接定为3。算法是为业务服务的,不要为了追求指标好看而脱离实际需求。

3.3 聚类模型训练与结果导出

确定K=5之后,训练模型就变成了一件很顺理成章的事情。我在KMeans里设置了随机状态random_state=42来保证结果可复现,设置了n_init=10来消除初始中心点选择的随机性影响。

# 最终聚类模型训练 kmeans = KMeans(n_clusters=5, n_init=10, random_state=42) cluster_labels = kmeans.fit_predict(scaled_features) # 将聚类标签添加回原始数据表 df['Cluster'] = cluster_labels # 查看各类别的样本数量 print(df['Cluster'].value_counts().sort_index())

跑完这步,你手上就有了2000条客户记录的分群结果。我的输出显示五个簇的样本量大约分别是500、400、350、380、370左右,比例相对均衡,没有出现某个簇特别大或者特别小的极端情况,说明分群结果在统计上比较健康。

我特别建议你在这个阶段就把聚类结果和原始客户ID以及其他特征字段合并保存下来,导出成CSV文件,方便后续做画像分析。这里的合并逻辑很简单,因为聚类标签的顺序和原始数据的顺序是一一对应的,直接用pandas的列合并操作就行。

# 导出带标签的完整数据 df.to_csv('customer_clusters.csv', index=False)

4. 聚类结果分析与客户画像解读

4.1 五个客户群体的特征归纳

聚类完成之后,最激动人心的部分来了:逐个簇去分析它们的特征,看业务方能从哪些维度理解每一类客户。我是用groupby操作把五个簇的年收入均值和消费评分均值一起算出来,对照着看每一簇的定位。

第一个簇的特征是高收入高评分。这批客户的年收入均值在80千美元以上,消费评分在75以上。他们既买得起也愿意买,是商场最核心的利润来源,我把它命名为“高价值忠诚客户”。第二个簇的特征是高收入低评分。年收入很高,但消费评分只有20左右。他们有消费能力但是对这家商场没有特别强的消费意愿,可能是被动的、偶尔才来一次的客户。我把它命名为“高收入潜力客户”。

第三个簇是低收入高评分。年收入在40千美元以下,消费评分却有70以上。这批客户收入不高但非常愿意消费,可能是年轻群体,或者对品牌忠诚度很高的群体。我把它命名为“价格敏感活跃客户”。第四个簇是低收入低评分。收入低、消费评分也低,属于冷眼旁观型,是商场目前没能吸引到的群体,我把它命名为“低价值观望客户”。第五个簇是中等收入中等评分,各项指标都处在中间位置,属于最普通的日常消费者群体,占比最大,我把它命名为“普通大众客户”。

这五个簇的划分,在散点图上非常直观:五个簇分别落在坐标系的五个区域,相互之间重叠不多,边界大体清晰,这说明聚类质量确实不错。

4.2 用画像让业务方看懂聚类结果

聚类结果要给业务方汇报的时候,不能只甩给他们一张散点图和几个统计数字,他们关心的是“这五类人分别长什么样、我该怎么服务他们”。所以我把统计结果转译成了更具体的用户画像描述,这部分工作才是整个项目交付的关键。

高价值忠诚客户往往是对商场品牌有深度认同的人群,他们的消费频次高、客单价也高、对新品和会员专属活动的响应率明显高于其他群体。运营策略上,应该给他们提供优先购买权、专属折扣和高等级会员权益,维系好关系防止流失。

高收入潜力客户是最有增长空间的群体,他们不是没有钱,只是商场目前的商品和服务还没戳中他们的痛点。运营策略上,需要重点挖掘他们的偏好,比如定向推送高端品牌活动信息、专属体验邀请等,尝试争取他们的消费频次提升。

价格敏感活跃客户是活动驱动型消费者,商场促销活动期间能看到他们明显活跃起来。运营策略上,应该多给他们发放满减券、折扣券,配合积分翻倍活动激活消费。

低价值观望客户暂时不是运营重点,但也不是完全放弃,可以通过低成本的品牌宣传慢慢培育。普通大众客户是基本盘,日常运营保持常规推送即可,不用做太多特殊投入。

4.3 数据可视化:一张图看清全部结果

我做了一张二维散点图,以年收入为横轴、消费评分为纵轴,用不同颜色标示五个簇。这张图是整个项目汇报中最直观的交付物。

import matplotlib.pyplot as plt # 按簇着色绘制散点图 plt.figure(figsize=(12, 8)) scatter = plt.scatter(df['Annual Income (k$)'], df['Spending Score (1-100)'], c=df['Cluster'], cmap='rainbow', alpha=0.6) plt.colorbar(scatter, label='聚类标签') plt.xlabel('年收入 (k$)') plt.ylabel('消费评分 (1-100)') plt.title('商场客户聚类结果可视化') plt.grid(True, linestyle='--', alpha=0.4) plt.show()

从这张图上可以清楚看到,五个簇在散点图上分别占据了五个相对独立的区域,左右两大阵营是收入高低之分,上下两大阵营是消费意愿高低之分,中间还夹着一个过渡的大众群体。这张图给业务方看的时候,几乎不用多做解释,一眼就能明白客户是怎么被分出来的。

5. 常见问题与排查技巧实录

5.1 标准化前后聚类效果差异巨大

我第一次跑KMeans的时候,偷懒没有做标准化,直接把原始的年收入和消费评分喂了进去。结果聚类结果非常奇怪:一个簇包含了绝大多数样本,另外几个簇几乎是贴着边界的极小团体。原因也很清楚:年收入的最大值超过130,方差很大,在欧氏距离计算中占据绝对主导,消费评分取值范围只有1到99,对距离的贡献微乎其微,结果聚类实际上只拿年收入一个维度在分。

这个问题不是个例,很多初学者都会踩。排查方法也很简单:看一眼标准化前后各特征的均值、方差,如果不同特征的数值范围差异超过一个数量级,就必须要做标准化。教训就是:先标准化,再聚类,顺序不能乱。

5.2 初始中心点随机性导致结果不稳定

另一个我踩过的问题是KMeans的结果在不同的随机种子下会轻微变动。第一次跑KMeans没设置random_state,第二次跑出来某个簇的样本量变化了百分之十,边缘客户在不同簇之间反复横跳。这个问题是因为KMeans采用随机初始化中心点的策略,遇到不同初始点会收敛到不同局部最优解。

解决办法有三层。第一层是设置random_state为固定数值,保证每次跑的结果一样,方便复现和排查;第二层是设置n_init参数,指定算法多次随机初始化并从中选最优结果;第三层是如果数据量不大、时间允许,可以多试几个随机种子,观察聚类结果是否稳定。如果不同随机种子下结果波动特别大,说明数据本身簇的边界不清晰,可以尝试增加聚类数或者换算法。

5.3 数据缺失和异常值怎么处理

真实业务数据远没有模拟数据干净。我在另一个类似项目里遇到过客户年龄字段有将近百分之十五的缺失值,收入字段出现了负数和几千美元以上的极端值。处理这类问题我的经验是分步走。

缺失值方面,先看缺失比例。低于百分之五,中位数填充可以接受;百分之五到百分之十五,考虑用多重插补或者根据其他相关字段预测,也可以在实际业务允许时直接把缺失样本剔除;超过百分之十五到百分之二十,这个字段的可靠性就要打问号,是否还值得参与建模需要慎重讨论。异常值方面,先用箱线图或三倍标准差规则识别异常值,再结合业务实际判断是否剔除。比如收入为负数,要么是数据录入错误,要么是退款场景导致的,必须单独处理;比如年龄超过100,基本可以确定是脏数据,直接剔除。

5.4 聚类数K选择的经验尺度

很多人在确定K值的时候容易陷入纠结,我直接把我个人经验整理成了一条参考思路:先画肘部图找到拐点范围,再用轮廓系数定量验证,最后结合业务需求做取舍。如果业务方有明确的分群数量要求,直接满足业务要求,不用纠结统计指标。

K值的实际下限不要低于2,上限不要高于节点的平方根数量,这是经验法则。比如你有2000条样本,K值上限大约在45,但实际上一般情况K在2到10之间就足够。K太小会让簇内部的差异过大,K太大会严重碎片化。还是那句话,聚类是为业务服务的,K值选多少要回到业务需求上去看。

5.5 标准化与特征选择踩坑补充

特征选择还有一个我差点忽略的点:如果两个特征之间的相关性过高,比如年收入和消费支出高度相关,它们实际上在重复表达同一个信息维度,参与聚类会强化该维度的影响。我在选特征时快速算了相关系数矩阵,确认年收入和消费评分的相关性在可接受范围,才放心使用。如果你的特征相关性很高,建议先做降维处理,比如主成分分析,或者只保留其中一个代表性特征。

5.6 真实业务数据与模拟数据的差异

这次项目用的是经典的商场客户模拟数据集,两百个样本是早期版本,我这次用的两千条版本,特征结构和分析逻辑一致。但真实商场的数据通常包含更多字段,比如消费时间、消费频次、到访门店、购买品类、支付渠道、会员等级等。字段变多的同时,特征工程的工作量也会大幅增加。

我建议你在真实业务场景中做客户聚类时,优先从“消费能力”“消费活跃度”“消费偏好”三个维度去构造特征,然后分别聚类再交叉分析。比如先用“消费金额”和“消费频次”做价值分层,再用“购买品类偏好”对每一层做偏好细分,这样得到的客户画像会比一次聚五六个维度要清晰得多。这也算是我做了多个类似项目之后总结出的一个通用打法。

整体来说,商场客户聚类这个项目麻雀虽小但五脏俱全,从数据理解、特征工程、标准化、建模到结果解释和业务落地,完整走完会对无监督学习的全链路有非常扎实的认知。如果你正在学机器学习,这个项目可以作为你第一个完整的数据挖掘实践;如果你在商业分析岗位工作,这个项目的思路和方法可以直接迁移到你手头的会员数据、用户行为数据上,并不只适用于商场场景。各位在实际操作中如果遇到别的问题,也可以沿着我上面提到的排查思路一步步检查,大部分问题都出在数据预处理和参数选择这两个环节。

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

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

立即咨询