Python 評分卡之數據預處理(重複值、填補缺失值、異常值與數據不平衡)

關注微信公共號:小程在線

關注CSDN博客:程志偉的博客

評分卡之數據預處理:重複值、填補缺失值、異常值與數據不平衡

 

在銀行借貸場景中,評分卡是一種以分數形式來衡量一個客戶的信用風險大小的手段,它衡量向別人借錢的人(受信人,需要融資的公司)不能如期履行合同中的還本付息責任,並讓借錢給別人的人(授信人,銀行等金融機構)造成經濟損失的可能性。一般來說,評分卡打出的分數越高,客戶的信用越好,風險越小。

Python 3.7.3 (default, Apr 24 2019, 15:29:51) [MSC v.1915 64 bit (AMD64)]
Type "copyright", "credits" or "license" for more information.

IPython 7.6.1 -- An enhanced Interactive Python.

 

1.導入庫並獲取數據

%matplotlib inline
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression as LR

data = pd.read_csv(r"H:\程志偉\python\rankingcard.csv",index_col=0)

 

2.數據探索與數據預處理
#觀察數據類型

data.head()
Out[3]: 
   SeriousDlqin2yrs  ...  NumberOfDependents
1                 1  ...                 2.0
2                 0  ...                 1.0
3                 0  ...                 0.0
4                 0  ...                 0.0
5                 0  ...                 0.0

[5 rows x 11 columns]

 

#觀察數據結構

data.shape
Out[4]: (150000, 11)

data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 150000 entries, 1 to 150000
Data columns (total 11 columns):
 #   Column                                Non-Null Count   Dtype  
---  ------                                --------------   -----  
 0   SeriousDlqin2yrs                      150000 non-null  int64  
 1   RevolvingUtilizationOfUnsecuredLines  150000 non-null  float64
 2   age                                   150000 non-null  int64  
 3   NumberOfTime30-59DaysPastDueNotWorse  150000 non-null  int64  
 4   DebtRatio                             150000 non-null  float64
 5   MonthlyIncome                         120269 non-null  float64
 6   NumberOfOpenCreditLinesAndLoans       150000 non-null  int64  
 7   NumberOfTimes90DaysLate               150000 non-null  int64  
 8   NumberRealEstateLoansOrLines          150000 non-null  int64  
 9   NumberOfTime60-89DaysPastDueNotWorse  150000 non-null  int64  
 10  NumberOfDependents                    146076 non-null  float64
dtypes: float64(4), int64(7)
memory usage: 13.7 MB

 

數據字段描述

SeriousDlqin2yrs 出現 90 天或更長時間的逾期行爲(即定義好壞客戶)
RevolvingUtilizationOfUnsecuredLines 貸款以及信用卡可用額度與總額度比例
age 借款人借款年齡
NumberOfTime30-59DaysPastDueNotWorse 過去兩年內出現35-59天逾期但是沒有發展得更壞的次數
DebtRatio 每月償還債務,贍養費,生活費用除以月總收入
MonthlyIncome 月收入
NumberOfOpenCreditLinesAndLoans 開放式貸款和信貸數量
NumberOfTimes90DaysLate 過去兩年內出現90天逾期或更壞的次數
NumberRealEstateLoansOrLines 抵押貸款和房地產貸款數量,包括房屋淨值信貸額度
NumberOfTime60-89DaysPastDueNotWorse 過去兩年內出現60-89天逾期但是沒有發展得更壞的次數
NumberOfDependents 家庭中不包括自身的家屬人數(配偶,子女等)

 

2.1 去重數據
#去除重複值

data.drop_duplicates(inplace=True)

data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 149391 entries, 1 to 150000
Data columns (total 11 columns):
 #   Column                                Non-Null Count   Dtype  
---  ------                                --------------   -----  
 0   SeriousDlqin2yrs                      149391 non-null  int64  
 1   RevolvingUtilizationOfUnsecuredLines  149391 non-null  float64
 2   age                                   149391 non-null  int64  
 3   NumberOfTime30-59DaysPastDueNotWorse  149391 non-null  int64  
 4   DebtRatio                             149391 non-null  float64
 5   MonthlyIncome                         120170 non-null  float64
 6   NumberOfOpenCreditLinesAndLoans       149391 non-null  int64  
 7   NumberOfTimes90DaysLate               149391 non-null  int64  
 8   NumberRealEstateLoansOrLines          149391 non-null  int64  
 9   NumberOfTime60-89DaysPastDueNotWorse  149391 non-null  int64  
 10  NumberOfDependents                    145563 non-null  float64
dtypes: float64(4), int64(7)
memory usage: 13.7 MB

 

#刪除之後千萬不要忘記,恢復索引,標紅部分發生變化

data.index = range(data.shape[0])

data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 149391 entries, 0 to 149390
Data columns (total 11 columns):
 #   Column                                Non-Null Count   Dtype  
---  ------                                --------------   -----  
 0   SeriousDlqin2yrs                      149391 non-null  int64  
 1   RevolvingUtilizationOfUnsecuredLines  149391 non-null  float64
 2   age                                   149391 non-null  int64  
 3   NumberOfTime30-59DaysPastDueNotWorse  149391 non-null  int64  
 4   DebtRatio                             149391 non-null  float64
 5   MonthlyIncome                         120170 non-null  float64
 6   NumberOfOpenCreditLinesAndLoans       149391 non-null  int64  
 7   NumberOfTimes90DaysLate               149391 non-null  int64  
 8   NumberRealEstateLoansOrLines          149391 non-null  int64  
 9   NumberOfTime60-89DaysPastDueNotWorse  149391 non-null  int64  
 10  NumberOfDependents                    145563 non-null  float64
dtypes: float64(4), int64(7)
memory usage: 12.5 MB

 

 

2.2 填補缺失值

查看每列缺失值的情況

data.isnull().sum()
Out[10]: 
SeriousDlqin2yrs                            0
RevolvingUtilizationOfUnsecuredLines        0
age                                         0
NumberOfTime30-59DaysPastDueNotWorse        0
DebtRatio                                   0
MonthlyIncome                           29221
NumberOfOpenCreditLinesAndLoans             0
NumberOfTimes90DaysLate                     0
NumberRealEstateLoansOrLines                0
NumberOfTime60-89DaysPastDueNotWorse        0
NumberOfDependents                       3828
dtype: int64

 

#查看缺失值的比例

data.isnull().sum()/data.shape[0]
Out[11]: 
SeriousDlqin2yrs                        0.000000
RevolvingUtilizationOfUnsecuredLines    0.000000
age                                     0.000000
NumberOfTime30-59DaysPastDueNotWorse    0.000000
DebtRatio                               0.000000
MonthlyIncome                           0.195601
NumberOfOpenCreditLinesAndLoans         0.000000
NumberOfTimes90DaysLate                 0.000000
NumberRealEstateLoansOrLines            0.000000
NumberOfTime60-89DaysPastDueNotWorse    0.000000
NumberOfDependents                      0.025624
dtype: float64

data.isnull().mean()
Out[12]: 
SeriousDlqin2yrs                        0.000000
RevolvingUtilizationOfUnsecuredLines    0.000000
age                                     0.000000
NumberOfTime30-59DaysPastDueNotWorse    0.000000
DebtRatio                               0.000000
MonthlyIncome                           0.195601
NumberOfOpenCreditLinesAndLoans         0.000000
NumberOfTimes90DaysLate                 0.000000
NumberRealEstateLoansOrLines            0.000000
NumberOfTime60-89DaysPastDueNotWorse    0.000000
NumberOfDependents                      0.025624
dtype: float64

 

使用均值填補“家屬人數”

data["NumberOfDependents"].fillna(int(data["NumberOfDependents"].mean()),inplace=True)

data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 149391 entries, 0 to 149390
Data columns (total 11 columns):
 #   Column                                Non-Null Count   Dtype  
---  ------                                --------------   -----  
 0   SeriousDlqin2yrs                      149391 non-null  int64  
 1   RevolvingUtilizationOfUnsecuredLines  149391 non-null  float64
 2   age                                   149391 non-null  int64  
 3   NumberOfTime30-59DaysPastDueNotWorse  149391 non-null  int64  
 4   DebtRatio                             149391 non-null  float64
 5   MonthlyIncome                         120170 non-null  float64
 6   NumberOfOpenCreditLinesAndLoans       149391 non-null  int64  
 7   NumberOfTimes90DaysLate               149391 non-null  int64  
 8   NumberRealEstateLoansOrLines          149391 non-null  int64  
 9   NumberOfTime60-89DaysPastDueNotWorse  149391 non-null  int64  
 10  NumberOfDependents                    149391 non-null  float64
dtypes: float64(4), int64(7)
memory usage: 12.5 MB

 

#填補之後在觀測每列的數據缺失比例

data.isnull().sum()/data.shape[0]
Out[15]: 
SeriousDlqin2yrs                        0.000000
RevolvingUtilizationOfUnsecuredLines    0.000000
age                                     0.000000
NumberOfTime30-59DaysPastDueNotWorse    0.000000
DebtRatio                               0.000000
MonthlyIncome                           0.195601
NumberOfOpenCreditLinesAndLoans         0.000000
NumberOfTimes90DaysLate                 0.000000
NumberRealEstateLoansOrLines            0.000000
NumberOfTime60-89DaysPastDueNotWorse    0.000000
NumberOfDependents                      0.000000
dtype: float64

 

使用隨機森林填補一個特徵的缺失值的函數


def fill_missing_rf(X,y,to_fill):
    """
    使用隨機森林填補一個特徵的缺失值的函數
    參數:
    X:要填補的特徵矩陣
    y:完整的,沒有缺失值的標籤
    to_fill:字符串,要填補的那一列的名稱

    X_train 特徵T不缺失的值:

    Y_train 特徵T缺失的值對應的其他n-1個特徵 + 本來的標籤:

    X_test 特徵T缺失的值:未知,

    我們需要預測的Y_test

    """
    #構建我們的新特徵矩陣和新標籤
    df = X.copy()
    fill = df.loc[:,to_fill]
    df = pd.concat([df.loc[:,df.columns != to_fill],pd.DataFrame(y)],axis=1)
    
    #找出我們的訓練集和測試集
    Ytrain = fill[fill.notnull()]
    Ytest = fill[fill.isnull()]
    Xtrain = df.iloc[Ytrain.index,:]
    Xtest = df.iloc[Ytest.index,:]
    
    #用隨機森林迴歸來填補缺失值
    from sklearn.ensemble import RandomForestRegressor as rfr
    rfr = rfr(n_estimators=100)
    rfr = rfr.fit(Xtrain, Ytrain)
    Ypredict = rfr.predict(Xtest)
    
    return Ypredict

 

將參數導入函數,產出結果:

X = data.iloc[:,1:]

y = data["SeriousDlqin2yrs"]

X.shape
Out[20]: (149391, 10)

y_pred = fill_missing_rf(X,y,"MonthlyIncome")

y_pred.shape
Out[22]: (29221,)

 

data.loc[data.loc[:,"MonthlyIncome"].isnull(),"MonthlyIncome"].shape
Out[23]: (29221,)

 

將預測數據填回到數據集中

data.loc[data.loc[:,"MonthlyIncome"].isnull(),"MonthlyIncome"] = y_pred

 

再次查看數據的缺失情況

data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 149391 entries, 0 to 149390
Data columns (total 11 columns):
 #   Column                                Non-Null Count   Dtype  
---  ------                                --------------   -----  
 0   SeriousDlqin2yrs                      149391 non-null  int64  
 1   RevolvingUtilizationOfUnsecuredLines  149391 non-null  float64
 2   age                                   149391 non-null  int64  
 3   NumberOfTime30-59DaysPastDueNotWorse  149391 non-null  int64  
 4   DebtRatio                             149391 non-null  float64
 5   MonthlyIncome                         149391 non-null  float64
 6   NumberOfOpenCreditLinesAndLoans       149391 non-null  int64  
 7   NumberOfTimes90DaysLate               149391 non-null  int64  
 8   NumberRealEstateLoansOrLines          149391 non-null  int64  
 9   NumberOfTime60-89DaysPastDueNotWorse  149391 non-null  int64  
 10  NumberOfDependents                    149391 non-null  float64
dtypes: float64(4), int64(7)
memory usage: 12.5 MB

 

2.3 描述性統計處理異常值

data.describe([0.01,0.1,0.25,.5,.75,.9,.99]).T
Out[26]: 
                                         count  ...        max
SeriousDlqin2yrs                      149391.0  ...        1.0
RevolvingUtilizationOfUnsecuredLines  149391.0  ...    50708.0
age                                   149391.0  ...      109.0
NumberOfTime30-59DaysPastDueNotWorse  149391.0  ...       98.0
DebtRatio                             149391.0  ...   329664.0
MonthlyIncome                         149391.0  ...  3008750.0
NumberOfOpenCreditLinesAndLoans       149391.0  ...       58.0
NumberOfTimes90DaysLate               149391.0  ...       98.0
NumberRealEstateLoansOrLines          149391.0  ...       54.0
NumberOfTime60-89DaysPastDueNotWorse  149391.0  ...       98.0
NumberOfDependents                    149391.0  ...       20.0

[11 rows x 12 columns]

 

#異常值也被我們觀察到,年齡的最小值居然有0,這不符合銀行的業務需求,即便是兒童賬戶也要至少8歲,我們可以
(data["age"] == 0).sum()
Out[27]: 1

 

#發現只有一個人年齡爲0,可以判斷這肯定是錄入失誤造成的,可以當成是缺失值來處理,直接刪除掉這個樣本
data = data[data["age"] != 0]

data.shape
Out[29]: (149390, 11)

 

"""
另外,有三個指標看起來很奇怪:
"NumberOfTime30-59DaysPastDueNotWorse"
"NumberOfTime60-89DaysPastDueNotWorse"
"NumberOfTimes90DaysLate"
這三個指標分別是“過去兩年內出現35-59天逾期但是沒有發展的更壞的次數”,“過去兩年內出現60-89天逾期但是沒
有發展的更壞的次數”,“過去兩年內出現90天逾期的次數”。這三個指標,在99%的分佈的時候依然是2,最大值卻是
98,看起來非常奇怪。一個人在過去兩年內逾期35~59天98次,一年6個60天,兩年內逾期98次這是怎麼算出來的?
我們可以去諮詢業務人員,請教他們這個逾期次數是如何計算的。如果這個指標是正常的,那這些兩年內逾期了98次的
客戶,應該都是壞客戶。在我們無法詢問他們情況下,我們查看一下有多少個樣本存在這種異常:
"""

 

data[data.loc[:,"NumberOfTimes90DaysLate"] > 90].count()
Out[30]: 
SeriousDlqin2yrs                        225
RevolvingUtilizationOfUnsecuredLines    225
age                                     225
NumberOfTime30-59DaysPastDueNotWorse    225
DebtRatio                               225
MonthlyIncome                           225
NumberOfOpenCreditLinesAndLoans         225
NumberOfTimes90DaysLate                 225
NumberRealEstateLoansOrLines            225
NumberOfTime60-89DaysPastDueNotWorse    225
NumberOfDependents                      225
dtype: int64

 

#有225個樣本存在這樣的情況,並且這些樣本,我們觀察一下,標籤並不都是1,他們並不都是壞客戶。因此,我們基
本可以判斷,這些樣本是某種異常,應該把它們刪除。

data = data[data.loc[:,"NumberOfTimes90DaysLate"] < 90]

data.index = range(data.shape[0])

data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 149165 entries, 0 to 149164
Data columns (total 11 columns):
 #   Column                                Non-Null Count   Dtype  
---  ------                                --------------   -----  
 0   SeriousDlqin2yrs                      149165 non-null  int64  
 1   RevolvingUtilizationOfUnsecuredLines  149165 non-null  float64
 2   age                                   149165 non-null  int64  
 3   NumberOfTime30-59DaysPastDueNotWorse  149165 non-null  int64  
 4   DebtRatio                             149165 non-null  float64
 5   MonthlyIncome                         149165 non-null  float64
 6   NumberOfOpenCreditLinesAndLoans       149165 non-null  int64  
 7   NumberOfTimes90DaysLate               149165 non-null  int64  
 8   NumberRealEstateLoansOrLines          149165 non-null  int64  
 9   NumberOfTime60-89DaysPastDueNotWorse  149165 non-null  int64  
 10  NumberOfDependents                    149165 non-null  float64
dtypes: float64(4), int64(7)
memory usage: 12.5 MB

 

2.4 樣本不均衡問題

X = data.iloc[:,1:]
y = data.iloc[:,0]

y.value_counts()
Out[35]: 
0    139292
1      9873
Name: SeriousDlqin2yrs, dtype: int64

可以看出,樣本嚴重不均衡

 

 

n_sample = X.shape[0]
n_1_sample = y.value_counts()[1]
n_0_sample = y.value_counts()[0]
print('樣本個數:{}; 1佔{:.2%}; 0佔{:.2%}'.format(n_sample,n_1_sample/n_sample,n_0_sample/n_sample))

樣本個數:149165; 1佔6.62%; 0佔93.38%


 

使用上採樣方法來平衡樣本

import imblearn

from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=42) #實例化
X,y = sm.fit_sample(X,y)
n_sample_ = X.shape[0]
pd.Series(y).value_counts()
Out[38]: 
1    139292
0    139292
dtype: int64

n_1_sample = pd.Series(y).value_counts()[1]
n_0_sample = pd.Series(y).value_counts()[0]
print('樣本個數:{}; 1佔{:.2%}; 0佔{:.2%}'.format(n_sample_,n_1_sample/n_sample_,n_0_sample/n_sample_))
樣本個數:278584; 1佔50.00%; 0佔50.00%

我們就實現了樣本平衡,樣本量也增加了

 


2.5 分訓練集和測試集

from sklearn.model_selection import train_test_split
X = pd.DataFrame(X)
y = pd.DataFrame(y)
X_train, X_vali, Y_train, Y_vali = train_test_split(X,y,test_size=0.3,random_state=420)

model_data = pd.concat([Y_train, X_train], axis=1)
model_data.index = range(model_data.shape[0])
model_data.columns = data.columns

vali_data = pd.concat([Y_vali, X_vali], axis=1)
vali_data.index = range(vali_data.shape[0])
vali_data.columns = data.columns

model_data.to_csv(r"H:\程志偉\python\model_data.csv")

vali_data.to_csv(r"H:\程志偉\python\\vali_data.csv")

 

作爲評分卡的數據預處理完成。

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