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

公式定义和原理解释见:

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.

Autor chenfeng
#!/usr/bin/env Python
coding=utf-8

'''
学习demo:
皮尔逊相关系数,woe计算
'''

import pandas as pd
import numpy as np
from scipy import stats

从正太分布种抽取20个随机数
x = np.array(np.random.randn((20)))
print(type(x))
X = pd.DataFrame(x, columns=['x'])
生成范围在[0, 2)的20个整数
y = np.random.randint(0,2,size=[20])
Y = pd.DataFrame(y, columns=['y'])
print(type(Y))   # dataframe类型

r = 0
n分箱个数
n=5

## 有sum和count函数,Y的类型必须是DataFrame
good = Y.sum()
bad = Y.count() - good
print(type(good))  # Series类型

while np.abs(r) < 1:
    # pandas&#x4E2D;&#x4F7F;&#x7528;qcut()&#xFF0C;&#x8FB9;&#x754C;&#x6613;&#x51FA;&#x73B0;&#x91CD;&#x590D;&#x503C;&#xFF0C;&#x5982;&#x679C;&#x4E3A;&#x4E86;&#x5220;&#x9664;&#x91CD;&#x590D;&#x503C;&#x8BBE;&#x7F6E; duplicates=&#x2018;drop'&#xFF0C;&#x5219;&#x6613;&#x51FA;&#x73B0;&#x4E8E;&#x5206;&#x7247;&#x4E2A;&#x6570;&#x5C11;&#x4E8E;&#x6307;&#x5B9A;&#x4E2A;&#x6570;&#x7684;&#x95EE;&#x9898;
    # &#x8FD9;&#x91CC;&#x7EC4;&#x5408;DataFrame&#x9700;&#x8981;&#x7684;X&#x3001;Y&#x7684;&#x7C7B;&#x578B;&#x5E94;&#x8BE5;&#x662F;array&#xFF0C;&#x4E0D;&#x80FD;&#x662F;DataFrame&#x3002;&#x53EF;&#x4EE5;&#x6709;2&#x79CD;&#x5199;&#x6CD5;&#xFF1A;
    # &#x65B9;&#x5F0F;&#x4E00;&#xFF1A;
    # d1 = pd.DataFrame({"X": X['x'], "Y": Y['y'], "Bucket": pd.qcut(X['x'], n, duplicates="drop")})
    # &#x65B9;&#x5F0F;&#x4E8C;&#xFF1A;
    d1 = pd.DataFrame({"X": x, "Y": y, "Bucket": pd.qcut(X['x'], n, duplicates="drop")})
    # as_index=True&#x4F1A;&#x8BB0;&#x5F55;&#x6BCF;&#x4E2A;&#x5206;&#x7BB1;&#x5305;&#x542B;&#x7684;&#x6570;&#x636E;index
    d2 = d1.groupby('Bucket', as_index=True)
    '''
    &#x8BA1;&#x7B97;&#x6BCF;&#x4E2A;&#x5206;&#x7BB1;&#x7684;X&#x5217;&#x5E73;&#x5747;&#x503C;d2.mean().X &#x4E0E;&#x6BCF;&#x4E2A;&#x5206;&#x7BB1;&#x7684;Y&#x5217;&#x5E73;&#x5747;&#x503C;d2.mean().Y &#x7684;spearman&#x79E9;&#x76F8;&#x5173;&#x6027;&#xFF08;pearson&#x76F8;&#x5173;&#x7CFB;&#x6570;&#x53EA;&#x53EF;&#x7528;&#x6765;&#x8BA1;&#x7B97;&#x8FDE;&#x7EED;&#x6570;&#x636E;&#x3001;&#x6B63;&#x592A;&#x5206;&#x5E03;&#x7684;&#x7EBF;&#x6027;&#x76F8;&#x5173;&#x6027;&#xFF09;&#x3002;
    1&#x3001;&#x8FD4;&#x56DE;&#x503C;r-&#x76F8;&#x5173;&#x7CFB;&#x6570;&#xFF0C;p-&#x4E0D;&#x76F8;&#x5173;&#x7CFB;&#x6570;&#xFF0C;p-values&#x5E76;&#x4E0D;&#x5B8C;&#x5168;&#x53EF;&#x9760;&#xFF0C;&#x4F46;&#x5BF9;&#x4E8E;&#x5927;&#x4E8E;500&#x5DE6;&#x53F3;&#x7684;&#x6570;&#x636E;&#x96C6;&#x53EF;&#x80FD;&#x662F;&#x5408;&#x7406;&#x7684;&#x3002;
    2&#x3001;&#x548C;&#x5176;&#x4ED6;&#x76F8;&#x5173;&#x7CFB;&#x6570;&#x4E00;&#x6837;&#xFF0C;r&#x548C;p&#x5728;-1&#x548C;+1&#x4E4B;&#x95F4;&#x53D8;&#x5316;&#xFF0C;&#x5176;&#x4E2D;0&#x8868;&#x793A;&#x65E0;&#x76F8;&#x5173;&#x3002;
      -1&#x6216;+1&#x7684;&#x76F8;&#x5173;&#x5173;&#x7CFB;&#x610F;&#x5473;&#x7740;&#x786E;&#x5207;&#x7684;&#x5355;&#x8C03;&#x5173;&#x7CFB;&#x3002;&#x6B63;&#x76F8;&#x5173;&#x8868;&#x660E;&#xFF0C;&#x968F;&#x7740;x&#x7684;&#x589E;&#x52A0;&#xFF0C;y&#x4E5F;&#x968F;&#x4E4B;&#x589E;&#x52A0;&#x3002;&#x8D1F;&#x76F8;&#x5173;&#x6027;&#x8868;&#x793A;&#x968F;&#x7740;x&#x589E;&#x52A0;&#xFF0C;y&#x51CF;&#x5C0F;&#x3002;
    '''
    # d2.mean()&#x53D6;&#x6BCF;&#x4E2A;&#x5206;&#x7BB1;&#x7684;&#x5E73;&#x5747;&#x503C;
    r, p = stats.spearmanr(d2.mean().X, d2.mean().Y)
    n = n - 1
    print('n={}, r={}&#xFF0C;p={}'.format(n, r, p))

print("=" * 60)
'''
d3 = pd.DataFrame(d2.X.min(), columns=['min'])&#x5F97;&#x5230;&#x7684;&#x662F;&#x4E00;&#x4E2A;&#x7A7A;&#x7684;dataframe
'''
d3 = pd.DataFrame(d2.X.min(), columns=['min'])
'''
d3&#xFF1A;&#x8BB0;&#x5F55;&#x6BCF;&#x4E2A;&#x5206;&#x7BB1;&#x7684;min&#x3001;max&#x3001;sum&#x3001;total&#x3001;rate=sum/total&#x3001;woe
'''
d3['min'] = d2.min().X
d3['max'] = d2.max().X
d3['sum'] = d2.sum().Y
d3['total'] = d2.count().Y
d3['rate'] = d2.mean().Y

'''
1&#x3001;Dataframe&#x7C7B;&#x578B;&#x548C;Series&#x7C7B;&#x578B;&#x505A;&#x52A0;&#x51CF;&#x4E58;&#x9664;&#x8BA1;&#x7B97;&#x65F6;&#xFF0C;Series&#x7684;&#x884C;&#x7D22;&#x5F15;&#x5BF9;&#x5E94;Dataframe&#x7684;&#x5217;&#x7D22;&#x5F15;&#xFF0C;&#x5982;&#x679C;&#x4E24;&#x8005;&#x6CA1;&#x6709;&#x5339;&#x914D;&#x7684;&#x7D22;&#x5F15;&#xFF0C;&#x5219;&#x8FD4;&#x56DE;Nan&#x3002;
&#x8FD9;&#x91CC;good&#x548C;bad&#x7684;&#x884C;&#x7D22;&#x5F15;&#x90FD;&#x662F;y&#xFF0C;&#x4F46;&#x662F;d3&#x662F;&#x6CA1;&#x6709;y&#x5217;&#x7684;&#xFF0C;&#x800C;&#x4E14;&#x8FD9;&#x91CC;&#x5176;&#x5B9E;&#x53EA;&#x662F;&#x60F3;&#x9664;&#x4EE5;&#x4E00;&#x4E2A;&#x5B9E;&#x6570;&#xFF0C;&#x6240;&#x4EE5;&#x76F4;&#x63A5;&#x53D6;Series&#x7684;values&#x5C31;&#x884C;&#x4E86;&#x3002;
2&#x3001;d3['sum']/(d3['total']-d3['sum']) = d3['rate']/(1 - d3['rate'])
'''
d3['woe'] = np.log((d3['rate']/(1 - d3['rate']))/(good / bad).values)

d3['goodrate'] = d3['sum']/good.values
d3['badrate'] = (d3['total']-d3['sum'])/bad.values
d3['iv'] = ((d3['goodrate']-d3['badrate'])*d3['woe']).sum()
'''
1&#x3001;sort_values(axis=0, by=&#x2018;min&#x2019;)&#x8868;&#x793A;&#x6CBF;&#x7B2C;&#x4E00;&#x7EF4;&#x7684;&#x65B9;&#x5411;&#xFF0C;&#x5BF9;min&#x5217;&#x6392;&#x5E8F;&#xFF0C;&#x9ED8;&#x8BA4;&#x662F;&#x9012;&#x589E;&#x3002;axis=1&#x8868;&#x793A;&#x6CBF;&#x7B2C;&#x4E8C;&#x7EF4;&#x6392;&#x5E8F;&#x3002;
   sort_index()&#x53EA;&#x80FD;&#x64CD;&#x4F5C;&#x7D22;&#x5F15;index&#xFF0C;&#x4E0D;&#x80FD;&#x64CD;&#x4F5C;&#x5176;&#x4ED6;&#x884C;&#x6216;&#x5217;
2&#x3001;reset_index(drop=True) &#x91CD;&#x7F6E;&#x7D22;&#x5F15;&#xFF0C;drop=true&#x8868;&#x793A;&#x5220;&#x9664;&#x65E7;&#x7D22;&#x5F15;&#xFF0C;&#x91CD;&#x7F6E;&#x540E;&#x7684;&#x7D22;&#x5F15;&#x9ED8;&#x8BA4;&#x4ECE;0&#x5F00;&#x59CB;&#x7684;&#x9012;&#x589E;&#x6570;&#x503C;&#x3002;
'''
d4 = (d3.sort_values(axis=0, by='min')).reset_index(drop=True)
print(d4)

2、稳定性PSI

Autor chenfeng
#!/usr/bin/env Python
coding=utf-8

import math
import numpy as np
import pandas as pd

def calculate_psi(base_list, test_list, bins=20, min_sample=10):
    try:
        base_df = pd.DataFrame(base_list, columns=['score'])
        test_df = pd.DataFrame(test_list, columns=['score'])

        #---------------------------- 1.&#x53BB;&#x9664;&#x7F3A;&#x5931;&#x503C;&#x540E;&#xFF0C;&#x7EDF;&#x8BA1;&#x4E24;&#x4E2A;&#x5206;&#x5E03;&#x7684;&#x6837;&#x672C;&#x91CF;-----------------------------------------------
        base_notnull_cnt = len(list(base_df['score'].dropna()))
        test_notnull_cnt = len(list(test_df['score'].dropna()))

        # &#x7A7A;&#x5206;&#x7BB1;
        base_null_cnt = len(base_df) - base_notnull_cnt
        test_null_cnt = len(test_df) - test_notnull_cnt

        #---------------------------- 2.&#x6700;&#x5C0F;&#x5206;&#x7BB1;&#x6570;&#xFF1A;&#x8FD9;&#x91CC;&#x8C8C;&#x4F3C;&#x662F;&#x7B49;&#x8DDD;&#x5206;&#x7BB1;&#xFF1F;&#xFF1F;&#x6BCF;&#x4E2A;&#x5206;&#x7BB1;&#x7684;&#x8DDD;&#x79BB;&#x662F;1/bin_num------------------------
        q_list = []
        if type(bins) == int:
            bin_num = min(bins, int(base_notnull_cnt / min_sample))
            q_list = [x / bin_num for x in range(1, bin_num)]
            break_list = []
            for q in q_list:
                '''
                df['score'].quantile(q=fraction, interpolation=linear)&#x8FD4;&#x56DE;df['score']&#x5217;&#x7684;&#x6570;&#x636E;&#x5728;q&#x5904;&#x7684;&#x5206;&#x4F4D;&#x6570;&#x503C;&#x3002;
                &#x5176;&#x4E2D;&#xFF0C;&#x63D2;&#x503C;&#x65B9;&#x5F0F;interpolation&#x6709;&#x4EE5;&#x4E0B;&#x51E0;&#x79CD;&#xFF1A;
                j-df['score']&#x5217;&#x4E2D;&#x7684;&#x6700;&#x5927;&#x503C;
                i-df['score']&#x5217;&#x4E2D;&#x7684;&#x6700;&#x5C0F;&#x503C;
                fraction-&#x63D2;&#x503C;&#x5206;&#x4F4D;&#xFF0C;&#x5F53;fraction=0.5, interpolation=linear&#x65F6;&#x5C31;&#x662F;&#x6C42;&#x4E2D;&#x4F4D;&#x6570;&#x3002;
                * linear: i + (j - i) * fraction, where fraction is the
                  fractional part of the index surrounded by i and j.

                * lower: i.

                * higher: j.

                * nearest: i or j whichever is nearest.

                * midpoint: (i + j) / 2.

                '''
                bk = base_df['score'].quantile(q)
                break_list.append(bk)
            break_list = sorted(list(set(break_list)))  # &#x53BB;&#x91CD;&#x590D;&#x540E;&#x6392;&#x5E8F;
            score_bin_list = [-np.inf] + break_list + [np.inf]
        else:
            score_bin_list = bins

        #----------------------------3.&#x7EDF;&#x8BA1;&#x5404;&#x5206;&#x7BB1;&#x5185;&#x7684;&#x6837;&#x672C;&#x91CF;----------------------------------------------------------------
        base_cnt_list = [base_null_cnt]
        test_cnt_list = [test_null_cnt]
        bucket_list = ["MISSING"]
        for i in range(len(score_bin_list) - 1):
            '''
            round()&#x65B9;&#x6CD5;&#x8FD4;&#x56DE;&#xFF1A;&#x6309;&#x8981;&#x6C42;&#x7684;&#x5C0F;&#x6570;&#x4F4D;&#x4E2A;&#x6570;&#x6C42;&#x56DB;&#x820D;&#x4E94;&#x5165;&#x7684;&#x7ED3;&#x679C;&#x3002;
            &#x5B9E;&#x9645;&#x4F7F;&#x7528;&#x4E2D;&#x53D1;&#x73B0;round&#x51FD;&#x6570;&#x5E76;&#x4E0D;&#x603B;&#x662F;&#x5982;&#x4E0A;&#x6240;&#x8BF4;&#x7684;&#x56DB;&#x820D;&#x4E94;&#x5165;&#xFF0C;&#x8FD9;&#x662F;&#x56E0;&#x4E3A;&#x8BE5;&#x51FD;&#x6570;&#x5BF9;&#x4E8E;&#x8FD4;&#x56DE;&#x7684;&#x6D6E;&#x70B9;&#x6570;&#x5E76;&#x4E0D;&#x662F;&#x6309;&#x7167;&#x56DB;&#x820D;&#x4E94;&#x5165;&#x7684;&#x89C4;&#x5219;&#x6765;&#x8BA1;&#x7B97;&#xFF0C;&#x800C;&#x4F1A;&#x6536;&#x5230;&#x8BA1;&#x7B97;&#x673A;&#x8868;&#x793A;&#x7CBE;&#x5EA6;&#x7684;&#x5F71;&#x54CD;&#x3002;
            &#x5F53;&#x53C2;&#x6570;n&#x4E0D;&#x5B58;&#x5728;&#x65F6;&#xFF0C;round()&#x51FD;&#x6570;&#x7684;&#x8F93;&#x51FA;&#x4E3A;&#x6574;&#x6570;&#x3002;
            &#x5F53;&#x53C2;&#x6570;n&#x5B58;&#x5728;&#x65F6;&#xFF0C;&#x5373;&#x4F7F;&#x4E3A;0&#xFF0C;round()&#x51FD;&#x6570;&#x7684;&#x8F93;&#x51FA;&#x4E5F;&#x4F1A;&#x662F;&#x4E00;&#x4E2A;&#x6D6E;&#x70B9;&#x6570;&#x3002;
             &#x6B64;&#x5916;&#xFF0C;n&#x7684;&#x503C;&#x53EF;&#x4EE5;&#x662F;&#x8D1F;&#x6570;&#xFF0C;&#x8868;&#x793A;&#x5728;&#x6574;&#x6570;&#x4F4D;&#x90E8;&#x5206;&#x56DB;&#x820D;&#x4E94;&#x5165;&#xFF0C;&#x4F46;&#x7ED3;&#x679C;&#x4ECD;&#x662F;&#x6D6E;&#x70B9;&#x6570;&#x3002;
            '''
            left = round(score_bin_list[i + 0], 4)
            right = round(score_bin_list[i + 1], 4)
            # &#x6784;&#x5EFA;&#x5206;&#x7BB1;&#x683C;&#x5F0F;&#xFF0C;&#x8FD4;&#x56DE;&#x5DE6;&#x5F00;&#x53C8;&#x95ED;&#x7684;&#x5F62;&#x5F0F;&#xFF0C;&#x5982;&#xFF08;0.1&#xFF0C;0.2]
            bucket_list.append("(" + str(left) + ',' + str(right) + ']')

            base_cnt = base_df[(base_df.score > left) & (base_df.score <= right)].shape[0] base_cnt_list.append(base_cnt) test_cnt="test_df[(test_df.score"> left) & (test_df.score <= 0 right)].shape[0] test_cnt_list.append(test_cnt) #------------------------4.汇总统计结果:求每个分箱的占比率------------------------------------------------------------------------ stat_df="pd.DataFrame({"bucket":" bucket_list, "base_cnt": base_cnt_list, "test_cnt": test_cnt_list}) stat_df['base_dist']="stat_df['base_cnt']" len(base_df) stat_df['test_dist']="stat_df['test_cnt']" len(test_df) def sub_psi(row): #------------------------ 5.计算每个分箱的不稳定性---------------------------------------------------------------------------- base_list="row['base_dist']" test_dist="row['test_dist']" # 处理某分箱内样本量为0的情况 if and 0: return elif> 0:
                base_list = 1 / base_notnull_cnt
            elif base_list > 0 and test_dist == 0:
                test_dist = 1 / test_notnull_cnt

            return (test_dist - base_list) * np.log(test_dist / base_list)

        '''
        &#x5BF9;stat_df&#x7684;&#x6BCF;&#x884C;&#x5E94;&#x7528;sub_psi(...)&#x51FD;&#x6570;
        '''
        stat_df['psi'] = stat_df.apply(lambda row: sub_psi(row), axis=1)
        '''
        &#x4ECE;stat_df&#x4E2D;&#x53D6;['bucket', 'base_cnt', 'base_dist', 'test_cnt', 'test_dist', 'psi']&#x8FD9;&#x4E9B;&#x5217;&#x7684;&#x6570;&#x636E;
        '''
        stat_df = stat_df[['bucket', 'base_cnt', 'base_dist', 'test_cnt', 'test_dist', 'psi']]
        # ------------------------6&#x3001;PSI = &#x6240;&#x6709;&#x5206;&#x7BB1;&#x7684;&#x4E0D;&#x7A33;&#x5B9A;&#x6027;&#x4E4B;&#x548C;------------------------------------------------------------------------
        psi = stat_df['psi'].sum()

    except:
        print('error!!!')
        psi = np.nan
        stat_df = None
    return psi, stat_df

if __name__=='__main__':
    # &#x4ECE;&#x6B63;&#x592A;&#x5206;&#x5E03;&#x79CD;&#x62BD;&#x53D6;20&#x4E2A;&#x968F;&#x673A;&#x6570;
    base_x = np.array(np.random.randn(100))
    base_list = pd.DataFrame({"score": base_x})

    test_x = np.array(np.random.randn(20))
    test_list = pd.DataFrame(test_x, columns=['score'])

    psi, stat_df = calculate_psi(base_list=base_list,
                                 test_list=test_list,
                                 bins=20, min_sample=10)
    print("psi:", psi)
    print(stat_df)</=></=>

3、ROC和KS曲线

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

tpr,fpr,threshold = sklearn.metrics.roc_curve(y_label,y_pred,pos_label=1)
备注:pos_label=positive label,当y_label是{0,1}或{-1,1}内的值,可以不写;否则必须明确。
Autor chenfeng
#!/usr/bin/env Python
coding=utf-8
import numpy as np
from sklearn import metrics
import matplotlib.pyplot as plt

def plot_roc(y_label,y_pred):
"""
    &#x7ED8;&#x5236;roc&#x66F2;&#x7EBF;
    param:
        y_label -- &#x771F;&#x5B9E;&#x7684;y&#x503C; list/array
        y_pred -- &#x9884;&#x6D4B;&#x7684;y&#x503C; list/array
    return:
        roc&#x66F2;&#x7EBF;
"""
    '''
    1&#x3001;tpr&#xFF08;&#x771F;&#x6B63;&#x7387;&#xFF09;&#x548C;fpr&#xFF08;&#x5047;&#x6B63;&#x7387;&#xFF09;
    2&#x3001;&#x5F53;y&#x7684;&#x503C;&#x4E0D;&#x5728;{0, 1} or {-1, 1}&#x8303;&#x56F4;&#x5185;&#x65F6;&#xFF0C;&#x9700;&#x8981;&#x8BBE;&#x7F6E;positive label&#x53C2;&#x6570;&#xFF1A;pos_label
    '''
    tpr,fpr,threshold = metrics.roc_curve(y_label, y_pred, pos_label=2)
    print("&#x771F;&#x6B63;&#x7387;tpr&#xFF1A;", tpr)
    print("&#x5047;&#x6B63;&#x7387;fpr&#xFF1A;", fpr)
    print("threshold&#xFF1A;", threshold)

    '''
    AUC&#x662F;roc&#x66F2;&#x7EBF;&#x4E0B;&#x65B9;&#x7684;&#x9762;&#x79EF;
    '''
    AUC = metrics.roc_auc_score(y_label,y_pred)

    fig = plt.figure(figsize=(6,4))
    ax = fig.add_subplot(1,1,1)
    ax.plot(fpr, tpr, color='blue', label='AUC=%.3f'%AUC)
    ax.plot([0,1],[0,1],'r--')
    ax.set_ylim(0,1)
    ax.set_xlim(0,1)
    ax.set_title('ROC')
    ax.legend(loc='best')
    plt.show()

def plot_model_ks(y_label, y_pred):
"""
    &#x7ED8;&#x5236;ks&#x66F2;&#x7EBF;
    param:
        y_label -- &#x771F;&#x5B9E;&#x7684;y&#x503C; list/array
        y_pred -- &#x9884;&#x6D4B;&#x7684;y&#x503C; list/array
    return:
        ks&#x66F2;&#x7EBF;
"""
    pred_list = list(y_pred)
    label_list = list(y_label)
    total_bad = sum(label_list)
    total_good = len(label_list) - total_bad
    items = sorted(zip(pred_list, label_list), key=lambda x: x[0])
    step = (max(pred_list) - min(pred_list)) / 200

    pred_bin = []
    good_rates = []
    bad_rates = []
    ks_list = []
    for i in range(1, 201):
        idx = min(pred_list) + i * step
        pred_bin.append(idx)
        label_bin = [x[1] for x in items if x[0] < idx]
        bad_num = sum(label_bin)
        good_num = len(label_bin) - bad_num
        goodrate = good_num / total_good
        badrate = bad_num / total_bad
        ks = abs(goodrate - badrate)
        good_rates.append(goodrate)
        bad_rates.append(badrate)
        ks_list.append(ks)

    fig = plt.figure(figsize=(6, 4))
    ax = fig.add_subplot(1, 1, 1)
    ax.plot(pred_bin, good_rates, color='green', label='good_rate')
    ax.plot(pred_bin, bad_rates, color='red', label='bad_rate')
    ax.plot(pred_bin, ks_list, color='blue', label='good-bad')
    ax.set_title('KS:{:.3f}'.format(max(ks_list)))
    ax.legend(loc='best')
    plt.show()

y_label = np.array([1, 1, 2, 2])
y_pred = np.array([0.1, 0.4, 0.35, 0.8])
plot_roc(y_label, y_pred)
plot_model_ks(y_label, y_pred)

Original: https://blog.csdn.net/qq_19072921/article/details/123402601
Author: 风路丞
Title: 评分卡:WOE、IV、PSI计算及ROC和KS曲线

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/695794/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球