R语言非参数模型在保险定价中的应用:Loess、GAM与样条回归实战
1. 项目概述当精算遇见数据科学在保险定价这个传统精算的核心领域我们长久以来习惯于与广义线性模型GLM打交道。它结构清晰、结果可解释是监管报备和业务沟通的“标准语言”。然而随着数据维度的爆炸式增长和客户风险画像的日益复杂一个尖锐的问题摆在了面前风险与费率因子之间的关系真的总是线性的吗年龄对车险出险概率的影响是否在青年司机阶段急剧上升而后平缓房屋的楼龄与火灾风险是否存在一个“老化拐点”这些非线性、非单调的隐秘关系GLM在预设链接函数和线性预测器的框架下往往力不从心或者需要我们手动进行繁琐的分箱、多项式变换不仅效率低下还容易引入主观偏差。这正是“R语言非参数模型厘定保险费率”这一实践主题的价值所在。它不是一个推翻传统的革命而是一次重要的范式扩展。我们不再强求数据去拟合一个预设的数学模型而是让数据自己“说话”揭示其内在的复杂结构。局部回归Loess、广义相加模型GAM和样条回归Spline Regression正是三把强大的“数据雕刻刀”它们以不同的方式松弛线性假设灵活捕捉费率因子与风险损失之间的非线性关联。对于一名一线的定价精算师或数据科学家而言掌握这套工具意味着能从数据中挖掘出更细腻的风险规律构建更具竞争力且符合风险实质的费率体系特别是在UBI车险、健康险、财产险等高度个性化的细分市场。接下来我将结合实战经验拆解如何运用R语言这把瑞士军刀将这三个非参数模型从理论公式落地为可靠的定价模型。2. 核心模型选型与思路解析面对非参数建模首要问题不是如何写代码而是理解每个模型的“性格”与适用场景从而在具体业务问题中做出明智的选择。这三种方法并非互斥而是构成了一个从完全局部到结构化非线性的光谱。2.1 局部回归数据敏感的风险趋势探索者局部回归特别是Loess其核心思想是“就近取材”。它对每一个待预测的数据点仅使用其邻域内的数据进行加权线性或二次回归。你可以把它想象成一个滑动的窗口沿着自变量如年龄滑动在每个窗口内拟合一个简单的模型从而得到一条光滑的曲线。为什么在费率厘定中考虑它它的最大优势是无预设形式完全由数据驱动非常适合进行探索性数据分析。在定价初期当我们面对一个新的风险因子比如对于新能源车险电池健康度SOH与理赔率的关系对其形态一无所知时Loess是绝佳的“侦察兵”。它能快速揭示出可能的趋势、拐点或异常区域为后续更结构化模型的设定提供依据。关键考量与局限它的“敏感性”既是优点也是缺点。模型结果严重依赖于两个超参数跨度span和光滑度参数。跨度决定了滑动窗口的大小太小会过拟合曲线抖动剧烈捕捉噪声太大则欠拟合曲线过于平滑丢失细节。在定价中一个不稳定的曲线会导致相邻年龄的费率跳跃不合理这是不可接受的。此外Loess纯粹是探索工具难以直接纳入包含多个因子的复杂定价模型且预测速度相对较慢。因此我的经验是将Loess主要用于单因子的可视化分析与关系初探为GAM或样条回归设定节点knots提供参考而非最终的定价模型。2.2 广义相加模型可加非线性的结构化主力如果说Loess是自由奔放的艺术家那么GAM就是一位严谨的建筑师。它是GLM的自然延伸将线性预测器η β0 β1*x1 ...中的线性项βj*xj替换为平滑函数fj(xj)即η β0 f1(x1) f2(x2) ...。每个fj都是一个平滑函数用以刻画第j个费率因子的非线性效应。为什么它是费率厘定的主力模型GAM完美地平衡了灵活性与可解释性。首先它允许每个变量以非线性方式影响响应变量如索赔频率同时又保持了可加性这意味着我们可以单独可视化并解释每个因子的效应这符合精算对模型解释性的严苛要求。其次它通过惩罚回归样条或平滑样条来控制函数的光滑度有效防止过拟合结果比Loess稳定得多。最后它能无缝融入GLM的框架处理各种分布泊松、伽马、Tweedie和链接函数与我们现有的精算建模流程兼容性极高。模型选型核心在R中实现GAMmgcv包是行业标准。它的核心优势在于能通过广义交叉验证GCV或REML限制性最大似然自动估计平滑项的光滑度参数这大大降低了调参负担。在定价中我们通常对连续变量年龄、车龄、保额使用平滑项对分类变量车型、地区仍使用因子项。一个典型的车险索赔频率GAM模型形式可能是log(预期索赔次数) f1(驾驶人年龄) f2(车辆价值) 车型因子 地区因子。2.3 样条回归灵活可控的数学构造器样条回归可以看作是GAM的“基础设施”。它通过一组定义在自变量取值区间上的多项式基函数B-spline, Natural Spline等来构造平滑函数。节点的位置和数量决定了样条的灵活性。在定价中的定位与选择样条回归为我们提供了比GAM更底层、更直接的控制力。当我们需要对非线性形状有非常明确的先验知识或约束时例如确保费率曲线在某个区间内单调递增使用样条并手动设置节点和约束是更直接的方法。在R中splines包提供了基础功能而mgcv包中的s()函数内部也使用了样条基。如何与GAM协同在实际操作中我们很少从头开始用lm拟合样条回归作为最终模型。更多的是1使用ns()自然样条或bs()B样条进行快速尝试确定大致的节点位置2将这些知识应用到GAM中通过k参数基函数的维度来间接控制复杂度。例如如果我们通过Loess或领域知识发现驾驶员风险在25岁和60岁有明显变化可以在GAM中对该年龄平滑项设置k4隐含节点让模型有足够的自由度去捕捉这些拐点。实操心得对于大多数保险定价场景我的首选推荐是GAMmgcv。它提供了“开箱即用”的稳健非线性建模能力自动化程度高解释性好。Loess用于前期探索样条知识用于深度定制GAM。不要陷入“哪个模型最好”的争论而应建立“探索Loess- 主力建模GAM- 精细调控样条思想”的工作流。3. 实战环境准备与数据预处理工欲善其事必先利其器。非参数建模对数据和工具链有特定要求正确的准备工作能避免后续大量返工。3.1 R环境与核心包配置首先确保你的R环境就绪。我强烈建议使用RStudio作为IDE并结合tidyverse系列包进行数据操作这能极大提升效率。# 安装必要套件 install.packages(c(tidyverse, mgcv, splines, ggplot2, patchwork, modelr)) # tidyverse: 数据操作与可视化 # mgcv: 拟合GAM模型的核心 # splines: 提供样条基函数 # ggplot2 patchwork: 高级图形绘制与拼图 # modelr: 辅助模型评估加载核心库并设置种子保证结果可复现这在模型比较时至关重要。library(tidyverse) library(mgcv) library(splines) library(ggplot2) library(patchwork) set.seed(123) # 设置随机种子3.2 保险数据特征与关键预处理保险定价数据通常是聚合数据以风险单元分组或个体保单数据。核心字段包括响应变量索赔次数泊松分布、索赔强度伽马分布、纯保费Tweedie分布。暴露量保单年数或车年数用于标准化响应变量。费率因子连续变量年龄、车龄、保额、分类变量性别、车型、地区。预处理关键步骤暴露量处理在建模时必须将暴露量作为偏移量offset纳入模型。例如对于索赔频率模型公式中应包含offset(log(exposure))。异常值处理连续型费率因子如高额保额中的极端值可能会过度影响非参数拟合。需要结合业务逻辑进行缩尾Winsorization或分箱处理但注意不要破坏非线性关系。缺失值处理GAM的mgcv包可以处理部分缺失但建议在建模前系统处理。对于分类变量可增加“未知”类别对于连续变量需谨慎使用插补避免引入虚假模式。数据缩放虽然GAM对数值尺度不如深度学习敏感但对连续预测变量进行标准化均值为0标准差为1有时能提升数值稳定性尤其当变量量纲差异巨大时。一个模拟数据的示例我们创建一个模拟的车险数据集来演示。# 模拟保险数据 n - 5000 sim_data - tibble( policy_id 1:n, driver_age runif(n, 18, 80), # 驾驶员年龄 car_value rlnorm(n, meanlog 10, sdlog 0.5), # 车辆价值 vehicle_type sample(c(Sedan, SUV, Truck), n, replace TRUE, prob c(0.6, 0.3, 0.1)), area sample(paste0(Zone, 1:5), n, replace TRUE), exposure runif(n, 0.5, 1.5), # 暴露年数 # 生成非线性风险年轻和年老司机风险高 true_freq exp(-5 0.1*driver_age - 0.0015*(driver_age^2) 0.00001*(driver_age^3) 0.2*as.numeric(factor(vehicle_type))) / 100, claim_count rpois(n, lambda true_freq * exposure) # 生成索赔次数 )4. 模型实现与费率因子效应解析现在我们进入核心环节用代码和结果来揭示风险的非线性面貌。4.1 第一步使用局部回归进行探索性分析在拟合复杂模型前先用Loess探查关键连续变量与风险的关系。这里我们关注驾驶员年龄。# 计算风险率每单位暴露的索赔次数 sim_data - sim_data %% mutate(risk_rate claim_count / exposure) # 使用ggplot2的geom_smooth进行Loess拟合可视化 p_loess - ggplot(sim_data, aes(x driver_age, y risk_rate)) geom_point(alpha 0.1, color grey60) # 原始数据点 geom_smooth(method loess, span 0.3, se TRUE, color blue, fill lightblue) geom_smooth(method loess, span 0.7, se FALSE, color red, linetype dashed) labs(title 局部回归探索驾驶员年龄与风险率关系, subtitle 蓝线(span0.3): 更灵活可能捕捉噪声红线(span0.7): 更平滑, x 驾驶员年龄, y 风险率索赔次数/年) theme_minimal() print(p_loess)这段代码会生成一张散点图并叠加两条Loess曲线。span0.3的曲线更波动可能揭示了年轻司机风险峰值、中年平稳、老年风险回升的细节而span0.7的曲线更宏观地展示了“U型”或“J型”风险趋势。这个图形是我们后续设定GAM平滑项复杂度的重要依据。如果曲线在某个年龄段出现明显拐点我们在GAM中可以考虑为该平滑项分配更多的基函数维度k值。4.2 第二步构建广义相加模型基于探索性分析我们构建一个正式的GAM模型。假设我们怀疑驾驶员年龄和车辆价值都有非线性效应而车型和地区是分类效应。# 使用mgcv拟合GAM模型 # 注意offset(log(exposure))用于处理暴露量 gam_model - gam(claim_count ~ s(driver_age, k 10, bs cr) # 对年龄使用三次回归样条预设10个基函数 s(car_value, k 8, bs cr) # 对车辆价值使用样条 vehicle_type # 车型作为因子 area, # 地区作为因子 family poisson(link log), # 泊松分布对数链接 data sim_data, offset log(exposure), # 偏移项 method REML) # 使用REML进行平滑参数估计 # 查看模型摘要 summary(gam_model)模型输出解读要点s(driver_age)和s(car_value)行会显示估计的自由度edf。如果edf接近1说明关系接近线性edf越大非线性越强。k是上限edf是实际使用的。Approximate significance of smooth terms部分给出了平滑项的显著性检验p值。显著的p值表明该因子的非线性效应是存在的。R-sq.(adj)和Deviance explained提供了模型整体拟合优度的度量。4.3 第三步可视化与解释费率因子效应GAM的强大之处在于可解释性。我们可以将每个平滑项的效应单独剥离出来可视化。# 绘制平滑效应图 plot(gam_model, pages 1, rug TRUE, shade TRUE, seWithMean TRUE, residuals FALSE)mgcv::plot.gam()会生成每个平滑项的效应图。Y轴是fj(xj)即该变量对线性预测器的贡献。如何转化为费率由于我们使用的是对数链接所以指数化后的效应就是乘法因子。例如如果f(年龄25) 0.5f(年龄50) 0那么在其他条件不变的情况下25岁司机相对于50岁司机的相对风险就是exp(0.5 - 0) ≈ 1.65即风险高出65%。为了更直观我们可以使用gratia包或手动计算预测值来绘制风险曲线。# 创建新数据框固定其他变量仅让年龄变化 new_age_data - expand_grid( driver_age seq(18, 80, length.out 100), car_value median(sim_data$car_value), vehicle_type Sedan, area Zone1, exposure 1 ) # 获取预测值及其标准误 pred_age - predict(gam_model, newdata new_age_data, type link, se.fit TRUE) new_age_data - new_age_data %% mutate(fit_link pred_age$fit, se_link pred_age$se.fit, fit_response exp(fit_link), # 转换为响应尺度风险率 lwr exp(fit_link - 1.96 * se_link), upr exp(fit_link 1.96 * se_link)) # 绘制年龄的风险曲线 p_age_effect - ggplot(new_age_data, aes(x driver_age)) geom_ribbon(aes(ymin lwr, ymax upr), fill lightblue, alpha 0.5) geom_line(aes(y fit_response), color blue, size 1) labs(title GAM模型驾驶员年龄对风险率的影响, x 驾驶员年龄, y 预测风险率相对值, caption 阴影部分为95%置信区间其他变量固定为典型值) theme_minimal() print(p_age_effect)这张图就是你的费率曲线核心依据。它清晰地展示了风险随年龄变化的非线性模式置信区间提供了不确定性度量。精算师可以基于此曲线结合业务策略如是否对年轻司机进行额外补贴或加费制定出差异化的年龄费率系数。4.4 第四步样条回归的基准与对比为了理解GAM在做什么我们可以用样条回归做一个基准对比。这有助于理解“平滑”背后的数学。# 使用自然样条手动拟合一个类似的模型 library(splines) ns_model - glm(claim_count ~ ns(driver_age, df 4) # 使用自然样条4个自由度 ns(car_value, df 3) vehicle_type area, family poisson(link log), data sim_data, offset log(exposure)) summary(ns_model)这里的关键是df自由度的选择它控制了样条的复杂度。df4意味着有4个参数来描述年龄的效应这需要基于之前的Loess探索或业务知识来设定。与GAM自动优化平滑度相比这更依赖于人工经验。你可以比较ns_model和gam_model的AIC或预测误差来评估哪种方式在当前数据上更有效。注意事项在最终定价模型中务必进行样本外验证。将数据分为训练集和测试集或使用时间交叉验证评估模型在未见数据上的预测性能如泊松偏差、基尼系数。GAM虽然灵活但也更容易过拟合训练数据。mgcv的自动平滑参数选择很大程度上缓解了这个问题但验证仍是黄金标准。5. 模型诊断、比较与过拟合防范一个模型在训练集上表现良好是起码的要求更重要的是它是否稳健、是否过拟合、以及在不同风险群体上的表现是否一致。5.1 GAM模型诊断图解读mgcv提供了强大的诊断函数。# 绘制模型诊断图 gam.check(gam_model)执行gam.check()会输出四张图和一个文本摘要残差Q-Q图检查残差是否符合假设分布此处为泊松。理想情况是点大致落在对角线上。残差 vs. 线性预测值图检查方差齐性和均值独立性。应无明显趋势或漏斗形状。直方图 of residuals检查残差分布是否近似正态对于大样本由于中心极限定理通常可接受。响应 vs. 拟合值图检查模型整体拟合效果。文本摘要会给出k值是否足够的建议。如果k-index接近1且p值低提示当前的k值基函数数量上限可能不足需要考虑增加k值例如将k10改为k15让模型有更多灵活性去捕捉未解释的模式。5.2 模型比较GLM vs. GAM我们需要量化非参数模型带来的提升。一个简单的比较是与线性模型GLM。# 拟合一个标准的泊松GLM线性效应 glm_model - glm(claim_count ~ driver_age I(driver_age^2) I(driver_age^3) # 尝试多项式 log(car_value) # 对价值取对数 vehicle_type area, family poisson(link log), data sim_data, offset log(exposure)) # 比较AIC AIC(glm_model, gam_model) # 比较训练集偏差 data.frame( Model c(GLM (多项式), GAM), Deviance_Explained c( 1 - (glm_model$deviance / glm_model$null.deviance), summary(gam_model)$dev.expl ) )如果GAM的AIC显著更低或解释的偏差比例更高且诊断图合理就证明了引入非线性平滑项的價值。但请注意在极度稀疏的数据或因子水平很多的情况下GAM可能不如有正则化的线性模型稳定。5.3 防范过拟合平滑参数与约束GAM通过惩罚项控制过拟合。mgcv默认的bs“cr”三次回归样条或bs“tp”薄板样条都内置了二阶导数惩罚使曲线趋向于直线。method “REML”是比默认“GCV”更稳定的平滑参数估计方法尤其适用于数据量不是特别大的情况。对于有明确业务逻辑的约束例如我们确信风险随车龄单调递增车越老风险越高可以在GAM中加入单调性约束bs“ps”或bs“cr”withmonoconstraints但需要更复杂的设置或者转而使用形状约束的加性模型SCAM这可以通过scam包实现。6. 从模型到费率表落地应用实务模型建好了曲线画出来了但如何将其转化为业务部门能用的费率表这是数据科学与精算实务结合的关键一步。6.1 计算相对风险系数费率表通常以一个基准风险水平为1.0其他组合按相对系数调整。假设我们以“30岁驾驶员Sedan车型Zone3地区车辆价值中位数”作为基准组合。# 定义基准风险剖面 baseline_profile - data.frame( driver_age 30, car_value median(sim_data$car_value), vehicle_type Sedan, area Zone3, exposure 1 ) # 预测基准组合的风险线性预测器尺度 baseline_linear_pred - predict(gam_model, newdata baseline_profile, type link) # 计算不同年龄的相对系数 age_grid - data.frame( driver_age seq(18, 80, by 5), # 按5岁分组 car_value median(sim_data$car_value), vehicle_type Sedan, area Zone3, exposure 1 ) age_grid$linear_pred - predict(gam_model, newdata age_grid, type link) age_grid$relativity - exp(age_grid$linear_pred - baseline_linear_pred) # 相对风险系数 # 输出费率系数表 rate_table_age - age_grid %% select(driver_age, relativity) %% mutate(relativity round(relativity, 3)) # 保留三位小数 print(rate_table_age)这样我们就得到了一个以30岁为1.0的年龄相对系数表。对于分类变量如车型计算方式类似将每个类别与基准类别比较即可。6.2 处理交互效应简单的可加模型假设变量间效应独立。但现实中年轻驾驶员开高性能车车型交互的风险可能不是简单的相加。GAM可以处理部分交互。# 在GAM中加入张量积平滑探索年龄和车辆价值的交互效应 gam_interaction - gam(claim_count ~ te(driver_age, car_value, k c(5, 5)) # 二维张量积平滑 vehicle_type area, family poisson(link log), data sim_data, offset log(exposure), method REML) # 可视化交互效应 vis.gam(gam_interaction, view c(driver_age, car_value), plot.type contour, too.far 0.1, main 驾驶员年龄与车辆价值的交互风险曲面, xlab 年龄, ylab 车辆价值)如果交互效应显著且符合业务直觉可以将其纳入模型。但需注意这会大大增加模型的复杂度和解释难度也可能需要更多数据支撑。一个务实的原则是先建立可加模型只有在其残差表现出明显的模式且业务上强烈怀疑存在交互时才谨慎引入。6.3 模型部署与监控最终模型需要集成到定价引擎中。通常有两种方式实时评分将GAM的预测函数predict.gam用生产级语言如Python的pyGAM或statsmodels或Java/Scala实现重写并部署。离线计算费率表如上所述预先计算所有可能风险组合的相对系数生成一个多维费率表供核心业务系统查询。这是更常见、更稳定的方式。上线后监控至关重要。需要持续跟踪模型稳定性比较预测风险与实际损失率的差异损失率分析。特征稳定性监控输入变量分布是否随时间漂移PSI分析。业务效果新费率下的业务增长、保单组合变化、盈利能力变化。非参数模型虽然灵活但对外部环境变化可能更敏感。建立定期的模型重训练和验证流程如每季度或每半年是保证定价持续有效的关键。在整个过程中沟通与解释同样重要。你需要能够向非技术背景的核保、销售和管理层解释为什么30岁的费率不是25岁的简单线性外推而是基于数据揭示的复杂模式。可视化图表是你最有力的沟通工具。