4. 数据质量初探:缺失值统计、异常值检测、重复记录处理、数据分布概览
在信用评分建模中,数据质量直接决定模型的上限。原始数据通常包含缺失值、异常值、重复记录以及分布偏移等问题。本章将系统性地介绍如何利用 Spark 进行数据质量的初步探查,为后续的特征工程和建模奠定可靠基础。
4.1 缺失值统计
缺失值是信用评分数据中最常见的问题之一。缺失可能源于数据采集失败、用户未填写、系统错误等。在 Spark 中,我们通常使用 DataFrame 的 summary() 或自定义聚合函数来统计每列的缺失比例。
关键指标: 缺失率 = 缺失记录数 / 总记录数。通常缺失率 > 50% 的字段会被直接剔除;缺失率在 5%-50% 之间的字段需要填充或编码;缺失率 < 5% 的字段可直接删除对应行或使用均值/中位数填充。
4.1.1 缺失值统计实现
from pyspark.sql import functions as F
# 统计每列缺失数量与缺失率
def missing_stats(df):
total_rows = df.count()
stats = []
for col_name in df.columns:
missing_count = df.filter(F.col(col_name).isNull() | F.isnan(col_name)).count()
missing_rate = round(missing_count / total_rows, 4)
stats.append((col_name, missing_count, missing_rate))
return spark.createDataFrame(stats, schema=["column", "missing_count", "missing_rate"])
missing_df = missing_stats(df)
missing_df.filter("missing_rate > 0").orderBy("missing_rate", ascending=False).show(10)
| 字段名 | 缺失数量 | 缺失率 | 处理建议 |
|---|---|---|---|
| income | 12,340 | 0.1234 | 中位数填充或模型预测填充 |
| employment_length | 8,567 | 0.0857 | 众数填充或单独编码为"未知" |
| credit_score | 1,234 | 0.0123 | 删除对应行或均值填充 |
| zip_code | 56,789 | 0.5679 | 缺失率过高,建议剔除该字段 |
4.2 异常值检测
异常值会严重干扰信用评分模型的稳定性。常见的异常值检测方法包括:Z-Score 法、IQR 四分位距法、以及基于业务规则的阈值法。在 Spark 中,我们通常使用窗口函数和聚合函数进行高效检测。
4.2.1 IQR 异常值检测
# 计算每列的 Q1、Q3 和 IQR
quantiles = df.approxQuantile("loan_amount", [0.25, 0.75], 0.01)
Q1, Q3 = quantiles[0], quantiles[1]
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 标记异常值
df_with_outlier_flag = df.withColumn(
"is_outlier",
(F.col("loan_amount") < lower_bound) | (F.col("loan_amount") > upper_bound)
)
outlier_count = df_with_outlier_flag.filter("is_outlier = true").count()
print(f"loan_amount 字段异常值数量: {outlier_count}")
信用评分场景提示: 对于收入、负债率等字段,建议结合业务规则设定上下限。例如,年收入低于 1 万元或高于 1000 万元的记录应标记为可疑,而非直接删除,需人工复核。
4.3 重复记录处理
重复记录会导致模型对某些样本过度学习,造成过拟合。重复可能来源于数据合并时的重复导入、同一用户多次申请等。Spark 中通过 dropDuplicates() 方法可以快速去重,但需要谨慎选择去重键。
4.3.1 重复记录检测与去重
# 基于所有字段的完全重复检测
total_duplicates = df.count() - df.distinct().count()
print(f"完全重复记录数: {total_duplicates}")
# 基于业务键的部分重复检测(如用户ID + 申请日期)
from pyspark.sql.window import Window
window_spec = Window.partitionBy("user_id", "application_date").orderBy("application_timestamp")
df_with_dup_rank = df.withColumn("dup_rank", F.row_number().over(window_spec))
# 保留每个用户每天的最新一条记录
df_deduplicated = df_with_dup_rank.filter("dup_rank = 1").drop("dup_rank")
| 重复类型 | 检测方法 | 处理策略 | 适用场景 |
|---|---|---|---|
| 完全重复 | df.count() - df.distinct().count() |
直接删除 | 数据导入异常 |
| 部分重复(业务键相同) | 窗口函数 + row_number | 保留最新或最早记录 | 同一用户多次申请 |
| 近似重复(文本相似度) | SimHash / MinHash | 人工审核或聚类去重 | 地址、公司名称等文本字段 |
4.4 数据分布概览
数据分布概览帮助我们理解每个特征的取值范围、集中趋势和离散程度。Spark 的 summary() 函数可以快速输出数值型字段的统计量,但对于信用评分场景,我们还需要关注分布的形状和偏态。
4.4.1 分布统计与可视化准备
# 使用 summary 快速获取统计量
df.select("loan_amount", "age", "income", "credit_score").summary().show()
# 自定义分位数统计
quantiles_list = [0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99]
for col_name in ["loan_amount", "income"]:
quantiles = df.approxQuantile(col_name, quantiles_list, 0.01)
print(f"{col_name} 分位数: {dict(zip(quantiles_list, quantiles))}")
注意: 信用评分数据通常呈现右偏分布(如收入、贷款金额),此时均值会远大于中位数。在后续特征工程中,建议对这类字段进行对数变换或 Box-Cox 变换,使其更接近正态分布。
4.5 综合质量报告与决策
完成上述四个维度的探查后,建议生成一份综合数据质量报告,为后续的特征工程提供决策依据。以下是一个典型的报告模板:
| 字段名 | 缺失率 | 异常值比例 | 重复率 | 分布形态 | 综合评级 | 处理建议 |
|---|---|---|---|---|---|---|
| loan_amount | 0.5% | 2.1% | 0.0% | 右偏 | B | 对数变换 + 异常值截断 |
| income | 12.3% | 1.8% | 0.1% | 严重右偏 | C | 中位数填充 + 对数变换 |
| age | 0.0% | 0.3% | 0.0% | 近似正态 | A | 无需处理 |
| zip_code | 56.8% | — | — | — | D | 剔除该字段 |
最佳实践: 在信用评分项目中,建议将数据质量探查脚本封装为可复用的 Spark 作业,每次数据更新后自动运行并生成报告。同时,将质量规则(如缺失率阈值、异常值上下限)配置在外部文件中,便于业务人员调整。
通过以上四个维度的系统探查,我们能够全面了解数据的健康状况,为后续的缺失值填充、异常值处理、特征构造和模型训练提供清晰的决策依据。在下一阶段,我们将基于这些质量报告,进行具体的特征工程操作。