3. 数据采集与探索:SQL提取数据、数据质量初探、缺失值/异常值初步诊断
做信用评分卡,第一件事不是建模,而是把数据搞到手、把数据看清楚。我记得刚入行那会儿,拿到一个几千张表的数据仓库,连主键都找不到,愣是花了三天才把申请单和逾期记录关联上。后来我学乖了——先画好取数逻辑,再写SQL,能省不少返工时间。
3.1 从数据库到数据框:SQL提取实战
风控数据通常散落在多个表里:申请主表、征信接口表、历史交易表等等。我自己的习惯是先建一个取数逻辑文档,把需要的字段、表名、关联键列清楚,再动手写SQL。核心思路是——左连接主表,其他表按客户维度拼进来。
举个常见的取数场景:我们需要客户ID、年龄、收入、近6个月逾期次数、当前负债金额。对应的SQL大概长这样:
SELECT
a.customer_id,
a.age,
a.income,
b.overdue_6m,
c.current_debt
FROM application_main a
LEFT JOIN (
SELECT customer_id, COUNT(*) AS overdue_6m
FROM overdue_log
WHERE overdue_date >= DATEADD(month, -6, GETDATE())
GROUP BY customer_id
) b ON a.customer_id = b.customer_id
LEFT JOIN (
SELECT customer_id, SUM(debt_balance) AS current_debt
FROM debt_account
WHERE status = 'active'
GROUP BY customer_id
) c ON a.customer_id = c.customer_id
WHERE a.application_date >= '2023-01-01'
你在实际项目中会遇到一些“坑”:关联字段有空值、同一个人有多个申请记录、同一笔贷款被重复统计……这些都会让结果出现脏数据。别慌,我们后面会讲怎么清理。
3.2 数据质量初探:看一眼就知道有没有坑
数据取上来后,我一般先做三件事:看行数、看唯一值、看空值占比。用pandas几行代码就能扫出大概:
import pandas as pd
df = pd.read_sql("SELECT * FROM base_wide_table", conn)
print(f"行数: {df.shape[0]}, 列数: {df.shape[1]}")
print(df.info())
print(df.describe(include='all').T)
扫一眼结果,你就能发现很多问题:
| 检查项 | 常见问题 | 怎么发现的 |
|---|---|---|
| 缺失值 | 部分字段空值比例超过50% | df.isnull().mean() > 0.5 |
| 异常值 | 年龄为-1、收入为0或1亿 | describe() 的 min/max 离谱 |
| 重复记录 | 同一个客户出现多次 | df.duplicated().sum() |
| 类型错误 | 电话号码被读成数值型 | df.dtypes 检查object比预期少 |
嗯,这里要注意——缺失值和异常值是信用评分卡最常见的数据灾难来源。我在一个汽车金融项目里,发现客户“工作年限”字段有30%的缺失,还有一堆负数,如果直接建模,最后跑出来的评分卡可能会把刚工作一个月的人当成优质客户——你想想看,多可怕。
3.3 缺失值诊断:找到“沉默”的数据
缺失不等于没用。你需要问自己几个问题:
- 缺失是随机的吗? 比如征信数据没查到位,缺了就缺了,和客户资质无关。
- 缺失原因是什么? 是系统没采集到,还是客户拒绝提供?
- 缺失率多少时该放弃这个变量? 我个人把阈值定在70%——超过70%的缺失,直接扔掉,不然填充出来也是假的。
代码层面,我习惯先画一张缺失值热图,直观看看哪些列缺失严重,哪些组合一起缺失:
import missingno as msno
msno.matrix(df, figsize=(12,6))
# 白色条纹代表缺失,越密越严重
另外,我还会算一下缺失模式:比如“工作年限”和“单位性质”同时缺失,说明这条记录可能是无业人群——这不一定是缺失,反而是个有意义的特征。我曾经就靠这个模式构造了一个“信息完整度分数”,直接作为入模变量,还提升了几个点的KS。
3.4 异常值诊断:揪出“不老实”的记录
我见过最离谱的异常:一个客户的年收入填了999亿,还通过了人工审批——后来发现是录入员多按了三个9。异常值大多来自三种情况:录入错误、系统截断、业务定义的边缘情况。
诊断方法我一般三种一起用:
- 箱线图+IQR:超过Q3+1.5*IQR或低于Q1-1.5*IQR的视为异常。适用于连续型变量。
- Z-Score:|Z|>3的数据点。适用于近似正态分布的变量。
- 业务阈值:比如年龄50岁的人孩子年龄填了40,明显矛盾。这个只能靠业务逻辑硬卡。
代码实现很简单:
# 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
anomaly = df[(df['income'] < lower_bound) | (df['income'] > upper_bound)]
print(f"异常值数量: {len(anomaly)}")
顺便提一嘴——异常值也可能是宝藏。比如信用卡申请时,有些人故意填一个很小的收入来隐藏真实水平,这种人往往被归为高风险,反欺诈模型就用得上这个异常信号。所以,动手之前先想想数据的业务含义。
做完以上几步,你的数据基本能“上战场”了。但记住——这只是初步诊断。后面我们还会在变量分箱和WOE转换时再次审视这些异常值,因为它们可能会直接影响分的单调性和稳定性。