• 互联网金融借款违约预测


    本项目主要关注实现,数据分析、特征工程涉及较少,而且数据量较大,并没有进行多次调参。
    另外,由于数据的分类极其不平衡,本项目尝试使用SMOTE增加偏少类的样本数量。

    %matplotlib inline
    import matplotlib.pyplot as plt
    import pandas as pd
    from dateutil.parser import parse
    import datetime
    import numpy as np
    
    path = ''
    
    lc = pd.read_csv(path + 'LC.csv')
    lp = pd.read_csv(path + 'LP.csv')
    
    lc.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 328553 entries, 0 to 328552
    Data columns (total 21 columns):
    ListingId    328553 non-null int64
    借款金额         328553 non-null int64
    借款期限         328553 non-null int64
    借款利率         328553 non-null float64
    借款成功日期       328553 non-null object
    初始评级         328553 non-null object
    借款类型         328553 non-null object
    是否首标         328553 non-null object
    年龄           328553 non-null int64
    性别           328553 non-null object
    手机认证         328553 non-null object
    户口认证         328553 non-null object
    视频认证         328553 non-null object
    学历认证         328553 non-null object
    征信认证         328553 non-null object
    淘宝认证         328553 non-null object
    历史成功借款次数     328553 non-null int64
    历史成功借款金额     328553 non-null float64
    总待还本金        328553 non-null float64
    历史正常还款期数     328553 non-null int64
    历史逾期还款期数     328553 non-null int64
    dtypes: float64(3), int64(7), object(11)
    memory usage: 52.6+ MB
    
    lc.head().T
    
    0 1 2 3 4
    ListingId 126541 133291 142421 149711 152141
    借款金额 18000 9453 27000 25000 20000
    借款期限 12 12 24 12 6
    借款利率 18 20 20 18 16
    初始评级 C D E C C
    借款类型 其他 其他 普通 其他 电商
    是否首标
    年龄 35 34 41 34 24
    性别
    手机认证 成功认证 未成功认证 成功认证 成功认证 成功认证
    户口认证 未成功认证 成功认证 未成功认证 成功认证 成功认证
    视频认证 成功认证 未成功认证 未成功认证 成功认证 成功认证
    学历认证 未成功认证 未成功认证 未成功认证 未成功认证 未成功认证
    征信认证 未成功认证 未成功认证 未成功认证 未成功认证 未成功认证
    淘宝认证 未成功认证 未成功认证 未成功认证 未成功认证 未成功认证
    历史成功借款次数 11 4 5 6 13
    历史成功借款金额 40326 14500 21894 36190 77945
    总待还本金 8712.73 7890.64 11726.3 9703.41 0
    历史正常还款期数 57 13 25 41 118
    历史逾期还款期数 16 1 3 1 14
    lp.info()
    
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 3203276 entries, 0 to 3203275
    Data columns (total 10 columns):
    ListingId     int64
    期数            int64
    还款状态          int64
    应还本金          float64
    应还利息          float64
    剩余本金          float64
    剩余利息          float64
    到期日期          object
    还款日期          object
    recorddate    object
    dtypes: float64(4), int64(3), object(3)
    memory usage: 244.4+ MB
    
    lp.head()
    
    ListingId 期数 还款状态 应还本金 应还利息 剩余本金 剩余利息 到期日期 还款日期 recorddate
    0 126541 1 1 1380.23 270.00 0.0 0.0 2015-06-04 2015-06-04 2017-02-22
    1 126541 2 1 1400.94 249.29 0.0 0.0 2015-07-04 2015-07-04 2017-02-22
    2 126541 3 1 1421.95 228.28 0.0 0.0 2015-08-04 2015-08-04 2017-02-22
    3 126541 4 1 1443.28 206.95 0.0 0.0 2015-09-04 2015-09-04 2017-02-22
    4 126541 5 1 1464.93 185.30 0.0 0.0 2015-10-04 2015-10-04 2017-02-22
    # 提前去掉不需要的列
    lc.drop('借款成功日期', axis=1, inplace=True)
    

    合成label

    目标是预测借款三个月内是否会逾期30天及以上。这只有在第一期和第二期逾期30天及以上时才会出现。下面开始提取label。

    # 第1、2期未还款的提取出来。
    u_data = lp.copy()
    u12_late = u_data[((u_data['期数'] == 1) | (u_data['期数'] == 2)) 
                      & ((u_data['还款状态'] == 0) | (u_data['还款状态'] == 2) | (u_data['还款状态'] == 4))]
    
    # 将没有还款日期的日期改为'2050-01-01'
    u12_late['还款日期'] = u12_late['还款日期'].apply(lambda x : x if x !='\N' else '2050-01-01')
    
    # 计算逾期天数
    u12_late['逾期天数'] = u12_late['还款日期'].apply(parse)-u12_late['到期日期'].apply(parse)
    
    # 提取出违约名单
    u_label = u12_late[u12_late['逾期天数'] > datetime.timedelta(30)]
    id_tar = u_label.ListingId.unique()
    label_id = pd.DataFrame({'ListingId':id_tar,'label':np.ones(id_tar.size)})
    
    # 联接,并删除不再需要的ListingId
    all_data = pd.merge(lc,label_id,how='outer',on='ListingId')
    all_data.drop('ListingId', axis=1, inplace=True)
    
    # 后面就不需要lp了
    del lp, u_data
    
    all_data.head()
    
    借款金额 借款期限 借款利率 初始评级 借款类型 是否首标 年龄 性别 手机认证 户口认证 视频认证 学历认证 征信认证 淘宝认证 历史成功借款次数 历史成功借款金额 总待还本金 历史正常还款期数 历史逾期还款期数 label
    0 18000 12 18.0 C 其他 35 成功认证 未成功认证 成功认证 未成功认证 未成功认证 未成功认证 11 40326.0 8712.73 57 16 NaN
    1 9453 12 20.0 D 其他 34 未成功认证 成功认证 未成功认证 未成功认证 未成功认证 未成功认证 4 14500.0 7890.64 13 1 NaN
    2 27000 24 20.0 E 普通 41 成功认证 未成功认证 未成功认证 未成功认证 未成功认证 未成功认证 5 21894.0 11726.32 25 3 NaN
    3 25000 12 18.0 C 其他 34 成功认证 成功认证 成功认证 未成功认证 未成功认证 未成功认证 6 36190.0 9703.41 41 1 NaN
    4 20000 6 16.0 C 电商 24 成功认证 成功认证 成功认证 未成功认证 未成功认证 未成功认证 13 77945.0 0.00 118 14 NaN
    # 缺失值分析
    check_null = all_data.isnull()
        .sum(axis=0)
        .sort_values(ascending=False)/float(len(lc))
    check_null
    
    label       0.85136
    历史逾期还款期数    0.00000
    借款期限        0.00000
    借款利率        0.00000
    初始评级        0.00000
    借款类型        0.00000
    是否首标        0.00000
    年龄          0.00000
    性别          0.00000
    手机认证        0.00000
    户口认证        0.00000
    视频认证        0.00000
    学历认证        0.00000
    征信认证        0.00000
    淘宝认证        0.00000
    历史成功借款次数    0.00000
    历史成功借款金额    0.00000
    总待还本金       0.00000
    历史正常还款期数    0.00000
    借款金额        0.00000
    dtype: float64
    
    # 对label,NaN的填0
    all_data['label'].fillna(value=0,inplace=True)
    
    # 数据探索与分析
    # 大致观察数据统计指标情况,发现一些需要修正的outlier。另外,label的类型极度不平衡,在后面将采用smote扩展样本。
    all_data.describe().T
    
    count mean std min 25% 50% 75% max
    借款金额 328553.0 4423.816906 11219.664024 100.0 2033.0 3397.00 5230.00 500000.00
    借款期限 328553.0 10.213594 2.780444 1.0 6.0 12.00 12.00 24.00
    借款利率 328553.0 20.601439 1.772408 6.5 20.0 20.00 22.00 24.00
    年龄 328553.0 29.143042 6.624286 17.0 24.0 28.00 33.00 56.00
    历史成功借款次数 328553.0 2.323159 2.922361 0.0 0.0 2.00 3.00 649.00
    历史成功借款金额 328553.0 8785.856771 35027.363482 0.0 0.0 5000.00 10355.00 7405926.00
    总待还本金 328553.0 3721.665361 8626.061205 0.0 0.0 2542.41 5446.81 1172652.87
    历史正常还款期数 328553.0 9.947658 14.839899 0.0 0.0 5.00 13.00 2507.00
    历史逾期还款期数 328553.0 0.423250 1.595681 0.0 0.0 0.00 0.00 60.00
    label 328553.0 0.148640 0.355733 0.0 0.0 0.00 0.00 1.00

    特征工程

    # 分开attributes和label
    y = all_data["label"].copy()
    all_data = all_data.drop('label', axis=1)
    
    # 特征的合并
    # 1.加权利率 = 借款期限 * 借款的利率
    # 2.还款期数比 = 历史正常还款期数 / (历史正常还款期数 + 逾期期数)
    # 3.未还款比 = 总待还本金/历史成功借款金额
    # 4.剩余还款压力 = 总待还本金 / 借款金额
    all_data['加权利率'] = all_data['借款期限'] * all_data['借款利率']
    all_data['还款期数比'] = all_data['历史正常还款期数'] / (all_data['历史正常还款期数'] + all_data['历史逾期还款期数'])
    all_data['未还款比'] = all_data['总待还本金'] / all_data['历史成功借款金额']
    all_data['剩余还款压力'] = all_data['总待还本金'] / all_data['借款金额']
    # fillna
    median = all_data[['还款期数比','未还款比','剩余还款压力']].mean()
    all_data.fillna(median, inplace=True)
    all_data.head().T
    
    0 1 2 3 4
    借款金额 18000 9453 27000 25000 20000
    借款期限 12 12 24 12 6
    借款利率 18 20 20 18 16
    初始评级 C D E C C
    借款类型 其他 其他 普通 其他 电商
    是否首标
    年龄 35 34 41 34 24
    性别
    手机认证 成功认证 未成功认证 成功认证 成功认证 成功认证
    户口认证 未成功认证 成功认证 未成功认证 成功认证 成功认证
    视频认证 成功认证 未成功认证 未成功认证 成功认证 成功认证
    学历认证 未成功认证 未成功认证 未成功认证 未成功认证 未成功认证
    征信认证 未成功认证 未成功认证 未成功认证 未成功认证 未成功认证
    淘宝认证 未成功认证 未成功认证 未成功认证 未成功认证 未成功认证
    历史成功借款次数 11 4 5 6 13
    历史成功借款金额 40326 14500 21894 36190 77945
    总待还本金 8712.73 7890.64 11726.3 9703.41 0
    历史正常还款期数 57 13 25 41 118
    历史逾期还款期数 16 1 3 1 14
    加权利率 216 240 480 216 96
    还款期数比 0.780822 0.928571 0.892857 0.97619 0.893939
    未还款比 0.216057 0.544182 0.535595 0.268124 0
    剩余还款压力 0.484041 0.834723 0.434308 0.388136 0
    all_data.hist(bins=100, figsize=(20,15))
    plt.show()
    

    SMOTENC

    # 首先numerise cat数据
    # 划分每一列类型
    row_ordial_attribs = ['初始评级']
    cat_attribs = ['借款类型', '是否首标', '性别', '手机认证', '户口认证', '视频认证', '学历认证', '征信认证', '淘宝认证']
    num_attribs = list(all_data.drop(list(row_ordial_attribs + cat_attribs), axis=1)) 
    
    # ordinarise
    # 下面方法自定义mapper
    # key = list(np.sort(all_data['初始评级'].unique()))
    # mapper = dict(zip(key, range(len(key))))
    # all_data['初始评级'].replace(mapper, inplace=True)
    
    # auto方式
    for col_name in row_ordial_attribs + cat_attribs:
        if col_name in row_ordial_attribs:
            all_data[col_name] = pd.factorize(all_data[col_name],sort=True)[0]
        else:
            all_data[col_name] = pd.factorize(all_data[col_name])[0]
    
    # one-hot
    # cat_attribs = ['借款类型', '是否首标', '性别', '手机认证', '户口认证', '视频认证', '学历认证', '征信认证', '淘宝认证']
    # all_data = pd.get_dummies(all_data, columns=cat_attribs)
    
    # SMOTE抽样。由于label极度不平衡。
    from imblearn.over_sampling import SMOTENC
    
    cols = list(all_data.columns)
    cat_index = []
    for col in row_ordial_attribs + cat_attribs:
        cat_index.append(cols.index(col))
        
    sm = SMOTENC(sampling_strategy=0.2, categorical_features=cat_index ,k_neighbors=5,n_jobs=4)
    X_res, y_res = sm.fit_sample(all_data, y) # 返回ndarray
    
    sm = SMOTENC(categorical_features=[1],k_neighbors=2)
    
    # 合成回df
    X_col = all_data.columns
    X_data = pd.DataFrame(X_res, columns=X_col)
    y_data = pd.DataFrame(y_res, columns=['label'])
    resample_data = pd.concat([X_data, y_data], axis=1)
    
    # 特征的离散化
    # 借款金额,借款期限,借款利率,年龄
    
    def money_2_cat(money):
        if money <= 10000:
            return money//3000
        elif money <= 100000:
            return money//10000 + 3
        elif money <= 1000000:
            return money//100000 + 13
        else:
            return 23
    
    
    def due_2_cat(day):
        if day <= 6:
            return 0
        elif day <= 12:
            return 1
        else:
            return 2
    
    
    def rate_2_cat(rate):
        if rate <= 13:
            return 0
        elif rate <= 17:
            return 1
        elif rate <= 21:
            return 2
        else:
            return 3
    
    
    def age_2_cat(age):
        if age <= 22:
            return 0
        elif age <= 25:
            return 1
        elif age <= 30:
            return 2
        elif age < 40:
            return 3
        else:
            return 4
    
    
    resample_data['借款金额'] = resample_data['借款金额'].apply(money_2_cat)
    resample_data['借款期限'] = resample_data['借款期限'].apply(due_2_cat)
    resample_data['借款利率'] = resample_data['借款利率'].apply(rate_2_cat)
    resample_data['年龄'] = resample_data['年龄'].apply(age_2_cat)
    
    final_data = resample_data.sample(frac=1)
    
    final_data.to_csv(path+'final_data.csv', index=None, encoding='utf-8')
    
    final_data.head().T
    
    20332 99979 88800 311869 289215
    借款金额 1.000000 0.000000 0.000000 1.000000 0.000000
    借款期限 0.000000 1.000000 1.000000 1.000000 0.000000
    借款利率 1.000000 3.000000 3.000000 3.000000 2.000000
    初始评级 1.000000 3.000000 3.000000 2.000000 2.000000
    借款类型 1.000000 3.000000 1.000000 1.000000 3.000000
    是否首标 0.000000 0.000000 0.000000 0.000000 0.000000
    年龄 3.000000 3.000000 0.000000 1.000000 1.000000
    性别 1.000000 0.000000 0.000000 0.000000 0.000000
    手机认证 0.000000 1.000000 1.000000 0.000000 1.000000
    户口认证 0.000000 0.000000 0.000000 0.000000 0.000000
    视频认证 1.000000 1.000000 0.000000 1.000000 1.000000
    学历认证 0.000000 0.000000 0.000000 0.000000 1.000000
    征信认证 0.000000 0.000000 0.000000 0.000000 0.000000
    淘宝认证 0.000000 0.000000 0.000000 0.000000 0.000000
    历史成功借款次数 1.000000 3.000000 2.000000 5.000000 3.000000
    历史成功借款金额 7500.000000 5975.000000 3935.000000 14544.000000 5149.000000
    总待还本金 5704.680000 4340.640000 3199.800000 4132.500000 3298.040000
    历史正常还款期数 3.000000 8.000000 5.000000 33.000000 10.000000
    历史逾期还款期数 0.000000 0.000000 0.000000 3.000000 0.000000
    加权利率 96.000000 264.000000 264.000000 264.000000 120.000000
    还款期数比 1.000000 1.000000 1.000000 0.916667 1.000000
    未还款比 0.760624 0.726467 0.813164 0.284138 0.640520
    剩余还款压力 1.503209 5.466801 2.461385 0.826500 4.704765
    label 0.000000 0.000000 0.000000 1.000000 0.000000
    final_data = pd.read_csv(path+'final_data.csv')
    

    模型训练与调优

    Y = final_data['label'].copy()
    X = final_data.drop('label', axis=1)
    
    from sklearn.model_selection import GridSearchCV
    from sklearn.ensemble import GradientBoostingClassifier
    
    param_grid = [
        {'n_estimators':[50], 'learning_rate': [0.1], 'min_samples_leaf': [100], 'min_samples_split': [100], 'max_depth':[7]}
      ]
    
    gbc = GradientBoostingClassifier()
    grid_search = GridSearchCV(gbc, param_grid, cv=4,
                               scoring='roc_auc', 
                               n_jobs=4,
                               return_train_score=True)
    grid_search.fit(X, Y)
    
    # 查看结果
    grid_search.best_params_
    
    grid_search.best_estimator_
    
    cvres = grid_search.cv_results_
    for mean_score, params in zip(cvres["mean_test_score"], cvres["params"]):
        print(mean_score, params)
        
    pd.DataFrame(grid_search.cv_results_).T
    
    0.7771415886609713 {'learning_rate': 0.1, 'max_depth': 7, 'min_samples_leaf': 100, 'min_samples_split': 100, 'n_estimators': 50}
    
    0
    mean_fit_time 170.069
    std_fit_time 1.23085
    mean_score_time 0.262221
    std_score_time 0.0795939
    param_learning_rate 0.1
    param_max_depth 7
    param_min_samples_leaf 100
    param_min_samples_split 100
    param_n_estimators 50
    params {'learning_rate': 0.1, 'max_depth': 7, 'min_sa...
    split0_test_score 0.776663
    split1_test_score 0.776003
    split2_test_score 0.777184
    split3_test_score 0.778716
    mean_test_score 0.777142
    std_test_score 0.0010006
    rank_test_score 1
    split0_train_score 0.791537
    split1_train_score 0.792024
    split2_train_score 0.791077
    split3_train_score 0.79236
    mean_train_score 0.79175
    std_train_score 0.0004864
  • 相关阅读:
    javascript之instanceof原理
    x86之描述符表寄存器
    Mac之DTerm
    C的一些特性
    Mac i386 Operands and Addressing Modes
    shell之条件测试
    linux之dup&dup2
    javascript之this
    x86之段描述符
    进制转换
  • 原文地址:https://www.cnblogs.com/code2one/p/10293531.html
Copyright © 2020-2023  润新知