3、数据预处理基础:缺失值处理、异常值检测、数据标准化与归一化
各位同学,咱们直接进入正题。
做金融关系网络,数据预处理是绕不开的第一道坎。我见过太多人,模型调得飞起,结果数据一塌糊涂,最后全白干。说白了,你喂进去的是垃圾,出来的只能是更精致的垃圾。
这一章,咱们就把地基打牢。我会把缺失值、异常值、标准化和归一化这几个核心操作,掰开揉碎了讲清楚。
3.1 缺失值处理:别让「空」坑了你
金融数据里,缺失值太常见了。比如某个交易对手的信用评级没填,或者某笔贷款的抵押物价值字段是空的。你想想看,这些空位如果不处理,模型直接报错,或者学出一些奇怪的东西。
我个人习惯,先分清楚缺失的类型:
- 完全随机缺失(MCAR):比如系统抽风,丢了几条记录。这种最简单,直接删掉或填充都行。
- 随机缺失(MAR):缺失跟其他变量有关。比如高收入人群更不愿意填收入字段。这时候不能乱删,会引入偏差。
- 非随机缺失(MNAR):缺失值本身就有信息。比如坏账客户故意不填联系方式。嗯,这里要小心。
处理策略,我一般按优先级来:
- 直接删除:缺失比例小于5%,且数据量够大。简单粗暴,但别滥用。
- 均值/中位数填充:连续型变量用中位数更稳健。我在项目中遇到过,用均值填充被极端值带偏,后来改成中位数就好多了。
- 众数填充:分类变量,比如「行业类型」,用出现最多的那个值。
- 模型预测填充:用KNN或回归模型,把缺失字段当目标变量来预测。精度高,但计算量大。
- 标记缺失:单独加一列「是否缺失」,有时候缺失本身就是一个特征。
避坑指南:我曾经在构建企业担保网络时,直接删除了所有含缺失值的样本。结果发现,那些缺失担保金额的企业,恰恰是风险最高的。删掉它们,模型直接失效。后来我改用「标记缺失+中位数填充」,效果才正常。
# Python 示例:缺失值处理
import pandas as pd
import numpy as np
# 模拟金融关系网络数据
data = {
'企业ID': ['A001', 'A002', 'A003', 'A004'],
'担保金额(万)': [500, np.nan, 300, np.nan],
'信用评级': ['AA', 'A', np.nan, 'BBB'],
'关系强度': [0.8, 0.5, 0.9, np.nan]
}
df = pd.DataFrame(data)
# 1. 查看缺失情况
print(df.isnull().sum())
# 2. 中位数填充连续变量
df['担保金额(万)'].fillna(df['担保金额(万)'].median(), inplace=True)
# 3. 众数填充分类变量
df['信用评级'].fillna(df['信用评级'].mode()[0], inplace=True)
# 4. 标记缺失列
df['关系强度_缺失'] = df['关系强度'].isnull().astype(int)
df['关系强度'].fillna(df['关系强度'].median(), inplace=True)
print(df)
3.2 异常值检测:揪出那些「捣乱分子」
金融网络里,异常值往往意味着欺诈、极端风险或者数据录入错误。比如一个企业的担保金额突然比同行高100倍,或者一笔交易的频率异常高。
我常用的方法有几种,各有各的适用场景:
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Z-Score | 数据近似正态分布 | 简单直观 | 对极端值敏感 |
| IQR(四分位距) | 非正态分布,有偏数据 | 稳健,不受极端值影响 | 需要手动设定阈值 |
| 孤立森林 | 高维数据,非线性关系 | 速度快,效果好 | 结果不易解释 |
| DBSCAN聚类 | 空间或网络结构数据 | 能发现局部异常 | 参数敏感 |
我个人最常用的是IQR法。为什么?因为金融数据很少是完美的正态分布,总有长尾。Z-Score容易被几个极端值带偏,而IQR基于分位数,稳健得多。
实战技巧:在金融关系网络中,异常值不一定要删除。有时候异常值代表「关键节点」或「风险信号」。我建议先标记,再单独分析。比如,担保金额异常高的企业,可能是核心担保方,也可能是风险传导的枢纽。
# Python 示例:IQR 异常值检测
def detect_outliers_iqr(data, column, factor=1.5):
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - factor * IQR
upper_bound = Q3 + factor * IQR
outliers = data[(data[column] < lower_bound) | (data[column] > upper_bound)]
return outliers
# 模拟数据
df['交易金额'] = [100, 200, 150, 5000, 180, 210, 99999]
outliers = detect_outliers_iqr(df, '交易金额')
print(f"检测到 {len(outliers)} 个异常值")
print(outliers)
3.3 数据标准化与归一化:让所有特征「平起平坐」
金融关系网络的特征,量纲差异巨大。比如「担保金额」可能是几千万,而「关系强度」只有0到1。如果不做处理,模型会天然认为数值大的特征更重要——这显然是错的。
这里有两个核心概念,别搞混了:
- 归一化(Normalization):把数据缩放到[0,1]区间。适合数据有明确边界的情况,比如像素值、比例值。
- 标准化(Standardization):把数据变成均值为0、标准差为1的分布。适合数据有异常值,或者需要用到距离计算的算法(如KNN、SVM)。
我一般怎么选?
- 如果后续用树模型(随机森林、XGBoost),其实标准化不是必须的,树模型对尺度不敏感。
- 如果用神经网络、逻辑回归、KNN,必须做标准化。否则模型训练会震荡,甚至不收敛。
- 如果数据有异常值,标准化比归一化更稳健。因为归一化会被极端值压缩其他值。
注意:标准化和归一化,一定要先拆分训练集和测试集,再用训练集的参数去转换测试集。千万别在整个数据集上做,否则会造成数据泄露,模型评估结果虚高。
# Python 示例:标准化与归一化
from sklearn.preprocessing import StandardScaler, MinMaxScaler
import numpy as np
# 模拟特征数据
features = np.array([[500, 0.8],
[300, 0.5],
[99999, 0.9],
[200, 0.1]])
# 标准化
scaler_std = StandardScaler()
features_std = scaler_std.fit_transform(features)
print("标准化结果:\n", features_std)
# 归一化
scaler_mm = MinMaxScaler()
features_mm = scaler_mm.fit_transform(features)
print("归一化结果:\n", features_mm)
3.4 本章知识体系总览
为了让你更直观地理解这三块内容的关系,我画了一张流程图。你可以看到,数据预处理不是孤立的步骤,而是一个环环相扣的流程。
这张图把三个步骤串起来了。你做完缺失值处理,再去检测异常值,最后做尺度变换。顺序别搞反了——如果先标准化再处理异常值,异常值会被「压缩」进正常范围,你就找不到了。
总结一句话:数据预处理没有银弹。每个方法都有适用场景,关键是你得理解你的数据。我在金融网络项目中,经常要反复迭代:先做一轮基础清洗,建模后发现效果不好,再回头检查数据质量。这是常态,别指望一步到位。