import numpy as np
import pandas as pd
import warnings
warnings.filterwarnings("ignore") # 忽略警告信息
# 加载数据集,第一行是字符串,所以要skiprows=1跳过第一行
loans_2020 = pd.read_csv('LoanStats3a.csv', skiprows=1)
print(loans_2020.info()) # 展示概览信息
print(loans_2020.describe()) # 展示数值类型字段的统计信息
print(loans_2020.sample(5)) # 随机抽样10条数据
print(loans_2020.shape) # 展示形状信息
-----------------------------------
(39535, 111)
首先删除空白值超过一半的列
# 剔除空白值超过一半的列,thresh=n:至少有n个NaN的列才会去除
loans_2020 = loans_2020.dropna(thresh=len(loans_2020) / 2 , axis=1)
# 输出到“loans_2020.csv”文件 , index=False表示不加索引
loans_2020.to_csv('loans_2020.csv', index=False)
重新加载数据集,查看数据信息:
loans_2020 = pd.read_csv("loans_2020.csv")
print(loans_2020.shape)
------------------------------- # 去除多余的列之后,剩余54列
(39535, 54)
print("第一行的数据展示 \n",loans_2020.iloc[0]) #第一行的数据
进一步分析数据,继续删除无用的列(描述、URL、id等对模型建立没有什么作用的字段):
"""
desc:贷款描述
url:URL链接
id:用户ID
member_id:会员编号
funded_amnt:承诺给该贷款的总金额
funded_amnt_inv:投资者为该贷款承诺的总金额
grade:贷款等级。贷款利率越高,则等级越高
sub_grade:贷款子等级
emp_title:工作名称
issue_d:贷款月份
...
"""
loans_2020 = loans_2020.drop(['desc', 'url',"id", "member_id", "funded_amnt", "funded_amnt_inv", "grade", "sub_grade", "emp_title", "issue_d","zip_code", "out_prncp", "out_prncp_inv", "total_pymnt", "total_pymnt_inv", "total_rec_prncp","total_rec_int", "total_rec_late_fee", "recoveries", "collection_recovery_fee", "last_pymnt_d", "last_pymnt_amnt"], axis=1)
print(loans_2020.shape)
------------------------------ # 进一步去除多余的列之后,剩余32列
(39535, 32)
进一步分析,去掉特征中只有一种属性的列(在原始数据中的特征值只有一种属性的话,对于分类模型的预测是没有用的):
orig_columns = loans_2020.columns # 展现出所有的列
drop_columns = [] # 初始化空列表
for col in orig_columns:
# dropna()先删除空值,再找出唯一的属性,否则加上空值就是两个属性了
col_series = loans_2020[col].dropna().unique() # 去重,找出唯一属性的特征
if len(col_series) == 1: #如果该特征的属性只有一个属性,就给过滤掉该特征
drop_columns.append(col)
loans_2020 = loans_2020.drop(drop_columns, axis=1) # 去除特征中只有一种属性的列
print(drop_columns)
print(loans_2020.shape)
-----------------------
['initial_list_status', 'collections_12_mths_ex_med', 'policy_code', 'application_type', 'acc_now_delinq', 'chargeoff_within_12_mths', 'delinq_amnt', 'tax_liens']
(39535, 24) # 去掉特征中只有一种属性的列之后,剩余24列
在原始数据中,没有0或者1的还不还款的特征,但是有“loan_status”这个特征,意思是当前“贷款的状态” ,可将贷款状态当作是标签。
首先查看一下贷款状态的信息:
print(loans_2020['loan_status'].value_counts()) # 计算该列特征的属性的个数
"""
Fully Paid:全部还清的贷款,后面给他打个“1”
Charged Off:违约的贷款,后面给他打个“0”
Late (16-30 days) :延期了16-30 days
Late (31-120 days):延期了31-120 days , 所以这些都不确定的属性,相当于“取保候审”
"""
--------------------------------------------
Fully Paid 33693
Charged Off 5612
Current 201
Late (31-120 days) 10
In Grace Period 9
Late (16-30 days) 5
Default 1
Name: loan_status, dtype: int64
主要考虑的是全部还清和违约的贷款信息,将其映射成二分类:
loans_2020 = loans_2020[(loans_2020['loan_status'] == "Fully Paid") |
(loans_2020['loan_status'] == "Charged Off")]
# 将全部还清和违约的贷款信息映射成一个二分类,用0,1 表示
status_replace = {
"loan_status": {
"Fully Paid": 1, # 完全还清
"Charged Off": 0, # 违约
}
}
loans_2020 = loans_2020.replace(status_replace) # replace:执行的是查找并替换的操作
查看一下转换的效果,并输出到“filtered_loans_2020.csv”文件
print(loans_2020['loan_status'].value_counts())#计算该列特征的属性的个数
loans_2020.to_csv('filtered_loans_2020.csv', index=False)
重新加载数据集,首先查看缺失值情况:
loans = pd.read_csv('filtered_loans_2020.csv')
null_counts = loans.isnull().sum() # 用pandas的isnull统计一下每列的缺失值,给累加起来
print(null_counts)
------------------------------------------
loan_amnt 0
term 0
int_rate 0
installment 0
emp_length 1073
home_ownership 0
annual_inc 0
verification_status 0
loan_status 0
pymnt_plan 0
purpose 0
title 11
addr_state 0
dti 0
delinq_2yrs 0
earliest_cr_line 0
inq_last_6mths 0
open_acc 0
pub_rec 0
revol_bal 0
revol_util 50
total_acc 0
last_credit_pull_d 1
pub_rec_bankruptcies 449
dtype: int64
从统计出的结果可以看出‘title’、‘revol_util’和’last_credit_pull_d’ 相对于数据总量来说较少,可以直接去掉缺失值所在的行。而‘pub_rec_bankruptcies ’和’emp_length’缺失值较多,需要根据它们的数据类型进行相应的缺失值填充,所以先查看它们的数据类型:
print(loans['emp_length'].dtypes)
print(loans['pub_rec_bankruptcies'].dtypes)
--------------------
object
float64
根据它们的数据类型进行相应的缺失值填充,并删除数据量较少的缺失值:
# 使用中位数填充数值变量
loans.fillna({"pub_rec_bankruptcies": loans["pub_rec_bankruptcies"].median()}, inplace=True)
# 使用众数填充类别变量
loans.fillna({"emp_length": loans["emp_length"].mode().iloc[0]}, inplace=True)
loans = loans.dropna(axis=0) # 删除其余缺失值
loans.isnull().sum() # 查看处理之后的效果
首先查看数据类型:
print(loans.dtypes.value_counts())
----------------------------------
object 12
float64 11
int64 1
dtype: int64
查看一下object类型的数据:
# Pandas里select_dtypes只选定“object”的类型,然后进行数据查看
object_columns_df = loans.select_dtypes(include=["object"])
print(object_columns_df.iloc[0])
---------------------------------------
term 36 months
int_rate 10.65%
emp_length 10+ years
home_ownership RENT
verification_status Verified
pymnt_plan n
purpose credit_card
title Computer
addr_state AZ
earliest_cr_line Jan-85
revol_util 83.70%
last_credit_pull_d Nov-16
Name: 0, dtype: object
"""
term:分期多少个月
int_rate:利息,10.65%,后面还要把%去掉
emp_length:10年的映射成10,9年的映射成9
home_ownership:房屋所有权,是租的、还是自己的、还是抵押出去了,那就用0 1 2来代替
...
"""
仔细分析一下数据,发现还有一些数据列可以进行删除:
"""
last_credit_pull_d:LC撤回最近的月份
earliest_cr_line:第一次借贷时间
addr_state:家庭邮编
title:标题,内容和"purpose表达的意思相近,保留一个即可
pymnt_plan:是否已为贷款实施付款计划,里面都为N,可直接删除
"""
loans = loans.drop(
["last_credit_pull_d", "earliest_cr_line", "addr_state", "title", "pymnt_plan"], axis=1)
使用 Label Encoding 进行数据的转换:
# 包含%的,直接删除%,然后再转换类型即可
loans["int_rate"] = loans["int_rate"].str.rstrip("%").astype("float")
loans["revol_util"] = loans["revol_util"].str.rstrip("%").astype("float")
# emp_length做成字典,emp_length当做key ,value里还是字典 ,"10+ years": 10...
mapping_dict = {
"emp_length": {
"10+ years": 10,
"9 years": 9,
"8 years": 8,
"7 years": 7,
"6 years": 6,
"5 years": 5,
"4 years": 4,
"3 years": 3,
"2 years": 2,
"1 year": 1,
"< 1 year": 0,
"n/a": 0
}
}
# 调用replace函数,进行类型转换
loans = loans.replace(mapping_dict)
使用 One-Hot Encoding 进行剩余数据的转换,此处选择使用pandas的get_dummies()函数,直接映射为数值型:
"""
home_ownership:房屋所有权
verification_status:身份保持证明
emp_length:客户公司名称
purpose:贷款的意图
term:贷款分期的时间
"""
cat_columns = ["home_ownership", "verification_status", "emp_length", "purpose", "term"]
dummy_df = pd.get_dummies(loans[cat_columns]) # pd.get_dummies() 实现独热编码
loans = pd.concat([loans, dummy_df], axis=1) # concat() 连接处理之后的列,
loans = loans.drop(cat_columns, axis=1) # 将原有的列删除
查看一下现在数据的形状,并将其输出到“cleaned_loans_2020.csv”文件 :
print(loans.shape)
-------------------- # 列数变多是因为进行独热编码之后,一列会变成多列
(39243, 49)
print(loans.dtypes.value_counts()) # 查看现在数据的类型
-------------------
uint8 35
float64 13
int64 1
dtype: int64
loans.to_csv('cleaned_loans_2020.csv', index=False)
查看是否有重复值,有的话直接删除:
print(loans.duplicated().sum()) # 查看重复值的数量
-------------------------------- # 没有重复值,不需要处理
0
使用逻辑回归算法进行模型训练:
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
loans = pd.read_csv("cleaned_loans_2020.csv") # 加载处理好之后的数据集
train_cols = loans.columns.drop("loan_status") # 删除loan_status这一列
X = loans[train_cols] # 特征值
y = loans["loan_status"] # loan_status作为目标值
# 切分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.20, random_state=0)
lr = LogisticRegression()
lr.fit(X_train, y_train) # 开始训练
y_hat = lr.predict(X_test) # 开始预测
y_hat[:10] # 0:代表没有偿还 1:代表偿还
-----------------------------------
array([1, 1, 1, 1, 1, 1, 1, 1, 1, 1], dtype=int64)
lr.predict_proba(X_test) # 获取预测的概率值,包含数据属于每个类别的概率。
------------------------------------------------------
array([[0.20101861, 0.79898139],
[0.22535089, 0.77464911],
[0.15100305, 0.84899695],
...,
[0.21337007, 0.78662993],
[0.16564957, 0.83435043],
[0.11056326, 0.88943674]])
建立混淆矩阵,查看模型预测情况:
from sklearn.metrics import confusion_matrix
# 传入真实值与预测值,创建混淆矩阵。
matrix = confusion_matrix(y_true=y_test, y_pred=y_hat)
print(matrix)
--------------------------------
[[ 3 1067]
[ 3 6776]]
从以上结果可以看出,假正例和真正例比例约为1:6.7,说明预测错误的正例太多了,模型效果不好,需要进一步进行处理。
分析原因可能为样本不均衡造成的,考虑权重后使用逻辑回归进行训练:
"""
class_weight:可以调整正反样本的权重
balanced:希望正负样本平衡一些
"""
lr = LogisticRegression(class_weight="balanced")
lr.fit(X_train, y_train) # 开始训练
y_hat = lr.predict(X_test) # 开始预测
matrix = confusion_matrix(y_true=y_test, y_pred=y_hat)
print(matrix)
-------------------------------
[[ 668 402]
[2963 3816]]
考虑权重之后,假正例和真正例比例情况有所改善,但是假负例太多了,说明很多能够还款的客户都预测为不能还款,这样就损失了一些客户,结果也是不好的,可以看一下AOC值:
from sklearn.metrics import roc_curve
from sklearn.metrics import auc
probo = lr.predict_proba(X_test) # 使用概率来作为每个样本数据的分值
fpr, tpr, thresholds = roc_curve(y_true=y_test, y_score=probo[:, 1], pos_label=1)
print("AUC面积值:", auc(fpr, tpr))
-----------------------------------
AUC面积值: 0.6307932827188969
可以看出AUC值也不是很大,效果一般,可以考虑使用其它算法进行模型的训练。
使用KNN算法进行模型训练:
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
knn = KNeighborsClassifier()
# 使用网格交叉验证方法选取合适的超参数
grid = {"n_neighbors": range(1, 20, 1), "weights": ['uniform', 'distance']}
gs = GridSearchCV(estimator=knn, param_grid=grid, scoring="accuracy", n_jobs=-1, cv=5, verbose=10, iid=True)
gs.fit(X_train, y_train)
print(" 最好的分值:", gs.best_score_)
print(" 最好的超参数组合:", gs.best_params_)
print(" 最好的超参数训练好的模型:", gs.best_estimator_)
------------------------------------
最好的分值: 0.8555456456647768
最好的超参数组合: {'n_neighbors': 19, 'weights': 'uniform'}
最好的超参数训练好的模型: KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski',
metric_params=None, n_jobs=None, n_neighbors=19, p=2,
weights='uniform')
建立混淆矩阵,查看模型预测情况:
estimator = gs.best_estimator_ # 最好的超参数训练好的模型
y_hat = estimator.predict(X_test)
matrix = confusion_matrix(y_true=y_test, y_pred=y_hat)
print(matrix)
--------------------------------------
[[ 1 1069]
[ 7 6772]]
查看AUC值:
probo = lr.predict_proba(X_test) # 使用概率来作为每个样本数据的分值
fpr, tpr, thresholds = roc_curve(y_true=y_test, y_score=probo[:, 1], pos_label=1)
print("AUC面积值:", auc(fpr, tpr))
---------------------------------
AUC面积值: 0.6269799669953802
从结果可以看出,KNN算法得出来的情况和逻辑回归差不多。
(1)调整正负样本的权重参数
(2)调整模型参数
(3)使用原数据,生成新特征。
(4)更换模型算法
(5)同时几个使用,进行模型预测,然后取最好的结果。