缺失值处理器
MISSING VALUES HANDLER
通用缺失值处理提示,采用思维链和思维树框架,支持Python/Pandas/Scikit-learn。
適用平台:
ChatGPTClaudeGemini
# PROMPT() — 通用缺失值处理器
> **版本**: 1.0 | **框架**: CoT + ToT | **技术栈**: Python / Pandas / Scikit-learn
---
## 常量变量
| 变量 | 定义 |
|----------|------------|
| `PROMPT()` | 此主模板 — 管理所有推理、规则和决策 |
| `DATA()` | 您提供的用于分析的原始数据集 |
---
## 角色
您是一名**高级数据科学家和机器学习管道工程师**,专注于生产级机器学习系统的数据质量、特征工程和预处理。
您的工作是分析 `DATA()` 并生成一个完全可复现、可解释的缺失值处理计划。
---
## 如何使用此提示
```
1. 将您的原始 DATA() 粘贴到此文件底部(或提供 df.head(20) + df.info() 输出)
2. 指定您的机器学习任务:分类 / 回归 / 聚类 / 仅限 EDA
3. 指定您的目标列 (y)
4. 指定您预期的模型类型(基于树的模型 vs 线性模型 vs 神经网络)
5. 严格按照顺序运行阶段 1 → 5
──────────────────────────────────────────────────────
DATA() = [在此处插入您的数据集]
ML_TASK = [例如,二元分类]
TARGET_COL = [例如,“price”]
MODEL_TYPE = [例如,XGBoost / LinearRegression / 神经网络]
──────────────────────────────────────────────────────
```
---
## 阶段 1 — 侦察
### *思维链:在采取任何行动之前,请逐步思考。*
**步骤 1.1 — 分析 DATA()**
在继续之前,请明确回答每个问题:
```
1. DATA() 的形状是什么?(行 × 列)
2. 列名及其数据类型是什么?
- 数值型 → 连续型(浮点数)或离散型(整数/计数)
- 分类型 → 名义型(无序)或序数型(有等级顺序)
- 日期时间型 → 顺序时间戳
- 文本型 → 自由格式字符串
- 布尔型 → 二进制标志(0/1, True/False)
3. 机器学习任务的背景是什么?
- 仅限分类 / 回归 / 聚类 / 探索性数据分析
4. 哪些列是特征(X),哪些是目标(y)?
5. 是否存在伪装的缺失值?
- 注意以下值:"?", "N/A", "unknown", "none", "—", "-", 0 (在年龄/价格中)
- 这些值在分析前必须转换为 NaN。
6. 关键列的领域/业务规则是什么?
- 例如:"年龄不能为 0 或负数"
- 例如:"CustomerID 必须唯一且非空"
- 例如:"价格是目标 — 缺少此值的行无法使用"
```
**步骤 1.2 — 量化缺失值**
```python
import pandas as pd
import numpy as np
df = DATA().copy() # 始终在副本上操作 — 绝不修改原始数据
# 步骤 0: 标准化伪装的缺失值
DISGUISED_NULLS = ["?", "N/A", "n/a", "unknown", "none", "—", "-", ""]
df.replace(DISGUISED_NULLS, np.nan, inplace=True)
# 步骤 1: 生成缺失值报告
missing_report = pd.DataFrame({
'Column' : df.columns,
'Missing_Count' : df.isnull().sum().values,
'Missing_%' : (df.isnull().sum() / len(df) * 100).round(2).values,
'Dtype' : df.dtypes.values,
'Unique_Values' : df.nunique().values,
'Sample_NonNull' : [df[c].dropna().head(3).tolist() for c in df.columns]
})
```
```python
missing_report = missing_report[missing_report['Missing_Count'] > 0]
missing_report = missing_report.sort_values('Missing_%', ascending=False)
print(missing_report.to_string())
print(f"\n总共有缺失值的列数: {len(missing_report)}")
print(f"总缺失单元格数: {df.isnull().sum().sum()}")
```
---
## 阶段 2 — 缺失值诊断
### *思维树:在做决定之前,探索所有三个分支。*
对于**每个含有缺失值的列**,同时评估所有三个分支:
```
┌──────────────────────────────────────────────────────────────────┐
│ 缺失机制决策树 │
│ │
│ 根问题:为什么这个值会缺失? │
│ │
│ ├── 分支 A: MCAR — 完全随机缺失 │
│ │ 迹象: 无规律。缺失行看起来与其他行无异。 │
│ │ 测试: 可视化热图 / Little's MCAR 测试 │
│ │ 风险: 低 — 可安全删除行或自由插补 │
│ │ 示例: 调查受访者随机跳过一个问题 │
│ │ │
│ ├── 分支 B: MAR — 随机缺失 │
│ │ 迹象: 缺失与其他列相关,而不是与缺失值本身相关。 │
│ │ 测试: 缺失标志与其它列的相关性 │
│ │ 风险: 中 — 使用条件/分组插补 │
│ │ 示例: 年轻受访者收入缺失更多 │
│ │ │
│ └── 分支 C: MNAR — 非随机缺失 │
│ 迹象: 缺失与缺失值本身相关。 │
│ 测试: 领域知识 + 分布比较 │
│ 风险: 高 — 可能严重偏倚模型 │
│ 行动: 领域专家审查 + 创建指示标志 │
│ 示例: 高收入者故意跳过收入字段 │
└──────────────────────────────────────────────────────────────────┘
```
**对于每个标记的列,填写此分析卡:**
```
┌─────────────────────────────────────────────────────┐
│ 列分析卡片 │
├─────────────────────────────────────────────────────┤
│ 列名 : │
│ 缺失百分比 : │
│ 数据类型 : │
│ 是否为目标变量(y)?: 是 / 否 │
│ 机制 : MCAR / MAR / MNAR │
│ 证据 : (你为什么这么认为) │
│ 缺失值是否 : │
│ 具有信息性? : 是 (创建指示器) / 否 │
│ 建议操作 : (参见阶段 3) │
└─────────────────────────────────────────────────────┘
```
---
## 阶段 3 — 处理决策框架
### *严格按顺序应用规则。不要跳过。*
---
### 规则 0 — 目标列 (y) — 最高优先级
```
如果缺失的列是目标变量 (y):
→ 总是删除这些行 — 绝不插补目标变量
→ df.dropna(subset=[TARGET_COL], inplace=True)
→ 原因:模型无法从未标记的数据中学习
```
---
### 规则 1 — 阈值检查 (缺失百分比)
```
┌───────────────────────────────────────────────────────────────┐
│ 如果 missing% > 60%: │
│ → 选项 A: 完全删除该列 │
│ (例外: 领域将其标记为关键 → 标记专家) │
│ → 选项 B: 保留 + 创建二元指示符标记 │
│ (col_was_missing = 1) 然后决定如何插补 │
│ │
│ 如果 30% < missing% ≤ 60%: │
│ → 使用高级插补: KNN 或 MICE (IterativeImputer) │
│ → 始终首先创建缺失指示符标记 │
│ → 考虑按组 (条件) 平均值/众数 │
│ │
│ 如果 missing% ≤ 30%: │
│ → 继续执行规则 2 │
└───────────────────────────────────────────────────────────────┘
```
---
### 规则 2 — 数据类型路由
```
┌───────────────────────────────────────────────────────────────────────┐
│ 数值型 — 连续(浮点数): │
│ ├─ 对称分布(均值 ≈ 中位数)→ 均值填充 │
│ ├─ 偏态分布(存在异常值)→ 中位数填充 │
│ ├─ 时间序列 / 有序行 → 前向填充 / 插值 │
│ ├─ MAR(与其他列相关) → 分组均值 │
│ └─ 复杂多变量模式 → KNN / MICE │
│ │
│ 数值型 — 离散 / 计数(整数): │
│ ├─ 低基数(少量唯一值) → 众数填充 │
│ └─ 高基数 → 中位数或 KNN │
│ │
│ 类别型 — 名义(无序): │
│ ├─ 低基数 → 众数填充 │
│ ├─ 高基数 → “未知” / “缺失” 作为新类别 │
│ └─ 怀疑 MNAR → “未提供” 作为有意义的类别 │
│ │
│ 类别型 — 有序(有排名顺序): │
│ ├─ 自然排名 → 中位数排名填充 │
│ └─ MCAR / MAR → 众数填充 │
│ │
│ 日期时间: │
│ ├─ 顺序数据 → 前向填充 → 后向填充 │
│ └─ 随机间隔 → 插值 │
```
│ │
│ 布尔型 / 二进制型: │
│ └─ 众数填充(或视为分类变量) │
└───────────────────────────────────────────────────────────────────────┘
```
---
### 规则 3 — 高级填充选择指南
```
┌─────────────────────────────────────────────────────────────────┐
│ 何时使用每种高级方法 │
│ │
│ 按组均值/众数: │
│ → 当缺失值在给定分组列的条件下是 MAR 时 │
│ → 示例:使用每个 age_group 的平均值填充收入 NaN │
│ → 比全局平均值更真实 │
│ │
│ KNN Imputer (默认 k=5): │
│ → 当存在多个相关数值列时 │
│ → 找到 k 个最近的完整行并平均它们的值 │
│ → 在大型数据集上速度较慢 │
│ │
│ MICE / IterativeImputer: │
│ → 最强大 — 使用所有其他列对每列进行建模 │
│ → 最适合具有复杂多变量关系的 MAR │
│ → 使用 max_iter=10, random_state=42 以实现可复现性 │
│ → 计算成本最高 │
│ │
│ 缺失值指示器标志: │
│ → 始终为 MNAR 列添加 │
│ → 对于缺失率超过 30% 的列,可选但推荐 │
│ → 创建:col_was_missing = 1 如果是 NaN,否则为 0 │
│ → 告诉模型“此值缺失”作为信号 │
└─────────────────────────────────────────────────────────────────┘
```
---
### 规则 4 — 机器学习模型兼容性
```
┌─────────────────────────────────────────────────────────────────┐
│ 基于树的模型(XGBoost, LightGBM, CatBoost, RandomForest): │
│ → 可原生处理 NaN │
│ → 仍建议:为 MNAR 创建指示器标志 │
│ │
│ 线性模型(LogReg, LinearReg, Ridge, Lasso): │
│ → 必须进行插补 — 零 NaN 容忍度 │
│ │
│ 神经网络 / 深度学习: │
│ → 必须进行插补 — 无 NaN 容忍度 │
│ │
│ SVM, KNN 分类器: │
│ → 必须进行插补 — 无 NaN 容忍度 │
│ │
│ ⚠️ 所有模型的通用规则: │
│ → 首先分割训练/测试集 │
│ → 仅在训练集上拟合插补器 │
│ → 使用已拟合的插补器转换训练集和测试集 │
│ → 绝不在完整数据集上拟合 — 会导致数据泄露 │
└─────────────────────────────────────────────────────────────────┘
```
---
## 阶段 4 — PYTHON 实现蓝图
```python
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer, KNNImputer
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np
```
# ─────────────────────────────────────────────────────────────────
# 步骤 0 — 加载并复制 DATA()
# ─────────────────────────────────────────────────────────────────
df = DATA().copy()
# ─────────────────────────────────────────────────────────────────
# 步骤 1 — 标准化伪装的缺失值
# ─────────────────────────────────────────────────────────────────
DISGUISED_NULLS = ["?", "N/A", "n/a", "unknown", "none", "—", "-", ""]
df.replace(DISGUISED_NULLS, np.nan, inplace=True)
# ─────────────────────────────────────────────────────────────────
# 步骤 2 — 删除 TARGET 缺失的行(规则 0)
# ─────────────────────────────────────────────────────────────────
TARGET_COL = 'your_target_column' # ← 修改此处
df.dropna(subset=[TARGET_COL], axis=0, inplace=True)
# ─────────────────────────────────────────────────────────────────
# 步骤 3 — 分离特征和目标
# ─────────────────────────────────────────────────────────────────
X = df.drop(columns=[TARGET_COL])
y = df[TARGET_COL]
# ─────────────────────────────────────────────────────────────────
# 步骤 4 — 在任何插补之前进行训练/测试集分割
# ─────────────────────────────────────────────────────────────────
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# ─────────────────────────────────────────────────────────────────
# 步骤 5 — 定义列组(在阶段 1-2 后填写)
# ─────────────────────────────────────────────────────────────────
num_cols_symmetric = [] # → 均值填充
num_cols_skewed = [] # → 中位数填充
cat_cols_low_card = [] # → 众数填充
cat_cols_high_card = [] # → 填充为 'Unknown'
knn_cols = [] # → KNN 填充
drop_cols = [] # → 删除(缺失值 >60% 或与领域无关)
mnar_cols = [] # → 指示标志 + 填充
# ─────────────────────────────────────────────────────────────────
# 步骤 6 — 删除高缺失或不相关的列
# ─────────────────────────────────────────────────────────────────
X_train = X_train.drop(columns=drop_cols, errors='ignore')
X_test = X_test.drop(columns=drop_cols, errors='ignore')
# ─────────────────────────────────────────────────────────────────
# 步骤 7 — 在填充前创建缺失值指示标志
# ─────────────────────────────────────────────────────────────────
for col in mnar_cols:
X_train[f'{col}_was_missing'] = X_train[col].isnull().astype(int)
X_test[f'{col}_was_missing'] = X_test[col].isnull().astype(int)
# ─────────────────────────────────────────────────────────────────
# 步骤 8 — 数值填充
# ─────────────────────────────────────────────────────────────────
if num_cols_symmetric:
imp_mean = SimpleImputer(strategy='mean')
X_train[num_cols_symmetric] = imp_mean.fit_transform(X_train[num_cols_symmetric])
X_test[num_cols_symmetric] = imp_mean.transform(X_test[num_cols_symmetric])
如果 num_cols_skewed 为真:
imp_median = SimpleImputer(strategy='median')
X_train[num_cols_skewed] = imp_median.fit_transform(X_train[num_cols_skewed])
X_test[num_cols_skewed] = imp_median.transform(X_test[num_cols_skewed])
# ─────────────────────────────────────────────────────────────────
# 步骤 9 — 类别特征填充
# ─────────────────────────────────────────────────────────────────
如果 cat_cols_low_card 为真:
imp_mode = SimpleImputer(strategy='most_frequent')
X_train[cat_cols_low_card] = imp_mode.fit_transform(X_train[cat_cols_low_card])
X_test[cat_cols_low_card] = imp_mode.transform(X_test[cat_cols_low_card])
如果 cat_cols_high_card 为真:
X_train[cat_cols_high_card] = X_train[cat_cols_high_card].fillna('Unknown')
X_test[cat_cols_high_card] = X_test[cat_cols_high_card].fillna('Unknown')
# ─────────────────────────────────────────────────────────────────
# 步骤 10 — 分组填充(MAR 模式)
# ─────────────────────────────────────────────────────────────────
# 示例:使用每组 'age_group' 的均值填充 'income' 中的 NaN
# GROUP_COL = 'age_group'
# TARGET_IMP_COL = 'income'
# group_means = X_train.groupby(GROUP_COL)[TARGET_IMP_COL].mean()
# X_train[TARGET_IMP_COL] = X_train[TARGET_IMP_COL].fillna(
# X_train[GROUP_COL].map(group_means)
# )
# X_test[TARGET_IMP_COL] = X_test[TARGET_IMP_COL].fillna(
# X_test[GROUP_COL].map(group_means)
# )
# ─────────────────────────────────────────────────────────────────
# 步骤 11 — 用于复杂模式的 KNN 填充
# ─────────────────────────────────────────────────────────────────
如果 knn_cols 为真:
imp_knn = KNNImputer(n_neighbors=5)
X_train[knn_cols] = imp_knn.fit_transform(X_train[knn_cols])
X_test[knn_cols] = imp_knn.transform(X_test[knn_cols])
# ─────────────────────────────────────────────────────────────────
# 步骤 12 — MICE / 迭代插补器 (最强大,按需使用)
# ─────────────────────────────────────────────────────────────────
# imp_iter = IterativeImputer(max_iter=10, random_state=42)
# X_train[advanced_cols] = imp_iter.fit_transform(X_train[advanced_cols])
# X_test[advanced_cols] = imp_iter.transform(X_test[advanced_cols])
# ─────────────────────────────────────────────────────────────────
# 步骤 13 — 最终验证
# ─────────────────────────────────────────────────────────────────
remaining_train = X_train.isnull().sum()
remaining_test = X_test.isnull().sum()
assert remaining_train.sum() == 0, f"训练集仍有缺失值:\n{remaining_train[remaining_train > 0]}"
assert remaining_test.sum() == 0, f"测试集仍有缺失值:\n{remaining_test[remaining_test > 0]}"
print("✅ 没有缺失值。数据已准备好用于机器学习。")
print(f" 训练集形状: {X_train.shape} | 测试集形状: {X_test.shape}")
```
---
## 阶段 5 — 综合与决策报告
完成阶段 1-4 后,提交此确切报告:
```
═══════════════════════════════════════════════════════════════
缺失值处理报告
═══════════════════════════════════════════════════════════════
1. 数据集摘要
形状 :
总缺失值 :
目标列 :
机器学习任务 :
模型类型 :
2. 缺失值清单表
| 列 | 缺失百分比 | 数据类型 | 机制 | 是否提供信息? | 处理方法 |
|--------|------------|----------|-----------|----------------|----------|
| ... | ... | ... | ... | ... | ... |
3. 决策日志
[列名]: [选择该处理方法的原因]
[列名]: [选择该处理方法的原因]
4. 已删除列
[列] — 原因:[例如,72% 缺失,非领域关键]
5. 已创建指标旗标
[col_was_missing] — 原因:[怀疑 MNAR / 缺失百分比高]
6. 使用的插补方法
[列] → [使用的策略 + 理由]
7. 警告与边缘情况
- 需要领域专家审查的 MNAR 列
- 插补过程中做出的假设
- 在完整 EDA 后标记为需要重新评估的列
- 发现的任何伪装空值(?、N/A、0 等)
8. 后续步骤 — 插补后检查清单
☐ 比较插补前后的分布(直方图)
☐ 确认所有插补器仅在训练集上拟合
☐ 验证目标列没有数据泄露
☐ 插补后重新检查相关矩阵
☐ 如果是分类任务,检查类别平衡
☐ 记录所有转换以实现可复现性
═══════════════════════════════════════════════════════════════
```
---
## 约束与防护措施
```
✅ 必须始终:
→ 在 df.copy() 上操作 — 绝不修改原始 DATA()
→ 删除目标 (y) 缺失的行 — 绝不插补 y
→ 仅在训练数据上拟合所有插补器
→ 使用已拟合的插补器转换测试集(不重新拟合)
→ 为所有 MNAR 列创建指标旗标
→ 在传递给模型之前验证没有空值残留
→ 检查伪装的缺失值(?、N/A、0、空白、“unknown”)
→ 记录每个决策并给出明确理由
❌ 绝不能:
→ 在未首先检查分布的情况下盲目插补
→ 在未检查列的领域重要性之前删除列
→ 在训练/测试拆分之前在完整数据集上拟合插补器(数据泄露)
→ 忽略 MNAR 列 — 它们可能严重偏倚模型
→ 对所有列应用相同的策略
→ 假设 NaN 是缺失值的唯一形式
```
---
## 快速参考 — 策略速查表
| 情况 | 策略 |
|---|---|
| 目标列 (y) 包含 NaN | 删除行 — 绝不插补 |
| 列缺失值超过 60% | 删除列(或指示器 + 专家审查) |
| 数值型,对称分布 | 均值插补 |
| 数值型,偏态分布 | 中位数插补 |
| 数值型,时间序列 | 前向填充 / 插值 |
| 分类型,低基数 | 众数插补 |
| 分类型,高基数 | 填充为“未知”类别 |
| 怀疑 MNAR(任何类型) | 指示器标志 + 领域审查 |
| MAR,基于组条件 | 按组均值/众数 |
| 复杂多变量模式 | KNN Imputer 或 MICE |
| 基于树的模型 (XGBoost 等) | 允许 NaN;仍需标记 MNAR |
| 线性 / NN / SVM | 必须插补 — 零 NaN 容忍度 |
---
*PROMPT() v1.0 — 专为 IBM GEN AI 工程 / Python 数据分析构建*
*框架:思维链 (CoT) + 思维树 (ToT)*
*参考:Coursera — 使用 Python 处理缺失值*