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的重头戏。我一般分三步走:

  1. 看缺失率:缺失率超过70%的特征,直接删除。信息量太少,留着也没用。
  2. 看缺失模式:是随机缺失,还是系统缺失?比如,收入字段缺失,可能是因为用户是学生,没有收入。这种缺失本身就有信息量。
  3. 决定填充方式:均值填充、中位数填充、还是用模型预测填充?
# 缺失值可视化
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花的时间,建模会加倍还给你。别急着跑模型,先把数据摸透了再说。

好了,这一章就到这里。下一章,我们会讲特征工程——怎么把原始数据变成模型能吃的“好料”。到时候见。