资讯动态

基于交叉表的风控规则生成:从数据挖掘到规则引擎的实战指南

发布时间:2026/10/27 6:50:53 来源:尧图企业网站定制
1. 风控规则为什么要从交叉表里“长”出来做风控的人大概都有过这种体验业务方丢过来一份逾期名单说“帮我看看这批人有什么共同点”你打开数据一看几十个字段性别、年龄段、职业、渠道、额度区间、还款方式……单看任何一个字段都觉得“好像有点关系”但真要把规则写出来又不知道从哪下手。更麻烦的是规则写得太宽拦不住坏人写得太窄误伤一片好人业务那边立刻投诉电话打爆。基于交叉表生成风控规则这套思路本质上就是解决“从数据里找规则”这个最原始也最核心的问题。交叉表Cross Tabulation简称crosstab说白了就是两个或多个维度字段的频数分布表把“某个年龄段”和“某个渠道”组合在一起看每个格子里的样本量、坏样本量、坏账率。风控规则的本质是什么是一组条件判断比如“年龄在25岁以下 且 渠道为某类线上推广 且 历史逾期次数大于等于2”命中这组条件的人我们判定为高风险。而交叉表恰好能把这些条件组合的“风险浓度”直观地摆在你面前。这套方法适合谁我总结下来有三类人最用得上。第一类是刚入行的风控策略分析师手里有数据但不知道怎么系统性地挖规则靠拍脑袋写规则效率低还容易漏第二类是数据挖掘或算法背景的同学模型跑得飞起但业务方要的是“可解释的规则”模型分再准也替代不了规则引擎里的硬性拦截第三类是中小机构的风控负责人没有庞大的特征平台和实时计算资源就靠Python和一份历史样本想快速搭起一套能跑起来的规则集。这三类人的共同点是需要一套可复现、可解释、可迭代的规则生成流程而不是一堆黑箱分数。我见过太多团队在这件事上走弯路。有的直接上决策树把树拆成规则结果规则又多又碎业务看不懂有的纯靠专家经验规则倒是简洁但覆盖率低得可怜坏人从缝里就溜过去了。交叉表这条路子的价值在于它把“统计显著性”和“业务可解释性”捏在了一起——每个格子你都能指着说“这批人坏账率是平均水平的3倍样本量还够这条规则值得上”。接下来我就把这套流程从设计思路到落地细节掰开揉碎讲清楚。2. 整体方案设计与核心思路拆解2.1 为什么选交叉表而不是直接上模型先说清楚一个前提规则和模型解决的不是同一个问题。模型输出的是概率分适合做额度定价、排序规则输出的是“是/否”的判断适合做硬性拦截、准入控制。风控体系里两者是互补的但如果你现在要的是能写进规则引擎、能被业务方审核、能在贷前拦截环节立刻生效的东西那规则生成就是绕不开的活。那为什么用交叉表来生成规则而不是用决策树或者关联规则挖掘我对比过几种方案说下实际感受。决策树比如CART确实能自动切分但它的切分逻辑是追求整体纯度提升切出来的叶子节点往往条件很深五六个条件串在一起业务方一看就懵而且树对类别型变量的处理不够直观独热编码之后维度爆炸。关联规则Apriori那套适合找“买了啤酒的人还买尿布”这种共现关系但风控场景里我们关心的是坏样本浓度不是简单的共现频次而且关联规则对样本量分布不敏感容易挖出一堆样本量只有个位数的“伪规则”。交叉表的优势就体现在这里。第一维度可控你可以自由选择两个、三个字段做组合每个组合的风险表现一目了然第二样本量和坏账率同时可见这是判断规则是否可用的两个硬指标缺一不可第三业务解释成本极低“25岁以下某渠道”这种条件业务方秒懂不需要解释什么叫基尼不纯度。当然交叉表也有短板字段多了组合爆炸所以后面我会讲怎么控制维度数量和筛选顺序。2.2 规则生成的整体流水线长什么样我把这套流程拆成五个环节串起来就是一条完整的流水线。第一步是样本准备确定好坏样本定义、观察期和表现期这是所有风控分析的地基地基歪了后面全白搭。第二步是单变量分析对每个候选字段做分箱和初步的风险排序筛掉那些区分度太弱的字段减少后续组合的搜索空间。第三步是交叉表构建把筛选后的字段两两组合或三三组合计算每个格子的样本量、坏样本数、坏账率、提升度Lift。第四步是规则提取与筛选设定样本量下限、坏账率下限、提升度下限把满足条件的格子拎出来转成“字段A属于区间X 且 字段B属于类别Y”的规则形式。第五步是规则评估与去重用覆盖率和命中率评估每条规则的价值去掉高度重叠的规则最终输出一份规则清单。这条流水线里最容易被忽视的是第二步和第五步。很多人上来就两两交叉结果字段太多跑出来的表几百张人工看不过来。单变量分析的作用就是“先粗筛一遍”把明显没区分度的字段提前干掉。而规则去重更是关键交叉表挖出来的规则天然存在重叠比如“年龄25”和“年龄30”两条规则可能命中同一批人不去重的话规则集冗余严重上线后互相干扰。2.3 好坏样本定义与观察期设计这块必须单独拎出来说因为它是整个分析的前提但恰恰是新手最容易含糊过去的地方。坏样本的定义不是简单的“逾期了”而是要明确“逾期多久算坏”。行业里常见的做法是M1逾期30天以上或M2逾期60天以上作为坏样本M0逾期1-29天通常算灰色地带可以先排除或者单独分析。为什么不用“只要逾期就算坏”因为短期逾期很多是忘记还款或者扣款失败导致的跟真正的信用风险不是一回事混在一起会把规则带偏。观察期和表现期的设计同样重要。观察期是你用来提取特征的时间窗口表现期是观察期之后用来判定好坏的时间窗口。举个例子你拿2023年1月到6月放款的客户做观察期看他们在这半年里的行为特征然后跟踪2023年7月到12月这半年的还款表现来定义好坏。这里有个硬性要求表现期必须足够长长到坏样本能充分暴露。如果表现期只有一个月很多潜在坏样本还没逾期你算出来的坏账率是偏低的规则也会偏松。一般来说表现期至少覆盖一个完整的还款周期消费贷场景下3到6个月是常见选择。注意观察期和表现期不能有重叠否则特征里会混入“未来信息”这在风控里叫标签泄漏是最致命的错误之一。我见过有团队把表现期内的逾期次数当成特征去挖规则挖出来的规则漂亮得不行一上线全废因为线上根本拿不到这个特征。2.4 字段筛选的优先级逻辑不是所有字段都值得拿去做交叉。我的经验是按这个优先级排第一梯队是强业务逻辑字段比如历史逾期次数、当前负债率、收入负债比这些字段跟违约的相关性有扎实的业务解释优先参与交叉第二梯队是客群属性字段比如年龄段、职业类别、学历、婚姻状况这些字段本身区分度可能一般但跟第一梯队字段交叉后往往能挖出有价值的组合第三梯队是渠道和产品字段比如获客渠道、产品类型、额度区间这类字段的风险差异通常很大但要注意渠道本身可能是“结果”而不是“原因”解释时要谨慎。有个细节值得说类别型字段的分箱。像职业这种字段原始类别可能几十上百个直接交叉表会炸。我的做法是先按坏账率排序把坏账率相近的类别合并同时保证每个合并后的类别样本量不低于总样本的5%。数值型字段则用等频分箱或者基于坏账率单调性的卡方分箱箱数控制在5到8个。分箱这件事没有绝对标准核心原则是“箱内足够纯、箱间有差异、每箱样本量够”。3. 核心细节解析与实操要点3.1 交叉表的指标计算不只看坏账率很多人做交叉表只看一个指标——坏账率这是不够的。一个格子坏账率50%听起来很吓人但如果这个格子只有10个样本5个坏的那这个50%的置信度极低可能纯属随机波动。所以交叉表至少要同时看四个指标样本量、坏样本数、坏账率、提升度。提升度的计算方式是Lift 格子坏账率 / 整体坏账率。整体坏账率是全部样本的坏账率Lift大于1说明这个格子的风险高于平均Lift越大风险越集中。我一般把Lift作为排序的主指标因为它剔除了整体坏账率水平的影响不同批次、不同产品之间可以横向比较。举个具体的数整体坏账率5%某个格子坏账率15%Lift就是3意味着命中这个格子的人违约概率是平均水平的3倍这个信号就很强了。还有一个容易被忽略的指标是坏样本覆盖率也就是这个格子里的坏样本数占全部坏样本数的比例。这个指标决定了规则的“拦截贡献”。一条规则Lift很高但只覆盖了1%的坏样本那它对整体资产质量的改善微乎其微。实操中我会把Lift和坏样本覆盖率放在一起看优先选那些Lift大于2且坏样本覆盖率不低于5%的格子。指标计算方式作用参考阈值样本量格子内样本总数判断统计可靠性不低于总样本1%且不少于100坏账率格子坏样本数/格子样本量衡量风险绝对水平高于整体坏账率提升度Lift格子坏账率/整体坏账率衡量风险相对集中度大于1.5优选大于2坏样本覆盖率格子坏样本数/总坏样本数衡量拦截贡献不低于3%优选5%以上3.2 维度组合的数量控制两两交叉是甜点区字段一多组合数量是指数级增长的。10个字段两两组合是45张表三三组合就是120张四四组合更夸张。我的经验是两两交叉作为主力三三交叉作为补充四维以上基本不考虑原因有两个一是四维以上的规则条件太苛刻命中样本量往往不够统计上不可靠二是规则条件越多业务解释和系统维护成本越高规则引擎里一条四条件规则出问题排查起来很痛苦。两两交叉也不是随便配。我会按“强字段配弱字段”的原则来组合第一梯队的强字段比如历史逾期次数分别跟第二、第三梯队的字段交叉这样既能利用强字段的区分度又能通过交叉发现细分客群里的风险差异。强字段之间也可以交叉但优先级靠后因为强字段各自的风险信号可能高度相关交叉后增量信息有限。实操中我会先跑一轮两两交叉把Lift和覆盖率达标的格子全部捞出来看看哪些字段组合贡献的规则最多再决定要不要对这几个字段做三三交叉深挖。3.3 规则阈值的设定样本量下限怎么定样本量下限是规则筛选的第一道闸门定得太低会捞出一堆噪声规则定得太高会漏掉细分客群里的真信号。我的做法是双阈值绝对下限和相对下限同时满足。绝对下限是格子样本量不少于100如果总样本只有几千可以放宽到50相对下限是格子样本量不低于总样本的1%。为什么两个都要因为总样本量大的时候1%可能就有几千条绝对下限形同虚设总样本量小的时候1%可能只有几十条绝对下限又太严。两个条件取交集能兼顾不同数据规模。坏账率阈值和Lift阈值则要看业务容忍度。如果这套规则是用于贷前拒绝那阈值可以设高一点Lift大于2.5、坏账率高于整体3倍以上才纳入因为拒绝规则误伤成本高宁可漏放不可错杀。如果是用于人工复审队列阈值可以放宽Lift大于1.5就可以进复审池反正有人工兜底。这个区分很重要同一批交叉表结果用途不同筛选标准完全不同。3.4 规则表达式的自动生成逻辑交叉表挖出来的格子要转成规则引擎能识别的表达式中间有个映射过程。数值型字段的分箱格子里对应的是区间比如“年龄分箱2”对应“25 年龄 35”规则表达式就写成age 25 and age 35。类别型字段对应的是类别集合比如“渠道分箱1”包含“渠道A、渠道B”表达式写成channel in (A, B)。多个字段之间用and连接。这里有个实操细节分箱边界要处理好开闭区间避免相邻箱之间出现“漏网”的数值。比如分箱是[18,25)、[25,35)那25岁的人落在第二个箱边界值25必须明确归属。我习惯统一用左闭右开最后一个箱右端闭合这样不会漏。另外类别型字段合并后的类别集合要完整覆盖原始类别不能有类别既不在任何箱里也没被排除否则线上跑规则时会出现“条件不匹配”的异常。# 规则表达式生成示意伪代码逻辑 def build_rule_expr(field_a, bin_a, field_b, bin_b, field_meta): cond_a field_meta[field_a][bins][bin_a][expr] # 如 age 25 and age 35 cond_b field_meta[field_b][bins][bin_b][expr] # 如 channel in (A,B) return f{cond_a} and {cond_b}4. 实操过程与核心环节实现4.1 数据准备与好坏标签构造先把手上的样本整理成一张宽表每行一个客户或一笔订单列包括客户ID、放款时间、各维度特征、还款表现。好坏标签的构造我一般用这段逻辑取表现期内最大逾期天数大于等于30天标记为1坏等于0标记为0好1到29天之间的先标记为灰色分析时排除。排除灰色样本会让坏账率的分母变小、坏账率绝对值变高但换来的是标签更干净规则信号更纯。import pandas as pd import numpy as np # 假设原始表包含 max_dpd最大逾期天数 df[label] np.where(df[max_dpd] 30, 1, np.where(df[max_dpd] 0, 0, np.nan)) df df.dropna(subset[label]).copy() df[label] df[label].astype(int) overall_bad_rate df[label].mean() print(f整体坏账率: {overall_bad_rate:.4f}, 样本量: {len(df)})这段代码看着简单但有两个坑要提醒。第一max_dpd的口径要跟业务确认清楚是“当前逾期天数”还是“历史最大逾期天数”前者会随时间变化后者是快照做规则分析必须用后者否则同一批客户不同时间跑出来的标签不一样。第二排除灰色样本后要重新检查好坏比例如果坏样本占比低于2%说明样本本身风险极低挖出来的规则可能不稳定需要考虑扩大样本时间范围或者换一个风险更高的客群做分析。4.2 单变量分箱与初步筛选分箱这步我用的是“先粗后细”的策略。数值型字段先按业务常识切几个粗箱比如年龄按18-25、25-35、35-45、45-55、55切然后看每个粗箱的坏账率和样本量如果某个粗箱内部坏账率差异很大比如25-35这个箱里25-28和28-35差了一倍再考虑细分。类别型字段先按原始类别算坏账率把坏账率接近且业务含义相近的类别合并。def univariate_analysis(df, col, labellabel, min_bin_pct0.05): tmp df.groupby(col)[label].agg([count, sum]) tmp[bad_rate] tmp[sum] / tmp[count] tmp[lift] tmp[bad_rate] / df[label].mean() tmp[pct] tmp[count] / len(df) # 样本占比过低的箱先标记出来后续合并 tmp[too_small] tmp[pct] min_bin_pct return tmp.sort_values(bad_rate, ascendingFalse)单变量分析的核心产出是字段区分度排序。我一般看两个东西一是坏账率最高箱和最低箱的Lift差距差距越大字段越有价值二是坏账率随分箱是否单调单调性好的字段比如逾期次数越多坏账率越高在交叉时更稳定。区分度太弱的字段最高最低Lift差距不到1.5倍直接不进交叉环节省得浪费计算资源。4.3 两两交叉表的批量构建这一步是体力活但代码可以写得很规整。核心是把筛选后的字段列表做两两组合对每个组合跑pd.crosstab或者groupby聚合算出每个格子的四个核心指标然后把所有结果拼成一张大表。from itertools import combinations def build_cross_table(df, col_a, col_b, labellabel): grp df.groupby([col_a, col_b])[label].agg([count, sum]).reset_index() grp.columns [col_a, col_b, sample_cnt, bad_cnt] grp[bad_rate] grp[bad_cnt] / grp[sample_cnt] grp[lift] grp[bad_rate] / df[label].mean() grp[bad_coverage] grp[bad_cnt] / df[label].sum() grp[field_a] col_a grp[field_b] col_b return grp candidate_fields [age_bin, channel_bin, job_bin, dpd_hist_bin, income_bin] all_cross [] for a, b in combinations(candidate_fields, 2): all_cross.append(build_cross_table(df, a, b)) cross_result pd.concat(all_cross, ignore_indexTrue)跑完这一步cross_result里就是所有两两组合的格子明细。我一般会先看一眼总行数如果超过几千行说明字段分箱太细或者字段太多需要回头收敛。然后按Lift降序排人工扫一遍头部结果看看有没有明显不合理的比如某个格子样本量只有几十但Lift高得离谱这类通常是噪声直接过滤。4.4 规则筛选、去重与优先级排序筛选就是套阈值样本量、坏账率、Lift、坏样本覆盖率四个条件同时满足的格子留下来。去重是这步的难点因为不同字段组合可能命中高度重叠的人群。我的去重逻辑是按Lift从高到低遍历规则计算每条规则与已保留规则的样本重叠率重叠率超过70%的规则丢弃。重叠率的计算方式是两条规则命中人群的交集除以并集Jaccard相似度这个阈值可以根据规则集规模调整规则集小的时候可以放宽到80%。def dedup_rules(rules, df, overlap_thresh0.7): kept [] for _, rule in rules.sort_values(lift, ascendingFalse).iterrows(): mask eval_mask(df, rule) # 根据规则条件生成布尔掩码 redundant False for k in kept: k_mask eval_mask(df, k) jaccard (mask k_mask).sum() / (mask | k_mask).sum() if jaccard overlap_thresh: redundant True break if not redundant: kept.append(rule) return kept去重之后还要做优先级排序因为规则引擎里规则是有执行顺序的。排序逻辑我一般综合三个因素Lift高低、坏样本覆盖率、规则条件复杂度。Lift高且覆盖率高的排前面条件简单的排前面同样效果下优先用两条件规则不用三条件。最终输出的规则清单里每条规则带上它的样本量、坏账率、Lift、覆盖率业务方审核时一目了然。4.5 规则效果的样本外验证交叉表是在训练样本上挖出来的直接上线有过度拟合的风险。我的做法是时间外验证用更晚一段时间的新样本跑一遍规则看命中人群的坏账率是否依然显著高于整体。如果训练样本上Lift是3样本外Lift掉到1.2那这条规则大概率是拟合了噪声不能上。样本外验证的样本量不用很大但时间上必须严格在训练样本之后不能有重叠。验证通过后规则清单就可以交给规则引擎配置了。配置时注意规则的互斥和优先级如果两条规则可能同时命中同一个客户要明确哪条先生效避免重复拦截或者逻辑冲突。我习惯在规则清单里加一列“互斥组”把可能重叠的规则编到同一组引擎里同组规则只执行第一条命中的。5. 常见问题与排查技巧实录5.1 交叉表跑出来规则太多太碎怎么办这是最高频的问题。原因通常是字段分箱太细或者候选字段太多。排查思路先看单变量分析里每个字段的箱数超过8个箱的字段重新合并再看候选字段数量超过10个的先砍到8个以内优先保留单变量区分度高的。还有一个隐藏原因是样本量太大比如百万级样本任何微小的坏账率差异都会统计显著这时候要把Lift阈值和坏账率阈值同时提高不能只看统计显著性要看业务显著性。5.2 某条规则Lift很高但样本量很少要不要保留我的原则是看用途。如果是贷前拒绝规则样本量低于100的基本不保留因为线上每天命中可能就几条拦截效果微乎其微还增加了规则维护成本。如果是人工复审规则可以保留但要在规则清单里标注“低样本量”并且设置一个观察期上线后持续监控命中量和命中人群的坏账表现表现不及预期就下线。低样本量规则最大的风险是不稳定这个月Lift是5下个月可能就变成0.8所以必须配合监控。5.3 规则上线后命中率远低于预期线下算的覆盖率是5%上线后实际命中率只有0.5%差了十倍。这种问题我遇到过几次排查下来通常是三个原因。第一是特征口径不一致线下用的“历史逾期次数”是截至观察期末的快照线上取的是实时值时间点对不上第二是分箱边界处理错误线下分箱时某个边界值归到了A箱线上规则写成了B箱的条件导致一批人漏掉第三是规则优先级冲突前面有条更宽的规则先把人拦走了后面这条规则实际能命中的只剩漏网之鱼。排查时先核对特征口径再抽查几条命中样本手工验证规则表达式最后看规则执行顺序。5.4 交叉出来的规则业务方不认可业务方不认可多半是因为规则“反直觉”。比如挖出“高学历人群坏账率更高”这种规则业务方第一反应是“不可能”。这时候要做两件事一是拆解验证看这个信号是不是被某个混杂因素带出来的比如高学历人群集中在某个高风险渠道控制渠道之后信号就消失了那这条规则就不该单独存在二是补充业务解释如果信号确实存在要找到合理的解释链条比如高学历人群可能多头借贷更严重或者对利率更敏感导致逆向选择。解释不通的规则哪怕统计上显著也建议先放观察名单不急着上拒绝策略。问题现象可能原因排查动作处理建议规则数量爆炸分箱过细/字段过多检查各字段箱数和候选字段数合并箱、砍字段控制两两组合在50张表以内高Lift低样本细分客群噪声看样本量和坏样本数绝对值拒绝规则不保留复审规则标注观察上线命中率骤降口径不一致/边界错误/优先级冲突核对特征口径、抽查规则表达式、看执行顺序统一口径、修正边界、调整规则优先级业务不认可规则混杂因素/缺乏解释做分层验证控制混杂变量解释不通的进观察名单不直接上策略5.5 几个踩过坑才明白的细节第一个坑是缺失值处理。交叉表默认会把缺失值当成一个独立类别如果某个字段缺失率很高比如30%缺失这个格子可能样本量很大、坏账率也有差异挖出来的规则就变成“字段X缺失”这种规则。这种规则能不能用要看缺失本身有没有业务含义。如果是“收入字段缺失”代表客户不愿提供收入证明那这个信号是有价值的如果是系统采集失败导致的随机缺失那这条规则就是噪声。我的做法是缺失率超过20%的字段先单独分析缺失人群的风险表现有区分度才保留缺失作为一个箱。第二个坑是时间穿越。前面提过标签泄漏但还有一种隐蔽的时间穿越特征的计算时间晚于放款时间。比如用“放款后30天内的APP登录次数”做特征这个特征在放款时根本拿不到线下分析时却混进去了。排查方法是逐个字段确认“这个字段在决策时点是否可得”拿不准的就问数据开发要字段的血缘和产出时间。第三个坑是规则之间的相互作用。两条规则单独看Lift都是2但组合起来命中的人群坏账率可能不是简单叠加甚至可能互相抵消。上线前我会做一次规则组合模拟把最终规则集在历史样本上跑一遍看整体拦截率、拦截人群坏账率、误伤率被拦截的好样本占比确保规则集整体效果符合预期而不是只看单条规则。6. 规则集的迭代维护与扩展思路规则不是挖完就一劳永逸的。客群在变、渠道在变、产品在变今天有效的规则半年后可能就失效了。我的维护节奏是月度监控、季度重挖。月度监控看两个指标每条规则的命中量和命中人群实际坏账率命中量骤降说明客群结构变了实际坏账率上升说明规则可能失效甚至反向。季度重挖则是把最新样本重新跑一遍交叉表流程对比新旧规则集的差异新增的规则进观察名单失效的规则下线。扩展方向上交叉表这套框架还能往几个地方延伸。一是多维度组合两两交叉之外对贡献最大的几个字段做三三交叉但要有节制三三交叉的规则必须样本量充足才保留。二是规则与模型的结合交叉表挖出的规则可以作为模型的特征输入或者用模型分做分层在分层内再做交叉表这样能控制混杂因素挖出的规则更纯。三是自动化流水线把整个流程封装成脚本输入一份新样本自动输出规则清单和评估报告减少人工重复劳动。我现在的做法是把分箱、交叉、筛选、去重都写成函数每次重挖只需要改样本路径和几个阈值参数半小时内能跑完一轮。最后分享一个实操中很管用的小技巧给每条规则打上“业务标签”。比如“年轻客群风险规则”“某渠道专项规则”“历史行为风险规则”标签不是为了好看是为了后续排查问题时能快速定位。当某个月某类客群的坏账突然抬头你能立刻从规则集里捞出相关标签的规则看是规则漏了还是规则失效了比一条条翻规则表达式快得多。这个习惯我坚持了好几年每次规则复盘都省下大量时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑