第3章:数据采集与清洗规范——字段定义、异常值处理、缺失值处理
核心观点:数据清洗不是事后补救,而是实验分析前必须完成的标准化工序。字段定义不一致、异常值和缺失值未经处理直接进入分析,会导致A/B测试结论偏移甚至完全错误。本章给出可直接落地的SOP。
3.1 适用场景
- A/B 测试已结束,原始日志或埋点数据已从服务端、客户端、第三方工具导出。
- 原始数据包含多个来源(前端、后端、CDP、GA4等),字段名、格式、枚举值不统一。
- 数据集中存在明显离群值(如曝光量骤降、点击率为负、转化时间异常),或字段存在空值、Null、占位符。
- 需要进行量化决策(如判定实验组是否显著优于对照组)之前,必须保证输入数据干净、可复现。
3.2 前置条件
| 编号 |
前置条件 |
验收标准 |
| PC-1 |
原始数据已完整导出至数据仓库或本地文件系统 |
文件行数与实验系统记录一致,时间戳覆盖实验全周期 |
| PC-2 |
字段定义文档(Field Dictionary)已由数据工程师与产品经理共同确认 |
每个字段有明确的中文名、类型、枚举值列表、业务含义 |
| PC-3 |
异常值与缺失值处理规则已预先评审 |
规则文档中包含判定阈值、处理策略(删除/填充/标记)及责任人 |
| PC-4 |
清洗脚本运行环境已就绪(Python 3.9+ / PySpark / SQL) |
可成功读取数据源,输出路径可写 |
3.3 操作步骤
数据清洗按以下6个步骤依次执行,每一步均需输出中间检查结果。
Step 1:原始数据接入与字段映射
- 将各数据源导入统一 staging 表或 DataFrame。
- 根据字段定义文档,统一字段名(如
user_id → user_id、exp_group → experiment_group)。
- 检查字段类型是否匹配:
event_time 必须为 datetime,revenue 必须为 float。
# 字段映射示例(Python + Pandas)
import pandas as pd
raw = pd.read_csv("ab_test_raw.csv")
field_map = {
"uid": "user_id",
"group": "experiment_group",
"evt_time": "event_time",
"page_views": "page_view_cnt",
"order_amount": "revenue"
}
df = raw.rename(columns=field_map)
df["event_time"] = pd.to_datetime(df["event_time"])
df["revenue"] = df["revenue"].astype(float)
print("字段映射完成,当前列名:", df.columns.tolist())
Step 2:异常值检测
- 数值型字段:采用 IQR 或 Z-score 方法识别离群点。
- 类别型字段:检查枚举值是否在允许列表中。
- 时间字段:检查是否在实验起止窗口内。
| 字段 |
检测方法 |
异常判定条件 |
page_view_cnt |
IQR (Q1 - 1.5×IQR, Q3 + 1.5×IQR) |
值 < 0 或 > Q3+1.5×IQR |
revenue |
Z-score (|Z| > 3) |
Z-score 绝对值 > 3 |
experiment_group |
值域检查 |
不在 ["control", "treatment"] 中 |
event_time |
范围检查 |
早于实验开始日期或晚于实验结束日期 |
# 异常值检测示例
import numpy as np
def detect_outliers_iqr(series):
Q1 = series.quantile(0.25)
Q3 = series.quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
return (series < lower) | (series > upper)
df["page_view_outlier"] = detect_outliers_iqr(df["page_view_cnt"])
print(f"页面浏览异常数:{df['page_view_outlier'].sum()}")
Step 3:异常值处理
- 可修正类:明显的数据录入错误(如负值取绝对值、时间戳偏移修复),在字段定义文档中记录修正规则。
- 不可修正类:超出业务合理范围(如单次购买金额 > 100万),标记为
异常 并移入异常数据集,不参与后续分析。
禁止行为:直接删除异常行而不记录日志。所有被移除的数据必须归档至 异常记录表,并注明移除原因与规则版本号。
Step 4:缺失值识别
- 逐字段统计缺失比例:
df.isnull().mean()。
- 区分缺失类型:完全随机缺失(MCAR)、随机缺失(MAR)、非随机缺失(MNAR)。
Step 5:缺失值处理
| 缺失比例 |
建议策略 |
适用字段 |
| < 1% |
直接删除缺失行或用众数/中位数填充 |
page_view_cnt、revenue |
| 1% ~ 5% |
分组填充(按 experiment_group 计算中位数)或模型预测 |
session_duration |
| > 5% |
必须调查缺失原因,将字段纳入数据质量监控看板,分析时考虑敏感性检验 |
device_type、referrer |
# 缺失值分组填充示例
df["session_duration"] = df.groupby("experiment_group")["session_duration"]\
.transform(lambda x: x.fillna(x.median()))
print("session_duration 缺失填充完成,剩余缺失:", df["session_duration"].isnull().sum())
Step 6:数据质量校验
- 完整性:记录总数与原始数据源数量一致(扣除合理删除)。
- 一致性:关键指标(如总曝光、总转化)与实验平台后台相差 < 1%。
- 及时性:时间戳连续,无中断。
3.4 输入输出物
| 类别 |
名称 |
格式 |
责任人 |
| 输入 |
原始实验日志 / 埋点数据 |
CSV / Parquet / JSON |
数据工程师 |
| 输入 |
字段定义文档(Field Dictionary) |
Markdown / Notion / Airtable |
数据分析师 + 产品经理 |
| 输出 |
清洗后结构化数据(Clean Data) |
Parquet(推荐)或 CSV |
数据分析师 |
| 输出 |
数据清洗报告(含异常/缺失处理记录) |
HTML / PDF |
数据分析师 |
3.5 责任边界
| 角色 |
职责 |
交付物 |
| 数据工程师 |
原始数据拉取、staging 表创建、字段类型转换 |
staging 数据表、接入日志 |
| 数据分析师 |
清洗规则制定、脚本编写、质量报告输出 |
清洗脚本 + 数据清洗报告 |
| 产品经理 |
确认业务含义、审核异常值是否合理、签字验收 |
字段定义确认签字、异常规则确认 |
3.6 记录材料
- 字段映射表(Field Mapping Log):源字段 → 目标字段 → 转换规则 → 版本号。
- 异常值处理记录(Outlier Log):用户ID / 实验组 / 异常字段 / 原始值 / 处理方式 / 操作时间。
- 缺失值处理记录(Missing Log):字段名 / 缺失比例 / 填充策略 / 填充值 / 剩余缺失数。
- 数据质量校验单(Quality Check List):完整率、一致率、及时性指标。
3.7 异常处理
| 异常场景 |
影响 |
处理预案 |
| 数据源连接失败或文件损坏 |
清洗流程无法启动 |
联系数据工程师修复源,检查存储权限;使用上一版本备份数据作为临时替代 |
| 字段类型转换报错 |
清洗脚本中断 |
检查字段定义文档与实际数据是否一致,将异常行隔离后继续执行 |
| 异常值比例超过阈值(> 5%) |
可能表示数据采集系统故障 |
立即停止清洗,通知工程团队排查埋点,待修复后重新采集 |
| 缺失值比例突增(> 10%) |
分析结果可能偏差 |
记录缺失模式,在分析阶段增加多重插补敏感性检验 |
3.8 验收标准
| 检查项 |
验收标准 |
验证方法 |
| 字段完整性 |
所有必需字段(user_id, experiment_group, event_time, 核心指标)100% 存在 |
df.info() 确认无缺失列 |
| 数据一致性 |
实验组标识与实验平台后台完全一致 |
交叉比对 1000 条随机抽样 |
| 异常值标记率 |
所有异常行已被标记且移入异常数据集,异常数据集行数 < 原始数据 3% |
检查异常数据集记录 |
| 缺失值处理 |
无未处理的 NaN 或 None 残留(目标字段) |
df.isnull().sum().sum() == 0 |
| 可复现性 |
清洗脚本可重复执行,输出结果一致 |
两次运行结果哈希值相同 |
3.9 数据清洗流程总图
下图完整呈现从原始数据接入到清洗完成的6个主节点及分支处理路径,是本章SOP的可视化浓缩。