信用貸款違約預(yù)測

項目背景:依據(jù)客戶的信用卡信息拾徙,分期付款信息玉罐,信用局信息等預(yù)測客戶貸款是否會違約腋逆。
分析流程:首先對數(shù)據(jù)進行可視化探索偶宫,發(fā)現(xiàn)數(shù)據(jù)中的缺失值和異常值并進行處理,之后對違約用戶和非違約用戶的屬性分布進行可視化分析毕泌,探索差異點喝检。通過可視化發(fā)現(xiàn)和業(yè)務(wù)理解構(gòu)造相應(yīng)特征工程刻畫違約用戶畫像,最后進行建模預(yù)測撼泛。

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")

導(dǎo)入數(shù)據(jù)

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

一挠说、主訓(xùn)練集探索

解釋一下主要指標的含義:


先了解一下主訓(xùn)練集有哪些字段

app_train.head() 

1. 數(shù)據(jù)探索

首先我們需要對數(shù)據(jù)做一些探索, 一是了解數(shù)據(jù)的缺失值情況愿题、異常值情況损俭,以便做對應(yīng)的數(shù)據(jù)清洗。 二是了解一下違約貸款和正常貸款用戶畫像的區(qū)別潘酗,加深對業(yè)務(wù)的理解杆兵,為我們后面的數(shù)據(jù)分析(特征工程)展開打基礎(chǔ)

缺失值探索

#定義缺失值檢測函數(shù)
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)
        
        # 顯示結(jié)果
        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)

異常值探索

對主訓(xùn)練集進行一些異常值的探索,這次異常值探索仔夺,我們采取最簡單的描述統(tǒng)計的方法拧咳,即查看特征的均值、極大值囚灼、極小值等信息判斷是否有異常值骆膝。
查看用戶年齡的數(shù)據(jù)分布情況(因為數(shù)據(jù)中祭衩,年齡的數(shù)值是負數(shù),反映的是申請貸款前阅签,這個用戶活了多少天掐暮,所以這里我除了負365做了下處理),發(fā)現(xiàn)數(shù)據(jù)的分布還是比較正常的,最大年齡69歲政钟,最小年齡20歲路克,沒有很異常的數(shù)字。

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

查看用戶的工作時間分布情況發(fā)現(xiàn)(同樣工作時間也是負數(shù)养交,所以我除了負365)精算,最小值是-1000年,這里的-1000年明顯是一個異常數(shù)據(jù)碎连,沒有人的工作時間是負數(shù)的灰羽,這可能是個異常值。

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

看一下用戶受工作時間的數(shù)據(jù)分布情況鱼辙,發(fā)現(xiàn)所有的異常值都是一個值廉嚼,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葛闷、違約用戶畫像探索

這部分分析的目標主要是查看違約用戶和非違約用戶的特征分布情況憋槐,目標是對違約用戶的畫像建立一個基本的了解,為后續(xù)特征工程打下基礎(chǔ)孵运。

#繪制合同數(shù)和違約率的柱狀圖秦陋,以函數(shù)的形式呈現(xiàn)蔓彩,方便后面使用
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();

首先來看一下男性和女性用戶的違約率情況治笨,發(fā)現(xiàn)男性用戶違約率更高,男性用戶違約率為10%赤嚼,女性為7%旷赖。



下面我們再來看一下違約用戶和正常用戶的年齡分布情況,因為年齡是連續(xù)型變量更卒,和性別不同等孵,所以我們使用分布圖去看年齡的分布情況,通過數(shù)據(jù)分布我們可以看到蹂空,違約用戶年輕用戶分布更多俯萌,所以我們可以推斷的結(jié)論是用戶年齡越小果录,違約的可能性越大

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');

對用戶的年齡進行分捅,進一步觀察觀察不同年齡段用戶的違約概率咐熙,發(fā)現(xiàn)確實是用戶年齡越小弱恒,違約的可能性越高。

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');

再來看一下不同貸款類型的違約率情況棋恼,對于現(xiàn)金貸款和流動資金循壞貸款返弹,現(xiàn)金貸款的違約率更高。

plot_stats('NAME_CONTRACT_TYPE')

看下用戶有沒有房和車對違約率的影響爪飘,發(fā)現(xiàn)沒有車和房的人違約率更高义起,但相差并不是很大。

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


從家庭情況看师崎,申請的用戶大多已經(jīng)結(jié)婚默终,單身和世俗結(jié)婚的違約率較高,寡居的違約率最低(世俗結(jié)婚:雙方不用在教堂舉行婚禮,只要有一個證婚人就可以了)

plot_stats('NAME_FAMILY_STATUS',True, True)

看一下子女信息抡诞,大部分申請者沒有孩子或孩子在3個以下穷蛹,孩子越多的家庭違約率越高,發(fā)現(xiàn)對于有9昼汗、11個孩子的家庭違約率達到了100%肴熏。

plot_stats('CNT_CHILDREN')

根據(jù)申請者的收入類型區(qū)分,可以發(fā)現(xiàn)休產(chǎn)假和沒有工作的人違約率較高顷窒,在35%以上蛙吏,對于這兩類人群放款需較為謹慎。

plot_stats('NAME_INCOME_TYPE',True,False)


從職業(yè)來看鞋吉,越相對收入較低鸦做、不穩(wěn)定的職業(yè)違約率越高,比如低廉勞動力谓着、司機泼诱、理發(fā)師,而像會計赊锚、高科技員工等具有穩(wěn)定高收入的職業(yè)違約率就較低治筒。

plot_stats('OCCUPATION_TYPE',True, False)


貸款申請人受教育程度大多為中學(xué),學(xué)歷越低越容易違約舷蒲。

plot_stats('NAME_EDUCATION_TYPE',True)

從房屋類型上看耸袜,租房的以及和父母居住的人群違約率較高,而居住自有房屋以及公寓式辦公樓的人群違約率較低牲平。

plot_stats('NAME_HOUSING_TYPE',True)

陪同辦理人員對違約率的影響不大堤框,但是有家人配偶孩子陪同的違約率相對較低。

plot_stats('NAME_TYPE_SUITE',True)

二、特征工程

通過對特征的一些理解蜈抓,嘗試做出一些新的特征

  1. CREDIT_INCOME_PERCENT: 貸款金額/客戶收入启绰,預(yù)期是這個比值越大,說明貸款金額大于用戶的收入沟使,用戶違約的可能性就越大

  2. ANNUITY_INCOME_PERCENT: 貸款的每年還款金額/客戶收入酬土,同上,該比值越大用戶還款的壓力越大格带,違約的可能性越大

  3. CREDIT_TERM: 貸款金額/貸款的每年還款金額撤缴,貸款的還款周期,猜測還款周期短的貸款叽唱,用戶的短期壓力可能會比較大屈呕,違約概率高

  4. DAYS_EMPLOYED_PERCENT: 用戶工作時間/用戶年齡,工作時間占年齡的百分比棺亭,預(yù)計越小違約概率越高

  5. INCOME_PER_CHILD:客戶收入/孩子數(shù)量虎眨,客戶的收入平均到每個孩子身上,同樣的收入镶摘,如果這個人的家庭很大嗽桩,孩子很多,那么他的負擔(dān)可能比較重,違約的可能性可能更高

  6. HAS_HOUSE_INFORMATION : 根據(jù)客戶是否有缺失房屋信息設(shè)計一個二分類特征,如果未缺失的話是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)

對我們設(shè)計出來的連續(xù)性特征查看它們在違約用戶和非違約用戶中的分布情況,可以發(fā)現(xiàn)除CREDIT_TERM這個特征外扑庞,其他的特征區(qū)分度似乎都不是很明顯,稍后我們可以放到模型中再看一下效果拒逮。

#通過圖形查看以下特征效果
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)



再來看一下通過缺失值設(shè)計的這個特征罐氨,通過下圖我們可以看到,缺失房屋信息的用戶違約概率要明顯高于未缺失用戶滩援,這在我們模型的預(yù)測中可以算是一個比較有效的特征了栅隐。

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)

三、建模預(yù)測

最后玩徊,利用現(xiàn)有的主數(shù)據(jù)集先進行一次建模預(yù)測租悄,模型選擇是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']
    
    # 提取訓(xùn)練集的結(jié)果
    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)
        
        # 訓(xùn)練模型
        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
        
        # 做預(yù)測
        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自帶的函數(shù)查看特征的重要性

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)

四、利用其他數(shù)據(jù)集信息

除了主訓(xùn)練集佣赖,還有一些其他的訓(xùn)練集來參考恰矩,可以從中提取一些新的特征加入记盒。

信用局信息

首先是信用局信息憎蛤,數(shù)據(jù)集中的每一行代表的是主訓(xùn)練集中的申請人曾經(jīng)在其他金融機構(gòu)申請的貸款信息,可以看到數(shù)據(jù)集中同樣有一列是“SK_ID_CURR',和主訓(xùn)練集中的列一致,我們可以通過這一列去把輔助訓(xùn)練集和主訓(xùn)練集做left join俩檬,但需要注意的一點是萎胰,一個SK_ID_CURR可能會對應(yīng)多個SK_ID_BUREAU,即一個申請人如果在其他金融機構(gòu)曾經(jīng)有多條貸款信息的話棚辽,這里就會有多條記錄技竟,因為模型訓(xùn)練每個申請人在數(shù)據(jù)集中只能有一條記錄,所以說我們不能直接把輔助訓(xùn)練集去和主訓(xùn)練集join屈藐,一般來說需要去計算一些統(tǒng)計特征(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()
#針對每個貸款申請人計算他們在其他金融機構(gòu)歷史上的貸款數(shù)量
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的統(tǒng)計屬性發(fā)現(xiàn)榔组,雖然非違約用戶的平均貸款申請數(shù)量要略多于違約用戶,但差異很小联逻,所以其實很難判斷這個特征對預(yù)測是否是有用的搓扯。

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

定義一個查看分布的函數(shù),以后再做出新特征時包归,我們可以用這個函數(shù)快速查看新的特征在違約用戶和非違約用戶中的分布情況锨推。

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)

連續(xù)型變量特征提取

對于連續(xù)型變量,我們都可以采用計算它們的統(tǒng)計值來作為特征公壤,為了快速計算出大量統(tǒng)計特征换可,我們可以結(jié)合采用python中的groupby和agg函數(shù)。

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()

通過上面的函數(shù)我們快速計算出了大量統(tǒng)計特征厦幅。

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()

同樣把新制作的特征和主數(shù)據(jù)集進行l(wèi)eft join

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

上面我們制作了大量統(tǒng)計特征沾鳄,同樣,我們也要去考察一下它們對模型預(yù)測的能力确憨,為了快速了解各個變量的情況洞渔,我們可以查看這些特征和Y值的相關(guān)性系數(shù)來做一個快速的判斷,雖然不夠準確缚态,但可以作為一個大概的參考磁椒。

new_corrs = []

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

下方函數(shù)輸出相關(guān)性絕對值前15的特征,可以看到DAYS_CREDIT_MEAN與Y值的正相關(guān)性最強玫芦,官方說明文檔給出的含義是“How many days before current application did client apply for Credit Bureau credit”浆熔,即申請人在信用局開戶的平均歷史天數(shù),大家可以看到因為數(shù)據(jù)集中這個值是負數(shù)桥帆,所以含義其實是用戶的開戶時間越長医增,歷史信用記錄的時間越久越不容易違約。

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


為了能快速把上面計算連續(xù)型變量特征的方法應(yīng)用到其他數(shù)據(jù)集中老虫,我們定義一個函數(shù)來完成上面所有的步驟叶骨。

bureau_agg_new = agg_numeric(bureau.drop(columns = ['SK_ID_BUREAU']), group_var = 'SK_ID_CURR', df_name = 'bureau')
bureau_agg_new.head()
#同樣再定義一個相關(guān)性計算函數(shù)
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

離散型變量特征提取

首先先把數(shù)據(jù)集中的離散特征變成啞變量



然后求總數(shù)和均值,sum列代表該類別的計數(shù)祈匙,mean表示其在整體中的占比忽刽,通過這種方式我們非常簡單的就完成了上述步驟天揖。

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

做好的特征可以合并到主訓(xùn)練集中

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

和連續(xù)型變量一樣,為了對其他的數(shù)據(jù)集也能快速進行類似的操作跪帝,我們把上面所有對離散型變量的特征提取步驟定義為一個函數(shù)今膊。

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
#來看一下效果,一個函數(shù)完成了上面的所有步驟
bureau_counts = count_categorical(bureau, group_var = 'SK_ID_CURR', df_name = 'bureau')
bureau_counts.head()

整合所有數(shù)據(jù)集

在上面的分析中伞剑,我們以信用局信息為例演示了對于連續(xù)型變量和離散型變量的特征提取方法斑唬,并且定義了連續(xù)型變量特征提取和離散型變量特征提取的函數(shù),下面我們就可以把之前定義的函數(shù)應(yīng)用到所有輔助數(shù)據(jù)集上黎泣。
首先重新讀取一遍數(shù)據(jù)集恕刘,把數(shù)據(jù)集還原到初始狀態(tài)。

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)

兩個函數(shù)完成之前對信用局數(shù)據(jù)中連續(xù)變量和離散變量的特征提取

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()

給訓(xùn)練集和測試集增加信用局相關(guān)特征

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')

特征篩選

在之前的一系列的特征工程中抒倚,我們給訓(xùn)練集和測試集增加了很多新的特征雪营,特征也膨脹到了600多列,在最后建模之前衡便,我們還需要對這些加入的特征再做一次篩選献起,排除一些具有共線性的特征以提高模型的效果。具體通過計算變量與變量之間的相關(guān)系數(shù)镣陕,來快速移除一些相關(guān)性過高的變量谴餐,這里可以定義一個閾值是0.8,即移除每一對相關(guān)性大于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))

建模預(yù)測

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末岂嗓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鹊碍,更是在濱河造成了極大的恐慌厌殉,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件侈咕,死亡現(xiàn)場離奇詭異公罕,居然都是意外死亡,警方通過查閱死者的電腦和手機耀销,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進店門楼眷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人熊尉,你說我怎么就攤上這事罐柳。” “怎么了狰住?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵张吉,是天一觀的道長。 經(jīng)常有香客問我催植,道長肮蛹,這世上最難降的妖魔是什么勺择? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮蔗崎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扰藕。我一直安慰自己缓苛,他們只是感情好,可當我...
    茶點故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布邓深。 她就那樣靜靜地躺著未桥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芥备。 梳的紋絲不亂的頭發(fā)上冬耿,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天,我揣著相機與錄音萌壳,去河邊找鬼亦镶。 笑死,一個胖子當著我的面吹牛袱瓮,可吹牛的內(nèi)容都是我干的缤骨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼尺借,長吁一口氣:“原來是場噩夢啊……” “哼绊起!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起燎斩,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤虱歪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后栅表,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笋鄙,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年怪瓶,在試婚紗的時候發(fā)現(xiàn)自己被綠了局装。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡劳殖,死狀恐怖铐尚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哆姻,我是刑警寧澤宣增,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站矛缨,受9級特大地震影響爹脾,放射性物質(zhì)發(fā)生泄漏帖旨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一灵妨、第九天 我趴在偏房一處隱蔽的房頂上張望解阅。 院中可真熱鬧,春花似錦泌霍、人聲如沸货抄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蟹地。三九已至,卻和暖如春藤为,著一層夾襖步出監(jiān)牢的瞬間怪与,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工缅疟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留分别,地道東北人。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓存淫,卻偏偏與公主長得像茎杂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,955評論 2 355