27 跨国电商零售数据探索
27.1 引言RFM客户分层模型
RFM模型是客户价值分析的经典方法:
- Recency(最近消费): 最近一次消费时间
- Frequency(消费频率): 消费次数
- Monetary(消费金额): 消费总金额
27.2 本章学习目标
本章用RFM客户分层模型对跨国电商的客户做价值分层。通过本章学习,你将掌握:
- RFM三个维度的含义与”高/低”三字组合编码的构造
- 用
apply+ 自定义函数把三字编码映射为客户等级标签 value_counts统计客户结构、normalize=True计算占比- 用带数值标注的柱状图与含百分比的饼图呈现客户结构
- 从分层结果推导差异化的客户运营策略
先修内容:第 章节 10 章(Series与 apply 基础)与第 章节 14 章(Excel文件读取);柱状图与饼图回看第 章节 20 章。
27.3 数据准备
任务要求:读取平台内置的 skrfm.xlsx,清洗 RFM 标签中的空格;定义 trans 函数,把八种”高/低”三字组合映射为对应的客户等级名称,并生成”用户等级”列;统计各等级人数;绘制带数值标注的客户等级柱状图与含百分比标注的饼图。
# 注:skrfm.xlsx 数据文件本地没有,但平台已经内置
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
import pandas as pd # 导入Pandas数据分析库
import numpy as np # 导入NumPy数值计算库
import matplotlib.pyplot as plt # 导入Matplotlib绘图库
import warnings # 导入warnings模块用于控制警告输出
warnings.filterwarnings('ignore') # 忽略报错(警告)
#加载数据
rfm = pd.read_excel('skrfm.xlsx')
rfm['RFM'] = rfm['RFM'].str.strip() # 去除空格
def trans(x): # 定义函数trans
if x == '高高高': # 条件判断:x == '高高高'
return '重要客户' # 返回计算结果
elif x == '高低高': # 否则判断:x == '高低高'
return '低购买频率客户' # 返回计算结果
elif x == '低高高': # 否则判断:x == '低高高'
return '长期未消费客户' # 返回计算结果
elif x == '低低高': # 否则判断:x == '低低高'
return '长期未消费低购买频率客户' # 返回计算结果
elif x == '高高低': # 否则判断:x == '高高低'
return '低消费客户' # 返回计算结果
elif x == '高低低': # 否则判断:x == '高低低'
return '低购买频率低消费客户' # 返回计算结果
elif x == '低高低': # 否则判断:x == '低高低'
return '长期未消费低消费客户' # 返回计算结果
else: # 不满足以上条件时
return '其他待发展客户' # 返回计算结果
rfm['用户等级'] = rfm['RFM'].apply(trans) # 对数据应用自定义函数
print(rfm) # 输出变量rfm的值
rfm['用户等级'].value_counts() # 统计用户等级列各取值的频率分布
plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体或其他支持中文的字体
plt.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题
# 创建柱状图
plt.figure(figsize=(12, 6))
# 绘制柱状图
bars = plt.bar(rfm['用户等级'].value_counts().index, rfm['用户等级'].value_counts().values, color='blue',alpha=0.4)
# 添加标题和标签
plt.title('各等级类型用户数', fontsize=16)
plt.xlabel('用户等级类型', fontsize=12) # 设置X轴标签
plt.ylabel('用户数', fontsize=12) # 设置Y轴标签
plt.xticks(rotation=45, ha='right') # 旋转x轴标签以便更好地显示
# 在柱子上方显示数值
for bar in bars:
height = bar.get_height() # 获取当前柱子的高度值(用于数据标注)
plt.text(bar.get_x() + bar.get_width() / 2., height, # 添加文本标注
f'{height:,}', # 格式化标注文本(千分位分隔)
ha='center', va='bottom', fontsize=9) # 设置标注文本的水平和垂直对齐方式
# 调整布局防止标签被截断
plt.tight_layout()
# 显示图形
plt.savefig("7.png")
#绘制饼图
user_levels = rfm['用户等级'].value_counts()
labels = user_levels.index # 用户等级的类别
values = user_levels.values # 每个类别的数量
# 创建饼图
plt.figure(figsize=(10, 6)) # 设置画布大小
# 绘制饼图
plt.pie(values, labels=labels, autopct='%1.1f%%', startangle=90, textprops={'fontsize': 12, 'color': 'white'})
# 添加标题和图例
plt.title('用户等级类型占比', fontsize=16)
plt.legend(labels, title="用户等级", loc="best",bbox_to_anchor=(1, 0.5)) # 图例位置自动调整为最佳位置
# 显示图表
plt.savefig("8.png")预期输出:数据文件平台内置,本地无数据,具体数值以平台运行结果为准。判读要点:
- 文本输出:追加”用户等级”列后的完整数据框,以及各等级用户数的频率分布——先确认八类标签实际出现了几类。
- 图形输出(7.png):各等级用户数柱状图。判读要点:哪些等级构成客群主体、哪些是长尾;柱顶数值标注与高度是否一致。图形输出(8.png):等级占比饼图。判读要点:头部等级合计占比、“重要客户”占比的相对大小。
- 若某等级计数为0,柱状图与饼图会自动缺省该类目——判读时先对照
value_counts输出再读图,避免把”缺省”误读为”不存在该分类规则”。
下方提供一个本地演练版:由于 skrfm.xlsx 仅平台内置,本地以一份 16 行的内联合成迷你表代替(仅含平台代码实际使用的 RFM 编码列,取值为演示流程而设的模拟值,其中两行故意带前导空格),并用与 trans 函数同口径的映射复现”八分层计数”的同型输出:
# 本地演练说明:平台内置的skrfm.xlsx本地没有,以下用同结构的内联合成迷你数据代替
import pandas as pd # 导入Pandas数据分析库
rfm_mini = pd.DataFrame({ # 构造16行迷你表,保留平台数据的RFM三字编码列
'RFM': ['高高高', '高高高', ' 高高高', '高高高', '低低低', '低低低', '低低低', '低高高',
'低高高', ' 高低高', '高低高', '高高低', '高高低', '低低高', '高低低', '低高低'] # 两行故意带前导空格,演示strip的必要性
})
rfm_mini['RFM'] = rfm_mini['RFM'].str.strip() # 与平台任务一致,先去除RFM编码的前后空格
level_map = { # 与平台任务trans函数同口径的编码到客户等级映射(低低低走else分支)
'高高高': '重要客户', '高低高': '低购买频率客户', '低高高': '长期未消费客户', # M高(消费金额高)的三类
'低低高': '长期未消费低购买频率客户', '高高低': '低消费客户', # M高其余一类与M低开始
'高低低': '低购买频率低消费客户', '低高低': '长期未消费低消费客户' # M低的其余两类
}
rfm_mini['用户等级'] = rfm_mini['RFM'].apply(lambda x: level_map.get(x, '其他待发展客户')) # 缺省值对应平台trans的else分支
print('RFM八类编码计数:')
print(rfm_mini['RFM'].value_counts()) # 八分层原始编码计数
print('用户等级计数:')
print(rfm_mini['用户等级'].value_counts()) # 映射后的等级计数,同平台任务的频率分布预期输出(本机实际运行结果):
RFM八类编码计数:
RFM
高高高 4
低低低 3
低高高 2
高低高 2
高高低 2
低低高 1
高低低 1
低高低 1
Name: count, dtype: int64
用户等级计数:
用户等级
重要客户 4
其他待发展客户 3
长期未消费客户 2
低购买频率客户 2
低消费客户 2
长期未消费低购买频率客户 1
低购买频率低消费客户 1
长期未消费低消费客户 1
Name: count, dtype: int64
判读要点与平台任务同型:str.strip() 之后八类编码各有 1—4 个客户,“低低低”经缺省分支归入”其他待发展客户”;编码计数表与等级计数表互为底账,两表总数均为 16,与”读图前先查计数表”的小结要点一致。
27.4 RFM分层映射
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# ==================== 定义客户分类函数 ====================
def classify_customer(rfm_value):
"""根据RFM值分类客户,返回客户等级"""
mapping = {
'高高高': '重要客户', # R、F、M三个维度都高,是最有价值的客户
'高低高': '低购买频率客户', # 最近消费、金额高,但频率低,有提升空间
'低高高': '长期未消费客户', # 频率高、金额高,但很久没消费,需要召回
'低低高': '长期未消费低购买频率客户', # 金额高但频率低且很久没消费
'高高低': '低消费客户', # 最近消费、频率高但金额低,适合培养
'高低低': '低购买频率低消费客户', # 最近消费但频率和金额都低
'低高低': '长期未消费低消费客户' # 很久没消费且金额低,需要激活或放弃
}
return mapping.get(rfm_value, '其他待发展客户') # 如果RFM值不在映射表中,归为"其他待发展客户"
# ==================== 应用分类函数 ====================
rfm['用户等级'] = rfm['RFM'].apply(classify_customer)
# 对每一行RFM值应用classify_customer函数,生成用户等级标签
# apply()是Pandas中高效处理行/列数据的方法
print('用户等级分布:') # 输出标题
print(rfm['用户等级'].value_counts()) # 统计每个等级的用户数量,了解客户结构27.5 可视化分析
27.5.1 柱状图
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# ==================== 设置中文字体支持 ====================
plt.rcParams['font.sans-serif'] = ['Source Han Serif SC', 'SimHei'] # 规范字体在前,SimHei 仅作后备 # 设置中文字体为黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
# ==================== 统计各等级用户数量 ====================
user_counts = rfm['用户等级'].value_counts() # 统计每个用户等级的出现次数
# ==================== 创建柱状图 ====================
plt.figure(figsize=(12, 6)) # 创建12x6英寸的画布
bars = plt.bar(
user_counts.index, # x轴为用户等级
user_counts.values, # y轴为用户数量
color='steelblue', # 设置柱状图颜色为钢蓝色
alpha=0.6 # 设置透明度为0.6,使图表更加柔和
)
# ==================== 添加数值标签 ====================
for bar in bars: # 遍历每个柱子
height = bar.get_height() # 获取柱子的高度(即用户数量)
plt.text(
bar.get_x() + bar.get_width() / 2., # x坐标为柱子中心
height, # y坐标为柱子顶部
f'{int(height):,}', # 显示数值,使用千位分隔符
ha='center', # 水平居中对齐
va='bottom', # 垂直底部对齐
fontsize=10 # 字体大小为10
)
plt.title('各等级类型用户数', fontsize=16) # 设置图表标题
plt.xlabel('用户等级类型', fontsize=12) # 设置x轴标签
plt.ylabel('用户数', fontsize=12) # 设置y轴标签
plt.xticks(rotation=45, ha='right') # x轴标签旋转45度,右对齐,避免重叠
plt.grid(axis='y', alpha=0.3) # 显示y轴网格线,透明度0.3
plt.tight_layout() # 自动调整布局
plt.show() # 显示图表27.5.2 饼图
# 注:该代码块依赖的数据来自上方平台任务代码块,本块承接上方平台任务的代码与数据(变量沿用平台任务)
# ==================== 准备饼图数据 ====================
user_levels = rfm['用户等级'].value_counts() # 统计各等级用户数
labels = user_levels.index # 饼图的标签(用户等级)
values = user_levels.values # 饼图的数值(用户数量)
# ==================== 创建饼图 ====================
plt.figure(figsize=(10, 6)) # 创建10x6英寸的画布
plt.pie(
values, # 饼图的数值
labels=labels, # 饼图的标签
autopct='%1.1f%%', # 显示百分比,保留1位小数
startangle=90, # 从90度(正上方)开始绘制
textprops={'fontsize': 11} # 设置文本字体大小为11
)
plt.title('用户等级类型占比', fontsize=16) # 设置图表标题
plt.legend(
labels, # 图例标签
title='用户等级', # 图例标题
loc='best', # 自动选择最佳位置
bbox_to_anchor=(1, 0.5) # 将图例放置在图表右侧
)
plt.tight_layout() # 自动调整布局
plt.show() # 显示图表27.6 分析结论
客户分层结果:
- 重要客户: RFM都高,需重点维护
- 低频客户: 频率低但金额高,可提升频率
- 沉睡客户: 未消费需重新激活
- 低价值客户: 需培育或放弃
营销策略:
- 重要客户: VIP服务,专属优惠
- 低频客户: 促销活动,提高复购
- 沉睡客户: 召回活动,重新激活
- 低价值客户: 降低服务成本
27.7 本章小结
本章新增的技能要点:
- 分层先于策略:RFM三字编码是规则打分的产物,本章的任务是把编码翻译成运营能直接使用的客户等级语言。
- 多分支映射的两种写法:if-elif链加
apply,或字典加get默认值,平台题面采用前者,后者更紧凑。 value_counts的两种口径:normalize=False得人数、True得占比,两者配合即可同时回答”有多少”与”占多少”。- 分类计数图的三个细节:x轴标签旋转防重叠、柱顶数值标注提升可读性、饼图图例外置防遮挡。
- 读图前先查计数表:饼图与柱状图会自动丢弃零计数类目,以计数表为读图底账。
易错点:
- 不先
str.strip()就做映射:带前导空格的” 高高高”进不了”高高高”分支,会走 else 被归入”其他待发展客户”,高价值客户被错分层 - “低低低”没有专属分支,靠 else 兜底;改用字典写法时漏掉
.get的默认值,它会映射成NaN而非”其他待发展客户” - 按单一维度排召回优先级:召回紧迫性来自”R低×F/M高”的叠加,“高低高”最近仍在消费、只是频次低,把它当抢救对象会错配预算
- 为图省事把”高高高”之外的七类全并入”其他”:最该抢救的高价值流失客户与长尾客户进了同一标签,运营动作退化为一刀切
- 把柱状图、饼图上”没有柱子/扇区”读成”该等级不存在”:零计数类目会被图自动丢弃,读图前先对照
value_counts计数表 - 只按人数占比分配营销预算:沉睡客户的历史金额占比可能远高于人数占比,人数与金额两个口径须并看
27.8 动手与思考
以下练习每题附参考答案(默认折叠);概念题与变式题可对照自查,商业判断题不设唯一答案,判断依据与口径比结论更重要。
概念辨析:“高低高”(最近消费高、频率低、金额高)与”低高高”(最近消费低、频率高、金额高)哪一类更需要立即召回?为什么?
参考答案(点开前请先独立完成)
解题思路:“低高高”(列表 27.1 中映射为“长期未消费客户”)更需要立即召回。理由在于召回的紧迫性来自“价值高”与“正在流失”两个条件的叠加:R低说明已经较长时间没有消费、出现流失迹象,而F高、M高说明这位客户历史上消费频繁、金额可观——高价值客户正在流失,挽回的时间窗口有限,每拖一个月召回成功率与回报都在下降。相比之下,“高低高”(低购买频率客户)最近仍在消费、金额也高,只是频次低,属于“还有提升空间的存量客户”,适合用会员权益、复购提醒去提升频率,是经营机会而不是抢救对象。单看任何一个维度都排不出优先级:只看R会召回一批低价值客户,只看M会忽略已经走远的人。
回扣本章:对应“本章新增的技能要点”第1条(分层先于策略:三字编码必须翻译成“先抢救谁、再培养谁”的运营语言)。
概念辨析:若把”高高高”之外的七类全部合并为”其他待发展客户”,分层的信息量会损失什么?运营动作会因此发生什么变化?
参考答案(点开前请先独立完成)
解题思路:损失的是“风险与机会的形状”。七类客户的处境完全不同:“低高高”是正在流失的高价值客户(要立即召回),“低低高”是接近流失且频次本就低的高金额客户(要重点挽回),“高高低”是常来但花钱少的客户(要培养客单价),“高低低”是最近来过一次的低价值客户(要观察),“低高低”“低低低”则接近放弃区——合并之后,最该抢救的高价值流失客户与最不值得投入的长尾客户进了同一个标签。运营动作随之退化为“一刀切”的大众营销:召回短信发给不需要召回的人、提频券发给本来就要放弃的人,预算无法按风险分级配置;同时合并后的口径也无法评估分层运营的效果,因为没有了可对照的组。保留八类的意义,正是让每一层对应一种动作与预算档位。
回扣本章:对应“本章新增的技能要点”第1条(分层先于策略:合并分层等于合并策略)。
变式任务:给两个等级更换更贴近运营习惯的名称(例如把”低高高”改称”重要挽留客户”)并重绘柱状图——改动涉及哪些行?
参考答案(点开前请先独立完成)
改造思路:若在平台的
trans函数里改,只动两个分支的return字符串(两行),其余分支与绘图代码全部不变;若在自查代码里改,用本章要点提到的字典写法更紧凑——只改字典里两个键对应的值。改名只影响标签文本,不改变任何计数:各类人数、柱高、饼图占比都不变,柱状图的x轴刻度文字换成新名称。# 变式代码:等级改名——用字典写法重建映射,仅改两个值;平台做题时仍按原样输入@lst-ch27-platform-task level_map = { # 与平台trans函数同口径的映射,仅两处改名 '高高高': '重要客户', '高低高': '低购买频率客户', '低高高': '重要挽留客户', # ← 改动点1:原“长期未消费客户” '低低高': '高价值沉睡客户', '高高低': '低消费客户', # ← 改动点2:原“长期未消费低购买频率客户” '高低低': '低购买频率低消费客户', '低高低': '长期未消费低消费客户' # 其余名称不变 } rfm['用户等级'] = rfm['RFM'].apply(lambda x: level_map.get(x, '其他待发展客户')) # 与平台任务同口径的映射应用 user_counts = rfm['用户等级'].value_counts() # ← 改动点3:计数与绘图改用改名后的等级列 plt.figure(figsize=(12, 6)) # 与平台任务一致 bars = plt.bar(user_counts.index, user_counts.values, color='blue', alpha=0.4) # 与平台任务一致 plt.title('各等级类型用户数(改名后)') # ← 标题同步注明改名,避免读者对照旧图困惑 plt.xticks(rotation=45, ha='right') # 与平台任务一致结构性判读:柱状图的柱数、柱高、排序与改名前完全一致,只有x轴标签与图例文字变化;若改名前后柱高有任何不同,说明改的不是标签而是映射逻辑,应回查。判读要点:新名称应与该编码的业务含义对得上,否则会在沟通中造成新的混淆。
本地演练版(模拟数据):在与正文本地演练同一份16行迷你表上实跑改名前后对照:
# 本地演练版:改名前后的等级计数对照(数据与@lst-ch27-local-drill一致) import pandas as pd # 导入Pandas数据分析库 rfm_mini = pd.DataFrame({ # 16行迷你表,两行故意带前导空格,演示strip的必要性 'RFM': ['高高高', '高高高', ' 高高高', '高高高', '低低低', '低低低', '低低低', '低高高', '低高高', ' 高低高', '高低高', '高高低', '高高低', '低低高', '高低低', '低高低'] }) rfm_mini['RFM'] = rfm_mini['RFM'].str.strip() # 与平台任务一致:先去除编码前后空格 old_map = {'高高高': '重要客户', '高低高': '低购买频率客户', '低高高': '长期未消费客户', '低低高': '长期未消费低购买频率客户', '高高低': '低消费客户', '高低低': '低购买频率低消费客户', '低高低': '长期未消费低消费客户'} # 平台原名映射 level_map = dict(old_map) # 复制一份再改名,便于并排对照 level_map['低高高'] = '重要挽留客户' # 改动点1:原“长期未消费客户” level_map['低低高'] = '高价值沉睡客户' # 改动点2:原“长期未消费低购买频率客户” rfm_mini['用户等级_原名'] = rfm_mini['RFM'].apply(lambda x: old_map.get(x, '其他待发展客户')) # 原名口径计数 rfm_mini['用户等级_新名'] = rfm_mini['RFM'].apply(lambda x: level_map.get(x, '其他待发展客户')) # 新名口径计数 print('改名前后同位置对照:') print(pd.DataFrame({'原名计数': rfm_mini['用户等级_原名'].value_counts(), '新名计数': rfm_mini['用户等级_新名'].value_counts()})) # 计数不随改名变化模拟数据演练结果(本机 peter 环境实际运行结果,仅演示流程与判读方法,真实数值以平台运行结果为准):
改名前后同位置对照: 原名计数 新名计数 低消费客户 2.0 2.0 低购买频率低消费客户 1.0 1.0 低购买频率客户 2.0 2.0 其他待发展客户 3.0 3.0 重要客户 4.0 4.0 重要挽留客户 NaN 2.0 长期未消费低消费客户 1.0 1.0 长期未消费低购买频率客户 1.0 NaN 长期未消费客户 2.0 NaN 高价值沉睡客户 NaN 1.0演练判读:两列合计都是16——“长期未消费客户”(2人)与“长期未消费低购买频率客户”(1人)分别原位变成“重要挽留客户”与“高价值沉睡客户”,其余行数值不变,证明改名只动标签、不动计数。
预期输出:数据文件由教学平台内置,本机无法复现,具体数值以平台运行结果为准。
注意:以上为本题变式的独立代码;列表 27.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应“本章新增的技能要点”第2条(多分支映射的两种写法:if-elif链与字典加
get默认值,改名在字典写法里只改值)。变式任务:在柱状图的数值标注后追加各等级占比,实现思路是什么?
参考答案(点开前请先独立完成)
改造思路:分两步。数据侧,在
value_counts()之后立刻补一列占比——user_shares = user_counts / user_counts.sum()(即normalize=True的手动写法,本章要点3的两种口径);标注侧,遍历柱子的循环改用enumerate拿到位置序号,标注文本由单段人数扩为“人数(占比)”两段式,用位置序号到user_shares里按位取对应占比。# 变式代码:柱顶标注由“人数”扩为“人数(占比)”两段式 user_counts = rfm['用户等级'].value_counts() # 与平台任务一致的计数 user_shares = user_counts / user_counts.sum() # ← 新增:各等级占比(value_counts(normalize=True)的同型写法) plt.figure(figsize=(12, 6)) # 与平台任务一致 bars = plt.bar(user_counts.index, user_counts.values, color='blue', alpha=0.4) # 与平台任务一致 for i, bar in enumerate(bars): # ← 改动点:增加位置序号,便于按位取占比 height = bar.get_height() # 与平台任务一致:柱高即人数 label = f'{int(height):,}({user_shares.iloc[i] * 100:.1f}%)' # ← 改动点:标注文本追加占比段 plt.text(bar.get_x() + bar.get_width() / 2., height, label, ha='center', va='bottom', fontsize=9) # 数据源换成新文本 plt.xticks(rotation=45, ha='right') # 与平台任务一致结构性判读:柱形不变,柱顶文本由“1,234”变为“1,234(12.3%)”样式;占比之和应为100%(受四舍五入影响约有0.1个百分点的出入)。判读要点:占比让“头部等级有多集中”一眼可读,不必再对照总数心算;标注字号过小时两段式文本可能重叠,可减小字号或旋转标签。
本地演练版(模拟数据):在与第3题同一份16行迷你表上实跑标注文本的生成逻辑:
# 本地演练版:生成“人数(占比)”两段式标注文本(数据同第3题) import pandas as pd # 导入Pandas数据分析库 rfm_mini = pd.DataFrame({ # 16行迷你表,与正文本地演练版一致 'RFM': ['高高高', '高高高', ' 高高高', '高高高', '低低低', '低低低', '低低低', '低高高', '低高高', ' 高低高', '高低高', '高高低', '高高低', '低低高', '高低低', '低高低'] }) rfm_mini['RFM'] = rfm_mini['RFM'].str.strip() # 与平台任务一致 level_map = { # 与平台任务同口径的映射 '高高高': '重要客户', '高低高': '低购买频率客户', '低高高': '长期未消费客户', '低低高': '长期未消费低购买频率客户', '高高低': '低消费客户', '高低低': '低购买频率低消费客户', '低高低': '长期未消费低消费客户' } rfm_mini['用户等级'] = rfm_mini['RFM'].apply(lambda x: level_map.get(x, '其他待发展客户')) # 同平台apply user_counts = rfm_mini['用户等级'].value_counts() # 与平台任务一致的计数 user_shares = user_counts / user_counts.sum() # 新增:各等级占比,分母为客户总数 print('柱顶标注文本(人数(占比)两段式):') for i, (level, count) in enumerate(user_counts.items()): # 模拟遍历柱子的循环,i与柱位置一一对应 label = f'{int(count):,}({user_shares.iloc[i] * 100:.1f}%)' # 改动点:标注文本追加占比段 print(f'{level}: {label}') # 输出各柱的标注文本模拟数据演练结果(本机 peter 环境实际运行结果,仅演示流程与判读方法,真实数值以平台运行结果为准):
柱顶标注文本(人数(占比)两段式): 重要客户: 4(25.0%) 其他待发展客户: 3(18.8%) 长期未消费客户: 2(12.5%) 低购买频率客户: 2(12.5%) 低消费客户: 2(12.5%) 长期未消费低购买频率客户: 1(6.2%) 低购买频率低消费客户: 1(6.2%) 长期未消费低消费客户: 1(6.2%)演练判读:8个等级的占比之和约99.9%(尾差来自四舍五入),头部“重要客户”占四分之一——人数与占比并排后,客群集中度不必心算即可读出。
预期输出:数据文件由教学平台内置,本机无法复现,具体数值以平台运行结果为准。
注意:以上为本题变式的独立代码;列表 27.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。
回扣本章:对应“本章新增的技能要点”第3条(
value_counts的两种口径:人数与占比配合回答“有多少”与“占多少”)与第4条(柱顶数值标注提升计数图可读性)。商业判断:若平台结果显示”长期未消费”各类客户合计占比明显偏高(具体数值以平台运行结果为准),下一步营销预算应优先投向”老客召回”还是”新客获取”?给出你的判断依据与需要的补充数据。
参考答案(点开前请先独立完成)
参考作答框架:本题不设唯一结论,优秀作答应覆盖以下维度。
- 口径先行:“长期未消费”的边界由R的阈值决定,阈值是谁定的、按多长观察窗计算,直接决定“占比偏高”是事实还是口径产物;占比分母是全部客户,人数口径之外还应看金额口径——沉睡客户的历史消费金额占比可能远高于其人数占比,两类口径给出的预算方向不同。
- 分析依据:比较单位预算的边际回报——召回的单位成本乘以召回转化率乘以客户历史客单价与预期存续期,对照新客的单位获客成本乘以转化率乘以预计客单价乘以留存率;老客数据齐全、历史价值可估,通常召回高价值沉睡客户的单位回报更可测,但这要用数据说话而不是直觉。
- 风险考量:召回渠道的疲劳与骚扰感(频繁触达加速拉黑)、优惠券对毛利的侵蚀、召回后再流失;新客侧则是获客成本上涨与低留存,投入可能打水漂。
- 模型边界:RFM阈值基于全样本分布,换观察窗结论会变;“长期未消费占比高”也可能是数据期自然到期(客户生命周期本就有限),未必都是可挽回的流失。
- 还需补充的数据:各等级客户的历史贡献金额与毛利、召回实验(A/B测试)的转化率与成本、新客获客成本与留存曲线、沉睡原因调研(价格、体验、需求消失)、客户分层的服务成本。
- 常见错误作答形态:只按人数占比分配预算而忽略金额贡献;不做小规模实验就直接全面铺开召回;把“占比高”直接等同为“值得召回”。
回扣本章:对应“本章新增的技能要点”第3条(人数口径与占比口径配合使用,预算决策还要再补金额口径)。