4. 数据预处理基础:缺失值处理、异常值检测、重复数据处理、数据类型统一
在贷后预警坏样本分析中,原始数据通常来自多个业务系统(催收系统、账务系统、客户信息系统),存在大量脏数据。数据预处理是建模前的关键步骤,直接影响后续特征工程和模型效果。本章聚焦四个核心预处理任务:缺失值处理、异常值检测、重复数据处理和数据类型统一。
业务提示:贷后数据中,逾期天数、催收次数、客户收入等字段常出现缺失或异常。预处理阶段需结合业务逻辑判断,避免机械删除或填充导致信息失真。
4.1 缺失值处理
缺失值在贷后数据中普遍存在,原因包括:客户未提供信息、系统采集失败、数据迁移丢失等。处理策略分为删除、填充和模型预测三类。
| 策略 | 适用场景 | 贷后案例 | 注意事项 |
|---|---|---|---|
| 删除缺失行 | 缺失比例 < 5%,且随机缺失 | 客户手机号缺失(极少) | 删除后样本量仍满足建模需求 |
| 删除缺失列 | 缺失比例 > 70%,或业务意义弱 | 客户配偶信息缺失率 85% | 需确认该字段非关键特征 |
| 均值/中位数填充 | 数值型,分布对称或偏态 | 客户收入(中位数填充) | 偏态分布用中位数更稳健 |
| 众数填充 | 分类型,缺失比例低 | 客户职业类型 | 避免引入新类别 |
| 前向/后向填充 | 时间序列数据 | 逾期天数按时间顺序填充 | 需确保数据有序 |
| 模型预测填充 | 缺失字段与其他字段相关性高 | 用收入、年龄预测信用评分缺失值 | 计算成本高,避免过拟合 |
# Python 示例:缺失值处理
import pandas as pd
import numpy as np
# 加载贷后数据
df = pd.read_csv('post_loan_data.csv')
# 查看缺失率
missing_rate = df.isnull().mean().sort_values(ascending=False)
print("缺失率前5字段:\n", missing_rate.head())
# 删除缺失率 > 70% 的列
df.drop(columns=['spouse_info'], inplace=True)
# 中位数填充收入字段
df['income'].fillna(df['income'].median(), inplace=True)
# 众数填充职业字段
df['occupation'].fillna(df['occupation'].mode()[0], inplace=True)
# 前向填充逾期天数(按客户ID分组后按时间排序)
df.sort_values(['customer_id', 'date'], inplace=True)
df['overdue_days'].fillna(method='ffill', inplace=True)
4.2 异常值检测
贷后数据中的异常值可能源于:录入错误、极端逾期行为、系统故障等。异常值会扭曲统计分布和模型参数,需结合业务规则和统计方法识别。
常用检测方法
- 业务规则法:逾期天数 > 365、年龄 > 120 或 < 18、催收次数为负数等,直接判定为异常。
- Z-Score 法:适用于近似正态分布,|Z| > 3 视为异常。
- IQR 四分位距法:适用于偏态分布,超出 Q1-1.5×IQR 或 Q3+1.5×IQR 为异常。
- 孤立森林:适用于高维数据,无分布假设。
# Python 示例:异常值检测
from scipy import stats
import numpy as np
# Z-Score 法检测逾期天数异常
z_scores = np.abs(stats.zscore(df['overdue_days']))
outliers_z = df[z_scores > 3]
print(f"Z-Score 检测异常数:{len(outliers_z)}")
# IQR 法检测收入异常
Q1 = df['income'].quantile(0.25)
Q3 = df['income'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers_iqr = df[(df['income'] < lower_bound) | (df['income'] > upper_bound)]
print(f"IQR 检测异常数:{len(outliers_iqr)}")
# 异常值处理:对极端值进行截尾(Winsorize)
df['income_winsorized'] = df['income'].clip(lower=lower_bound, upper=upper_bound)
风险提示:贷后场景中,部分“异常值”可能是真实的高风险样本(如逾期 300 天以上)。建议先标记异常,由业务专家复核,而非直接删除或替换。
4.3 重复数据处理
重复数据通常由数据合并、系统重复录入或接口重试导致。贷后分析中,重复记录会高估样本量、扭曲统计指标。
| 重复类型 | 定义 | 贷后示例 | 处理方式 |
|---|---|---|---|
| 完全重复 | 所有字段完全相同 | 催收记录重复导入 | 保留第一条,删除其余 |
| 部分重复 | 关键字段相同,其他字段有差异 | 同一客户多条逾期记录,日期不同 | 按业务规则合并或保留最新 |
| 近似重复 | 字段相似但不完全相同 | 客户姓名、身份证号略有差异 | 使用模糊匹配(如 Levenshtein 距离) |
# Python 示例:重复数据处理
# 完全重复删除
df.drop_duplicates(inplace=True)
# 按客户ID和日期去重,保留最新记录
df.sort_values('date', ascending=False, inplace=True)
df.drop_duplicates(subset=['customer_id', 'loan_id'], keep='first', inplace=True)
# 近似重复检测(使用 recordlinkage 库)
import recordlinkage
indexer = recordlinkage.Index()
indexer.block('customer_id') # 按客户ID分块
pairs = indexer.index(df)
compare = recordlinkage.Compare()
compare.string('name', 'name', method='jarowinkler', label='name_score')
compare.exact('id_card', 'id_card', label='id_card_match')
features = compare.compute(pairs, df)
# 筛选高相似度记录
potential_duplicates = features[features['name_score'] > 0.9]
4.4 数据类型统一
数据类型不一致会导致计算错误、内存浪费和模型报错。贷后数据常见问题包括:数值型存为字符串、日期格式不统一、分类变量编码混乱。
统一步骤
- 数值型字段:将字符串转为 float/int,处理千分位逗号、货币符号。
- 日期字段:统一为 datetime 类型,指定格式(如 %Y-%m-%d)。
- 分类字段:转为 category 类型,降低内存占用。
- 布尔字段:统一为 bool 类型,处理“是/否”、“1/0”等映射。
# Python 示例:数据类型统一
# 查看当前数据类型
print(df.dtypes)
# 数值型转换:去除逗号和货币符号
df['loan_amount'] = df['loan_amount'].str.replace(',', '').str.replace('¥', '').astype(float)
# 日期统一
df['overdue_date'] = pd.to_datetime(df['overdue_date'], format='%Y-%m-%d', errors='coerce')
# 分类变量优化
df['customer_grade'] = df['customer_grade'].astype('category')
# 布尔映射
df['is_default'] = df['is_default'].map({'是': True, '否': False, '1': True, '0': False}).astype(bool)
# 验证转换结果
print("转换后数据类型:\n", df.dtypes)
print("内存占用减少:", df.memory_usage(deep=True).sum() / 1024**2, "MB")
最佳实践:贷后预警项目中,建议将预处理逻辑封装为可复用的 Pipeline,每次新数据接入时自动执行。同时保留原始数据副本,便于回溯和审计。