实战小项目:奶茶店会员流失分析
前四课各练了一个动作,这一课把它们串成一套连招。数据、清洗、建模、分群、关联规则、业务结论——一个不落,全部在这一课里跑通。
项目做完,你的产出不是一堆截图,而是三条能直接发给店长的建议。
一、项目背景
楼下的奶茶店给了你一份会员登记表(20 位会员,见下),店长的原话是:"最近感觉老客人不来了,你帮我看看谁快流失了,我该做点什么?"
翻译成挖掘的语言(CRISP-DM 第 ① 步:业务理解):
- 业务问题:哪些会员快要流失?流失的人有什么共同特征?
- 可用数据:会员的年龄、月均消费、月均到店次数、距上次到店天数、是否领过优惠券、以及历史上有过多少人流失
- 预期产出:一份流失预测模型 + 会员分群画像 + 三条建议
二、第 0 步:准备数据
新建 project.py,把数据直接写进脚本(真实项目里这一步是 pd.read_csv("会员表.csv"),这里为了让你复制即跑):
python
import io
import pandas as pd
csv_text = """会员ID,年龄,月均消费,月均到店次数,距上次到店天数,领过优惠券,是否流失
M01,19,180,8,7,1,0
M02,25,240,10,10,0,0
M03,31,320,12,5,1,0
M04,22,,6,15,0,0
M05,28,150,5,30,1,0
M06,35,90,3,40,0,0
M07,27,260,9,12,1,0
M08,41,410,13,8,1,0
M09,23,120,4,38,0,0
M10,30,290,11,9,0,0
M11,26,80,2,75,1,1
M12,33,60,2,60,0,1
M13,29,,3,82,1,1
M14,38,110,4,55,0,1
M15,21,70,2,90,1,1
M16,45,95,3,68,0,1
M17,32,130,5,50,1,1
M18,24,65,2,85,0,1
M19,36,140,5,47,1,1
M20,28,85,3,72,0,1
M20,28,85,3,72,0,1
"""
df = pd.read_csv(io.StringIO(csv_text))列说明:是否流失 是答案列(1 = 已经流失,0 = 还在来);其余 5 列是特征。真实项目里"是否流失"由业务系统按"超过 60 天没来"之类的规则打出来,模型学的是在它发生之前就认出这种人。
三、第 1 步:体检与清洗
第二课的三板斧直接上场:
python
print(df.info()) # 21 行?还有缺失——表里有猫腻
df["月均消费"] = df["月均消费"].fillna(df["月均消费"].median()) # 填中位数
df = df.drop_duplicates().reset_index(drop=True) # 删重复行
print(df.info()) # 现在:20 行、无缺失,体检通过两处 月均消费 缺失用中位数补上,一条 M20 整行重复删掉。到这里为止,数据准备(CRISP-DM 第 ③ 步)完成。
四、第 2 步:探索——先猜,再算
建模之前先让数据自己说话。按是否流失分组算均值:
python
print(df.groupby("是否流失")[["月均消费", "月均到店次数", "距上次到店天数"]].mean())你会看到非常扎眼的对比:留存组平均约 17 天没来,流失组平均约 68 天没来;月均到店次数一个 8 次、一个 3 次。先用自己的话把规律讲出来("流失的人就是很久没来、来得也少的人"),再让模型去验证——模型是用来检验直觉的,不是用来代替直觉的。
五、第 3 步:建模——预测谁会流失
决策树上场(第三课的原配方,只是换了数据):
python
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.metrics import accuracy_score
X = df[["年龄", "月均消费", "月均到店次数", "距上次到店天数", "领过优惠券"]]
y = df["是否流失"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42, stratify=y
)
model = DecisionTreeClassifier(max_depth=3, random_state=42)
model.fit(X_train, y_train)
print("测试集准确率:", accuracy_score(y_test, model.predict(X_test)))
print(export_text(model, feature_names=list(X.columns)))打印出的树大概率非常简洁——几乎只靠"距上次到店天数"一两个问题就把人分开了,和第 2 步你的直觉互相印证。
两句必须说透的实话:
- 这份数据样本少、规律干净,准确率接近 1.0 并不稀奇;真实数据远没有这么乖,那时你会需要交叉验证和更多特征
- 决策树给出的其实是一条业务规则:"超过 N 天没来 → 流失"。它值钱的地方不是准确率,而是店长听得懂、收银台上贴得住
🧠 记忆锚点:模型输出的是规则,能贴在收银台上的才是好模型。
六、第 4 步:分群——给会员画像
预测是对"单个会员"说话,分群是对"整群会员"说话。用 K-Means 把会员分成三群(第三课讲过 k 的选法,这里店长就想要三群):
python
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
feat = df[["月均消费", "月均到店次数"]]
X_scaled = StandardScaler().fit_transform(feat) # 消费是几百、次数是个位数,必须先标准化
df["分群"] = KMeans(n_clusters=3, n_init=10, random_state=42).fit_predict(X_scaled)
print(df.groupby("分群")[["月均消费", "月均到店次数", "距上次到店天数", "是否流失"]].mean())解读三群的典型画像(你的分组编号可能不同,看特征不看编号):
| 群 | 画像 | 店长视角 |
|---|---|---|
| 高消费、来得勤 | 核心客 | 宝贝,新品先给他们试,别让他们被隔壁挖走 |
| 中等消费、稳定到店 | 主力客 | 稳住即可,办卡/储值的最好目标 |
| 低消费、很久没来 | 沉睡客 | 流失率最高的群体,挽回的主战场 |
注意那个 StandardScaler:月均消费是几百的量级、到店次数是个位数,不缩放的话 K-Means 会完全被消费额牵着走——第二课说"数据洗多干净模型多聪明",这里再补一句:尺度不齐,聚类失真。
七、第 5 步:把结果讲成人话
CRISP-DM 第 ⑤⑥ 步:评估和部署,本质都是"说人话 + 用起来"。给店长的建议这样写(每条都挂着数据证据):
- 距上次到店超过 43 天的会员,流失风险最高(模型的第一判据就是"距上次到店天数",分界线在 43.5)——建议在第 40 天左右就自动发一张定向券,别等他过了线
- 高消费高频的会员是核心资产(分群结果,约占四分之一)——新品试饮、生日礼遇优先给他们,成本花在刀刃上
- 沉睡群流失率最高、领没领过券差别不大(数据里优惠券与流失相关性弱)——问题不在券的力度,在于"太久没来",所以先把他拉回店,再谈优惠
这三条就是本项目的最终交付物。挖掘给弹药,业务扣扳机——把数字翻译成动作,是整个项目里人最不可替代的部分。
八、加餐:顺手做个购物篮
店长还有 8 张订单小票,用第四课的迷你 Apriori 直接跑(把 apriori.py 里的 baskets 换成下面这份,min_support 改成 0.3):
python
orders = [
{"珍珠奶茶", "蛋糕"},
{"珍珠奶茶", "鸡排"},
{"珍珠奶茶", "蛋糕", "鸡排"},
{"柠檬茶", "鸡排"},
{"珍珠奶茶", "蛋糕"},
{"柠檬茶"},
{"珍珠奶茶", "鸡排"},
{"珍珠奶茶", "蛋糕"},
]跑完你会看到"蛋糕 → 珍珠奶茶"的提升度约 1.33,而"鸡排 → 珍珠奶茶"的提升度约 1.0——蛋糕和珍珠奶茶是官配,鸡排只是路人。于是第 4 条建议:凑单券绑"珍珠奶茶 + 蛋糕"这个组合,别白送鸡排折扣。
九、验收自查
做完对照这份清单:
- [ ] 能说出项目的业务问题,以及最终三条建议分别靠哪一步的什么结论支撑
- [ ] 能解释为什么补缺失用中位数、为什么 K-Means 前要
StandardScaler - [ ] 能指着决策树的输出,把学到的规则翻译成店长听得懂的一句话
- [ ] 把
random_state删掉重跑一次,观察结果会不会变,并解释为什么
💡 动手实验 1(换血):往数据里加 5 行你自己编的会员(比如"消费巨高但两个月没来"的土豪),重新跑第 3~4 步,看模型和分群的结论变没变。给模型喂新案例,是理解模型脾气最快的办法。
💡 动手实验 2(换数据):把
csv_text整个换成sklearn.datasets自带的葡萄酒数据集(load_wine(as_frame=True),178 条、13 个特征、3 个品类),重跑第 3~5 步。流程一模一样——CRISP-DM 的意义就在这:换数据不换流程。
💡 动手实验 3(部署):把脚本存成
weekly_report.py,想象它每周一自动跑一遍、把三条建议发到店长微信。这就是 CRISP-DM 第 ⑥ 步"部署"——模型不住在 Notebook 里,模型住在业务流程里。
小结
text
业务理解(店长想留客)→ 数据理解(21 行里有猫腻)→ 数据准备(补缺、去重)
→ 建模(决策树:超过 N 天没来 ≈ 流失)→ 分群(三群画像)
→ 评估与部署(三条挂着证据的建议 + 购物篮加餐)- 六步流程一步没跳,但你发现没有:写代码只占三成,想清楚问题、洗干净数据、讲成人话占了七成——和第一课说的"八成时间洗数据"遥相呼应
- 预测回答"谁会流失",分群回答"客人分几类",关联规则回答"怎么搭着卖"——三类问题、三次出手,这就是数据挖掘的日常
到这里,数据挖掘学习站的五门课就毕业了。把这套流程用在你自己的网站访问日志上(谁在扫描我?哪个页面最热?),你就从"学过挖掘"走到了"用挖掘干活"。