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

生成本文的测试数据:

  1. import pandas as pd
  2. import numpy as np
  3. import pyspark.sql.functions as F
  4. from pyspark.sql.window import Window
  5. from pyspark.sql.types import StringType, DoubleType
  6. from pyspark.sql import SparkSession, functions
  7. from sklearn.metrics import roc_auc_score,roc_curve
  8. tmptable = pd.DataFrame({'y':[np.random.randint(2) for i in range(1000000)]})
  9. tmptable['y'] = tmptable['score'].apply(lambda x:1 if np.random.rand()+x>0.8 else 0)
  10. tmp_sparkdf = spark.createDataFrame(tmptable)
  11. 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

  1. select max(abs(cumgood/totalgood-cumbad/totalbad)) as ks
  2. from (
  3. select score,
  4. sum(totalbad)over(order by score) as cumbad,
  5. sum(totalgood)over(order by score) as cumgood,
  6. sum(totalbad) over() as totalbad,
  7. sum(totalgood) over() as totalgood
  8. from (
  9. select
  10. score,
  11. sum(y) as totalbad,
  12. sum(1-y) as totalgood
  13. from tmpview
  14. group by score
  15. )
  16. )

2. Python 计算 KS

  1. def get_ks(y_true:pd.Series,y_pred:pd.Series):
  2. '''
  3. A staticmethod to caculate the KS of the model.
  4. Args:
  5. y_true: true value of the sample
  6. y_pred: pred value of the sample
  7. Returns:
  8. max(tpr-fpr): KS of the model
  9. '''
  10. fpr,tpr,_ = roc_curve(y_true,y_pred)
  11. return str(max(abs(tpr-fpr)))
  12. ksdata = spark.sql('select * from tmpview').toPandas()
  13. print(get_ks(ksdata['y'],ksdata['score']))

3. Pyspark 计算 KS

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

a. SQL 逻辑改写

  1. ksdata = spark.sql('select * from tmpview')
  2. def calks(df,ycol='y',scorecol='score'):
  3. return df.withColumn(ycol,F.col(ycol).cast('int')).withColumn(scorecol,F.col(scorecol).cast('float'))\
  4. .withColumn('totalbad',F.sum(F.col(ycol)).over(Window.orderBy(F.lit(1))))\
  5. .withColumn('totalgood',F.sum(1-F.col(ycol)).over(Window.orderBy(F.lit(1))))\
  6. .withColumn('cumgood',F.sum(1-F.col(ycol)).over(Window.orderBy(F.col(scorecol).asc())))\
  7. .withColumn('cumbad',F.sum(F.col(ycol)).over(Window.orderBy(F.col(scorecol).asc())))\
  8. .select(F.max(F.abs(F.col('cumgood')/F.col('totalgood')-F.col('cumbad')/F.col('totalbad'))).alias('KS'))
  9. calks(ksdata).show()

b. python 转 UDF 函数

  1. def get_ks(y_true:pd.Series,y_pred:pd.Series):
  2. '''
  3. A staticmethod to caculate the KS of the model.
  4. Args:
  5. y_true: true value of the sample
  6. y_pred: pred value of the sample
  7. Returns:
  8. max(tpr-fpr): KS of the model
  9. '''
  10. fpr,tpr,_ = roc_curve(y_true,y_pred)
  11. return str(max(abs(tpr-fpr)))
  12. get_ks_udfs = F.udf(get_ks, returnType=StringType())
  13. ksdata = spark.sql('select * from tmpview')
  14. print(ksdata.withColumn('eval metrics',F.lit('KS'))\
  15. .groupby('eval metrics')\
  16. .agg(get_ks_udfs(F.collect_list(F.col('y')),F.collect_list(F.col('score'))).alias('KS'))\
  17. .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

  1. select (sumpositivernk-totalbad*(1+totalbad)/2)/(totalbad*totalgood) as auc
  2. from
  3. (
  4. select sum(if(y=1,rnk,0)) as sumpositivernk,
  5. sum(y) as totalbad,
  6. sum(1-y) as totalgood
  7. from
  8. (
  9. select y,row_number() over (order by score) as rnk
  10. from tmpview
  11. )
  12. )

2. Python 计算 AUC

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

3. Pyspark 计算 AUC

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

a. SQL 逻辑改写

  1. aucdata = spark.sql('select * from tmpview')
  2. def calauc(df,ycol='y',scorecol='score'):
  3. return df.withColumn(ycol,F.col(ycol).cast('int')).withColumn(scorecol,F.col(scorecol).cast('float'))\
  4. .withColumn('totalbad',F.sum(F.col(ycol)).over(Window.orderBy(F.lit(1))))\
  5. .withColumn('totalgood',F.sum(1-F.col(ycol)).over(Window.orderBy(F.lit(1))))\
  6. .withColumn('rnk2',F.row_number().over(Window.orderBy(F.col(scorecol).asc())))\
  7. .filter(F.col(ycol)==1)\
  8. .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'))\
  9. calauc(aucdata).show()

b. UDF 函数

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

c. 调包

  1. from pyspark.ml.evaluation import BinaryClassificationEvaluator
  2. evaluator = BinaryClassificationEvaluator(rawPredictionCol='score',labelCol='y')
  3. aucdata = spark.sql('select * from tmpview')
  4. 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

  1. select
  2. sum(grouppsi) as psi
  3. from (
  4. select g
  5. ,log(count(1) / sum(count(1))over() / 0.1)*(count(1) / sum(count(1))over() - 0.1) as grouppsi
  6. from (
  7. select
  8. case when score<cutpoint[1] then 1
  9. when score<cutpoint[2] then 2
  10. when score<cutpoint[3] then 3
  11. when score<cutpoint[4] then 4
  12. when score<cutpoint[5] then 5
  13. when score<cutpoint[6] then 6
  14. when score<cutpoint[7] then 7
  15. when score<cutpoint[8] then 8
  16. when score<cutpoint[9] then 9
  17. when score<cutpoint[10] then 10 else 'error' end as g
  18. from (
  19. select *
  20. ,array(0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1) as cutpoint
  21. from tmpview
  22. )
  23. )
  24. group by g
  25. )

2. Python 计算 PSI

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

3. Pyspark 计算 PSI

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

  1. from pyspark.ml.feature import Bucketizer
  2. def psi(df, splits, inputCol, outputCol):
  3. if len(splits) < 2:
  4. raise RuntimeError("splits's length must grater then 2.")
  5. standratio = 1 / (len(splits)-1)
  6. bucketizer = Bucketizer(
  7. splits=splits, inputCol=inputCol, outputCol='split')
  8. with_split = bucketizer.transform(df)
  9. with_split = with_split.groupby('split')\
  10. .agg((F.count(F.col(inputCol))/F.sum(F.count(F.col(inputCol))).over(Window.orderBy(F.lit(1)))).alias('groupratio'))\
  11. .select(F.sum((F.col('groupratio')-standratio)*F.log(F.col('groupratio')/standratio)).alias('PSI'))
  12. return with_split
  13. 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. 【JAVA】笔记(8)--- java.lang.String 精讲

    String 特性: 1.String 表示字符串类型,属于引用数据类型,所以其储存的是地址: 2.java 中规定,双引号括起来的字符串是不可变的,也就说" name "永远也只 ...

  2. [cf1479E]School Clubs

    对于当前班级状态$S$,定义一个函数$\varphi(S)$,要求其满足: 令结束状态为$S_{end}$,对于任意$S\ne S_{end}$,若其下一个状态为$S'$,则$E(\varphi(S) ...

  3. springboot和mybatis集成

    springboot和mybatis集成 pom  <?xml version="1.0" encoding="UTF-8"?> <proje ...

  4. electron另一种运行方式

    编写helloword 全局安装软件  npm install -g electron 快速编写html  html:5 完整代码和流程: 1.index.html  <!DOCTYPE htm ...

  5. Linux图片查看软件ImageMagick安装

    在Linux中查看图片,这个需求是非常常见的.总不至于在集群中生成个图片,随便看下效果,也要用filezilla.winscp之类的远程文件传输工具导过来导过去吧,这样效率太低. Linux图片查看常 ...

  6. kubernetes部署 kube-apiserver服务

    kubernetes部署 kube-apiserver 组件 本文档讲解使用 keepalived 和 haproxy 部署一个 3 节点高可用 master 集群的步骤. kube-apiserve ...

  7. centos 7的命令变化

    1.service -> systemctl命令 2.ifconfig -> ip 命令 3.netstat -> ss命令 4.route -> ip route命令 5.t ...

  8. PyTools-包罗万象的python工具包

    PyTools-包罗万象的python工具包 <---点击这里获取代码,欢迎star. 自己平时写的代码都以函数方式封装起来了,方便代码复用. _________ ________ ______ ...

  9. LeetCode替换空格

    LeetCode 替换空格 题目描述 请实现一个函数,把字符串 s 中的每个空格替换成"%20". 实例 1: 输入:s = "We are happy." 输 ...

  10. 面向多场景而设计的 Erda Pipeline

    作者|林俊(万念) 来源|尔达 Erda 公众号 Erda Pipeline 是端点自研.用 Go 编写的一款企业级流水线服务.截至目前,已经为众多行业头部客户提供交付和稳定的服务. 为什么我们坚持自 ...