2. 数据准备与探索:数据来源与采集、数据清洗与预处理、探索性数据分析(EDA)基础
在保险精算建模中,数据质量直接决定模型效果的上限。本章聚焦于从原始数据到可用分析数据集的完整链路,涵盖数据来源识别、采集策略、清洗规则以及探索性数据分析(EDA)的核心方法。精算师需要确保数据不仅“干净”,而且能够真实反映风险分布与业务逻辑。
2.1 数据来源与采集
保险精算模型的数据来源通常分为内部业务系统与外部数据源两大类。采集时需重点关注数据的时间跨度、完整性以及是否满足监管合规要求。
| 数据来源类别 | 典型数据内容 | 采集方式 | 常见问题 |
|---|---|---|---|
| 内部承保系统 | 保单信息、保额、费率、免赔额 | ETL 抽取、API 接口 | 字段缺失、历史版本不一致 |
| 理赔系统 | 理赔金额、理赔次数、事故类型、结案时间 | 数据库直连、批量导出 | 理赔延迟、重复报案 |
| 客户管理系统 | 年龄、性别、职业、地区、信用评分 | CRM 导出、数据仓库同步 | 隐私字段脱敏、更新滞后 |
| 外部数据源 | 行业风险指数、气象数据、宏观经济指标 | 第三方 API、公开数据集 | 数据格式不统一、授权限制 |
采集避坑提示: 精算建模通常要求数据至少覆盖 3-5 个完整业务年度,以捕捉费率周期与季节性波动。采集时务必记录数据版本与采集时间戳,便于后续回溯。
2.2 数据清洗与预处理
原始数据中普遍存在缺失值、异常值、重复记录和格式不一致等问题。清洗流程需要兼顾统计合理性与业务可解释性,避免过度清洗导致信息失真。
2.2.1 缺失值处理策略
- 删除法: 当缺失比例低于 5% 且为随机缺失时,可直接删除对应记录或变量。
- 均值/中位数填充: 适用于连续型变量(如年龄、保额),但会降低方差。
- 模型预测填充: 使用 KNN、回归或随机森林对缺失值进行预测,保留变量间关系。
- 业务规则填充: 例如“理赔原因”缺失时,根据事故类型字段进行逻辑推断。
2.2.2 异常值识别与处理
精算数据中的异常值可能代表欺诈、录入错误或真实的高风险事件。常用识别方法包括:
- Z-Score 法: 假设数据服从正态分布,将 |Z| > 3 的观测标记为异常。
- IQR 四分位距法: 将低于 Q1 - 1.5×IQR 或高于 Q3 + 1.5×IQR 的值视为异常。
- 业务阈值法: 例如理赔金额超过保额 120% 时,直接标记为人工审核。
注意: 不要机械地删除所有异常值。对于车险理赔中的高额赔付案例,应结合业务背景判断是否为真实大额索赔,避免丢失尾部风险信息。
2.2.3 数据清洗流程示例
# Python 伪代码:精算数据清洗流水线
import pandas as pd
import numpy as np
# 1. 加载原始数据
df = pd.read_csv('insurance_raw.csv')
# 2. 删除重复保单号
df = df.drop_duplicates(subset='policy_id')
# 3. 缺失值处理:年龄用中位数填充,理赔原因用众数填充
df['age'].fillna(df['age'].median(), inplace=True)
df['claim_reason'].fillna(df['claim_reason'].mode()[0], inplace=True)
# 4. 异常值处理:对连续变量进行截尾处理(1% 分位数截断)
lower = df['claim_amount'].quantile(0.01)
upper = df['claim_amount'].quantile(0.99)
df['claim_amount'] = df['claim_amount'].clip(lower, upper)
# 5. 格式统一:日期字段转为 datetime 类型
df['policy_start_date'] = pd.to_datetime(df['policy_start_date'])
print(f"清洗后数据量: {len(df)} 条")
2.3 探索性数据分析(EDA)基础
EDA 是连接数据清洗与模型构建的桥梁。精算 EDA 的核心目标是:理解风险变量的分布形态、识别变量间的相关性、发现潜在的数据质量问题,并为特征工程提供方向。
2.3.1 单变量分析
- 连续变量: 使用直方图、箱线图观察分布形态(左偏/右偏/双峰),计算均值、中位数、标准差、偏度与峰度。
- 分类变量: 使用频数表、条形图查看各类别占比,关注低频类别是否需要合并。
2.3.2 双变量分析
- 数值 vs 数值: 散点图 + 相关系数矩阵(Pearson / Spearman),识别共线性问题。
- 分类 vs 数值: 分组箱线图或小提琴图,比较不同类别下的目标变量均值差异。
- 分类 vs 分类: 列联表 + 卡方检验,评估两个分类变量是否独立。
2.3.3 精算 EDA 核心检查清单
| 检查项 | 具体内容 | 精算关注点 |
|---|---|---|
| 风险暴露分布 | 保单生效时间、保额区间分布 | 是否存在暴露集中度过高或过低 |
| 理赔率与赔付率 | 按险种、地区、年龄分组的理赔频率 | 识别高风险细分群体 |
| 零膨胀问题 | 理赔次数中零值占比 | 是否需要使用零膨胀模型 |
| 时间趋势 | 月度/季度赔付额变化 | 是否存在季节性或费率调整影响 |
| 缺失模式 | 变量缺失率及缺失是否随机 | 判断填充方法是否合理 |
精算 EDA 黄金法则: 不要只看统计指标,一定要结合业务逻辑验证。例如,如果发现“年轻驾驶员”的理赔率异常低,应检查是否因为该群体保单数量少或免赔额设置过高导致,而非直接认为该群体风险低。
2.4 数据探索与清洗流程总览
下图展示了从原始数据到建模数据集的完整流程,以及各环节的关键输出物。
2.5 精算 EDA 实战:理赔数据分布检查
以下是一个针对车险理赔数据的 EDA 实战案例,展示如何通过分组统计快速定位风险异常。
# Python 伪代码:理赔数据分组 EDA
import pandas as pd
import numpy as np
# 加载清洗后的理赔数据
claims = pd.read_csv('claims_clean.csv')
# 按驾驶员年龄分组计算理赔率
age_bins = [18, 25, 35, 50, 65, 80]
age_labels = ['18-25', '26-35', '36-50', '51-65', '66-80']
claims['age_group'] = pd.cut(claims['driver_age'], bins=age_bins, labels=age_labels)
# 计算各年龄组的理赔频率与平均赔付额
eda_result = claims.groupby('age_group').agg(
保单数=('policy_id', 'nunique'),
理赔次数=('claim_id', 'count'),
平均赔付额=('claim_amount', 'mean')
).reset_index()
eda_result['理赔频率'] = eda_result['理赔次数'] / eda_result['保单数']
print(eda_result.round(2))
输出解读: 若发现 18-25 岁年龄组的理赔频率显著高于其他组,但平均赔付额较低,说明该群体事故频发但单次损失较小。在后续建模中,应考虑为该群体引入“驾驶经验”或“车辆安全配置”等特征,以提升模型区分度。
通过系统化的数据准备与探索,精算师能够为后续的模型构建、参数估计与效果评估奠定坚实的数据基础。下一阶段将基于清洗后的数据开展特征工程与模型训练工作。