German credit data modeling baseline!

Public number: You Er Hut
Author: Peter
Editor: Peter

Hello everyone, my name is Peter~

This paper is a simple modeling of a German credit data based on the 3 tree model, which can be used as a baseline, and finally puts forward the direction of optimization. The main contents include:

import library

Imported libraries for data manipulation, visualization, modeling, etc.

import pandas as pd
import numpy as np

# 1、基于plotly
import plotly as py
import plotly.express as px
import plotly.graph_objects as go
py.offline.init_notebook_mode(connected = True)
from plotly.subplots import make_subplots  # 多子图
# 2、基于matplotlib
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
%matplotlib inline
# 中文显示问题
#设置字体
plt.rcParams["font.sans-serif"]=["SimHei"] 
#正常显示负号
plt.rcParams["axes.unicode_minus"]=False 

# 3、基于seaborn
import seaborn as sns
# plt.style.use("fivethirtyeight")
plt.style.use('ggplot')

# 数据标准化、分割、交叉验证
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler,LabelEncoder
from sklearn.model_selection import train_test_split,cross_val_score

# 模型
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeRegressor,DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC

# 模型评价
from sklearn import metrics  
from sklearn.metrics import classification_report, confusion_matrix, ConfusionMatrixDisplay
from sklearn.metrics import accuracy_score, recall_score, roc_auc_score, precision_score, f1_score

# 忽略notebook中的警告
import warnings
warnings.filterwarnings("ignore")

Data Introduction

The data comes from the UCI official website: archive.ics.uci.edu/ml/datasets…

Basic information: 1000 pieces of data + 20 variables + target variable + no missing values

Chinese and English meanings of characteristic variables:

  • Feature vector Chinese: 1. Checking account status; 2. Borrowing cycle; 3. Historical credit; 4. Purpose of borrowing; 5. Credit limit; 6. Savings account status; 7. Current employment status; Percentage; 9. Gender and marital status; 10. Guarantee information; 11. Current residence; 12. Property status; 13. Age; 14. Other installments; 15. Property status; 16. Number of credit cards; 17. Working status ; 18. Number of dependents; 19. Registration of telephone numbers; 20. Whether there is any overseas work experience

  • The feature vector corresponds to English: 1.status_account, 2.duration, 3.credit_history, 4,purpose, 5.amount, 6.svaing_account, 7.present_emp, 8.income_rate, 9.personal_status, 10.other_debtors, 11.residence_info, 12 .property, 13.age, 14.inst_plans, 15.housing, 16.num_credits, 17.job, 18.dependents, 19.telephone, 20.foreign_worker

read data

The downloaded data has no header, and the corresponding English header is found on the Internet to generate a DataFrame:

In [4]:

df.shape

Out[4]:

(1000, 21)

In [5]:

df.dtypes  # 字段类型

Out[5]:

checking_account_status    object
duration                    int64
credit_history             object
purpose                    object
credit_amount               int64
savings                    object
present_employment         object
installment_rate            int64
personal                   object
other_debtors              object
present_residence           int64
property                   object
age                         int64
other_installment_plans    object
housing                    object
existing_credits            int64
job                        object
dependents                  int64
telephone                  object
foreign_worker             object
customer_type               int64
dtype: object

In [6]:

# 不同的字段类型统计

pd.value_counts(df.dtypes.values)

Out[6]:

object    13
int64      8
dtype: int64

In [7]:

df.isnull().sum()

Out[7]:

checking_account_status    0
duration                   0
credit_history             0
purpose                    0
credit_amount              0
savings                    0
present_employment         0
installment_rate           0
personal                   0
other_debtors              0
present_residence          0
property                   0
age                        0
other_installment_plans    0
housing                    0
existing_credits           0
job                        0
dependents                 0
telephone                  0
foreign_worker             0
customer_type              0
dtype: int64

不同字段下的取值统计

In [8]:

columns = df.columns     # 字段
columns

Out[8]:

Index(['checking_account_status', 'duration', 'credit_history', 'purpose',
       'credit_amount', 'savings', 'present_employment', 'installment_rate',
       'personal', 'other_debtors', 'present_residence', 'property', 'age',
       'other_installment_plans', 'housing', 'existing_credits', 'job',
       'dependents', 'telephone', 'foreign_worker', 'customer_type'],
      dtype='object')

1、针对字符类型字段的取值情况统计:

string_columns = df.select_dtypes(include="object").columns

# 两个基本参数:设置行、列
fig = make_subplots(rows=3, cols=5)  

for i, v in enumerate(string_columns):  
    r = i // 5 + 1
    c = (i+1) % 5
    
    data = df[v].value_counts().reset_index()

    if c ==0:
        fig.add_trace(go.Bar(x=data["index"],y=data[v],
                             text=data[v],name=v),
                      row=r, col=5)
    else:
        fig.add_trace(go.Bar(x=data["index"],y=data[v],
                             text=data[v],name=v),
                     row=r, col=c)

fig.update_layout(width=1000, height=900)

fig.show()

2、针对数值型字段的分布情况:

number_columns = df.select_dtypes(exclude="object").columns.tolist()
number_columns

# 两个基本参数:设置行、列
fig = make_subplots(rows=2, cols=4)  # 2行4列

for i, v in enumerate(number_columns):  # number_columns 长度是8
    r = i // 4 + 1
    c = (i+1) % 4

    if c ==0:
        fig.add_trace(go.Box(y=df[v].tolist(),name=v),
                 row=r, col=4)
    else:
        fig.add_trace(go.Box(y=df[v].tolist(),name=v),
                 row=r, col=c)

fig.update_layout(width=1000, height=900)

fig.show()

字段处理

支票状态-checking_account_status

中文含义:现有支票帐户的状态

  • A11:<0 DM
  • A12:0 <= x <200 DM
  • A13:> = 200 DM /至少一年的薪水分配
  • A14:无支票帐户)

In [11]:

df["checking_account_status"].value_counts()

Out[11]:

A14    394
A11    274
A12    269
A13     63
Name: checking_account_status, dtype: int64

In [12]:

fig,ax = plt.subplots(figsize=(12,8), dpi=80)

sns.countplot(x="checking_account_status", data=df)

plt.title("number of checking_account_status")

for p in ax.patches:
    ax.annotate(f'\n{p.get_height()}', (p.get_x(), p.get_height()+5), color='black', size=20)
plt.show()

在这里我们根据每个人的支票账户金额的大小进行硬编码

In [13]:

# A11:<0 DM,A12:0 <= x <200 DM,A13:> = 200 DM /至少一年的薪水分配,A14:无支票帐户
# 编码1
cas = {"A11": 1,"A12":2, "A13":3, "A14":0}
df["checking_account_status"] = df["checking_account_status"].map(cas)

借款周期-duration

中文含义是:持续时间(月)

In [14]:

duration = df["duration"].value_counts()
duration.head()

Out[14]:

24    184
12    179
18    113
36     83
6      75
Name: duration, dtype: int64

In [15]:

fig = px.violin(df,y="duration")

fig.show()

信用卡历史-credit_history

中文含义

  • A30:未提取任何信用/已全额偿还所有信用额
  • A31:已偿还该银行的所有信用额
  • A32:已到期已偿还的现有信用额
  • A33:过去的还款延迟
  • A34:关键帐户/其他信用额现有(不在此银行)

In [17]:

ch = df["credit_history"].value_counts().reset_index()
ch

Out[17]:

index credit_history
0 A32 530
1 A34 293
2 A33 88
3 A31 49
4 A30 40

In [18]:

fig = px.pie(ch,names="index",values="credit_history")

fig.update_traces(
    textposition='inside',
    textinfo='percent+label'
)

fig.show()

# 编码2:独热码

df_credit_history = pd.get_dummies(df["credit_history"])
df = df.join(df_credit_history)
df.drop("credit_history", inplace=True, axis=1)

借款目的-purpose

借款目的

In [20]:

# 统计每个目的下的人数,根据人数的多少来实施硬编码
purpose = df["purpose"].value_counts().sort_values(ascending=True).reset_index()

purpose.columns = ["purpose", "number"]

purpose

# 编码3
df["purpose"] = df["purpose"].map(dict(zip(purpose.purpose,purpose.index)))

信用额度-credit_amount

表示的是信用额度

In [22]:

px.violin(df["credit_amount"])

账户储蓄-savings

账户/债券储蓄(A61:<100 DM,A62:100 <= x <500 DM,A63:500 <= x <1000 DM,A64:> = 1000 DM,A65:未知/无储蓄账户

In [24]:

string_columns

Out[24]:

Index(['checking_account_status', 'credit_history', 'purpose', 'savings',
       'present_employment', 'personal', 'other_debtors', 'property',
       'other_installment_plans', 'housing', 'job', 'telephone',
       'foreign_worker'],
      dtype='object')

In [25]:

df["savings"].value_counts()

Out[25]:

A61    603
A65    183
A62    103
A63     63
A64     48
Name: savings, dtype: int64

In [26]:

# 编码6:硬编码
savings = {"A61":1,"A62":2, "A63":3, "A64":4,"A65":0}

df["savings"] = df["savings"].map(savings)

目前状态-present_employment

  • A71:待业
  • A72:<1年
  • A73:1 <= x <4年
  • A74:4 <= x <7年
  • A75:..> = 7年

In [28]:

df["present_employment"].value_counts()

Out[28]:

A73    339
A75    253
A74    174
A72    172
A71     62
Name: present_employment, dtype: int64

In [29]:

# 编码7:独热码

df_present_employment = pd.get_dummies(df["present_employment"])

In [30]:

df = df.join(df_present_employment)

df.drop("present_employment", inplace=True, axis=1)

个人婚姻状态和性别-personal

个人婚姻状况和性别(A91:男性:离婚/分居,A92:女性:离婚/分居/已婚,A93:男性:单身,A94:男性:已婚/丧偶,A95:女性:单身)

In [31]:

# 编码8:独热码

df_personal = pd.get_dummies(df["personal"])
df = df.join(df_personal)

df.drop("personal", inplace=True, axis=1)

其他担保人-other_debtors

A101:无,A102:共同申请人,A103:担保人

In [32]:

# 编码9:独热码

df_other_debtors = pd.get_dummies(df["other_debtors"])
df = df.join(df_other_debtors)

df.drop("other_debtors", inplace=True, axis=1)

资产-property

In [33]:

# 编码10:独热码

df_property = pd.get_dummies(df["property"])
df = df.join(df_property)

df.drop("property", inplace=True, axis=1)

住宿-housing

A151:租房,A152:自有,A153:免费

In [34]:

# 编码11:独热码

df_housing = pd.get_dummies(df["housing"])
df = df.join(df_housing)

df.drop("housing", inplace=True, axis=1)

其他投资计划-other_installment_plans

A141:银行,A142:店铺,A143:无

In [35]:

fig,ax = plt.subplots(figsize=(12,8), dpi=80)

sns.countplot(x="other_installment_plans", data=df)

plt.title("number of other_installment_plans")

for p in ax.patches:
    ax.annotate(f'\n{p.get_height()}', (p.get_x(), p.get_height()+5), color='black', size=20)
plt.show()

# 编码12:独热码

df_other_installment_plans = pd.get_dummies(df["other_installment_plans"])
df = df.join(df_other_installment_plans)

df.drop("other_installment_plans", inplace=True, axis=1)

工作-job

  • A171 : 非技术人员-非居民
  • A172:非技术人员-居民
  • A173:技术人员/官员
  • A174:管理/个体经营/高度合格的员工/官员

In [37]:

fig,ax = plt.subplots(figsize=(12,8), dpi=80)

sns.countplot(x="job", data=df)

plt.title("number of job")

for p in ax.patches:
    ax.annotate(f'\n{p.get_height()}', (p.get_x(), p.get_height()+5), color='black', size=20)
plt.show()

# 编码13:独热码

df_job = pd.get_dummies(df["job"])
df = df.join(df_job)

df.drop("job", inplace=True, axis=1)

电话-telephone

A191:无,A192:有,登记在客户名下

In [39]:

# 编码14:独热码

df_telephone = pd.get_dummies(df["telephone"])
df = df.join(df_telephone)

df.drop("telephone", inplace=True, axis=1)

是否国外工作-foreign_worker

A201: 有,A202: 无

In [40]:

# 编码15:独热码

df_foreign_worker = pd.get_dummies(df["foreign_worker"])
df = df.join(df_foreign_worker)

df.drop("foreign_worker", inplace=True, axis=1)

两种类型顾客统计-customer_type

预测类别:1 =良好,2 =不良

In [41]:

fig,ax = plt.subplots(figsize=(12,8), dpi=80)

sns.countplot(x="customer_type", data=df)

plt.title("number of customer_type")

for p in ax.patches:
    ax.annotate(f'\n{p.get_height()}', (p.get_x(), p.get_height()+5), color='black', size=20)
plt.show()

打乱数据shuffle

In [42]:

from sklearn.utils import shuffle

# 随机打乱数据
df = shuffle(df).reset_index(drop=True)

建模

数据分割

In [44]:

# 选取特征
X  = df.drop("customer_type",axis=1)

# 目标变量
y = df['customer_type']
from sklearn.model_selection import train_test_split

In [45]:

# 2-8比例
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.2, random_state=42)

数据标准化

In [46]:

ss = StandardScaler()

X_train = ss.fit_transform(X_train)

In [47]:

y_train

Out[47]:

556    1
957    1
577    2
795    2
85     1
      ..
106    1
270    2
860    1
435    1
102    2
Name: customer_type, Length: 200, dtype: int64

In [48]:

# 分别求出训练集的均值和标准差

mean_ = ss.mean_  # 均值
var_ = np.sqrt(ss.var_)  # 标准差

将上面求得的均值和标准差用于测试集中:

In [50]:

# 归一化之后的测试集中的特征数据
X_test = (X_test - mean_) / var_

模型1:决策树

In [51]:

dt = DecisionTreeClassifier(max_depth=5)

dt.fit(X_train, y_train)

Out[51]:

DecisionTreeClassifier(max_depth=5)

In [52]:

# 预测
y_pred = dt.predict(X_test)
y_pred[:5]

Out[52]:

array([2, 1, 1, 2, 1])

In [53]:

# 混淆矩阵
confusion_mat = metrics.confusion_matrix(y_test,y_pred)
confusion_mat

Out[53]:

array([[450, 118],
       [137,  95]])

In [54]:

# 混淆矩阵可视化

classes = ["良好","不良"]

disp = ConfusionMatrixDisplay(confusion_matrix=confusion_mat, display_labels=classes)
disp.plot(
    include_values=True,            # 混淆矩阵每个单元格上显示具体数值
    cmap="GnBu",                 # matplotlib识别的颜色图
    ax=None,  
    xticks_rotation="horizontal",    
    values_format="d"              
)

plt.show()

## auc-roc

auc_roc = metrics.roc_auc_score(y_test, y_pred)  # 测试值和预测值
auc_roc

0.5008681398737251

模型2:随机森林

In [56]:

rf = RandomForestClassifier()
rf.fit(X_train, y_train)

Out[56]:

RandomForestClassifier()

In [57]:

# 预测
y_pred = rf.predict(X_test)
y_pred[:5]

Out[57]:

array([1, 1, 1, 2, 1])

In [58]:

# 混淆矩阵
confusion_mat = metrics.confusion_matrix(y_test,y_pred)
confusion_mat

Out[58]:

array([[476,  92],
       [142,  90]])

In [59]:

# 混淆矩阵可视化

classes = ["良好","不良"]

disp = ConfusionMatrixDisplay(confusion_matrix=confusion_mat, display_labels=classes)
disp.plot(
    include_values=True,            # 混淆矩阵每个单元格上显示具体数值
    cmap="GnBu",                 # matplotlib识别的颜色图
    ax=None,  
    xticks_rotation="horizontal",    
    values_format="d"              
)

plt.show()

## auc-roc

auc_roc = metrics.roc_auc_score(y_test, y_pred)  # 真实值和预测值
auc_roc

0.6129796017484215

模型3:XGboost

In [62]:

from xgboost.sklearn import XGBClassifier
## 定义 XGBoost模型 
clf = XGBClassifier()

# X_train = X_train.values
# X_test = X_test.values

In [63]:

clf.fit(X_train, y_train)

Out[63]:

XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
              colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
              importance_type='gain', interaction_constraints='',
              learning_rate=0.300000012, max_delta_step=0, max_depth=6,
              min_child_weight=1, missing=nan, monotone_constraints='()',
              n_estimators=100, n_jobs=0, num_parallel_tree=1, random_state=0,
              reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
              tree_method='exact', validate_parameters=1, verbosity=None)

In [65]:

# 先转成数组再传进来
X_test = X_test.values

y_pred = clf.predict(X_test)
y_pred[:5]

Out[65]:

array([1, 1, 1, 2, 1])

In [66]:

# 混淆矩阵
confusion_mat = metrics.confusion_matrix(y_test,y_pred)
confusion_mat

Out[66]:

array([[445, 123],
       [115, 117]])

In [67]:

# 混淆矩阵可视化

classes = ["良好","不良"]

disp = ConfusionMatrixDisplay(confusion_matrix=confusion_mat, display_labels=classes)
disp.plot(
    include_values=True,            # 混淆矩阵每个单元格上显示具体数值
    cmap="GnBu",                 # matplotlib识别的颜色图
    ax=None,  
    xticks_rotation="horizontal",    
    values_format="d"              
)

plt.show()

## auc-roc

auc_roc = metrics.roc_auc_score(y_test, y_pred)  # 真实值和预测值
auc_roc

0.6438805245264692

模型优化

基于相关系数进行特征筛选

# y:customer_type是目标变量

# 1、计算每个特征和目标变量的相关系数

data = pd.concat([X,y],axis=1)

corr = data.corr()
corr[:5]

相关系数的描述统计信息:发现整体的相关系数(绝对值)都比较小

热力图

ax = plt.subplots(figsize=(20,16))

ax = sns.heatmap(corr, 
                 vmax=0.8, 
                 square=True, 
                 annot=True,  # 显示数据
                 cmap="YlGnBu")

根据相关系数筛选前20个变量

k = 20

cols = corr.nlargest(k,"customer_type")["customer_type"].index
cols
Index(['customer_type', 'duration', 'checking_account_status', 'credit_amount',
       'A30', 'A31', 'A124', 'A72', 'A141', 'A151', 'A201', 'A153', 'A92',
       'installment_rate', 'A102', 'A142', 'A91', 'A32', 'A174', 'A71'],
      dtype='object')
cm = np.corrcoef(data[cols].values.T)

hm = plt.subplots(figsize=(10,10))  # 调整画布大小
hm = sns.heatmap(data[cols].corr(),  # 前10个属性的相关系数
                 annot=True, 
                 square=True)
plt.show()

筛选相关系数绝对值大于0.1的变量

threshold = 0.1

corrmat = data.corr()
top_corr_features = corrmat.index[abs(corrmat["customer_type"]) > threshold]

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

g = sns.heatmap(data[top_corr_features].corr(),  # 大于0.5的特征构成的DF的相关系数矩阵
                annot=True,
                square=True,
                cmap="nipy_spectral_r"
               )

新数据建模

# 筛选出为True的特征
useful_col = corrmat.index[abs(corrmat["customer_type"]) > threshold].tolist()  
new_df = df[useful_col]
new_df.head()

数据切分

# 选取特征
X  = new_df.drop("customer_type",axis=1)

# 目标变量
y = new_df['customer_type']
# 3-7比例
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.3, random_state=42)

标准化

ss = StandardScaler()
X_train = ss.fit_transform(X_train)
# 分别求出训练集的均值和标准差

mean_ = ss.mean_  # 均值
var_ = np.sqrt(ss.var_)  # 标准差

# 归一化之后的测试集中的特征数据

X_test = (X_test - mean_) / var_

建模

from xgboost.sklearn import XGBClassifier
## 定义 XGBoost模型 
clf = XGBClassifier()
clf.fit(X_train, y_train)
XGBClassifier(base_score=0.5, booster='gbtree', colsample_bylevel=1,
              colsample_bynode=1, colsample_bytree=1, gamma=0, gpu_id=-1,
              importance_type='gain', interaction_constraints='',
              learning_rate=0.300000012, max_delta_step=0, max_depth=6,
              min_child_weight=1, missing=nan, monotone_constraints='()',
              n_estimators=100, n_jobs=0, num_parallel_tree=1, random_state=0,
              reg_alpha=0, reg_lambda=1, scale_pos_weight=1, subsample=1,
              tree_method='exact', validate_parameters=1, verbosity=None)

In [80]:

# 先转成数组再传进来
X_test = X_test.values

y_pred = clf.predict(X_test)
y_pred[:5]

Out[80]:

array([2, 1, 2, 2, 1])

In [81]:

# 混淆矩阵
confusion_mat = metrics.confusion_matrix(y_test,y_pred)
confusion_mat

Out[81]:

array([[406,  94],
       [ 96, 104]])

In [82]:

## auc-roc

auc_roc = metrics.roc_auc_score(y_test, y_pred)  # 真实值和预测值
auc_roc

Out[82]:

0.666

优化方向

经过3种不同树模型的建模,我们发现模型的AUC值并不是很高。AUC 值是一个概率值,AUC 值越大,分类算法越好。可以考虑优化的方向:

  1. 特征工程处理:这个可以重点优化。目前对原始的特征变量使用了3种不同类型编码、独热码和硬编码;有些字段的编码方式需要优化。
  2. 筛选变量:相关系数是用来检测两个连续型变量之间线性相关的程度;特征变量和最终因变量的关系不一定线性相关。本文中观察到相关系数都很低,似乎佐证了这点。后续考虑通过其他方法来筛选变量进行建模
  3. 模型调优:通过网格搜索等优化单个模型的参数,或者通过模型融合来增强整体效果。

数据集获取

关注公众号【尤而小屋】,回复德国即可领取本文数据集。

Guess you like

Origin juejin.im/post/7120845817841188894