金融风控贷款违约预测:数据分析与特征工程实战指南 做金融风控类的机器学习项目如果说建模阶段是炼丹那数据分析就是选矿。矿没选好后面怎么调参都白搭。天池的金融风控-贷款违约预测系列走到二—— 数据分析讲的就是把这份贷款违约数据从一堆CSV变成一套能指导特征工程的信息地图的过程。这篇文章我就把这个环节的完整思路展开来写从字段识别、目标变量拆解到缺失值、分布体检、相关性分析再到容易被忽略的隐藏信息和几个新手必踩的坑。无论你是第一次碰信贷数据还是已经在其他数据集上有过建模经验这篇文章都能帮你把EDA阶段做得更扎实而不是简单跑几张图就交差。1. 拿到贷款违约数据的第一件事先摸清字段的家底不管你在哪个平台参加贷款违约预测类的比赛拿到数据后最忌讳的就是直接train.describe()一把梭。我的习惯永远是先看 shape、再看 columns然后用 dtypes 对字段做一个粗分类搞清楚这份数据到底在描述什么业务。用 pandas 读进来之后我通常这样打印全局信息import pandas as pd train pd.read_csv(train.csv) test pd.read_csv(testA.csv) print(train.shape, test.shape) print(train.columns.tolist()) print(train.dtypes.value_counts())这份数据的量级我记得大概是训练集八十万条、测试集二十万条左右特征在四五十个的量级具体数字以你本地读入为准。特征类型并不复杂但业务含义跨度很大我建议按下面这种方式把字段分成几类来理解。字段类型代表性字段说明连续数值类loanAmnt、interestRate、annualIncome、dti贷款金额、利率、年收入、债务收入比有序类别类grade、subGrade、term信用评级、分期期数无序类别类homeOwnership、purpose、applicationType房产状况、借款用途、申请类型时间日期类issueDate、earliesCreditLine放款日期、最早信用记录日期匿名数值类以n开头的若干列已脱敏或加工过的数值特征这种分类不是纸上谈兵它直接决定了后面每一步分析怎么做。连续数值类字段要做分布体检和异常值处理有序类别类字段可以编码后参与相关性计算无序类别类字段更多依靠分组统计来观察规律时间日期类字段往往藏着趋势信息和可衍生的人工特征匿名数值类字段则要格外小心因为你不知道它的原始含义只能通过分布和相关性去推测。我特别想强调一点理解字段的业务含义比跑任何统计量都重要。比如 annualIncome 代表年收入就业年限代表工作稳定性dti 代表债务收入比这些在银行风控里都是评估还款能力的基础指标。你不一定要有信贷行业从业经验但至少要能从字段名推断出这个变量大概在描述什么风险维度。还有一个非常实用的动作检查是否存在全表只有一个取值的字段。我记得当时数据里就有一个策略枚举类字段全表都是同一个值这种列提供不了任何区分度后面直接删掉就好。这种单值字段在竞赛数据里很常见删掉之前先确认一下训练集和测试集里的取值是否一致如果测试集里有新取值那就要另当别论了。2. 目标变量的分布真相不平衡率决定后续一切决策数据分析阶段最核心的锚点就是目标变量 y也就是 isDefault。在贷款违约预测里0 代表正常还款1 代表违约。拿到数据后的第一个统计动作永远是看这个二分类标签的比例。train[isDefault].value_counts(normalizeTrue)我当时看到的结果是典型的信贷场景分布违约样本占比处于个位数百分比水平正常样本占绝对多数。这种不平衡率会给后面的建模带来一系列连锁反应最直接的一点就是——不能用准确率 Accuracy 作为主要评价指标。假设违约率只有 5%那模型什么都不学全部预测为 0准确率也有 95%但这显然没有任何风控价值。所以这个比赛以及绝大多数金融风控任务都会选择 AUC、KS 这类对不平衡不那么敏感、更关注排序能力的指标。但这里我想多说一句指标选择只是不平衡问题的冰山一角。真正影响后续建模质量的是你要不要做采样、怎么设定预测阈值、以及如何在模型里让少数类样本的犯错代价被体现出来。这些当然不是数据分析阶段就能全部解决的但你在看分布图的时候就要有这个意识。除了看整体比例我强烈建议做两个交叉分析。第一个是按时间看违约率的变化。通过解析 issueDate 的年月按月统计放款量和违约率你能看到这条业务的宏观走势。比如某一段时间放款量激增同时违约率明显上行那很可能意味着那段时间平台放宽了审核标准或者客群结构发生了变化。如果你后面要做时间序列相关的特征这个观察就是地基。第二个是按信用评级 grade 看违约率。信贷业务里有个非常稳定的常识信用等级越差违约风险越高。我当时做了分组统计之后发现违约率从 A 级到 G 级呈现出清晰的单调上升趋势评级越低的群体违约率越高。这种单调关系一方面验证了数据的业务一致性——说明这份数据的字段不是瞎编的确实符合信贷逻辑另一方面也暗示着 grade 和 subGrade 这两个字段很可能在模型里占据重要位置。信用等级区间违约率特征A类最低B类较低C-D类中等E类及以下明显升高这种分组统计的价值在于业务验证。如果一份信贷数据的违约率和信用评级没有关系那我反而要怀疑数据质量或者评级字段的定义有问题。反过来如果关系符合常识你就可以放心地把这个字段作为后续特征工程的重要原料。3. 缺失值分析在征信场景里空白往往比数字更有话讲信贷数据几乎没有哪份是没有缺失值的关键在于你怎么看待缺失。很多新手拿到缺失率统计之后第一反应就是要不要删掉要不要填充但我的看法是缺失值在风控场景里本身就是一种信息。先上代码missing train.isnull().mean().sort_values(ascendingFalse) missing[missing 0].head(20)把缺失率按降序排出来你会看到有些字段的缺失率可能超过 70%有些在 30% 到 70% 之间有些只有不到 5%。面对这张清单最重要的动作不是急着决定填充策略而是给每个高缺失字段贴一个标签这个字段是业务上天然会缺失还是采集过程中人为导致的技术缺失举个例子如果一个字段和房产估值有关那么对于没有房产的申请人这个字段缺失是合理的这种缺失本身就在告诉你此人名下没有房产。再比如工作年限字段如果平台在早期阶段没有采集这一项那后加入的申请人大概率也会有缺失这种缺失就和技术采集团队的历史有关信号价值弱一些。两者对模型的含义是完全不同的。我在这里要重点强调一个在风控业务里非常经典的观点申请人连资料都填不齐这件事本身就值得模型注意。办过贷款或者信用卡的人应该有体会银行让你填的资料越全、提供的证明越多说明审核越严格、资质越被看好反过来借款人如果连必填信息都大面积缺漏违约概率往往是偏高的。所以在后面的特征工程里我会给高缺失字段额外生成一个是否缺失的布尔特征把缺失本身交给模型去学习而不是简单粗暴地填一个默认值掩盖掉。初步的处理策略我一般这样划分缺失率范围建议处理思路高于70%谨慎看待优先做成缺失标志特征再考虑是否删除30%-70%保留缺失标志同时按分布或业务逻辑做填充低于30%按字段类型正常填充均值、中位数、众数或模型填充均可这里有一个容易忽略的细节填充不是越精确越好而是要和后续模型兼容。决策树类模型本身能较好地处理缺失很多实现里会自动把缺失值分到最优分支而线性模型、神经网络则完全不能接受 NaN。所以到底填不填、怎么填取决于你打算把哪类模型作为主力。我的建议是在数据分析阶段先把缺失率排名、缺失类型搞清楚至于具体的填充方案等模型框架定了再动手也不迟。4. 数值特征的分布体检偏态、长尾和异常值的信号看完了缺失值接下来要盯着一堆数值特征看分布。这一步的核心目的有两个一是找出明显的偏态和长尾特征为后面的变换预留思路二是揪出那些不合理的异常值判断它们是脏数据还是真实风险信号。最有效率的做法是先跑一个 describetrain[[loanAmnt, interestRate, annualIncome, dti]].describe()把均值和中位数对比着看。如果均值明显大于中位数说明分布右偏、存在长尾。我当时印象比较深的是 annualIncome 这类收入字段少数高收入样本会把均值拉得很高而大多数人的收入集中在一个相对低的区间。这种长尾分布直接喂给线性模型会带来麻烦要么取对数压缩尺度要么按分位数截断。对于树模型来说虽然没有那么敏感但做好尺度变换始终是一种更稳妥的做法。再来看异常值。这里要举一个我几乎每次做信贷数据都会遇到的例子dti也就是债务收入比。常识告诉我们 dti 超过 100 意味着负债比收入还高这在业务上已经属于极端危险信号如果出现负数或者超过几百的值那就多半是采集或者计算过程出了问题。面对这类异常值我的原则是不要急着删除而是先看看这些异常样本的违约率是否显著高于正常样本。如果违约率确实更高说明即使是脏数据也携带了和风险高度相关的信息后面可以采用截断而非直接删除的方式来处理。还有一个典型的非纯数值特征需要注意比如就业年限 employmentLength。这份数据里它很可能不是数字而是类似10年“1年”这种字符串。处理方式很简单把1年解析成 010年解析成一个合理上限比如 10中间的数字转成整数。这种小动作看起来不起眼但如果不做这个字段就只能当类别特征处理白白损失了它本来应有的排序信息。在做完单字段分布体检之后别忘了做一件事对比训练集和测试集的分布。方法很简单把同一个特征在两个数据集里的 describe 或者直方图拉出来并排看一眼。如果某些特征在两个集里的分布差异很大就要警惕了。一种常见的情况是测试集来自更晚的时间窗口而这段时间客群结构发生了变化导致特征分布整体漂移。这种漂移特征在后续交叉验证里是最容易让模型翻车的因为你很可能在训练集上表现很好上线或者提交后效果骤降。数据分析阶段提早发现这些问题绝对能让你后面少掉不少头发。5. 相关性矩阵里的隐语从系数排名到匿名特征的共线性陷阱分布看完了下一步就是特征之间的关系。大多数人的习惯是直接画一个相关性热力图然后对着花花绿绿的色块感叹好多相关。但我想说的是热力图只能给你一个全局印象真正能指导特征工程的是有目的的相关性分析建议分两步走。第一步看每个特征与目标变量 isDefault 的相关性corr_with_target train.corr()[isDefault].sort_values() print(corr_with_target.head(10)) print(corr_with_target.tail(10))这一正一负两个列表能快速告诉你哪些字段对违约风险的影响最大。我记得当时跑出来利率类、循环额度使用率这类字段排在显著相关的位置这非常符合信贷业务直觉借款利率高的人往往是因为资质不够好、被定价更高风险当然更大循环额度使用率很高的人说明长期处于刷爆信用卡的状态资金链紧张违约概率上升。这一步的产出其实是在帮你验证哪些特征在业务上本来就应该重要而不是盲目相信计算出来的数字。第二步看特征之间的相关性尤其是共线性陷阱。信贷数据里经常出现的现象是利率和信用评级高度相关因为评级本来就是决定利率的依据之一而一堆匿名特征之间也可能存在极强的正相关因为它们很可能都是从同一套底层变量加工出来的衍生体。比如一组以 n 开头的匿名数值列如果它们两两之间的相关系数超过了 0.8那基本可以断定它们在信息上高度重叠。这种高相关特征对树模型来说不至于导致模型崩溃但会把特征重要性分散掉让你分不清到底哪一个才是幕后推手对线性模型和依赖特征降维的方案来说则会引发回归系数不稳、解释性变差的问题处理时可以考虑合并、去重或者做 PCA。相关性分析还有一个非常值得挖掘的隐藏宝藏那就是时间字段。这份数据里有 issueDate放款日期和 earliesCreditLine最早信用记录时间。我第一次做的时候差点把这两个字段直接丢了后来才发现它们能衍生出很实用的特征从 issueDate 提取出年月可以按月观察放款规模和违约率的趋势用 issueDate 和 earliesCreditLine 做差可以算出一个人的信用历史长度。信用历史长的人在风控视角下通常更稳定因为银行能看到他在足够长的时间内怎么还款这个特征在后续模型里往往有不错的解释力。需要提醒的是相关系数捕捉的是线性关系它不代表事情的全貌。比如某个匿名特征和违约率的关系可能是非线性的——在区间 A 内风险很低在区间 B 内风险骤升这种模式在相关系数上可能只体现出一个微弱的负相关。所以相关性榜单只是第一轮筛选器真正要挖非线性关系和交互效应还需要借助分箱统计、特征交叉分析这类手段这也是为什么我说只看一张热力图远远不够。6. 数据分析阶段最常见的五个坑每一个都影响建模结果这部分我想聊点实际的。数据竞赛也好真实风控项目也好我在这个阶段看到过太多人踩进同样的坑里有些坑甚至我自己也踩过。把它们写下来帮大家避一避。第一个坑把测试集混进来一起做统计。有人图省事直接把训练和测试拼接成一个 DataFrame 再去看分布和缺失率。这样做最致命的问题是你可能在不知不觉中把测试集的信息泄漏到了训练集里。举个最简单的例子你在某个字段的缺失率统计里看到了测试集的信息然后在填充时人为加了偏向模型的泛化能力就会受影响。正确的做法是全程分开看最多只做分布对比不要合并。第二个坑只用 Pearson 相关系数走天下。Pearson 只能捕捉线性关系对单调非线性关系虽然也有一定敏感度但对 U 型、倒 U 型这类关系基本无效。遇到和违约率呈非线性关系的特征相关系数可能低得可怜这时候不要急着删除它。建议多算一个 Spearman 秩相关系数做对照或者直接做分箱统计看趋势不然很容易把好特征误杀。第三个坑缺失值一刀切。要么全部填充 0要么全部填充均值要么缺失率超过某个阈值就整列删除。我在前面已经反复强调过缺失在风控场景里本身可能是信号。不分青红皂白地填充等于把这个信号从数据里抹掉了。哪怕你最后决定填充也应该同时保留一个该样本此字段是否缺失的标志特征让模型自己决定怎么用。第四个坑忽略时间泄漏。这份数据有明确的放款日期那就不能随便打乱样本做交叉验证。比如你按随机切分训练集和验证集等于允许模型偷看未来验证集上的指标会虚高但提交到测试集或者上线之后效果就会大幅缩水。正确的做法是严格按时间顺序切分用过去预测未来这样才能真实反映模型在实际业务中的表现。这个坑在竞赛里尤其隐蔽很多人前期分数很高到了后期才发现是时间泄漏在帮忙。第五个坑分析结论不沉淀。EDA 做的事情非常多字段含义、缺失率、分布特征、相关性排名、异常值情况、交叉验证结论这些信息如果只是散落在聊天记录和临时代码里等到特征工程阶段你会发现自己又要重新看一遍数据。我现在的习惯是数据分析阶段结束之后在项目文档里整理一页字段观察清单记录每个字段的类型、缺失率、分布特征、与目标变量的关系、初步处理建议以及字段可信度。这份文档在后面模型迭代里会被反复用到花两小时整理能省后面两天的事。最后一个附加提醒也算半个坑不要把所有异常值都当成脏数据处理。在风控业务里极端值背后往往站着的是真正的坏客户。一份收入极低却申请了巨额贷款的数据在统计上可能是离群点在真实世界里却可能是一个高风险借款人的特征。对于这类异常值先看它与违约率的关系再决定是截断、删除还是单独做成特征。说到这里其实这个项目的核心方法论已经很清晰了把每个特征当成一个业务变量去理解而不是当成一串数字去运算。这份贷款违约数据里的信息量比想象中大得多但能不能挖出来取决于你愿不愿意在建模之前先花足够的时间把数据读懂。尤其是时间字段、匿名特征、缺失值这些角落里藏着的细节往往才是区分高手和新手的分水岭。数据分析和后续特征工程之间没有明确的边界你在分析阶段埋下的每一分思考都会在模型效果里得到回报。