背景:现在有一些客户数据以及他们是否会购买贷款的统计,公司希望在此数据基础上。对用户数据进行分析,找出那些更加有可能购买贷款的客户,(有监督学习, 二元分类)。
说明:尽可能多地比较了常见模型
import numpy as npimport pandas as pdimport matplotlib.pyplot as pltimport seaborn as sns%matplotlib inlinesns.set() # 重置sns的默认设置sns.set_style("whitegrid") # 设置seaborn的主题样式
1.读取文件
data = pd.read_excel('Personal_Loan.xlsx','Data') # 读取文件"P_L.xlsx" 中的"Data"表data.head()
2.数据概况
data.info() # 数据概况<class 'pandas.core.frame.DataFrame'>RangeIndex: 5000 entries, 0 to 4999Data columns (total 14 columns):ID 5000 non-null int64Age 5000 non-null int64Experience 5000 non-null int64Income 5000 non-null int64ZIP Code 5000 non-null int64Family 5000 non-null int64CCAvg 5000 non-null float64Education 5000 non-null int64Mortgage 5000 non-null int64Personal Loan 5000 non-null int64Securities Account 5000 non-null int64CD Account 5000 non-null int64Online 5000 non-null int64CreditCard 5000 non-null int64dtypes: float64(1), int64(13)memory usage: 547.0 KB
data.isnull().sum() # 没有空数据
ID 0Age 0Experience 0Income 0ZIP Code 0Family 0CCAvg 0Education 0Mortgage 0Personal Loan 0Securities Account 0CD Account 0Online 0CreditCard 0dtype: int64
简要分析数据各列意义
二分类 (0/1)
- Personal Loan 是否接受贷款 (标注)
- Securities Account 是否有安全账号
- CD account 是否有存款证书
- Online 是否开通网银
- CreditCard 是否开通信用卡
定距分类 Interval (变量值可以比较大小 差值有意义)
- Age 年龄
- Experience 就职时间
- Income 收入
- CCAvg 每月的信用卡消费
- Mortgage 房屋可抵押款
定序分类 Ordinal:差值无明确意义 (例如:教育程度,有高低之分 没有高多少的含义)
- Family 家庭成员数
- Education 受教育程度
定类变量 Norminal (数值大小没有意义)
- ID 区分用户
- ZIP Code 美国的邮政编码
len(data[data.Experience < 0])
52
数据目标:
- 分析”客户是否接收贷款的二值分类问题”,通过建模,得到”数据特征”—“数据标注”的关系。
数据预处理:
异常数据识别:
- 观察到,Experience 最小值为-3 有52条记录小于0,(认为) ‘-2’ 代表2年后入职, 不予处理。只是这么认为,实际有可能是异常数据,需要剔除或者填充。
定序数据处理:
- 定序数据通常需要进行独热化 转化为 只包含0,1的矩阵形式(稀疏矩阵)。
规范化处理:
- 减弱量纲对数据的影响,进行最小最大标准化, 或者Z分数标准化。
预处理函数
def minmax(ser):"""输入Series 进行最小最大标准化"""ser = ser.agg(lambda x : (x-x.min())/(x.max()-x.min()))return ser.astype(np.float64)def zscore(ser):"""输入Series 进行Z分数标准化"""ser = ser.agg(lambda x : (x-x.mean()) / x.std())return ser.astype(np.float64)
3.数据特征了解
# 丢弃两列数据,这两列数据的数值没有意义data.drop(['ID','ZIP Code'],inplace=True, axis=1)
先画出相关性矩阵,了解各个变量之间的相关性。
corr = data.corr() # 生成相关性矩阵mask = np.zeros_like(corr) # 生成同样size的0填充矩阵mask[np.triu_indices_from(mask)] = True # 生成一个上三角矩阵 作为热力图的遮罩plt.figure(figsize=(10,8))sns.set_context(context='notebook') # seaborn调整默认元素的大小sns.heatmap(corr, mask=mask, annot=True, fmt='.2f')plt.xticks(rotation=90)

可以看出,’Personal Loan’ 和 ‘Income , CCAvg’相关性相对比较明显。
或者,我们可以过滤出相关性小于0.2的部分
plt.figure(figsize=(10,8.2))sns.set_context(context='notebook')sns.heatmap(corr,mask=corr.abs()<0.2, # 过滤掉相关性绝对值小于0.2的部分annot=True, # 打开文字注解fmt='.2f',linewidths=0.01, # 文字格式,线型线宽linecolor='black')

数据规范化
data2 = pd.get_dummies(data, columns=['Family','Education']) # 对指定列进行规范化for each in ['Age','Experience','Income','CCAvg']:data2[each] = zscore(data2[each])data2.head()
data2.columns
Index(['Age', 'Experience', 'Income', 'CCAvg', 'Mortgage', 'Personal Loan','Securities Account', 'CD Account', 'Online', 'CreditCard', 'Family_1','Family_2', 'Family_3', 'Family_4', 'Education_1', 'Education_2','Education_3'],dtype='object')
对于像Education,Family 之类的定序数据,差值没有明确的意义,Education中:3-2不等于1,只是表明不同了种类,可以进行独热化。
三个不同Education的人的表示不再是(1,2,3),而是(001,010,100)。
4.分类特征、标注,拆分训练集 验证集 测试集
label_v = data2['Personal Loan'].values # 标注的值feature_data = data2.drop('Personal Loan', axis=1) # 表示特征的矩阵feature_v = feature_data.values # 表示特征的值feature_names = feature_data.columns # 特征名称
用 sklearn 中分离数据集的方法进行拆分,
最终训练集:验证集:测试集 = 6:2:2
要求不高时,也可以直接用训练集:测试集 = 8:2
训练集:生成模型。
验证集:模型对比,调整必要的模型参数,直到在验证集上表现良好。
测试集:模型已经建立,仅测试模型的泛化能力。
from sklearn.model_selection import train_test_split # 分离 训练集 验证集 测试集X_t, X_test, Y_t, Y_test = train_test_split(feature_v, label_v, test_size=0.2) # X_test 占0.2X_train, X_val, Y_train, Y_val = train_test_split(X_t, Y_t, test_size=0.25) # X_train 占 0.6 X_val 占0.2
模型的保存
from sklearn.externals import joblib# joblib.dump(clf, name) 保存模型# joblib.load(clf) 读取模型
导入几种常见的分类模型
from sklearn.externals.six import StringIO # 文件流输出,与模型无关from sklearn.neighbors import KNeighborsClassifier # KNNfrom sklearn.naive_bayes import GaussianNB, BernoulliNB # 朴素贝叶斯 高斯贝特斯 伯努力贝叶斯from sklearn.tree import DecisionTreeClassifier, export_graphviz # 决策树from sklearn.svm import SVC # 支持向量机# 集成方法from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier # 随机森林 AdaBoostfrom sklearn.ensemble import GradientBoostingClassifier # 梯度提升决策树# 线性模型from sklearn.linear_model import LogisticRegression # 逻辑回归# 模型评估from sklearn.metrics import accuracy_score, recall_score, f1_score # 正确率 召回率 F分数
构建一个模型列表用来比较各个模型的泛化能力
models=[]models.append(("KNN", KNeighborsClassifier(n_neighbors=3))) # KNN 近邻数为3models.append(("GaussianNB", GaussianNB())) # 高斯贝叶斯models.append(("BernoulliNB", BernoulliNB())) # 伯努利贝叶斯models.append(("DecisionTreeGini", DecisionTreeClassifier())) # 默认基尼不纯度的决策树models.append(("DecisionTreeEntropy", DecisionTreeClassifier(criterion='entropy'))) # 熵增益的决策树models.append(("SVM Classifier", SVC(C=1000,gamma='scale'))) # SVCmodels.append(("RadomForest", RandomForestClassifier())) # 并联投票的随机森林models.append(("OriginalRandomForest", RandomForestClassifier(n_estimators=11, max_features=None))) # 11棵树的森林 每棵树选择特征全部models.append(("Adaboost", AdaBoostClassifier(n_estimators=100))) # 100个弱分类器串联分权的集成方法 梯度提升决策树models.append(("LogisticRegression", LogisticRegression(C=1000, tol=1e-10, solver="sag", max_iter=10000))) # 逻辑回归-随机梯度下降-最大迭代1000models.append(("GBDT", GradientBoostingClassifier(max_depth=6, n_estimators=100))) # 集成方法 GBDT 梯度提升树
绘制决策树
import re, random, time
import pydotplus # 决策树绘制
result_lst = pd.DataFrame()for clf_name, clf in models:clf.fit(X_train, Y_train) # 生成模型xy_lst = [(X_train,Y_train),(X_val,Y_val)]# 如果是决策树模型, 绘制决策树if re.search(str.lower('tree'), str.lower(clf_name)):dot_data = StringIO()export_graphviz(clf, out_file=dot_data,feature_names=feature_names,class_names=["Y","N"],filled=True,rounded=True,special_characters=True)graph = pydotplus.graph_from_dot_data(dot_data.getvalue())file_name = clf_name + time.strftime('%H%M') + ".pdf"graph.write_pdf(file_name)for i in range(len(xy_lst)):X_part = xy_lst[i][0]Y_part = xy_lst[i][1]Y_pred = clf.predict(X_part) # 预测模型if i == 1 :# 画出两个ROC曲线if clf_name in ["DecisionTreeGini","SVM Classifier"]:Y_pred_pro = clf.predict_proba(X_part)[:,1]fpr_rat, tpr_rat ,_ = roc_curve(Y_part, Y_pred_pro)plt.figure(1)plt.plot([0, 1], [0, 1], 'k--')plt.plot(fpr_rat, tpr_rat, label=clf_name)plt.legend(loc='lower right')plt.figure(2)plt.plot([0, 1], [0, 1], 'k--')plt.plot(fpr_rat, tpr_rat, label=clf_name)plt.xlim([0,0.3])plt.ylim([0.6,1.0])plt.legend(loc='lower right')if i == 0 : # 生成结果的DataFrame的columnsadd = str("train") + "-"else:add = str("val") + "-"result_lst.at[clf_name, add+"ACC"] = accuracy_score(Y_part,Y_pred) # 正确率result_lst.at[clf_name, add+"REC"] = recall_score(Y_part,Y_pred) # 召回率result_lst.at[clf_name, add+'F1'] = f1_score(Y_part,Y_pred) # F分数result_lst = result_lst.sort_index(axis=1) # 对columns排序 将相同指标放在一起
5. 模型对比
plt.figure(figsize=(8,6))sns.set_context('notebook')sns.heatmap(result_lst,annot=True)
各个模型在数据集上的评价:
- Accuracy:正确率 (识别正确的)
- Precision:精准率 (所有识别为正的样本中, 识别正确的比例)
- Recall:召回率 (所有实际为正的样本中, 识别正确的比例)
- 精准率和召回率的存在,是为了避免样本分布不均,正类极少的情况下,正确率很高,但是模型泛化能力仍然较差。
F1:F1分数 比较均衡 (2 precision recall)/(precision+recall) 无论哪一个数值小,F分数都会下降。
各个比率均越接近1, 表现越好。

可以看出,作为集成方法的随机森林,表现较好。不同模型参数调整对模型泛化能力都有影响,这里就不一一比较了。
Gini树 和 SVM的ROC曲线:


- Gini树的混淆矩阵

