4. 数据准备:坏账率数据来源与预处理
回测系统的可靠性高度依赖输入数据的质量。本章将系统讲解坏账率回测所需的数据来源、字段定义以及清洗预处理流程,确保后续策略计算建立在干净、一致的数据基础之上。
4.1 数据来源:CSV 与数据库
实际项目中,坏账数据通常来自两个渠道:离线导出的 CSV 文件或在线业务数据库。两种来源各有适用场景,下表对比了核心差异:
| 对比维度 | CSV 文件 | 关系数据库 |
|---|---|---|
| 适用阶段 | 原型验证、本地回测 | 生产环境、持续监控 |
| 数据量级 | 百万行以内 | 千万至亿级 |
| 读取方式 | pd.read_csv() |
pd.read_sql() + SQL 查询 |
| 版本管理 | Git 可追踪 | 需快照或 CDC |
| 实时性 | 离线快照 | 准实时 |
推荐做法:课程案例统一使用 CSV 作为数据源,便于复现和版本控制。生产环境迁移时,只需将
pd.read_csv() 替换为数据库连接查询即可。
4.2 数据字段定义
回测坏账率告警系统至少需要三个核心字段:时间戳、逾期天数、坏账标记。下表给出完整字段规范:
| 字段名 | 类型 | 示例值 | 说明 |
|---|---|---|---|
date |
datetime64 | 2024-01-15 | 观测日期,粒度通常为日 |
loan_id |
string | LN20240115001 | 每笔借款的唯一标识 |
due_date |
datetime64 | 2024-02-14 | 应还款日 |
overdue_days |
int | 30 | 截至观测日已逾期天数 |
is_bad |
int (0/1) | 1 | 坏账标记:1=坏账,0=正常 |
principal |
float | 10000.00 | 借款本金(可选,用于计算损失率) |
关键约定:坏账标记
is_bad 的判定标准必须与业务规则一致。本课程统一采用“逾期天数 ≥ 90 天”作为坏账阈值。若业务规则不同,请在预处理阶段调整。
4.3 数据清洗与预处理流程
原始数据通常包含缺失值、重复记录、异常日期等问题。以下流程图为清洗预处理的完整步骤:
4.4 核心清洗步骤详解
4.4.1 缺失值处理
常见缺失场景及处理策略:
- 日期缺失:直接删除该行,无法回填。
- 逾期天数缺失:若
due_date存在,可计算(观测日 - 应还款日).days填充。 - 坏账标记缺失:根据逾期天数重新生成。
# 缺失值处理示例
import pandas as pd
df = pd.read_csv('loan_data.csv')
# 删除日期缺失的行
df.dropna(subset=['date', 'due_date'], inplace=True)
# 根据 due_date 计算 overdue_days
df['overdue_days'] = (pd.to_datetime(df['date']) - pd.to_datetime(df['due_date'])).dt.days
df['overdue_days'] = df['overdue_days'].clip(lower=0) # 未到期设为0
4.4.2 重复记录去重
同一笔借款在同一天不应出现两条记录。去重逻辑:
# 按 loan_id + date 去重,保留第一条
df.drop_duplicates(subset=['loan_id', 'date'], keep='first', inplace=True)
4.4.3 坏账标记生成
根据业务规则,逾期天数 ≥ 90 天标记为坏账:
# 生成坏账标记
df['is_bad'] = (df['overdue_days'] >= 90).astype(int)
验证提示:生成后建议检查
df['is_bad'].value_counts(),确保坏账比例在合理范围(通常 1%~10%)。若比例异常,需回溯逾期天数计算逻辑。
4.5 数据字段与清洗流程总览
以下 SVG 图展示数据从原始字段到清洗后字段的映射关系:
4.6 完整预处理代码模板
将上述步骤整合为一个可复用的预处理函数:
def preprocess_loan_data(filepath: str) -> pd.DataFrame:
"""
加载并清洗借款数据,返回可直接用于回测的 DataFrame。
Parameters
----------
filepath : str
CSV 文件路径
Returns
-------
pd.DataFrame
包含 date, loan_id, due_date, overdue_days, is_bad 的清洗后数据
"""
df = pd.read_csv(filepath)
# 1. 删除关键字段缺失的行
df.dropna(subset=['date', 'due_date'], inplace=True)
# 2. 日期类型转换
df['date'] = pd.to_datetime(df['date'])
df['due_date'] = pd.to_datetime(df['due_date'])
# 3. 计算逾期天数
df['overdue_days'] = (df['date'] - df['due_date']).dt.days
df['overdue_days'] = df['overdue_days'].clip(lower=0)
# 4. 去重
df.drop_duplicates(subset=['loan_id', 'date'], keep='first', inplace=True)
# 5. 生成坏账标记
df['is_bad'] = (df['overdue_days'] >= 90).astype(int)
# 6. 按日期排序
df.sort_values(['date', 'loan_id'], inplace=True)
df.reset_index(drop=True, inplace=True)
return df
使用建议:将上述函数保存为
data_prep.py,后续回测脚本直接 from data_prep import preprocess_loan_data 调用,保持代码模块化。
4.7 数据质量检查清单
预处理完成后,建议逐项检查以下指标,确保数据可用:
- 时间范围:
df['date'].min()至df['date'].max()是否覆盖回测区间。 - 无空值:
df.isnull().sum().sum()应为 0。 - 无重复:
df.duplicated(subset=['loan_id','date']).sum()应为 0。 - 坏账比例:
df['is_bad'].mean()应在预期范围。 - 逾期天数分布:检查
df['overdue_days'].describe(),最大值不应异常。