数据预处理:缺失值处理、异常值检测与处理

说实话,做图谱变量衍生之前,大部分人会把精力花在特征创造上。但我个人的经验是,数据预处理没做好,后面所有工夫都可能白费。你想想看,一个带缺失值的变量衍生出10个新特征,那10个特征里全是空洞。异常值更狠,它可能直接把你的分箱边界拉到天上。

所以这一章,咱们就把缺失值和异常值这两块硬骨头啃下来。我会结合自己在信贷评分、用户画像项目中的真实踩坑经历,讲清楚什么时候该删、什么时候该补、什么时候该标记。

核心原则:预处理不是流水线操作,每一步都要有业务判断。别上来就均值填充,也别看到异常值就一删了之。

2.1 缺失值处理:是补还是删?

缺失值在真实数据里太常见了。用户没填生日、设备没上报操作系统版本、交易记录缺了商户类别……每个字段缺失的原因都不一样。

我个人的分类方法:

缺失类型 典型场景 我的建议
完全随机缺失 (MCAR) 系统故障导致部分记录丢失 如果样本量充足,直接删除行;否则用均值/中位数填充
随机缺失 (MAR) 女性用户更少填收入字段 用其他特征预测填充(如回归、KNN)
非随机缺失 (MNAR) 高收入人群故意不填收入 新增“是否缺失”标志位,再填充一个占位值(如-1)

嗯,这里要注意:图谱变量里很多节点本身就是从原始字段衍生出来的。如果你在原始字段上用均值填充了缺失值,那么衍生出来的图特征也会继承这种“平均化”的错觉,反而削弱了区分能力。所以在图谱场景下,我倾向于对缺失值单独做标志位,而不是一味填充。

踩坑回忆:我曾经在做社交网络图谱时,对用户“关注数”字段做了线性插值填充缺失值,结果导致后续PageRank计算出现大量重复值。后来改成“关注数缺失=0+标志位”,效果反而更好。

常见的缺失值处理方法

  • 删除法:行删除(listwise)或列删除。适合缺失比例小于5%且MCAR的情况。记得先检查一下删除后样本分布有没有偏移。
  • 统计填充:均值/中位数/众数。简单粗暴,但会降低方差,不适合用在需要保留极端值的场景(如收入、年龄)。
  • 模型预测填充:用随机森林、KNN等模型预测缺失值。我在风控项目中常用KNN(k=5),效果比均值好不少。
  • 多重插补 (MICE):产生多个填充版本,最后取平均或做不确定性分析。适合学术研究,工程上跑得慢。

代码实现其实不复杂。拿Python举例,pandas里直接 df.isnull().sum() 就能看到缺失分布。我个人习惯写一个函数,先区分缺失类型,再决定填充策略。

import pandas as pd
import numpy as np

def handle_missing(df, strategy='auto'):
    """
    auto策略: 数值列用中位数填充,分类列用众数填充,同时生成缺失标志列
    """
    missing_cols = df.columns[df.isnull().any()]
    for col in missing_cols:
        df[f'{col}_missing'] = df[col].isnull().astype(int)
        if df[col].dtype in ['int64', 'float64']:
            df[col].fillna(df[col].median(), inplace=True)
        else:
            df[col].fillna(df[col].mode()[0], inplace=True)
    return df
小技巧:对图谱变量,可以在节点表里新增一个“缺失特征向量”,每个维度表示该节点哪些原始字段有缺失。这样GNN在聚合邻居信息时能自动学习缺失模式。

2.2 异常值检测:别让一个离群点毁掉整张图谱

异常值在图谱变量衍生里尤其致命。一个用户的交易金额是正常值的100倍,如果直接用它衍生的图谱变量(如平均交易金额),整个邻域的特征都会被拉偏。

为什么会这样?因为图谱里的聚合操作(求和、平均)天然对异常值敏感。你想想看,一个强连通分量里只要有一个异常节点,它的影响力会顺着边传播到几十个邻居。

三大检测方法

方法 适用场景 阈值/参数
Z-score 法 近似正态分布的数据 |Z| > 3 视为异常
IQR 箱线图法 偏态分布,稳健 下界 Q1-1.5IQR,上界 Q3+1.5IQR
DBSCAN 聚类法 高维空间或图谱嵌入 eps 和 minPts 需调参

我个人在金融风控里最常用的是 IQR 法,因为它不依赖正态假设,对长尾分布很友好。不过要注意:当数据本身就有业务含义的极端值时(比如黑产账户的交易次数可能极少),IQR 会把这些正常值也误判为异常。

踩坑回忆:有一次做电商图谱的“购买力”变量,发现top 1%用户的年消费金额在200万以上。按IQR法这些全是异常值,但业务上这些就是真实的高净值用户。解决方案:对变量做对数变换后再检测异常,或者直接把异常阈值设为业务定义的99.9%分位数。

异常值的处理策略

  • 删除:仅当你能确认这是数据录入错误或系统噪声。记得记录删除日志,便于回溯。
  • 截尾(Winsorization):将超过上下界的值替换为边界值。比如对收入变量,把大于99%分位数的值设为99%分位数。
  • 分箱:直接把连续值离散化,异常值自然落在最边缘的箱子里。自动分箱章节会详细讲。
  • 标记:新增一个二值特征“是否异常”,保留原始值。这样模型可以自己学习异常权重。

如果后续要做图谱变量衍生,我强烈建议采用“截尾+标记”组合。先对原始字段做截尾,再衍生图谱特征;同时把是否异常的标记也作为节点特征喂给模型。这样既控制了异常的影响,又不丢失信息。

2.3 缺失与异常联合处理的流程

实际项目中,缺失值和异常值往往是共生关系。一个字段如果大量缺失,那它本身可能就是异常记录的一部分。我的预处理流水线通常是这样的:

图:缺失值与异常值联合处理流程 原始数据 步骤1:缺失值检测与填充/删除 生成缺失标志列 步骤2:异常值检测(IQR/Z-score) 截尾 / 分箱 / 标记异常 干净数据 + 标记特征

这张图代表了我个人在项目中实际跑通的流水线。注意两个关键点:

  • 缺失值处理一定要先生成标志列,再填充。这样模型能知道哪些值是“人工补的”。
  • 异常值检测放在缺失值之后,因为填充后的数据分布更稳定,异常阈值不会因为缺失值而偏移。

2.4 实操中的避坑清单

我曾遇到过的一个典型问题:对时间序列图谱变量做缺失值填充,用了前向填充法(forward fill)。结果有一条长边的缺失时间窗口超过30天,填充时把30天前的值一直往后推,导致图上的时间特征完全错位。后来我改成:连续缺失超过5个时间点就标记为“异常时间窗口”,用前后平均值填充。

整理成清单给大家参考:

场景 坑 对策
数值型缺失率>50% 填充会引入大量噪声 直接删除该列,或新建二值“是否有值”特征
类别型缺失 用众数填充会掩盖稀有类别 新增“未知”类别(字符串"unknown")
极端异常值 截尾后改变业务含义 先和业务确认合理上限,用业务阈值而非统计阈值
多重共线性与异常 高相关特征中的异常会相互放大 先对特征做PCA降维,再在低维空间检测异常
我的经验数值:对于图谱变量衍生,建议缺失标志位占最终特征总数的5%-10%,太多了会稀释有效信息。异常检测的阈值可以设松一点(比如IQR的倍数设为2.0而不是1.5),因为图谱聚合有一定的平滑能力。

好了,到这步你的数据已经清洗干净了,接下来就可以进入真正的图谱变量衍生环节。不过在那之前,别忘了对清洗后的数据做一次分布检查——把缺失和异常处理前后的分布对比图打印出来,能帮你快速发现问题。嗯,这个习惯我一直保留着。