拓冰建站拓冰建站
首页 / 资讯中心 / 正文

保险精算进阶:用R语言GAM、样条与局部回归实现非线性费率厘定

1. 项目概述当精算遇见“自由曲线”在保险精算这个传统上被广义线性模型GLM统治的领域费率厘定工作常常伴随着一个核心矛盾我们既希望模型能精准捕捉风险因子与赔付成本之间复杂的非线性关系又受限于GLM要求预先指定函数形式如对数链接、幂次关系的束缚。很多时候一个风险变量比如年龄、车辆价值、区域代码对赔付率的影响并不是一条简单的直线或平滑的指数曲线它可能有拐点、有平台期、甚至在某个区间剧烈波动。强行用参数模型去拟合要么导致拟合不足模型解释力差要么为了追求拟合优度而加入过多的高阶项和交互项让模型变得复杂、不稳定且难以解释。这正是“非参数”和“半参数”模型的价值所在。它们不像参数模型那样要求我们事先告诉数据“你应该长成什么样子”而是让数据自己“说话”揭示其内在的结构。局部回归Loess、广义相加模型GAM和样条回归Spline Regression就是三类强大的工具能帮助我们在费率厘定中以更灵活、更数据驱动的方式去刻画那些“说不清道不明”的非线性效应。我处理过不少车险和健康险的定价项目深切体会到当业务方拿着精算部出具的费率表指着某个年龄段的费率跳跃问“为什么这里突然贵这么多”时如果你只能回答“因为GLM里年龄的二次项系数显著”那说服力是远远不够的。而如果你能展示一张基于GAM或样条回归拟合出的、光滑的“年龄-相对风险”曲线并指出“你看在23-28岁这个新手司机风险集中区曲线有一个明显的凸起”这种基于数据形态本身的解释无论是内部沟通还是对外监管报备都更具直观性和说服力。简单来说这个项目就是利用R语言强大的统计建模和可视化生态将局部回归、GAM和样条回归应用于保险损失数据旨在不预设强假设的前提下更优雅、更稳健地完成风险分级与纯保费计算为精细化定价提供支撑。2. 核心思路与模型选型背后的考量在动手写代码之前搞清楚这三个模型的核心思想、联系与区别以及它们在保险定价场景下的适用边界至关重要。这决定了你面对一个具体数据集时第一反应应该尝试哪个。2.1 模型哲学从“局部平均”到“全局约束”局部回归Loess的思想最为直观要预测某一点的值我只关心它“邻居”们的信息。对于给定的数据点x0我在其附近划出一个窗口由带宽参数控制只用窗口内的数据点拟合一个简单的低阶多项式通常是一阶或二阶然后用这个局部模型来预测x0的值。接着滑动这个窗口到下一个点重复这个过程。它的优点是极度灵活能捕捉非常局部的波动缺点也是因为太灵活容易对噪声敏感产生过拟合且输出是一系列离散的预测点没有显式的全局模型公式不利于外推和解释。样条回归Spline Regression提供了一种折中方案。它不像Loess那样完全“各自为政”而是用一组光滑连接的分段多项式样条基函数来拟合整个数据范围。这些分段点在内部称为“节点”。通过控制节点的数量和位置以及多项式阶数我们可以在灵活性与平滑度之间取得平衡。惩罚样条如P-splines更进一步通过对样条系数的波动性施加惩罚自动控制模型的复杂度防止过拟合。样条回归给出了一个明确的数学表达式便于计算和解释。广义相加模型GAM则是一个更高级的框架。你可以把它理解为GLM的升级版。在GLM里预测值是协变量的线性组合通过链接函数。在GAM里这个线性组合被替换为各个协变量的平滑函数之和。也就是说g(E(Y)) β0 f1(x1) f2(x2) ...。这里的f1, f2等平滑函数通常就是用样条如薄板样条、三次回归样条或局部回归等方法来实现的。因此GAM是样条/局部回归等平滑技术的“容器”和“推广”它天然处理多变量并可以方便地纳入参数项线性部分与半参数项平滑部分。2.2 保险场景下的选型逻辑那么在厘定费率时如何选择探索性数据分析与单变量分析首选局部回归与样条图。当你拿到一个新的风险因子第一步绝不是直接塞进GLM。应该先用ggplot2的geom_smooth(method loess)快速绘制一条局部回归曲线观察其大致形态。同时使用mgcv包的gam函数拟合一个单变量平滑模型如gam(loss ~ s(age), data...)然后用plot()函数绘制其样条平滑效应图。这个阶段的目标是“看”了解变量是否存在非线性以及非线性的粗略形状单调递增、S形、有峰有谷等。多变量风险建模与正式定价核心使用广义相加模型GAM。这是生产环境的主力。保险定价几乎总是多变量的年龄、车型、地域、历史赔付等。GAM允许你为每个连续变量如年龄、车价指定一个平滑项s()为分类变量如车型、省份指定随机效应或因子项。你可以像构建GLM一样构建GAM并通过summary()查看每个平滑项的显著性近似p值和有效自由度衡量非线性复杂度。最终的模型输出可以直接用于计算每个风险组合的纯保费。基准对比与模型诊断样条回归作为参照。有时为了验证GAM中某个平滑项的合理性可以单独对该变量做一个惩罚样条回归对比其拟合曲线与GAM中该变量的偏效应图是否一致。这有助于确认模型间的稳定性。注意不要陷入“唯技术论”。最终用于发布的费率表必须是稳定、单调且易于解释的。GAM拟合出的曲线可能在小范围内有非单调波动可能是噪声所致。在实际定价中我们常常会对GAM输出的风险系数进行“后平滑”或“分箱处理”将其转换为几个风险等级以保证费率的商业可解释性和稳健性。模型是工具商业逻辑才是根本。3. 实操环境准备与数据理解我们假设一个典型的车险损失数据场景。数据框claim_data包含以下关键字段policy_id: 保单号age: 驾驶员年龄连续18-80vehicle_value: 车辆价值连续单位万area: 地区分类因子A-F共6类claims: 索赔次数计数total_loss: 总损失金额连续可能右偏严重我们的目标通常是两个1) 建模索赔频率泊松或负二项分布2) 建模案均赔款或纯保费伽马或Tweedie分布。这里以建模索赔频率claims为例演示全过程。3.1 工具包加载与数据检查# 加载核心工具包 library(mgcv) # GAM建模的核心也包含样条 library(ggplot2) # 可视化 library(dplyr) # 数据操作 library(tidyr) # 数据整理 # 可选用于模型比较与诊断 library(gratia) # 增强的GAM诊断与可视化 library(itsadug) # GAM时间序列相关但也有一些好用的绘图函数 # 假设数据已加载为 claim_data # 首先进行探索性分析 glimpse(claim_data) summary(claim_data) # 检查损失分布通常过度离散 var(claim_data$claims) / mean(claim_data$claims)保险损失数据通常存在过度离散方差远大于均值因此后续选择负二项分布而非泊松分布的可能性很大。同时检查连续变量age和vehicle_value的分布与离群值极端的值可能会对局部回归和样条拟合产生不良影响。3.2 单变量关系可视化局部回归与样条初探这是关键的第一步用图形感受风险。# 方法1ggplot2的局部回归平滑快速直观 p1 - ggplot(claim_data, aes(x age, y claims)) geom_point(alpha 0.1, size 0.5) # 数据点透明化避免重叠 geom_smooth(method loess, span 0.3, se TRUE, color blue) labs(title 驾驶员年龄与索赔次数关系 (Loess平滑), x 驾驶员年龄, y 平均索赔次数) theme_minimal() # 方法2使用mgcv的gam函数拟合单变量平滑并绘制偏效应图 # 这里先拟合一个简单的模型仅用于探索 gam_explore - gam(claims ~ s(age, k 10), # k是基函数维度设置稍大一些以捕捉细节 data claim_data, family nb(), # 假设使用负二项分布 method REML) # 推荐使用REML进行平滑参数估计 plot(gam_explore, scheme 1, residuals TRUE, pch 19, cex 0.5, shade TRUE) # shadeTRUE会绘制置信带residualsTRUE会添加部分残差通过对比p1中的Loess曲线和plot(gam_explore)输出的样条曲线你可以判断两种方法揭示的非线性模式是否一致。如果曲线在某个年龄段出现急剧的峰或谷这就是你需要重点关注的风险区间。实操心得geom_smooth中的span参数控制Loess的平滑度窗口比例越小越关注局部越大越平滑。探索时可以从0.2到0.8多试几个值。对于gam的s()函数k值不宜一开始就设得太大否则容易过拟合。可以先从k5-10开始如果模型诊断图gam.check()显示k‘值太低再逐步增加。4. 构建广义相加模型GAM进行多变量定价探索之后我们进入正式建模。假设我们认为年龄、车辆价值存在非线性效应地区是分类效应。4.1 模型设定与拟合# 拟合一个完整的GAM模型 gam_model - gam(claims ~ s(age, k 15) s(vehicle_value, k 10) area, data claim_data, family nb(link log), # 负二项分布对数链接 method REML, # 限制极大似然估计平滑参数更稳定 select TRUE) # 启用额外惩罚可将不重要的平滑项收缩至线性甚至零 # 查看模型摘要 summary(gam_model)解读摘要输出Approximate significance of smooth terms: 这里会给出每个平滑项的近似F检验和p值。如果p值显著如0.05表明该变量的非线性效应是显著的。edf: 有效自由度。如果edf接近1说明该平滑项接近线性关系edf越大说明曲线越复杂。edf远小于设定的k值通常是好事说明模型没有过度使用复杂度。s(age,k15)的edf可能达到7-8说明年龄与索赔频率的关系是一条相当复杂的曲线。area作为因子项会给出与基线水平相比的相对风险系数。Family: Negative Binomial(1.234)会给出离散参数theta的估计值。4.2 模型诊断确保拟合可靠拟合完模型绝不能直接使用必须进行严格的诊断。# 基础诊断图 par(mfrow c(2, 2)) gam.check(gam_model)gam.check()会生成四张图残差vs拟合值图检查残差是否随机分布有无趋势。理想情况是散点均匀分布在0附近。QQ图检查残差是否符合理论分布此处是负二项。点应大致落在对角线上。直方图残差的分布情况。响应vs拟合值图观察预测值与实际值的整体匹配程度。更重要的是它会输出一段文字提示例如“k‘值太低”。如果看到某个平滑项如s(age)后面跟着“k‘ index k‘”并且p值很小这意味着你为该平滑项设置的基函数维度k可能不足无法充分捕捉数据的复杂性需要考虑增加k值例如从15增加到20然后重新拟合模型。# 如果gam.check提示k值不足重新拟合 if(FALSE){ # 假设需要调整 gam_model_v2 - gam(claims ~ s(age, k 20) s(vehicle_value, k 12) area, data claim_data, family nb(link log), method REML, select TRUE) gam.check(gam_model_v2) }4.3 可视化模型结果理解风险曲线模型诊断通过后下一步是将模型结果转化为可理解的费率因子。# 绘制各个平滑项的偏效应图 par(mfrow c(1, 2)) plot(gam_model, select 1, shade TRUE, shade.col lightblue, main 年龄的偏效应 (控制其他变量后), ylab 对索赔频率的贡献(s(age))) plot(gam_model, select 2, shade TRUE, shade.col lightgreen, main 车辆价值的偏效应, ylab 贡献(s(vehicle_value))) # 使用gratia包绘制更美观的图 library(gratia) draw(gam_model, residuals FALSE) # 绘制所有平滑项偏效应图展示了在控制其他所有变量不变的情况下单个变量如年龄从最小值变化到最大值时它对响应变量对数尺度下的索赔频率的“贡献”如何变化。Y轴不是直接的索赔频率而是平滑函数s(x)的值。曲线的趋势就是风险的趋势曲线上升的部分意味着该变量取值增加会导致风险上升下降则风险降低。4.4 生成费率因子表这是精算工作的最终产出之一。我们需要利用拟合的GAM模型预测出每个风险水平下的相对风险系数通常以某个基准水平为1.0。# 1. 创建用于预测的网格数据 # 假设我们想生成年龄从18到80每岁和地区A-F的组合费率车辆价值取中位数 pred_grid - expand.grid(age seq(18, 80, by 1), area factor(levels(claim_data$area)), vehicle_value median(claim_data$vehicle_value)) # 2. 进行预测类型为“link”得到的是对数尺度下的预测值 pred_grid$log_freq - predict(gam_model, newdata pred_grid, type link) # 3. 转换为原始尺度相对风险系数 # 选择一个基准点例如年龄30地区A车辆价值中位数 baseline_condition - data.frame(age 30, area factor(A, levels levels(claim_data$area)), vehicle_value median(claim_data$vehicle_value)) baseline_log_freq - predict(gam_model, newdata baseline_condition, type link) pred_grid$relativity - exp(pred_grid$log_freq - baseline_log_freq) # 4. 查看结果例如筛选地区A的年龄费率因子 rate_factor_age_A - pred_grid %% filter(area A) %% select(age, relativity) head(rate_factor_age_A) # 5. 可以进一步将连续的费率因子离散化为几个风险等级便于制表 rate_factor_age_A$risk_band - cut(rate_factor_age_A$relativity, breaks c(0, 0.7, 0.9, 1.1, 1.3, 2, Inf), labels c(很低, 低, 标准, 高, 很高, 极高)) table(rate_factor_age_A$risk_band)5. 局部回归与样条回归的深入应用与对比虽然GAM是主力但局部回归和样条回归作为其组件和补充工具在特定场景下非常有用。5.1 局部回归Loess的深度用法非参数趋势检验与基准对比Loess输出的不是模型对象而是一系列拟合值。它的一个妙用是作为“真相”的近似基准来检验参数模型或GAM的拟合效果。# 使用loess函数进行更精细的控制 loess_fit - loess(claims ~ age, data claim_data, span 0.4, degree 2) # 生成拟合值 age_seq - seq(min(claim_data$age), max(claim_data$age), length.out 100) loess_pred - predict(loess_fit, newdata data.frame(age age_seq), se TRUE) # 将Loess拟合曲线与GAM的偏效应曲线画在一起对比 # 首先获取GAM在年龄上的偏效应预测值 gam_age_effect - predict(gam_model, newdata data.frame(age age_seq, vehicle_value median(claim_data$vehicle_value), area factor(A, levels levels(claim_data$area))), type terms, terms s(age)) # 注意predict(typeterms)返回的是中心化的贡献均值为0 # 为了与Loess比较趋势我们需要对齐两者的尺度比如都从0开始 gam_age_effect_centered - gam_age_effect[,1] - mean(gam_age_effect[,1]) loess_pred_centered - loess_pred$fit - mean(loess_pred$fit) plot_df - data.frame(age rep(age_seq, 2), effect c(gam_age_effect_centered, loess_pred_centered), method rep(c(GAM, Loess), each 100), se c(rep(NA, 100), loess_pred$se.fit)) library(ggplot2) ggplot(plot_df, aes(x age, y effect, color method)) geom_line(size 1) geom_ribbon(data subset(plot_df, method Loess), aes(ymin effect - 1.96*se, ymax effect 1.96*se), alpha 0.2, fill blue, color NA) labs(title GAM与Loess对年龄效应估计的对比, x 年龄, y 中心化的效应值) theme_minimal()如果两条曲线在主要趋势上吻合则增强了GAM模型结果的可靠性。如果存在显著差异尤其是在数据稀疏的区域就需要警惕检查是否是GAM的平滑参数选择不当或者是Loess的span参数过于敏感。5.2 样条回归的独立应用惩罚样条与节点选择有时你可能需要单独对一个变量进行非常灵活的拟合或者想深入了解样条的工作原理。# 使用mgcv包中的惩罚样条默认这其实就是GAM中s()函数的单变量版本 # 但我们可以用gam函数只拟合一个平滑项 spline_model - gam(claims ~ s(age, bs ps, k 20), # bsps指P-splines data claim_data, family nb(), method REML) # 与之前GAM中的s(age)对比 plot(spline_model, main 单变量惩罚样条拟合 (年龄)) plot(gam_model, select 1, main GAM中年龄的偏效应) # 两者应该非常相似 # 节点选择的影响比较不同k值 spline_k10 - gam(claims ~ s(age, k10), dataclaim_data, familynb(), methodREML) spline_k30 - gam(claims ~ s(age, k30), dataclaim_data, familynb(), methodREML) par(mfrowc(1,2)) plot(spline_k10, maink10 (可能欠拟合), ylimc(-1,1)) plot(spline_k30, maink30 (可能过拟合), ylimc(-1,1))k30的曲线可能会在数据两端或稀疏处出现不合理的剧烈波动这就是过拟合的迹象。gam.check()和模型的edf值会帮你判断哪个k值更合适。通常edf比k小不少且诊断图良好就是可接受的。6. 高级话题与实战避坑指南在实际保险定价项目中应用这些模型会遇到许多在教科书里没有的挑战。6.1 处理交互效应当非线性相遇风险因子之间往往存在交互作用。例如年轻驾驶员驾驶高价车风险可能不是简单的年龄风险车价风险。GAM可以处理这种非线性交互。# 使用te()或ti()张量积平滑来拟合二维交互 gam_interaction - gam(claims ~ s(age) s(vehicle_value) te(age, vehicle_value, k c(10, 8)), data claim_data, family nb(), method REML) summary(gam_interaction) # 如果te()项显著说明存在交互效应 # 可视化二维交互表面 vis.gam(gam_interaction, view c(age, vehicle_value), plot.type contour, too.far 0.1, main 年龄与车辆价值的交互风险表面, xlab 年龄, ylab 车辆价值(万)) # too.far参数可以排除数据稀疏区域的预测二维等高线图可以清晰显示哪些“年龄-车价”组合是高风险区域红色哪些是低风险区域蓝色。这为设计多维度的优惠或加费规则提供了直接依据。6.2 模型比较与选择不要迷信统计量你可能会拟合多个GAM模型例如包含交互项和不包含交互项的。如何选择# 拟合基准模型无交互 gam_base - gam(claims ~ s(age) s(vehicle_value) area, data claim_data, family nb(), method REML) # 拟合交互模型 gam_int - gam(claims ~ s(age) s(vehicle_value) te(age, vehicle_value) area, data claim_data, family nb(), method REML) # 使用AIC或广义交叉验证(GCV)分数比较 AIC(gam_base, gam_int) # 交互模型的AIC如果显著更低差值2则支持交互模型 # 但更重要的是检查模型诊断和商业解释性 gam.check(gam_int) # 交互模型是否更稳定残差图是否更优 # 交互效应的表面是否符合业务常识还是产生了无法解释的复杂波动一个重要的原则统计上显著的复杂模型未必是业务上最好的模型。一个略微简化但更稳定、更容易向业务方解释的模型往往在落地时更受欢迎。特别是当交互效应曲面非常“崎岖”时要谨慎判断这是真实的业务信号还是数据噪声或过拟合的结果。6.3 常见陷阱与排查技巧“k值不足”警告这是最常见的问题。mgcv的gam.check()会提示。解决方案增加该平滑项的k值例如从10增加到15或20。但要注意k值增大会增加计算量也可能引入不必要的波动。可以尝试bs cr三次回归样条或bs tp薄板样条它们有时比默认的bs tp默认对k值更不敏感。收敛警告有时拟合算法可能不收敛。解决方案尝试更改优化方法如method REML改为method ML或反之。也可以尝试增加迭代次数control gam.control(maxit 500)。检查数据中是否有异常值或极端共线性。结果不稳定每次运行结果略有差异或者添加/删除少量数据后曲线形状大变。解决方案这可能是数据在该区域过于稀疏或者平滑参数sp估计不稳定。可以尝试使用select TRUE启用额外惩罚。对连续变量进行适当的变换如取对数使其分布更均匀。考虑在数据稀疏的区域将连续变量进行分箱处理转为有序因子用随机效应平滑s(..., bsre)来建模。计算速度慢当数据量巨大100万行或平滑项很多时GAM拟合会变慢。解决方案使用bam()函数代替gam()它是为大数据集设计的。减少不必要的平滑项k值。对于分类变量如果水平很多如几千个邮编考虑使用随机效应平滑s(zip_code, bsre)而非固定效应。从模型输出到费率表的“最后一公里”GAM预测出的风险系数可能是连续且非单调的。直接使用可能导致费率曲线出现不合理的“锯齿”。标准做法后平滑对GAM输出的连续风险系数再用一个简单的低阶样条或移动平均进行平滑确保单调性如年龄费率应随年龄增长单调递减或先增后减。分箱将连续风险系数离散化为5-10个风险等级。分箱的边界可以基于业务理解如新手司机年龄段、或基于统计方法如寻找曲线的拐点。业务校验将最终的费率因子与当前在用的费率、主要竞争对手的费率以及业务团队的直觉进行对比对明显不符合常识的局部进行调整。模型是仆人不是主人。在我经历的一个车险定价项目中GAM模型揭示出车辆车龄在0-1年新车和10年以上老车的风险显著高于中间车龄呈现一个“U型”曲线。这与业务经验新车主爱惜、老车故障多吻合但之前的线性模型无法捕捉这种细节。我们依据此曲线对新老车分别设计了更具差异化的费率上线后在目标客群中的风险区分度提升了约15%。然而我们也发现模型在“25岁”这个点有一个微小但尖锐的峰值经核查是某个特定促销活动带来的数据扰动并非真实风险。我们最终在费率表中平滑掉了这个峰值。这个过程完美诠释了如何让数据驱动与业务智慧相结合。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门