基于RFM模型和聚类分析的用户分层分析
1.项目背景
1.1 行业背景与问题提出
在零售行业中,超市作为商品与消费者之间的重要纽带,面对的客户群体越来越多样且复杂。为更准确地把握客户需求、优化商品布局、提升营销的针对性,并最终提高客户的满意度与忠诚度,对超市客户进行科学细分显得尤为关键。在此背景下,本实验尝试将RFM模型与K-means聚类算法相结合,对超市客户群体展开深入的细分研究。
RFM模型是评估客户价值和预测其行为的一种经典工具,它通过三个关键维度——最近一次消费时间(Recency)、消费频率(Frequency)和消费金额(Monetary)——来系统刻画客户的购买特征与潜在消费能力。借助这一模型,超市管理者可以更清楚地识别出哪些客户是活跃且具有高价值的,哪些客户则需要进一步激活或争取挽回。
不过,仅依靠RFM模型的三个指标,有时候还难以全面揭示客户之间更深层的差异与潜在联系。为此,本实验引入K-means聚类算法。该算法能够在无监督学习框架下,依据客户的RFM特征,自动将其划分为若干具有一定相似性的群体。通过聚类,超市管理者不仅能更直观地认识客户群体的整体结构,也能为后续制定差异化的营销策略提供坚实的数据基础。
综上所述,本实验立足于应对超市客户群体日益多元和复杂的现实挑战,融合RFM模型的分析能力与K-means聚类的分组优势,推动对超市客户的精细化细分。这一细分结果预计将在库存管理、商品陈列、促销活动设计及客户关系维护等多个方面带来优化和提升,助力超市在日趋激烈的市场竞争中保持优势地位。
1.2 研究对象与数据说明
本次分析以某在线零售平台为研究对象。该公司主营家居用品、礼品及装饰品等B2C零售业务,于2010年12月至2011年12月期间积累了全年度的交易历史。我们获取了其共541909条订单记录(data.csv),该数据集(包含8个字段)记录了每一笔交易的订单编号、商品编号及描述、购买数量、订单日期、单价、客户ID及客户所在国家/地区,其中订单编号以“C”开头的记录表示订单被取消。通过该数据,我们能够构建RFM模型并进行客户价值分层与聚类分析。
1.3 分析目标与研究问题
- 构建RFM指标体系,计算每位客户的R、F、M值
- 基于RFM值进行初步客户分层(如重要价值客户、一般挽留客户等)
- 利用K-means聚类算法对客户进行更客观的群体划分
- 分析各客户群的特征,并提出针对性的运营策略
2.数据理解与预处理
2.1 数据加载与字段说明
| 字段 | 说明 |
|---|---|
| InvoiceNo | 订单编号,如果订单以“C”开头,表示订单被取消 |
| StockCode | 商品编号 |
| Description | 商品描述信息 |
| Quantity | 购买商品数量 |
| InvoiceDate | 订单日期 |
| UnitPrice | 商品单价 |
| CustomerID | 客户ID |
| Country | 客户所在的国家 |
# 导入数据和相关库
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
sns.set(style='darkgrid', font_scale=1.2)
plt.rcParams['font.sans-serif'] = ['SimHei'] #解决中文显示
plt.rcParams['axes.unicode_minus'] = False #解决符号无法显示
import warnings
warnings.filterwarnings('ignore')
# 设置风格
plt.style.use('ggplot')
data = pd.read_csv('data.csv')
print(data.shape)
data.head()
(541909, 8)
| InvoiceNo | StockCode | Description | Quantity | InvoiceDate | UnitPrice | CustomerID | Country | |
|---|---|---|---|---|---|---|---|---|
| 0 | 536365 | 85123A | WHITE HANGING HEART T-LIGHT HOLDER | 6 | 2010/12/1 8:26 | 2.55 | 17850.0 | United Kingdom |
| 1 | 536365 | 71053 | WHITE METAL LANTERN | 6 | 2010/12/1 8:26 | 3.39 | 17850.0 | United Kingdom |
| 2 | 536365 | 84406B | CREAM CUPID HEARTS COAT HANGER | 8 | 2010/12/1 8:26 | 2.75 | 17850.0 | United Kingdom |
| 3 | 536365 | 84029G | KNITTED UNION FLAG HOT WATER BOTTLE | 6 | 2010/12/1 8:26 | 3.39 | 17850.0 | United Kingdom |
| 4 | 536365 | 84029E | RED WOOLLY HOTTIE WHITE HEART. | 6 | 2010/12/1 8:26 | 3.39 | 17850.0 | United Kingdom |
2.2 数据质量检查
1.检查数据缺失值
data.columns # 查看列名
data.info() # 查看数据类型和缺失值情况
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 541909 entries, 0 to 541908
Data columns (total 8 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 InvoiceNo 541909 non-null object
1 StockCode 541909 non-null object
2 Description 540455 non-null object
3 Quantity 541909 non-null int64
4 InvoiceDate 541909 non-null object
5 UnitPrice 541909 non-null float64
6 CustomerID 406829 non-null float64
7 Country 541909 non-null object
dtypes: float64(2), int64(1), object(5)
memory usage: 33.1+ MB
# 缺失值检查
print(data.isnull().sum())
InvoiceNo 0
StockCode 0
Description 1454
Quantity 0
InvoiceDate 0
UnitPrice 0
CustomerID 135080
Country 0
dtype: int64
经检查,数据集中存在以下缺失值情况:
CustomerID: 存在大量的缺失值,而缺失具体用户的订单,对我们来说是没有意义的,我们选择将其删除。Description: 存在少许的缺失值,该列对我们分析没有影响,我们将其填充为“无”即可。
此外,初步观察还发现以下几点需要注意:
InvoiceNo:当前为object类型,表明订单编号并非纯数值(如包含字符“C”表示取消订单),需在计算时特殊处理。CustomerID:当前为float64类型,实际应为整数,后续需转换为int类型。InvoiceDate:当前为object类型,需转换为datetime类型,以便计算时间间隔。
# 对缺失值进行处理
data["Description"].fillna("无", inplace=True)
data.dropna(subset=["CustomerID"], inplace=True) # 删除缺失具体用户的订单。
2.重复值处理
# 重复值检查
print("重复记录数:", data.duplicated().sum())
# 查找数据中所有重复的行,并按 InvoiceNo 和 StockCode 排序后显示前几行
data[data.duplicated(keep=False)].sort_values(by=['InvoiceNo', 'StockCode']).head()
重复记录数: 5225
| InvoiceNo | StockCode | Description | Quantity | InvoiceDate | UnitPrice | CustomerID | Country | |
|---|---|---|---|---|---|---|---|---|
| 494 | 536409 | 21866 | UNION JACK FLAG LUGGAGE TAG | 1 | 2010/12/1 11:45 | 1.25 | 17908.0 | United Kingdom |
| 517 | 536409 | 21866 | UNION JACK FLAG LUGGAGE TAG | 1 | 2010/12/1 11:45 | 1.25 | 17908.0 | United Kingdom |
| 485 | 536409 | 22111 | SCOTTIE DOG HOT WATER BOTTLE | 1 | 2010/12/1 11:45 | 4.95 | 17908.0 | United Kingdom |
| 539 | 536409 | 22111 | SCOTTIE DOG HOT WATER BOTTLE | 1 | 2010/12/1 11:45 | 4.95 | 17908.0 | United Kingdom |
| 489 | 536409 | 22866 | HAND WARMER SCOTTY DOG DESIGN | 1 | 2010/12/1 11:45 | 2.10 | 17908.0 | United Kingdom |
数据集中存在约 5,225 条完全重复的记录。经检查,这些重复主要出现在同一订单编号(InvoiceNo)下,同一商品(StockCode)被拆分多行记录,可能是由于系统录入过程中的重复采集或订单拆分所致。考虑到这些重复行在业务逻辑上仍属于同一有效交易,且后续 RFM 计算中数量和金额均采用累加方式处理(不受重复行影响),因此暂不进行删除操作。
3.异常值处理
在该数据集中,我们需要对如下变量检测异常值:
-
Quantity -
UnitPrice
Quantity 和 UnitPrice 存在负值(退货订单),在RFM计算中通过退货标识(InvoiceNo以’C’开头)进行逻辑排除,不直接删除。
# 类型转换
data['CustomerID'] = data['CustomerID'].astype(int)
data['InvoiceDate'] = pd.to_datetime(data['InvoiceDate'])
data['Monetary'] = data['Quantity'] * data['UnitPrice']
2.3 数据分布情况
# 用describe函数查看数值型数据的统计信息
data.describe()
| Quantity | InvoiceDate | UnitPrice | CustomerID | Monetary | |
|---|---|---|---|---|---|
| count | 406829.000000 | 406829 | 406829.000000 | 406829.000000 | 406829.000000 |
| mean | 12.061303 | 2011-07-10 16:30:57.879207424 | 3.460471 | 15287.690570 | 20.401854 |
| min | -80995.000000 | 2010-12-01 08:26:00 | 0.000000 | 12346.000000 | -168469.600000 |
| 25% | 2.000000 | 2011-04-06 15:02:00 | 1.250000 | 13953.000000 | 4.200000 |
| 50% | 5.000000 | 2011-07-31 11:48:00 | 1.950000 | 15152.000000 | 11.100000 |
| 75% | 12.000000 | 2011-10-20 13:06:00 | 3.750000 | 16791.000000 | 19.500000 |
| max | 80995.000000 | 2011-12-09 12:50:00 | 38970.000000 | 18287.000000 | 168469.600000 |
| std | 248.693370 | NaN | 69.315162 | 1713.600303 | 427.591718 |
# 绘制"Quantity"和"UnitPrice"的分布图
# 创建1行2列的子图布局
fig, ax = plt.subplots(1, 2)
# 设置整个图形的大小为宽15英寸、高5英寸
fig.set_size_inches(15, 5)
# 在第一个子图(左侧)上绘制"Quantity"列的分布图
sns.kdeplot(data["Quantity"], ax=ax[0])
# 在第二个子图(右侧)上绘制"UnitPrice"列的分布图
sns.kdeplot(data["UnitPrice"], ax=ax[1])

# 绘制"Quantity"和"UnitPrice"的箱线图
# 创建1行2列的子图布局
fig, ax = plt.subplots(1, 2)
# 设置整个图形的大小为宽15英寸、高5英寸
fig.set_size_inches(15, 5)
# 在第一个子图(左侧)上绘制"Quantity"列的箱线图,用于观察数量数据的分布和异常值
sns.boxplot(y="Quantity", data=data, ax=ax[0])
# 在第二个子图(右侧)上绘制"UnitPrice"列的箱线图,用于观察单价数据的分布和异常值
sns.boxplot(y="UnitPrice", data=data, ax=ax[1])

3.RFM模型
3.1 模型概念
RFM模型是客户关系管理领域应用最广泛的价值评估工具之一,它通过三个简单而有力的行为指标来刻画客户的购买特征。其中R代表最近一次消费时间(Recency),用于衡量客户距离当前日期最后一次有效购买的天数,该指标越小表明客户越活跃;F代表消费频率(Frequency),即客户在观察期内完成的有效购买次数,它反映了客户与品牌之间互动的紧密程度与忠诚度;M代表消费金额(Monetary),即客户累计贡献的总消费额(扣除退货后的净值),直接体现了客户的经济价值。三个维度从不同侧面描述客户的行为模式,综合起来可以较为全面地评估客户对企业的贡献潜力,为后续分层与精细化运营奠定数据基础。
3.2 客户划分
在计算出每位客户的R、F、M原始值之后,需要设计合理的规则对客户进行类型划分,从而将具有相似行为特征的客户归为同一群体。常用的划分方法包括阈值法、打分法和聚类法。阈值法以各维度的均值或中位数作为边界,将客户划分为“高”或“低”两组,进而组合出八种基本类型,例如重要价值客户(R、F、M均高于均值)、一般挽留客户(均低于均值)等。
以下是基于RFM模型(以各指标均值或中位数为阈值,高于阈值为“高”、低于阈值为“低”)所划分的八种客户类型:
| 类型 | R(最近消费) | F(频率) | M(金额) | 客户特征简述 |
|---|---|---|---|---|
| 重要价值客户 | 高 | 高 | 高 | 近期有消费、购买次数多且金额大,是最有价值的忠诚客户。 |
| 重要保持客户 | 低 | 高 | 高 | 曾经消费频率高、金额大,但近期没有活跃,需要主动召回和维系。 |
| 重要发展客户 | 高 | 低 | 高 | 近期有消费且金额高,但购买频率较低,有潜力发展为重要价值客户。 |
| 重要挽留客户 | 低 | 低 | 高 | 消费金额高但频率低且近期不活跃,需重点挽留防止流失。 |
| 一般价值客户 | 高 | 高 | 低 | 近期频繁购买但单次金额不大,属于活跃但价值偏低的人群。 |
| 一般保持客户 | 低 | 高 | 低 | 过去购买频繁但金额小,近期不活跃,可尝试激活。 |
| 一般发展客户 | 高 | 低 | 低 | 新近接触、消费额和频率均低,需培育消费习惯。 |
| 一般挽留客户 | 低 | 低 | 低 | 所有指标均低于阈值,价值最低,可适当放弃或批量唤醒。 |
打分法则将每个指标划分为多个等级,通过评分或加权求和得到综合分数再进行归类。而聚类算法(如K‑means)可以在无需预设分类标准的情况下,基于客户在RFM特征空间中的距离自动形成若干紧密簇,使群体内部的相似性最大、群体间的差异性最大,从而提升划分的客观性和数据驱动性。本分析先采用均值阈值法完成初步分层,再结合聚类分析对客户群体进行更深入的细分,以便发现不同价值层次中隐藏的潜在规律。
3.3 模型作用
RFM模型的核心作用在于将模糊的客户行为转化为可量化、可比较的数值指标,进而支持企业制定差异化的营销策略与资源分配方案。通过识别出R、F、M均高的“重要价值客户”,企业可以为其提供专属服务、优先客服或个性化推荐,从而维持其高忠诚度并提升其生命周期价值;对于R值较高但F和M较低的“潜在价值客户”(通常为新近接触品牌的用户),可以采取频繁的互动与激励措施(如推送优惠券、会员积分活动等)以增强其复购意愿,逐步提升其消费频率与金额;而对于R、F、M均较低的“一般挽留客户”,则无需过度投入大量资源,可通过批量触达或节日促销进行适当激活。此外,定期更新RFM指标还能帮助运营团队动态监控客户群体的迁移趋势,及时发现价值下降或流失风险,为库存管理、商品陈列优化及客户挽回策略提供数据依据。将RFM模型与聚类算法相结合,能够进一步提升分层的客观性与解释力,使企业能够更科学地把握客户结构,在激烈的市场竞争中保持优势。
3.4 具体实现
3.4.1 RFM指标计算
def build_R(group, current_date):
"""计算客户最近消费间隔天数"""
# 筛选出该客户的正常订单(非退货)
normal = group[~group['InvoiceNo'].str.startswith('C')]
# 如果没有正常订单,返回一个较大值
# result用于存储结果,实际是timedelta对象,需要使用.days属性获取天数
if len(normal) == 0:
return (current_date - group['InvoiceDate'].min()).days
# 计算当前日期与最后一次正常购买日期的间隔
return (current_date - normal['InvoiceDate'].max()).days
def build_F(group):
"""
计算客户的有效消费频率(排除退货订单)
group_data: 分组后的客户数据
"""
# 判断是否为退货订单(订单编号以"C"开头表示退货)
normal = group[~group['InvoiceNo'].str.startswith('C')]
refund = group[group['InvoiceNo'].str.startswith('C')]
# 正常订单数量 - 退货订单数量 = 有效购买次数
# 如果订单数量小于0,则视为0
return max(normal['InvoiceNo'].nunique() - refund['InvoiceNo'].nunique(), 0)
def build_M(group):
return max(group['Monetary'].sum(), 0)
RFM = pd.DataFrame()
g = data.groupby('CustomerID')
current_date = pd.to_datetime('2012-01-01')
RFM['Recency'] = g.apply(build_R, current_date)
RFM['Frequency'] = g.apply(build_F)
RFM['Monetary'] = g.apply(build_M)
RFM['Recency'] = max(RFM['Recency'])-RFM['Recency'] # 为了方便评估,R值越小价值越大,这里将R值做相应处理
RFM.head()
| Recency | Frequency | Monetary | |
|---|---|---|---|
| CustomerID | |||
| 12346 | 48 | 0 | 0.00 |
| 12347 | 371 | 7 | 4310.00 |
| 12348 | 298 | 4 | 1797.24 |
| 12349 | 355 | 1 | 1757.55 |
| 12350 | 63 | 1 | 334.40 |
3.4.2 基于均值的客户分层
本节以 R、F、M 各维度的均值作为划分阈值,然后根据客户的消费金额是否高于均值判断“重要”或“一般”,再结合 R 和 F 与均值的关系,将客户细分为“价值客户”“保持客户”“发展客户”“挽留客户”四种类型。组合后总共可得到八种客户标签,例如“重要价值客户”表示 R、F、M 均高于均值,是最具忠诚度和贡献度的群体。最后通过条形图直观展示各类客户的数量分布,帮助企业识别核心客户群体及待激活的客户。
R_bar, F_bar, M_bar = RFM.mean()
def classify(row):
level = '重要' if row['Monetary'] >= M_bar else '一般'
if row['Recency'] >= R_bar and row['Frequency'] >= F_bar:
level += '价值'
elif row['Recency'] < R_bar and row['Frequency'] >= F_bar:
level += '保持'
elif row['Recency'] >= R_bar and row['Frequency'] < F_bar:
level += '发展'
else:
level += '挽留'
return level + '客户'
RFM['RFM_Class'] = RFM.apply(classify, axis=1)
RFM['RFM_Class'].value_counts()
RFM_Class
一般发展客户 1628
一般挽留客户 1317
重要价值客户 682
一般价值客户 443
重要发展客户 168
一般保持客户 65
重要挽留客户 40
重要保持客户 29
Name: count, dtype: int64
# 设置图形尺寸
plt.figure(figsize=(10, 6))
# 获取'RFM_Class'列中各类别的出现次数,并按次数从高到低排序,只保留类别名称作为顺序
# value_counts() 返回 Series,index 就是类别名称,且默认按计数降序排列
order = RFM['RFM_Class'].value_counts().index
# 绘制条形图:x轴是RFM_Class列,order指定条形的显示顺序(按频数降序)
# palette='viridis' 使用viridis颜色映射
ax = sns.countplot(x=RFM['RFM_Class'], order=order, palette='viridis')
# 旋转x轴刻度标签45度,避免重叠
plt.xticks(rotation=45)
# 在每个条形上方添加数字标签
# ax.patches 包含所有条形对象(矩形)
for p in ax.patches:
# p.get_height() 获取条形的高度(即该类的客户数)
# p.get_x() + p.get_width()/2 计算条形中心点的x坐标
# ha='center' 水平居中,va='bottom' 垂直底部对齐(使标签贴在条形顶端上方)
ax.annotate(f'{p.get_height()}',
(p.get_x() + p.get_width() / 2, p.get_height()),
ha='center', va='bottom')
plt.title('RFM初步客户分层数量分布')
plt.xlabel('客户类型')
plt.ylabel('客户数')
plt.tight_layout()
plt.show()

3.5 RFM扩展说明
实际上,RFM 模型仅提供了 R(最近一次消费)、F(消费频率)和 M(消费金额)三个核心维度,而对于每个维度上的具体划分准则,并不存在绝对统一的标准。以下是几种常见的可行方案:
- 以各维度的均值作为划分阈值
- 以各维度的分位数作为划分阈值(中位数属于分位数的一种特例)
- 将每个维度划分为两个区间,也可进一步细分为多个区间
- 先对区间进行打分处理(分箱),再基于得分进行划分
- 为 R、F、M 三个维度赋予不同的权重
然而,通过 RFM 模型对用户进行划分后,往往会产生较多的用户类别,且划分方式在一定程度上带有主观性。那么,如何在减少客户类别数量的同时,提高划分结果的客观性呢?聚类算法是一种有效的解决方案。
4.K-means算法
4.1 聚类分析介绍
4.1.1 概念
聚类分析是一种无监督学习方法,它能在没有已知标签(即没有标准答案)的情况下,根据数据的内在相似性将样本自动分成多个簇(Cluster),使得:
-
组内相似度尽可能高(同一类用户特征接近)
-
组间差异尽可能大(不同类用户有明显区别)
4.1.2 应用场景
| 应用领域 | 具体说明 |
|---|---|
| 客户群体划分 | 根据不同的客户群体制定不同的营销策略 |
| 社交网络分析 | 根据用户的交互关系(私信、@、转发、共同好友)进行聚类,判断哪些用户之间相互认识、属于同一社交圈层 |
| 文档处理 | 对文档按主题自动聚类(新闻分类、工单归类),或对词汇按语义聚类,对相似内容的文档进行划分 |
| 异常检测 | 识别离群点或极小簇,用于欺诈检测、刷单识别、设备异常监控等 |
| 市场调研 | 根据问卷答案聚类出不同价值观或消费观念的客群,辅助产品定位 |
4.2 算法步骤
K-Means 是一种基于距离的划分式聚类算法,核心思想是将数据分成 K 个簇,使得每个样本到其所属簇中心的距离平方和最小。
| 步骤 | 操作说明 |
|---|---|
| 1. 设定 K 值 | 预先指定要划分的簇的数量 K |
| 2. 初始化质心 | 随机选取 K 个样本点作为初始簇中心(质心) |
| 3. 分配样本 | 计算每个样本到各个质心的距离,将其归入距离最近的质心所在簇 |
| 4. 更新质心 | 对每个簇,计算簇内所有样本的均值,作为新的质心 |
| 5. 重复迭代 | 重复步骤 3 和 4,直到质心不再变化或达到预设迭代次数 |
4.3 优化目标
K-Means 的优化目标是最小化所有簇内样本到其对应质心的距离平方和,即最小化 SSE(Sum of Squared Errors,误差平方和)。
数学形式
SSE=∑i=1K∑x∈Ci∥x−μi∥2SSE = \sum_{i=1}^{K} \sum_{x \in C_i} \| x - \mu_i \|^2SSE=i=1∑Kx∈Ci∑∥x−μi∥2
其中:
- KKK:簇的数量
- CiC_iCi:第 iii 个簇
- xxx:属于簇 CiC_iCi 的样本点
- μi\mu_iμi:簇 CiC_iCi 的质心(簇内所有样本的均值)
直观理解
| 目标 | 含义 |
|---|---|
| 组内距离最小化 | 同一个簇内的样本尽可能紧密地聚集在质心周围 |
| 组间距离最大化 | 不同簇的质心之间自然会相互远离(因为 SSE 不直接优化组间距离,但通过最小化组内距离间接实现) |
算法本质
K-Means 是在尝试找到使 SSE 最小的 K 个质心和对应的簇划分,这是一个 NP 难问题,算法通过迭代逼近局部最优解。
4.4 具体实现
4.4.1 数据标准化
在进行RFM模型用户聚类时,数据标准化是一个关键步骤。标准化的目的是将不同量纲的特征值调整到同一数量级,以避免某些特征对聚类结果产生过大的影响。
# 获取Recency,Frequency,Monetary的数据
X = RFM[["Recency", "Frequency", "Monetary"]]
from sklearn.preprocessing import StandardScaler
# 创建标准化器实例
scaler = StandardScaler()
# 对特征数据 X 进行标准化处理(均值为0,标准差为1)
X_scaled_array = scaler.fit_transform(X)
# 将标准化后的数组转换为 DataFrame,保留原始列名和索引
X_scaled = pd.DataFrame(
X_scaled_array,
columns=X.columns,
index=X.index
)
# 显示标准化后的前几行数据
X_scaled.head()
| Recency | Frequency | Monetary | |
|---|---|---|---|
| CustomerID | |||
| 12346 | -2.276416 | -0.542685 | -0.231399 |
| 12347 | 0.901762 | 0.566089 | 0.293102 |
| 12348 | 0.183475 | 0.090900 | -0.012686 |
| 12349 | 0.744329 | -0.384288 | -0.017516 |
| 12350 | -2.128822 | -0.384288 | -0.190705 |
4.4.2 肘部法则确定簇数量
在本例中,我们采用肘部法则(Elbow Method) 来确定 K‑Means 聚类的簇数 K。其基本原理是:随着 K 值增加,样本点与其所属簇中心的距离平方和(SSE,即误差平方和)会逐渐下降。当 K 值小于真实类别数时,增加 K 会显著降低 SSE;而当 K 达到最佳值后,SSE 的下降幅度会变得平缓,形成类似“肘部”的拐点,故称肘部法则。
除肘部法则外,常用的确定 K 值的方法还有:
- 轮廓系数法(Silhouette Coefficient):计算每个样本与自身簇内点的平均距离(内聚度)和与最近其他簇点的平均距离(分离度),轮廓系数取值在 [-1,1] 之间,越大表示聚类效果越好。选择使轮廓系数最大化的 K。
- 间隔统计量(Gap Statistic):比较实际数据的对数惯性与其在均匀分布参考数据下的对数惯性期望,取使 Gap 值最大或开始稳定的 K。
- Calinski‑Harabasz 指数:基于簇间离散度与簇内离散度的比值,比值越大说明聚类越清晰。
- 业务经验或实际需求:当聚类有明确的应用目标(如客户分群为高、中、低三类)时,可直接根据业务粒度决定 K 值。
实际应用中常将肘部法则与轮廓系数等方法结合使用,以交叉验证最佳簇数。
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
import seaborn as sns
# 利用肘部法确定最优K值
# 定义候选的 K 值范围(从1到9)
k_range = range(1, 10)
# 定义 SSE 列表,用于存放不同 K 值下的簇内误差平方和
sse_list = []
# 遍历不同的 K 值
for k in k_range:
# 创建 KMeans 聚类器
kmeans = KMeans(n_clusters=k, random_state=42) # 添加随机种子确保结果可复现
# 对标准化后的数据进行拟合
kmeans.fit(X_scaled) # 注意:应使用 X_scaled(标准化后的数据)
# 记录 inertia_(SSE 值)
sse_list.append(kmeans.inertia_)
# 绘制肘部法则图
plt.figure(figsize=(8, 5)) # 设置图形大小
plt.xticks(k_range) # 设置 x 轴刻度为所有 K 值
sns.lineplot(x=k_range, y=sse_list, marker="o") # 绘制折线图,添加圆形标记点
plt.xlabel("K 值(聚类数)") # 添加 x 轴标签
plt.ylabel("SSE(簇内误差平方和)") # 添加 y 轴标签
plt.title("肘部法则确定最优 K 值") # 添加图表标题
plt.grid(True, alpha=0.3) # 添加网格线(可选)
plt.show() # 显示图形

上图利用肘部法则来选择最优的聚类数 K,图中当 K 从 1 增加到 3 时 SSE 下降明显,之后趋于平缓,因此选择 K=3 作为聚类的簇数。
4.4.3 聚类结果及客户群聚类分析
kmeans = KMeans(n_clusters=3, random_state=31, n_init=10)
kmeans.fit(X_scaled)
RFM['Cluster'] = kmeans.labels_
cluster_means = RFM.groupby('Cluster')[['Recency', 'Frequency', 'Monetary']].mean()
cluster_means
| Recency | Frequency | Monetary | |
|---|---|---|---|
| Cluster | |||
| 0 | 332.083025 | 3.828704 | 1863.497587 |
| 1 | 124.353998 | 1.250674 | 470.319776 |
| 2 | 367.263158 | 62.210526 | 92215.624211 |
# 各簇样本量
RFM['Cluster'].value_counts().sort_index()
Cluster
0 3240
1 1113
2 19
Name: count, dtype: int64
# 可视化展示聚类结果
fig, axes = plt.subplots(1, 3, figsize=(15, 4))
for i, metric in enumerate(['Recency', 'Frequency', 'Monetary']):
sns.boxplot(x='Cluster', y=metric, data=RFM, ax=axes[i])
axes[i].set_title(metric)
plt.tight_layout()
plt.show()

集群特征解读:
| 簇编号 | 客户类型 | 特征描述 | 占比 | 运营建议 |
|---|---|---|---|---|
| 簇2 | 重要价值客户 | 高消费、高频次、近期活跃 | 较少 | 这类客户属于高价值核心客户,需要重点关注,给予特别 VIP 服务。 |
| 簇0 | 潜在价值客户 | 近期活跃但消费频次和金额偏低 | 中等 | 这类客户属于最近消费的用户(很可能是新客户),其中可能包含很多潜在客户。需要频繁发送活动消息,增加其消费频率与消费金额。 |
| 簇1 | 一般挽留客户 | 长期未消费、消费能力弱 | 最多 | 该类客户可以不用耗费过多投入,在促销活动时发送通知,给予一定的优惠券进行激活。 |
# 绘制客户群体散点图
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
# 创建图形
fig = plt.figure(figsize=(14, 10))
# 创建3D坐标轴
ax = fig.add_subplot(111, projection='3d')
# 定义颜色和标记
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1'] # 更美观的颜色
markers = ['o', '^', 's'] # 不同形状的标记
# 获取簇的数量
n_clusters = len(np.unique(kmeans.labels_))
# 遍历每个簇
for i in range(n_clusters):
# 获取第i个客户群的数据
d = X[kmeans.labels_ == i]
# 绘制3D散点图
ax.scatter(d["Recency"], d["Frequency"], d["Monetary"],
c=colors[i % len(colors)],
marker=markers[i % len(markers)],
label=f'客户群 {i} (n={len(d)})',
s=30, alpha=0.7, edgecolors='black', linewidth=0.5)
# 可选:绘制簇中心点
center = kmeans.cluster_centers_[i]
ax.scatter(center[0], center[1], center[2],
c=colors[i], marker='*', s=200,
edgecolors='black', linewidth=1.5,
label=f'中心 {i}')
# 设置坐标轴标签
ax.set_xlabel('Recency (最近消费天数)', fontsize=12, labelpad=10)
ax.set_ylabel('Frequency (消费频率)', fontsize=12, labelpad=10)
ax.set_zlabel('Monetary (消费金额)', fontsize=12, labelpad=10)
# 设置标题
ax.set_title('客户群3D空间分布可视化', fontsize=14, pad=20)
# 显示图例
ax.legend(loc='upper right', fontsize=10)
# 设置视角(可调整观察角度)
ax.view_init(elev=25, azim=-60) # elevation仰角, azimuth方位角
# 添加网格
ax.grid(True, alpha=0.3)
# 显示图形
plt.tight_layout()
plt.show()

5. 扩展分析:客户地理与退货行为
5.1 国家维度分析
在前面的分析中,我们未使用客户的 Country 字段。该字段记录了客户所在国家/地区,可以帮助我们了解不同市场的消费特征,为国际化运营和区域营销提供依据。
5.1.1 各国客户数量与消费概况
# 5.1.1 各国客户数量与消费概况
# 统计各国客户数(有消费记录的去重客户)
country_customers = data.groupby('Country')['CustomerID'].nunique().sort_values(ascending=False)
print("客户数 Top 10 国家:")
print(country_customers.head(10))
# 计算各国的 RFM 均值
# 先构建客户级别的 RFM(已存在 RFM DataFrame),需要将 Country 映射到每个客户
customer_country = data.groupby('CustomerID')['Country'].first() # 每个客户取第一个出现的国家
RFM_with_country = RFM.copy()
RFM_with_country['Country'] = customer_country
# 按国家聚合 RFM 均值
country_rfm = RFM_with_country.groupby('Country')[['Recency', 'Frequency', 'Monetary']].mean().sort_values('Monetary', ascending=False)
print("\n各国平均消费金额 Top 10:")
print(country_rfm.head(10))
客户数 Top 10 国家:
Country
United Kingdom 3950
Germany 95
France 87
Spain 31
Belgium 25
Switzerland 21
Portugal 19
Italy 15
Finland 12
Austria 11
Name: CustomerID, dtype: int64
各国平均消费金额 Top 10:
Recency Frequency Monetary
Country
EIRE 316.000000 67.000000 83428.406667
Netherlands 271.444444 9.888889 31629.060000
Australia 273.444444 5.333333 15385.267778
Singapore 322.000000 4.000000 9120.390000
Sweden 302.375000 3.250000 4574.488750
Japan 244.875000 1.250000 4417.577500
Iceland 371.000000 7.000000 4310.000000
Norway 308.000000 3.200000 3516.346000
Switzerland 261.900000 1.600000 2775.919500
Germany 292.852632 3.294737 2333.744316
根据表格结果,我们可以观察到一个很明显的现象:英国客户数量断层第一,但在平均消费金额前10中完全看不到,这说明英国市场以“量大价低”为主(或许是小额高频的零售订单),而某些客户数很少的国家(如爱尔兰、荷兰)却凭借单笔大额订单拉高了平均消费金额。这种“数量 vs 质量”的差异,单看两个独立排序表格并不直观。
为了更全面地评估各国市场的综合价值,本文接下来采用气泡图与双轴柱线图,同时展示“客户数量、平均消费金额、总消费金额”三个维度。其中:
- 气泡图以客户数量为 X 轴、平均金额为 Y 轴、气泡大小映射总金额,并引入全球平均值作为象限分割线,将国家划分为明星市场、潜力市场、现金牛市场、问题市场四类。
- 双轴柱线图则聚焦总金额排名靠前的国家,并用折线叠加客户数量,便于对比总量与体量之间的关系。
通过这种联合可视化,我们可以更直观地识别不同国家所处的市场阶段,并为后续的差异化运营策略提供数据依据。
# 准备数据
country_summary = RFM_with_country.groupby('Country').agg(
CustomerCount=('Monetary', 'count'),
TotalMonetary=('Monetary', 'sum'),
AvgMonetary=('Monetary', 'mean')
).sort_values('TotalMonetary', ascending=False)
# 剔除客户数过少的国家(减少噪声)
country_summary = country_summary[country_summary['CustomerCount'] >= 5]
# 计算全球平均值(作为象限分割线)
global_avg_customers = country_summary['CustomerCount'].mean()
global_avg_monetary = country_summary['AvgMonetary'].mean()
# 绘制气泡图
plt.figure(figsize=(12, 8))
scatter = plt.scatter(
country_summary['CustomerCount'],
country_summary['AvgMonetary'],
s=country_summary['TotalMonetary'] / 500, # 气泡大小映射总金额
c=country_summary['TotalMonetary'], # 颜色也映射总金额
cmap='viridis',
alpha=0.7,
edgecolors='black',
linewidth=0.5
)
# 添加全球平均值参考线
plt.axhline(global_avg_monetary, color='red', linestyle='--', alpha=0.6, label=f'全球平均金额: {global_avg_monetary:.0f}')
plt.axvline(global_avg_customers, color='red', linestyle='--', alpha=0.6, label=f'全球平均客户数: {global_avg_customers:.0f}')
# 标注国家(客户数前5 或 总金额前5,避免标签重叠)
important_countries = list(country_summary.nlargest(5, 'CustomerCount').index) + \
list(country_summary.nlargest(5, 'TotalMonetary').index)
important_countries = list(set(important_countries)) # 去重
for country in important_countries:
row = country_summary.loc[country]
plt.annotate(country,
(row['CustomerCount'], row['AvgMonetary']),
xytext=(8, 8), textcoords='offset points',
fontsize=9, fontweight='bold',
bbox=dict(boxstyle='round,pad=0.3', facecolor='white', alpha=0.7))
plt.xlabel('客户数量 (Customer Count)', fontsize=12)
plt.ylabel('平均消费金额 (Avg Monetary)', fontsize=12)
plt.title('国家市场价值气泡图\n(气泡大小 = 总消费金额 | 颜色深浅 = 总消费金额 | 虚线 = 全球平均值)', fontsize=14)
plt.grid(True, alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()
# 输出四象限分类及策略建议
print("\n国家市场四象限分类如下:")
# 定义象限函数
def get_quadrant(row):
if row['CustomerCount'] >= global_avg_customers and row['AvgMonetary'] >= global_avg_monetary:
return '明星市场 (高客户量 + 高客单价)'
elif row['CustomerCount'] >= global_avg_customers and row['AvgMonetary'] < global_avg_monetary:
return '现金牛市场 (高客户量 + 低客单价)'
elif row['CustomerCount'] < global_avg_customers and row['AvgMonetary'] >= global_avg_monetary:
return '潜力市场 (低客户量 + 高客单价)'
else:
return '问题市场 (低客户量 + 低客单价)'
country_summary['Quadrant'] = country_summary.apply(get_quadrant, axis=1)
# 按象限展示
for quad in ['明星市场 (高客户量 + 高客单价)', '潜力市场 (低客户量 + 高客单价)',
'现金牛市场 (高客户量 + 低客单价)', '问题市场 (低客户量 + 低客单价)']:
countries_in_quad = country_summary[country_summary['Quadrant'] == quad].index.tolist()
if countries_in_quad:
print(f"\n{quad}:")
print(", ".join(countries_in_quad))
else:
print(f"\n{quad}: 无")

国家市场四象限分类如下:
明星市场 (高客户量 + 高客单价): 无
潜力市场 (低客户量 + 高客单价):
Netherlands, Australia, Sweden
现金牛市场 (高客户量 + 低客单价):
United Kingdom
问题市场 (低客户量 + 低客单价):
Germany, France, Switzerland, Spain, Belgium, Japan, Norway, Portugal, Finland, Channel Islands, Denmark, Italy, Cyprus, Austria, Poland
上面给出了四象限法的分析结果。四象限法是一种战略分析工具,通过选取两个关键指标(如本例中的“客户数量”和“平均消费金额”)作为横纵坐标,并以它们的平均值(或中位数)为分割线,将分析对象划分到四个象限中,每个象限代表不同的发展状态和应对策略:高‑高区域为“明星市场”(优先投入),高‑低区域为“现金牛市场”(维持并提升效率),低‑高区域为“潜力市场”(重点拓展),低‑低区域为“问题市场”(谨慎投入或优化)。该方法能将复杂的数据关系可视化,帮助决策者快速识别资源分配的优先级。
根据四象限分类,目前公司尚未拥有“客户数量多且客单价高”的明星市场;荷兰、澳大利亚、瑞典属于客户数少但客单价极高的潜力市场,应重点拓客并维护高价值客户;英国是唯一的现金牛市场,客户基数断层第一但客单价偏低,需通过提升客单价和复购率来巩固其总销售额主力地位;而德国、法国等十余个国家属于客户量少且客单价低的问题市场,现阶段仅需低成本测试或暂缓投入,优先资源应集中用于潜力市场的规模扩张和英国市场的客单价提升。
fig, ax1 = plt.subplots(figsize=(12,6))
top_countries = country_summary.head(10)
ax1.bar(top_countries.index, top_countries['TotalMonetary'], color='b', alpha=0.7, label='总金额')
ax1.set_ylabel('总消费金额', color='b')
ax1.tick_params(axis='x', rotation=45)
ax2 = ax1.twinx()
ax2.plot(top_countries.index, top_countries['CustomerCount'], color='r', marker='o', label='客户数')
ax2.set_ylabel('客户数量', color='r')
plt.title('Top 10 国家:总金额 vs 客户数量')
plt.show()

5.1.2 各国聚类分布
在完成各国家市场的消费规模与客户数量分析之后,我们进一步关注 不同国家客户的聚类构成。前文的 K‑means 聚类将全部客户划分为三个群体:簇0(潜在价值客户)、簇1(一般挽留客户)和簇2(重要价值客户)。不同国家的客户在这三个聚类中的分布可能存在显著差异:例如,有些国家可能拥有更高比例的重要价值客户(簇2),而另一些国家则可能以潜在价值或挽留客户为主。通过对比这些分布,我们可以:
-
识别出 高价值客户集中的国家,为优先投入营销资源提供依据;
-
发现那些 高占比的低价值或沉睡客户国家,从而制定针对性的激活或优化策略;
-
结合各国市场象限分类(明星/潜力/现金牛/问题市场),更精准地调整区域运营方案。
因此,本节将对主要国家(客户数较多的前6个国家)的聚类构成进行交叉统计与可视化,同时揭示不同市场的客户质量差异。
# 5.1.2 各国聚类分布
# 添加聚类标签到带国家的 RFM
RFM_with_country['Cluster'] = RFM['Cluster']
# 选取客户数较多的前6个国家(避免小样本偏差)
top6_countries = country_customers.head(6).index
rfm_top6 = RFM_with_country[RFM_with_country['Country'].isin(top6_countries)]
# 交叉表:国家 vs 聚类
cross_tab = pd.crosstab(rfm_top6['Country'], rfm_top6['Cluster'], normalize='index') * 100
print("各国聚类构成(百分比):")
print(cross_tab.round(1))
# 绘制堆叠条形图
cross_tab.plot(kind='bar', stacked=True, figsize=(12, 6), colormap='viridis')
plt.title('主要国家客户聚类分布')
plt.ylabel('客户占比 (%)')
plt.xlabel('国家')
plt.legend(title='聚类', labels=['簇0(潜力)', '簇1(一般挽留)', '簇2(重要价值)'])
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()
各国聚类构成(百分比):
Cluster 0 1 2
Country
Belgium 75.0 25.0 0.0
France 78.2 21.8 0.0
Germany 80.0 20.0 0.0
Spain 79.3 20.7 0.0
Switzerland 80.0 20.0 0.0
United Kingdom 74.1 25.5 0.4

在完成对六个主要国家的客户聚类构成分析之后,我们进一步将目光转向市场四象限与客户聚类分布的联合分析。上一节我们根据“客户数量”和“平均消费金额”两个维度,将各国市场划分为明星、潜力、现金牛、问题四类。然而,仅从宏观指标判断市场价值仍可能忽视不同市场中客户内部结构的差异——例如,同为“问题市场”的两个国家,一个可能以“潜在价值客户(簇0)”为主,另一个可能以“沉睡客户(簇1)”为主,两者所需的运营策略截然不同。
因此,本节将从“宏观价值 + 微观构成”进行双层分析:
- 按照之前划分的四象限对各国进行分组;
- 分别统计每个象限内所有国家客户的聚类构成(簇0/簇1/簇2占比);
- 通过对比不同象限的客户结构,揭示:
- 哪些象限的市场更依赖高质量客户(簇2);
- 哪些象限的市场主要被低活跃客户占据;
- 为后续制定差异化的区域运营策略提供更深层的客户画像依据。
帮助管理者更精准地针对不同市场应采取相应的客户价值提升手段。
# 四象限与聚类分布联合分析
# 为 country_summary 添加国家对应的簇占比(用于后续查看)
country_cluster_share = RFM_with_country.groupby('Country')['Cluster'].value_counts(normalize=True).unstack(fill_value=0)
country_cluster_share = country_cluster_share.rename(columns={0: 'Cluster0_%', 1: 'Cluster1_%', 2: 'Cluster2_%'}) * 100
country_summary = country_summary.join(country_cluster_share)
# 按四象限分组统计客户聚类构成(所有国家客户汇总)
quadrant_cluster_stats = []
for quad in country_summary['Quadrant'].unique():
# 获取该象限下的所有国家
countries = country_summary[country_summary['Quadrant'] == quad].index
# 筛选这些国家的客户数据
sub_data = RFM_with_country[RFM_with_country['Country'].isin(countries)]
# 计算聚类占比(基于客户行数,每个客户一行)
total_customers = len(sub_data)
cluster_counts = sub_data['Cluster'].value_counts()
pct_cluster0 = cluster_counts.get(0, 0) / total_customers * 100
pct_cluster1 = cluster_counts.get(1, 0) / total_customers * 100
pct_cluster2 = cluster_counts.get(2, 0) / total_customers * 100
quadrant_cluster_stats.append({
'象限': quad,
'簇0占比(%)': round(pct_cluster0, 1),
'簇1占比(%)': round(pct_cluster1, 1),
'簇2占比(%)': round(pct_cluster2, 1),
'国家数量': len(countries)
})
quadrant_df = pd.DataFrame(quadrant_cluster_stats)
print("各象限客户聚类构成(按客户汇总):")
print(quadrant_df.to_string(index=False))
# 可选:绘制堆叠条形图
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(10, 6))
quadrants = quadrant_df['象限']
cluster0 = quadrant_df['簇0占比(%)']
cluster1 = quadrant_df['簇1占比(%)']
cluster2 = quadrant_df['簇2占比(%)']
ax.bar(quadrants, cluster0, label='簇0 (潜在价值)', color='#FF6B6B')
ax.bar(quadrants, cluster1, bottom=cluster0, label='簇1 (一般挽留)', color='#4ECDC4')
ax.bar(quadrants, cluster2, bottom=cluster0 + cluster1, label='簇2 (重要价值)', color='#45B7D1')
ax.set_ylabel('客户占比 (%)')
ax.set_title('不同市场象限的客户聚类构成对比')
ax.legend()
plt.xticks(rotation=15)
plt.tight_layout()
plt.show()
各象限客户聚类构成(按客户汇总):
象限 簇0占比(%) 簇1占比(%) 簇2占比(%) 国家数量
现金牛市场 (高客户量 + 低客单价) 74.1 25.5 0.4 1
潜力市场 (低客户量 + 高客单价) 69.2 23.1 7.7 3
问题市场 (低客户量 + 低客单价) 77.4 22.6 0.0 15

| 市场象限 | 客户结构(簇0/簇1/簇2占比) | 关键特征 | 战略方向 |
|---|---|---|---|
| 现金牛市场(英国) | 74.1% / 25.5% / 0.4% | 客户量大,但高价值客户极少,以低活跃、低消费人群为主 | 提升客单价与复购率,激活存量客户 |
| 潜力市场(荷兰、澳大利亚、瑞典) | 69.2% / 23.1% / 7.7% | 客户量小,但高价值客户占比显著,质量最优 | 优先扩大客户规模,维护核心价值客户 |
| 问题市场(德、法、西等15国) | 77.4% / 22.6% / 0.0% | 量小且完全没有高价值客户,质量最差 | 低成本测试或逐步退出,资源向潜力市场倾斜 |
从客户聚类构成的象限对比可以发现:
-
潜力市场(荷兰、澳大利亚、瑞典)虽然客户总数不多,但高价值客户(簇2)占比达到 7.7%,是现金牛市场的 19 倍,说明这些市场拥有极高的客户价值密度。它们是未来增长的核心引擎,应优先投入资源扩大客户规模,同时维护好现有高价值客户。
-
现金牛市场(英国)客户数量断层领先,但73%以上都是低活跃、低消费的簇0客户,高价值客户仅占 0.4%。这说明英国市场“量大质低”,总销售额依赖量大而非客户深度。战略上应着力提升客单价和复购率,激活存量客户。
-
问题市场(德国、法国等15国) 既无规模优势,也无客户质量优势,完全没有簇2客户,且超过 22% 是长期不活跃的簇1客户。这类市场应谨慎投入,可采取低成本测试或逐步退出的策略,释放资源给潜力市场。
核心建议:将营销预算从问题市场转移至潜力市场,同时针对现金牛市场设计客户价值提升方案。
5.2 退货行为分析
原始订单中,InvoiceNo 以 “C” 开头的记录表示订单被取消(即退货)。在这一节,我们进一步关注退货行为在不同客户群体中的分布差异。退货不仅直接侵蚀企业利润,也在一定程度上反映了客户对商品质量、物流服务或购物体验的满意度。不同价值层级的客户,其退货频率和退货金额可能呈现显著差异:例如,高价值客户可能对商品有更高期望,一旦不满意更容易产生退货;而低频客户即使发生退货,也可能是偶发行为,未必代表普遍不满。
通过对退货订单进行识别和统计,并将退货指标(退货次数、退货金额)与客户聚类结果(簇0、簇1、簇2)进行关联分析,我们可以:
- 量化各客户群体的退货风险,识别哪些群体需要优先改善售后体验;
- 判断高价值客户是否伴随异常高的退货率,若存在则需重点优化其商品推荐或服务流程,防止核心客户流失。
因此,本节首先统计整体退货情况,然后按客户聚类分组计算平均退货次数与退货金额,并可视化各聚类的退货金额占比,从而揭示退货行为与客户价值之间的内在联系。
5.2.1 退货订单识别与统计
# 5.2.1 退货订单识别与统计
# 标记是否为退货订单
data['IsReturn'] = data['InvoiceNo'].astype(str).str.startswith('C')
# 统计退货订单数量与金额
return_orders = data[data['IsReturn']]
print(f"总订单行数(含退货): {len(data)}")
print(f"退货订单行数: {len(return_orders)}")
print(f"退货订单行占比: {len(return_orders)/len(data)*100:.2f}%")
# 计算退货金额(退货记录的 Monetary 已经是负数 Quantity * UnitPrice,但金额应取绝对值)
return_amount = abs(return_orders['Monetary'].sum())
total_sales = data[~data['IsReturn']]['Monetary'].sum()
print(f"总销售金额(不含退货): {total_sales:,.2f}")
print(f"退货总金额: {return_amount:,.2f}")
print(f"退货率(金额): {return_amount/total_sales*100:.2f}%")
总订单行数(含退货): 406829
退货订单行数: 8905
退货订单行占比: 2.19%
总销售金额(不含退货): 8,911,407.90
退货总金额: 611,342.09
退货率(金额): 6.86%
5.2.2 退货与客户分层的关系
在完成整体退货情况的统计之后,我们进一步将退货行为与客户聚类结果进行关联分析。这一部分主要探究的是:不同价值层级的客户,其退货行为是否存在显著差异?例如,高价值客户是否因为期望更高而更容易退货?
为了回答这些问题,我们首先按客户维度聚合了退货次数与退货金额,然后分别计算各聚类内客户的平均退货次数和平均退货金额,并进一步测算各聚类退货金额占总消费金额的比例。通过这种对比,可以识别出哪个客户群的退货风险最高,从而为优化售后服务、调整商品质量管控或制定差异化的退货策略提供直接的数据依据。这一分析也能帮助判断当前的高价值客户是否“虚高”(即虽然消费多但退货也多),避免被表面金额误导。因此,下文将对退货指标按聚类进行分组统计与可视化展示。
# 按客户聚合退货指标
return_by_customer = return_orders.groupby('CustomerID').agg(
ReturnCount=('InvoiceNo', lambda x: x.nunique()), # 退货订单数(去重)
ReturnAmount=('Monetary', lambda x: abs(x.sum())) # 退货总金额
)
# 合并到 RFM DataFrame
RFM_with_returns = RFM.copy()
RFM_with_returns = RFM_with_returns.join(return_by_customer, how='left').fillna({'ReturnCount': 0, 'ReturnAmount': 0})
# 按聚类分组统计退货指标
return_by_cluster = RFM_with_returns.groupby('Cluster')[['ReturnCount', 'ReturnAmount']].mean()
print("各客户群平均退货情况:")
print(return_by_cluster.round(2))
# 可视化:各聚类退货率(退货金额占总消费金额比例)
cluster_total_monetary = RFM_with_returns.groupby('Cluster')['Monetary'].sum()
cluster_return_amount = RFM_with_returns.groupby('Cluster')['ReturnAmount'].sum()
cluster_return_rate = (cluster_return_amount / cluster_total_monetary) * 100
plt.figure(figsize=(8, 5))
cluster_return_rate.plot(kind='bar', color=['#FF6B6B', '#4ECDC4', '#45B7D1'])
plt.title('各客户群退货总金额占比')
plt.ylabel('退货总金额占比 (%)')
plt.xlabel('聚类')
plt.xticks(rotation=0)
plt.grid(axis='y', alpha=0.3)
for i, v in enumerate(cluster_return_rate):
plt.text(i, v + 0.5, f"{v:.1f}%", ha='center')
plt.tight_layout()
plt.show()
各客户群平均退货情况:
ReturnCount ReturnAmount
Cluster
0 0.93 111.06
1 0.34 154.59
2 13.11 4180.93

从退货数据来看,不同客户群的退货行为差异显著。其中 簇2(重要价值客户)平均退货次数高达13.11次,平均退货金额也远超其他群体,说明高价值客户对商品质量或服务期望更高,需要重点关注售后体验。但其退货总金额占比仅为4.5%,说明虽然该群体个体退货频繁、金额高,但因客户数量极少,对整体退货金额的影响有限;簇1(一般挽留客户)退货金额占比最高(32.9%),且平均单次退货金额较大,表明这部分客户的退货行为对整体利润侵蚀最为严重;簇0(潜在价值客户)退货指标相对温和。总体而言,高价值客户个体退货风险突出,而一般挽留客户的群体退货贡献最大。
针对三个客户群的退货特征,可采取如下差异化管理策略:
-
簇2(重要价值客户):个体退货频率高、金额大,反映其对商品品质和服务体验有更高标准。策略应聚焦于“保留存、提体验”。具体措施包括:开通专属客服通道,优先处理其退换货需求;分析其退货原因(如尺码、物流、描述不符),联合供应链优化商品品质或页面信息;定期回访或赠送优惠券以补偿体验不佳,维持其忠诚度。
-
簇1(一般挽留客户):退货金额占比最高(32.9%),是退货损失的主要来源。策略重点为“控成本、降损失”。需深入分析其退货商品共性(如折扣品、特定品类),改进商品描述或加强质量检查;适当设置退货门槛(如非质量原因收取运费),减少无理由退货;同时可推送低门槛优惠券尝试激活购买,避免其彻底流失。
-
簇0(潜在价值客户):退货指标相对温和,该群体尚处于消费习惯培养期。策略应注重“防微杜渐、促转化”。通过优化商品详情页、强化用户评价展示,减少因信息不透明导致的误购退货;利用其近期活跃优势,推送个性化推荐,提升复购率和客单价,引导其向更高价值群体迁移。
6. 综合结论
6.1 项目成果总结
- 成功构建了 RFM 模型,从 最近消费时间(Recency)、消费频率(Frequency) 和 消费金额(Monetary) 三个维度刻画客户行为,并基于均值阈值完成了初步的客户分层(八类)。
- 引入 K‑means 聚类算法,结合肘部法则确定最优簇数 K=3,将客户划分为三个具有显著差异的群体:重要价值客户(簇2)、潜在价值客户(簇0) 和 一般挽留客户(簇1),增强了分层的客观性和数据驱动性。
- 进一步拓展分析维度,从 地理分布(国家) 和 退货行为 两个角度深入剖析客户价值,揭示了不同市场区域以及不同价值客户群的退货风险差异。
- 利用气泡图、堆叠条形图、双轴柱线图等多种可视化手段,直观呈现了客户群特征、国家市场价值与客户质量分布,为运营决策提供了清晰的数据依据。
6.2 基于用户分层的运营建议
根据聚类划分的三个客户群体,制定差异化的运营策略:
| 客户群 | 特征 | 占比 | 运营策略 |
|---|---|---|---|
| 簇2(重要价值客户) | 高消费、高频次、近期活跃 | 约 0.4% | 专属服务 + 深度激活:提供 VIP 客服通道、优先发货、专属折扣;定期推送高价值新品或限量商品;开展会员日、积分加速等活动,巩固忠诚度。 |
| 簇0(潜在价值客户) | 近期活跃、频次与金额偏低 | 约 74% | 培养习惯 + 提升客单价:通过新客礼包、满减券、第二件半价等激励复购;依据浏览/购买历史做个性化推荐,引导购买相关商品;推送会员升级进度,增强留存。 |
| 簇1(一般挽留客户) | 长期未消费、消费能力弱 | 约 25% | 低成本激活 + 适度放弃:在大型促销活动(如双十一、黑五)时发送优惠券或赠品通知;分析其历史偏好推送“回归好礼”;对长期无响应的客户减少触达,节约资源。 |
6.3 基于国家市场分析的策略建议
结合客户数量、平均消费金额及聚类构成,将主要国家市场划分为四类,并制定区域化策略:
| 市场象限 | 代表国家 | 客户数量 | 平均消费金额 | 高价值客户占比 | 战略方向 |
|---|---|---|---|---|---|
| 潜力市场 | 荷兰、澳大利亚、瑞典 | 低 | 高 | 7.7% | 优先拓客 + 维护核心:加大广告投放、本地化营销;为高价值客户提供专属体验;复制成功模式至邻近市场。 |
| 现金牛市场 | 英国 | 高 | 低 | 0.4% | 提升客单价 + 存量激活:推出捆绑销售、会员等级体系;针对簇0客户开展复购激励;优化商品组合。 |
| 问题市场 | 德国、法国、西班牙等15国 | 低 | 低 | 0.0% | 低成本测试或逐步退出:仅用自动化邮件/推送进行低预算触达;若长期无改善,收缩资源转向潜力市场。 |
6.4 基于退货行为的策略建议
不同客户群的退货特征差异显著,需采取针对性措施:
| 客户群 | 退货特点 | 策略建议 |
|---|---|---|
| 簇2(重要价值客户) | 个体退货频率高、金额大(平均13次 / 4181元) | 售后体验升级:开通专属退款通道,快速处理;分析退货原因(尺码、物流、描述),联动品控与详情页优化;回访并给予补偿优惠券,避免流失。 |
| 簇1(一般挽留客户) | 退货金额占比最高(32.9%) | 成本控制 + 损失降低:提高非质量退货门槛(如收取运费);重点监控退货集中的品类,改进质量或描述;推送低门槛券尝试激活。 |
| 簇0(潜在价值客户) | 退货指标温和 | 防微杜渐 + 转化引导:加强评价展示、提供详细尺码表减少误购;利用活跃度推送个性化推荐,提升客单价。 |
6.5 局限性及改进方向
- 数据集未包含退货原因、商品类别等信息,无法进行更细粒度的行为分析。
- 聚类数K的选择带有一定主观性,可尝试轮廓系数或Gap统计量辅助判断。
- 未来可结合时间序列分析客户生命周期价值(LTV),或使用DBSCAN处理噪声点。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)