信用贷款违约预测

项目背景:依据客户的信用卡信息,分期付款信息,信用局信息等预测客户贷款是否会违约。
分析流程:首先对数据进行可视化探索,发现数据中的缺失值和异常值并进行处理,之后对违约用户和非违约用户的属性分布进行可视化分析,探索差异点。通过可视化发现和业务理解构造相应特征工程刻画违约用户画像,最后进行建模预测。

import numpy as np 
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
color = sns.color_palette()
import plotly.offline as py
py.init_notebook_mode(connected=True)
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode(connected=True)
import plotly.graph_objs as go
import pandas as pd
import plotly.offline as offline
offline.init_notebook_mode()

import cufflinks as cf
cf.go_offline()
plt.style.use('fivethirtyeight')

import warnings
warnings.filterwarnings("ignore")

导入数据

app_train=pd.read_csv('application_train.csv')
app_test = pd.read_csv('application_test.csv')

一、主训练集探索

解释一下主要指标的含义:


先了解一下主训练集有哪些字段

app_train.head() 

1. 数据探索

首先我们需要对数据做一些探索, 一是了解数据的缺失值情况、异常值情况,以便做对应的数据清洗。 二是了解一下违约贷款和正常贷款用户画像的区别,加深对业务的理解,为我们后面的数据分析(特征工程)展开打基础

缺失值探索

#定义缺失值检测函数
def missing_values_table(df):
        # 总的缺失值
        mis_val = df.isnull().sum()
        
        # 缺失值占比
        mis_val_percent = 100 * df.isnull().sum() / len(df)
        
        # 将上述值合并成表
        mis_val_table = pd.concat([mis_val, mis_val_percent], axis=1)
        
        # 重命名列名
        mis_val_table_ren_columns = mis_val_table.rename(
        columns = {0 : 'Missing Values', 1 : '% of Total Values'})
        
        # 按缺失值占比降序排列
        mis_val_table_ren_columns = mis_val_table_ren_columns[
            mis_val_table_ren_columns.iloc[:,1] != 0].sort_values(
        '% of Total Values', ascending=False).round(1)
        
        # 显示结果
        print ("Your selected dataframe has " + str(df.shape[1]) + " columns.\n"      
            "There are " + str(mis_val_table_ren_columns.shape[0]) +
              " columns that have missing values.")
        
        return mis_val_table_ren_columns
missing_values = missing_values_table(app_train)
missing_values.head(20)

异常值探索

对主训练集进行一些异常值的探索,这次异常值探索,我们采取最简单的描述统计的方法,即查看特征的均值、极大值、极小值等信息判断是否有异常值。
查看用户年龄的数据分布情况(因为数据中,年龄的数值是负数,反映的是申请贷款前,这个用户活了多少天,所以这里我除了负365做了下处理),发现数据的分布还是比较正常的,最大年龄69岁,最小年龄20岁,没有很异常的数字。

(app_train['DAYS_BIRTH'] / -365).describe()

查看用户的工作时间分布情况发现(同样工作时间也是负数,所以我除了负365),最小值是-1000年,这里的-1000年明显是一个异常数据,没有人的工作时间是负数的,这可能是个异常值。

(app_train['DAYS_EMPLOYED']/-365).describe()

看一下用户受工作时间的数据分布情况,发现所有的异常值都是一个值,365243,对于这个异常值我的理解是它可能是代表缺失值,所以我的选择是将这个异常值用空值去替换,这样可以保留这个信息,又抹去了异常值,替换之后我们再看一下工作时间的分布情况,正常了很多。

app_train['DAYS_EMPLOYED'].plot.hist(title = 'Days Employment Histogram');
app_train['DAYS_EMPLOYED'].replace({365243: np.nan}, inplace = True)
(app_train['DAYS_EMPLOYED']/-365).plot.hist(title = 'Days Employment Histogram');
plt.xlabel('Days Employment');

2、违约用户画像探索

这部分分析的目标主要是查看违约用户和非违约用户的特征分布情况,目标是对违约用户的画像建立一个基本的了解,为后续特征工程打下基础。

#绘制合同数和违约率的柱状图,以函数的形式呈现,方便后面使用
def plot_stats(feature,label_rotation=False,horizontal_layout=True):
    temp = app_train[feature].value_counts()
    df1 = pd.DataFrame({feature: temp.index,'Number of contracts': temp.values})

    cat_perc = app_train[[feature, 'TARGET']].groupby([feature],as_index=False).mean()
    cat_perc.sort_values(by='TARGET', ascending=False, inplace=True)
    
    if(horizontal_layout):
        fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(12,6))
    else:
        fig, (ax1, ax2) = plt.subplots(nrows=2, figsize=(12,14))
    sns.set_color_codes("pastel")
    s = sns.barplot(ax=ax1, x = feature, y="Number of contracts",data=df1)
    if(label_rotation):
        s.set_xticklabels(s.get_xticklabels(),rotation=45)
    
    s = sns.barplot(ax=ax2, x = feature, y='TARGET', order=cat_perc[feature], data=cat_perc)
    if(label_rotation):
        s.set_xticklabels(s.get_xticklabels(),rotation=45)
    plt.ylabel('Percent of target with value 1 [%]', fontsize=10)
    plt.tick_params(axis='both', which='major', labelsize=10)

    plt.show();
    
def plot_distribution(var):
    
    i = 0
    t1 = app_train.loc[app_train['TARGET'] != 0]
    t0 = app_train.loc[app_train['TARGET'] == 0]

    sns.set_style('whitegrid')
    plt.figure()
    fig, ax = plt.subplots(2,2,figsize=(12,12))

    for feature in var:
        i += 1
        plt.subplot(2,2,i)
        sns.kdeplot(t1[feature], bw=0.5,label="TARGET = 1")
        sns.kdeplot(t0[feature], bw=0.5,label="TARGET = 0")
        plt.ylabel('Density plot', fontsize=12)
        plt.xlabel(feature, fontsize=12)
        locs, labels = plt.xticks()
        plt.tick_params(axis='both', which='major', labelsize=12)
    plt.show();

首先来看一下男性和女性用户的违约率情况,发现男性用户违约率更高,男性用户违约率为10%,女性为7%。



下面我们再来看一下违约用户和正常用户的年龄分布情况,因为年龄是连续型变量,和性别不同,所以我们使用分布图去看年龄的分布情况,通过数据分布我们可以看到,违约用户年轻用户分布更多,所以我们可以推断的结论是用户年龄越小,违约的可能性越大

plt.figure(figsize = (10, 8))

# 及时还款的客户的kde图
sns.kdeplot(app_train.loc[app_train['TARGET'] == 0, 'DAYS_BIRTH'] / -365, label = 'target == 0')

# 未及时还款客户的KED图
sns.kdeplot(app_train.loc[app_train['TARGET'] == 1, 'DAYS_BIRTH'] / -365, label = 'target == 1')

plt.xlabel('Age (years)'); plt.ylabel('Density'); plt.title('Distribution of Ages');

对用户的年龄进行分捅,进一步观察观察不同年龄段用户的违约概率,发现确实是用户年龄越小,违约的可能性越高。

age_data = app_train[['TARGET', 'DAYS_BIRTH']]
age_data['YEARS_BIRTH'] = age_data['DAYS_BIRTH'] / -365

# 对用户年龄进行分桶
age_data['YEARS_BINNED'] = pd.cut(age_data['YEARS_BIRTH'], bins = np.linspace(20, 70, num = 11))
age_groups  = age_data.groupby('YEARS_BINNED').mean()
plt.figure(figsize = (8, 8))

# 绘制条形图
plt.bar(age_groups.index.astype(str), 100 * age_groups['TARGET'])

plt.xticks(rotation = 75); plt.xlabel('Age Group (years)'); plt.ylabel('Failure to Repay (%)')
plt.title('Failure to Repay by Age Group');

再来看一下不同贷款类型的违约率情况,对于现金贷款和流动资金循坏贷款,现金贷款的违约率更高。

plot_stats('NAME_CONTRACT_TYPE')

看下用户有没有房和车对违约率的影响,发现没有车和房的人违约率更高,但相差并不是很大。

plot_stats('FLAG_OWN_CAR')
plot_stats('FLAG_OWN_REALTY')


从家庭情况看,申请的用户大多已经结婚,单身和世俗结婚的违约率较高,寡居的违约率最低(世俗结婚:双方不用在教堂举行婚礼,只要有一个证婚人就可以了)

plot_stats('NAME_FAMILY_STATUS',True, True)

看一下子女信息,大部分申请者没有孩子或孩子在3个以下,孩子越多的家庭违约率越高,发现对于有9、11个孩子的家庭违约率达到了100%。

plot_stats('CNT_CHILDREN')

根据申请者的收入类型区分,可以发现休产假和没有工作的人违约率较高,在35%以上,对于这两类人群放款需较为谨慎。

plot_stats('NAME_INCOME_TYPE',True,False)


从职业来看,越相对收入较低、不稳定的职业违约率越高,比如低廉劳动力、司机、理发师,而像会计、高科技员工等具有稳定高收入的职业违约率就较低。

plot_stats('OCCUPATION_TYPE',True, False)


贷款申请人受教育程度大多为中学,学历越低越容易违约。

plot_stats('NAME_EDUCATION_TYPE',True)

从房屋类型上看,租房的以及和父母居住的人群违约率较高,而居住自有房屋以及公寓式办公楼的人群违约率较低。

plot_stats('NAME_HOUSING_TYPE',True)

陪同办理人员对违约率的影响不大,但是有家人配偶孩子陪同的违约率相对较低。

plot_stats('NAME_TYPE_SUITE',True)

二、特征工程

通过对特征的一些理解,尝试做出一些新的特征

  1. CREDIT_INCOME_PERCENT: 贷款金额/客户收入,预期是这个比值越大,说明贷款金额大于用户的收入,用户违约的可能性就越大

  2. ANNUITY_INCOME_PERCENT: 贷款的每年还款金额/客户收入,同上,该比值越大用户还款的压力越大,违约的可能性越大

  3. CREDIT_TERM: 贷款金额/贷款的每年还款金额,贷款的还款周期,猜测还款周期短的贷款,用户的短期压力可能会比较大,违约概率高

  4. DAYS_EMPLOYED_PERCENT: 用户工作时间/用户年龄,工作时间占年龄的百分比,预计越小违约概率越高

  5. INCOME_PER_CHILD:客户收入/孩子数量,客户的收入平均到每个孩子身上,同样的收入,如果这个人的家庭很大,孩子很多,那么他的负担可能比较重,违约的可能性可能更高

  6. HAS_HOUSE_INFORMATION : 根据客户是否有缺失房屋信息设计一个二分类特征,如果未缺失的话是1,缺失的是0

app_train_domain = app_train.copy()
app_test_domain = app_test.copy()

app_train_domain['CREDIT_INCOME_PERCENT'] = app_train_domain['AMT_CREDIT'] / app_train_domain['AMT_INCOME_TOTAL']
app_train_domain['ANNUITY_INCOME_PERCENT'] = app_train_domain['AMT_ANNUITY'] / app_train_domain['AMT_INCOME_TOTAL']
app_train_domain['CREDIT_TERM'] = app_train_domain['AMT_CREDIT'] / app_train_domain['AMT_ANNUITY']
app_train_domain['DAYS_EMPLOYED_PERCENT'] = app_train_domain['DAYS_EMPLOYED'] / app_train_domain['DAYS_BIRTH']
app_train_domain['INCOME_PER_CHILD'] = app_train_domain['AMT_INCOME_TOTAL'] / app_train_domain['CNT_CHILDREN']
app_train_domain['HAS_HOUSE_INFORMATION'] = app_train_domain['COMMONAREA_MEDI'].apply(lambda x:1 if x>0 else 0)

对我们设计出来的连续性特征查看它们在违约用户和非违约用户中的分布情况,可以发现除CREDIT_TERM这个特征外,其他的特征区分度似乎都不是很明显,稍后我们可以放到模型中再看一下效果。

#通过图形查看以下特征效果
plt.figure(figsize = (12, 20))
for i, feature in enumerate(['CREDIT_INCOME_PERCENT', 'ANNUITY_INCOME_PERCENT', 'CREDIT_TERM', 'DAYS_EMPLOYED_PERCENT','INCOME_PER_CHILD']):
    plt.subplot(5, 1, i + 1)
    sns.kdeplot(app_train_domain.loc[app_train_domain['TARGET'] == 0, feature], label = 'target == 0')
    sns.kdeplot(app_train_domain.loc[app_train_domain['TARGET'] == 1, feature], label = 'target == 1')
    plt.title('Distribution of %s by Target Value' % feature)
    plt.xlabel('%s' % feature); plt.ylabel('Density');
    
plt.tight_layout(h_pad = 2.5)



再来看一下通过缺失值设计的这个特征,通过下图我们可以看到,缺失房屋信息的用户违约概率要明显高于未缺失用户,这在我们模型的预测中可以算是一个比较有效的特征了。

plot_stats('HAS_HOUSE_INFORMATION')

对测试集我们做同样的处理

app_test_domain['CREDIT_INCOME_PERCENT'] = app_test_domain['AMT_CREDIT'] / app_test_domain['AMT_INCOME_TOTAL']
app_test_domain['ANNUITY_INCOME_PERCENT'] = app_test_domain['AMT_ANNUITY'] / app_test_domain['AMT_INCOME_TOTAL']
app_test_domain['CREDIT_TERM'] = app_test_domain['AMT_ANNUITY'] / app_test_domain['AMT_CREDIT']
app_test_domain['DAYS_EMPLOYED_PERCENT'] = app_test_domain['DAYS_EMPLOYED'] / app_test_domain['DAYS_BIRTH']
app_test_domain['INCOME_PER_CHILD'] = app_test_domain['AMT_INCOME_TOTAL'] / app_test_domain['CNT_CHILDREN']
app_test_domain['HAS_HOUSE_INFORMATION'] = app_test_domain['COMMONAREA_MEDI'].apply(lambda x:1 if x>0 else 0)

三、建模预测

最后,利用现有的主数据集先进行一次建模预测,模型选择是LGB模型

!pip install lightgbm 
from sklearn.model_selection import KFold
from sklearn.metrics import roc_auc_score
import lightgbm as lgb
import gc
def model(features, test_features, encoding = 'ohe', n_folds = 5):

    
    #提取ID
    train_ids = features['SK_ID_CURR']
    test_ids = test_features['SK_ID_CURR']
    
    # 提取训练集的结果
    labels = features['TARGET']
    
    # 移除ID和target
    features = features.drop(columns = ['SK_ID_CURR', 'TARGET'])
    test_features = test_features.drop(columns = ['SK_ID_CURR'])
    
    
    # One Hot Encoding
    if encoding == 'ohe':
        features = pd.get_dummies(features)
        test_features = pd.get_dummies(test_features)
        
        features, test_features = features.align(test_features, join = 'inner', axis = 1)
        
        cat_indices = 'auto'
    
    # Integer label encoding
    elif encoding == 'le':
        
        label_encoder = LabelEncoder()
        
        cat_indices = []
        
        for i, col in enumerate(features):
            if features[col].dtype == 'object':
                features[col] = label_encoder.fit_transform(np.array(features[col].astype(str)).reshape((-1,)))
                test_features[col] = label_encoder.transform(np.array(test_features[col].astype(str)).reshape((-1,)))

                cat_indices.append(i)
    
    else:
        raise ValueError("Encoding must be either 'ohe' or 'le'")
        
    print('Training Data Shape: ', features.shape)
    print('Testing Data Shape: ', test_features.shape)
    
    feature_names = list(features.columns)
    
    features = np.array(features)
    test_features = np.array(test_features)

    k_fold = KFold(n_splits = n_folds, shuffle = True, random_state = 50)
    
    feature_importance_values = np.zeros(len(feature_names))
    
    test_predictions = np.zeros(test_features.shape[0])
    
    out_of_fold = np.zeros(features.shape[0])
    
    valid_scores = []
    train_scores = []
    
    for train_indices, valid_indices in k_fold.split(features):
        
        train_features, train_labels = features[train_indices], labels[train_indices]
        valid_features, valid_labels = features[valid_indices], labels[valid_indices]
        
        #建模
        model = lgb.LGBMClassifier(n_estimators=1000, objective = 'binary', 
                                   class_weight = 'balanced', learning_rate = 0.05, 
                                   reg_alpha = 0.1, reg_lambda = 0.1, 
                                   subsample = 0.8, n_jobs = -1, random_state = 50)
        
        # 训练模型
        model.fit(train_features, train_labels, eval_metric = 'auc',
                  eval_set = [(valid_features, valid_labels), (train_features, train_labels)],
                  eval_names = ['valid', 'train'], categorical_feature = cat_indices,
                  early_stopping_rounds = 100, verbose = 200)
        
        best_iteration = model.best_iteration_
        
        # 特征重要性
        feature_importance_values += model.feature_importances_ / k_fold.n_splits
        
        # 做预测
        test_predictions += model.predict_proba(test_features, num_iteration = best_iteration)[:, 1] / k_fold.n_splits
        
        out_of_fold[valid_indices] = model.predict_proba(valid_features, num_iteration = best_iteration)[:, 1]
        
        valid_score = model.best_score_['valid']['auc']
        train_score = model.best_score_['train']['auc']
        
        valid_scores.append(valid_score)
        train_scores.append(train_score)
        
        gc.enable()
        del model, train_features, valid_features
        gc.collect()
        
    submission = pd.DataFrame({'SK_ID_CURR': test_ids, 'TARGET': test_predictions})
    
    feature_importances = pd.DataFrame({'feature': feature_names, 'importance': feature_importance_values})
    
    valid_auc = roc_auc_score(labels, out_of_fold)
    
    valid_scores.append(valid_auc)
    train_scores.append(np.mean(train_scores))

    fold_names = list(range(n_folds))
    fold_names.append('overall')
    
    metrics = pd.DataFrame({'fold': fold_names,
                            'train': train_scores,
                            'valid': valid_scores}) 
    
    return submission, feature_importances, metrics
submission, fi, metrics = model(app_train_domain, app_test_domain)
print('Baseline metrics')
print(metrics)

del app_train_domain,app_test_domain
gc.collect

通过lgb自带的函数查看特征的重要性

def plot_feature_importances(df):
    
    df = df.sort_values('importance', ascending = False).reset_index()

    df['importance_normalized'] = df['importance'] / df['importance'].sum()

    plt.figure(figsize = (10, 6))
    ax = plt.subplot()

    ax.barh(list(reversed(list(df.index[:15]))), 
            df['importance_normalized'].head(15), 
            align = 'center', edgecolor = 'k')

    ax.set_yticks(list(reversed(list(df.index[:15]))))
    ax.set_yticklabels(df['feature'].head(15))

    plt.xlabel('Normalized Importance'); plt.title('Feature Importances')
    plt.show()
    
    return df
fi_sorted = plot_feature_importances(fi)

四、利用其他数据集信息

除了主训练集,还有一些其他的训练集来参考,可以从中提取一些新的特征加入。

信用局信息

首先是信用局信息,数据集中的每一行代表的是主训练集中的申请人曾经在其他金融机构申请的贷款信息,可以看到数据集中同样有一列是“SK_ID_CURR',和主训练集中的列一致,我们可以通过这一列去把辅助训练集和主训练集做left join,但需要注意的一点是,一个SK_ID_CURR可能会对应多个SK_ID_BUREAU,即一个申请人如果在其他金融机构曾经有多条贷款信息的话,这里就会有多条记录,因为模型训练每个申请人在数据集中只能有一条记录,所以说我们不能直接把辅助训练集去和主训练集join,一般来说需要去计算一些统计特征(groupby操作)
bureau = pd.read_csv('bureau.csv')
bureau.head()


previous_loan_counts = bureau.groupby('SK_ID_CURR', as_index=False)['SK_ID_BUREAU'].count().rename(columns = {'SK_ID_BUREAU': 'previous_loan_counts'})
previous_loan_counts.head()
#针对每个贷款申请人计算他们在其他金融机构历史上的贷款数量
app_train = app_train.merge(previous_loan_counts, on = 'SK_ID_CURR', how = 'left')

# 缺失的填充为0
app_train['previous_loan_counts'] = app_train['previous_loan_counts'].fillna(0)
app_train.head()

通过查看违约和非违约用户previous_loan_counts的统计属性发现,虽然非违约用户的平均贷款申请数量要略多于违约用户,但差异很小,所以其实很难判断这个特征对预测是否是有用的。

print(app_train[app_train.TARGET==1]['previous_loan_counts'].describe())
print(app_train[app_train.TARGET==0]['previous_loan_counts'].describe())

定义一个查看分布的函数,以后再做出新特征时,我们可以用这个函数快速查看新的特征在违约用户和非违约用户中的分布情况。

def kde_target(var_name, df):
    
    corr = df['TARGET'].corr(df[var_name])
    
    avg_repaid = df.loc[df['TARGET'] == 0, var_name].median()
    avg_not_repaid = df.loc[df['TARGET'] == 1, var_name].median()
    
    plt.figure(figsize = (12, 6))
    
    sns.kdeplot(df.loc[df['TARGET'] == 0, var_name], label = 'TARGET == 0')
    sns.kdeplot(df.loc[df['TARGET'] == 1, var_name], label = 'TARGET == 1')

    plt.xlabel(var_name); plt.ylabel('Density'); plt.title('%s Distribution' % var_name)
    plt.legend();

    print('The correlation between %s and the TARGET is %0.4f' % (var_name, corr))
    print('Median value for loan that was not repaid = %0.4f' % avg_not_repaid)
    print('Median value for loan that was repaid =     %0.4f' % avg_repaid)

连续型变量特征提取

对于连续型变量,我们都可以采用计算它们的统计值来作为特征,为了快速计算出大量统计特征,我们可以结合采用python中的groupby和agg函数。

bureau_agg = bureau.drop(columns = ['SK_ID_BUREAU']).groupby('SK_ID_CURR', as_index = False).agg(['count', 'mean', 'max', 'min', 'sum']).reset_index()
bureau_agg.head()

通过上面的函数我们快速计算出了大量统计特征。

columns = ['SK_ID_CURR']

for var in bureau_agg.columns.levels[0]:
    if var != 'SK_ID_CURR':
        
        for stat in bureau_agg.columns.levels[1][:-1]:
            columns.append('bureau_%s_%s' % (var, stat))
bureau_agg.columns = columns
bureau_agg.head()

同样把新制作的特征和主数据集进行left join

app_train = app_train.merge(bureau_agg, on = 'SK_ID_CURR', how = 'left')
app_train.head()

上面我们制作了大量统计特征,同样,我们也要去考察一下它们对模型预测的能力,为了快速了解各个变量的情况,我们可以查看这些特征和Y值的相关性系数来做一个快速的判断,虽然不够准确,但可以作为一个大概的参考。

new_corrs = []

for col in columns:
    corr = app_train['TARGET'].corr(app_train[col])
    
    new_corrs.append((col, corr))

下方函数输出相关性绝对值前15的特征,可以看到DAYS_CREDIT_MEAN与Y值的正相关性最强,官方说明文档给出的含义是“How many days before current application did client apply for Credit Bureau credit”,即申请人在信用局开户的平均历史天数,大家可以看到因为数据集中这个值是负数,所以含义其实是用户的开户时间越长,历史信用记录的时间越久越不容易违约。

new_corrs = sorted(new_corrs, key = lambda x: abs(x[1]), reverse = True)
new_corrs[:15]


为了能快速把上面计算连续型变量特征的方法应用到其他数据集中,我们定义一个函数来完成上面所有的步骤。

bureau_agg_new = agg_numeric(bureau.drop(columns = ['SK_ID_BUREAU']), group_var = 'SK_ID_CURR', df_name = 'bureau')
bureau_agg_new.head()
#同样再定义一个相关性计算函数
def target_corrs(df):

    corrs = []

    for col in df.columns:
        print(col)
        if col != 'TARGET':
            corr = df['TARGET'].corr(df[col])
            corrs.append((col, corr))
    corrs = sorted(corrs, key = lambda x: abs(x[1]), reverse = True)
    
    return corrs

离散型变量特征提取

首先先把数据集中的离散特征变成哑变量



然后求总数和均值,sum列代表该类别的计数,mean表示其在整体中的占比,通过这种方式我们非常简单的就完成了上述步骤。

categorical_grouped = categorical.groupby('SK_ID_CURR').agg(['sum', 'mean'])
categorical_grouped.head()

做好的特征可以合并到主训练集中

app_train = app_train.merge(categorical_grouped, left_on = 'SK_ID_CURR', right_index = True, how = 'left')
app_train.head()

和连续型变量一样,为了对其他的数据集也能快速进行类似的操作,我们把上面所有对离散型变量的特征提取步骤定义为一个函数。

def count_categorical(df, group_var, df_name):
 
    categorical = pd.get_dummies(df.select_dtypes('object'))
    categorical[group_var] = df[group_var]
    categorical = categorical.groupby(group_var).agg(['sum', 'mean'])
    column_names = []
    
    for var in categorical.columns.levels[0]:
        for stat in ['count', 'count_norm']:
            column_names.append('%s_%s_%s' % (df_name, var, stat))
    
    categorical.columns = column_names
    
    return categorical
#来看一下效果,一个函数完成了上面的所有步骤
bureau_counts = count_categorical(bureau, group_var = 'SK_ID_CURR', df_name = 'bureau')
bureau_counts.head()

整合所有数据集

在上面的分析中,我们以信用局信息为例演示了对于连续型变量和离散型变量的特征提取方法,并且定义了连续型变量特征提取和离散型变量特征提取的函数,下面我们就可以把之前定义的函数应用到所有辅助数据集上。
首先重新读取一遍数据集,把数据集还原到初始状态。

app_train=pd.read_csv('application_train.csv')
app_test = pd.read_csv('application_test.csv')
bureau = pd.read_csv('bureau.csv')
previous_application = pd.read_csv('previous_application.csv')
app_train['CREDIT_INCOME_PERCENT'] = app_train['AMT_CREDIT'] / app_train['AMT_INCOME_TOTAL']
app_train['ANNUITY_INCOME_PERCENT'] = app_train['AMT_ANNUITY'] / app_train['AMT_INCOME_TOTAL']
app_train['CREDIT_TERM'] = app_train['AMT_ANNUITY'] / app_train['AMT_CREDIT']
app_train['DAYS_EMPLOYED_PERCENT'] = app_train['DAYS_EMPLOYED'] / app_train['DAYS_BIRTH']
app_train['INCOME_PER_CHILD'] = app_train['AMT_INCOME_TOTAL'] / app_train['CNT_CHILDREN']
app_train['HAS_HOUSE_INFORMATION'] = app_train['COMMONAREA_MEDI'].apply(lambda x:1 if x>0 else 0)

两个函数完成之前对信用局数据中连续变量和离散变量的特征提取

bureau_counts = count_categorical(bureau, group_var = 'SK_ID_CURR', df_name = 'bureau')
bureau_agg_new = agg_numeric(bureau.drop(columns = ['SK_ID_BUREAU']), group_var = 'SK_ID_CURR', df_name = 'bureau')
bureau_agg_new.head()

给训练集和测试集增加信用局相关特征

app_train = app_train.merge(bureau_counts, on = 'SK_ID_CURR', how = 'left')
app_train = app_train.merge(bureau_agg_new, on = 'SK_ID_CURR', how = 'left')

app_test = app_test.merge(bureau_counts, on = 'SK_ID_CURR', how = 'left')
app_test = app_test.merge(bureau_agg_new, on = 'SK_ID_CURR', how = 'left')

特征筛选

在之前的一系列的特征工程中,我们给训练集和测试集增加了很多新的特征,特征也膨胀到了600多列,在最后建模之前,我们还需要对这些加入的特征再做一次筛选,排除一些具有共线性的特征以提高模型的效果。具体通过计算变量与变量之间的相关系数,来快速移除一些相关性过高的变量,这里可以定义一个阈值是0.8,即移除每一对相关性大于0.8的变量中的其中一个变量。

corrs = app_train.corr()

threshold = 0.8
above_threshold_vars = {}

for col in corrs:
    above_threshold_vars[col] = list(corrs.index[corrs[col] > threshold])
cols_to_remove = []
cols_seen = []
cols_to_remove_pair = []

for key, value in above_threshold_vars.items():
    cols_seen.append(key)
    for x in value:
        if x == key:
            next
        else:
            if x not in cols_seen:
                cols_to_remove.append(x)
                cols_to_remove_pair.append(key)
            
cols_to_remove = list(set(cols_to_remove))
print('Number of columns to remove: ', len(cols_to_remove))

建模预测

submission, fi, metrics = model(train_corrs_removed, test_corrs_removed)
print('metrics')
print(metrics)

你可能感兴趣的:(信用贷款违约预测)