PSI群体稳定指数
群体稳定性指标PSI(population stability index )用于衡量测试样本和建模样本分数间数据分布差异性,是模型稳定性的常见指标。公式如下所示:
P S I = ∑ i = 0 b i n s ( A c t u a l i − E x p e c t e d i ) × l n ( A c t u a l i E x p e c t e d i ) PSI = \sum_{i=0}^{bins}\left ( Actual_{i}-Expected_{i} \right )\times ln\left ( \frac{Actual_{i}}{Expected_{i}} \right ) PSI=i=0∑bins(Actuali−Expectedi)×ln(ExpectediActuali)
其中,bins是分箱数量,Actual是实际占比,Expected是预期占比。
一般来说,PSI小于0.1模型稳定性较高,0.1至0.2之间稳定性一般,大于0.2建议重新迭代模型。
计算样例:
对比去年和今年某地区人的年龄分布的差异性,去年抽查的年龄命名为Actual,今年年龄命名为Expect,如下所示。
Actual : 81, 48, 49, 82, 35, 6,……, 49, 69, 73, 57, 93, 77, 21
Expect : 41, 34, 62, 81, 11, 33, ……, 21, 21, 74, 68, 62, 21, 83
将Actual等宽分为10组,并按Actual相同的区间将Expect分组,计算结果如下:
其次,计算每个分组内占总量的占比,如下图中,第0号分组内,actual有15条数据,actual共100条数据,则对应的占比为15%。计算结果如下:
最后,按照公式计算得出每个组内的PSI,公式如下,将所有分组的psi求和为最终PSI值为0.184。
P S I i = ( a c t u a l _ r a t e i − e x p e c t _ r a t e i ) × l n ( a c t u a l _ r a t e i e x p e c t _ r a t e i ) PSI_{i} =\left (actual\_rate_{i}-expect\_rate_{i} \right )\times ln\left ( \frac{actual\_rate_{i}}{expect\_rate_{i}} \right ) PSIi=(actual_ratei−expect_ratei)×ln(expect_rateiactual_ratei)
计算结果如下:
Python 实现
#Python代码实现
import pandas as pd
import numpy as np
def cal_psi(actual, predict, bins=10):
"""
功能: 计算PSI值,并输出实际和预期占比分布曲线
:param actual: Array或series,代表真实数据,如训练集模型得分
:param predict: Array或series,代表预期数据,如测试集模型得分
:param bins: 分段数
:return:
psi: float,PSI值
psi_df:DataFrame
Examples
-----------------------------------------------------------------
>>> import random
>>> act = np.array([random.random() for _ in range(5000000)])
>>> pct = np.array([random.random() for _ in range(500000)])
>>> psi, psi_df = cal_psi(act,pct)
>>> psi
1.65652278590053e-05
>>> psi_df
actual predict actual_rate predict_rate psi
0 498285 49612 0.099657 0.099226 1.869778e-06
1 500639 50213 0.100128 0.100428 8.975056e-07
2 504335 50679 0.100867 0.101360 2.401777e-06
3 493872 49376 0.098775 0.098754 4.296694e-09
4 500719 49710 0.100144 0.099422 5.224199e-06
5 504588 50691 0.100918 0.101384 2.148699e-06
6 499988 50044 0.099998 0.100090 8.497110e-08
7 496196 49548 0.099239 0.099098 2.016157e-07
8 498963 50107 0.099793 0.100216 1.790906e-06
9 502415 50020 0.100483 0.100042 1.941479e-06
"""
actual_min = actual.min() # 实际中的最小概率
actual_max = actual.max() # 实际中的最大概率
binlen = (actual_max - actual_min) / bins
cuts = [actual_min + i * binlen for i in range(1, bins)]#设定分组
cuts.insert(0, -float("inf"))
cuts.append(float("inf"))
actual_cuts = np.histogram(actual, bins=cuts)#将actual等宽分箱
predict_cuts = np.histogram(predict, bins=cuts)#将predict按actual的分组等宽分箱
actual_df = pd.DataFrame(actual_cuts[0],columns=['actual'])
predict_df = pd.DataFrame(predict_cuts[0], columns=['predict'])
psi_df = pd.merge(actual_df,predict_df,right_index=True,left_index=True)
psi_df['actual_rate'] = (psi_df['actual'] + 1) / psi_df['actual'].sum()#计算占比,分子加1,防止计算PSI时分子分母为0
psi_df['predict_rate'] = (psi_df['predict'] + 1) / psi_df['predict'].sum()
psi_df['psi'] = (psi_df['actual_rate'] - psi_df['predict_rate']) * np.log(
psi_df['actual_rate'] / psi_df['predict_rate'])
psi = psi_df['psi'].sum()
return psi, psi_df