第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的可视化浓缩。

数据采集与清洗流程结构图 原始数据接入 字段定义映射 异常值检测 ← 上一章 📖 返回目录 下一章 →