作者:韩信子@ShowMeAI

数据分析实战系列https://www.showmeai.tech/tutorials/40

机器学习实战系列https://www.showmeai.tech/tutorials/41

本文地址https://www.showmeai.tech/article-detail/308

声明:版权所有,转载请联系平台与作者并注明出处

收藏ShowMeAI查看更多精彩内容

人力资源是组织的一个部门,负责处理员工的招聘、培训、管理和福利。一个组织每年都会雇佣几名员工,并投入大量时间、金钱和资源来提高员工的绩效和效率。每家公司都希望能够吸引和留住优秀的员工,失去一名员工并再次雇佣一名新员工的成本是非常高的,HR部门需要知道雇用和留住重要和优秀员工的核心因素是什么,那那么可以做得更好。

在本项目中,ShowMeAI 带大家通过数据科学和AI的方法,分析挖掘人力资源流失问题,并基于机器学习构建解决问题的方法,并且,我们通过对AI模型的反向解释,可以深入理解导致人员流失的主要因素,HR部门也可以根据分析做出正确的决定。

本篇涉及到的数据集大家可以通过 ShowMeAI 的百度网盘地址获取。

实战数据集下载(百度网盘):公众号『ShowMeAI研究中心』回复『实战』,或者点击 这里 获取本文 [17]人力资源流失场景机器学习建模与调优HR-Employee-Attrition 数据集

ShowMeAI官方GitHubhttps://github.com/ShowMeAI-Hub

探索性数据分析

和 ShowMeAI 之前介绍过的所有AI项目一样,我们需要先对场景数据做一个深度理解,这就是我们提到的EDA(Exploratory Data Analysis,探索性数据分析)过程。

EDA部分涉及的工具库,大家可以参考ShowMeAI制作的工具库速查表和教程进行学习和快速使用。

数据科学工具库速查表 | Pandas 速查表

数据科学工具库速查表 | Seaborn 速查表

图解数据分析:从入门到精通系列教程

数据&字段说明

我们本次使用到的数据集字段基本说明如下:

列名 含义
Age 年龄
Attrition 离职
BusinessTravel 出差:0-不出差、1-偶尔出差、2-经常出差
Department 部门:1-人力资源、2-科研、3-销售
DistanceFromHome 离家距离
Education 教育程度:1-大学一下、2-大学、3-学士、4-硕士、5-博士
EducationField 教育领域
EnvironmentSatisfaction 环境满意度
Gender 性别:1-Mae男、0- Female女
Joblnvolvement 工作投入
JobLevel 职位等级
JobRole 工作岗位
JobSatisfaction 工作满意度
Maritalstatus 婚姻状况:0- Divorced离婚、1- Single未婚、2-已婚
Monthlylncome 月收入
NumCompaniesWorked 服务过几家公司
OverTime 加班
RelationshipSatisfaction 关系满意度
StockOptionLevel 股权等级
TotalworkingYears 总工作年限
TrainingTimesLastYear 上一年培训次数
WorkLifeBalance 工作生活平衡
YearsAtCompany 工作时长
YearsInCurrentRole 当前岗位在职时长
YearsSinceLastPromotion 上次升职时间
YearsWithCurrManager 和现任经理时长

数据速览

下面我们先导入所需工具库、加载数据集并查看数据基本信息:

  1. import pandas as pd
  2. import numpy as np
  3. import matplotlib as mpl
  4. import matplotlib.pyplot as plt
  5. import seaborn as sns
  6. sns.set_style("darkgrid")
  7. import warnings
  8. warnings.filterwarnings("ignore")
  9. pd.set_option('display.max_columns',100)
  10. print("import complete")
  1. # 读取数据
  2. data = pd.read_csv("HR-Employee-Attrition.csv")
  3. data.head()

查看前 5 条数据记录后,我们了解了一些基本信息:

① 数据包含『数值型』和『类别型』两种类型的特征。

② 有不少离散的数值特征。

查看数据基本信息

接下来我们借助工具库进一步探索数据。

  1. # 字段、类型、缺失情况
  2. data.info()

我们使用命令 data.info``() 来获取数据的信息,包括总行数(样本数)和总列数(字段数)、变量的数据类型、数据集中非缺失的数量以及内存使用情况。

从数据集的信息可以看出,一共有 35 个特征,Attrition 是目标字段,26 个变量是整数类型变量,9 个是对象类型变量。

缺失值检测&处理

我们先来做一下缺失值检测与处理,缺失值的存在可能会降低模型效果,也可能导致模型出现偏差。

  1. # 查看缺失值情况
  2. data.isnull().sum()

从结果可以看出,数据集中没有缺失值。

特征编码

因为目标特征“Attrition”是一个类别型变量,为了分析方便以及能够顺利建模,我们对它进行类别编码(映射为整数值)。

  1. #since Attrition is a categotical in nature so will be mapping it with integrs variables for further analysis
  2. data.Attrition = data.Attrition.map({"Yes":1,"No":0})

数据统计概要

接下来,我们借助于pandas的describe函数检查数值特征的统计摘要:

  1. #checking statistical summary
  2. data.describe().T

注意这里的“.T”是获取数据帧的转置,以便更好地分析。

从统计摘要中,我们得到数据的统计信息,包括数据的中心趋势——平均值、中位数、众数和散布标准差和百分位数,最小值和最大值等。

数值型特征分析

我们进一步对数值型变量进行分析

  1. # 选出数值型特征
  2. numerical_feat = data.select_dtypes(include=['int64','float64'])
  3. numerical_feat

  1. print(numerical_feat.columns)
  2. print("No. of numerical variables :",len(numerical_feat.columns))
  3. print("Number of unique values \n",numerical_feat.nunique())

我们有以下观察结论:

① 共有27个数值型特征变量

② 月收入、日费率、员工人数、月费率等为连续数值

③ 其余变量为离散数值(即有固定量的候选取值)

我们借助于 seaborn 工具包中的分布图方法 sns.distplot() 来绘制数值分布图

  1. # 数据分析&分布绘制
  2. plt.figure(figsize=(25,30))
  3. plot = 1
  4. for var in numerical_feat:
  5. plt.subplot(9,3,plot)
  6. sns.distplot(data[var],color='skyblue')
  7. plot+=1
  8. plt.show()

通过以上分析,我们获得以下一些基本观察和结论:

  • 大多数员工都是 30 多岁或 40 多岁
  • 大多数员工具有 3 级教育
  • 大多数员工将环境满意度评为 3 和 4
  • 大多数员工的工作参与度为 3 级
  • 大多数员工来自工作级别 1 和 2
  • 大多数员工将工作满意度评为 3 和 4
  • 大多数员工只在 1 个公司工作过
  • 大多数员工的绩效等级为 3
  • 大多数员工要么没有股票期权,要么没有一级股票期权
  • 大多数员工有 5-10 年的工作经验
  • 大多数员工的工作与生活平衡评分为 3

接下来我们对目标变量做点分析:

  1. # 目标变量分布
  2. sns.countplot('Attrition',data=data)
  3. plt.title("Distribution of Target Variable")
  4. plt.show()
  5. print(data.Attrition.value_counts())

我们可以看到数据集中存在类别不平衡问题(流失的用户占比少)。类别不均衡情况下,我们要选择更有效的评估指标(如auc可能比accuracy更有效),同时在建模过程中做一些优化处理。

我们分别对各个字段和目标字段进行联合关联分析。

  1. # Age 与 attrition
  2. age=pd.crosstab(data.Age,data.Attrition)
  3. age.div(age.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(14,7),cmap='spring')
  4. plt.title("Age vs Attrition",fontsize=20)
  5. plt.show()

  1. # Distance from home 与 attrition
  2. dist=pd.crosstab(data.DistanceFromHome,data.Attrition)
  3. dist.div(dist.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7))
  4. plt.title("Distance From Home vs Attrition",fontsize=20)
  5. plt.show()

  1. # Education 与 Attrition
  2. edu=pd.crosstab(data.Education,data.Attrition)
  3. edu.div(edu.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7))
  4. plt.title("Education vs Attrition",fontsize=20)
  5. plt.show()

  1. # Environment Satisfaction 与 Attrition
  2. esat=pd.crosstab(data.EnvironmentSatisfaction,data.Attrition)
  3. esat.div(esat.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='BrBG')
  4. plt.title("Environment Satisfaction vs Attrition",fontsize=20)
  5. plt.show()

  1. # Job Involvement 与 Attrition
  2. job_inv=pd.crosstab(data.JobInvolvement,data.Attrition)
  3. job_inv.div(job_inv.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='Spectral')
  4. plt.title("Job Involvement vs Attrition",fontsize=20)
  5. plt.show()

  1. # Job Level 与 Attrition
  2. job_lvl=pd.crosstab(data.JobLevel,data.Attrition)
  3. job_lvl.div(job_lvl.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='prism_r')
  4. plt.title("Job Level vs Attrition",fontsize=20)
  5. plt.show()

  1. # Job Satisfaction 与 Attrition
  2. job_sat=pd.crosstab(data.JobSatisfaction,data.Attrition)
  3. job_sat.div(job_sat.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='inferno')
  4. plt.title("Job Satisfaction vs Attrition",fontsize=20)
  5. plt.show()

  1. # Number of Companies Worked 与 Attrition
  2. num_org=pd.crosstab(data.NumCompaniesWorked,data.Attrition)
  3. num_org.div(num_org.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='cividis_r')
  4. plt.title("Number of Companies Worked vs Attrition",fontsize=20)
  5. plt.show()

  1. # Percent Salary Hike 与 Attrition
  2. sal_hike_percent=pd.crosstab(data.PercentSalaryHike,data.Attrition)
  3. sal_hike_percent.div(sal_hike_percent.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='RdYlBu')
  4. plt.title("Percent Salary Hike vs Attrition",fontsize=20)
  5. plt.show()

  1. # Performance Rating 与 Attrition
  2. performance=pd.crosstab(data.PerformanceRating,data.Attrition)
  3. performance.div(performance.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='viridis_r')
  4. plt.title("Performance Rating vs Attrition",fontsize=20)
  5. plt.show()

  1. # Relationship Satisfaction 与 Attrition
  2. rel_sat=pd.crosstab(data.RelationshipSatisfaction,data.Attrition)
  3. rel_sat.div(rel_sat.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='brg_r')
  4. plt.title("Relationship Satisfaction vs Attrition",fontsize=20)
  5. plt.show()

  1. # Stock Option Level 与 Attrition
  2. stock_opt=pd.crosstab(data.StockOptionLevel,data.Attrition)
  3. stock_opt.div(stock_opt.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='Accent')
  4. plt.title("Stock Option Level vs Attrition",fontsize=20)
  5. plt.show()

  1. # Training Times Last Year 与 Attrition
  2. tr_time=pd.crosstab(data.TrainingTimesLastYear,data.Attrition)
  3. tr_time.div(tr_time.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='coolwarm')
  4. plt.title("Training Times Last Year vs Attrition",fontsize=20)
  5. plt.show()

  1. # Work Life Balance 与 Attrition
  2. work=pd.crosstab(data.WorkLifeBalance,data.Attrition)
  3. work.div(work.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='gnuplot')
  4. plt.title("Work Life Balance vs Attrition",fontsize=20)
  5. plt.show()

  1. # Years With Curr Manager 与 Attrition
  2. curr_mang=pd.crosstab(data.YearsWithCurrManager,data.Attrition)
  3. curr_mang.div(curr_mang.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='OrRd_r')
  4. plt.title("Years With Curr Manager vs Attrition",fontsize=20)
  5. plt.show()

  1. # Years Since Last Promotion 与 Attrition
  2. prom=pd.crosstab(data.YearsSinceLastPromotion,data.Attrition)
  3. prom.div(prom.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='PiYG_r')
  4. plt.title("Years Since Last Promotion vs Attrition",fontsize=20)
  5. plt.show()

  1. # Years In Current Role 与 Attrition
  2. role=pd.crosstab(data.YearsInCurrentRole,data.Attrition)
  3. role.div(role.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='terrain')
  4. plt.title("Years In Current Role vs Attrition",fontsize=20)
  5. plt.show()

这些堆积条形图显示了员工流失情况与各个字段取值的关系,从上图我们可以得出以下基本结论:

  • 30 岁以下或特别是年轻的员工比年长员工的流失率更高。
  • 离家较远的员工的流失率较高,即如果员工住在附近,他或她离开公司的机会较小。
  • 3 级和1 级教育的流失率较高,5 级教育的流失率最低,高等教育水平的候选人稳定性更高。
  • 环境满意度低会导致较高的人员流失率,满意度1 级的人员流失率较高,4 级人员的流失率最低。
  • 工作参与级别 1 的员工流失率较高,级别 4 的员工流失率最低,这意味着工作参与度更高的员工离职机会较低。
  • 级别 1 和级别 3 的员工流失率较高,级别 5 的员工流失率最低,即职位级别较高的员工流失的可能性较小。
  • 工作满意度级别 1 的员工流失率较高,级别 4 的员工流失率最低,工作满意度较高的员工流失的可能性较小。
  • 在超过四家公司工作过的员工流失率较高,这个字段本身在一定程度上体现了员工的稳定性。
  • 1 级关系满意度较高,4 级最少,这意味着与雇主关系好的员工流失可能性较低。
  • 股票期权级别为 0 的员工流失率较高,而级别 1 和 2 的员工流失率较低,这意味着如果员工持有股票,会更倾向于留下
  • 工作与生活平衡水平为 1 的员工流失率高,或者我们可以说工作与生活平衡低的员工更可能流失。
  • 自过去 8 年以来未晋升的员工有大量流失。
  • 随着与经理相处的时间变长,员工流失率会下降。

类别型特征分析

现在我们对类别型特征进行分析,在这里我们使用饼图和堆积条形图来分析它们的分布以及它们和目标变量的相关性。

  1. # 分析Buisness Travel
  2. colors=['red','green','blue']
  3. size = data.BusinessTravel.value_counts().values
  4. explode_list=[0,0.05,0.1]
  5. plt.figure(figsize=(15,10))
  6. plt.pie(size,labels=None,explode=explode_list,colors=colors,autopct="%1.1f%%",pctdistance=1.15)
  7. plt.title("Business Travel",fontsize=15)
  8. plt.legend(labels=['Travel_Rarely','Travel_Frequently','Non-Travel'],loc='upper left')
  9. plt.show()

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1202deea187a4f6fa13e1879f565e8f2~tplv-k3u1fbpfcp-zoom-1.image" width="50%" referrerpolicy="no-referrer">

  1. # 分析Department
  2. colors=['orchid','gold','olive']
  3. size = data.Department.value_counts().values
  4. explode_list=[0,0.05,0.06]
  5. plt.figure(figsize=(15,10))
  6. plt.pie(size,labels=None,explode=explode_list,colors=colors,autopct="%1.1f%%",pctdistance=1.1)
  7. plt.title("Department",fontsize=15)
  8. plt.legend(labels=['Sales','Research & Development','Human Resources'],loc='upper left')
  9. plt.show()

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a70b20dbddf248629a5797342d302480~tplv-k3u1fbpfcp-zoom-1.image" width="50%" referrerpolicy="no-referrer">

  1. # 分析Education Field
  2. colors=["cyan","orange","hotpink","green","navy","grey"]
  3. size = data.EducationField.value_counts().values
  4. explode_list=[0,0.05,0.05,0.08,0.08,0.1]
  5. plt.figure(figsize=(15,10))
  6. plt.pie(size,labels=None,explode=explode_list,colors=colors,autopct="%1.1f%%",pctdistance=1.1)
  7. plt.title("Education Field",fontsize=15)
  8. plt.legend(labels=['Life Sciences','Other','Medical','Marketing','Technical Degree','Human Resources'],loc='upper left')
  9. plt.show()

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b4fcd92a5e5b41fb8230c88a336196d6~tplv-k3u1fbpfcp-zoom-1.image" width="50%" referrerpolicy="no-referrer">

  1. # 分析婚姻状况
  2. colors=["red","orange","magenta","green","navy","grey","cyan","blue","black"]
  3. size = data.JobRole.value_counts().values
  4. explode_list=[0,0.05,0.05,0.05,0.08,0.08,0.08,0.1,0.1]
  5. plt.figure(figsize=(15,10))
  6. plt.pie(size,labels=None,explode=explode_list,colors=colors,autopct="%1.1f%%",pctdistance=1.1)
  7. plt.title("Job Role",fontsize=15)
  8. plt.legend(labels=['Sales Executive','Research Scientist','Laboratory Technician','Manufacturing Director','Healthcare Representative','Manager','Sales Representative','Research Director','Human Resources'],loc='upper left')
  9. plt.show()

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ea22f43ca2244414ab7254e87eaec9f7~tplv-k3u1fbpfcp-zoom-1.image" width="50%" referrerpolicy="no-referrer">

  1. # 分析gender性别
  2. plt.figure(figsize=(10,9))
  3. plt.title('Gender distribution',fontsize=15)
  4. sns.countplot('Gender',data=data,palette='magma')

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2caef3d3dae2417996540fe359e4f883~tplv-k3u1fbpfcp-zoom-1.image" width="50%" referrerpolicy="no-referrer">

从上面的图中,我们获得了一些信息:

  • 大部分员工很少出差。
  • 销售部门是公司的主体,研发占公司的30%左右,人力资源占比最小。
  • 拥有生命科学教育背景的员工数量较多,而人力资源教育背景的员工数量较少。
  • 大部分员工来自销售职位,最少来自人力资源部门。
  • 大部分员工未婚。
  • 公司中男性的数量多于女性。

下面做关联分析:

  1. # Business Travel 与 Attrition
  2. trav = pd.crosstab(data.BusinessTravel,data.Attrition)
  3. trav.div(trav.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='Set1')
  4. plt.title("Business Travel vs Attrition",fontsize=20)
  5. plt.show()

  1. # Department 与 Attrition
  2. dept = pd.crosstab(data.Department,data.Attrition)
  3. dept.div(dept.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='Set1')
  4. plt.title("Department vs Attrition",fontsize=20)
  5. plt.show()

  1. # Education Field 与 Attrition
  2. edu_f = pd.crosstab(data.EducationField,data.Attrition)
  3. edu_f.div(edu_f.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='Set1')
  4. plt.title("Education Field vs Attrition",fontsize=20)
  5. plt.show()

  1. # Job Role 与 Attrition
  2. jobrole = pd.crosstab(data.JobRole,data.Attrition)
  3. jobrole.div(jobrole.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='Set1')
  4. plt.title("Job Role vs Attrition",fontsize=20)
  5. plt.show()

  1. # Marital Status 与 Attrition
  2. mary = pd.crosstab(data.MaritalStatus,data.Attrition)
  3. mary.div(mary.sum(1),axis=0).plot(kind='bar',stacked=True,figsize=(12,7),cmap='Set1')
  4. plt.title("Marital Status vs Attrition",fontsize=20)
  5. plt.show()

  1. # gender 与 Attrition
  2. plt.figure(figsize=(10,9))
  3. plt.title('Gender distribution',fontsize=15)
  4. sns.countplot('Gender',data=data,palette='magma')

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/53d9583627f4444aaef15d3f95b73864~tplv-k3u1fbpfcp-zoom-1.image" width="50%" referrerpolicy="no-referrer">

上图反应了一些信息:

  • 经常出差员工的离职率较高,非出差员工离职率较低,也就是说,经常出差的员工更有可能流失。
  • 销售和人力资源的流失率较高,而研发的流失率较低。
  • 人力资源教育背景的流失率较高,而医学和其他教育背景的流失率最低。,医学和其他教育背景的员工离职的可能性较小。
  • 销售代表、人力资源和实验室技术人员的流失率最高。
  • 未婚员工离职率较高,离婚员工离职率最低。
  • 男性员工的流失率更高。

相关性分析

我们计算特征之间的相关系数并绘制热力图:

  1. # 计算相关度矩阵并绘制热力图
  2. plt.figure(figsize=(20,15))
  3. sns.heatmap(data.corr(method='spearman'),annot=True,cmap='Accent')
  4. plt.title('Correlation of features',fontsize=20)
  5. plt.show()

  1. # 相关度排序
  2. plt.figure(figsize=(15,9))
  3. correlation = data . corr(method='spearman')
  4. correlation.Attrition.sort_values(ascending=False).drop('Attrition').plot.bar(color='r')
  5. plt.title('Correlation of independent features with target feature',fontsize=20)
  6. plt.show()

异常值检测与处理

下面我们检测一下数据集中的异常值,在这里,我们使用箱线图来可视化分布并检测异常值。

  1. # 绘制箱线图
  2. plot=1
  3. plt.figure(figsize=(15,30))
  4. for i in numerical_feat.columns:
  5. plt.subplot(9,3,plot)
  6. sns.boxplot(data[i],color='navy')
  7. plt.xlabel(i)
  8. plot+=1
  9. plt.show()

箱线图显示数据集中有不少异常值,不过这里的异常值主要是因为离散变量(可能是取值较少的候选),我们将保留它们(不然会损失掉这些样本信息),不过我们注意到月收入的异常值比较奇怪,这可能是由于数据收集错误造成的,可以清洗一下。

特征工程

关于机器学习特征工程,大家可以参考 ShowMeAI 整理的特征工程最全解读教程。

机器学习实战 | 机器学习特征工程最全解读

类别均衡处理

下面我们来完成特征工程的部分,从原始数据中抽取强表征的信息,以便模型能更直接高效地挖掘和建模。

我们在EDA过程中发现 MonthlyIncome、JobLevel 和 YearsAtCompany 以及 YearsInCurrentRole 高度相关,可能会带来多重共线性问题,我们会做一些筛选,同时我们会删除一些与 EmployeeCount、StandardHours 等变量不相关的特征,并剔除一些对预测不重要的特征。

  1. dataset = data.copy()
  2. # 删除与目标相关性低的Employee count 和 standard hours特征
  3. dataset.drop(['EmployeeCount','StandardHours'],inplace=True,axis=1)
  4. dataset.head(2)

下面我们对类别型特征进行编码,包括数字映射与独热向量编码。

  1. # 按照出差的频度进行编码
  2. dataset.BusinessTravel = dataset.BusinessTravel.replace({
  3. 'Non-Travel':0,'Travel_Rarely':1,'Travel_Frequently':2
  4. })
  5. # 性别与overtime编码
  6. dataset.Gender = dataset.Gender.replace({'Male':1,'Female':0})
  7. dataset.OverTime = dataset.OverTime.replace({'Yes':1,'No':0})
  8. # 独热向量编码
  9. new_df = pd.get_dummies(data=dataset,columns=['Department','EducationField','JobRole', 'MaritalStatus'])
  10. new_df

处理与转换后的数据如下所示:

在前面的数据探索分析过程中,我们发现目标变量是类别不平衡的,因此可能会导致模型偏向多数类而带来偏差。我们在这里会应用过采样技术 SMOTE(合成少数类别的样本补充)来处理数据集中的类别不平衡问题。

我们把数据先切分为特征和标签,处理之前的标签类别比例如下:

  1. # 切分特征和标签
  2. X = new_df.drop(['Attrition'],axis=1)
  3. Y = new_df.Attrition
  4. # 标签01取值比例
  5. sns.countplot(data=new_df,x=Y,palette='Set1')
  6. plt.show()
  7. print(Y.value_counts())

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2665749a4bfa46199cc9fc94e556f83f~tplv-k3u1fbpfcp-zoom-1.image" width="50%" referrerpolicy="no-referrer">

应用过采样技术 SMOTE:

  1. # SMOTE处理类别不均衡
  2. from imblearn.over_sampling import SMOTE
  3. sm = SMOTE(sampling_strategy='minority')
  4. x,y = sm.fit_resample(X,Y)
  5. print(x.shape," \t ",y.shape)
  6. # (2466, 45) (2466,)

过采样后

  1. # 过采样之后的比例
  2. sns.countplot(data=new_df,x=y,palette='Set1')
  3. plt.show()
  4. print(y.value_counts())

![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/81fc7f8493c34adb809e60a8058ee726~tplv-k3u1fbpfcp-zoom-1.image" width="50%" referrerpolicy="no-referrer">

特征幅度缩放

现在数据集已经类别均衡了,我们做一点特征工程处理,比如有些模型对于特征值的幅度是敏感的,我们做一点幅度缩放,这里我们调用sklearn.preprocessing 类中的 MinMaxScaler 方法。

  1. # 特征幅度缩放
  2. from sklearn.preprocessing import MinMaxScaler
  3. scaler = MinMaxScaler()
  4. x_scaled = scaler.fit_transform(x)
  5. x_scaled = pd.DataFrame(x_scaled, columns=x.columns)
  6. x_scaled

处理后我们的数据集看起来像这样

所有取值都已调整到 0 -1 的幅度范围内。

分析特征重要性

通常在特征工程之后,我们会得到非常多的特征,太多特征会带来模型训练性能上的问题,不相关的差特征甚至会拉低模型的效果。

我们很多时候会进行特征重要度分析的工作,筛选和保留有效特征,而对其他特征进行剔除。我们先将数据集拆分为训练集和测试集,再基于互信息判定特征重要度。

  1. ## 训练集测试集切分
  2. from sklearn.model_selection import train_test_split
  3. xtrain,xtest,ytrain,ytest = train_test_split(x_scaled,y,test_size=0.3,random_state=1)

我们使用 sklearn.feature_selection 类中的mutual_info_classif 方法来获得特征重要度。Mutual _info_classif的工作原理是类似信息增益。

  1. from sklearn.feature_selection import mutual_info_classif
  2. mutual_info = mutual_info_classif(xtrain,ytrain)
  3. mutual_info

下面我们绘制一下特征重要性

  1. mutual_info = pd.Series(mutual_info)
  2. mutual_info.index = xtrain.columns
  3. mutual_info.sort_values(ascending=False)
  4. plt.title("Feature Importance",fontsize=20)
  5. mutual_info.sort_values().plot(kind='barh',figsize=(12,9),color='r')
  6. plt.show()

当然,实际判定特征重要度的方式有很多种,甚至结果也会有一些不同,我们只是基于这个步骤,进行一定的特征筛选,把最不相关的特征剔除。

模型构建和评估

关于建模与评估,大家可以参考 ShowMeAI 的机器学习系列教程与模型评估基础知识文章。

图解机器学习算法:从入门到精通系列教程

图解机器学习算法(1) | 机器学习基础知识

图解机器学习算法(2) | 模型评估方法与准则

好,我们前序工作就算完毕啦!下面要开始构建模型了。在建模之前,有一件非常重要的事情,是我们需要选择合适的评估指标对模型进行评估,这能给我们指明模型优化的方向,我们在这里,针对分类问题,尽量覆盖地选择了下面这些评估指标

  • 准确度得分
  • 混淆矩阵
  • precision
  • recall
  • F1-score
  • Auc-Roc

我们这里选用了8个模型构建baseline,并应用交叉验证以获得对模型无偏的评估结果。

  1. # 导入工具库
  2. from sklearn.linear_model import LogisticRegression
  3. from sklearn.tree import DecisionTreeClassifier
  4. from sklearn.svm import SVC
  5. from sklearn.neighbors import KNeighborsClassifier
  6. from sklearn.naive_bayes import BernoulliNB
  7. from sklearn.ensemble import RandomForestClassifier
  8. from sklearn.ensemble import AdaBoostClassifier
  9. from sklearn.ensemble import GradientBoostingClassifier
  10. from sklearn.model_selection import cross_val_score,cross_validate
  11. from sklearn.metrics import classification_report,confusion_matrix,accuracy_score,plot_roc_curve,roc_curve,auc,roc_auc_score,precision_score,r
  12. # 初始化baseline模型(使用默认参数)
  13. LR = LogisticRegression()
  14. KNN = KNeighborsClassifier()
  15. SVC = SVC()
  16. DTC = DecisionTreeClassifier()
  17. BNB = BernoulliNB()
  18. RTF = RandomForestClassifier()
  19. ADB = AdaBoostClassifier()
  20. GB = GradientBoostingClassifier()
  21. # 构建模型列表
  22. models = [("Logistic Regression ",LR),
  23. ("K Nearest Neighbor classifier ",KNN),
  24. ("Support Vector classifier ",SVC),
  25. ("Decision Tree classifier ",DTC),
  26. ("Random forest classifier ",RTF),
  27. ("AdaBoost classifier",ADB),
  28. ("Gradient Boosting classifier ",GB),
  29. ("Naive Bayes classifier",BNB)]

接下来我们遍历这些模型进行训练和评估:

  1. for name,model in models:
  2. model.fit(xtrain,ytrain)
  3. print(name," trained")
  4. # 遍历评估
  5. train_scores=[]
  6. test_scores=[]
  7. Model = []
  8. for name,model in models:
  9. print("******",name,"******")
  10. train_acc = accuracy_score(ytrain,model.predict(xtrain))
  11. test_acc = accuracy_score(ytest,model.predict(xtest))
  12. print('Train score : ',train_acc)
  13. print('Test score : ',test_acc)
  14. train_scores.append(train_acc)
  15. test_scores.append(test_acc)
  16. Model.append(name)
  17. # 不同的评估准则
  18. precision_ =[]
  19. recall_ = []
  20. f1score = []
  21. rocauc = []
  22. for name,model in models:
  23. print("******",name,"******")
  24. cm = confusion_matrix(ytest,model.predict(xtest))
  25. print("\n",cm)
  26. fpr,tpr,thresholds=roc_curve(ytest,model.predict(xtest))
  27. roc_auc= auc(fpr,tpr)
  28. print("\n","ROC_AUC_SCORE : ",roc_auc)
  29. rocauc.append(roc_auc)
  30. print(classification_report(ytest,model.predict(xtest)))
  31. precision = precision_score(ytest, model.predict(xtest))
  32. print('Precision: ', precision)
  33. precision_.append(precision)
  34. recall = recall_score(ytest, model.predict(xtest))
  35. print('Recall: ', recall)
  36. recall_.append(recall)
  37. f1 = f1_score(ytest, model.predict(xtest))
  38. print('F1 score: ', f1)
  39. f1score.append(f1)
  40. plt.figure(figsize=(10,20))
  41. plt.subplot(211)
  42. print(sns.heatmap(cm,annot=True,fmt='d',cmap='Accent'))
  43. plt.subplot(212)
  44. plt.plot([0,1],'k--')
  45. plt.plot(fpr,tpr)
  46. plt.xlabel('false positive rate')
  47. plt.ylabel('true positive rate')
  48. plt.show()

我们把所有的评估结果汇总,得到一个模型结果对比表单

  1. # 构建一个Dataframe存储所有模型的评估指标
  2. evaluate = pd.DataFrame({})
  3. evaluate['Model'] = Model
  4. evaluate['Train score'] = train_scores
  5. evaluate['Test score'] = test_scores
  6. evaluate['Precision'] = precision_
  7. evaluate['Recall'] = recall_
  8. evaluate['F1 score'] = f1score
  9. evaluate['Roc-Auc score'] = rocauc
  10. evaluate

我们从上述baseline模型的汇总评估结果里看到:

  • 逻辑回归和随机森林在所有模型中表现最好,具有最高的训练和测试准确度得分,并且它具有低方差的泛化性
  • 从precision精度来看,逻辑回归0.976、随机森林0.982,也非常出色
  • 从recall召回率来看,Adaboost、逻辑回归、KNN表现都不错
  • F1-score会综合precision和recall计算,这个指标上,逻辑回归、随机森林、Adaboost表现都不错
  • Roc-Auc评估的是排序效果,它对于类别不均衡的场景,评估非常准确,这个指标上,逻辑回归和随机森林、Adaboost都不错

我们要看一下最终的交叉验证得分情况

  1. # 查看交叉验证得分
  2. for name,model in models:
  3. print("******",name,"******")
  4. cv_= cross_val_score(model,x_scaled,y,cv=5).mean()
  5. print(cv_)

从交叉验证结果上看,随机森林表现最优,我们把它选为最佳模型,并将进一步对它进行调优以获得更高的准确性。

超参数调优

关于建模与评估,大家可以参考ShowMeAI的相关文章。

深度学习教程(7) | 网络优化:超参数调优、正则化、批归一化和程序框架

我们刚才建模过程,使用的都是模型的默认超参数,实际超参数的取值会影响模型的效果。我们有两种最常用的方法来进行超参数调优:

  • 网格搜索:模型针对具有一定范围值的超参数网格进行评估,尝试参数值的每种组合,并实验以找到最佳超参数,计算成本很高。
  • 随机搜索:这种方法评估模型的超参数值的随机组合以找到最佳参数,计算成本低于网格搜索。

下面我们演示使用随机搜索调参优化。

  1. from sklearn.model_selection import RandomizedSearchCV
  2. params = {'n_estimators': [int(x) for x in np.linspace(start = 100, stop = 1200, num = 12)],
  3. 'criterion':['gini','entropy'],
  4. 'max_features': ['auto', 'sqrt'],
  5. 'max_depth': [int(x) for x in np.linspace(5, 30, num = 6)],
  6. 'min_samples_split': [2, 5, 10, 15, 100],
  7. 'min_samples_leaf': [1, 2, 5, 10]
  8. }
  9. random_search=RandomizedSearchCV(RTF,param_distributions=params,n_jobs=-1,cv=5,verbose=5)
  10. random_search.fit(xtrain,ytrain)

拟合随机搜索后,我们取出最佳参数和最佳估计器。

  1. random_search.best_params_
  1. random_search.best_estimator_

我们对最佳估计器进行评估

  1. # 最终模型
  2. final_mod = RandomForestClassifier(max_depth=10, max_features='sqrt', n_estimators=500)
  3. final_mod.fit(xtrain,ytrain)
  4. final_pred = final_mod.predict(xtest)
  5. print("Accuracy Score",accuracy_score(ytest,final_pred))
  6. cross_val = cross_val_score(final_mod,x_scaled,y,scoring='accuracy',cv=5).mean()
  7. print("Cross val score",cross_val)
  8. plot_roc_curve(final_mod,xtest,ytest)

我们可以看到,超参数调优后:

  • 模型的整体性能有所提升。
  • 准确度和交叉严重分数提高了。
  • Auc 得分达到了97%。

保存模型

最后我们对模型进行存储,以便后续使用或者部署上线。

  1. import joblib
  2. joblib.dump(final_mod,'hr_attrition.pkl')
  3. # ['hr_attrition.pkl']

参考链接

员工离职困扰?来看AI如何解决,基于人力资源分析的 ML 模型构建全方案 ⛵的更多相关文章

  1. 用R语言分析与预測员工离职

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/kMD8d5R/article/details/83542978 https://mmbiz.qpic ...

  2. 解决基于BAE python+bottle开发上的一系列问题 - artwebs - 博客频道 - CSDN.NET

    解决基于BAE python+bottle开发上的一系列问题 - artwebs - 博客频道 - CSDN.NET 解决基于BAE python+bottle开发上的一系列问题 分类: python ...

  3. kaggle之员工离职分析

    本文探讨的是kaggle中的一个案例-员工离职分析,从数据集中分析员工的离职原因,并发现其中的问题.数据主要包括影响员工离职的各种因素(工资.绩效.工作满意度.参加项目数.工作时长.是否升职.等)以及 ...

  4. C#.NET 大型企业信息化系统集成快速开发平台 4.2 版本 – 员工离职管理

    C#.NET 大型企业信息化系统集成快速开发平台 4.2 版本 – 员工离职管理 当公司有几万人,上千家加盟网点,几个庞大的直属分公司后,系统账户的有效管理也是一个头疼的问题,把所有的帐户及时进行科学 ...

  5. 【转载】NeurIPS 2018 | 腾讯AI Lab详解3大热点:模型压缩、机器学习及最优化算法

    原文:NeurIPS 2018 | 腾讯AI Lab详解3大热点:模型压缩.机器学习及最优化算法 导读 AI领域顶会NeurIPS正在加拿大蒙特利尔举办.本文针对实验室关注的几个研究热点,模型压缩.自 ...

  6. 详解Linux2.6内核中基于platform机制的驱动模型 (经典)

    [摘要]本文以Linux 2.6.25 内核为例,分析了基于platform总线的驱动模型.首先介绍了Platform总线的基本概念,接着介绍了platform device和platform dri ...

  7. Android Gradle基于参数化配置实现差异化构建

    一.背景: 项目中有一些特殊的需求,如个别渠道集成腾讯bugly,个别渠道集成易观统计,不同的渠道集成不同的推送策略(如Oppo渠道优先Opush推送),不同的渠道拥有不同的第三方登录集成等等.这些需 ...

  8. Microsoft宣布为Power BI提供AI模型构建器,关键驱动程序分析和Azure机器学习集成

    微软的Power BI现在是一种正在大量结合人工智能(AI)的商业分析服务,它使用户无需编码经验或深厚的技术专长就能够创建报告,仪表板等.近日西雅图公司宣布推出几款新的AI功能,包括图像识别和文本分析 ...

  9. 基于PaddlePaddle的语义匹配模型DAM,让聊天机器人实现完美回复 |

    来源商业新知网,原标题:让聊天机器人完美回复 | 基于PaddlePaddle的语义匹配模型DAM 语义匹配 语义匹配是NLP的一项重要应用.无论是问答系统.对话系统还是智能客服,都可以认为是问题和回 ...

随机推荐

  1. ant design pro生产和开发环境的坑

    1.axios的get请求开发环境会自动带上cookie,但是生产环境则不会,需要手动设置以下代码: axios.defaults.withCredentials=true; 2.models全局和局 ...

  2. JDBC(Java Database Connectivity)编写步骤

    JDBC是代表一组公共的接口,是Java连接数据库技术: JDBC中的这些公共接口和DBMS数据库厂商提供的实现类(驱动jar),是为了实现Java代码可以连接DBMS,并且操作它里面的数据而声名的. ...

  3. SLSA 框架与软件供应链安全防护

    随着软件供应链攻击浪潮愈演愈烈,Google 发布了一系列指南来确保软件包的完整性,旨在防止影响软件供应链的未经授权的代码修改.新的 Google SLSA 框架(Supply-chain Level ...

  4. RK3568开发笔记(四):在虚拟机上使用SDK编译制作uboot、kernel和buildroot镜像

    前言   上一篇搭建好了ubuntu宿主机开发环境,本篇的目标系统主要是开发linux+qt,所以需要刷上billdroot+Qt创建的系统,为了更好的熟悉原理和整个开发过程,选择从零开始搭建rk35 ...

  5. 【ASP.NET Core】选项模式的相关接口

    在 .NET 中,配置与选项模式其实有联系的(这些功能现在不仅限于 ASP.NET Core,而是作为平台扩展来提供,在其他.NET 项目中都能用).配置一般从多个来源(上一篇水文中的例子,记得否?) ...

  6. 用VS Code搞Qt6:编译源代码与基本配置

    先说明一下,本水文老周仅讨论新版的 Qt 6,旧版的 Qt 不讨论. 尽管 Qt 有自己的开发环境,但老周必须说句不装逼的话:真的不好用.说起写代码,当然了,用记事本也能写.但是,有个高逼格的工具,写 ...

  7. 使用flex防止fit-content子元素冲出父元素宽度的方法

    父元素设置了min-width:fit-content后,其宽度由子元素的宽度来决定 <!DOCTYPE html> <html lang="en"> &l ...

  8. python闭包函数及装饰器简介

    目录: 闭包函数简介 闭包函数的实际应用 装饰器简介 装饰器初期-完整版 装饰器语法糖 闭包函数简介 1.定义在函数内部的函数(函数的嵌套) 2.内部函数运用外部函数局部名称空间中的变量名 注:函数名 ...

  9. 华为分析&联运活动,助您提升游戏总体付费

    ARPU如何提升?付费率如何提升?活动ROI如何提升?这些都是游戏运营人员较常遇到的难题.华为分析与联运活动可以帮助运营提升这些用户付费指标,通过对玩家打标签和用户画像,对目标群体的进行精准推送,实现 ...

  10. MySQL 的prepare使用中的bug解析过程

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 目录 一.问题发现 二.问题调查过程 三.问题解决方案 四.问题总结 一.问题发现 在一次开发中使用 MySQL PREP ...