ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

R语言评分卡实战:互金风控模型与数据挖掘落地指南

R语言评分卡实战:互金风控模型与数据挖掘落地指南 简介面向数据挖掘学习者与互联网金融风控从业者该课程资源以高级数据挖掘为主线演示如何利用R语言处理海量信贷与交易数据完成数据清洗、特征筛选、信用评分卡构建并借助逻辑回归、决策树、随机森林等算法建立分类模型。压缩包内含4个文件约10.15MB包含R语言程序源代码、R历史命令记录、27页PDF讲义和27页PPT课件。其中R源码覆盖数据读取、预处理、特征工程、模型训练、交叉验证与网格调参等完整操作Rhistory可帮助复盘每条命令及其执行结果PPT与PDF则系统呈现大数据预处理、特征重要性评估、模型评估指标及互联网金融风控应用实例。配套源码和课件相互对照便于读者从理论到实践复现整个建模流程也可直接用于课程设计或内部培训参考。目前已有266人学习下载对希望快速上手R语言风控建模的读者是一份紧凑、实用的学习材料。1. 互联网金融风控模型为什么R语言评分卡在数据挖掘落地时依然是硬通货面试过互金风控模型岗的人大概率遇到过这类追问坏样本怎么定义、观察期表现期怎么切、分数和授信额度怎么挂钩。很多人把大数据挖掘理解成“模型越重越高级”但到了互联网信贷场景真正能过评审、能解释给业务听、能接监控的往往是R语言加逻辑回归做出来的评分卡。你手上这门《大数据挖掘之互联网金融风控模型》课程做的就是这件事把原始信贷行为数据清洗成特征用R语言建立一张可解释、可监控、可上线的互金风控评分卡并附了完整源代码方便逐行复现。它适合已经有SQL和基础统计能力、想从数据分析转到风控建模的从业者。与其背一串数据挖掘十大算法的名词不如先跑通一套从变量到分数的最小闭环。2. 先把R语言环境拉起来从压缩包到一张可以建模的数据表2.1 R语言在风控建模里的位置不拼算力拼试错效率常见认知是风控建模必须用Python才算大数据挖掘真实业务里两者是混着用的。Python强在把各种来源的数据拼起来做大宽表R语言强在统计检验、分箱、回归诊断这些细活。你在互金公司里看到的现象往往是这样今日数据跑批用Python特征分析、WOE分箱、评分卡参数校验脚本反而是R写的。R的data.table读几千万行文本数据不吃力glm、MASS、scorecard这些包做传统评分卡几乎是开箱即用的。打开课程压缩包后先别急着双击某个.R文件按下面三步走装R、装RStudio、装齐包。这一步卡住的人远比你想象多多半是下载了32位版本、装了包但library调不到、或者忘记设置工作目录。# 从官方CRAN装好R和RStudio后先把常用建模包装上 # Mirrors选择离你最近的即可RStudio默认配置已可用 install.packages(c(data.table, ggplot2, scorecard, MASS)) # data.table读写大文件、分组聚合 # ggplot2变量分布和分箱效果可视化 # scorecardWOE分箱、变量筛选、评分卡转换一体 # MASSglm.nb等扩展建模工具这段代码的逻辑很简单install.packages可以一次装多个包首次安装会同时装依赖等待时间经常超过十分钟进度条不动时不要立刻关掉。另外包装好但加载失败优先检查R版本和二进制来源Windows下不要混用源码编译和二进制包。装完包之后验证一下包版本和加载路径很多报错其实发生在这里。library(data.table) sessionInfo() # 打印R版本、平台、已加载包的版本号 # 如果library(data.table)报错先回头看安装日志是补丁错误还是依赖缺失2.2 解压、设置工作目录与读取模拟数据拿到资料包第一步是解压记住一条经验目录里不要有中文路径和空格。R在Windows上读中文路径经常出乱码课程资料包本身一般是英文目录但很多人习惯把压缩包放到“桌面/新建文件夹”后面fread会直接翻车。解压后打开RStudio用setwd把工作目录指到源码所在根目录。# 工作目录指向课程源码根目录注意用正斜杠或双反斜杠 setwd(C:/Users/your_name/course/r_credit_scorecard) getwd() # 先看目录结构常见课程包会有ppt/和code/两个层级 dir(all.files FALSE)如果手里没有课程自带的脱敏数据可以先造一份模拟数据跑通链路这一步对学R的人尤其管用。下面代码生成一万条样本字段分别是客户ID、年龄、收入负债比、近3月查询次数和好坏标签坏样本占比18%更接近互金早期资产的质量分布。# 如果没有真实脱敏数据用模拟数据先验证代码逻辑 set.seed(42) n - 10000 sim_data - data.frame( cust_id sprintf(C%06d, 1:n), age round(rnorm(n, 32, 8)), income_liability_ratio round(runif(n, 0.1, 5), 2), inquiry_cnt_3m sample(0:15, n, replace TRUE), target rbinom(n, 1, 0.18) ) fwrite(sim_data, sim_data.csv)逻辑与参数说明set.seed(42)是为了每次生成一样的数据跑出来的模型系数可对照复现age用正态分布但会生成小于18岁和大于70岁的异常值正好用来演示后续分箱如何容忍离群点inquiry_cnt_3m是计数变量右偏明显适合演示分箱时小数箱的问题。生产环境不要用随机切分来替代时间切分这里为了讲清流程才用随机抽样。真实数据通常长成一张订单表和一张用户行为表要先把两表按客户ID关联。R里最常见的做法是用data.table的键关联速度比merge快一个量级。# 订单表和用户行为表关联示例 order_dt - fread(order_table.csv, encoding UTF-8) user_dt - fread(user_behavior.csv, encoding UTF-8) setkey(order_dt, cust_id) setkey(user_dt, cust_id) wide_dt - user_dt[order_dt] # data.table左连接 wide_dt - wide_dt[!duplicated(cust_id)] # 一个客户只保留一条申请信息逻辑与参数说明user_dt[order_dt]是data.table的连接语法连接键由setkey指定。duplicated那行是为了去掉重复申请A卡建模一般以申请事件为主体一个客户多次申请要么取首次要么取额度最高那次取决于业务口径。2.3 第一轮变量检查缺失率、常量和单变量区分度建模不是上来就灌进glm。第一步先看三张表缺失率表、常量表、单变量区分度排序表。我一般会用下面这段代码把训练集扫一遍谁缺失率超过70%、谁只有单一取值直接进回收站。# 训练集第一轮粗筛 miss_rate - sort(colMeans(is.na(train_dt)), decreasing TRUE) const_flag - sapply(train_dt, function(x) length(unique(x)) 2) print(head(miss_rate, 10)) print(names(const_flag)[const_flag]) # 缺失率超70%的字段先观察是否业务上本来就没有 # 常量字段一般不进入模型因为不含判别信息且容易在跨期时失效这段的逻辑是缺失率不是越高越删除要看字段属性和原因。比如“客户职业”缺失40%可能是产品流程里不强制填写这种缺失本身就是一个状态可以单独编码成“未知”而不是删掉。常量字段则基本无建模价值除非业务上明确要和另一字段交叉出交互项。变量初筛不只看缺失和常量还要看单变量区分度。连续变量最简单的办法是拿它和目标做t检验或算AUC这里给一个快速AUC近似算法兜底避免对每个变量都调一次完整模型。# 连续变量的快速区分度wilcox检验 简单AUC quick_auc - function(x, y) { rank_mean_bad - mean(rank(x)[y 1]) n_bad - sum(y 1); n_good - sum(y 0) auc - (rank_mean_bad - n_bad * (n_bad 1) / 2) / (n_bad * n_good) return(auc) } sapply(c(age, income_liability_ratio, inquiry_cnt_3m), function(v) quick_auc(train_dt[[v]], train_dt$target))这里说明AUC为0.5表示没有区分度高于0.6需要结合业务再看高于0.7的字段要警惕它是不是带未来信息。比如“是否有逾期催收记录”这种字段算出来AUC到0.9基本可以断定字段时间窗口越界属于数据泄漏而不是模型能力强。3. 从WOE到评分卡R语言拟合、评估与输出一张可解释分数表3.1 为什么互金风控选择逻辑回归而不是重算法数据挖掘十大算法里逻辑回归不算花哨但它有三个不可替代的优势。第一系数与风险方向直接对应业务审模型时能一个变量一个变量过。第二输出概率可以直接通过logit变换映射到分数评分卡从数学上就是从逻辑回归推导出来的。第三逻辑回归的抗过拟合表现通常更好尤其在样本量只有几万、坏样本几千的场景里。这里不是说要放弃XGBoost而是互金评分卡落地的主模型一般保留逻辑回归复杂模型要么做影子模型要么用在贷中降额等场景。R语言在这个环节特别顺手因为分箱、WOE、IV这些传统评分卡工具箱最早就是在R社区成熟起来的。你如果是从Python数据分析与数据挖掘实战转过来的会发现R里的统计检验、分箱单调性检查函数更全写起来也更短。3.2 WOE分箱与IV计算手写和包调用两种方式WOE的含义是“这一箱好样本占比相对坏样本占比的偏离程度”IV是把所有箱的这种偏离按信息量加权求和。公式不复杂但手写一遍能避免你后面被现成包的输出绕晕。下面这个自写函数用data.table做聚合返回每个箱的样本量、好坏占比、WOE和IV。library(data.table) calc_woe_iv - function(x, y, breaks NULL, min_pct 0.02) { d - data.table(x x, y y) # 未指定分箱点时默认按十分位粗分箱 if (is.null(breaks)) { cuts - unique(quantile(x, probs seq(0, 1, length.out 11), na.rm TRUE)) breaks - c(-Inf, cuts[-c(1, length(cuts))], Inf) } else { breaks - c(-Inf, unique(breaks), Inf) } d[, bin : cut(x, breaks breaks, include.lowest TRUE)] agg - d[, .(cnt .N, bad sum(y)), by bin] agg[, good : cnt - bad] agg[, bad_pct : bad / sum(bad)] agg[, good_pct : good / sum(good)] # 加0.5平滑避免某箱坏样本为0时woe出现Inf agg[, woe : log((good 0.5) / (bad 0.5))] agg[, iv : (good_pct - bad_pct) * woe] return(agg) } # 用法对age变量做十分位粗分箱 res - calc_woe_iv(train_dt$age, train_dt$target) print(res)逻辑与参数说明min_pct参数这里只是预留真实使用时会在粗分箱后人工合并样本占比低于2%的箱否则WOE会波动很大。防止坏样本为0导致log(Inf)的办法这里用加0.5平滑近似但这是临时手段正式分箱时要保证每箱好坏样本都足够。IV计算公式里good_pct - bad_pct是分布差异的权重WOE是方向两者相乘再求和就是IV。理解了手写逻辑再看scorecard包就轻松很多它的woebin函数做的事情和上面这段一致只是多加了自动合并、单调性检验几种策略。生产环境用包更快原理理解还是得靠手写一遍。library(scorecard) # 用scorecard包生成分箱规则再应用到训练和测试集 bins - woebin(train_dt, y target, x c(age, income_liability_ratio, inquiry_cnt_3m), bin_num_limit 6, positive bad|1, no_cores 1) train_woe - woe_apply(train_dt, bins) test_woe - woe_apply(test_dt, bins)逻辑与参数说明positivebad|1告诉包把标签1当成坏样本分箱方向会按“坏样本占比”排序bin_num_limit6限制每变量最多6箱箱数越多越容易过拟合互金A卡常用4到6箱no_cores1是调试时避免并行输出乱序正式跑大数据时再放开。3.3 用WOE变量拟合逻辑回归并转成评分卡用WOE变换后的变量做glm边跑边看系数方向。每个变量的系数应当与业务理解一致比如查询次数越多风险越高那么查询次数的WOE和系数相乘后整体分数应该随查询次数增加而走低。model - glm(target ~ age_woe income_liability_ratio_woe inquiry_cnt_3m_woe, data train_woe, family binomial(link logit)) summary(model) # 重点看每个变量的Estimate方向和Pr值 # 若某变量系数方向与业务直觉相反先排查分箱方向和共线性逻辑与参数说明familybinomial指定二分类logitsummary输出里Coefficients表的Pr(|z|)是wald检验p值A卡筛选变量常以p0.05为基线但不必死守因为互金场景变量间相关性高p值大不代表变量无效。看到某个系数符号与预期相反时常见原因不是模型错了而是WOE方向定义反了或者该变量与另一个变量高度相关导致符号翻转。评分卡最核心的转换公式就一行。给定两个常数基准分和翻倍分。常见做法是设“好坏比19:1时打600分好坏比每翻一倍加20分”然后算factor和offset。# PDO20, base_score600, base_odds19 factor_val - 20 / log(2) # 约28.85 offset_val - 600 - factor_val * log(19) # 约684.94 cat(factor , factor_val, offset , offset_val, \n) # 测试集打分predict的link类型直接给出log(odds) test_woe$score - offset_val factor_val * predict(model, test_woe, type link) summary(test_woe$score)逻辑与参数说明predict(typelink)返回线性组合值也就是log(odds)乘factor加offset就是标准评分卡分数。分数越高代表风险越低。这个分数不是最终授信分数它通常还要叠加额度模型和定价模型一起使用。生产代码里我习惯把factor和offset写到配置文件后续调基准分只改两个数不要在每个脚本里重算。如果需要输出每个变量的得分明细表把系数乘以该变量的WOE再乘factor就得到变量贡献分。下面这段生成一张映射表供业务解释使用。# 生成变量贡献得分表 coefs - coef(model) contrib - data.frame( var names(coefs), beta as.numeric(coefs), factor factor_val ) contrib$score_per_woe_unit - contrib$beta * contrib$factor print(contrib) # 单个客户的score offset 截距项贡献 各变量WOE乘以对应score_per_woe_unit之和说明截距项的贡献也要乘factor再与offset合并所以表里第一行Intercept的beta乘factor含义是基准常数调整。测试集打分的完整表达式就是把所有这些加总。4. 模型上线前的评估与避坑记录KS、PSI和五个高频翻车案例4.1 评估区分度KS比单看AUC更贴近评分卡使用方式互金风控模型看区分度AUC当然要看但KS更常用因为KS直接反映模型在哪个分数段上把好坏客户分得最开。下面这个函数不依赖任何包输入预测风险和真实标签输出KS值。cal_ks - function(pred, label) { d - data.frame(pred pred, label label) d - d[order(-d$pred), ] # 按预测风险从高到低排序 d$cum_bad - cumsum(d$label) d$cum_good - cumsum(1 - d$label) n_bad - sum(d$label); n_good - sum(1 - d$label) ks - max(abs(d$cum_bad / n_bad - d$cum_good / n_good)) return(ks) } cal_ks(test_woe$score, test_woe$target)逻辑与参数说明排序方向不影响结果因为abs取了绝对差。业务上一般把0.3作为评分卡可用的底线0.4到0.5属于区分度良好超过0.6反而要警惕变量泄漏。上线前我会把训练集、测试集、最近一个月新客户的KS全部打出来三个数字一起看避免只拿测试集一个数汇报。4.2 监控分数偏移PSI的计算与阈值评分卡上线后最怕分数整体漂移但模型上线前就应该用PSI把“开发样本”和“近期样本”做一次对比。PSI的计算是用近期样本的分布相对开发样本分布的信息值偏移下面代码用十分位断点算PSI。cal_psi - function(score_new, score_base, bins 10) { cuts - quantile(score_base, probs seq(0, 1, length.out bins 1), na.rm TRUE) cuts[1] - -Inf; cuts[length(cuts)] - Inf base_cut - table(cut(score_base, breaks cuts)) new_cut - table(cut(score_new, breaks cuts)) base_pct - as.numeric(base_cut) / sum(base_cut) new_pct - as.numeric(new_cut) / sum(new_cut) psi - sum((new_pct - base_pct) * log((new_pct 1e-6) / (base_pct 1e-6))) return(psi) } # 用开发样本做基准对比跨期样本 cal_psi(test_woe$score, train_woe$score)逻辑与参数说明bins指定分箱数十分位是常用配置1e-6是防止某箱样本为0导致log(0)。PSI小于0.1说明两个样本分布可接受0.1到0.25需要关注变量层面到底发生了什么大于0.25就不要再继续用旧分数做决策了。需要注意PSI只看分布形状不直接回答“分数偏移是否造成更多坏账”所以它要和KS一起看。4.3 避坑一训练集和测试集随机切分导致“虚假精度”现象自己写脚本时习惯set.seed然后sample出训练集模型训练集KS 0.45测试集也有0.4自我感觉很好结果上线后一个月KS掉到0.2。原因随机切分把同一时期的数据分到两边特征里只要有一点点隐含的时间趋势就会被模型当成规律学进去上线后时间一变规律失效。解决A卡建模必须按申请时间顺序切分比如2019-01到2020-06做训练2020-07到2020-09做测试时间断点两边没有任何样本重叠。课程源码里如果是随机切分你复现时至少手动改成时间切分再评估一次。4.4 避坑二WOE分箱出现单箱IV占比90%现象某个变量的IV异常高打开分箱表发现有一箱好坏比特别极端WOE绝对值到3以上一箱就贡献了全部IV的九成。原因这类箱子通常是某个埋点字段在特定渠道、特定时间段的数据采集异常比如查询次数15次以上全是坏客户但实际原因是渠道导流集中了一批多头借贷用户而不是查询次数这个变量本身有强因果关系。解决先看这一箱的样本量是否低于整体2%低于就合并如果样本量够大说明字段与目标的关系只在局部成立要拆渠道或者引入交叉变量而不是直接拿这个WOE进模型。4.5 避坑三表现期口径不统一导致标签系统飘移现象同一个模型按月观察KS忽高忽低每个月跑出来的AUC像过山车。原因坏账标签没有统一的表现期有的月份用M1口径有的月份用M2有的月份把6个月表现期缩成了3个月标签本身的变化全部被算进模型效果。解决固定一个表现期比如以放款后6个月内是否出现M2作为坏样本定义训练集、测试集、上线后的监控样本全部用同一口径。源码包里的target列如果只写了1和0先搞清楚它对应逾期几期口径不对就自己重算一版标签。4.6 避坑四拒绝推论被省略模型只在被放款人群上自嗨现象模型上线后做反事实验证发现全量客群预测分布和开发样本差异巨大。原因开发样本只有被审批通过的客户被拒绝的客户没有标签模型学到了通过客户的偏好上线后面对全量申请时评分失真。解决至少做两件事一是记录每位被拒绝客户的拒绝原因和当时模型分数为后续做准备二是把拒绝样本纳入后续模型迭代用半监督学习或者人工外呼抽样补齐标签。课程源码一般不会覆盖拒绝推论因为课件数据已经是被放款样本这一点要在实际业务里自己补上。4.7 避坑五字段时间窗越界造成标签泄漏现象一个特征比如“近6个月最大逾期天数”跑出IV 0.8业务还很兴奋结果发现字段统计截止日比申请日还晚两个月。原因ETL取数时用了整张表的最新快照而不是申请时点之前的快照特征里嵌着未来信息。解决对所有特征做回溯校验每个特征的取值时间必须早于样本的观察点代码里统一加一个as_of_date参数做特征工程时把数据截到as_of_date之前。遇到任何IV高到离谱的字段第一反应不是高兴而是先查它是不是带未来信息。5. 把课程源代码变成自己的资产复现验证与改造技巧5.1 拿到源码先看结构别急着跑第一个R文件资料包解压后先找PPT课件的目录结构。一个讲评分卡的课程PPT通常会按数据清洗、分箱、建模、评估、监控展开R脚本一般也按这个顺序编号。打开任意一个R文件先看它的library行把用到的包一次性装齐。如果源码里直接写了install.packages还好最怕跑到一半遇到缺包再回头装。用下面这段扫描脚本把整个code目录的依赖汇总出来。scripts - list.files(pattern \\.R$, full.names TRUE) need - character() for (s in scripts) { lines - readLines(s, warn FALSE) pkgs - gsub(library\\((.*?)\\), \\1, grep(^library\\(|^require\\(, lines, value TRUE)) need - union(need, pkgs) } print(need)逻辑与参数说明gsub把library调用里的包名抓出来union去重后一次性打印。课件来源的代码很少用复杂包管理这个简单办法足以应付大多数情况。遇到install_github的再单独处理即可。5.2 跑通之后做四个检查点源码跑通不等于复现成功我会对照业务输出做四个检查。第一查目标变量坏样本占比课程里target的坏占比一般在10%到20%偏离太大说明数据切分或标签口径没对齐。第二查WOE方向查询次数这类风险正向变量WOE应随箱号下降年龄应呈U型。第三查KS和AUC数量级训练集KS在0.3到0.5之间正常高到0.7先怀疑泄漏。第四查分数分布画直方图看是否是偏正态集中在几个离散值上通常是WOE映射没对齐。# 检查分数分布是否合理 hist(test_woe$score, breaks 40, main 测试集分数分布, xlab score) quantile(test_woe$score, probs c(0.01, 0.1, 0.5, 0.9, 0.99))逻辑与参数说明分数跨度太小说明评分卡区分度不足跨度大到200分以上要检查是不是某个变量WOE极端值在驱动分数。5.3 把一次性脚本改成可迭代的模型骨架课程源码基本是“一次性脚本”上线前需要把散落的常量抽成配置、把逻辑回归的factor和offset计算固定下来。我一般会在脚本开头定义三个配置项就够了。config - list(pdo 20, base_score 600, base_odds 19) factor_val - config$pdo / log(2) offset_val - config$base_score - factor_val * log(config$base_odds)改完之后换数据、换基准分都只改这三行。我早期跑别人的评分卡代码跑通就以为掌握被问到“WOE方向为什么这样定”才意识到代码可以复制口径和逻辑不能。后来每个脚本都要求自己讲得清每一步在算什么这门课才算真正消化。希望帮到你。本文还有配套的精品资源点击获取
返回列表