第3章:数据收集与清洗复盘——从混乱到可用

数据收集与清洗,听起来像是脏活累活,对吧?说实话,在MD特征学习项目里,这一步往往是决定整个项目成败的关键。模型选得再好,架构搭得再漂亮,喂进去的数据是垃圾,那出来的结果也只能是垃圾。

我早期带项目时,最怕的就是复盘时发现数据源头就有问题。那种感觉,就像打了一场大仗,最后发现枪里装的是空包弹。所以这次复盘,咱们得把数据这关把死。

3.1 数据收集复盘:不只是“拉数据”那么简单

很多团队复盘时,只关心数据量够不够,样本分布均不均匀。但我个人的复盘习惯,会多问几个“为什么”。

为什么特定样本数量少?是采集策略有问题,还是原始来源本身就有偏向?我见过一个项目,模型在测试集上表现极好,结果上线就崩。复盘发现,训练数据里超过80%是白天场景的样本,晚上样本几乎为零。你说这模型能不出事吗?

数据来源这块,我建议你用下表来做框架式复盘。这不是死规矩,是我试过很多种模板后保留下来最实用的一个。

复盘维度 关键问题 典型风险
来源完整性 所有预期的数据源都对齐了吗?有没有遗漏的日志流或数据库表? 源表字段变更未通知,导致管道断裂
时序对齐 不同源的数据时间戳是否一致?时区处理是否正确? 毫秒级时间戳与秒级时间戳混用,造成特征错位
采样策略 全量采集还是采样?采样策略是否引入了偏差? 随机采样去掉了罕见的但重要的极端样本
版本控制 数据集的版本和代码版本能否对应上? 线上模型用的是V2数据,实验却用V1,结果完全不可比

在复盘日志时,我通常会先跑一条命令,快速概览一下原始数据的基本情况。

# 快速概览日志数量和时间范围
wc -l raw_logs/*.log
head -1 raw_logs/20250401.log
tail -1 raw_logs/20250430.log

这里有个坑——日志里经常夹杂着一些奇怪的字符或空行。我曾经在一次复盘中发现,某个日志文件在每日轮转时出现了重复的行,导致后续统计全乱套了。所以,简单的数量计算只是第一步,你必须结合字段级别的质量检查。

3.2 数据质量评估:量化你的脏数据

数据干不干净,不是靠感觉的。我习惯用一套通用的质量指标来量化,这样复盘时大家有据可依,不会吵架。

数据质量七大指标

  1. 完整性 —— 缺失值的比例。超过5%的字段需要特别关注。
  2. 准确性 —— 值与真实情况的偏差。比如年龄字段出现负值,那就是红线。
  3. 一致性 —— 同一实体的值在不同源中是否冲突。同一个人身份证号码,A表是18位,B表是15位,这事得查。
  4. 时效性 —— 数据是否在有效时间范围内。过期的特征会导致模型产生错误的关联。
  5. 唯一性 —— 重复记录的占比。重复样本在训练时会变相增加权重,影响模型公平性。
  6. 有效性 —— 值是否符合业务规则。比如性别字段不能填“阿巴阿巴”。
  7. 均匀性 —— 分布是否合理。长尾分布下,头部数据可能掩盖尾部模式。

这些指标不是空话。我手里有一份当时项目的实际质量报告片段,给你看看具体的实现。你可以把它当模板,放到自己的复盘文档里。

表名:user_behavior_log
总记录数:1,234,567
缺失率:
  - device_id: 0.02%
  - click_time: 0.00%
  - page_url: 1.23%   <-- 注意这个字段
重复率:
  - 完全重复行: 2,345 (0.19%)
  - 基于(user_id, click_time)的重复: 1,098 (0.09%)
异常值:
  - click_time 中未来时间戳: 12 条
  - page_url 为空字符串: 321 条
一致性检查:
  - user_id 在 user_profile 表中存在率: 98.76%
  - 存在 1.24% 的 user_id 未关联到任何用户画像

你看,数据是不会骗人的。当缺失率超过1%时,你就得问一句:这些缺失值背后有没有系统性的原因?是采集程序写错了,还是业务本身就有跑单的用户?这些信息,是后面清洗策略的决策依据。

3.3 清洗策略复盘:动作要留痕,结果要可复现

清洗数据时,最忌讳的就是直接用Excel打开文件手动改。我从不允许团队这么干。为什么?因为你改完之后,没人知道你改了哪里、改了多久、为什么这么改。这种脏活一旦没了记录,复盘就成了猜谜游戏。

我的做法是:每个清洗操作,都必须有对应的代码和日志。清洗程序必须是幂等的,跑一次和跑一百次,结果一模一样。这样复盘时,只要重跑一遍清洗管道,就能复现当时的处理流程。

3.3.1 常见的清洗动作归类

复盘时,把清洗动作分门别类整理,能帮你发现规律。比如,某个阶段突然出现大量异常值清洗,往往意味着上游接口发生了变化。

清洗类别 典型动作 复盘关注点
缺失值处理 填充、删除、标记 填充策略是否基于领域知识?还是直接用了均值?
异常值过滤 截断、盖帽法、分箱 过滤阈值是怎么定的?有没有过度清洗掉有意义的极端样本?
重复数据删除 完全去重、基于主键去重 重复是业务允许的重复,还是采集缺陷?
格式统一 时间格式、数值单位、编码 转换规则是否覆盖了所有边缘情况?
业务规则校验 逻辑约束、依赖关系 规则版本是否与当前业务版本一致?

一个实用小技巧

清洗代码里一定要加一个 is_raw_data 的参数。设为 True 时,从原始数据重新跑一遍完整流程。这样无论何时复盘,你都能保证结果的一致性。我因为这个习惯,救过两次项目复盘会。

看一段实际的清洗代码片段,注意我是怎么保留清洗日志的。

说实话,这段代码没什么炫技的地方,但贵在清晰。每处理一步,都记录下被处理了多少行。这样复盘时,你就能精确地知道:“哦,原来在缺失值填充这一步,丢掉了12%的数据。然后异常值截断又动了2%的范围。”

def clean_behavior_data(df):
    """清洗用户行为数据,并记录清洗日志。"""
    log = {}
    original_rows = len(df)

    # 1. 删除完全重复行
    df = df.drop_duplicates()
    log['duplicates_removed'] = original_rows - len(df)

    # 2. 填充缺失的device_id
    before_fill = df['device_id'].isna().sum()
    df['device_id'] = df['device_id'].fillna('unknown_device')
    log['device_id_filled'] = before_fill

    # 3. 过滤未来时间戳
    max_time = pd.Timestamp.now()
    future_mask = df['click_time'] > max_time
    log['future_timestamps_removed'] = future_mask.sum()
    df = df[~future_mask]

    # 4. 删除空page_url
    empty_url_mask = df['page_url'].str.strip().eq('') | df['page_url'].isna()
    log['empty_url_removed'] = empty_url_mask.sum()
    df = df[~empty_url_mask]

    return df, log

警告:版本混沌陷阱

我曾经在复盘时遇到一个极其头疼的情况:团队里有人用Pandas 1.4跑的清洗,有人用2.0跑的。某个fillna的默认行为在两版之间发生了变化!结果导致同一份原始数据,跑出两份不同的清洗后数据。排查了一整天才找到原因。

所以,清洗环境的依赖必须锁定:pandas==2.0.3,numpy==1.24.2。用requirements.txt或conda env export固定下来。

3.4 工具与标准化:构建可复用的复盘流水线

如果每次复盘都手工搞一套清洗脚本,那就太累了。我建议在项目初期就搭建一个标准化的数据收集与清洗管道。这个管道本身也应该是复盘内容的一部分。

下面这张图概括了数据清洗复盘的核心流程,你看看能不能匹配你的项目情况。

数据收集与清洗复盘核心流程 多数据源 原始数据 日志文件 API响应 数据质量评估 完整性 | 准确性 | 一致性 清洗策略执行 缺失值处理 | 异常值过滤 | 重复删除 | 格式统一 清洗日志与版本记录 可复现的清洗结果 复盘核心:每一个步骤 必须是可重放的

这个流程里,我最看重的是 “可重放” 。即,给你一份新的原始数据,按相同的清洗管道执行,必须得到相同质量的结果。复盘既是对过去工作的检查,也是在为下一次项目铺经验。

3.4.1 清洗工具的标准化选型

不同项目用不同工具,但在复盘阶段,我们需要统一的视角。我比较推荐用Pandas或PySpark做核心处理,配合Great Expectations做质量断言。你也可以用Deequ、Dolt之类的工具,但关键是团队统一。

标准化选型后,复盘报告里可以直接引用质检断言的结果。比如用Great Expectations检查关键字段:

expect_column_values_to_not_be_null("user_id")
expect_column_values_to_be_between("age", min_value=0, max_value=120)
expect_column_values_to_match_regex("email", r"^[^@]+@[^@]+\.[^@]+$")

断言不通过的地方,就是清洗策略需要微调的地方。这份质检报告本身,就是你复盘时的最强论据。