3. 数据探索与预处理:加载逾期数据集、数据清洗、缺失值处理、异常值检测与处理

数据质量直接决定模型性能的上限。本章将系统性地对原始逾期数据集进行加载、清洗、缺失值填充与异常值处理,为后续特征工程与模型训练打下坚实基础。

3.1 数据集加载与初步探索

我们使用 pandas 加载 CSV 格式的逾期样本数据,并快速了解数据的基本结构与统计特征。

import pandas as pd
import numpy as np

# 加载数据集
df = pd.read_csv('loan_default_data.csv', encoding='utf-8')

# 基础信息查看
print('数据集形状:', df.shape)
print('列名:\n', df.columns.tolist())
print('数据类型:\n', df.dtypes.value_counts())
print('前5行:\n', df.head())

加载后,通过 df.info() 与 df.describe() 快速掌握字段缺失情况与数值分布。

检查项 方法 目的
形状与列名 df.shape, df.columns 确认样本量与特征数量
数据类型 df.dtypes 识别数值型、类别型、时间型字段
缺失统计 df.isnull().sum() 定位缺失字段与缺失比例
数值分布 df.describe() 发现异常极值、标准差过大字段
实践建议: 加载后先检查 dtypes,确保日期字段被正确解析为 datetime 类型,避免后续时间特征提取出错。

3.2 数据清洗:重复值与格式统一

数据清洗主要处理重复记录与字段格式不一致问题。重复样本会导致模型过拟合于特定模式,降低泛化能力。

# 检查重复行
dup_count = df.duplicated().sum()
print(f'重复行数: {dup_count}')

# 删除重复行
df = df.drop_duplicates()

# 统一字符串格式:去除首尾空格、统一大小写
df['employment_type'] = df['employment_type'].str.strip().str.upper()

# 统一日期格式
df['loan_date'] = pd.to_datetime(df['loan_date'], errors='coerce')
清洗要点:
  • 重复行:使用 df.duplicated() 检测,drop_duplicates() 删除。
  • 字符串字段:去除空格、统一大小写、替换特殊符号。
  • 日期字段:强制转换为 datetime,无效日期设为 NaT 后续处理。

3.3 缺失值处理策略

缺失值处理需结合字段含义与缺失比例选择合适方法。以下为常见策略对比:

缺失比例 推荐策略 适用场景
< 5% 删除缺失行 样本量充足,缺失随机
5% - 30% 均值/中位数/众数填充 数值型用中位数,类别型用众数
30% - 60% 模型预测填充或KNN插补 字段与其他特征相关性较强
> 60% 删除该字段 信息量过低,易引入噪声
# 数值型字段:用中位数填充
df['annual_income'].fillna(df['annual_income'].median(), inplace=True)

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

# 时间字段:用前向填充
df['loan_date'].fillna(method='ffill', inplace=True)

# 删除缺失比例超过60%的字段
threshold = 0.6
df = df.dropna(thresh=len(df) * (1 - threshold), axis=1)
注意: 填充缺失值后务必检查填充后的分布是否发生剧烈偏移。对于信用评分、收入等敏感字段,建议保留缺失指示列(is_missing 特征),供模型学习缺失模式。

3.4 异常值检测与处理

异常值会严重干扰模型训练,尤其在逾期预测中,极端收入或负债值可能导致错误决策。常用检测方法包括 IQR 规则与 Z-Score 方法。

3.4.1 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)

# 检测 'loan_amount' 字段异常值
outliers_iqr = detect_outliers_iqr(df['loan_amount'])
print(f'IQR 检测异常值数量: {outliers_iqr.sum()}')

3.4.2 Z-Score 方法(适用于近似正态分布)

from scipy import stats

z_scores = np.abs(stats.zscore(df['annual_income']))
outliers_z = z_scores > 3
print(f'Z-Score 检测异常值数量: {outliers_z.sum()}')
方法 适用分布 阈值 优点 缺点
IQR 偏态分布 1.5×IQR 不受极端值影响 对样本量敏感
Z-Score 近似正态 3 或 3.5 计算简单 受极端值影响大

3.4.3 异常值处理策略

# 策略1:截尾(Winsorize)—— 将异常值替换为边界值
from scipy.stats.mstats import winsorize
df['loan_amount_winsorized'] = winsorize(df['loan_amount'], limits=[0.01, 0.01])

# 策略2:删除异常样本(仅当异常比例 < 1% 时使用)
df_clean = df[~outliers_iqr]

# 策略3:视为缺失值,后续用填充策略处理
df.loc[outliers_iqr, 'loan_amount'] = np.nan
df['loan_amount'].fillna(df['loan_amount'].median(), inplace=True)
推荐流程: 先使用 IQR 检测偏态分布字段(如收入、贷款金额),再使用 Z-Score 检测近似正态字段(如年龄、信用分)。处理时优先截尾或视为缺失,避免直接删除过多样本。
数据预处理全流程 原始数据集 数据清洗 缺失值处理 异常值检测 异常值处理 干净数据集 去重、格式统一 中位数/众数/模型填充 IQR / Z-Score 截尾 / 删除 / 视为缺失

3.5 预处理结果验证

完成所有步骤后,需对预处理结果进行验证,确保数据质量满足建模要求。

# 验证缺失值已处理
print('剩余缺失值:\n', df.isnull().sum().max())

# 验证异常值已处理
print('异常值比例:', outliers_iqr.sum() / len(df))

# 验证数据形状
print('最终数据集形状:', df.shape)

# 保存清洗后数据
df.to_parquet('clean_loan_data.parquet', index=False)
最佳实践: 将预处理逻辑封装为 DataPreprocessor 类,包含 fit() 和 transform() 方法,便于在训练与推理阶段保持处理一致性。后续实时 API 发布时,可直接复用该预处理管线。
缺失值与异常值处理决策树 检查字段缺失/异常 缺失值处理 比例 < 5% → 删除行 5%-30% → 中位数/众数 30%-60% → 模型预测 > 60% → 删除字段 异常值处理 偏态分布 → IQR 正态分布 → Z-Score 截尾 / 删除 / 视为缺失