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. SQL->Python->PySpark计算KS,AUC及PSI

    KS,AUC 和 PSI 是风控算法中最常计算的几个指标,本文记录了多种工具计算这些指标的方法. 生成本文的测试数据: import pandas as pd import numpy as np i ...

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

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

  3. (二)基于商品属性的相似商品推荐算法——Flink SQL实时计算实现商品的隐式评分

    系列随笔: (总览)基于商品属性的相似商品推荐算法 (一)基于商品属性的相似商品推荐算法--整体框架及处理流程 (二)基于商品属性的相似商品推荐算法--Flink SQL实时计算实现商品的隐式评分 ( ...

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

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

  5. SQL + Python 面试题:之二(难度:中等)

    SQL + Python 面试题:之二(难度:中等)

  6. Python科学计算基础包-Numpy

    一.Numpy概念 Numpy(Numerical Python的简称)是Python科学计算的基础包.它提供了以下功能: 快速高效的多维数组对象ndarray. 用于对数组执行元素级计算以及直接对数 ...

  7. Python科学计算PDF

    Python科学计算(高清版)PDF 百度网盘 链接:https://pan.baidu.com/s/1VYs9BamMhCnu4rfN6TG5bg 提取码:2zzk 复制这段内容后打开百度网盘手机A ...

  8. 光照问题之常见算法比较(附Python代码)

    一.灰度世界算法 ① 算法原理 灰度世界算法以灰度世界假设为基础,该假设认为:对于一幅有着大量色彩变化的图像,R,G,B三个分量的平均值趋于同一灰度值Gray.从物理意义上讲,灰色世界法假设自然界景物 ...

  9. DeepFM算法解析及Python实现

    1. DeepFM算法的提出 由于DeepFM算法有效的结合了因子分解机与神经网络在特征学习中的优点:同时提取到低阶组合特征与高阶组合特征,所以越来越被广泛使用. 在DeepFM中,FM算法负责对一阶 ...

  10. GBDT+LR算法解析及Python实现

    1. GBDT + LR 是什么 本质上GBDT+LR是一种具有stacking思想的二分类器模型,所以可以用来解决二分类问题.这个方法出自于Facebook 2014年的论文 Practical L ...

随机推荐

  1. 7.2 Windows驱动开发:内核注册并监控对象回调

    在笔者上一篇文章<内核枚举进程与线程ObCall回调>简单介绍了如何枚举系统中已经存在的进程与线程回调,本章LyShark将通过对象回调实现对进程线程的句柄监控,在内核中提供了ObRegi ...

  2. 微服务保护-Sentinel

    1.初识Sentinel 1.1.雪崩问题及解决方案 1.1.1.雪崩问题 微服务中,服务间调用关系错综复杂,一个微服务往往依赖于多个其它微服务. 如图,如果服务提供者I发生了故障,当前的应用的部分业 ...

  3. 官方实锤!AMD真的已经有了大小核:不搞Intel那一套

    Intel 12代酷睿开始引入大小核混合架构,多核跑分提升立竿见影,在游戏.渲染等场景中也有很好的辅助作用,但因为大核心.小核心基于完全不同的架构,需要复杂的系统.软件调度配合,也直接导致失去了AVX ...

  4. CF1706E Qpwoeirut and Vertices 题解

    题目链接:CF 或者 洛谷 官解看上去挺智慧的,来点朴素的解法.我们来当做纯 ds 题去做.首先明确一点,图中若干个点关于最早连通性的这个问题可以考虑 \(MST\),我们有一类东西叫 \(krusk ...

  5. Delphi中的注释,仅此一篇

    在Pascal中,注释括在大括号中或带星号的圆括号中.Delphi 也认可C++ 风格的注释,即把注释放在双斜线后.例如: {this is a comment} (* this is another ...

  6. DBGRIDEH 底部多列 发现

    1.设置底部行数 2.点击footers 单独对每一行进行设置 3.单独对这两行 进行设置 5.看下辅助 所以用的时候可以这样用:WeiTopTradeShow.FieldColumns['top_x ...

  7. 《ASP.NET Core 微服务实战》-- 读书笔记(第8章)

    第 8 章 服务发现 面对大量服务,为了简化配置和管理工作,我们需要了解"服务发现"概念 回顾云原生特性 配置外置 将 URL 和登录凭证移到配置文件和 C# 代码之外,放到环境变 ...

  8. NC50614 取石子游戏 1

    题目链接 题目 题目描述 有一种有趣的游戏,玩法如下: 玩家:2人: 道具:N颗石子: 规则: 游戏双方轮流取石子:每人每次取走若干颗石子(最少取1颗,最多取K颗):石子取光,则游戏结束:最后取石子的 ...

  9. java常用包下载地址(非maven)

    httpclient与httpcore: http://hc.apache.org/downloads.cgi jdbc: https://dev.mysql.com/downloads/connec ...

  10. springboot项目读取自定义的properties文件

    现在我们要在某个地方读取config.properties里的这几个属性值 这里使用三个注解即可读取自定义的配置文件内容(注意这里需要写他的setter和getter方法) @Component #注 ...