KS,AUC 和 PSI 是风控算法中最常计算的几个指标,本文记录了多种工具计算这些指标的方法。

生成本文的测试数据:

import pandas as pd
import numpy as np
import pyspark.sql.functions as F
from pyspark.sql.window import Window
from pyspark.sql.types import StringType, DoubleType
from pyspark.sql import SparkSession, functions
from sklearn.metrics import roc_auc_score,roc_curve tmptable = pd.DataFrame({'y':[np.random.randint(2) for i in range(1000000)]})
tmptable['y'] = tmptable['score'].apply(lambda x:1 if np.random.rand()+x>0.8 else 0)
tmp_sparkdf = spark.createDataFrame(tmptable)
tmp_sparkdf.craeteOrReplaceTempView('tmpview')

一、KS

​KS 指标来源于 Kolmogorov-Smirnov 检验,通常用于比较两组样本是否来源于同一分布。在建模中划分训练集与测试集后,通常运用 KS 检验来检验训练集与测试集的分布差异,如果分布差异过大,那可能就会因为训练集、测试集划分不合理而降低模型的泛化性。(关于 KS 检验的更多细节

在风控中,KS 指标通过来衡量模型对于好坏样本的区分能力,其具体的算法为:

  1. 按模型分从小到大排序,并分为 n 组(等频分组或每个不同的分值作为一组)
  2. 计算截至每一组的累积好样本(y=0)占比与累积坏样本(y=1)占比,记为 \(cumgoodratio_i\) 和 \(cumbadratio_i\)

    如第 k 组:

    累积好样本占比=第 k 组前包括第 k 组 y=0 样本数量 / 全部 y=0 样本的数量

    累积坏样本占比=第 k 组前包括第 k 组 y=1 样本数量 / 全部 y=1 样本的数量
  3. 则 \(KS=max(abs(cumgoodratio_i-cumbadratio_i))\)

1. SQL 计算 KS

select max(abs(cumgood/totalgood-cumbad/totalbad)) as ks
from (
select score,
sum(totalbad)over(order by score) as cumbad,
sum(totalgood)over(order by score) as cumgood,
sum(totalbad) over() as totalbad,
sum(totalgood) over() as totalgood
from (
select
score,
sum(y) as totalbad,
sum(1-y) as totalgood
from tmpview
group by score
)
)

2. Python 计算 KS

def get_ks(y_true:pd.Series,y_pred:pd.Series):
'''
A staticmethod to caculate the KS of the model.
Args:
y_true: true value of the sample
y_pred: pred value of the sample Returns:
max(tpr-fpr): KS of the model
'''
fpr,tpr,_ = roc_curve(y_true,y_pred)
return str(max(abs(tpr-fpr)))
ksdata = spark.sql('select * from tmpview').toPandas()
print(get_ks(ksdata['y'],ksdata['score']))

3. Pyspark 计算 KS

有两种方法,1 是对用 pyspark 的语法把 SQL 的逻辑给写出来,可以算出来 KS;2 就是包装成 UDF 函数,这样当需要 groupby 后计算 KS 时,可以直接调用 UDF 函数分组计算 KS

a. SQL 逻辑改写

ksdata = spark.sql('select * from tmpview')

def calks(df,ycol='y',scorecol='score'):
return df.withColumn(ycol,F.col(ycol).cast('int')).withColumn(scorecol,F.col(scorecol).cast('float'))\
.withColumn('totalbad',F.sum(F.col(ycol)).over(Window.orderBy(F.lit(1))))\
.withColumn('totalgood',F.sum(1-F.col(ycol)).over(Window.orderBy(F.lit(1))))\
.withColumn('cumgood',F.sum(1-F.col(ycol)).over(Window.orderBy(F.col(scorecol).asc())))\
.withColumn('cumbad',F.sum(F.col(ycol)).over(Window.orderBy(F.col(scorecol).asc())))\
.select(F.max(F.abs(F.col('cumgood')/F.col('totalgood')-F.col('cumbad')/F.col('totalbad'))).alias('KS'))
calks(ksdata).show()

b. python 转 UDF 函数

def get_ks(y_true:pd.Series,y_pred:pd.Series):
'''
A staticmethod to caculate the KS of the model.
Args:
y_true: true value of the sample
y_pred: pred value of the sample Returns:
max(tpr-fpr): KS of the model
'''
fpr,tpr,_ = roc_curve(y_true,y_pred)
return str(max(abs(tpr-fpr)))
get_ks_udfs = F.udf(get_ks, returnType=StringType())
ksdata = spark.sql('select * from tmpview')
print(ksdata.withColumn('eval metrics',F.lit('KS'))\
.groupby('eval metrics')\
.agg(get_ks_udfs(F.collect_list(F.col('y')),F.collect_list(F.col('score'))).alias('KS'))\
.select('KS').toPandas())

二、AUC

AUC(Area Under Curve)被定义为 ROC 曲线下与坐标轴围成的面积,通常用来衡量二分类模型全局的区分能力。在 python 和 pyspark 中可以直接调包计算,在 SQL 中可以根据公式计算获得,其计算方法如下:

  1. 对 score 从小到大排序

  2. 根据公式计算:

    \[AUC=\frac{\sum_{i\in{positiveClass}}rank_i-\frac{M(1+M)}{2}}{M\times N}
    \]

    其中,\(rank_i\) 代表第 i 个正样本的排序序号,M 和 N 分别代表正样本和负样本的总个数。

关于该公式的详细理解,可参考 AUC 的计算方法(及评论)

1. SQL 计算 AUC

select (sumpositivernk-totalbad*(1+totalbad)/2)/(totalbad*totalgood) as auc
from
(
select sum(if(y=1,rnk,0)) as sumpositivernk,
sum(y) as totalbad,
sum(1-y) as totalgood
from
(
select y,row_number() over (order by score) as rnk
from tmpview
)
)

2. Python 计算 AUC

ksdata = spark.sql('select * from tmpview').toPandas()
print(roc_auc_score(ksdata['y'],ksdata['score']))

3. Pyspark 计算 AUC

同 KS 的计算,除了提到的两种方式,还可以调用 pyspark 的 ML 包下二分类评价,来计算 AUC

a. SQL 逻辑改写

aucdata = spark.sql('select * from tmpview')

def calauc(df,ycol='y',scorecol='score'):
return df.withColumn(ycol,F.col(ycol).cast('int')).withColumn(scorecol,F.col(scorecol).cast('float'))\
.withColumn('totalbad',F.sum(F.col(ycol)).over(Window.orderBy(F.lit(1))))\
.withColumn('totalgood',F.sum(1-F.col(ycol)).over(Window.orderBy(F.lit(1))))\
.withColumn('rnk2',F.row_number().over(Window.orderBy(F.col(scorecol).asc())))\
.filter(F.col(ycol)==1)\
.select(((F.sum(F.col('rnk2'))-0.5*(F.max(F.col('totalbad')))*(1+F.max(F.col('totalbad'))))/(F.max(F.col('totalbad'))*F.max(F.col('totalgood')))).alias('AUC'))\ calauc(aucdata).show()

b. UDF 函数

def auc(ytrue,ypred):
return str(roc_auc_score(ytrue,ypred))
get_auc_udfs = F.udf(auc, returnType=StringType())
aucdata = spark.sql('select * from tmpview')
aucdata.withColumn('eval metrics',F.lit('AUC'))\
.groupby('eval metrics')\
.agg(get_auc_udfs(F.collect_list(F.col('y')),F.collect_list(F.col('score'))).alias('AUC'))\
.select('AUC').show()

c. 调包

from pyspark.ml.evaluation import BinaryClassificationEvaluator
evaluator = BinaryClassificationEvaluator(rawPredictionCol='score',labelCol='y')
aucdata = spark.sql('select * from tmpview')
evaluator.evaluate(aucdata)

三、PSI

PSI(Population Stability Index:群体稳定性指标),通常被用于衡量两个样本模型分分布的差异,在风控建模中通常有两个作用:

  1. 用于建模时筛选掉不稳定的特征
  2. 用于建模后及上线后评估和监控模型分值的稳定程度

个人认为该指标无一个比较明确的标准,在样本量较大的条件下,筛选特征时尽量控制特征 PSI<0.1,或更严格。

计算 PSI 首先需要一个分箱基准,假定本文随机生成的模型分的分箱切分点为\([0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1]\)

1. SQL 计算 PSI

select
sum(grouppsi) as psi
from (
select g
,log(count(1) / sum(count(1))over() / 0.1)*(count(1) / sum(count(1))over() - 0.1) as grouppsi
from (
select
case when score<cutpoint[1] then 1
when score<cutpoint[2] then 2
when score<cutpoint[3] then 3
when score<cutpoint[4] then 4
when score<cutpoint[5] then 5
when score<cutpoint[6] then 6
when score<cutpoint[7] then 7
when score<cutpoint[8] then 8
when score<cutpoint[9] then 9
when score<cutpoint[10] then 10 else 'error' end as g
from (
select *
,array(0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1) as cutpoint
from tmpview
)
)
group by g
)

2. Python 计算 PSI

psidata = spark.sql('select * from tmpview').toPandas()
psidata['g'] = pd.cut(psidata['score'],cut_point)
psitable = psidata.groupby('g')['y'].count()
psitable /= psitable.sum()
standratio = 1/(len(cut_point)-1)
psi = sum((psitable-standratio)*np.log(psitable/standratio))

3. Pyspark 计算 PSI

参考Pyspark 实现连续分桶映射并自定义标签,调包分箱后按公式计算 PSI

from pyspark.ml.feature import Bucketizer

def psi(df, splits, inputCol, outputCol):
if len(splits) < 2:
raise RuntimeError("splits's length must grater then 2.") standratio = 1 / (len(splits)-1)
bucketizer = Bucketizer(
splits=splits, inputCol=inputCol, outputCol='split')
with_split = bucketizer.transform(df)
with_split = with_split.groupby('split')\
.agg((F.count(F.col(inputCol))/F.sum(F.count(F.col(inputCol))).over(Window.orderBy(F.lit(1)))).alias('groupratio'))\
.select(F.sum((F.col('groupratio')-standratio)*F.log(F.col('groupratio')/standratio)).alias('PSI')) return with_split
psi(aucdata,cut_point,'score','group').show()

参考资料

深入理解 AUC​

SQL 计算多模型分的 PSI

Pyspark 实现连续分桶映射并自定义标签

使用 pyspark dataframe 的 groupby 计算 AUC

SQL->Python->PySpark计算KS,AUC及PSI的更多相关文章

  1. 模型监控指标- 混淆矩阵、ROC曲线,AUC值,KS曲线以及KS值、PSI值,Lift图,Gain图,KT值,迁移矩阵

    1. 混淆矩阵 确定截断点后,评价学习器性能 假设训练之初以及预测后,一个样本是正例还是反例是已经确定的,这个时候,样本应该有两个类别值,一个是真实的0/1,一个是预测的0/1 TP(实际为正预测为正 ...

  2. pyspark计算最大值、最小值、平均值

    需求:使用pyspark计算相同key的最大值.最小值.平均值 说明: 最大值和最小值好计算,直接reduceByKey后使用python内置的max.min方法 平均值计算提供两种计算方法,直接先上 ...

  3. windows下安装python科学计算环境,numpy scipy scikit ,matplotlib等

    安装matplotlib: pip install matplotlib 背景: 目的:要用Python下的DBSCAN聚类算法. scikit-learn 是一个基于SciPy和Numpy的开源机器 ...

  4. Python TF-IDF计算100份文档关键词权重

    上一篇博文中,我们使用结巴分词对文档进行分词处理,但分词所得结果并不是每个词语都是有意义的(即该词对文档的内容贡献少),那么如何来判断词语对文档的重要度呢,这里介绍一种方法:TF-IDF. 一,TF- ...

  5. Python科学计算(二)windows下开发环境搭建(当用pip安装出现Unable to find vcvarsall.bat)

    用于科学计算Python语言真的是amazing! 方法一:直接安装集成好的软件 刚开始使用numpy.scipy这些模块的时候,图个方便直接使用了一个叫做Enthought的软件.Enthought ...

  6. 目前比较流行的Python科学计算发行版

    经常有身边的学友问到用什么Python发行版比较好? 其实目前比较流行的Python科学计算发行版,主要有这么几个: Python(x,y) GUI基于PyQt,曾经是功能最全也是最强大的,而且是Wi ...

  7. Python科学计算之Pandas

    Reference: http://mp.weixin.qq.com/s?src=3&timestamp=1474979163&ver=1&signature=wnZn1UtW ...

  8. Python 科学计算-介绍

    Python 科学计算 作者 J.R. Johansson (robert@riken.jp) http://dml.riken.jp/~rob/ 最新版本的 IPython notebook 课程文 ...

  9. Python科学计算库

    Python科学计算库 一.numpy库和matplotlib库的学习 (1)numpy库介绍:科学计算包,支持N维数组运算.处理大型矩阵.成熟的广播函数库.矢量运算.线性代数.傅里叶变换.随机数生成 ...

随机推荐

  1. 自由导入你的增量数据-根据条件将sqlserver表批量生成INSERT语句的存储过程实施笔记

    文章标题: 自由导入你的增量数据-根据条件将sqlserver表批量生成INSERT语句的存储过程增强版 关键字 : mssql-scripter,SQL Server 文章分类: 技术分享 创建时间 ...

  2. python实现对象测量

    目录: 问题,轮廓找到了,如何去计算对象的弧长与面积(闭合),多边形拟合,几何矩的计算等 (一)对象的弧长与面积 (二)多边形拟合 (三)几何矩的计算 (四)获取图像的外接矩形boundingRect ...

  3. 一文理解Java-class字节码文件

    前言 java语言在其刚诞生之际喊出的口号--"Write Once,Run Anywhere",正是基于字节码(byte code)而存在的,java能够做到平台无关性,得力于这 ...

  4. 【2020五校联考NOIP #8】自闭

    题目传送门 题意: 有一个 \(n \times m\) 的矩阵,里面已经填好了 \(k\) 个非负整数. 问是否能在其它 \(n \times m-k\) 个格子里各填上一个非负整数,使得得到的矩阵 ...

  5. LOJ #2769 -「ROI 2017 Day 1」前往大都会(单调栈维护斜率优化)

    LOJ 题面传送门 orz 斜率优化-- 模拟赛时被这题送走了,所以来写篇题解( 首先这个最短路的求法是 trivial 的,直接一遍 dijkstra 即可( 重点在于怎样求第二问.注意到这个第二问 ...

  6. P2336 [SCOI2012]喵星球上的点名(SA+莫队)

    题面传送门 一道还算有点含金量的 SA 罢-- 首先按照套路我们把读入的所有字符串都粘在一起,中间用分隔符隔开并建出后缀数组出来. 我们考虑对于一个固定的字符串 \(s\),什么样的字符串 \(t\) ...

  7. CF1542E2 Abnormal Permutation Pairs (hard version)

    CF1542E2 Abnormal Permutation Pairs (hard version) good tea. 对于两个排列 \(p,q\),如果 \(p\) 的字典序小于 \(q\),则要 ...

  8. CMake 工程调用 Makefile 编译项目

    本文主要介绍如何将一个依赖 Makefile 项目(MIDG)移植到 CMake 上. 首先介绍项目文件结构,文件主要由三个目录组成 3rdParty include src 其中,3rdParty ...

  9. Mysql 预处理 PREPARE以及预处理的好处

    Mysql 预处理 PREPARE以及预处理的好处 Mysql手册 预处理记载: 预制语句的SQL语法在以下情况下使用:   · 在编代码前,您想要测试预制语句在您的应用程序中运行得如何.或者也许一个 ...

  10. R语言与医学统计图形【5】饼图、条件图

    R语言基础绘图系统 基础图形--饼图.克利夫兰点图.条件图 6.饼图 pie(rep(1,26),col=rainbow(26), labels = LETTERS[1:26], #标签 radius ...