背景
好久没写这种基础的做机器学习流程了,最近过完年感觉自己代码忘了好多.....复习一下。
本次带来的是信贷违约的预测,即根据这个人的特征(年龄收入什么的),预测他是不是会违约,会违约就拒绝贷款,不会就给他贷款。
所以这是一个二分类的问题。
数据介绍
本次数据的主要变量和其含义如下:
数据使用csv储存:
第一列是用户的编号id,最后一列loan_status是借款状态,1表示违约,0表示没有违约。
需要本次数据案例的的全部代码文件和数据的csv文件的同学还是可以参考:贷款预测
代码实现
首先导入数据分析常用的包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
plt.rcParams['font.sans-serif'] = ['KaiTi'] #指定默认字体 SimHei黑体
plt.rcParams['axes.unicode_minus'] = False #解决保存图像是负号'
读取数据,展示前五行:
df=pd.read_csv('train.csv').set_index('id')
df.head()
没什么问题,下面对数据做一下描述性统计。
描述性统计
df.info()
可以清楚的查看到每个变量的数据类型和非空数量。
查看缺失值数量
## 缺失值数量
df.isna().sum()
可以看到所有变量均不存在缺失值,
对非数值型变量进行描述性统计计算
df.select_dtypes(exclude=['number']).describe().T
可以看到,在住房拥有情况(person_home_ownership)方面,共有58645个样本,其中唯一值为4,最常见的值为RENT,出现频数为30594,表明大多数借款人选择租房。其次,关于贷款目的(loan_intent),同样有58645个样本,唯一值为6,最常见的目的为EDUCATION,频数为12271,说明教育目的的贷款需求相对较高。此外,在贷款等级(loan_grade)中,样本数为58645,唯一值为7,最常见的等级为A,频数为20984,这表明借款人中较多具有较好的信用等级,反映出贷款申请者的整体信用水平较高。最后,在是否有违约记录(cb_person_default_on_file)这一变量中,样本数同样为58645,唯一值为2,最常见的记录为N,频数为49943,显示出大多数借款人没有违约记录。
数值型变量进行描述性统计
df.select_dtypes(include=['number']).describe().round(3).T
在年龄(person_age)方面,共有58645个样本,平均年龄为27.55岁,标准差为6.03,最小值为20岁,最大值为123岁;年龄的分布呈现出较大的离散性,可能存在数据录入错误或特殊情况。其次,借款人的年收入(person_income)平均为64046.17,标准差为37931.11,最小值为4200,最大值为1900000,表明借款人的收入差异显著,部分个体的收入水平远高于平均水平,这可能影响其贷款申请的额度和利率。此外,借款人的工作年限(person_emp_length)平均为4.70年,标准差为3.96,最小值为0,最大值为123年,显示出相对较大的变动性,部分借款人可能处于短期就业状态。贷款金额(loan_amnt)平均为9217.56,标准差为5563.81,最小值为500,最大值为35000,表明贷款金额的分布较为集中,但仍有部分高额贷款存在。关于贷款利率(loan_int_rate),平均为10.68,标准差为3.04,最小值为5.42,最大值为23.22,反映出贷款利率的变化范围较广,可能与借款人的信用评级和贷款目的相关。贷款金额占收入的比例(loan_percent_income)平均为0.159,标准差为0.092,最小值为0,最大值为0.83,说明大部分借款人的贷款金额占其年收入的比例相对合理,然而仍有个别情况可能导致借款人承担较高的债务风险。信用历史长度(cb_person_cred_hist_length)平均为5.81年,标准差为4.03,最小值为2年,最大值为30年,表明借款人信用历史的多样性,可能影响其贷款申请的批准情况。最后,贷款状态(loan_status)的平均值为0.142,标准差为0.349,说明约14.2%的借款人存在违约风险,
响应变量:
## 响应变量
df['loan_status'].value_counts().plot.bar(figsize=(5,3))
可以看到大部分人都是没意义违约的,0居多,1偏少,这个类别不是很均衡,只有14.2%的1。
类别变量可视化
对于非数值型的几个数据,都是分类型的变量,直接使用柱状图进行可视化
# Select non-numeric columns
non_numeric_columns = df.select_dtypes(exclude=['number'])
f, axes = plt.subplots(2, 2, figsize=(5,5),dpi=128)
# Flatten axes for easy iterating
axes_flat = axes.flatten()
for i, column in enumerate(non_numeric_columns):
if i < 9:
sns.countplot(x=column, data=df, ax=axes_flat[i])
axes_flat[i].set_title(f'Count of {column}')
for label in axes_flat[i].get_xticklabels():
label.set_rotation(90) #类别标签旋转一下,免得多了堆叠看不清
#Hide any unused subplots
for j in range(i + 1, 4):
f.delaxes(axes_flat[j])
plt.tight_layout()
plt.show()
其分析和上面的描述性统计差不多。
数值变量可视化
对于数值型变量,采用密度曲线进行可视化:
#画密度图,
num_columns = df.select_dtypes(include=['number']).columns
dis_cols = 4 #一行几个
dis_rows = len(num_columns)
plt.figure(figsize=(3 * dis_cols, 2 * dis_rows),dpi=128)
for i in range(len(num_columns)):
ax = plt.subplot(dis_rows, dis_cols, i+1)
ax = sns.kdeplot(df[num_columns[i]], color="skyblue" ,fill=True)
ax.set_xlabel(num_columns[i],fontsize = 14)
plt.tight_layout()
#plt.savefig('训练测试特征变量核密度图',formate='png',dpi=500)
plt.show()
分析和前面的描述性统计类似。
不同变量之间的分析
我们想观察不同的loan_status,也就是不同的响应变量,是否会违约的人群里面,其他的特征分分布是否有较大差异,我们对不同类别进行分组,得到的的数值型数据画箱线图对比:
def plot_distribution_comparison(df, features, response, plot_type='violin', dis_cols=3):
"""
绘制每个特征在不同响应变量取值下的分布对比图。
参数:
df (pd.DataFrame): 数据框
features (list): 要对比的特征列表
response (str): 响应变量名称
plot_type (str): 绘图类型,'violin' 或 'box'
dis_cols (int): 一行显示的子图数量
"""
n_features = len(features)
# 计算需要的行数
dis_rows = (n_features + dis_cols - 1) // dis_cols
# 创建子图
fig, axes = plt.subplots(dis_rows, dis_cols, figsize=(5 * dis_cols, 4 * dis_rows),dpi=128)
# 如果只有一行,将 axes 转换为二维数组方便统一处理
if dis_rows == 1:
axes = axes.reshape(1, -1)
# 遍历每个特征并绘制图形
for i, feature in enumerate(features):
row = i // dis_cols # 计算当前特征所在的行
col = i % dis_cols # 计算当前特征所在的列
if plot_type == 'violin':
sns.violinplot(x=response, y=feature, data=df, ax=axes[row, col])
elif plot_type == 'box':
sns.boxplot(x=response, y=feature, data=df, ax=axes[row, col])
else:
raise ValueError("plot_type 必须是 'violin' 或 'box'")
axes[row, col].set_title(f'{feature} 分布对比')
# 如果子图数量少于总格子数,隐藏多余的子图
for i in range(n_features, dis_rows * dis_cols):
row = i // dis_cols
col = i % dis_cols
fig.delaxes(axes[row, col])
plt.tight_layout()
plt.show()
画图
# 使用箱线图
plot_distribution_comparison(df, features= num_columns[:-2], response = 'loan_status', plot_type='box')
person_age(年龄): 两个贷款状态组在年龄上的分布相似,箱线图的中心线和四分位数范围几乎重合,表明两组借款人的平均年龄和年龄分布差异不大。 person_income(收入): 收入在不同贷款状态下的分布有显著差异。未批准/偿还组的收入较低,而已批准/偿还组的收入较高且更分散。 person_emp_length(工作年限): 两组的工作年限分布较为一致,但已批准/偿还组的中位数略高一些。 loan_amnt(贷款金额): 贷款金额在两组中的分布也有明显差异。已批准/偿还组的贷款金额普遍更高。 loan_int_rate(贷款利率): 利率在两组中有重叠,但总体上已批准/偿还组的利率似乎稍低一些。 loan_percent_income(贷款占收入百分比): 这个指标在两组中的分布相对一致,显示无论贷款是否被批准或偿还,贷款额相对于收入的占比没有太大差异。 总结:从图中可以看出,贷款状态与个人收入、贷款金额等因素密切相关。已批准/偿还贷款的人通常具有更高的收入和贷款金额,这可能反映了他们的还款能力较强。同时,年龄和工作年限对贷款状态的影响较小。
相关性分析
画出所有的变量的相关系数热力图:
plt.figure(figsize=(7,4),dpi=128)
sns.heatmap(df.corr(method='spearman'), annot=True, fmt=".3")
plt.show()
IV分析
IV是信贷领域特有的一个指标,越大越好,越大越说明这个变量对于响应变量的区分能力越强。
我们实现依靠scorecardpy 这个库,没安装可以直接pip安装一下,不想安装也没事,不运行这一段就行,不影响下面环节的的代码。
import scorecardpy as sc
### 变量筛选咋定义看IV的方法
def scorecardpy_display_bin(bins_info):
df_list = []
for col, bin_data in bins_info.items():
df = pd.DataFrame(bin_data)
df_list.append(df)
result_df = pd.concat(df_list, ignore_index=True)
# 增加 lift 列
total_bad = result_df['bad'].sum() ; total_count = result_df['count'].sum()
overall_bad_rate = total_bad / total_count
result_df['lift'] = result_df['badprob'] / overall_bad_rate
result_df=result_df.sort_values(['total_iv','variable'],ascending=False).set_index(['variable','total_iv','bin'])[['count_distr',
'count','good','bad','badprob','lift','bin_iv','woe']]
return result_df.style.format(subset=['count','good','bad'], precision=0).format(subset=['count_distr', 'bad','lift',
'badprob','woe','bin_iv'], precision=4).bar(subset=['badprob','bin_iv','lift'], color=['#d65f5f', '#5fba7d'])
train_miss=df.copy()
bins_adj = sc.woebin(train_miss, y="loan_status")
scorecardpy_display_bin(bins_adj)
可以看到loan_grade,loan_percent_income,loan_int_rate等变量的IV很高,也可以清楚的看到是这个变量的哪些取值的黑样本浓度高。对于这个变量取值风险高的情况,可以重点关注。
这些对于模型的可解释性也有很大的帮助。
特征工程
由于本次的数据较为整洁,没有什么缺失值,变量也没啥特殊的情况,都是可以直接用的,所以特征工程就对于非数值变量进行一下编码就好了。
非数值变量的 类别不多,直接独热编码好了
df_dummy=pd.get_dummies(df)
df_dummy.shape
取出X和y
X=df_dummy.drop(columns='loan_status')
y=df_dummy['loan_status']
X.shape,y.shape
查看y的比例:
y.value_counts()
由于我们要用很多机器学习模型,所以还是对数据进行一下标准化
# 进行标准化
#数据标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X)
X_s = scaler.transform(X)
print('标准化后数据形状:')
print(X_s.shape,y.shape)
机器学习
划分训练集和测试集
num_classes=2 #2分类问题
#划分训练集和测试集
from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test=train_test_split(X_s,y,stratify=y,test_size=0.2,random_state=0)
print(X_train.shape,X_test.shape,y_train.shape,y_test.shape)
我们这里使用的是分层划分,保证训练集和测试集的y的分布比例是一样。可以查看一下:
### 查看训练和测试集的y的类别占比
y_train.value_counts(normalize=True)
y_test.value_counts(normalize=True)
分类问题的评价指标体系
一般大家都是用准确率,精准度,召回率,F1值进行评价,公式详情:
计算评价指标函数定义
下面来定义上面这些指标计算函数:
from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from sklearn.metrics import cohen_kappa_score
def evaluation(y_test, y_predict):
accuracy=classification_report(y_test, y_predict,output_dict=True)['accuracy']
s=classification_report(y_test, y_predict,output_dict=True)['weighted avg']
precision=s['precision']
recall=s['recall']
f1_score=s['f1-score']
#kappa=cohen_kappa_score(y_test, y_predict)
return accuracy,precision,recall,f1_score #, kappa
建立分类模型
使用十种机器学习模型进行对比,我们直接批量化
#导包
from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from xgboost.sklearn import XGBClassifier
from lightgbm import LGBMClassifier
from sklearn.svm import SVC
from sklearn.neural_network import MLPClassifier
实例化,都装入一个列表,
#逻辑回归
model1 = LogisticRegression(C=1e10,max_iter=10000)
#线性判别分析
model2 = LinearDiscriminantAnalysis()
#K近邻
model3 = KNeighborsClassifier(n_neighbors=10)
#决策树
model4 = DecisionTreeClassifier(random_state=7,max_features='sqrt',class_weight='balanced')
#随机森林
model5= RandomForestClassifier(n_estimators=300, max_features='sqrt',random_state=7)
#梯度提升
model6 = GradientBoostingClassifier(random_state=123)
#极端梯度提升
model7 = XGBClassifier(objective='binary:logistic', random_state=1, eval_metric='logloss', use_label_encoder=False)
#轻量梯度提升
model8 =LGBMClassifier(objective='binary', random_state=1, verbose=-1)
#支持向量机
model9 = SVC(kernel="rbf", random_state=0)
#神经网络
model10 = MLPClassifier(hidden_layer_sizes=(16,8), random_state=1, max_iter=10000)
model_list=[model1,model2,model3,model4,model5,model6,model7,model8,model9,model10]
model_name=['逻辑回归','线性判别','K近邻','决策树','随机森林','梯度提升','极端梯度提升','轻量梯度提升','支持向量机','神经网络']
都是很常见的10种模型,不懂的可以去问ai。
模型训练
对上面这是个模型一起循环进行训练,预测,评价。
#遍历所有的模型,训练,预测,评估
df_eval=pd.DataFrame(columns=['Accuracy','Precision','Recall','F1_score'])
for i in range(len(model_list)):
model_C=model_list[i]
name=model_name[i]
model_C.fit(X_train, y_train)
pred=model_C.predict(X_test)
#s=classification_report(y_test, pred)
s=evaluation(y_test,pred)
df_eval.loc[name,:]=list(s)
print(f'{name}模型完成')
训练完成后,我们样式化查看其评价指标的数值:
df_eval.astype('float').style.bar(color='gold')
随机森林 极端梯度提升 轻量梯度提升 效果最好(符合常识和预期)
评价指标可视化
bar_width = 0.4
colors=['c', 'b', 'g', 'tomato', 'm', 'y', 'lime', 'k','orange','pink','grey','tan']
fig, ax = plt.subplots(2,2,figsize=(10,8),dpi=128)
for i,col in enumerate(df_eval.columns):
n=int(str('22')+str(i+1))
plt.subplot(n)
df_col=df_eval[col]
m =np.arange(len(df_col))
plt.bar(x=m,height=df_col.to_numpy(),width=bar_width,color=colors)
#plt.xlabel('Methods',fontsize=12)
names=df_col.index
plt.xticks(range(len(df_col)),names,fontsize=10)
plt.xticks(rotation=40)
plt.ylabel(col,fontsize=14)
plt.tight_layout()
#plt.savefig('柱状图.jpg',dpi=512)
plt.show()
基本来说,四个指标都是一致的,我们就看F1值就好,随机森林 极端梯度提升 轻量梯度提升效果最好,很符合普遍的认知。几乎所有的数据上,这3个模型都是最强的。
接下来可以做一下模型的交叉验证,超参数搜索,但是这个数据量太大了,我就没跑了。
简单弄一个还不错的参数进行下面的分类问题的评价指标的可视化。
模型就直接使用lgbm了,虽然RF和xgboost也还不错,但是我还是喜欢用lgbm,因为他很快.....
混淆矩阵
bp={'subsample': 0.95, 'n_estimators': 500, 'min_child_samples': 10, 'max_depth': 5, 'learning_rate': 0.07, 'colsample_bytree': 0.95}
带入上面的参数进lgbm模型训练,可以自己修改调试这些参数
model = LGBMClassifier(objective='binary', random_state=1 ,**bp)
model.fit(X_train, y_train)
# 使用测试集进行预测
pred = model.predict(X_test)
# 评估模型性能
print(classification_report(y_test, pred))
evaluation(y_test,pred)
# 绘制混淆矩阵
conf_matrix = confusion_matrix(y_test, pred)
plt.figure(figsize=(5, 4),dpi=128)
sns.heatmap(conf_matrix, annot=True, fmt='d', cmap='Blues', xticklabels=model.classes_, yticklabels=model.classes_)
plt.ylabel('Actual')
plt.xlabel('Predicted')
plt.title('Confusion Matrix')
plt.show()
ROC曲线
这也是分类问题常用的评价体系
from sklearn.metrics import roc_curve, auc, confusion_matrix, classification_report
from sklearn.metrics import RocCurveDisplay
# 绘制 ROC 曲线
if len(model.classes_) == 2:
# 二分类情况
probs = model.predict_proba(X_test)[:, 1]
fpr, tpr, _ = roc_curve(y_test, probs)
roc_auc = auc(fpr, tpr)
plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC curve (area = %0.4f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic')
plt.legend(loc="lower right")
plt.show()
else:
# 多分类情况:为每个类别绘制 ROC 曲线
y_score = model.predict_proba(X_test)
for i in range(len(model.classes_)):
fpr, tpr, _ = roc_curve(y_test == i, y_score[:, i])
roc_auc = auc(fpr, tpr)
plt.plot(fpr, tpr, lw=2, label='Class %d ROC curve (area = %0.4f)' % (i, roc_auc))
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic - Multiclass')
plt.legend(loc="lower right")
plt.show()
可以看到AUC为0.959,非常高!模型效果很强。
变量重要性
树模型都可以输出变量的重要性,随机森林,xgboost,lgbm,都可以输出重要性:
sorted_index = model.feature_importances_.argsort()[::-1]
mfs=model.feature_importances_[sorted_index]
plt.figure(figsize=(6,4),dpi=128)
sns.barplot(y=np.array(range(len(mfs))),x=mfs,orient='h')
plt.yticks(np.arange(X.shape[1]), X.columns[sorted_index])
plt.xlabel('Feature Importance')
#plt.ylabel('Feature')
#plt.title('LGBM')
plt.show()
可以看到‘’个人收入‘’对于预测这个人是不是会违约最为重要,其次是 ‘’借款人申请的贷款年利率‘’,‘贷款金额占借款人年收入的比例’,也都很合理。
总结
本文简单展示了做一个机器学习项目的全流程,从数据的描述统计,可视化到机器学习的模型对比,评价指标计算等但是由于数据过于简单,所以数据不需要太多清洗,特征工程没有做很多,数据量有点大,没有做K折交叉验证和超参数搜索。但是之前的文章都有,可以去参考。