Loading [MathJax]/jax/output/CommonHTML/jax.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >特征稳定性指标PSI的原理与代码分享

特征稳定性指标PSI的原理与代码分享

作者头像
Sam Gor
发布于 2022-02-25 10:02:35
发布于 2022-02-25 10:02:35
4.1K02
代码可运行
举报
文章被收录于专栏:SAMshareSAMshare
运行总次数:2
代码可运行

PSI这个指标我们在风控建模前后都是需要密切关注的,这个指标直接反映了模型的稳定性,对于我们评估模型是否需要迭代有着直接的参考意义。今天我将从下面几方面来介绍一下这个指标。

Index

01 PSI的概念 02 PSI的生成逻辑 03 PSI的业务应用 04 PSI的Python实现

01 PSI的概念

PSI全称叫做“Population Stability Index”,中文翻译是群体稳定性指标,从风控应用的角度理解就是分组的测试与跨时间稳定性指标。 在我们建模的时候,数据(变量或者模型分)的分组占比分布是我们的期望值,也就是我们希望在测试数据集里以及未来的数据集里,也能够展示出相似的分组分布,我们称之为稳定。 PSI值没有指定的值域,我们需要知道的是值越小越稳定,一般在风控中会拿0.25来作为筛选阈值,即PSI>0.25我们就认定这个变量或者模型不稳定了。好了,那具体PSI怎么计算呢?不急,请接着看下一节。

02 PSI的生成逻辑

按照惯例,我们先把PSI的计算公式放上来:

其中,代表第i组的实际占比(占全部数量),代表第i组的期望占比(也就是训练时或者上线时的分组占比)。我们还是拿之前的《风控ML[5] | WOE前的分箱一定要单调吗》 文章里的数据来举例,具体可以看下面的表:

公式比较简单,在Excel里就可以实现了,结果计算出来PSI为0.018,所以是稳定的。

03 PSI的业务应用

那么有了这个稳定性指标,在具体的风控场景中可以怎么应用呢?我一般会在下面几个场景应用: 1、建模前筛选变量 2、模型上线后监控模型

建模前筛选变量

我们在做评分卡的时候一般都是会选择稳定性比较强的变量,因为模型一般上线后,下一次迭代都要1年后了,所以我们倾向于稳定性强的变量。那一般怎么筛选呢?我们从下面几个步骤来操作: 1)选择训练数据,并且确定变量的最优分箱(具体可以参考上篇关于最优分箱的文章

[1] 风控建模中的自动分箱的方法有哪些 [2] 3种连续变量分箱方法的代码分享

2)初始化变量的期望占比分布 3)计算变量每个月的变量PSI 4)观察是否有PSI超过0.25的变量,剔除。

模型上线后监控模型

而模型上线后,我们一般会对模型分进行分组,比如A - F,所以我们会去监控模型分的分组稳定性,同样地,模型的入参也一样会监控。

04 PSI的Python实现

我们在前一篇文章(3种连续变量分箱方法的代码分享 )里介绍的自动分箱算法的基础上,基于numpy进行PSI的计算,测试集可以在公众号SamShare的后台输入关键词cut获取。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import pandas as pd
import numpy as np
import random
import math
from scipy.stats import chi2
import scipy

# 测试数据构造,其中target为Y1代表坏人,0代表好人。  
df = pd.read_csv('./data/autocut_testdata.csv')
print(len(df))
print(df.target.value_counts()/len(df))
print(df.head())

def get_maxks_split_point(data, var, target, min_sample=0.05):
    """ 计算KSArgs:
        data: DataFrame,待计算卡方分箱最优切分点列表的数据集
        var: 待计算的连续型变量名称
        target: 待计算的目标列Y的名称
        min_sample: int,分箱的最小数据样本,也就是数据量至少达到多少才需要去分箱,一般作用在开头或者结尾处的分箱点
    Returns:
        ks_v: KS值,float
        BestSplit_Point: 返回本次迭代的最优划分点,float
        BestSplit_Position: 返回最优划分点的位置,最左边为0,最右边为1,float
    """
    if len(data) < min_sample:
        ks_v, BestSplit_Point, BestSplit_Position = 0, -9999, 0.0
    else:
        freq_df = pd.crosstab(index=data[var], columns=data[target])
        freq_array = freq_df.values
        if freq_array.shape[1] == 1: # 如果某一组只有一个枚举值,如01,则数组形状会有问题,跳出本次计算
            # tt = np.zeros(freq_array.shape).T
            # freq_array = np.insert(freq_array, 0, values=tt, axis=1)
            ks_v, BestSplit_Point, BestSplit_Position = 0, -99999, 0.0
        else:
            bincut = freq_df.index.values
            tmp = freq_array.cumsum(axis=0)/(np.ones(freq_array.shape) * freq_array.sum(axis=0).T)
            tmp_abs = abs(tmp.T[0] - tmp.T[1])
            ks_v = tmp_abs.max()
            BestSplit_Point = bincut[tmp_abs.tolist().index(ks_v)]
            BestSplit_Position = tmp_abs.tolist().index(ks_v)/max(len(bincut) - 1, 1)
        
    return ks_v, BestSplit_Point, BestSplit_Position


def get_bestks_bincut(data, var, target, leaf_stop_percent=0.05):
    """ 计算最优分箱切分点
    Args:
        data: DataFrame,拟操作的数据集
        var: String,拟分箱的连续型变量名称
        target: String,Y列名称
        leaf_stop_percent: 叶子节点占比,作为停止条件,默认5%
    
    Returns:
        best_bincut: 最优的切分点列表,List
    """
    min_sample = len(data) * leaf_stop_percent
    best_bincut = []
    
    def cutting_data(data, var, target, min_sample, best_bincut):
        ks, split_point, position = get_maxks_split_point(data, var, target, min_sample)
        
        if split_point != -99999:
            best_bincut.append(split_point)
        
        # 根据最优切分点切分数据集,并对切分后的数据集递归计算切分点,直到满足停止条件
        # print("本次分箱的值域范围为{0} ~ {1}".format(data[var].min(), data[var].max()))
        left = data[data[var] < split_point]
        right = data[data[var] > split_point]
        
        # 当切分后的数据集仍大于最小数据样本要求,则继续切分
        if len(left) >= min_sample and position not in [0.0, 1.0]:
            cutting_data(left, var, target, min_sample, best_bincut)
        else:
            pass
        if len(right) >= min_sample and position not in [0.0, 1.0]:
            cutting_data(right, var, target, min_sample, best_bincut)
        else:
            pass
        return best_bincut
    best_bincut = cutting_data(data, var, target, min_sample, best_bincut)
    
    # 把切分点补上头尾
    best_bincut.append(data[var].min())
    best_bincut.append(data[var].max())
    best_bincut_set = set(best_bincut)
    best_bincut = list(best_bincut_set)
    
    best_bincut.remove(data[var].min())
    best_bincut.append(data[var].min()-1)
    # 排序切分点
    best_bincut.sort()
    
    return best_bincut
    

age_bins=get_bestks_bincut(df, 'age', 'target')
df['age_bins'] = pd.cut(df['age'], bins=age_bins)
print("age的最优分箱切分点:", age_bins)
print("age的最优分箱结果:\n", df['age_bins'].value_counts())
df.head()

接下来导入测试集,计算PSI。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 导入测试数据集
df_test = pd.read_csv('./data/psi_testdata.csv')

def cal_psi(df_train, df_test, var, target):
    train_bins = get_bestks_bincut(df_train, var, target)
    train_cut_nums = pd.cut(df_train[var], bins=train_bins).value_counts().sort_index().values
    
    # 根据训练集分箱切分点对测试集进行切分
    def cut_test_data(data, var, bincut):
        # 扩大两端边界线
        if bincut[0] > data[var].min()-1:
            bincut.remove(bincut[0])
            bincut.append(data[var].min()-1)
        if bincut[-1] < data[var].max():
            bincut.remove(bincut[-1])
            bincut.append(data[var].max())
    
        # 排序切分点
        bincut.sort()
        return bincut
    
    test_cut_nums = pd.cut(df_test[var], 
                            bins=cut_test_data(df_test, var, train_bins)).value_counts().sort_index().values
    
    tt = pd.DataFrame(np.vstack((train_cut_nums, test_cut_nums)).T, columns=['train', 'test'])
    
    # 计算PSI
    E = tt['train'].values/tt['train'].values.sum(axis=0)
    A = tt['test'].values/tt['test'].values.sum(axis=0)
    A_sub_E = A-E
    A_divide_E = A/E
    ln_A_divide_E = np.log(A_divide_E) # numpy里的log其实指的是ln
    PSI_i = A_sub_E * ln_A_divide_E
    psi = PSI_i.sum()
    return tt, psi
    
tt, psi = cal_psi(df, df_test, 'age', 'target')
print("PSI: ", psi)
tt

Reference

[1] 风控模型—群体稳定性指标(PSI)深入理解应用 https://zhuanlan.zhihu.com/p/79682292

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 SAMshare 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
3种连续变量分箱方法的代码分享
大家好呀!在上一篇我们介绍了3种业界常用的自动最优分箱方法。 1)基于CART算法的连续变量最优分箱 2)基于卡方检验的连续变量最优分箱 3)基于最优KS的连续变量最优分箱 今天这篇文章就来分享一下这3种方法的Python实现。
Sam Gor
2022/02/25
1.6K0
3种连续变量分箱方法的代码分享
风控模型:PSI 稳定性指标详解(Python)
在风控中,风险意味着不确定性,不确定性越强意味着越不可控,做数据化风控也是同理,追求的就是让确定性越来越强,转换成统计概率论来说就是不断提高我们的胜算的概率。当然,没有任何人可以做到100%的确定,因为没有人是上帝视角,所以在风控决策过程中总会产生错杀或者误放。
Python数据科学
2023/11/30
4.7K1
风控模型:PSI 稳定性指标详解(Python)
模型稳定性指标—PSI
之前阐述了模型本身的评价指标:混淆矩阵、F1值、KS曲线、count_table和ROC曲线AUC面积,本文介绍模型稳定性指标PSI。
阿黎逸阳
2023/08/21
2.9K0
模型稳定性指标—PSI
特征漂移指标 PSI
稳定性指的是参与对比两者相同指标差异性很小。机器学习使用训练数据(训练集和验证集)建模,使用测试数据模拟生产环境数据测试模型结果,其建模的假设是:训练数据涵盖了该问题所有的案例数据,即训练数据和测试(生产)数据之间的差异是很小的。
骑着蜗牛向前跑
2023/11/06
3730
评分卡模型(一)评分卡建模实战
由于评分卡是基于LR模型训练的,虽然在特征处理过程较为严格,但本身模型准确性较低。因此可以考虑通过其他准确率高的模型进行训练,例如XGBoost。只需将odd的计算换为(1-p)/p即可,这里的p为模型输出的概率值。
HsuHeinrich
2023/05/25
3.4K0
评分卡模型(一)评分卡建模实战
[1111]python scorecardpy(评分卡)使用
随着互联网在传统金融和电子商务领域的不断渗透,风控+互联网的融合也对传统的风控提出了新的要求和挑战。以评分卡为例,互联网形态下的评分卡需要面临更多维数据、更实时数据、更异常数据的挑战。因此,懂得互联网业务下的风控评分卡已经成为互联网风控从业人员的新要求。
周小董
2022/04/13
3.5K2
[1111]python scorecardpy(评分卡)使用
新人赛《金融风控贷款违约》避坑指南!
本文以天池的金融风控赛为背景,梳理了金融风控的整个实践流程,帮助大家避坑学习。赛事的场景是个人信贷,要求选手根据贷款申请人的数据信息预测其是否有违约的可能,以此判断是否通过此项贷款,这个问题在现实的风控场景中很常见,属于典型的分类问题。另外,准入模型,评分卡模型皆是属于这个范畴。
Datawhale
2020/10/23
3.1K0
新人赛《金融风控贷款违约》避坑指南!
评分卡模型(二)基于评分卡模型的用户付费预测
只需要定义好什么是好人,什么是坏人,就可以按照标准流程构建评分卡了,是不是很方便~
HsuHeinrich
2023/05/25
1.3K0
评分卡模型(二)基于评分卡模型的用户付费预测
Machine Learning-特征工程之卡方分箱(Python)
初次接触变量分箱是在做评分卡模型的时候,SAS软件里有一段宏可以直接进行连续变量的最优分箱,但如果搬到Python的话,又如何实现同样或者说类似的操作呢,今天就在这里简单介绍一个办法——卡方分箱算法。
Sam Gor
2019/08/22
6.1K0
利用Python进行常见的特征工程
上期说到数据分析师一般对业务数据提取的时候就会进行数据清洗,也会做一些业务逻辑或者数据逻辑上的特征处理。但由于特征工程是数据建模重要的一环,所以这里就做一个简单的总结。希望能给大家带来一些小小地帮助~
HsuHeinrich
2023/03/29
1K0
利用Python进行常见的特征工程
数据分析(EDA)学习总结!
探索性数据分析(Exploratory Data Analysis,EDA)是一种探索数据的结构和规律的一种数据分析方法。其主要的工作包含:对数据进行清洗,对数据进行描述(描述统计量,图表),查看数据的分布,比较数据之间的关系,培养对数据的直觉和对数据进行总结。
Datawhale
2020/08/04
7330
数据分析(EDA)学习总结!
基于客户数据的银行信用卡风险控制模型研究-金融风控模型标准评分卡
在银行借贷场景中,评分卡是一种以分数形式来衡量一个客户的信用风险大小的手段,它衡量向别人借钱的人(受信人,需要融资的公司)不能如期履行合同中的还本付息责任,并让借钱给别人的人(授信人,银行等金融机构), 造成经济损失的可能性。一般来说,评分卡打出的分数越高,客户的信用越好,风险越小。
YGingko
2021/08/13
1.3K1
基于客户数据的银行信用卡风险控制模型研究-金融风控模型标准评分卡
AB试验(六)A/B实验常见知识点的Python计算
前面理论知识上提到了很多的知识点需要计算,作为一个实用主义的博主,怎么可以忍受空谈呢?所以本期就给大家分享如何利用Python对这些知识点进行计算。
HsuHeinrich
2023/10/25
9440
AB试验(六)A/B实验常见知识点的Python计算
2021科大讯飞-车辆贷款违约预测赛事 Top1方案!
Hello,大家好。我是“摸鱼打比赛”队的wangli,首先介绍下自己吧,一枚半路出家的野生算法工程师。之所以起名字叫摸鱼打比赛,是因为当时5/6月份自己还处于业务交接没那么忙的一个状态中,然后想起自己也已经毕业两年,但对赛圈一直还是比较关注的,平日看到一些题目也会手痒,但奈何打工人下班之后惰性使然只想躺平,毕业之后始终没有好好打一场比赛,偶尔也会在深夜里问起自己:“廉颇老矣,尚能饭否”,就想着,这回我就利用下这段尚且不忙的日子好好打一场比赛吧。于是我就参加了这次的比赛,不仅侥幸获得了车贷这个小比赛的第一,然后还结识了一些好友,比如我尚在读研的队友陈兄,以及忙于秋招中的好友崔兄。真是收获满满~
算法进阶
2022/06/02
7710
一文深度解读模型评估方法
我们训练学习好的模型,通过客观地评估模型性能,才能更好实际运用决策。模型评估主要有:预测误差情况、拟合程度、模型稳定性等方面。还有一些场景对于模型预测速度(吞吐量)、计算资源耗用量、可解释性等也会有要求,这里不做展开。
算法进阶
2022/06/02
1.9K0
一文深度解读模型评估方法
案例实战 | 决策树预测客户违约
而且将连续变量转化为类别变量后,可以与其他类别变量一起,都直接使用卡方检验或方差分析,写成函数快捷操作也更方便。
萝 卜
2022/05/12
8360
案例实战 | 决策树预测客户违约
图解机器学习特征工程
上图为大家熟悉的机器学习建模流程图(扩展阅读:一文全览机器学习建模流程(Python代码)),整个建模流程非常重要的一步,是对于数据的预处理和特征工程,它很大程度决定了最后建模效果的好坏。
算法进阶
2023/09/01
1.4K0
图解机器学习特征工程
【干货】手把手教你搭建评分卡模型
【前方高能】本篇文章是从零开始构造评分卡模型,各个环节都比较详细,故内容比较长,可能会占用你较长的时间,谢谢谅解。
1480
2019/06/19
10.5K0
【干货】手把手教你搭建评分卡模型
机器学习实战 | 机器学习特征工程最全解读
教程地址:http://www.showmeai.tech/tutorials/41
ShowMeAI
2022/03/22
2.1K0
机器学习实战 | 机器学习特征工程最全解读
数据挖掘实践(金融风控):金融风控之贷款违约预测挑战赛(上篇)xgboots/lightgbm/Catboost等模型--模型融合:stacking、blend
赛题以金融风控中的个人信贷为背景,要求选手根据贷款申请人的数据信息预测其是否有违约的可能,以此判断是否通过此项贷款,这是一个典型的分类问题。通过这道赛题来引导大家了解金融风控中的一些业务背景,解决实际问题,帮助竞赛新人进行自我练习、自我提高。
汀丶人工智能
2023/05/17
5.4K0
数据挖掘实践(金融风控):金融风控之贷款违约预测挑战赛(上篇)xgboots/lightgbm/Catboost等模型--模型融合:stacking、blend
推荐阅读
相关推荐
3种连续变量分箱方法的代码分享
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验