项目三教学文档:电信客户流失预测 —— 表格分类全流程

项目三教学文档:电信客户流失预测 ------ 表格分类全流程

https://gitee.com/ghaweiuptgb/machine-learning-AI.git (代码包)

使用说明 :本文档是项目三的完整教学记录。每完成一个步骤,我会把该步骤的详细教学过程写入对应章节。

配套文件

代码规范 :逐行注释 + 图表中文(见根目录 .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 本步目标

  1. 创建 project_03_churn/ 目录结构
  2. 加载 Telco Customer Churn 数据集
  3. 检查 customerID 是否应作为特征(应剔除)
  4. 列出数值特征类别特征列表
  5. 发现 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 本步目标

  1. TotalCharges 转为数值(errors='coerce'),处理 11 行空格
  2. Churn 编码为 0/1 整数
  3. 检查重复行SeniorCitizen 是否已是 0/1
  4. 验收:清洗后无缺失值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 本步目标

  1. 计算总体流失率
  2. Contract(合同类型)分组看流失率
  3. tenure(在网时长)分段看流失率
  4. 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%

业务回答:

  1. 月付客户最容易流失(无长期绑定,随时可退)
  2. 新客户 (0-12 月)流失率近 47%,需 onboarding 挽留
  3. 两年期 + 老客户最稳定,维护满意度即可

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 本步目标

  1. X 去掉 customerIDChurnyChurn(0/1)
  2. train_test_split(stratify=y, test_size=0.2, random_state=42)
  3. 验证训练/测试/全集流失占比一致

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 重要提醒

  1. 预处理只能 fit 训练集 :Imputer、Scaler、OneHot 的统计量来自 X_train,再 transform 测试集
  2. 测试集不能参与训练y_test 只在最终评估时用
  3. 与项目二相同: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 本步目标

  1. 数值列SimpleImputer(median) + StandardScaler
  2. 类别列SimpleImputer(most_frequent) + OneHotEncoder
  3. ColumnTransformer 组合
  4. 演示 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__tenurecat__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 本步目标

  1. Pipeline([('preprocessor', ...), ('classifier', LogisticRegression)])
  2. 训练集 fit,测试集 predict
  3. 输出 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 本步目标

  1. 同样 Pipeline 结构,替换分类器为 LR / 随机森林 / HistGBR
  2. 对比 Accuracy、流失 F1、ROC-AUC
  3. 选定最佳模型

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 本步目标

  1. 逻辑回归:系数绝对值 Top 10(最佳模型)
  2. 随机森林feature_importances_ Top 10(对比)
  3. 结合步骤 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 本步目标

  1. 在测试集上用 predict_proba 得到每位客户的流失概率
  2. 设定运营规则:概率 ≥ 0.6 进入挽留名单
  3. 假设挽留成功率 30%,估算可挽回多少客户
  4. joblib 保存完整 Pipeline + 阈值 + 元数据
  5. 写出项目 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 个关键教训

  1. 混合类型表格必须 Pipeline:数值 Imputer+Scaler,类别 One-Hot,避免泄露。
  2. 解释要与 EDA 对照:月付、在网时长是流失关键,模型与业务洞察一致才可信。
  3. 概率 + 阈值 = 运营动作:Accuracy 不够,要把模型变成「挽留名单」才有业务价值。

9.14 本步小结

要点 内容
模型 LR Pipeline(步骤 7 F1 最佳)
运营阈值 0.6
挽留名单 209 人(真流失 150)
预计挽回 45 人(30% 成功率)
项目状态 全部 9 步已完成

9.15 学习记录(请你填写)

  • 完成日期
  • 若你当运营,阈值会设多少?理由
  • 疑问(如有):

文档版本:v1.9 | 最后更新:全步骤动手任务与验收标准参考答案已补充