【阿旭机器学习实战】【32】预测银行客户是否会开设定期存款账户--逻辑回归

【阿旭机器学习实战】系列文章主要介绍机器学习的各种算法模型及其实战案例,欢迎点赞,关注共同学习交流。

本文主要是依据现有数据预测客户是否会开设到定期存款账户(预测值y),并详细介绍了从加载数据、特征工程到最后使用逻辑回归模型训练的详细过程。

注:逻辑回归主要用于二分类问题,即要求预测值为 0 或者 1, 自变量特征值应该彼此独立。

1.读取并查看数据

关注公众号:阿旭算法与机器学习,回复:“ML32”即可获取本文数据集、源码与项目文档

import pandas as pd
import numpy as np
from sklearn import preprocessing
import matplotlib.pyplot as plt 
plt.rc("font", size=14)
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
import seaborn as sns
sns.set(style="white")
sns.set(style="whitegrid", color_codes=True)
data = pd.read_csv('banking.csv', header=0)
data = data.dropna()
print(data.shape)
print(list(data.columns))
(41188, 21)
['age', 'job', 'marital', 'education', 'default', 'housing', 'loan', 'contact', 'month', 'day_of_week', 'duration', 'campaign', 'pdays', 'previous', 'poutcome', 'emp_var_rate', 'cons_price_idx', 'cons_conf_idx', 'euribor3m', 'nr_employed', 'y']
data.head()
agejobmaritaleducationdefaulthousingloancontactmonthday_of_week...campaignpdayspreviouspoutcomeemp_var_ratecons_price_idxcons_conf_idxeuribor3mnr_employedy
044blue-collarmarriedbasic.4yunknownyesnocellularaugthu...19990nonexistent1.493.444-36.14.9635228.10
153technicianmarriedunknownnononocellularnovfri...19990nonexistent-0.193.200-42.04.0215195.80
228managementsingleuniversity.degreenoyesnocellularjunthu...362success-1.794.055-39.80.7294991.61
339servicesmarriedhigh.schoolnononocellularaprfri...29990nonexistent-1.893.075-47.11.4055099.10
455retiredmarriedbasic.4ynoyesnocellularaugfri...131success-2.992.201-31.40.8695076.21

5 rows × 21 columns

数据各列的含义

bank client data:

  • 1 - age (numeric)
  • 2 - job : type of job (categorical: ‘admin.’,‘blue-collar’,‘entrepreneur’,‘housemaid’,‘management’,‘retired’,‘self-employed’,‘services’,‘student’,‘technician’,‘unemployed’,‘unknown’)
  • 3 - marital : marital status (categorical: ‘divorced’,‘married’,‘single’,‘unknown’; note: ‘divorced’ means divorced or widowed)
  • 4 - education (categorical: ‘basic.4y’,‘basic.6y’,‘basic.9y’,‘high.school’,‘illiterate’,‘professional.course’,‘university.degree’,‘unknown’)
  • 5 - default: has credit in default? (categorical: ‘no’,‘yes’,‘unknown’)
  • 6 - housing: has housing loan? (categorical: ‘no’,‘yes’,‘unknown’)
  • 7 - loan: has personal loan? (categorical: ‘no’,‘yes’,‘unknown’)

related with the last contact of the current campaign:

  • 8 - contact: contact communication type (categorical: ‘cellular’,‘telephone’)
  • 9 - month: last contact month of year (categorical: ‘jan’, ‘feb’, ‘mar’, …, ‘nov’, ‘dec’)
  • 10 - day_of_week: last contact day of the week (categorical: ‘mon’,‘tue’,‘wed’,‘thu’,‘fri’)
  • 11 - duration: last contact duration, in seconds (numeric). Important note: this attribute highly affects the output target (e.g., if duration=0 then y=‘no’). Yet, the duration is not known before a call is performed. Also, after the end of the call y is obviously known. Thus, this input should only be included for benchmark purposes and should be discarded if the intention is to have a realistic predictive model.

other attributes:

  • 12 - campaign: number of contacts performed during this campaign and for this client (numeric, includes last contact)
  • 13 - pdays: number of days that passed by after the client was last contacted from a previous campaign (numeric; * 999 means client was not previously contacted)
  • 14 - previous: number of contacts performed before this campaign and for this client (numeric)
  • 15 - poutcome: outcome of the previous marketing campaign (categorical: ‘failure’,‘nonexistent’,‘success’)

social and economic context attributes

  • 16 - emp.var.rate: employment variation rate - quarterly indicator (numeric)
  • 17 - cons.price.idx: consumer price index - monthly indicator (numeric)
  • 18 - cons.conf.idx: consumer confidence index - monthly indicator (numeric)
  • 19 - euribor3m: euribor 3 month rate - daily indicator (numeric)
  • 20 - nr.employed: number of employees - quarterly indicator (numeric)

Output variable (desired target):

  • 21 - y - has the client subscribed a term deposit? (binary: ‘yes’,‘no’)

2.数据分析及处理

data['education'].unique()
array(['basic.4y', 'unknown', 'university.degree', 'high.school',
       'basic.9y', 'professional.course', 'basic.6y', 'illiterate'],
      dtype=object)
# 将教育类型basic.9y,basic.6y,basic.4y 均变为Basic
data['education']=np.where(data['education'] =='basic.9y', 'Basic', data['education'])
data['education']=np.where(data['education'] =='basic.6y', 'Basic', data['education'])
data['education']=np.where(data['education'] =='basic.4y', 'Basic', data['education'])
data['education'].unique()
array(['Basic', 'unknown', 'university.degree', 'high.school',
       'professional.course', 'illiterate'], dtype=object)
data['y'].value_counts()
0    36548
1     4640
Name: y, dtype: int64
# 查看数据标签比例
sns.countplot(x='y', data = data, palette='hls')
plt.show()
plt.savefig('count_plot')

请添加图片描述

count_no_sub = len(data[data['y']==0])
count_sub = len(data[data['y']==1])
pct_of_no_sub = count_no_sub/(count_no_sub+count_sub)
print('未开户的百分比:  %.2f%%' % (pct_of_no_sub*100))
pct_of_sub = count_sub/(count_no_sub+count_sub)
print('开户的百分比:  %.2f%%' % (pct_of_sub*100))
未开户的百分比:  88.73%
开户的百分比:  11.27%

可以看到,数据非常的不均衡,会影响模型的训练效果。后续针对该问题进行处理。

data.groupby('y').mean()
agedurationcampaignpdayspreviousemp_var_ratecons_price_idxcons_conf_idxeuribor3mnr_employed
y
039.911185220.8448072.633085984.1138780.1323740.24887593.603757-40.5930973.8114915176.166600
140.913147553.1911642.051724792.0355600.492672-1.23344893.354386-39.7897842.1231355095.115991

2.1 数据分析

购买定期存款的客户的平均年龄高于未购买定期存款的客户的平均年龄。

购买定期存款的客户的 pdays(自上次联系客户以来的日子)较低。 pdays越低,最后一次通话的记忆越好,因此销售的机会就越大。

令人惊讶的是,购买定期存款的客户的销售通话次数较低。

我们可以计算其他特征值(如教育和婚姻状况)的分布,以更详细地了解数据。

data.groupby('job').mean()
agedurationcampaignpdayspreviousemp_var_ratecons_price_idxcons_conf_idxeuribor3mnr_employedy
job
admin.38.187296254.3121282.623489954.3192290.1890230.01556393.534054-40.2454333.5502745164.1253500.129726
blue-collar39.555760264.5423602.558461985.1603630.1225420.24899593.656656-41.3758163.7719965175.6151500.068943
entrepreneur41.723214263.2678572.535714981.2671700.1387360.15872393.605372-41.2836543.7911205176.3135300.085165
housemaid45.500000250.4547172.639623960.5792450.1377360.43339693.676576-39.4952834.0096455179.5296230.100000
management42.362859257.0581402.476060962.6470590.185021-0.01268893.522755-40.4894663.6113165166.6505130.112175
retired62.027326273.7122092.476744897.9360470.327326-0.69831493.430786-38.5730812.7700665122.2621510.252326
self-employed39.949331264.1421532.660802976.6213930.1435610.09415993.559982-40.4881073.6893765170.6743840.104856
services37.926430258.3980852.587805979.9740490.1549510.17535993.634659-41.2900483.6991875171.6001260.081381
student25.894857283.6834292.104000840.2171430.524571-1.40800093.331613-40.1875431.8842245085.9390860.314286
technician38.507638250.2322412.577339964.4081270.1537890.27456693.561471-39.9275693.8204015175.6483910.108260
unemployed39.733728249.4516772.564103935.3165680.199211-0.11173693.563781-40.0075943.4665835157.1565090.142012
unknown45.563636239.6757582.648485938.7272730.1545450.35787993.718942-38.7978793.9490335172.9318180.112121
data.groupby('marital').mean()
agedurationcampaignpdayspreviousemp_var_ratecons_price_idxcons_conf_idxeuribor3mnr_employedy
marital
divorced44.899393253.7903302.61340968.6398530.1686900.16398593.606563-40.7070693.7156035170.8786430.103209
married42.307165257.4386232.57281967.2476730.1556080.18362593.597367-40.2706593.7458325171.8487720.101573
single33.158714261.5243782.53380949.9095780.211359-0.16798993.517300-40.9186983.3174475155.1992650.140041
unknown40.275000312.7250003.18750937.1000000.275000-0.22125093.471250-40.8200003.3130385157.3937500.150000
data.groupby('education').mean()
agedurationcampaignpdayspreviousemp_var_ratecons_price_idxcons_conf_idxeuribor3mnr_employedy
education
Basic42.163910263.0438742.559498974.8779670.1410530.19132993.639933-40.9275953.7296545172.0141130.087029
high.school37.998213260.8868102.568576964.3583820.1859170.03293793.584857-40.9406413.5561575164.9947350.108355
illiterate48.500000276.7777782.277778943.8333330.111111-0.13333393.317333-39.9500003.5165565171.7777780.222222
professional.course40.080107252.5338552.586115960.7659740.1630750.17301293.569864-40.1241083.7104575170.1559790.113485
university.degree38.879191253.2233732.563527951.8076920.192390-0.02809093.493466-39.9758053.5296635163.2262980.137245
unknown43.481225262.3905262.596187942.8307340.2264590.05909993.658615-39.8778163.5710985159.5495090.145003
%matplotlib inline
table=pd.crosstab(data.job,data.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True)
plt.title('Stacked Bar Chart of Job title vs Purchase')
plt.xlabel('Job')
plt.ylabel('Proportion of Purchase')
plt.savefig('purchase_vs_job')

在这里插入图片描述

具有不同职位的人购买存款的频率不一样。 因此,职称可以是良好的预测因素。

table=pd.crosstab(data.marital,data.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True)
plt.title('Stacked Bar Chart of Marital Status vs Purchase')
plt.xlabel('Marital Status')
plt.ylabel('Proportion of Customers')
plt.savefig('mariral_vs_pur_stack')

婚姻状况似乎不是好的预测因素。

table=pd.crosstab(data.education,data.y)
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True)
plt.title('Stacked Bar Chart of Education vs Purchase')
plt.xlabel('Education')
plt.ylabel('Proportion of Customers')
plt.savefig('edu_vs_pur_stack')

在这里插入图片描述

教育似乎是结果变量的良好预测指标。

table=pd.crosstab(data.day_of_week,data.y)#.plot(kind='bar')
table.div(table.sum(1).astype(float), axis=0).plot(kind='bar', stacked=True)
plt.title('Stacked Bar Chart of Day of Week vs Purchase')
plt.xlabel('Day of Week')
plt.ylabel('Proportion of Purchase')
plt.savefig('dow_vs_purchase')

在这里插入图片描述

一周工作时间不是预测结果的良好预测因素。

2.2 选取特征参数并进行独热编码

cat_vars=['job','marital','education','default','housing','loan','contact','month','day_of_week','poutcome']
# 对以上特征参数进行独热编码
for var in cat_vars:
    cat_list = pd.get_dummies(data[var], prefix=var)
    data=data.join(cat_list)
# 删除cat_vars原始类型特征列
data_final=data.drop(cat_vars, axis=1)
data_final.columns.values
array(['age', 'duration', 'campaign', 'pdays', 'previous', 'emp_var_rate',
       'cons_price_idx', 'cons_conf_idx', 'euribor3m', 'nr_employed', 'y',
       'job_admin.', 'job_blue-collar', 'job_entrepreneur',
       'job_housemaid', 'job_management', 'job_retired',
       'job_self-employed', 'job_services', 'job_student',
       'job_technician', 'job_unemployed', 'job_unknown',
       'marital_divorced', 'marital_married', 'marital_single',
       'marital_unknown', 'education_Basic', 'education_high.school',
       'education_illiterate', 'education_professional.course',
       'education_university.degree', 'education_unknown', 'default_no',
       'default_unknown', 'default_yes', 'housing_no', 'housing_unknown',
       'housing_yes', 'loan_no', 'loan_unknown', 'loan_yes',
       'contact_cellular', 'contact_telephone', 'month_apr', 'month_aug',
       'month_dec', 'month_jul', 'month_jun', 'month_mar', 'month_may',
       'month_nov', 'month_oct', 'month_sep', 'day_of_week_fri',
       'day_of_week_mon', 'day_of_week_thu', 'day_of_week_tue',
       'day_of_week_wed', 'poutcome_failure', 'poutcome_nonexistent',
       'poutcome_success'], dtype=object)

2.3 解决数据不均衡问题—使用SMOTE进行过采样

创建我们的训练数据后,我将使用SMOTE算法(合成少数过采样技术)对已经开户的用户进行上采样。 在高层次上,SMOTE:

通过从次要类(已经开户的用户)创建合成样本而不是创建副本来工作。
随机选择一个k-最近邻居并使用它来创建一个类似但随机调整的新观察结果。

使用如下命令安装:
conda install -c conda-forge imbalanced-learn

X = data_final.loc[:, data_final.columns != 'y']
y = data_final.loc[:, data_final.columns == 'y'].values.ravel()

from imblearn.over_sampling import SMOTE
os = SMOTE(random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
columns = X_train.columns
os_data_X,os_data_y=os.fit_resample(X_train, y_train)
os_data_X = pd.DataFrame(data=os_data_X,columns=columns )
os_data_y= pd.DataFrame(data=os_data_y,columns=['y'])
# we can Check the numbers of our data
print("过采样以后的数据量: ",len(os_data_X))
print("未开户的用户数量: ",len(os_data_y[os_data_y['y']==0]))
print("开户的用户数量: ",len(os_data_y[os_data_y['y']==1]))
print("未开户的用户数量的百分比: ",len(os_data_y[os_data_y['y']==0])/len(os_data_X))
print("开户的用户数量的百分比: ",len(os_data_y[os_data_y['y']==1])/len(os_data_X))
过采样以后的数据量:  51134
未开户的用户数量:  25567
开户的用户数量:  25567
未开户的用户数量的百分比:  0.5
开户的用户数量的百分比:  0.5

现在我们拥有完美平衡的1:1数据! 您可能已经注意到我仅对训练数据进行了过采样

3.训练模型

from sklearn.linear_model import LogisticRegression
from sklearn import metrics
#X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
logreg = LogisticRegression()
logreg.fit(os_data_X, os_data_y.values.reshape(-1))
y_pred = logreg.predict(X_test)
print('在测试数据集上面的预测准确率: {:.2f}'.format(logreg.score(X_test, y_test)))
在测试数据集上面的预测准确率: 0.90
from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
              precision    recall  f1-score   support

           0       0.95      0.93      0.94     10981
           1       0.52      0.63      0.57      1376

    accuracy                           0.90     12357
   macro avg       0.74      0.78      0.76     12357
weighted avg       0.90      0.90      0.90     12357
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve
logit_roc_auc = roc_auc_score(y_test, logreg.predict(X_test))
fpr, tpr, thresholds = roc_curve(y_test, logreg.predict_proba(X_test)[:,1])
plt.figure()
plt.plot(fpr, tpr, label='Logistic Regression (area = %0.2f)' % logit_roc_auc)
plt.plot([0, 1], [0, 1],'r--')
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.savefig('Log_ROC')
plt.show()

在这里插入图片描述

如果文章对你有帮助,感谢点赞+关注!

关注下方GZH:阿旭算法与机器学习,回复:“ML32”即可获取本文数据集、源码与项目文档,欢迎共同学习交流

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿_旭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值