邏輯迴歸評分卡實現和評估

評分卡實現和評估

  上一節講得是模型評估,主要有ROC曲線、KS曲線、學習曲線和混淆矩陣。今天學習如何實現評分卡和對評分卡進行評估。

  首先,要了解評分卡是如何從概率映射到評分的,這個之前寫過評分卡映射的邏輯。見邏輯迴歸卡評分映射邏輯,一定要看,明白概率如何映射到評分的以及每個變量的得分如何計算。附上評分卡映射的代碼。結合邏輯迴歸評分卡映射的原理才能看懂代碼。

from sklearn.linear_model import LogisticRegression
'''
第六步:邏輯迴歸模型。
要求:
1,變量顯著
2,符號爲負
'''
y = trainData['y']
x = trainData[multi_analysis]

lr_model = LogisticRegression(C=0.1)
lr_model.fit(x,y)
trainData['prob'] = lr_model.predict_proba(x)[:,1]

# 評分卡刻度 
def cal_scale(score,odds,PDO,model):
    """
    odds:設定的壞好比
    score:在這個odds下的分數
    PDO: 好壞翻倍比
    model:邏輯迴歸模型
    
    return :A,B,base_score
    """
    B = PDO/np.log(2)
    A = score+B*np.log(odds)
    # base_score = A+B*model.intercept_[0]
    print('B: {:.2f}'.format(B))
    print('A: {:.2f}'.format(A))
    # print('基礎分爲:{:.2f}'.format(base_score))
    return A,B
    
#假設基礎分爲50,odds爲5%,PDO爲10,可以自行調整。這一步是爲了計算出A和B。    
cal_scale(50,0.05,10,lr_model)

def Prob2Score(prob, A,B):
    #將概率轉化成分數且爲正整數
    y = np.log(prob/(1-prob))
    return float(A-B*y)

trainData['score'] = trainData['prob'].map(lambda x:Prob2Score(x, 6.78,14.43))

  可以看到,評分越高,違約概率越低。網上很多實現評分卡映射的代碼,都沒太看懂,這個是根據邏輯來寫的,有時間再把映射邏輯整理一下。

1. 得分的KS曲線

  和模型的KS曲線一樣,只不過橫座標的概率變成了得分。
直接放上代碼。

# 得分的KS 
def plot_score_ks(df,score_col,target):
    """
    df:數據集
    target:目標變量的字段名
    score_col:最終得分的字段名
    """
    total_bad = df[target].sum()
    total_good = df[target].count()-total_bad
    score_list = list(df[score_col])
    target_list = list(df[target])
    items = sorted(zip(score_list,target_list),key=lambda x:x[0]) 
    step = (max(score_list)-min(score_list))/200 
    
    score_bin=[] 
    good_rate=[] 
    bad_rate=[] 
    ks_list = [] 
    for i in range(1,201):
        idx = min(score_list)+i*step 
        score_bin.append(idx) 
        target_bin = [x[1] for x in items if x[0]<idx]  
        bad_num = sum(target_bin)
        good_num = len(target_bin)-bad_num 
        goodrate = good_num/total_good 
        badrate = bad_num/total_bad
        ks = abs(goodrate-badrate) 
        good_rate.append(goodrate)
        bad_rate.append(badrate)
        ks_list.append(ks)
        
    fig = plt.figure(figsize=(8,6))
    ax = fig.add_subplot(1,1,1)
    ax.plot(score_bin,good_rate,color='green',label='good_rate')
    ax.plot(score_bin,bad_rate,color='red',label='bad_rate')
    ax.plot(score_bin,ks_list,color='blue',label='good-bad')
    ax.set_title('KS:{:.3f}'.format(max(ks_list)))
    ax.legend(loc='best')
    return plt.show(ax)

2. PR曲線

  還是這個混淆矩陣的圖,P是查準率、精確率,R是查全率、召回率。這兩個指標時既矛盾又統一的。因爲爲了提高精確率P,就是要更準確地預測正樣本,但此時往往會過於保守而漏掉很多沒那麼有把握的正樣本,導致召回率R降低。
  同ROC曲線的形成一樣,PR曲線的形成也是不斷移動截斷點形成不同的(R,P)繪製成一條線。

  當接近原點時,召回率R接近於0,精確率P較高,說明得分前幾位的都是正樣本。隨着召回率的增加,精確率整體下降,當召回率爲1時,說明所有的正樣本都被挑了出來,此時的精確率很低,其實就是相當於你將大部分的樣本都預測爲正樣本。注意,只用某個點對應的(R,P)無法全面衡量模型的性能,必須要通過PR曲線的整體表現。此外,還有F1 score和ROC曲線也能反映一個排序模型的性能。

  • PR曲線和ROC曲線的區別
      當正負樣本的分佈發生變化時,ROC曲線的形狀基本不變,PR曲線形狀會發生劇烈變化。上圖中PR曲線整體較低就是因爲正負樣本不均衡導致的。因爲比如評分卡中壞客戶只有1%,好客戶有99%,將全部客戶預測爲好客戶,那麼準確率依然有99%。雖然模型整體的準確率很高,但並不代表對壞客戶的分類準確率也高,這裏壞客戶的分類準確率爲0,召回率也爲0。
# PR曲線
def plot_PR(df,score_col,target,plt_size=None):
    """
    df:得分的數據集
    score_col:分數的字段名
    target:目標變量的字段名
    plt_size:繪圖尺寸
    
    return: PR曲線
    """
    total_bad = df[target].sum()
    score_list = list(df[score_col])
    target_list = list(df[target])
    score_unique_list = sorted(set(list(df[score_col])))
    items = sorted(zip(score_list,target_list),key=lambda x:x[0]) 

    precison_list = []
    tpr_list = []
    for score in score_unique_list:
        target_bin = [x[1] for x in items if x[0]<=score]  
        bad_num = sum(target_bin)
        total_num = len(target_bin)
        precison = bad_num/total_num
        tpr = bad_num/total_bad
        precison_list.append(precison)
        tpr_list.append(tpr)
    
    plt.figure(figsize=plt_size)
    plt.title('PR曲線')
    plt.xlabel('查全率')
    plt.ylabel('精確率')
    plt.plot(tpr_list,precison_list,color='tomato',label='PR曲線')
    plt.legend(loc='best')
    return plt.show()

3.得分分佈圖

  理想中最好的評分卡模型應該是將好壞客戶完全區分出來,但是實際中好壞用戶的評分會有一定的重疊,我們要做的儘量減小重疊。
  另外好壞用戶的得分分佈最好都是正態分佈,如果呈雙峯或多峯分佈,那麼很有可能是某個變量的得分過高導致,這樣對評分卡的穩定性會有影響。

# 得分分佈圖
def plot_score_hist(df,target,score_col,plt_size=None,cutoff=None):
    """
    df:數據集
    target:目標變量的字段名
    score_col:最終得分的字段名
    plt_size:圖紙尺寸
    cutoff :劃分拒絕/通過的點
    
    return :好壞用戶的得分分佈圖
    """    
    plt.figure(figsize=plt_size)
    x1 = df[df[target]==1][score_col]
    x2 = df[df[target]==0][score_col]
    sns.kdeplot(x1,shade=True,label='壞用戶',color='hotpink')
    sns.kdeplot(x2,shade=True,label='好用戶',color ='seagreen')
    plt.axvline(x=cutoff)
    plt.legend()
    return plt.show()

4.得分明細表

  按分數段區分,看不同分數段的好壞樣本情況、違約率等指標。

  可以看到高分段的違約概率明顯比低分段低,說明評分卡的效果是顯著的。

# 得分明細表 
def score_info(df,score_col,target,x=None,y=None,step=None):
    """
    df:數據集
    target:目標變量的字段名
    score_col:最終得分的字段名
    x:最小區間的左值
    y:最大區間的右值
    step:區間的分數間隔
    
    return :得分明細表
    """
    df['score_bin'] = pd.cut(df[score_col],bins=np.arange(x,y,step),right=True)
    total = df[target].count()
    bad = df[target].sum()
    good = total - bad
    
    group = df.groupby('score_bin')
    score_info_df = pd.DataFrame()
    score_info_df['用戶數'] = group[target].count()
    score_info_df['壞用戶'] = group[target].sum()
    score_info_df['好用戶'] = score_info_df['用戶數']-score_info_df['壞用戶']
    score_info_df['違約佔比'] = score_info_df['壞用戶']/score_info_df['用戶數']
    score_info_df['累計用戶'] = score_info_df['用戶數'].cumsum()
    score_info_df['壞用戶累計'] = score_info_df['壞用戶'].cumsum()
    score_info_df['好用戶累計'] = score_info_df['好用戶'].cumsum()
    score_info_df['壞用戶累計佔比'] = score_info_df['壞用戶累計']/bad 
    score_info_df['好用戶累計佔比'] = score_info_df['好用戶累計']/good
    score_info_df['累計用戶佔比'] = score_info_df['累計用戶']/total 
    score_info_df['累計違約佔比'] = score_info_df['壞用戶累計']/score_info_df['累計用戶']
    score_info_df = score_info_df.reset_index()
    return score_info_df

5.提升圖和洛倫茲曲線

  假設目前有10000個樣本,壞用戶佔比爲30%,我們做了一個評分卡(分數越低,用戶壞的概率越高),按照評分從低到高劃分成10等份(每個等份用戶數爲1000),計算每等份的壞用戶佔比,如果評分卡效果很好,那麼越靠前的等份裏,包含的壞用戶應該越多,越靠後的等份裏,包含的壞用戶應該要更少。作爲對比,如果不對用戶評分,按照總體壞用戶佔比30%來算,每個等份中壞用戶佔比也是30%。將這兩種方法的每等份壞用戶佔比放在一張柱狀圖上進行對比,就是提升圖。

  將這兩種方法的累計壞用戶佔比放在一張曲線圖上,就是洛倫茲曲線圖。

  此外,洛倫茲曲線可以比較兩個評分卡的優劣,例如下圖中虛線對應的分數假設是600分,那麼在600分這cutoff點下,A和B的拒絕率都是40%,但A可以拒絕掉88%的壞用戶,B只能拒掉78%的壞用戶,說明A評分卡的效果更好。

# 繪製提升圖和洛倫茲曲線
def plot_lifting(df,score_col,target,bins=10,plt_size=None):
    """
    df:數據集,包含最終的得分
    score_col:最終分數的字段名
    target:目標變量名
    bins:分數劃分成的等份數
    plt_size:繪圖尺寸
    
    return:提升圖和洛倫茲曲線
    """
    score_list = list(df[score_col])
    label_list = list(df[target])
    items = sorted(zip(score_list,label_list),key = lambda x:x[0])
    step = round(df.shape[0]/bins,0)
    bad = df[target].sum()
    all_badrate = float(1/bins)
    all_badrate_list = [all_badrate]*bins
    all_badrate_cum = list(np.cumsum(all_badrate_list))
    all_badrate_cum.insert(0,0)
    
    score_bin_list=[]
    bad_rate_list = []
    for i in range(0,bins,1):
        index_a = int(i*step)
        index_b = int((i+1)*step)
        score = [x[0] for x in items[index_a:index_b]]
        tup1 = (min(score),)
        tup2 = (max(score),)
        score_bin = tup1+tup2
        score_bin_list.append(score_bin)
        label_bin = [x[1] for x in items[index_a:index_b]]
        bin_bad = sum(label_bin)
        bin_bad_rate = bin_bad/bad
        bad_rate_list.append(bin_bad_rate)
    bad_rate_cumsum = list(np.cumsum(bad_rate_list))
    bad_rate_cumsum.insert(0,0)
    
    plt.figure(figsize=plt_size)
    x = score_bin_list
    y1 = bad_rate_list
    y2 = all_badrate_list
    y3 = bad_rate_cumsum
    y4 = all_badrate_cum
    plt.subplot(1,2,1)
    plt.title('提升圖')
    plt.xticks(np.arange(bins)+0.15,x,rotation=90)
    bar_width= 0.3
    plt.bar(np.arange(bins),y1,width=bar_width,color='hotpink',label='score_card')
    plt.bar(np.arange(bins)+bar_width,y2,width=bar_width,color='seagreen',label='random')
    plt.legend(loc='best')
    plt.subplot(1,2,2)
    plt.title('洛倫茲曲線圖')
    plt.plot(y3,color='hotpink',label='score_card')
    plt.plot(y4,color='seagreen',label='random')
    plt.xticks(np.arange(bins+1),rotation=0)
    plt.legend(loc='best')
    return plt.show()
plot_lifting(trainData,'score','y',bins=10,plt_size=(10,5))

6.設定cutoff

  cutoff即根據評分劃分通過/拒絕的點,其實就是看不同的閾值下混淆矩陣的情況。設定cutoff時有兩個指標,一個是誤傷率,即FPR,就是好客戶中有多少被預測爲壞客戶而拒絕。另一個是拒絕率,就是這樣劃分的情況下有多少客戶被拒絕。

# 設定cutoff點,衡量有效性
def rule_verify(df,col_score,target,cutoff):
    """
    df:數據集
    target:目標變量的字段名
    col_score:最終得分的字段名    
    cutoff :劃分拒絕/通過的點
    
    return :混淆矩陣
    """
    df['result'] = df.apply(lambda x:30 if x[col_score]<=cutoff else 10,axis=1)
    TP = df[(df['result']==30)&(df[target]==1)].shape[0] 
    FN = df[(df['result']==30)&(df[target]==0)].shape[0] 
    bad = df[df[target]==1].shape[0] 
    good = df[df[target]==0].shape[0] 
    refuse = df[df['result']==30].shape[0] 
    passed = df[df['result']==10].shape[0] 
    
    acc = round(TP/refuse,3) 
    tpr = round(TP/bad,3) 
    fpr = round(FN/good,3) 
    pass_rate = round(refuse/df.shape[0],3) 
    matrix_df = pd.pivot_table(df,index='result',columns=target,aggfunc={col_score:pd.Series.count},values=col_score) 
    
    print('精確率:{}'.format(acc))
    print('查全率:{}'.format(tpr))
    print('誤傷率:{}'.format(fpr))
    print('規則拒絕率:{}'.format(pass_rate))
    return matrix_df

【作者】:Labryant
【原創公衆號】:風控獵人
【簡介】:某創業公司策略分析師,積極上進,努力提升。乾坤未定,你我都是黑馬。
【轉載說明】:轉載請說明出處,謝謝合作!~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章