项目三教学文档:电信客户流失预测 ------ 表格分类全流程
https://gitee.com/ghaweiuptgb/machine-learning-AI.git (代码包)
使用说明 :本文档是项目三的完整教学记录。每完成一个步骤,我会把该步骤的详细教学过程写入对应章节。
配套文件:
- 总体规划见
../机器学习实战学习路线.md- 你的个人笔记写在
README.md- 代码写在
src/train.py代码规范 :逐行注释 + 图表中文(见根目录
.cursor/rules/)讲解规范 :详细讲解 + 新名词必解释 + Mermaid 图解 + 每步提供动手任务与验收标准参考答案
目录
| 步骤 | 标题 | 状态 |
|---|---|---|
| [步骤 1](#步骤 标题 状态 步骤 1 加载与初步检查 ✅ 已完成教学 步骤 2 数据清洗 ✅ 已完成教学 步骤 3 EDA 与业务洞察 ✅ 已完成教学 步骤 4 特征与标签分离 + 分层划分 ✅ 已完成教学 步骤 5 构建预处理 Pipeline ✅ 已完成教学 步骤 6 模型 + Pipeline 一体训练 ✅ 已完成教学 步骤 7 多模型对比 ✅ 已完成教学 步骤 8 特征重要性与可解释性 ✅ 已完成教学 步骤 9 模拟运营应用 ✅ 已完成教学) | 加载与初步检查 | ✅ 已完成教学 |
| [步骤 2](#步骤 标题 状态 步骤 1 加载与初步检查 ✅ 已完成教学 步骤 2 数据清洗 ✅ 已完成教学 步骤 3 EDA 与业务洞察 ✅ 已完成教学 步骤 4 特征与标签分离 + 分层划分 ✅ 已完成教学 步骤 5 构建预处理 Pipeline ✅ 已完成教学 步骤 6 模型 + Pipeline 一体训练 ✅ 已完成教学 步骤 7 多模型对比 ✅ 已完成教学 步骤 8 特征重要性与可解释性 ✅ 已完成教学 步骤 9 模拟运营应用 ✅ 已完成教学) | 数据清洗 | ✅ 已完成教学 |
| [步骤 3](#步骤 标题 状态 步骤 1 加载与初步检查 ✅ 已完成教学 步骤 2 数据清洗 ✅ 已完成教学 步骤 3 EDA 与业务洞察 ✅ 已完成教学 步骤 4 特征与标签分离 + 分层划分 ✅ 已完成教学 步骤 5 构建预处理 Pipeline ✅ 已完成教学 步骤 6 模型 + Pipeline 一体训练 ✅ 已完成教学 步骤 7 多模型对比 ✅ 已完成教学 步骤 8 特征重要性与可解释性 ✅ 已完成教学 步骤 9 模拟运营应用 ✅ 已完成教学) | EDA 与业务洞察 | ✅ 已完成教学 |
| [步骤 4](#步骤 标题 状态 步骤 1 加载与初步检查 ✅ 已完成教学 步骤 2 数据清洗 ✅ 已完成教学 步骤 3 EDA 与业务洞察 ✅ 已完成教学 步骤 4 特征与标签分离 + 分层划分 ✅ 已完成教学 步骤 5 构建预处理 Pipeline ✅ 已完成教学 步骤 6 模型 + Pipeline 一体训练 ✅ 已完成教学 步骤 7 多模型对比 ✅ 已完成教学 步骤 8 特征重要性与可解释性 ✅ 已完成教学 步骤 9 模拟运营应用 ✅ 已完成教学) | 特征与标签分离 + 分层划分 | ✅ 已完成教学 |
| [步骤 5](#步骤 标题 状态 步骤 1 加载与初步检查 ✅ 已完成教学 步骤 2 数据清洗 ✅ 已完成教学 步骤 3 EDA 与业务洞察 ✅ 已完成教学 步骤 4 特征与标签分离 + 分层划分 ✅ 已完成教学 步骤 5 构建预处理 Pipeline ✅ 已完成教学 步骤 6 模型 + Pipeline 一体训练 ✅ 已完成教学 步骤 7 多模型对比 ✅ 已完成教学 步骤 8 特征重要性与可解释性 ✅ 已完成教学 步骤 9 模拟运营应用 ✅ 已完成教学) | 构建预处理 Pipeline | ✅ 已完成教学 |
| [步骤 6](#步骤 标题 状态 步骤 1 加载与初步检查 ✅ 已完成教学 步骤 2 数据清洗 ✅ 已完成教学 步骤 3 EDA 与业务洞察 ✅ 已完成教学 步骤 4 特征与标签分离 + 分层划分 ✅ 已完成教学 步骤 5 构建预处理 Pipeline ✅ 已完成教学 步骤 6 模型 + Pipeline 一体训练 ✅ 已完成教学 步骤 7 多模型对比 ✅ 已完成教学 步骤 8 特征重要性与可解释性 ✅ 已完成教学 步骤 9 模拟运营应用 ✅ 已完成教学) | 模型 + Pipeline 一体训练 | ✅ 已完成教学 |
| [步骤 7](#步骤 标题 状态 步骤 1 加载与初步检查 ✅ 已完成教学 步骤 2 数据清洗 ✅ 已完成教学 步骤 3 EDA 与业务洞察 ✅ 已完成教学 步骤 4 特征与标签分离 + 分层划分 ✅ 已完成教学 步骤 5 构建预处理 Pipeline ✅ 已完成教学 步骤 6 模型 + Pipeline 一体训练 ✅ 已完成教学 步骤 7 多模型对比 ✅ 已完成教学 步骤 8 特征重要性与可解释性 ✅ 已完成教学 步骤 9 模拟运营应用 ✅ 已完成教学) | 多模型对比 | ✅ 已完成教学 |
| [步骤 8](#步骤 标题 状态 步骤 1 加载与初步检查 ✅ 已完成教学 步骤 2 数据清洗 ✅ 已完成教学 步骤 3 EDA 与业务洞察 ✅ 已完成教学 步骤 4 特征与标签分离 + 分层划分 ✅ 已完成教学 步骤 5 构建预处理 Pipeline ✅ 已完成教学 步骤 6 模型 + Pipeline 一体训练 ✅ 已完成教学 步骤 7 多模型对比 ✅ 已完成教学 步骤 8 特征重要性与可解释性 ✅ 已完成教学 步骤 9 模拟运营应用 ✅ 已完成教学) | 特征重要性与可解释性 | ✅ 已完成教学 |
| [步骤 9](#步骤 标题 状态 步骤 1 加载与初步检查 ✅ 已完成教学 步骤 2 数据清洗 ✅ 已完成教学 步骤 3 EDA 与业务洞察 ✅ 已完成教学 步骤 4 特征与标签分离 + 分层划分 ✅ 已完成教学 步骤 5 构建预处理 Pipeline ✅ 已完成教学 步骤 6 模型 + Pipeline 一体训练 ✅ 已完成教学 步骤 7 多模型对比 ✅ 已完成教学 步骤 8 特征重要性与可解释性 ✅ 已完成教学 步骤 9 模拟运营应用 ✅ 已完成教学) | 模拟运营应用 | ✅ 已完成教学 |
步骤 1:加载与初步检查
状态 :✅ 已完成教学(报告见
reports/step1_data_inspection.txt)
1.1 本步目标
- 创建
project_03_churn/目录结构 - 加载 Telco Customer Churn 数据集
- 检查
customerID是否应作为特征(应剔除) - 列出数值特征 与类别特征列表
- 发现
TotalCharges中的空格字符串问题
1.2 这一步在解决什么问题?
项目二学的是「极度不平衡 + 匿名特征」。项目三进入真实业务表格数据:
- 有客户编号 、合同类型 、月费等可读字段
- 数值列和类别列混在一起
- 原始 CSV 常有脏数据(如空格当缺失)
在清洗和建模之前,必须先「验货」:行数对不对、哪些列能当特征、哪里有问题。
1.3 本步在整体流程中的位置
#mermaid-svg-g7DDNpcoCvk1Rz1H{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-g7DDNpcoCvk1Rz1H .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-g7DDNpcoCvk1Rz1H .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-g7DDNpcoCvk1Rz1H .error-icon{fill:#552222;}#mermaid-svg-g7DDNpcoCvk1Rz1H .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-g7DDNpcoCvk1Rz1H .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-g7DDNpcoCvk1Rz1H .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-g7DDNpcoCvk1Rz1H .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-g7DDNpcoCvk1Rz1H .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-g7DDNpcoCvk1Rz1H .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-g7DDNpcoCvk1Rz1H .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-g7DDNpcoCvk1Rz1H .marker{fill:#333333;stroke:#333333;}#mermaid-svg-g7DDNpcoCvk1Rz1H .marker.cross{stroke:#333333;}#mermaid-svg-g7DDNpcoCvk1Rz1H svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-g7DDNpcoCvk1Rz1H p{margin:0;}#mermaid-svg-g7DDNpcoCvk1Rz1H .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-g7DDNpcoCvk1Rz1H .cluster-label text{fill:#333;}#mermaid-svg-g7DDNpcoCvk1Rz1H .cluster-label span{color:#333;}#mermaid-svg-g7DDNpcoCvk1Rz1H .cluster-label span p{background-color:transparent;}#mermaid-svg-g7DDNpcoCvk1Rz1H .label text,#mermaid-svg-g7DDNpcoCvk1Rz1H span{fill:#333;color:#333;}#mermaid-svg-g7DDNpcoCvk1Rz1H .node rect,#mermaid-svg-g7DDNpcoCvk1Rz1H .node circle,#mermaid-svg-g7DDNpcoCvk1Rz1H .node ellipse,#mermaid-svg-g7DDNpcoCvk1Rz1H .node polygon,#mermaid-svg-g7DDNpcoCvk1Rz1H .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-g7DDNpcoCvk1Rz1H .rough-node .label text,#mermaid-svg-g7DDNpcoCvk1Rz1H .node .label text,#mermaid-svg-g7DDNpcoCvk1Rz1H .image-shape .label,#mermaid-svg-g7DDNpcoCvk1Rz1H .icon-shape .label{text-anchor:middle;}#mermaid-svg-g7DDNpcoCvk1Rz1H .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-g7DDNpcoCvk1Rz1H .rough-node .label,#mermaid-svg-g7DDNpcoCvk1Rz1H .node .label,#mermaid-svg-g7DDNpcoCvk1Rz1H .image-shape .label,#mermaid-svg-g7DDNpcoCvk1Rz1H .icon-shape .label{text-align:center;}#mermaid-svg-g7DDNpcoCvk1Rz1H .node.clickable{cursor:pointer;}#mermaid-svg-g7DDNpcoCvk1Rz1H .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-g7DDNpcoCvk1Rz1H .arrowheadPath{fill:#333333;}#mermaid-svg-g7DDNpcoCvk1Rz1H .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-g7DDNpcoCvk1Rz1H .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-g7DDNpcoCvk1Rz1H .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g7DDNpcoCvk1Rz1H .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-g7DDNpcoCvk1Rz1H .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g7DDNpcoCvk1Rz1H .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-g7DDNpcoCvk1Rz1H .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-g7DDNpcoCvk1Rz1H .cluster text{fill:#333;}#mermaid-svg-g7DDNpcoCvk1Rz1H .cluster span{color:#333;}#mermaid-svg-g7DDNpcoCvk1Rz1H div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-g7DDNpcoCvk1Rz1H .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-g7DDNpcoCvk1Rz1H rect.text{fill:none;stroke-width:0;}#mermaid-svg-g7DDNpcoCvk1Rz1H .icon-shape,#mermaid-svg-g7DDNpcoCvk1Rz1H .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-g7DDNpcoCvk1Rz1H .icon-shape p,#mermaid-svg-g7DDNpcoCvk1Rz1H .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-g7DDNpcoCvk1Rz1H .icon-shape .label rect,#mermaid-svg-g7DDNpcoCvk1Rz1H .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-g7DDNpcoCvk1Rz1H .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-g7DDNpcoCvk1Rz1H .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-g7DDNpcoCvk1Rz1H :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 步骤1 加载检查
步骤2 数据清洗
步骤3 EDA
步骤4 划分数据
步骤5~6 Pipeline
步骤7~9 模型与运营
1.4 名词解释(本步首次出现)
| 名词 | 是什么 | 为什么需要 | 在本项目中 | 易混淆 |
|---|---|---|---|---|
| Telco Churn | 电信客户是否流失(解约) | 挽留高流失客户能省获客成本 | 标签列 Churn |
不是欺诈检测 |
| customerID | 客户唯一编号 | 仅用于标识,与流失无关 | 建模时剔除 | 不是特征 |
| 数值特征 | 可取大小比较的列 | 模型需要区分类型 | tenure、MonthlyCharges | 不是类别列 |
| 类别特征 | 离散取值的列 | 需 One-Hot 等编码 | Contract、gender | 不是连续数值 |
| TotalCharges | 客户累计总费用 | 重要预测因子 | 读入时是字符串,含 11 行空格 | 不是 MonthlyCharges |
1.5 项目目录结构
project_03_churn/
├── 教学文档.md # 详细教学(本文件)
├── README.md # 你的学习笔记
├── data/
│ └── WA_Fn-UseC_-Telco-Customer-Churn.csv
├── src/
│ └── train.py # 主训练脚本
├── reports/figures/ # 图表输出
└── models/ # 最终 Pipeline 保存
1.6 任务清单
- 创建
project_03_churn/目录结构 - 下载 CSV 到
data/ - 编写
src/train.py(含load_data()、列类型检查) - 检查 customerID、TotalCharges 异常
- 输出数值/类别特征列表
- 保存
step1_data_inspection.txt
1.7 运行本步
powershell
cd "d:\JavaCode\机器学习\project_03_churn"
python src/train.py
1.8 数据集概览(你的环境)
| 项目 | 数值 |
|---|---|
| 总行数 | 7,043 |
| 总列数 | 21(含 customerID + 19 特征 + Churn) |
| 未流失 No | 5,174(73.46%) |
| 已流失 Yes | 1,869(26.54%) |
| TotalCharges 空格行 | 11(多为 tenure=0 新用户) |
与项目二对比 :流失率约 26%,比欺诈 0.17% 均衡得多,Accuracy 在此项目相对更有参考价值(但仍要看 F1、Recall)。
1.9 数值特征 vs 类别特征
数值特征(3 列,dtype 已是数字):
| 列名 | 含义 |
|---|---|
| SeniorCitizen | 是否老年客户(0/1) |
| tenure | 在网时长(月) |
| MonthlyCharges | 月费 |
类别特征(16 列,含待清洗的 TotalCharges):
gender、Partner、Dependents、PhoneService、MultipleLines、InternetService、OnlineSecurity、OnlineBackup、DeviceProtection、TechSupport、StreamingTV、StreamingMovies、Contract、PaperlessBilling、PaymentMethod、TotalCharges(当前为字符串)
步骤 2 将把 TotalCharges 转为数值,清洗后数值特征变为 4 列。
1.10 customerID 为什么要剔除?
#mermaid-svg-v50qHWH24W0Glzde{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-v50qHWH24W0Glzde .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-v50qHWH24W0Glzde .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-v50qHWH24W0Glzde .error-icon{fill:#552222;}#mermaid-svg-v50qHWH24W0Glzde .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-v50qHWH24W0Glzde .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-v50qHWH24W0Glzde .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-v50qHWH24W0Glzde .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-v50qHWH24W0Glzde .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-v50qHWH24W0Glzde .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-v50qHWH24W0Glzde .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-v50qHWH24W0Glzde .marker{fill:#333333;stroke:#333333;}#mermaid-svg-v50qHWH24W0Glzde .marker.cross{stroke:#333333;}#mermaid-svg-v50qHWH24W0Glzde svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-v50qHWH24W0Glzde p{margin:0;}#mermaid-svg-v50qHWH24W0Glzde .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-v50qHWH24W0Glzde .cluster-label text{fill:#333;}#mermaid-svg-v50qHWH24W0Glzde .cluster-label span{color:#333;}#mermaid-svg-v50qHWH24W0Glzde .cluster-label span p{background-color:transparent;}#mermaid-svg-v50qHWH24W0Glzde .label text,#mermaid-svg-v50qHWH24W0Glzde span{fill:#333;color:#333;}#mermaid-svg-v50qHWH24W0Glzde .node rect,#mermaid-svg-v50qHWH24W0Glzde .node circle,#mermaid-svg-v50qHWH24W0Glzde .node ellipse,#mermaid-svg-v50qHWH24W0Glzde .node polygon,#mermaid-svg-v50qHWH24W0Glzde .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-v50qHWH24W0Glzde .rough-node .label text,#mermaid-svg-v50qHWH24W0Glzde .node .label text,#mermaid-svg-v50qHWH24W0Glzde .image-shape .label,#mermaid-svg-v50qHWH24W0Glzde .icon-shape .label{text-anchor:middle;}#mermaid-svg-v50qHWH24W0Glzde .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-v50qHWH24W0Glzde .rough-node .label,#mermaid-svg-v50qHWH24W0Glzde .node .label,#mermaid-svg-v50qHWH24W0Glzde .image-shape .label,#mermaid-svg-v50qHWH24W0Glzde .icon-shape .label{text-align:center;}#mermaid-svg-v50qHWH24W0Glzde .node.clickable{cursor:pointer;}#mermaid-svg-v50qHWH24W0Glzde .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-v50qHWH24W0Glzde .arrowheadPath{fill:#333333;}#mermaid-svg-v50qHWH24W0Glzde .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-v50qHWH24W0Glzde .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-v50qHWH24W0Glzde .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-v50qHWH24W0Glzde .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-v50qHWH24W0Glzde .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-v50qHWH24W0Glzde .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-v50qHWH24W0Glzde .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-v50qHWH24W0Glzde .cluster text{fill:#333;}#mermaid-svg-v50qHWH24W0Glzde .cluster span{color:#333;}#mermaid-svg-v50qHWH24W0Glzde div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-v50qHWH24W0Glzde .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-v50qHWH24W0Glzde rect.text{fill:none;stroke-width:0;}#mermaid-svg-v50qHWH24W0Glzde .icon-shape,#mermaid-svg-v50qHWH24W0Glzde .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-v50qHWH24W0Glzde .icon-shape p,#mermaid-svg-v50qHWH24W0Glzde .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-v50qHWH24W0Glzde .icon-shape .label rect,#mermaid-svg-v50qHWH24W0Glzde .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-v50qHWH24W0Glzde .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-v50qHWH24W0Glzde .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-v50qHWH24W0Glzde :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} customerID 7590-VHVEG
只是编号
与是否流失无因果关系
建模时必须剔除
每个 ID 唯一,模型若「记住 ID」等于作弊式过拟合,对新客户毫无用处。
1.11 核心代码解读
(1)load_data() ------ 本地优先 + 自动下载
python
if DATA_PATH.exists():
return pd.read_csv(DATA_PATH)
_download_data(DATA_DOWNLOAD_URL, DATA_PATH)
return pd.read_csv(DATA_PATH)
| 要点 | 说明 |
|---|---|
| 本地优先 | 已下载则秒开 |
| GitHub 备用 | 换电脑运行脚本可自动拉取(约 1 MB) |
| 不提交 Git | 已加入 .gitignore |
(2)inspect_total_charges_issues() ------ 发现脏数据
python
stripped = df["TotalCharges"].astype(str).str.strip()
blank_mask = stripped.eq("") | stripped.eq("nan")
coerced = pd.to_numeric(stripped, errors="coerce")
发现 :11 行是空字符串,对应 tenure=0 的新用户------还没产生账单,TotalCharges 为空。步骤 2 会用中位数填充或按业务规则处理。
1.12 动手任务
任务 A :运行 python src/train.py,确认 7043 行
任务 B :在 Python 中执行 df['Contract'].value_counts(),看看有几种合同
任务 C :找出 tenure==0 的行数,与 TotalCharges 空格行是否一致?
任务 D:在 README 步骤 1 写下日期和一句话感受
1.13 验收标准
- 能
pd.read_csv加载并打印前 5 行 - 能说出 customerID 为何不能当特征
- 能列出数值特征与类别特征
- 知道 TotalCharges 有 11 行需清洗
1.14 常见问题
1.14 动手任务与验收标准参考答案
以下为教学标准答案,便于自学对照;
README.md学习记录仍建议用自己的话写。
动手任务答案
| 任务 | 参考答案 |
|---|---|
| A | 7043 行。 |
| B | 三种合同:Month-to-month、One year、Two year。 |
| C | tenure=0 共 11 行,与 TotalCharges 空格行一致。 |
| D | 示例:混合类型需清洗 TotalCharges。 |
验收标准答案
| 验收项 | 参考答案 |
|---|---|
| read_csv | 能加载并 head。 |
| customerID | 唯一 ID,不作特征。 |
| 数值/类别 | 数值 3 列 + 类别 15 列。 |
| 11 行 | 步骤 2 清洗。 |
Q:数据从哪下载?
A:运行 train.py 自动从 GitHub 下载。也可从 Kaggle Telco Churn 手动下载同名文件。
Q:SeniorCitizen 是数值还是类别?
A:虽是 0/1,但本质是「是否老年」,步骤 5 可当作数值或类别,本步先列入数值列。
Q:为何 TotalCharges 被读成字符串?
A:列里混有空格,pandas 整列按 object/str 读入。步骤 2 用 pd.to_numeric(..., errors='coerce') 修复。
1.15 本步小结
| 要点 | 内容 |
|---|---|
| 数据规模 | 7,043 行 × 21 列 |
| 流失率 | 约 26.5% |
| 数值特征 | SeniorCitizen、tenure、MonthlyCharges(3 列) |
| 脏数据 | TotalCharges 11 行空格 |
| 下一步 | 数据清洗 |
1.16 学习记录(请你填写)
- 完成日期:
- 动手任务完成情况:任务 A / B / C / D
- 我对混合类型表格数据的初印象:
- 疑问(如有):
步骤 2:数据清洗
状态 :✅ 已完成教学(报告见
reports/step2_data_cleaning.txt)
2.1 本步目标
- 将 TotalCharges 转为数值(
errors='coerce'),处理 11 行空格 - 将 Churn 编码为 0/1 整数
- 检查重复行 、SeniorCitizen 是否已是 0/1
- 验收:清洗后无缺失值 ,
y为整数 0/1
2.2 这一步在解决什么问题?
步骤 1 发现 TotalCharges 是字符串、Churn 是 Yes/No。机器学习模型需要:
- 数值列能算大小(TotalCharges 必须是 float)
- 标签是 0/1(sklearn 分类器标准输入)
数据质量决定模型上限------脏数据不洗,后面 Pipeline 会报错或学偏。
2.3 本步在整体流程中的位置
#mermaid-svg-h97Q9paVWGnEaQsD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-h97Q9paVWGnEaQsD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-h97Q9paVWGnEaQsD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-h97Q9paVWGnEaQsD .error-icon{fill:#552222;}#mermaid-svg-h97Q9paVWGnEaQsD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-h97Q9paVWGnEaQsD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-h97Q9paVWGnEaQsD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-h97Q9paVWGnEaQsD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-h97Q9paVWGnEaQsD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-h97Q9paVWGnEaQsD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-h97Q9paVWGnEaQsD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-h97Q9paVWGnEaQsD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-h97Q9paVWGnEaQsD .marker.cross{stroke:#333333;}#mermaid-svg-h97Q9paVWGnEaQsD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-h97Q9paVWGnEaQsD p{margin:0;}#mermaid-svg-h97Q9paVWGnEaQsD .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-h97Q9paVWGnEaQsD .cluster-label text{fill:#333;}#mermaid-svg-h97Q9paVWGnEaQsD .cluster-label span{color:#333;}#mermaid-svg-h97Q9paVWGnEaQsD .cluster-label span p{background-color:transparent;}#mermaid-svg-h97Q9paVWGnEaQsD .label text,#mermaid-svg-h97Q9paVWGnEaQsD span{fill:#333;color:#333;}#mermaid-svg-h97Q9paVWGnEaQsD .node rect,#mermaid-svg-h97Q9paVWGnEaQsD .node circle,#mermaid-svg-h97Q9paVWGnEaQsD .node ellipse,#mermaid-svg-h97Q9paVWGnEaQsD .node polygon,#mermaid-svg-h97Q9paVWGnEaQsD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-h97Q9paVWGnEaQsD .rough-node .label text,#mermaid-svg-h97Q9paVWGnEaQsD .node .label text,#mermaid-svg-h97Q9paVWGnEaQsD .image-shape .label,#mermaid-svg-h97Q9paVWGnEaQsD .icon-shape .label{text-anchor:middle;}#mermaid-svg-h97Q9paVWGnEaQsD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-h97Q9paVWGnEaQsD .rough-node .label,#mermaid-svg-h97Q9paVWGnEaQsD .node .label,#mermaid-svg-h97Q9paVWGnEaQsD .image-shape .label,#mermaid-svg-h97Q9paVWGnEaQsD .icon-shape .label{text-align:center;}#mermaid-svg-h97Q9paVWGnEaQsD .node.clickable{cursor:pointer;}#mermaid-svg-h97Q9paVWGnEaQsD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-h97Q9paVWGnEaQsD .arrowheadPath{fill:#333333;}#mermaid-svg-h97Q9paVWGnEaQsD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-h97Q9paVWGnEaQsD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-h97Q9paVWGnEaQsD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h97Q9paVWGnEaQsD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-h97Q9paVWGnEaQsD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h97Q9paVWGnEaQsD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-h97Q9paVWGnEaQsD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-h97Q9paVWGnEaQsD .cluster text{fill:#333;}#mermaid-svg-h97Q9paVWGnEaQsD .cluster span{color:#333;}#mermaid-svg-h97Q9paVWGnEaQsD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-h97Q9paVWGnEaQsD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-h97Q9paVWGnEaQsD rect.text{fill:none;stroke-width:0;}#mermaid-svg-h97Q9paVWGnEaQsD .icon-shape,#mermaid-svg-h97Q9paVWGnEaQsD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-h97Q9paVWGnEaQsD .icon-shape p,#mermaid-svg-h97Q9paVWGnEaQsD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-h97Q9paVWGnEaQsD .icon-shape .label rect,#mermaid-svg-h97Q9paVWGnEaQsD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-h97Q9paVWGnEaQsD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-h97Q9paVWGnEaQsD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-h97Q9paVWGnEaQsD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 步骤1 发现问题
步骤2 数据清洗
步骤3 EDA
TotalCharges 数值化
Churn 0/1
2.4 名词解释(本步首次出现)
| 名词 | 大白话 | 本步用法 |
|---|---|---|
| errors='coerce' | 转不成数字的变成 NaN,不报错 | TotalCharges 空格→NaN |
| fillna / 填充 | 用某个值补上缺失 | tenure=0 填 0 |
| 标签编码 | 把 Yes/No 变成 0/1 | Churn: No→0, Yes→1 |
| 中位数填充 | 用中间值补缺失 | 本数据集 11 行全用 0 即可 |
2.5 TotalCharges 怎么洗?(Mermaid)
#mermaid-svg-NmC3Z5iTDCot1HcA{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NmC3Z5iTDCot1HcA .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NmC3Z5iTDCot1HcA .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NmC3Z5iTDCot1HcA .error-icon{fill:#552222;}#mermaid-svg-NmC3Z5iTDCot1HcA .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NmC3Z5iTDCot1HcA .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NmC3Z5iTDCot1HcA .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NmC3Z5iTDCot1HcA .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NmC3Z5iTDCot1HcA .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NmC3Z5iTDCot1HcA .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NmC3Z5iTDCot1HcA .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NmC3Z5iTDCot1HcA .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NmC3Z5iTDCot1HcA .marker.cross{stroke:#333333;}#mermaid-svg-NmC3Z5iTDCot1HcA svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NmC3Z5iTDCot1HcA p{margin:0;}#mermaid-svg-NmC3Z5iTDCot1HcA .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NmC3Z5iTDCot1HcA .cluster-label text{fill:#333;}#mermaid-svg-NmC3Z5iTDCot1HcA .cluster-label span{color:#333;}#mermaid-svg-NmC3Z5iTDCot1HcA .cluster-label span p{background-color:transparent;}#mermaid-svg-NmC3Z5iTDCot1HcA .label text,#mermaid-svg-NmC3Z5iTDCot1HcA span{fill:#333;color:#333;}#mermaid-svg-NmC3Z5iTDCot1HcA .node rect,#mermaid-svg-NmC3Z5iTDCot1HcA .node circle,#mermaid-svg-NmC3Z5iTDCot1HcA .node ellipse,#mermaid-svg-NmC3Z5iTDCot1HcA .node polygon,#mermaid-svg-NmC3Z5iTDCot1HcA .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NmC3Z5iTDCot1HcA .rough-node .label text,#mermaid-svg-NmC3Z5iTDCot1HcA .node .label text,#mermaid-svg-NmC3Z5iTDCot1HcA .image-shape .label,#mermaid-svg-NmC3Z5iTDCot1HcA .icon-shape .label{text-anchor:middle;}#mermaid-svg-NmC3Z5iTDCot1HcA .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NmC3Z5iTDCot1HcA .rough-node .label,#mermaid-svg-NmC3Z5iTDCot1HcA .node .label,#mermaid-svg-NmC3Z5iTDCot1HcA .image-shape .label,#mermaid-svg-NmC3Z5iTDCot1HcA .icon-shape .label{text-align:center;}#mermaid-svg-NmC3Z5iTDCot1HcA .node.clickable{cursor:pointer;}#mermaid-svg-NmC3Z5iTDCot1HcA .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NmC3Z5iTDCot1HcA .arrowheadPath{fill:#333333;}#mermaid-svg-NmC3Z5iTDCot1HcA .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NmC3Z5iTDCot1HcA .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NmC3Z5iTDCot1HcA .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NmC3Z5iTDCot1HcA .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NmC3Z5iTDCot1HcA .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NmC3Z5iTDCot1HcA .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NmC3Z5iTDCot1HcA .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NmC3Z5iTDCot1HcA .cluster text{fill:#333;}#mermaid-svg-NmC3Z5iTDCot1HcA .cluster span{color:#333;}#mermaid-svg-NmC3Z5iTDCot1HcA div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NmC3Z5iTDCot1HcA .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NmC3Z5iTDCot1HcA rect.text{fill:none;stroke-width:0;}#mermaid-svg-NmC3Z5iTDCot1HcA .icon-shape,#mermaid-svg-NmC3Z5iTDCot1HcA .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NmC3Z5iTDCot1HcA .icon-shape p,#mermaid-svg-NmC3Z5iTDCot1HcA .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NmC3Z5iTDCot1HcA .icon-shape .label rect,#mermaid-svg-NmC3Z5iTDCot1HcA .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NmC3Z5iTDCot1HcA .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NmC3Z5iTDCot1HcA .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NmC3Z5iTDCot1HcA :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
TotalCharges 字符串
去空格 + to_numeric
tenure=0 且 NaN?
填 0(新用户无账单)
其余 NaN 用中位数
float64 数值列
2.6 任务清单
-
pd.to_numeric(..., errors='coerce')转换 TotalCharges - tenure=0 的 11 行填 0
- Churn 映射为 0/1 整数
- 检查重复行、SeniorCitizen
- 保存清洗报告
2.7 运行本步
powershell
cd "d:\JavaCode\机器学习\project_03_churn"
python src/train.py
2.8 实验结果(你的环境)
| 项目 | 清洗前 | 清洗后 |
|---|---|---|
| TotalCharges dtype | str | float64 |
| TotalCharges NaN | 11 行(空格) | 0(11 行填 0) |
| Churn dtype | str (Yes/No) | int64 (0/1) |
| 全表缺失值 | 0 | 0 |
| 完全重复行 | --- | 0 |
| customerID 重复 | --- | 0 |
| SeniorCitizen | --- | 0, 1 ✓ |
清洗后特征类型:
- 数值特征(4 列):SeniorCitizen、tenure、MonthlyCharges、TotalCharges
- 类别特征(15 列):gender、Contract 等
Churn 分布(不变): 0=5174(73.46%),1=1869(26.54%)
2.9 核心代码解读
(1)clean_total_charges() ------ 新用户填 0
python
out[TOTAL_CHARGES_COL] = pd.to_numeric(
out[TOTAL_CHARGES_COL].astype(str).str.strip(),
errors="coerce",
)
new_user_mask = (out["tenure"] == 0) & out[TOTAL_CHARGES_COL].isna()
out.loc[new_user_mask, TOTAL_CHARGES_COL] = 0.0
为何填 0 而不是中位数? tenure=0 表示刚入网,累计费用本应为 0;填中位数(约 1394)会歪曲事实。
(2)encode_churn_label() ------ Yes/No → 0/1
python
out[TARGET_COL] = out[TARGET_COL].map({"No": 0, "Yes": 1}).astype(int)
(3)clean_data() ------ 供后续步骤复用
后续步骤 3~9 可调用 clean_data(load_data()) 一键得到清洗后的表。
2.10 动手任务
任务 A :清洗后执行 df['TotalCharges'].describe(),看 min/max
任务 B :验证 df[df['tenure']==0]['TotalCharges'].unique() 是否全是 0
任务 C:思考:若删除 11 行而非填充,对流失率影响大吗?
任务 D:在 README 步骤 2 写下你的日期和一句话
2.11 验收标准
- 能解释 errors='coerce' 的作用
- 能说出 tenure=0 为何填 0
- 清洗后 TotalCharges 为 float、Churn 为 0/1
- 全表无缺失值
2.12 动手任务与验收标准参考答案
以下为教学标准答案,便于自学对照;
README.md学习记录仍建议用自己的话写。
动手任务答案
| 任务 | 参考答案 |
|---|---|
| A | describe() 中 min≈0 ,max 约 8000+,dtype 为 float64。 |
| B | df[df['tenure']==0]['TotalCharges'].unique() 结果为 [0.](仅 0)。 |
| C | 11/7043≈0.16%,对总体流失率 26.54% 影响可忽略;填充比删除更保留样本。 |
| D | 示例:「新用户 TotalCharges 填 0 符合业务含义。」 |
验收标准答案
| 验收项 | 参考答案 |
|---|---|
| errors='coerce' | 无法转数字的变成 NaN,便于发现脏数据再处理。 |
| tenure=0 填 0 | 新用户尚未产生账单,总费用应为 0 而非缺失。 |
| 类型正确 | TotalCharges→float64;Churn→int 0/1。 |
| 无缺失 | 清洗后 df.isnull().sum().sum()==0。 |
2.13 本步小结
| 要点 | 内容 |
|---|---|
| TotalCharges | str → float64,11 行新用户填 0 |
| Churn | Yes/No → 0/1 |
| 缺失值 | 0 |
| 数值特征 | 4 列(含 TotalCharges) |
| 下一步 | EDA 与业务洞察 |
2.14 学习记录(请你填写)
- 完成日期:
- 我对「数据质量决定上限」的理解:
- 疑问(如有):
步骤 3:EDA 与业务洞察
状态 :✅ 已完成教学(报告见
reports/step3_eda_insights.txt,图表见01_~03_PNG)
3.1 本步目标
- 计算总体流失率
- 按 Contract(合同类型)分组看流失率
- 按 tenure(在网时长)分段看流失率
- 画 2~3 张图,回答「哪类客户更容易流失」
3.2 这一步在解决什么问题?
建模前先「用眼睛看数据」------EDA 不是为画图而画图,而是驱动业务策略:
- 流失率整体多少?
- 月付 vs 年付客户谁更爱跑?
- 新客户是不是比老客户更容易流失?
这些发现会指导后面的特征工程和运营挽留策略。
3.3 名词解释(通俗版)
| 名词 | 大白话 | 本步 |
|---|---|---|
| EDA | 探索性数据分析,先摸清数据规律 | 本步核心 |
| 流失率 | 已流失客户 / 总客户 | 总体 26.54% |
| 分组聚合 | 按某列分组再算指标 | groupby Contract |
| pd.cut | 把连续数值切成几段 | tenure 按 12 月一段 |
3.4 哪类客户更容易流失?(Mermaid)
#mermaid-svg-XiUmtNQhkO3G0bdE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XiUmtNQhkO3G0bdE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XiUmtNQhkO3G0bdE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XiUmtNQhkO3G0bdE .error-icon{fill:#552222;}#mermaid-svg-XiUmtNQhkO3G0bdE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XiUmtNQhkO3G0bdE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XiUmtNQhkO3G0bdE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XiUmtNQhkO3G0bdE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XiUmtNQhkO3G0bdE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XiUmtNQhkO3G0bdE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XiUmtNQhkO3G0bdE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XiUmtNQhkO3G0bdE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XiUmtNQhkO3G0bdE .marker.cross{stroke:#333333;}#mermaid-svg-XiUmtNQhkO3G0bdE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XiUmtNQhkO3G0bdE p{margin:0;}#mermaid-svg-XiUmtNQhkO3G0bdE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XiUmtNQhkO3G0bdE .cluster-label text{fill:#333;}#mermaid-svg-XiUmtNQhkO3G0bdE .cluster-label span{color:#333;}#mermaid-svg-XiUmtNQhkO3G0bdE .cluster-label span p{background-color:transparent;}#mermaid-svg-XiUmtNQhkO3G0bdE .label text,#mermaid-svg-XiUmtNQhkO3G0bdE span{fill:#333;color:#333;}#mermaid-svg-XiUmtNQhkO3G0bdE .node rect,#mermaid-svg-XiUmtNQhkO3G0bdE .node circle,#mermaid-svg-XiUmtNQhkO3G0bdE .node ellipse,#mermaid-svg-XiUmtNQhkO3G0bdE .node polygon,#mermaid-svg-XiUmtNQhkO3G0bdE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XiUmtNQhkO3G0bdE .rough-node .label text,#mermaid-svg-XiUmtNQhkO3G0bdE .node .label text,#mermaid-svg-XiUmtNQhkO3G0bdE .image-shape .label,#mermaid-svg-XiUmtNQhkO3G0bdE .icon-shape .label{text-anchor:middle;}#mermaid-svg-XiUmtNQhkO3G0bdE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XiUmtNQhkO3G0bdE .rough-node .label,#mermaid-svg-XiUmtNQhkO3G0bdE .node .label,#mermaid-svg-XiUmtNQhkO3G0bdE .image-shape .label,#mermaid-svg-XiUmtNQhkO3G0bdE .icon-shape .label{text-align:center;}#mermaid-svg-XiUmtNQhkO3G0bdE .node.clickable{cursor:pointer;}#mermaid-svg-XiUmtNQhkO3G0bdE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XiUmtNQhkO3G0bdE .arrowheadPath{fill:#333333;}#mermaid-svg-XiUmtNQhkO3G0bdE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XiUmtNQhkO3G0bdE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XiUmtNQhkO3G0bdE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XiUmtNQhkO3G0bdE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XiUmtNQhkO3G0bdE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XiUmtNQhkO3G0bdE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XiUmtNQhkO3G0bdE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XiUmtNQhkO3G0bdE .cluster text{fill:#333;}#mermaid-svg-XiUmtNQhkO3G0bdE .cluster span{color:#333;}#mermaid-svg-XiUmtNQhkO3G0bdE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XiUmtNQhkO3G0bdE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XiUmtNQhkO3G0bdE rect.text{fill:none;stroke-width:0;}#mermaid-svg-XiUmtNQhkO3G0bdE .icon-shape,#mermaid-svg-XiUmtNQhkO3G0bdE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XiUmtNQhkO3G0bdE .icon-shape p,#mermaid-svg-XiUmtNQhkO3G0bdE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XiUmtNQhkO3G0bdE .icon-shape .label rect,#mermaid-svg-XiUmtNQhkO3G0bdE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XiUmtNQhkO3G0bdE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XiUmtNQhkO3G0bdE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XiUmtNQhkO3G0bdE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 总体流失率 26.5%
月付 42.7% 最高
一年期 11.3%
两年期 2.8% 最低
在网 0-12 月 47.4%
新客户高风险
在网 61-72 月 6.6%
老客户最稳
3.5 任务清单
- 总体流失率
- 按 Contract 分组流失率
- 按 tenure 分段流失率
- 3 张中文名图表 + 报告
3.6 运行本步
powershell
cd "d:\JavaCode\机器学习\project_03_churn"
python src/train.py
3.7 实验结果(你的环境)
总体: 7,043 客户,1,869 流失,流失率 26.54%
按合同类型:
| 合同 | 客户数 | 流失率 |
|---|---|---|
| Month-to-month(月付) | 3,875 | 42.71% |
| One year(一年期) | 1,473 | 11.27% |
| Two year(两年期) | 1,695 | 2.83% |
按在网时长:
| 分段 | 客户数 | 流失率 |
|---|---|---|
| 0-12月(新) | 2,186 | 47.44% |
| 13-24月 | 1,024 | 28.71% |
| 25-36月 | 832 | 21.63% |
| 37-48月 | 762 | 19.03% |
| 49-60月 | 832 | 14.42% |
| 61-72月 | 1,407 | 6.61% |
业务回答:
- 月付客户最容易流失(无长期绑定,随时可退)
- 新客户 (0-12 月)流失率近 47%,需 onboarding 挽留
- 两年期 + 老客户最稳定,维护满意度即可
3.8 输出文件
| 文件 | 说明 |
|---|---|
reports/step3_eda_insights.txt |
EDA 业务洞察报告 |
reports/figures/01_流失标签分布.png |
流失/未流失人数 |
reports/figures/02_合同类型流失率对比.png |
三种合同流失率 |
reports/figures/03_在网时长分段流失率.png |
tenure 分段流失率 |
3.9 动手任务
任务 A:为何月付流失率是两年期的 15 倍?
任务 B:若你是运营,会先挽留哪两类客户?
任务 C:打开图 03,流失率随在网时长如何变化?
3.10 验收标准
- 能说出总体流失率
- 能指出最高/最低流失率的合同类型
- 能解释新客户为何高风险
- 能读懂 3 张 EDA 图
3.11 动手任务与验收标准参考答案
以下为教学标准答案,便于自学对照;
README.md学习记录仍建议用自己的话写。
动手任务答案
| 任务 | 参考答案 |
|---|---|
| A | 月付 42.7% vs 两年期 2.8% ,约 15 倍;月付无长期绑定、切换成本低。 |
| B | 月付合同客户 + 在网 0~12 月新客(流失率约 47.4%)。 |
| C | 图 03:0~12 月流失率最高(≈47%),随 tenure 增加整体下降,61~72 月仅约 6.6%。 |
验收标准答案
| 验收项 | 参考答案 |
|---|---|
| 总体流失率 | 26.54%。 |
| 最高/最低合同 | 最高:Month-to-month 42.7% ;最低:Two year 2.8%。 |
| 新客户高风险 | 刚入网尚未形成习惯与沉没成本,竞品拉走更容易。 |
| 三张 EDA 图 | 01 流失标签分布;02 合同类型流失率;03 在网时长分段流失率。 |
3.12 本步小结
| 要点 | 内容 |
|---|---|
| 总体流失率 | 26.54% |
| 最高风险 | 月付 42.7% + 新用户 47.4% |
| 最低风险 | 两年期 2.8% ,61-72 月 6.6% |
| 下一步 | 特征标签分离 + 分层划分 |
3.13 学习记录(请你填写)
- 完成日期:
- 我印象最深的发现:
- 疑问(如有):
步骤 4:特征与标签分离 + 分层划分
状态 :✅ 已完成教学(报告见
reports/step4_train_test_split.txt,图表见04_~05_PNG)
4.1 本步目标
- X 去掉
customerID和Churn;y 为Churn(0/1) train_test_split(stratify=y, test_size=0.2, random_state=42)- 验证训练/测试/全集流失占比一致
4.2 这一步在解决什么问题?
模型需要「考题」(X)和「标准答案」(y)分开。还要把数据切成:
- 训练集:用来教模型
- 测试集:用来考试(模拟没见过的新客户)
用 stratify 分层抽样,保证训练集和测试集里的流失比例与总体一致------否则测试分数可能失真。
4.3 名词解释(通俗版)
| 名词 | 大白话 | 本步 |
|---|---|---|
| X(特征) | 用来预测的输入 | 19 列(无 ID、无标签) |
| y(标签) | 要预测的目标 | Churn 0/1 |
| train_test_split | 随机切分训练/测试 | 80% / 20% |
| stratify | 分层抽样,保持类别比例 | stratify=y |
4.4 分层划分示意(Mermaid)
#mermaid-svg-DTD9tjb36vf5Toq4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DTD9tjb36vf5Toq4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DTD9tjb36vf5Toq4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DTD9tjb36vf5Toq4 .error-icon{fill:#552222;}#mermaid-svg-DTD9tjb36vf5Toq4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DTD9tjb36vf5Toq4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DTD9tjb36vf5Toq4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DTD9tjb36vf5Toq4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DTD9tjb36vf5Toq4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DTD9tjb36vf5Toq4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DTD9tjb36vf5Toq4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DTD9tjb36vf5Toq4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DTD9tjb36vf5Toq4 .marker.cross{stroke:#333333;}#mermaid-svg-DTD9tjb36vf5Toq4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DTD9tjb36vf5Toq4 p{margin:0;}#mermaid-svg-DTD9tjb36vf5Toq4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DTD9tjb36vf5Toq4 .cluster-label text{fill:#333;}#mermaid-svg-DTD9tjb36vf5Toq4 .cluster-label span{color:#333;}#mermaid-svg-DTD9tjb36vf5Toq4 .cluster-label span p{background-color:transparent;}#mermaid-svg-DTD9tjb36vf5Toq4 .label text,#mermaid-svg-DTD9tjb36vf5Toq4 span{fill:#333;color:#333;}#mermaid-svg-DTD9tjb36vf5Toq4 .node rect,#mermaid-svg-DTD9tjb36vf5Toq4 .node circle,#mermaid-svg-DTD9tjb36vf5Toq4 .node ellipse,#mermaid-svg-DTD9tjb36vf5Toq4 .node polygon,#mermaid-svg-DTD9tjb36vf5Toq4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DTD9tjb36vf5Toq4 .rough-node .label text,#mermaid-svg-DTD9tjb36vf5Toq4 .node .label text,#mermaid-svg-DTD9tjb36vf5Toq4 .image-shape .label,#mermaid-svg-DTD9tjb36vf5Toq4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-DTD9tjb36vf5Toq4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DTD9tjb36vf5Toq4 .rough-node .label,#mermaid-svg-DTD9tjb36vf5Toq4 .node .label,#mermaid-svg-DTD9tjb36vf5Toq4 .image-shape .label,#mermaid-svg-DTD9tjb36vf5Toq4 .icon-shape .label{text-align:center;}#mermaid-svg-DTD9tjb36vf5Toq4 .node.clickable{cursor:pointer;}#mermaid-svg-DTD9tjb36vf5Toq4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DTD9tjb36vf5Toq4 .arrowheadPath{fill:#333333;}#mermaid-svg-DTD9tjb36vf5Toq4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DTD9tjb36vf5Toq4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DTD9tjb36vf5Toq4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DTD9tjb36vf5Toq4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DTD9tjb36vf5Toq4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DTD9tjb36vf5Toq4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DTD9tjb36vf5Toq4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DTD9tjb36vf5Toq4 .cluster text{fill:#333;}#mermaid-svg-DTD9tjb36vf5Toq4 .cluster span{color:#333;}#mermaid-svg-DTD9tjb36vf5Toq4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DTD9tjb36vf5Toq4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DTD9tjb36vf5Toq4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-DTD9tjb36vf5Toq4 .icon-shape,#mermaid-svg-DTD9tjb36vf5Toq4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DTD9tjb36vf5Toq4 .icon-shape p,#mermaid-svg-DTD9tjb36vf5Toq4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DTD9tjb36vf5Toq4 .icon-shape .label rect,#mermaid-svg-DTD9tjb36vf5Toq4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DTD9tjb36vf5Toq4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DTD9tjb36vf5Toq4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DTD9tjb36vf5Toq4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 全集 7043 人 流失 26.54%
训练集 5634 80%
测试集 1409 20%
流失占比 26.54%
流失占比 26.54%
4.5 任务清单
-
split_features_target()分离 X/y -
stratify=y分层划分 - 打印流失占比对比表
- 保存 2 张中文图表 + 报告
4.6 运行本步
powershell
cd "d:\JavaCode\机器学习\project_03_churn"
python src/train.py
4.7 实验结果(你的环境)
| 数据集 | 样本数 | 流失数 | 流失占比 |
|---|---|---|---|
| 全集 | 7,043 | 1,869 | 26.5370% |
| 训练集 | 5,634 | 1,495 | 26.5353% |
| 测试集 | 1,409 | 374 | 26.5436% |
特征矩阵: X_train (5634, 19),X_test (1409, 19)
三者流失占比几乎相同 ------ stratify 生效。
4.8 输出文件
| 文件 | 说明 |
|---|---|
reports/step4_train_test_split.txt |
划分报告 |
reports/figures/04_训练测试集数量对比.png |
80/20 数量 |
reports/figures/05_分层抽样流失占比对比.png |
流失占比对比 |
4.9 重要提醒
- 预处理只能 fit 训练集 :Imputer、Scaler、OneHot 的统计量来自
X_train,再 transform 测试集 - 测试集不能参与训练 :
y_test只在最终评估时用 - 与项目二相同:stratify 在不平衡/分类场景都适用
4.10 动手任务
任务 A:不用 stratify 再划分一次,看测试集流失占比偏差多少?
任务 B:19 个特征里,哪些是数值、哪些是类别?(步骤 2 已分过)
任务 C:在 README 写下 train/test 样本数
4.11 验收标准
- 能说出 X、y 各是什么
- 能解释 stratify 的作用
- 训练/测试流失占比与总体接近
- 知道预处理不能先 fit 全量数据
4.12 本步小结
4.12 动手任务与验收标准参考答案
以下为教学标准答案,便于自学对照;
README.md学习记录仍建议用自己的话写。
动手任务答案
| 任务 | 参考答案 |
|---|---|
| A | 无 stratify 测试流失占比可能偏 1~2 点;有则均 26.54%。 |
| B | 数值 4 列 + 类别 15 列 = 19 特征。 |
| C | 5634 / 1409。 |
验收标准答案
| 验收项 | 参考答案 |
|---|---|
| X/y | X 19 列;y 为 Churn。 |
| stratify | 保持流失比例。 |
| 占比接近 | 均 26.54%。 |
| 不能全量 fit | 会泄露测试集信息。 |
| 要点 | 内容 |
|---|---|
| 特征数 | 19 列(剔除 customerID) |
| 训练/测试 | 5634 / 1409(80/20) |
| 流失占比 | 三者均约 26.54% |
| 下一步 | 构建预处理 Pipeline |
4.13 学习记录(请你填写)
- 完成日期:
- 我对 stratify 的理解:
- 疑问(如有):
步骤 5:构建预处理 Pipeline
状态 :✅ 已完成教学(报告见
reports/step5_preprocessor.txt,图表见06_PNG)
5.1 本步目标
- 数值列 :
SimpleImputer(median)+StandardScaler - 类别列 :
SimpleImputer(most_frequent)+OneHotEncoder - 用
ColumnTransformer组合 - 演示
preprocessor.fit_transform(X_train)的 shape
5.2 这一步在解决什么问题?
原始数据里数值和类别混在一起,模型不能直接吃字符串。需要一条预处理流水线:
- 数值:填缺失 + 缩放到相近量纲
- 类别:填缺失 + One-Hot 变成 0/1 列
且统计量(中位数、众数、均值方差)只能来自训练集,否则会泄露测试集信息。
5.3 名词解释(通俗版)
| 名词 | 大白话 | 本步 |
|---|---|---|
| SimpleImputer | 自动填缺失值 | 数值用中位数,类别用众数 |
| StandardScaler | 数值标准化到均值0方差1 | 4 个数值列 |
| OneHotEncoder | 每个类别取值变一列 0/1 | 15 个类别列展开 |
| ColumnTransformer | 对不同列用不同变换 | num + cat 两路 |
| Label Encoding | 把类别编成 0,1,2... | 不用(会引入虚假顺序) |
5.4 预处理流程(Mermaid)
#mermaid-svg-D2py7V1B39ye4oVT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-D2py7V1B39ye4oVT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-D2py7V1B39ye4oVT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-D2py7V1B39ye4oVT .error-icon{fill:#552222;}#mermaid-svg-D2py7V1B39ye4oVT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-D2py7V1B39ye4oVT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-D2py7V1B39ye4oVT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-D2py7V1B39ye4oVT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-D2py7V1B39ye4oVT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-D2py7V1B39ye4oVT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-D2py7V1B39ye4oVT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-D2py7V1B39ye4oVT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-D2py7V1B39ye4oVT .marker.cross{stroke:#333333;}#mermaid-svg-D2py7V1B39ye4oVT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-D2py7V1B39ye4oVT p{margin:0;}#mermaid-svg-D2py7V1B39ye4oVT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-D2py7V1B39ye4oVT .cluster-label text{fill:#333;}#mermaid-svg-D2py7V1B39ye4oVT .cluster-label span{color:#333;}#mermaid-svg-D2py7V1B39ye4oVT .cluster-label span p{background-color:transparent;}#mermaid-svg-D2py7V1B39ye4oVT .label text,#mermaid-svg-D2py7V1B39ye4oVT span{fill:#333;color:#333;}#mermaid-svg-D2py7V1B39ye4oVT .node rect,#mermaid-svg-D2py7V1B39ye4oVT .node circle,#mermaid-svg-D2py7V1B39ye4oVT .node ellipse,#mermaid-svg-D2py7V1B39ye4oVT .node polygon,#mermaid-svg-D2py7V1B39ye4oVT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-D2py7V1B39ye4oVT .rough-node .label text,#mermaid-svg-D2py7V1B39ye4oVT .node .label text,#mermaid-svg-D2py7V1B39ye4oVT .image-shape .label,#mermaid-svg-D2py7V1B39ye4oVT .icon-shape .label{text-anchor:middle;}#mermaid-svg-D2py7V1B39ye4oVT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-D2py7V1B39ye4oVT .rough-node .label,#mermaid-svg-D2py7V1B39ye4oVT .node .label,#mermaid-svg-D2py7V1B39ye4oVT .image-shape .label,#mermaid-svg-D2py7V1B39ye4oVT .icon-shape .label{text-align:center;}#mermaid-svg-D2py7V1B39ye4oVT .node.clickable{cursor:pointer;}#mermaid-svg-D2py7V1B39ye4oVT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-D2py7V1B39ye4oVT .arrowheadPath{fill:#333333;}#mermaid-svg-D2py7V1B39ye4oVT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-D2py7V1B39ye4oVT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-D2py7V1B39ye4oVT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D2py7V1B39ye4oVT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-D2py7V1B39ye4oVT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D2py7V1B39ye4oVT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-D2py7V1B39ye4oVT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-D2py7V1B39ye4oVT .cluster text{fill:#333;}#mermaid-svg-D2py7V1B39ye4oVT .cluster span{color:#333;}#mermaid-svg-D2py7V1B39ye4oVT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-D2py7V1B39ye4oVT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-D2py7V1B39ye4oVT rect.text{fill:none;stroke-width:0;}#mermaid-svg-D2py7V1B39ye4oVT .icon-shape,#mermaid-svg-D2py7V1B39ye4oVT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-D2py7V1B39ye4oVT .icon-shape p,#mermaid-svg-D2py7V1B39ye4oVT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-D2py7V1B39ye4oVT .icon-shape .label rect,#mermaid-svg-D2py7V1B39ye4oVT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-D2py7V1B39ye4oVT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-D2py7V1B39ye4oVT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-D2py7V1B39ye4oVT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} X_train 19 列
ColumnTransformer
数值 4 列: Imputer + Scaler
类别 15 列: Imputer + OneHot
合并 45 列矩阵
5.5 任务清单
- 数值/类别列分离
- 构建 ColumnTransformer
- 仅 X_train fit,train/test transform
- 验证无 NaN、shape 合理
- 保存报告与列数对比图
5.6 运行本步
powershell
cd "d:\JavaCode\机器学习\project_03_churn"
python src/train.py
5.7 实验结果(你的环境)
| 项目 | 数值 |
|---|---|
| 数值列 | 4:SeniorCitizen, tenure, MonthlyCharges, TotalCharges |
| 类别列 | 15:gender, Contract 等 |
| X_train 原始 shape | (5634, 19) |
| X_train 变换后 shape | (5634, 45) |
| X_test 变换后 shape | (1409, 45) |
| 变换后 NaN | 0 |
为何 19 → 45? 15 个类别列经 One-Hot 展开成约 41 列,加上 4 个数值列 ≈ 45 列。
特征名示例: num__tenure、cat__Contract_Month-to-month
5.8 输出文件
| 文件 | 说明 |
|---|---|
reports/step5_preprocessor.txt |
预处理配置与全部特征名 |
reports/figures/06_预处理前后特征列数对比.png |
19 vs 45 列 |
5.9 为何 One-Hot 不用 Label Encoding?
Contract 有 Month-to-month / One year / Two year,没有大小顺序。若编成 0,1,2,模型会误以为「两年期 > 月付」------这是虚假关系。One-Hot 每类独立一列,更合理。
5.10 动手任务
任务 A :打开 step5_preprocessor.txt,数一下 cat__Contract_ 有几列?
任务 B:若只在全量 X 上 fit 再划分,会泄露什么信息?
任务 C:SeniorCitizen 是 0/1,为何仍走数值流水线而非 One-Hot?
5.11 验收标准
- 能说出数值/类别各自的处理步骤
- 能解释为何只在 X_train 上 fit
- 变换后无 NaN,列数从 19 增至 45
- 能区分 One-Hot 与 Label Encoding
5.12 本步小结
5.12 动手任务与验收标准参考答案
以下为教学标准答案,便于自学对照;
README.md学习记录仍建议用自己的话写。
动手任务答案
| 任务 | 参考答案 |
|---|---|
| A | cat__Contract_ 共 3 列。 |
| B | 泄露测试集类别/均值分布。 |
| C | 0/1 已数值化,走数值流水线可少扩维(也可 One-Hot,本项目 ColumnTransformer 对类别用 One-Hot)。 |
验收标准答案
| 验收项 | 参考答案 |
|---|---|
| 数值/类别处理 | Imputer+Scaler / Imputer+OneHot。 |
| 只 fit 训练集 | 测试 transform。 |
| 19→45 | 无 NaN。 |
| One-Hot vs Label | One-Hot 无虚假顺序;Label 编码有大小关系风险。 |
| 要点 | 内容 |
|---|---|
| 预处理器 | ColumnTransformer |
| shape | 19 → 45 列 |
| NaN | 0 |
| 关键原则 | 只 fit 训练集 |
| 下一步 | Pipeline + 逻辑回归(步骤 6) |
5.13 学习记录(请你填写)
- 完成日期:
- 我对 One-Hot 的理解:
- 疑问(如有):
步骤 6:模型 + Pipeline 一体训练
状态 :✅ 已完成教学(报告见
reports/step6_lr_pipeline.txt,图表见07_PNG)
6.1 本步目标
Pipeline([('preprocessor', ...), ('classifier', LogisticRegression)])- 训练集
fit,测试集predict - 输出 classification_report 和混淆矩阵
6.2 这一步在解决什么问题?
步骤 5 的预处理和模型是分开的。若手工写:
python
preprocessor.fit(X_train)
X_train_t = preprocessor.transform(X_train)
model.fit(X_train_t, y_train)
# 若不小心用全量 X fit preprocessor → 泄露!
Pipeline 把预处理和模型串成一条链,pipeline.fit(X_train, y_train) 一步完成,内部保证顺序正确、无泄露。
6.3 名词解释(通俗版)
| 名词 | 大白话 | 本步 |
|---|---|---|
| Pipeline | 多步操作串成一条流水线 | preprocessor + LR |
| 一步 fit | 一次调用完成预处理+训练 | pipeline.fit |
| 流失 Recall | 真流失中被找出的比例 | 测试 0.56 |
| 流失 Precision | 预测流失中真是流失的比例 | 测试 0.66 |
6.4 Pipeline 如何防泄露?(Mermaid)
#mermaid-svg-TK0UFMPJKWE8tS22{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TK0UFMPJKWE8tS22 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TK0UFMPJKWE8tS22 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TK0UFMPJKWE8tS22 .error-icon{fill:#552222;}#mermaid-svg-TK0UFMPJKWE8tS22 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TK0UFMPJKWE8tS22 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TK0UFMPJKWE8tS22 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TK0UFMPJKWE8tS22 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TK0UFMPJKWE8tS22 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TK0UFMPJKWE8tS22 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TK0UFMPJKWE8tS22 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TK0UFMPJKWE8tS22 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TK0UFMPJKWE8tS22 .marker.cross{stroke:#333333;}#mermaid-svg-TK0UFMPJKWE8tS22 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TK0UFMPJKWE8tS22 p{margin:0;}#mermaid-svg-TK0UFMPJKWE8tS22 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TK0UFMPJKWE8tS22 .cluster-label text{fill:#333;}#mermaid-svg-TK0UFMPJKWE8tS22 .cluster-label span{color:#333;}#mermaid-svg-TK0UFMPJKWE8tS22 .cluster-label span p{background-color:transparent;}#mermaid-svg-TK0UFMPJKWE8tS22 .label text,#mermaid-svg-TK0UFMPJKWE8tS22 span{fill:#333;color:#333;}#mermaid-svg-TK0UFMPJKWE8tS22 .node rect,#mermaid-svg-TK0UFMPJKWE8tS22 .node circle,#mermaid-svg-TK0UFMPJKWE8tS22 .node ellipse,#mermaid-svg-TK0UFMPJKWE8tS22 .node polygon,#mermaid-svg-TK0UFMPJKWE8tS22 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TK0UFMPJKWE8tS22 .rough-node .label text,#mermaid-svg-TK0UFMPJKWE8tS22 .node .label text,#mermaid-svg-TK0UFMPJKWE8tS22 .image-shape .label,#mermaid-svg-TK0UFMPJKWE8tS22 .icon-shape .label{text-anchor:middle;}#mermaid-svg-TK0UFMPJKWE8tS22 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TK0UFMPJKWE8tS22 .rough-node .label,#mermaid-svg-TK0UFMPJKWE8tS22 .node .label,#mermaid-svg-TK0UFMPJKWE8tS22 .image-shape .label,#mermaid-svg-TK0UFMPJKWE8tS22 .icon-shape .label{text-align:center;}#mermaid-svg-TK0UFMPJKWE8tS22 .node.clickable{cursor:pointer;}#mermaid-svg-TK0UFMPJKWE8tS22 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TK0UFMPJKWE8tS22 .arrowheadPath{fill:#333333;}#mermaid-svg-TK0UFMPJKWE8tS22 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TK0UFMPJKWE8tS22 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TK0UFMPJKWE8tS22 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TK0UFMPJKWE8tS22 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TK0UFMPJKWE8tS22 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TK0UFMPJKWE8tS22 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TK0UFMPJKWE8tS22 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TK0UFMPJKWE8tS22 .cluster text{fill:#333;}#mermaid-svg-TK0UFMPJKWE8tS22 .cluster span{color:#333;}#mermaid-svg-TK0UFMPJKWE8tS22 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TK0UFMPJKWE8tS22 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TK0UFMPJKWE8tS22 rect.text{fill:none;stroke-width:0;}#mermaid-svg-TK0UFMPJKWE8tS22 .icon-shape,#mermaid-svg-TK0UFMPJKWE8tS22 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TK0UFMPJKWE8tS22 .icon-shape p,#mermaid-svg-TK0UFMPJKWE8tS22 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TK0UFMPJKWE8tS22 .icon-shape .label rect,#mermaid-svg-TK0UFMPJKWE8tS22 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TK0UFMPJKWE8tS22 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TK0UFMPJKWE8tS22 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TK0UFMPJKWE8tS22 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} pipeline.fit(X_train)
preprocessor 学训练集统计量
classifier 学训练集标签
pipeline.predict(X_test)
preprocessor.transform 用训练参数
classifier.predict
6.5 任务清单
- 构建 Pipeline(preprocessor + LogisticRegression)
- fit / predict
- classification_report + 混淆矩阵
- 保存报告与中文混淆矩阵图
6.6 运行本步
powershell
cd "d:\JavaCode\机器学习\project_03_churn"
python src/train.py
6.7 实验结果(你的环境)
| 指标(测试集) | 数值 |
|---|---|
| Accuracy | 0.8055 |
| 流失 Precision | 0.6572 |
| 流失 Recall | 0.5588 |
| 流失 F1 | 0.6040 |
混淆矩阵: TN=926,FP=109,FN=165,TP=209
通俗解读:
- 测试集 374 名真流失客户,模型抓住 209 人(Recall 56%),漏掉 165 人
- 预测为流失的客户中约 66% 真是流失(Precision)
- 训练/测试 Accuracy 接近(0.806 vs 0.806),无明显过拟合
6.8 输出文件
| 文件 | 说明 |
|---|---|
reports/step6_lr_pipeline.txt |
LR Pipeline 评估报告 |
reports/figures/07_逻辑回归Pipeline混淆矩阵.png |
测试集混淆矩阵 |
6.9 核心代码
python
pipeline = Pipeline([
("preprocessor", preprocessor),
("classifier", LogisticRegression(max_iter=1000)),
])
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
6.10 动手任务
任务 A:FN=165 表示什么?对运营意味着什么?
任务 B:若更怕漏掉流失客户,Recall 和 Precision 哪个更要提高?
任务 C:对比步骤 3 EDA:月付客户流失率 42%,模型 Recall 56% 算合理吗?
6.11 验收标准
- 能解释 Pipeline 为何能防泄露
- 能读懂 classification_report
- 能解释混淆矩阵四格含义
- Pipeline 一键 fit/predict 跑通
6.12 本步小结
6.12 动手任务与验收标准参考答案
以下为教学标准答案,便于自学对照;
README.md学习记录仍建议用自己的话写。
动手任务答案
| 任务 | 参考答案 |
|---|---|
| A | FN=165:165 真流失被判未流失,运营漏挽留。 |
| B | 怕漏流失应提高 Recall。 |
| C | 合理:全测试集 Recall 56%,月付子群 EDA 更高,模型部分捕捉。 |
验收标准答案
| 验收项 | 参考答案 |
|---|---|
| Pipeline 防泄露 | 预处理+模型一体 fit/predict。 |
| classification_report | 看流失类 P/R/F1。 |
| 混淆矩阵 | TN/FP/FN/TP。 |
| 跑通 | Accuracy 0.8055,流失 F1 0.6040。 |
| 要点 | 内容 |
|---|---|
| 模型 | LogisticRegression + 预处理 Pipeline |
| 测试 Accuracy | 0.8055 |
| 流失 F1 | 0.6040 |
| 下一步 | 多模型对比(步骤 7) |
6.13 学习记录(请你填写)
- 完成日期:
- 我对 Pipeline 的理解:
- 疑问(如有):
步骤 7:多模型对比
状态 :✅ 已完成教学(报告见
reports/step7_model_comparison.txt,图表见08_~09_PNG)
7.1 本步目标
- 同样 Pipeline 结构,替换分类器为 LR / 随机森林 / HistGBR
- 对比 Accuracy、流失 F1、ROC-AUC
- 选定最佳模型
7.2 这一步在解决什么问题?
步骤 6 只试了逻辑回归。不同模型「形状」不同:
- LR:线性边界,快、可解释
- 随机森林:多棵树投票,抓非线性
- HistGBR:梯度提升树,常很强
公平对比的关键:同一个 preprocessor,只换 classifier。
7.3 实验结果(你的环境)
| 模型 | Accuracy | 流失 F1 | ROC-AUC |
|---|---|---|---|
| 逻辑回归 | 0.8055 | 0.6040 | 0.8421 |
| 随机森林 | 0.7864 | 0.5474 | 0.8185 |
| HistGBR | 0.7906 | 0.5731 | 0.8328 |
最佳模型(流失 F1):逻辑回归
通俗解读: 本数据集上 LR 反而最好------说明线性关系已够用,或树模型略过拟合。不是「越复杂越好」,要以测试指标为准。
7.4 输出文件
| 文件 | 说明 |
|---|---|
reports/step7_model_comparison.txt |
三模型对比报告 |
reports/figures/08_三模型测试集指标对比.png |
指标分组条形图 |
reports/figures/09_最佳模型混淆矩阵.png |
LR 混淆矩阵 |
7.5 本步小结
| 要点 | 内容 |
|---|---|
| 最佳模型 | 逻辑回归 |
| 测试 F1 | 0.6040 |
| ROC-AUC | 0.8421 |
| 教训 | 树模型不一定赢,需实测 |
| 下一步 | 特征重要性(步骤 8) |
7.6 学习记录(请你填写)
7.6 动手任务与验收标准参考答案
以下为教学标准答案,便于自学对照;
README.md学习记录仍建议用自己的话写。
动手任务答案
| 任务 | 参考答案 |
|---|---|
| (思考) | LR F1 0.6040 最高;树未超越因线性已够用。 |
| (思考) | 选 LR 因 F1/AUC 最佳且可解释。 |
| (思考) | 复杂模型不一定更好。 |
验收标准答案
| 验收项 | 参考答案 |
|---|---|
| 最佳模型 | 逻辑回归。 |
| 三模型 F1 | 0.6040 / 0.5474 / 0.5731。 |
| ROC-AUC | LR 0.8421。 |
| 教训 | 以测试指标选型。 |
- 完成日期:
- 我对最佳模型的看法:
- 疑问(如有):
步骤 8:特征重要性与可解释性
状态 :✅ 已完成教学(报告见
reports/step8_feature_importance.txt,图表见10_~11_PNG)
8.1 本步目标
- 逻辑回归:系数绝对值 Top 10(最佳模型)
- 随机森林 :
feature_importances_Top 10(对比) - 结合步骤 3 EDA,回答「模型认为什么导致流失」
8.2 这一步在解决什么问题?
模型不是黑箱就完事------运营需要知道为什么预测某客户会流失,才能制定策略(推长期合同?降月费?)。
但要牢记:相关 ≠ 因果 。系数/importance 用于生成假设,不是定论。
8.3 名词解释
| 名词 | 大白话 |
|---|---|
| LR 系数 | 正=促流失,负=抑流失(在标准化特征空间) |
| feature_importances_ | 树模型认为该特征对分裂贡献多大 |
| One-Hot 后特征名 | 如 cat__Contract_Month-to-month |
8.4 逻辑回归 Top 10(你的环境)
| 排名 | 特征 | 系数 | 解读 |
|---|---|---|---|
| 1 | 在网时长 | -1.26 | 越久越不易流失 ✓ |
| 2 | 合同_两年 | -0.78 | 长期合同抑流失 ✓ |
| 3 | 宽带_DSL | -0.65 | DSL 相对稳 |
| 4 | 宽带_光纤 | +0.63 | 光纤用户更易流失?需业务验证 |
| 5 | 月费 | -0.59 | (标准化后方向需结合 One-Hot 一起看) |
| 6 | 合同_月付 | +0.58 | 月付促流失 ✓ 与 EDA 一致 |
| 7 | 总费用 | +0.54 | 累计费用高与流失相关 |
8.5 随机森林 Top 5
| 排名 | 特征 | 重要性 |
|---|---|---|
| 1 | 总费用 | 0.160 |
| 2 | 在网时长 | 0.140 |
| 3 | 月费 | 0.137 |
| 4 | 合同_月付 | 0.050 |
| 5 | 无网络安全服务 | 0.033 |
8.6 与 EDA 对照
#mermaid-svg-TFDUzQ0p15Np7wGp{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TFDUzQ0p15Np7wGp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TFDUzQ0p15Np7wGp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TFDUzQ0p15Np7wGp .error-icon{fill:#552222;}#mermaid-svg-TFDUzQ0p15Np7wGp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TFDUzQ0p15Np7wGp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TFDUzQ0p15Np7wGp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TFDUzQ0p15Np7wGp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TFDUzQ0p15Np7wGp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TFDUzQ0p15Np7wGp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TFDUzQ0p15Np7wGp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TFDUzQ0p15Np7wGp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TFDUzQ0p15Np7wGp .marker.cross{stroke:#333333;}#mermaid-svg-TFDUzQ0p15Np7wGp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TFDUzQ0p15Np7wGp p{margin:0;}#mermaid-svg-TFDUzQ0p15Np7wGp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TFDUzQ0p15Np7wGp .cluster-label text{fill:#333;}#mermaid-svg-TFDUzQ0p15Np7wGp .cluster-label span{color:#333;}#mermaid-svg-TFDUzQ0p15Np7wGp .cluster-label span p{background-color:transparent;}#mermaid-svg-TFDUzQ0p15Np7wGp .label text,#mermaid-svg-TFDUzQ0p15Np7wGp span{fill:#333;color:#333;}#mermaid-svg-TFDUzQ0p15Np7wGp .node rect,#mermaid-svg-TFDUzQ0p15Np7wGp .node circle,#mermaid-svg-TFDUzQ0p15Np7wGp .node ellipse,#mermaid-svg-TFDUzQ0p15Np7wGp .node polygon,#mermaid-svg-TFDUzQ0p15Np7wGp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TFDUzQ0p15Np7wGp .rough-node .label text,#mermaid-svg-TFDUzQ0p15Np7wGp .node .label text,#mermaid-svg-TFDUzQ0p15Np7wGp .image-shape .label,#mermaid-svg-TFDUzQ0p15Np7wGp .icon-shape .label{text-anchor:middle;}#mermaid-svg-TFDUzQ0p15Np7wGp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TFDUzQ0p15Np7wGp .rough-node .label,#mermaid-svg-TFDUzQ0p15Np7wGp .node .label,#mermaid-svg-TFDUzQ0p15Np7wGp .image-shape .label,#mermaid-svg-TFDUzQ0p15Np7wGp .icon-shape .label{text-align:center;}#mermaid-svg-TFDUzQ0p15Np7wGp .node.clickable{cursor:pointer;}#mermaid-svg-TFDUzQ0p15Np7wGp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TFDUzQ0p15Np7wGp .arrowheadPath{fill:#333333;}#mermaid-svg-TFDUzQ0p15Np7wGp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TFDUzQ0p15Np7wGp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TFDUzQ0p15Np7wGp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TFDUzQ0p15Np7wGp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TFDUzQ0p15Np7wGp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TFDUzQ0p15Np7wGp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TFDUzQ0p15Np7wGp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TFDUzQ0p15Np7wGp .cluster text{fill:#333;}#mermaid-svg-TFDUzQ0p15Np7wGp .cluster span{color:#333;}#mermaid-svg-TFDUzQ0p15Np7wGp div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TFDUzQ0p15Np7wGp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TFDUzQ0p15Np7wGp rect.text{fill:none;stroke-width:0;}#mermaid-svg-TFDUzQ0p15Np7wGp .icon-shape,#mermaid-svg-TFDUzQ0p15Np7wGp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TFDUzQ0p15Np7wGp .icon-shape p,#mermaid-svg-TFDUzQ0p15Np7wGp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TFDUzQ0p15Np7wGp .icon-shape .label rect,#mermaid-svg-TFDUzQ0p15Np7wGp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TFDUzQ0p15Np7wGp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TFDUzQ0p15Np7wGp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TFDUzQ0p15Np7wGp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} EDA: 月付流失 42.7%
模型: 合同_月付 Top
EDA: 新客流失 47%
模型: 在网时长 Top
EDA: 两年期 2.8%
模型: 合同_两年 负系数
结论: Top 特征与步骤 3 业务洞察高度一致------模型没有「胡说」。
8.7 输出文件
| 文件 | 说明 |
|---|---|
reports/step8_feature_importance.txt |
LR + RF Top 10 与 EDA 对照 |
reports/figures/10_逻辑回归系数绝对值Top10.png |
红=促流失,蓝=抑流失 |
reports/figures/11_随机森林特征重要性Top10.png |
树模型重要性 |
8.8 本步小结
| 要点 | 内容 |
|---|---|
| LR Top1 | 在网时长( |
| RF Top1 | 总费用(imp=0.16) |
| 与 EDA | 月付、tenure 均 Top,一致 |
| 下一步 | 项目三全部完成 |
8.9 学习记录(请你填写)
8.10 动手任务与验收标准参考答案
以下为教学标准答案,便于自学对照;
README.md学习记录仍建议用自己的话写。
动手任务答案
| 任务 | 参考答案 |
|---|---|
| (思考) | LR Top1 在网时长;RF Top1 总费用。 |
| (思考) | 与 EDA 月付/tenure 一致。 |
| (思考) | 相关≠因果,用于假设。 |
验收标准答案
| 验收项 | 参考答案 |
|---|---|
| LR 系数 | 负=抑流失,正=促流失。 |
| RF 重要性 | 总费用、tenure、月费 Top。 |
| 与 EDA | 高度一致。 |
| 注意 | 解释不等于因果。 |
- 完成日期:
- 我觉得最合理的 Top 特征:
- 疑问(如有):
步骤 9:模拟运营应用
状态 :✅ 已完成教学(报告见
reports/final_project_summary.txt,模型见models/churn_best.pkl)
9.1 本步目标
- 在测试集上用
predict_proba得到每位客户的流失概率 - 设定运营规则:概率 ≥ 0.6 进入挽留名单
- 假设挽留成功率 30%,估算可挽回多少客户
- 用
joblib保存完整 Pipeline + 阈值 + 元数据 - 写出项目 3 个关键教训与全部步骤回顾
9.2 这一步在解决什么问题?
前面步骤回答了「模型准不准、哪些特征重要」。运营团队更关心:
- 给谁打电话/发优惠券?(不是给所有人)
- 大概能留住多少人?(定性估算 ROI)
- 模型怎么上线?(保存 Pipeline,新数据直接 predict_proba)
9.3 名词解释(通俗版)
| 名词 | 大白话 | 本步 |
|---|---|---|
| predict_proba | 输出「流失概率」0~1,不是硬判 0/1 | 比默认 0.5 阈值更灵活 |
| 运营阈值 | 业务定的「高风险线」,如 0.6 | 越高名单越短、漏报越多 |
| 挽留成功率 | 真流失客户里,被挽留活动成功留下的比例 | 本步假设 30% |
| joblib.dump | 把 Python 对象存成 .pkl 文件 |
models/churn_best.pkl |
| bundle | 打包:Pipeline + 阈值 + 列名 + 指标 | 加载一次即可推理 |
9.4 运营模拟流程(Mermaid)
#mermaid-svg-bP6NFXYw5vviYQVO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bP6NFXYw5vviYQVO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bP6NFXYw5vviYQVO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bP6NFXYw5vviYQVO .error-icon{fill:#552222;}#mermaid-svg-bP6NFXYw5vviYQVO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bP6NFXYw5vviYQVO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bP6NFXYw5vviYQVO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bP6NFXYw5vviYQVO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bP6NFXYw5vviYQVO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bP6NFXYw5vviYQVO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bP6NFXYw5vviYQVO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bP6NFXYw5vviYQVO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bP6NFXYw5vviYQVO .marker.cross{stroke:#333333;}#mermaid-svg-bP6NFXYw5vviYQVO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bP6NFXYw5vviYQVO p{margin:0;}#mermaid-svg-bP6NFXYw5vviYQVO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bP6NFXYw5vviYQVO .cluster-label text{fill:#333;}#mermaid-svg-bP6NFXYw5vviYQVO .cluster-label span{color:#333;}#mermaid-svg-bP6NFXYw5vviYQVO .cluster-label span p{background-color:transparent;}#mermaid-svg-bP6NFXYw5vviYQVO .label text,#mermaid-svg-bP6NFXYw5vviYQVO span{fill:#333;color:#333;}#mermaid-svg-bP6NFXYw5vviYQVO .node rect,#mermaid-svg-bP6NFXYw5vviYQVO .node circle,#mermaid-svg-bP6NFXYw5vviYQVO .node ellipse,#mermaid-svg-bP6NFXYw5vviYQVO .node polygon,#mermaid-svg-bP6NFXYw5vviYQVO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bP6NFXYw5vviYQVO .rough-node .label text,#mermaid-svg-bP6NFXYw5vviYQVO .node .label text,#mermaid-svg-bP6NFXYw5vviYQVO .image-shape .label,#mermaid-svg-bP6NFXYw5vviYQVO .icon-shape .label{text-anchor:middle;}#mermaid-svg-bP6NFXYw5vviYQVO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bP6NFXYw5vviYQVO .rough-node .label,#mermaid-svg-bP6NFXYw5vviYQVO .node .label,#mermaid-svg-bP6NFXYw5vviYQVO .image-shape .label,#mermaid-svg-bP6NFXYw5vviYQVO .icon-shape .label{text-align:center;}#mermaid-svg-bP6NFXYw5vviYQVO .node.clickable{cursor:pointer;}#mermaid-svg-bP6NFXYw5vviYQVO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bP6NFXYw5vviYQVO .arrowheadPath{fill:#333333;}#mermaid-svg-bP6NFXYw5vviYQVO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bP6NFXYw5vviYQVO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bP6NFXYw5vviYQVO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bP6NFXYw5vviYQVO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bP6NFXYw5vviYQVO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bP6NFXYw5vviYQVO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bP6NFXYw5vviYQVO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bP6NFXYw5vviYQVO .cluster text{fill:#333;}#mermaid-svg-bP6NFXYw5vviYQVO .cluster span{color:#333;}#mermaid-svg-bP6NFXYw5vviYQVO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bP6NFXYw5vviYQVO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bP6NFXYw5vviYQVO rect.text{fill:none;stroke-width:0;}#mermaid-svg-bP6NFXYw5vviYQVO .icon-shape,#mermaid-svg-bP6NFXYw5vviYQVO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bP6NFXYw5vviYQVO .icon-shape p,#mermaid-svg-bP6NFXYw5vviYQVO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bP6NFXYw5vviYQVO .icon-shape .label rect,#mermaid-svg-bP6NFXYw5vviYQVO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bP6NFXYw5vviYQVO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bP6NFXYw5vviYQVO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bP6NFXYw5vviYQVO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
测试集 X_test
pipeline.predict_proba
概率 >= 0.6?
进入挽留名单
暂不联系
名单中真流失 × 30%
预计挽回人数
9.5 任务清单
- 训练最终 LR Pipeline(步骤 7 最佳)
-
predict_proba计算测试集流失概率 - 阈值 0.6 生成挽留名单并统计 TP/FP
- 30% 成功率估算挽回人数
- 保存概率分布图 + 挽留模拟条形图
-
joblib保存 bundle + 配置说明 + 项目总结
9.6 运行本步
powershell
cd "d:\JavaCode\机器学习\project_03_churn"
python src/train.py
9.7 测试集运营模拟结果
| 指标 | 数值 |
|---|---|
| 测试集真流失总数 | 374 人 |
| 挽留名单(≥0.6) | 209 人 |
| 名单中真流失 | 150 人 |
| 名单中误报 | 59 人 |
| 名单外漏报 | 224 人 |
| 预计挽回(30%) | 45 人 |
通俗解读:
- 209 人进名单,其中 150 人是真会走的;若挽留活动对真流失客户 30% 有效,约 45 人被留住
- 59 人是「误报」------本来不会走也被联系(运营成本)
- 224 人概率 <0.6 但实际流失,模型没圈到(漏报)
9.8 最终推荐
| 项目 | 内容 |
|---|---|
| 推荐模型 | Pipeline + LogisticRegression |
| 运营阈值 | 0.6 |
| 测试 F1(流失) | 0.6040 |
| ROC-AUC | 0.8421 |
| 模型文件 | models/churn_best.pkl |
9.9 输出文件
| 文件 | 说明 |
|---|---|
models/churn_best.pkl |
完整 Pipeline + 阈值 bundle(Git 忽略) |
reports/churn_model_config.txt |
阈值与加载示例 |
reports/final_project_summary.txt |
全项目汇总 + 3 个关键教训 |
reports/figures/12_测试集流失概率分布.png |
按真实标签分色的概率直方图 |
reports/figures/13_运营挽留模拟效果图.png |
名单/真流失/挽回/漏报条形图 |
9.10 动手任务
任务 A:若把阈值从 0.6 降到 0.5,名单会变长还是变短?漏报会怎样?
任务 B:若挽留成功率只有 10%,预计挽回多少人?(150 × 10% = ?)
任务 C:写一句:电信公司更怕「漏掉要走的客户」还是「误打扰 loyal 客户」?阈值该升还是降?
9.11 验收标准
- 能解释 predict 与 predict_proba 的区别
- 能说明阈值高低对名单长度、漏报的影响
- 能读懂挽留模拟的 4 个数字(名单、真流失、挽回、漏报)
- 会用 joblib 加载 bundle 并对新数据 predict_proba
9.12 动手任务与验收标准参考答案
以下为教学标准答案,便于自学对照;
README.md学习记录仍建议用自己的话写。
动手任务答案
| 任务 | 参考答案 |
|---|---|
| A | 阈值 0.6→0.5 :名单变长 ;漏报减少 (更多真流失被圈进),但误报增加(更多 loyal 客户被打扰)。 |
| B | 150 × 10% = 15 人预计挽回。 |
| C | 电信通常更怕漏掉要走的客户 (流失直接丢收入)→ 倾向降阈值 ;若营销成本/打扰 loyal 代价大则升阈值。 |
验收标准答案
| 验收项 | 参考答案 |
|---|---|
| predict vs predict_proba | predict 用默认 0.5 硬分类;predict_proba[:,1] 是流失概率,运营用自定义阈值 0.6。 |
| 阈值影响 | 阈值↑→名单缩短、漏报↑;阈值↓→名单变长、漏报↓、误报↑。 |
| 四个数字 | 名单 209 ;真流失 150 ;预计挽回 45 (30%);名单外漏报 224。 |
| joblib | bundle=joblib.load('models/churn_best.pkl');pipe=bundle['pipeline'];proba=pipe.predict_proba(X)[:,1];proba>=bundle['retention_threshold']。 |
9.13 本项目 3 个关键教训
- 混合类型表格必须 Pipeline:数值 Imputer+Scaler,类别 One-Hot,避免泄露。
- 解释要与 EDA 对照:月付、在网时长是流失关键,模型与业务洞察一致才可信。
- 概率 + 阈值 = 运营动作:Accuracy 不够,要把模型变成「挽留名单」才有业务价值。
9.14 本步小结
| 要点 | 内容 |
|---|---|
| 模型 | LR Pipeline(步骤 7 F1 最佳) |
| 运营阈值 | 0.6 |
| 挽留名单 | 209 人(真流失 150) |
| 预计挽回 | 45 人(30% 成功率) |
| 项目状态 | 全部 9 步已完成 |
9.15 学习记录(请你填写)
- 完成日期:
- 若你当运营,阈值会设多少?理由:
- 疑问(如有):
文档版本:v1.9 | 最后更新:全步骤动手任务与验收标准参考答案已补充