天天看點

計算通過率逾期率并繪制通過率逾期率曲線

在信貸領域AUC&KS名額能提現模型的學習效果,但是在評估模型的相對好壞,以及制定使用方案的時候還是需要通過率&逾期率曲線進行評估模型的。

橫坐标為累計通過率,縱坐标為累計逾期率,此時比較相同的通過率情況下,逾期率越高,曲線位置就越靠近上方。

1、可以了解曲線下的面積越小越好,跟AUC曲線是相反的邏輯。

2、要看細節部分,在某個局部區間通過率下是否局部更好,是否可以交叉使用

3、是否整個曲線都是纏繞的,如果是的話,考慮選擇AUC評估名額吧。

本文代碼分為兩個部分

首先計算通過率&逾期率名額,然後繪制通過率逾期率曲線。

# 簡化版的通過率逾期率
def get_pass_and_overdue_with_list(pred_result_and_real_label: List[Tuple[float, float]]) -> Tuple[List, List]:
    """
    輸入預測機率和實際label,輸出通過率和逾期率序列
    pred_result_and_real_label = data[['ruleset_111_mexico_loan_xgboost_score','first1_overdue3']].values.tolist()
    :param pred_result_and_real_label: 包含預測機率和實際label的元組組成的清單,注意元組的第一個元素為預測機率
    :return: 通過率和逾期率序列
    """
    # 按照元組中的第一個元素,也就是預測值進行升序排列,可變對象已更改
    pred_result_and_real_label.sort(key=lambda x: x[0])

    # 擷取序列的長度
    the_length = len(pred_result_and_real_label)
    print(f"the_length {the_length} ")
    # pass_rate
    pass_rate = []
    # overdue_rate
    overdue_rate = []

    # 周遊過程中每個節點的總逾期數目
    overdue_sum = 0.0

    for i in range(the_length):
        # 實時計算通過率并儲存
        pass_rate.append((i + 1.0) / the_length)
        # 實時計算總逾期數目
        overdue_sum += pred_result_and_real_label[i][1]
        # 使用實時計算的總逾期數目計算目前在總樣本上的逾期率并儲存
        overdue_rate.append(overdue_sum / (i + 1.0))
    return pass_rate, overdue_rate


def get_auc_ks(data, prob_col='prob', label_col='overdue'):
    """
    :param data:
    :param prob_col:
    :param label_col:
    :return:
    """
    from sklearn.metrics import roc_curve, auc
    fpr, tpr, thresholds = roc_curve(data[label_col], data[prob_col])
    auc_value = auc(fpr, tpr)
    ks = max(tpr - fpr)
    return auc_value, ks


def get_pass_and_overdue_by_dataframe(data, prob_col='prob', label_col='overdue', float_round=5):
    """

    :param data:
    :param prob_col:
    :param label_col:
    :param float_round:
    :return:
    """
    from tqdm import tqdm
    data[prob_col] = data[prob_col].apply(lambda x: float(str(x)[0:float_round]))
    data.sort_values(by=prob_col, ascending=True, inplace=True)

    data['flag'] = 1
    data_agg = data.groupby(prob_col).agg({'flag': 'sum', label_col: 'sum'})
    data_agg.reset_index(drop=False, inplace=True)
    probs = list(set(data_agg[prob_col]))
    probs.sort()
    ttl_cnt = data.shape[0]

    pass_rats = []
    overdue_rats = []
    for prob_ in tqdm(probs):
        pass_rat = data_agg[data_agg[prob_col] <= prob_]['flag'].sum() / ttl_cnt
        pass_rats.append(pass_rat)

        cum_overdue = data_agg[data_agg[prob_col] <= prob_][label_col].sum()
        overdue_rat = cum_overdue / data[data[prob_col] <= prob_].shape[0]
        overdue_rats.append(overdue_rat)
    return pass_rats, overdue_rats

def get_auc_ks_pass_overdue_rat(data, prob_col='prob', label_col='overdue', float_round=5, plot=False):
    """

    :param data:
    :param prob_col:
    :param label_col:
    :param float_round:
    :param plot:
    :return:
    """
    print(f'get_auc_ks_pass_overdue_rat prob_col:{prob_col} label_col:{label_col}')
    data = data[[prob_col, label_col]].copy()
    num = data.shape[0]
    auc_value, ks = get_auc_ks(data, prob_col=prob_col, label_col=label_col)
    pass_rats, overdue_rats = get_pass_and_overdue_by_dataframe(data, prob_col=prob_col, label_col=label_col,
                                                                float_round=float_round)
    if plot == True:
        plt.figure(figsize=(6, 6))
        # 開始畫圖 modes_metrics oot_dstx_num,oot_zcfl_num
        plt.title(f'{label_col}|num:{num}')
        plt.plot(pass_rats, overdue_rats, color='green', label=f'auc:{auc_value:.4f}_ks:{ks:.4f}')
        plt.legend()  # 顯示圖例
        plt.xlabel('pass_rat')
        plt.ylabel('cum_overdue')
        plt.grid()
        plt.show()
    return auc_value, ks, pass_rats, overdue_rats, num


def get_auc_ks_pass_overdue_rat_with_data_type(train_data
                                               , data_type='data_type'
                                               , dstx='dstx'
                                               , zcfl='zcfl'
                                               , prob_col='prob'
                                               , label_col='overdue'
                                               , float_round=5
                                               , plot=False):
    """
    計算正常分流&大赦天下的模型名額
    :param train_data: dataframe
    :param data_type: dstx&zcfl
    :param dstx: dstx的标記
    :param zcfl: zcfl的标記
    :param prob_col: 預測機率值列
    :param label_col: 标簽列
    :param float_round: 保留精度
    :param plot: 是否繪制通過預期曲線
    :return:
    """
    data = train_data[train_data[data_type] == dstx][[prob_col, label_col]].copy()
    oot_dstx_auc_value, oot_dstx_ks, oot_dstx_pass_rats, oot_dstx_overdue_rats, oot_dstx_num = get_auc_ks_pass_overdue_rat(
          data\
        , prob_col=prob_col\
        , label_col=label_col\
        , float_round=5\
        , plot=False)

    data = train_data[train_data[data_type] == zcfl][[prob_col, label_col]].copy()
    oot_zcfl_auc_value, oot_zcfl_ks, oot_zcfl_pass_rats, oot_zcfl_overdue_rats, oot_zcfl_num = get_auc_ks_pass_overdue_rat(
        data \
        , prob_col=prob_col \
        , label_col=label_col \
        , float_round=5 \
        , plot=False)

    if plot:
        fig, axs = plt.subplots(2, 1)
        plt.rcParams['figure.figsize'] = (9, 12.0)
        axs[0].plot(oot_dstx_pass_rats, oot_dstx_overdue_rats)
        axs[0].set_ylim(0, 0.5)
        axs[0].set_xlim(0, 1)
        axs[0].set_title(f'oot dstx nocv fpd3|num:{oot_dstx_num}')
        axs[0].set_xlabel('pass_rat')
        axs[0].set_ylabel('cum_overdue')  # ,fontproperties = font
        axs[0].grid(True)
        axs[0].legend(["auc:%.4f ks:%.4f" % (oot_dstx_auc_value, oot_dstx_ks)], loc="lower left")

        axs[1].plot(oot_zcfl_pass_rats, oot_zcfl_overdue_rats)
        axs[1].set_ylim(0, 0.5)
        axs[1].set_xlim(0, 1)
        axs[1].set_title(f'oot zcfl nocv fpd3|num:{oot_zcfl_num}')

        axs[1].set_xlabel('pass_rat')
        axs[1].set_ylabel('cum_overdue')  # ,fontproperties = font
        axs[1].grid(True)

        axs[1].legend(["auc:%.4f ks:%.4f" % (oot_zcfl_auc_value, oot_zcfl_ks)], loc="lower left")

    return oot_dstx_auc_value, oot_dstx_ks, oot_dstx_pass_rats, oot_dstx_overdue_rats, oot_dstx_num, \
           oot_zcfl_auc_value, oot_zcfl_ks, oot_zcfl_pass_rats, oot_zcfl_overdue_rats, oot_zcfl_num      

繪制通過率逾期率曲線

def plot_dstx_zcfl_pass_overdue(modes_metrics, label='fpd3', line_desc=None,
                                 colors=['skyblue', 'green', 'blue', 'y', 'r']):
     """
     一張圖繪制兩個子圖,分别繪制zcfl & dstx的通過率逾期率
     :param modes_metrics: each row contains oot_dstx_auc_value, oot_dstx_ks, oot_dstx_pass_rats,  oot_dstx_overdue_rats,
      oot_dstx_num, oot_zcfl_auc_value, oot_zcfl_ks, oot_zcfl_pass_rats, oot_zcfl_overdue_rats, oot_zcfl_num
     :param label: overdue label
     :param line_desc: 圖例描述
     :param colors: 圖例顔色
     :return: None
     """
     import matplotlib.font_manager as fm
     font_size=12
     # 設定family、size 
     font = fm.FontProperties(fname='/data/simhei.ttf', size=font_size)
     plt.rcParams['figure.figsize'] = (9, 12.0)
     fig, axs = plt.subplots(2, 1)
     if line_desc is None:
         line_desc = ['']*len(modes_metrics)
         
     for i in range(len(modes_metrics)):
         oot_dstx_auc_value, oot_dstx_ks, oot_dstx_pass_rats, oot_dstx_overdue_rats, oot_dstx_num = modes_metrics[i][0:5]
         axs[0].plot(oot_dstx_pass_rats, oot_dstx_overdue_rats, color=colors[i],
                     label=f'auc:{oot_dstx_auc_value:.4f}|ks:{oot_dstx_ks:.4f}_{line_desc[i]}' )
     axs[0].set_ylim(0, 0.4)
     axs[0].set_xlim(0, 1)
     axs[0].set_title(f'oot dstx nocv {label}|num:{oot_dstx_num}')
     axs[0].set_xlabel('pass_rat')
     axs[0].set_ylabel('cum_overdue', fontproperties=font)
     axs[0].legend(loc="lower right",prop=font)  # 顯示圖例
     axs[0].grid(True)    for i in range(len(modes_metrics)):
         oot_dstx_auc_value, oot_dstx_ks, oot_dstx_pass_rats, oot_dstx_overdue_rats, oot_dstx_num = modes_metrics[i][5:]
         axs[1].plot(oot_dstx_pass_rats, oot_dstx_overdue_rats, color=colors[i],
                     label=f'auc:{oot_dstx_auc_value:.4f}|ks:{oot_dstx_ks:.4f}_{line_desc[i]}' )
     axs[1].set_ylim(0, 0.4)
     axs[1].set_xlim(0, 1)
     axs[1].set_title(f'oot zcfl nocv {label}|num:{oot_dstx_num}')
     axs[1].set_xlabel('pass_rat')
     axs[1].set_ylabel('cum_overdue' , fontproperties = font)
     axs[1].legend(loc="lower right",prop=font)  # 顯示圖例
     axs[1].grid(True)