4. 数据清洗基础:缺失值处理、异常值检测与重复数据去重

在推荐系统的特征流水线中,原始数据几乎不可能完美无缺。数据清洗是特征工程的第一步,也是决定模型效果下限的关键环节。本章聚焦三类最常见的数据质量问题:缺失值、异常值和重复数据,并给出在 PD(Personalized Recommendation)模型场景下的最佳实践。

4.1 缺失值处理

缺失值在用户行为日志、画像标签、上下文特征中普遍存在。处理策略分为三类:删除、填充和插值。

策略 适用场景 PD 模型注意事项
删除 缺失比例 < 5% 且随机缺失 避免删除用户 ID 或 item ID 等关键主键
统计填充 数值型特征(均值/中位数/众数) 均值填充会降低方差,影响树模型分裂
模型预测填充 特征间关联性强 用 KNN 或线性回归预测缺失值,计算成本高
插值法 时间序列特征(如用户近 7 天活跃度) 线性插值、样条插值保持趋势
标记法 类别型特征或业务含义明确的缺失 将缺失作为独立类别(如 "unknown")
PD 模型实战建议:对于用户实时特征(如“当前网络类型”),缺失往往代表“未获取到”,建议用标记法填充为 -1 或 "unknown",保留缺失的业务语义。

4.1.1 删除与填充代码示例

import pandas as pd
import numpy as np

# 示例数据:用户特征表
df = pd.DataFrame({
    'user_id': [1, 2, 3, 4, 5],
    'age': [25, np.nan, 30, 22, np.nan],
    'gender': ['M', 'F', np.nan, 'F', 'M'],
    'last_7d_clicks': [12, 5, np.nan, 8, 15]
})

# 1. 删除缺失比例超过 50% 的列
df.dropna(thresh=len(df) * 0.5, axis=1, inplace=True)

# 2. 数值型填充:用中位数填充年龄
df['age'].fillna(df['age'].median(), inplace=True)

# 3. 类别型填充:用众数填充性别
df['gender'].fillna(df['gender'].mode()[0], inplace=True)

# 4. 时间序列插值:线性插值填充 last_7d_clicks
df['last_7d_clicks'] = df['last_7d_clicks'].interpolate(method='linear')

print(df)

4.2 异常值检测与处理

异常值在推荐特征中常表现为:用户单日点击量异常高、商品价格录入错误、连续特征出现极端离群点。异常值会严重干扰特征缩放和模型训练。

4.2.1 常用检测方法对比

方法 原理 适用特征类型 PD 场景示例
Z-Score |Z| > 3 视为异常 近似正态分布 用户年龄、商品价格
IQR 四分位距 Q1 - 1.5*IQR 或 Q3 + 1.5*IQR 之外 偏态分布 用户点击次数、停留时长
孤立森林 基于树模型隔离异常点 高维特征 多维度用户画像异常检测
DBSCAN 密度聚类,低密度点为异常 空间/行为序列 用户行为轨迹异常

4.2.2 异常值处理策略

  • 截断(Winsorize):将超出阈值的值替换为阈值边界值,保留样本数量。
  • 删除:当异常值明显为录入错误且数量极少时直接删除。
  • 视为缺失:将异常值标记为缺失,再走缺失值处理流程。
  • 分箱离散化:将连续值分箱,异常值落入首尾箱,降低影响。
避坑提醒:不要对类别型特征(如 gender、city)做 Z-Score 异常检测。类别型特征的异常应通过频次过滤处理,例如出现次数 < 10 的类别合并为 "other"。

4.2.3 IQR 异常检测代码示例

def detect_outliers_iqr(series):
    Q1 = series.quantile(0.25)
    Q3 = series.quantile(0.75)
    IQR = Q3 - Q1
    lower_bound = Q1 - 1.5 * IQR
    upper_bound = Q3 + 1.5 * IQR
    return (series < lower_bound) | (series > upper_bound)

# 示例:检测用户点击量异常
df['click_outlier'] = detect_outliers_iqr(df['last_7d_clicks'])

# 截断处理
click_col = 'last_7d_clicks'
lower = df[click_col].quantile(0.05)
upper = df[click_col].quantile(0.95)
df[click_col] = df[click_col].clip(lower, upper)
数据清洗核心流程:缺失值 + 异常值 原始特征数据 缺失值处理 异常值检测 处理策略 • 删除(行/列) • 统计填充(均值/中位数/众数) • 插值(线性/样条) 检测方法 • Z-Score / IQR 四分位距 • 孤立森林 / DBSCAN • 业务规则阈值 清洗后特征数据

4.3 重复数据去重

重复数据在推荐系统中主要来自:数据采集重试、多源数据合并、日志重复上报。重复数据会导致模型对某些样本过拟合,并扭曲统计特征。

4.3.1 去重策略

  • 完全去重:所有列完全相同的行只保留一条。
  • 基于主键去重:按 user_id + item_id + timestamp 等关键字段去重,保留最新或最早记录。
  • 模糊去重:对文本特征(如商品标题)用编辑距离或 SimHash 去重,保留质量最高的一条。

4.3.2 代码示例

# 完全去重
df.drop_duplicates(inplace=True)

# 基于主键去重:保留最后一条记录
df.drop_duplicates(subset=['user_id', 'item_id', 'date'], keep='last', inplace=True)

# 查看重复统计
print(f"去重前样本数: {len(df)}")
print(f"重复行数: {df.duplicated().sum()}")
print(f"去重后样本数: {len(df.drop_duplicates())}")
PD 模型实战要点:在构建训练样本时,务必先做重复数据去重,再做缺失值和异常值处理。否则重复数据可能掩盖真实的缺失比例和异常分布。
重复数据去重流程 原始数据 重复? 去重策略 策略选择 • 完全去重 • 主键去重(保留最新) 保留样本 去重后数据集

4.4 数据清洗在 PD 模型流水线中的位置

数据清洗不是一次性工作,而是特征流水线中的前置标准化步骤。在 PD 模型的生产环境中,清洗逻辑通常封装在特征计算层,确保离线训练和在线推理使用相同的清洗规则。

流水线阶段 清洗动作 输出
原始日志接入 去重、过滤无效记录 干净的事件流
特征计算 缺失值填充、异常值截断 稳定的特征值
特征存储 统一缺失标记、类型校验 可回溯的特征快照
训练样本构建 最终清洗、样本去重 高质量训练集
最佳实践:将清洗规则以配置文件(YAML/JSON)形式管理,版本化存储。每次清洗规则变更后,重新回溯历史特征,保证训练和推理数据分布一致。