当前位置:   article > 正文

评分卡:WOE、IV、PSI计算及ROC和KS曲线_woe iv psi

woe iv psi

公式定义和原理解释见:

风控模型—WOE与IV指标的深入理解应用 - 知乎

风控模型—群体稳定性指标(PSI)深入理解应用 - 知乎

1、WOE和IV

延伸:分箱后求 WOE 和 IV

1. WOE describes the relationship between a predictive variable and a binary target variable.
2. IV measures the strength of that relationship.

  1. # Autor chenfeng
  2. #!/usr/bin/env Python
  3. # coding=utf-8
  4. '''
  5. 学习demo:
  6. 皮尔逊相关系数,woe计算
  7. '''
  8. import pandas as pd
  9. import numpy as np
  10. from scipy import stats
  11. # 从正太分布种抽取20个随机数
  12. x = np.array(np.random.randn((20)))
  13. print(type(x))
  14. X = pd.DataFrame(x, columns=['x'])
  15. # 生成范围在[0, 2)的20个整数
  16. y = np.random.randint(0,2,size=[20])
  17. Y = pd.DataFrame(y, columns=['y'])
  18. print(type(Y)) # dataframe类型
  19. r = 0
  20. # n分箱个数
  21. n=5
  22. ## 有sumcount函数,Y的类型必须是DataFrame
  23. good = Y.sum()
  24. bad = Y.count() - good
  25. print(type(good)) # Series类型
  26. while np.abs(r) < 1:
  27. # pandas中使用qcut(),边界易出现重复值,如果为了删除重复值设置 duplicates=‘drop',则易出现于分片个数少于指定个数的问题
  28. # 这里组合DataFrame需要的X、Y的类型应该是array,不能是DataFrame。可以有2种写法:
  29. # 方式一:
  30. # d1 = pd.DataFrame({"X": X['x'], "Y": Y['y'], "Bucket": pd.qcut(X['x'], n, duplicates="drop")})
  31. # 方式二:
  32. d1 = pd.DataFrame({"X": x, "Y": y, "Bucket": pd.qcut(X['x'], n, duplicates="drop")})
  33. # as_index=True会记录每个分箱包含的数据index
  34. d2 = d1.groupby('Bucket', as_index=True)
  35. '''
  36. 计算每个分箱的X列平均值d2.mean().X 与每个分箱的Y列平均值d2.mean().Y 的spearman秩相关性(pearson相关系数只可用来计算连续数据、正太分布的线性相关性)。
  37. 1、返回值r-相关系数,p-不相关系数,p-values并不完全可靠,但对于大于500左右的数据集可能是合理的。
  38. 2、和其他相关系数一样,r和p在-1+1之间变化,其中0表示无相关。
  39. -1+1的相关关系意味着确切的单调关系。正相关表明,随着x的增加,y也随之增加。负相关性表示随着x增加,y减小。
  40. '''
  41. # d2.mean()取每个分箱的平均值
  42. r, p = stats.spearmanr(d2.mean().X, d2.mean().Y)
  43. n = n - 1
  44. print('n={}, r={},p={}'.format(n, r, p))
  45. print("=" * 60)
  46. '''
  47. d3 = pd.DataFrame(d2.X.min(), columns=['min'])得到的是一个空的dataframe
  48. '''
  49. d3 = pd.DataFrame(d2.X.min(), columns=['min'])
  50. '''
  51. d3:记录每个分箱的min、max、sum、total、rate=sum/total、woe
  52. '''
  53. d3['min'] = d2.min().X
  54. d3['max'] = d2.max().X
  55. d3['sum'] = d2.sum().Y
  56. d3['total'] = d2.count().Y
  57. d3['rate'] = d2.mean().Y
  58. '''
  59. 1、Dataframe类型和Series类型做加减乘除计算时,Series的行索引对应Dataframe的列索引,如果两者没有匹配的索引,则返回Nan。
  60. 这里good和bad的行索引都是y,但是d3是没有y列的,而且这里其实只是想除以一个实数,所以直接取Series的values就行了。
  61. 2、d3['sum']/(d3['total']-d3['sum']) = d3['rate']/(1 - d3['rate'])
  62. '''
  63. d3['woe'] = np.log((d3['rate']/(1 - d3['rate']))/(good / bad).values)
  64. d3['goodrate'] = d3['sum']/good.values
  65. d3['badrate'] = (d3['total']-d3['sum'])/bad.values
  66. d3['iv'] = ((d3['goodrate']-d3['badrate'])*d3['woe']).sum()
  67. '''
  68. 1sort_values(axis=0, by=‘min’)表示沿第一维的方向,对min列排序,默认是递增。axis=1表示沿第二维排序。
  69. sort_index()只能操作索引index,不能操作其他行或列
  70. 2reset_index(drop=True) 重置索引,drop=true表示删除旧索引,重置后的索引默认从0开始的递增数值。
  71. '''
  72. d4 = (d3.sort_values(axis=0, by='min')).reset_index(drop=True)
  73. print(d4)

 2、稳定性PSI

  1. # Autor chenfeng
  2. #!/usr/bin/env Python
  3. # coding=utf-8
  4. import math
  5. import numpy as np
  6. import pandas as pd
  7. def calculate_psi(base_list, test_list, bins=20, min_sample=10):
  8. try:
  9. base_df = pd.DataFrame(base_list, columns=['score'])
  10. test_df = pd.DataFrame(test_list, columns=['score'])
  11. #---------------------------- 1.去除缺失值后,统计两个分布的样本量-----------------------------------------------
  12. base_notnull_cnt = len(list(base_df['score'].dropna()))
  13. test_notnull_cnt = len(list(test_df['score'].dropna()))
  14. # 空分箱
  15. base_null_cnt = len(base_df) - base_notnull_cnt
  16. test_null_cnt = len(test_df) - test_notnull_cnt
  17. #---------------------------- 2.最小分箱数:这里貌似是等距分箱??每个分箱的距离是1/bin_num------------------------
  18. q_list = []
  19. if type(bins) == int:
  20. bin_num = min(bins, int(base_notnull_cnt / min_sample))
  21. q_list = [x / bin_num for x in range(1, bin_num)]
  22. break_list = []
  23. for q in q_list:
  24. '''
  25. df['score'].quantile(q=fraction, interpolation=linear)返回df['score']列的数据在q处的分位数值。
  26. 其中,插值方式interpolation有以下几种:
  27. j-df['score']列中的最大值
  28. i-df['score']列中的最小值
  29. fraction-插值分位,当fraction=0.5, interpolation=linear时就是求中位数。
  30. * linear: `i + (j - i) * fraction`, where `fraction` is the
  31. fractional part of the index surrounded by `i` and `j`.
  32. * lower: `i`.
  33. * higher: `j`.
  34. * nearest: `i` or `j` whichever is nearest.
  35. * midpoint: (`i` + `j`) / 2.
  36. '''
  37. bk = base_df['score'].quantile(q)
  38. break_list.append(bk)
  39. break_list = sorted(list(set(break_list))) # 去重复后排序
  40. score_bin_list = [-np.inf] + break_list + [np.inf]
  41. else:
  42. score_bin_list = bins
  43. #----------------------------3.统计各分箱内的样本量----------------------------------------------------------------
  44. base_cnt_list = [base_null_cnt]
  45. test_cnt_list = [test_null_cnt]
  46. bucket_list = ["MISSING"]
  47. for i in range(len(score_bin_list) - 1):
  48. '''
  49. round()方法返回:按要求的小数位个数求四舍五入的结果。
  50. 实际使用中发现round函数并不总是如上所说的四舍五入,这是因为该函数对于返回的浮点数并不是按照四舍五入的规则来计算,而会收到计算机表示精度的影响。
  51. 当参数n不存在时,round()函数的输出为整数。
  52. 当参数n存在时,即使为0,round()函数的输出也会是一个浮点数。
  53. 此外,n的值可以是负数,表示在整数位部分四舍五入,但结果仍是浮点数。
  54. '''
  55. left = round(score_bin_list[i + 0], 4)
  56. right = round(score_bin_list[i + 1], 4)
  57. # 构建分箱格式,返回左开又闭的形式,如(0.10.2]
  58. bucket_list.append("(" + str(left) + ',' + str(right) + ']')
  59. base_cnt = base_df[(base_df.score > left) & (base_df.score <= right)].shape[0]
  60. base_cnt_list.append(base_cnt)
  61. test_cnt = test_df[(test_df.score > left) & (test_df.score <= right)].shape[0]
  62. test_cnt_list.append(test_cnt)
  63. #------------------------4.汇总统计结果:求每个分箱的占比率------------------------------------------------------------------------
  64. stat_df = pd.DataFrame({"bucket": bucket_list, "base_cnt": base_cnt_list, "test_cnt": test_cnt_list})
  65. stat_df['base_dist'] = stat_df['base_cnt'] / len(base_df)
  66. stat_df['test_dist'] = stat_df['test_cnt'] / len(test_df)
  67. def sub_psi(row):
  68. #------------------------ 5.计算每个分箱的不稳定性----------------------------------------------------------------------------
  69. base_list = row['base_dist']
  70. test_dist = row['test_dist']
  71. # 处理某分箱内样本量为0的情况
  72. if base_list == 0 and test_dist == 0:
  73. return 0
  74. elif base_list == 0 and test_dist > 0:
  75. base_list = 1 / base_notnull_cnt
  76. elif base_list > 0 and test_dist == 0:
  77. test_dist = 1 / test_notnull_cnt
  78. return (test_dist - base_list) * np.log(test_dist / base_list)
  79. '''
  80. 对stat_df的每行应用sub_psi(...)函数
  81. '''
  82. stat_df['psi'] = stat_df.apply(lambda row: sub_psi(row), axis=1)
  83. '''
  84. 从stat_df中取['bucket', 'base_cnt', 'base_dist', 'test_cnt', 'test_dist', 'psi']这些列的数据
  85. '''
  86. stat_df = stat_df[['bucket', 'base_cnt', 'base_dist', 'test_cnt', 'test_dist', 'psi']]
  87. # ------------------------6、PSI = 所有分箱的不稳定性之和------------------------------------------------------------------------
  88. psi = stat_df['psi'].sum()
  89. except:
  90. print('error!!!')
  91. psi = np.nan
  92. stat_df = None
  93. return psi, stat_df
  94. if __name__=='__main__':
  95. # 从正太分布种抽取20个随机数
  96. base_x = np.array(np.random.randn(100))
  97. base_list = pd.DataFrame({"score": base_x})
  98. test_x = np.array(np.random.randn(20))
  99. test_list = pd.DataFrame(test_x, columns=['score'])
  100. psi, stat_df = calculate_psi(base_list=base_list,
  101. test_list=test_list,
  102. bins=20, min_sample=10)
  103. print("psi:", psi)
  104. print(stat_df)

3、ROC和KS曲线

roc曲线涉:
x轴--假正率(negative的召回率)fpr
y轴--真正率(positive的召回率)tpr

常用:sklearn.metrics.roc_curve使用简要说明_sun91019718的博客-CSDN博客_sklearn.metrics.roc_curve

tpr,fpr,threshold = sklearn.metrics.roc_curve(y_label,y_pred,pos_label=1)
备注:pos_label=positive label,当y_label是{0,1}或{-1,1}内的值,可以不写;否则必须明确。
  1. # Autor chenfeng
  2. #!/usr/bin/env Python
  3. # coding=utf-8
  4. import numpy as np
  5. from sklearn import metrics
  6. import matplotlib.pyplot as plt
  7. def plot_roc(y_label,y_pred):
  8. """
  9. 绘制roc曲线
  10. param:
  11. y_label -- 真实的y值 list/array
  12. y_pred -- 预测的y值 list/array
  13. return:
  14. roc曲线
  15. """
  16. '''
  17. 1、tpr(真正率)和fpr(假正率)
  18. 2、当y的值不在{0, 1} or {-1, 1}范围内时,需要设置positive label参数:pos_label
  19. '''
  20. tpr,fpr,threshold = metrics.roc_curve(y_label, y_pred, pos_label=2)
  21. print("真正率tpr:", tpr)
  22. print("假正率fpr:", fpr)
  23. print("threshold:", threshold)
  24. '''
  25. AUC是roc曲线下方的面积
  26. '''
  27. AUC = metrics.roc_auc_score(y_label,y_pred)
  28. fig = plt.figure(figsize=(6,4))
  29. ax = fig.add_subplot(1,1,1)
  30. ax.plot(fpr, tpr, color='blue', label='AUC=%.3f'%AUC)
  31. ax.plot([0,1],[0,1],'r--')
  32. ax.set_ylim(0,1)
  33. ax.set_xlim(0,1)
  34. ax.set_title('ROC')
  35. ax.legend(loc='best')
  36. plt.show()
  37. def plot_model_ks(y_label, y_pred):
  38. """
  39. 绘制ks曲线
  40. param:
  41. y_label -- 真实的y值 list/array
  42. y_pred -- 预测的y值 list/array
  43. return:
  44. ks曲线
  45. """
  46. pred_list = list(y_pred)
  47. label_list = list(y_label)
  48. total_bad = sum(label_list)
  49. total_good = len(label_list) - total_bad
  50. items = sorted(zip(pred_list, label_list), key=lambda x: x[0])
  51. step = (max(pred_list) - min(pred_list)) / 200
  52. pred_bin = []
  53. good_rates = []
  54. bad_rates = []
  55. ks_list = []
  56. for i in range(1, 201):
  57. idx = min(pred_list) + i * step
  58. pred_bin.append(idx)
  59. label_bin = [x[1] for x in items if x[0] < idx]
  60. bad_num = sum(label_bin)
  61. good_num = len(label_bin) - bad_num
  62. goodrate = good_num / total_good
  63. badrate = bad_num / total_bad
  64. ks = abs(goodrate - badrate)
  65. good_rates.append(goodrate)
  66. bad_rates.append(badrate)
  67. ks_list.append(ks)
  68. fig = plt.figure(figsize=(6, 4))
  69. ax = fig.add_subplot(1, 1, 1)
  70. ax.plot(pred_bin, good_rates, color='green', label='good_rate')
  71. ax.plot(pred_bin, bad_rates, color='red', label='bad_rate')
  72. ax.plot(pred_bin, ks_list, color='blue', label='good-bad')
  73. ax.set_title('KS:{:.3f}'.format(max(ks_list)))
  74. ax.legend(loc='best')
  75. plt.show()
  76. y_label = np.array([1, 1, 2, 2])
  77. y_pred = np.array([0.1, 0.4, 0.35, 0.8])
  78. plot_roc(y_label, y_pred)
  79. plot_model_ks(y_label, y_pred)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小惠珠哦/article/detail/955383
推荐阅读
相关标签
  

闽ICP备14008679号