3. 数据采集与EDA:数据源介绍、数据采集方法、探索性数据分析(EDA)实战
各位同学,欢迎来到第三章。
前面两章我们聊了风控的底层逻辑和指标体系。说白了,那些都是“道”。从这章开始,我们要碰“术”了——也就是实实在在的数据。
我经常跟团队里的小朋友说:风控模型做得再好,也架不住数据是坨屎。这话糙,但理不糙。数据采集和EDA(探索性数据分析),就是给模型打地基的活儿。地基不稳,楼盖得再高也得塌。
3.1 数据源介绍:你的“弹药”从哪来?
做金融风控,数据就是你的子弹。没子弹,你枪法再好也没用。我个人习惯把数据源分成三大类:
3.1.1 内部数据
这是你最宝贵的资产。说白了,就是用户在你自家平台留下的“脚印”。
- 用户基本信息:姓名、身份证、手机号、住址。这些是基础,但要注意,很多用户会填假信息。我在项目中遇到过,有人填的地址是“北京市朝阳区火星路1号”,一看就是假的。
- 交易流水:消费金额、频次、时间、商户类型。这是判断用户消费能力和稳定性的关键。
- 借贷历史:借了多少、还了多少、有没有逾期。这是最直接的信用表现。
- 行为日志:登录时间、浏览页面、点击行为。你想想看,一个半夜三点频繁登录、反复修改资料的用户,风险是不是很高?
3.1.2 外部数据
光靠内部数据,视野太窄。外部数据能帮你补全用户画像。
- 央行征信:最权威的信用数据。但覆盖人群有限,很多年轻人没有征信记录。
- 第三方征信:比如百行征信、朴道征信。这些是央行的补充,数据维度更丰富。
- 运营商数据:通话时长、联系人数量、手机号使用时长。嗯,这里要注意,运营商数据涉及隐私,采集时必须合规。
- 电商数据:淘宝、京东的消费记录。能反映用户的消费能力和消费偏好。
- 社交数据:微信、微博的社交关系。比如,你的好友里如果有大量逾期用户,你的风险也会升高。
3.1.3 替代数据
这是近几年兴起的。专门服务那些“征信白户”——没有信用卡、没有贷款记录的人。
- 水电煤缴费记录:按时缴费说明用户有责任心。
- 教育背景:学历越高,违约率通常越低。当然,这不是绝对的。
- 设备信息:手机型号、操作系统。用高端手机的用户,还款能力一般更强。
核心观点:数据不是越多越好,而是越“干净”越好。我见过太多团队,堆了几百个特征,结果模型效果还不如几十个核心特征。记住:少即是多。
3.2 数据采集方法:怎么把数据“搬”回来?
数据源找到了,接下来就是怎么把数据弄到手。数据采集,说白了就是“搬砖”。但搬砖也有技术含量。
3.2.1 API接口采集
这是最常用的方式。外部数据源提供API,你调用接口,数据就过来了。
import requests
import json
# 调用第三方征信API
url = "https://api.credit.com/v1/user/query"
headers = {
"Authorization": "Bearer your_api_key",
"Content-Type": "application/json"
}
payload = {
"name": "张三",
"id_card": "110101199001011234"
}
response = requests.post(url, headers=headers, data=json.dumps(payload))
data = response.json()
print(data)
这里有个坑:API调用频率限制。我曾经有个项目,没注意限流,结果把对方服务器打挂了,直接被拉黑。所以,一定要做好重试和限流机制。
3.2.2 数据库直连
内部数据通常存在数据库里。直接连库拉数据,效率最高。
import pymysql
# 连接数据库
conn = pymysql.connect(
host="192.168.1.100",
user="data_engineer",
password="your_password",
database="risk_control"
)
# 查询用户基本信息
sql = "SELECT user_id, name, age, income FROM user_info WHERE create_date >= '2024-01-01'"
df = pd.read_sql(sql, conn)
conn.close()
嗯,这里要注意:不要在生产环境直接跑全量查询。我建议分批次拉取,比如一次拉10万条,避免把数据库拖垮。
3.2.3 爬虫采集
有些公开数据,比如法院的失信被执行人名单,可以通过爬虫采集。但要注意:合规第一。别碰个人隐私数据,别爬反爬严格的网站。
避坑指南:我曾经有个同事,为了省事,直接用爬虫爬了某电商平台的用户评价数据。结果被对方法务找上门,差点吃官司。记住:数据采集,合规是底线。
3.3 探索性数据分析(EDA)实战:数据长什么样?
数据拉回来了,别急着建模。先做EDA,看看数据到底长什么样。我见过太多人,上来就调包跑模型,结果跑出来一堆垃圾。为什么?因为数据有问题,他没发现。
3.3.1 数据概览:先看全貌
拿到数据,第一件事:看形状、看类型、看缺失值。
import pandas as pd
import numpy as np
# 加载数据
df = pd.read_csv("loan_data.csv")
# 数据概览
print("数据形状:", df.shape)
print("\n数据类型:")
print(df.dtypes)
print("\n缺失值统计:")
print(df.isnull().sum())
print("\n基本统计量:")
print(df.describe())
你想想看,如果数据有50%的缺失值,你还敢直接建模吗?肯定要先处理。
3.3.2 单变量分析:每个特征自己“长”啥样?
对每个特征,单独分析它的分布情况。
import matplotlib.pyplot as plt
import seaborn as sns
# 连续变量分布
plt.figure(figsize=(12, 4))
plt.subplot(1, 3, 1)
sns.histplot(df['age'], bins=30, kde=True)
plt.title('年龄分布')
plt.subplot(1, 3, 2)
sns.histplot(df['income'], bins=30, kde=True)
plt.title('收入分布')
plt.subplot(1, 3, 3)
sns.boxplot(x=df['loan_amount'])
plt.title('贷款金额箱线图')
plt.show()
我在项目中遇到过,有个特征的分布严重偏态,比如收入,大部分人月入5000,但有个别人月入100万。这种极端值如果不处理,模型会被带偏。
3.3.3 双变量分析:特征和目标的关系
看每个特征和“是否逾期”这个目标变量的关系。
# 年龄与逾期率的关系
age_bins = [18, 25, 30, 40, 50, 60, 100]
age_labels = ['18-25', '25-30', '30-40', '40-50', '50-60', '60+']
df['age_group'] = pd.cut(df['age'], bins=age_bins, labels=age_labels)
default_rate = df.groupby('age_group')['is_default'].mean()
print("各年龄段逾期率:")
print(default_rate)
# 可视化
plt.figure(figsize=(10, 5))
default_rate.plot(kind='bar')
plt.title('各年龄段逾期率')
plt.ylabel('逾期率')
plt.show()
你看,如果18-25岁的逾期率明显高于其他年龄段,那“年龄”就是一个很好的风控特征。
3.3.4 缺失值分析:数据“空洞”怎么处理?
缺失值处理是EDA的重头戏。我一般分三步走:
- 看缺失率:缺失率超过70%的特征,直接删除。信息量太少,留着也没用。
- 看缺失模式:是随机缺失,还是系统缺失?比如,收入字段缺失,可能是因为用户是学生,没有收入。这种缺失本身就有信息量。
- 决定填充方式:均值填充、中位数填充、还是用模型预测填充?
# 缺失值可视化
plt.figure(figsize=(12, 6))
sns.heatmap(df.isnull(), cbar=False, cmap='viridis')
plt.title('缺失值热力图')
plt.show()
嗯,这里要注意:不要盲目填充。我曾经有个项目,把“收入”字段的缺失值用均值填充了。结果模型上线后,发现那些收入缺失的用户(其实是学生)违约率特别高。为什么?因为他们的真实收入是0,你用均值填充,等于把风险低估了。
3.3.5 异常值检测:揪出“害群之马”
异常值会严重干扰模型。我常用的方法:
- 3σ原则:超过均值±3倍标准差的值,视为异常。
- 箱线图法:超过上下四分位数1.5倍IQR的值,视为异常。
- 业务规则:比如,年龄超过120岁,明显是异常。
# 用箱线图法检测异常值
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 = df[(df['income'] < lower_bound) | (df['income'] > upper_bound)]
print(f"异常值数量: {len(outliers)}")
print(f"异常值占比: {len(outliers)/len(df):.2%}")
个人经验:异常值不一定要删除。有时候,异常值本身就是风险信号。比如,一个用户突然申请了远超他收入水平的贷款,这很可能就是欺诈行为。所以,处理异常值之前,先问问业务人员:这个值合理吗?
3.4 EDA总结:数据“体检报告”
做完EDA,你应该能回答这几个问题:
- 数据质量怎么样?缺失值多不多?异常值多不多?
- 每个特征长什么样?分布是否合理?
- 哪些特征和目标变量关系强?哪些是噪音?
- 数据里有没有明显的“坑”?比如,某个特征在训练集和测试集分布不一致。
说白了,EDA就是给数据做一次全面体检。体检报告出来了,你才知道下一步该怎么走:哪些特征要清洗,哪些要转换,哪些要删除。
我经常跟团队说:EDA花的时间,建模会加倍还给你。别急着跑模型,先把数据摸透了再说。
好了,这一章就到这里。下一章,我们会讲特征工程——怎么把原始数据变成模型能吃的“好料”。到时候见。