Python数据分析案例79——基于征信数据开发信贷风控模型

背景

虽然模型基本都是表格数据那一套了,算法都没什么新鲜点,但是本次数据还是很值得写个案例的,有征信数据,各种,个人,机构,逾期汇总.....

这么多特征来做机器学习模型应该还不错。本次带来,在实际生产业务中,拿到一大堆的表,怎么对这些数据进行合并,清洗整理,特征工程,然后机器学习建模,评估模型效果。


数据介绍

本次数据主要分这么多表格:

"contest_basic

(基础表-数据集)"

"contest_ext_crd_hd_report

(机构版征信-报告主表)"

"contest_ext_crd_cd_ln

(机构版征信-贷款)"

"contest_ext_crd_cd_lnd

(机构版征信-贷记卡)"

"contest_ext_crd_is_creditcue

(机构版征信-信用提示)"

"contest_ext_crd_is_sharedebt

(机构版征信-未销户贷记卡或者未结清贷款)"

"contest_ext_crd_is_ovdsummary

(机构版征信-逾期(透支)信息汇总)"

"contest_ext_crd_qr_recordsmr

(机构版征信-查询记录汇总)"

"contest_ext_crd_qr_recorddtlinfo

(机构版征信-信贷审批查询记录明细)"

"contest_ext_crd_cd_ln_spl

(机构版征信-贷款特殊交易)"

"contest_ext_crd_cd_lnd_ovd

(机构版征信-贷记卡逾期/透支记录)"

表格里面都有各种的详细的字段

太多了就不详细列举了。数据都放在了"原始数据"这个文件夹里面方便下面代码读取。

当然需要本文的全部案例数据和代码文件的同学还是可以参考:征信风控

实验过程与结果

  • 数据预处理(空值处理、异常值处理、变量标注、独热编码......)
  • 数据可视化
  • 特征工程
  • 实验过程
  • 实验结果

代码实现

下面开始写代码吧

导入包

python 复制代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import os,random

plt.rcParams["font.sans-serif"] = ["SimHei"]  # 设置显示中文字体
plt.rcParams["axes.unicode_minus"] = False  # 设置正常显示符号

数据的合并整理

读取合并数据文件,由于这个数据里面的征信不同,来源的数据集太多,所以文件会很乱,做机器学习首先得把他们这些数据都合并一起,就得做很多清洗和处理,下面就开始这样合并和整理的过程。

python 复制代码
#查看数据文件夹里面的所有数据文件的名称
data_lis=os.listdir('原始数据')
data_lis

#循环读取所有数据,放入列表

python 复制代码
def read_and_merge_files(folder_path):
    # 用于存储所有数据框的列表
    dataframes = []
    # 遍历文件夹中的所有文件
    for filename in os.listdir(folder_path):
        file_path = os.path.join(folder_path, filename)
        
        # 根据文件后缀使用不同的方法读取
        if filename.endswith('.csv'):
            df = pd.read_csv(file_path)
        elif filename.endswith('.tsv'):
            df = pd.read_csv(file_path, sep='\t')
        else:
            continue  # 忽略非CSV/TSV文件
        df.columns=[c.lower() for c in df.columns]
        print(f"{filename}读取完成,数据形状为{df.shape}")
        dataframes.append(df)
    return dataframes

读取所有的文件

python 复制代码
folder_path = '原始数据'  # 替换为文件夹路径
dataframes_list = read_and_merge_files(folder_path)
len(dataframes_list)

所有的数据合并需要按照report_id进行关联,但是有的表可能report_id可能会重复,所以先查看一下

python 复制代码
#查看数据信息,report_id有重复
def check_files(dataframes):
    # 确保至少有一个数据框
    if not dataframes:
        raise ValueError("No CSV or TSV files found in the directory.")
    # 依次检查所有表的report_id数量
    for i,df in enumerate(dataframes):
        duplicates = df['report_id'].duplicated().sum()
        print(f'表{data_lis[i]}中重复的 report_id 数量: {duplicates}')

查看每个表的重复情况

python 复制代码
check_files(dataframes_list) 

对这些重复数据选取最后一条进行合并,表示最新最近的情况

合并的时候不需要的字段就过滤掉

python 复制代码
unuseless=["query_date","querier",'get_time',    #查询,信息更新的日期
  "scheduled_payment_date", "scheduled_payment_amount", "actual_payment_amount", "recent_pay_date",  #本月、最近的应还什么的,没有时效性,去掉
    "finance_org", "currency",  "open_date",'end_date',    #日期类都去掉
           'first_loan_open_month','first_loancard_open_month','first_sl_open_month', 
          'loan_date','month_dw','report_create_time','payment_state', #一些ID类,编码类 也都去掉
           'id_card','loan_id','loancard_id','content' ]

def merge_files(dataframes):
    # 使用第一个表(假设是contest_basic)作为左连接底表
    main_df = dataframes[0]
    for i,df in enumerate(dataframes[1:]):
        #按照report_id 去重保留最后一个
        df=df[[col for col in df.columns if col not in unuseless]]
        df=df.drop_duplicates(subset='report_id', keep='last')
        print(f'正在合并数据表:{data_lis[1:][i]}...')
        # 按照'report_id'列进行左连接
        name=data_lis[1:][i].split('_')[-1]
        main_df = pd.merge(main_df, df, on='report_id', how='left',suffixes=('', f'_@{name}')) ## 名称重复的特征后面加上@和数据文件名称
        print(f'合并之后的数据形状{main_df.shape}')
    return main_df

使用这个函数

python 复制代码
threshold = len(df_all) * 0.5

df_all = df_all.dropna(axis=1, thresh=threshold)
df_all.shape
python 复制代码
df_all=merge_files(dataframes_list)

删除缺失率高达50%的列

python 复制代码
threshold = len(df_all) * 0.5

df_all = df_all.dropna(axis=1, thresh=threshold)
df_all.shape
python 复制代码
### 去除一些 id列(唯一编码,对模型没有用),设置report_id为索引
for c in ['id_card','loan_date','loan_id','loancard_id']:
    if c in df_all.columns:
        df_all.drop(c,axis=1,inplace=True)
df_all=df_all.set_index('report_id')

查看数据信息

python 复制代码
df_all.info()

文本的变量很多,需要处理,我们首先查看文本变量的信息

python 复制代码
### 查看文本变量的信息
df_all.select_dtypes(include='object').describe().T

##查看字符变量名称

python 复制代码
str_columns=df_all.select_dtypes(include='object').columns.to_list()
str_columns
python 复制代码
df_all.select_dtypes(exclude='object').describe().T

数据洗完了,储存一下吧

python 复制代码
### 查看了上述信息,数据合并没问题,进行储存
df_all.to_csv('合并后的数据.csv')

数据预处理

数据预处理(空值处理、异常值处理、变量标注、独热编码......)

缺失值查看

读取合并的数据

python 复制代码
df=pd.read_csv('合并后的数据.csv').set_index('report_id')
df.head(3)

查看标签y的比例

python 复制代码
df['y'].value_counts()

查看数据信息

python 复制代码
df.info()

这样看可能不全面不直观,我们观察缺失值情况直接画图就行了

python 复制代码
#观察缺失值
import missingno as msno
msno.matrix(df)

可以很清晰的看到哪些列的缺失率比较多,并且这些缺失都是某些样本造成的,可能是合并数据的时候,某些人某些身份证没有这一类的数据。

变量预处理

#取值唯一的变量删除

python 复制代码
for col in df.columns:
    if len(df[col].value_counts())==1:
        print(col)
        df.drop(col,axis=1,inplace=True)

这两个变量取值唯一就删除掉了。

#查看数值型数据,

python 复制代码
#pd.set_option('display.max_columns', 30)
df.select_dtypes(exclude=['object']).describe()

做机器学习当然需要特征越分散越好,因为这样就可以在X上更加有区分度,从而更好的分类。所以那些数据分布很集中的变量可以扔掉。我们用异众比例来衡量数据的分散程度

#计算异众比例

python 复制代码
variation_ratio_s=0.05
for col in df.select_dtypes(exclude=['object']).columns:
    df_count=df[col].value_counts()
    kind=df_count.index[0]
    variation_ratio=1-(df_count.iloc[0]/len(df[col]))
    if variation_ratio<variation_ratio_s:
        print(f'{col} 最多的取值为{kind},异众比例为{round(variation_ratio,4)},太小了,没区分度删掉')
        df.drop(col,axis=1,inplace=True)

##查看非数值型数据

python 复制代码
df.select_dtypes(exclude=['int64','float64']).describe()

基本都是可以转为类别型变量

空值处理

缺失值有很多填充方式,可以用中位数,均值,众数。 也可以就采用那一行前面一个或者后面一个有效值去填充空的

python 复制代码
#数值型变量使用均值填充
#文本型数据使用众数填充
def fill_missing_values(df):
    # 选择数值型变量并使用均值填充缺失值
    for col in df.select_dtypes(include=['int64', 'float64']).columns:
        mean_value = df[col].mean()
        df[col].fillna(mean_value, inplace=True)

    # 选择字符型变量并使用众数填充缺失值
    for col in df.select_dtypes(exclude=['int64', 'float64']).columns:
        mode_value = df[col].mode()[0]
        df[col].fillna(mode_value, inplace=True)

    return df
df=fill_missing_values(df)

查看数据信息

python 复制代码
df.info()

可以看到所有的变量没有缺失值了。

异常值处理

主要是对数值型变量,查看是否有验证的异常值处理

python 复制代码
df_number=df.select_dtypes(include=['int64', 'float64'])
python 复制代码
#X异常值处理,先标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df_number_s = scaler.fit_transform(df_number)

可视化一下看看异常值

python 复制代码
plt.figure(figsize=(20,8))
plt.boxplot(x=df_number_s,labels=df_number.columns)
#plt.hlines([-10,10],0,len(columns))
plt.show()

可以看到,很多变量都存在极大值,我们需要进行异常值处理

这个函数传入三个参数,要处理的数据框,异常值多的变量列名,还有筛掉几倍的方差。我下面选用的是10,也是就说如果一个样本数据大于整体10倍的标准差之外就筛掉。

python 复制代码
#异常值多的列进行处理
def deal_outline(data,col,n):   #数据,要处理的列名,几倍的方差
    for c in col:
        mean=data[c].mean()
        std=data[c].std()
        data=data[(data[c]>mean-n*std)&(data[c]<mean+n*std)]
        #print(data.shape)
    return data
python 复制代码
df_number=deal_outline(df_number,df_number.columns,10)
df_number.shape
python 复制代码
### 处理完成后将数据筛选给原始数据
df=df.loc[df_number.index,:]
df.shape

这样,原始3w数据减少了594条

变量标注、独热编码......

由于这里分类变量有点多,并且每个变量的类别的取值也较多,使用独热编码可能造成维度灾难,所以我们这里进行labelencode,因子化处理类别变量

python 复制代码
df_category=df.copy()
#选出列表变量
categorical_feature=df.select_dtypes(include=['category','object']).columns.to_list()
for col in categorical_feature:
    df[col]=df[col].astype('category').cat.codes    #因子化
df.shape
python 复制代码
df.info()  

可以看到所有的变量都变成数值型变量了,下面进行可视化


数据可视化

类别变量可视化

python 复制代码
### 我们对类别变量画柱状图
len(df_category.select_dtypes(exclude=['int64', 'float64']).columns)
python 复制代码
# Select non-numeric columns
non_numeric_columns = df_category.select_dtypes(exclude=['int64', 'float64']).columns
f, axes = plt.subplots(4, 4, figsize=(14,14),dpi=256)
# Flatten axes for easy iterating
axes_flat = axes.flatten()
for i, column in enumerate(non_numeric_columns):
    if i < 15:  
        sns.countplot(x=column, data=df_category, 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, 15):
    f.delaxes(axes_flat[j])

plt.tight_layout()
plt.show()

可以很清楚的看到每一个类别变量的哪些类别比较多的类别分布。

数值变量可视化

python 复制代码
#画密度图,
num_columns = df.select_dtypes(include=['int64', 'float64']).columns.tolist() # 列表头
dis_cols = 4                   #一行几个
dis_rows = len(num_columns)
plt.figure(figsize=(3 * dis_cols, 2 * dis_rows),dpi=256)
 
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()

具体的结论就不一个变量一个变量的看了,可以很清楚的看到每一个变量的一些分布的特点。例如他们基本都是右偏分布,也就是说具有一些极大值。

和y进行变量之间的相关性研究

python 复制代码
# 将数据按 'edu_level' 和 'y' 进行分组,然后计数
edu_vs_y = df_category.groupby(['edu_level', 'y']).size().unstack()

# 将数据按 'marry_status' 和 'y' 进行分组,然后计数
marry_vs_y = df_category.groupby(['marry_status', 'y']).size().unstack()

# 创建子图
fig, axes = plt.subplots(1, 2, figsize=(12, 4), dpi=128)

# 绘制第一个子图的分组条形图
edu_vs_y.plot(kind='bar', stacked=False, ax=axes[0])
axes[0].set_title('不同教育水平的违约情况')
axes[0].set_xlabel('教育水平')
axes[0].set_ylabel('计数')
axes[0].legend(title='是否违约', loc='upper right')

# 绘制第二个子图的分组条形图
marry_vs_y.plot(kind='bar', stacked=False, ax=axes[1])
axes[1].set_title('不同婚姻状态的违约情况')
axes[1].set_xlabel('婚姻状态')
axes[1].set_ylabel('计数')
axes[1].legend(title='是否违约', loc='upper right')

# 调整子图之间的间距
plt.tight_layout()

# 显示图形
plt.show()

看不出明显的区别

计算相关性系数的热力图

python 复制代码
corr = plt.subplots(figsize = (18,16),dpi=128)
corr= sns.heatmap(df.corr(method='spearman'),annot=True,square=True)

变量有点多,所以说这里就不详细写哪些变量和哪些变量之间的相关性比较高了。


特征工程

前面对数据已经进行了很多预处理了,现在就是要对数据进行进行标准化

#首先取出X和y

python 复制代码
X=df.drop('y',axis=1)
y=df['y']

标准化

python 复制代码
# 进行标准化
#数据标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X)
X_s = scaler.transform(X)
print('标准化后数据形状:')
print(X_s.shape,y.shape)

实验过程

机器学习------模型选择

我们首先定义分类问题所使用的评价指标:准确率,精准度,召回率,F1值

定义评价指标
python 复制代码
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
训练集测试集划分
python 复制代码
#划分训练集和测试集
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的黑白占比

python 复制代码
y_train.value_counts(normalize=True)
python 复制代码
y_test.value_counts(normalize=True)

两者比例是类似的,不过样本很不平衡,取值为1的太少了,所以我们要做一点采样处理。

在处理极度不平衡的分类样本时,可以考虑以下几种方法: 欠采样(Undersampling):从多数类别中随机选择一部分样本,使得多数类别的样本数量与少数类别的样本数量相近。这种方法的优点是简单快捷,但可能会丢失一些有用信息。

过采样(Oversampling):从少数类别中随机复制一些样本,使得少数类别的样本数量与多数类别的样本数量相近。这种方法的优点是可以充分利用数据集,但可能会导致过拟合。

SMOTE(Synthetic Minority Over-sampling Technique)算法:是一种常用的过采样方法,它通过对少数类别样本进行插值生成新的样本来扩充数据集。这种方法可以有效地避免过拟合问题。

混合采样(Mixed Sampling):结合欠采样和过采样的优点,既可以减少数据量,又可以充分利用数据集。可以先进行欠采样,然后再对欠采样后的数据进行过采样。

我们首先对样本少的类别,即逾期类别,取值为1 的进行上采样。

python 复制代码
from imblearn.over_sampling import RandomOverSampler
os=RandomOverSampler(sampling_strategy=0.1)
X_train_ns,y_train_ns=os.fit_resample(X_s,y)
print("The number of classes before fit {}".format(y.value_counts().to_dict()))
print("The number of classes after fit {}".format(y_train_ns.value_counts().to_dict()))
python 复制代码
X_train_ns.shape,y_train_ns.shape

再对样本多的进行下采样,比例为0.2,即0类数量8成,1类数量2成。虽然也不平衡,但是比刚刚那个好多了。。也能训练了。

python 复制代码
from imblearn.under_sampling import RandomUnderSampler
rus = RandomUnderSampler(sampling_strategy=0.25)
X_resampled, y_resampled = rus.fit_resample(X_train_ns, y_train_ns)
X_train_ns2,y_train_ns2=rus.fit_resample(X_train_ns,y_train_ns)
print("The number of classes before fit {}".format(y_train_ns.value_counts().to_dict()))
print("The number of classes after fit {}".format(y_train_ns2.value_counts().to_dict()))

查看形状

python 复制代码
print(X_train_ns2.shape,y_train_ns2.shape)
python 复制代码
##再次重新划分训练集和测试集
X_train,X_test,y_train,y_test=train_test_split(X_train_ns2,y_train_ns2,stratify=y_train_ns2,test_size=0.2,random_state=0)
print(X_train.shape,X_test.shape,y_train.shape,y_test.shape)
python 复制代码
### 查看训练和测试集的y的黑白占比
y_train.value_counts(normalize=True)
python 复制代码
y_test.value_counts(normalize=True)

比例大概8:2,比刚刚严重不平衡好很多,可以进行机器学习了


模型训练

python 复制代码
#导包
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

实例化分类器:

python 复制代码
#逻辑回归
model1 =  LogisticRegression(C=1e10,max_iter=10000)
 
#线性判别分析
model2 = LinearDiscriminantAnalysis()
 
#K近邻
model3 = KNeighborsClassifier(n_neighbors=10)
 
#决策树
model4 = DecisionTreeClassifier(random_state=77)
 
#随机森林
model5= RandomForestClassifier(n_estimators=1000,  max_features='sqrt',random_state=10)
 
#梯度提升
model6 = GradientBoostingClassifier(random_state=123)

#极端梯度提升
model7 =  XGBClassifier(objective='binary:logistic',random_state=1)

#轻量梯度提升
model8 = LGBMClassifier(objective='binary',random_state=1,verbose=-1)

#支持向量机
model9 = SVC(kernel="rbf", random_state=77)
 
#神经网络
model10 = MLPClassifier(hidden_layer_sizes=(16,8), random_state=77, max_iter=10000)
 
model_list=[model1,model2,model3,model4,model5,model6,model7,model8,model9,model10]
model_name=['逻辑回归','线性判别','K近邻','决策树','随机森林','梯度提升','极端梯度提升','轻量梯度提升','支持向量机','神经网络']

训练和评估

python 复制代码
df_eval=pd.DataFrame(columns=['Accuracy','Precision','Recall','F1_score'])
for i in range(10):
    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}模型完成')

查看评价指标

python 复制代码
df_eval

可视化看看

python 复制代码
import matplotlib.pyplot as plt 
plt.rcParams['font.sans-serif'] = ['KaiTi']  #中文
plt.rcParams['axes.unicode_minus'] = False   #负号
 
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()

可以看到随机森林模型效果最好,我们选择他作为最终模型

超参数搜索

简单网格化定义几个超参数,搜索一下。对模型的效果进行一定的优化。

水系森林超凡树不太多,我们这里就搜索一下最影响最大的估计器个数吧。

python 复制代码
# 网格化搜索最优超参数
from sklearn.model_selection import KFold, train_test_split, GridSearchCV
rf_model = RandomForestClassifier(max_features='sqrt',random_state=10)
param_dict = { 'n_estimators': [100,500,700]}
clf = GridSearchCV(rf_model, param_dict, verbose=1,cv=3)
clf.fit(X_train, y_train)
print(clf.best_score_)
print(clf.best_params_)

将搜索出来的这个超参数代入模型去训练

python 复制代码
model=RandomForestClassifier( n_estimators=700   , max_features='sqrt',random_state=10)
model.fit(X_train, y_train)
y_pred=model.predict(X_test)
evaluation(y_test,y_pred)

超参数 可以看到整体的准确率精准度召回率f1值,都稍微提高了一些。


实验结果

AUC,ROC,KS

信贷场景是一个经典的2分类问题,就是判断它是不是会违约,从而做出要不要给他贷款的决策。2分类问题就逃不开要计算roc曲线,计算auc值和ks。 用如下的代码画出roc曲线跟pr曲线。

python 复制代码
from sklearn.metrics import roc_curve, auc, precision_recall_curve
y_pred_proba = model.predict_proba(X_test)[:, 1]
# 计算ROC曲线和AUC值
fpr, tpr, _ = roc_curve(y_test, y_pred_proba)
roc_auc = auc(fpr, tpr)
# 计算PR曲线
precision, recall, _ = precision_recall_curve(y_test, y_pred_proba)
 
# 创建1*2的子图
plt.figure(figsize=(10, 4),dpi=128)
 
# 绘制ROC曲线
plt.subplot(1, 2, 1)
plt.plot(fpr, tpr, color='tomato', lw=2, label='AUC = %0.2f' % roc_auc)
plt.plot([0, 1], [0, 1], color='k', lw=1, 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 (ROC) Curve')
plt.legend(loc="lower right")
 
# 绘制PR曲线
plt.subplot(1, 2, 2)
plt.plot(recall, precision, color='skyblue', lw=2)
plt.xlim([0.0, 1.0])  ;  plt.ylim([0.0, 1.05])
plt.xlabel('Recall')  ;  plt.ylabel('Precision')
plt.title('Precision-Recall (PR) Curve')
 
# 显示图像
plt.tight_layout()
plt.show()

很漂亮的曲线,效果很好,不过也可能是上采样和下采样导致的有些样本重复。

画KS图:

python 复制代码
import scikitplot as skplt
skplt.metrics.plot_ks_statistic(y_test,model.predict_proba(X_test))
plt.show()

模型的AUC0.92, KS0.674,说明模型效果很好,对好坏客户有很强的区分能力

进一步的去观察他每个预测的概率区间里面有多少个样本,多少个好样本,坏样本,坏样本的比例有多少,它的提升度有多少,累积的这个坏样本的百分比有多少。

自定义函数

python 复制代码
def calculate_pred_proba_bin(true_labels,predictions, bins=10):
    # 创建分箱区间
    bin_edges = np.linspace(0, 1, bins + 1)
    # 分箱
    bin_labels = [f"{bin_edges[i]:.2f}-{bin_edges[i+1]:.2f}" for i in range(len(bin_edges)-1)]
    bin_indices = np.digitize(predictions, bin_edges, right=False) - 1
    
    # 创建数据框
    df = pd.DataFrame({ 'bin': [bin_labels[i] for i in bin_indices], 'label': true_labels })
    
    # 统计各个分箱的总数、类别为0和1的样本数
    result = df.groupby('bin')['label'].agg(total='count',
        count_0=lambda x: (x == 0).sum(),
        count_1=lambda x: (x == 1).sum()).reset_index()
    
    # 计算坏样本率和坏样本在所有坏样本中的比例
    total_bad_samples = result['count_1'].sum()
    result['bad_rate'] = result['count_1'] / result['total']
    result['bad_percent'] = result['count_1'] / total_bad_samples
    #result=result.sort_values('bin',ascending=False)
    result['lift']=result['bad_rate']/(total_bad_samples/result['total'].sum())
    result['cumulative_bad_percent'] = result['bad_percent'][::-1].cumsum()[::-1]
    return result.style.bar(color='skyblue').format(subset=['bad_rate','bad_percent','lift','cumulative_bad_percent'], precision=4)
 
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'])
python 复制代码
calculate_pred_proba_bin(y_test,y_pred_proba, bins=20)

基本上随着模型的概率越来越高,模型预测正确的把握越来越大,其提升度也就是每一箱的这个黑样本的比例也是越来越高的。说明模型其性能没有什么太多的问题。

如果要进行使用的话建议是可以模型预测概率为0.6区间以上的全部拒绝掉,可以过滤到50%的黑样本,0.15到0.5之间的样本再进行一次审查,0.15以下的贷款可以接受.

相关推荐
F_D_Z34 分钟前
【PyTorch】图像二分类项目
人工智能·pytorch·深度学习·分类·数据挖掘
Yweir1 小时前
Elastic Search 8.x 分片和常见性能优化
java·python·elasticsearch
小蜗牛狂飙记2 小时前
在github上传python项目,然后在另外一台电脑下载下来后如何保障成功运行
开发语言·python·github
倔强青铜三2 小时前
苦练Python第27天:嵌套数据结构
人工智能·python·面试
倔强青铜三2 小时前
苦练Python第26天:精通字典8大必杀技
人工智能·python·面试
望百川归海3 小时前
基于自定义数据集微调SigLIP2-分类任务
人工智能·分类·数据挖掘
creator_Li3 小时前
python学习笔记
笔记·python·学习
DeniuHe3 小时前
基于Pytorch的人脸识别程序
pytorch·python·深度学习
万粉变现经纪人4 小时前
如何解决pip安装报错ModuleNotFoundError: No module named ‘django’问题
后端·python·pycharm·django·numpy·pandas·pip
仰望星空的凡人4 小时前
【JS逆向基础】数据库之mysql
javascript·数据库·python·mysql