前言
目前社会上呈现出一种公司招不到人,大批失业人员的矛盾现象,且大部分公司的离职率居高不下,很多入职没多久就辞职,所花费的培训招聘等资源都浪费了。为了弄清楚公司员工离职原因,通过kaggle上某一家企业员工离职的真实数据来对离职率进行分析建模。
文章目录
一、背景
Ⅰ 数据来源
Ⅱ 数据背景
数据背景: 该数据集是指某公司员工的离职数据, 其包含14999个样本以及10个特征, 这10个特征分别为: 员工对公司满意度, 最新考核评估, 项目数, 平均每月工作时长, 工作年限, 是否出现工作事故, 是否离职, 过去5年是否升职, 岗位, 薪资水平。
Ⅲ 分析目的
结合现有数据特征,需要分析解决的问题如下:
- 员工对公司满意度平均水平如何?员工的最新考核情况又是如何?员工所参加项目数是怎样?员工平均每月工作时长以及平均工作年限分别是多少?
- 当前离职率是多少?工作事故发生率?过去5年升职率?薪资水平又如何?共有多少种岗位?
- 是否离职和其他9个特征的关系如何(相关分析)?
- 根据现有数据, 是否可以对某个员工是否离职进行预测(建模)?
二、数据探索性分析
Ⅰ 数据类型
#coding=utf-8
hr = pd.read_csv('D:\\hr.csv')
hr.info()
前两个特征为64位浮点型, 后两个为字符型, 其余为64位整型, 且均无缺失值。
Ⅱ 描述性统计
print(hr.describe(include=['O']).T)
#员工对公司满意度, 最新评价, 项目数, 平均每月工作时长, 工作年限, 是否出现工作事故,1代表有, 是否离职,1代表离职, 过去5年是否升职,1代表升职, 岗位, 薪资水平.
hr.describe()
- 员工对公司满意度平均水平如何?员工的最新考核情况又是如何?员工所参加项目数是怎样?员工平均每月工作时长以及平均工作年限分别是多少?
员工对公司的满意度: 范围 0.09~1, 中位数0.640, 均值0.613, 总体来说员工对公司较满意。
最新考核评估: 范围 0.36~1, 中位数0.720, 均值0.716, 员工考核平均水平中等偏上。
项目数: 范围 2~7个, 中位数4, 均值3.8, 平均参加项目数为4个。
平均每月工作时长: 范围96~310小时, 中位数200, 均值201。
工作年限: 范围2~10年, 中位数3, 均值3.5。 - 当前离职率是多少?工作事故发生率?过去5年升职率?薪资水平又如何?共有多少种岗位?
当前离职率为23.81%。
工作事故发生率14.46%。
过去5年升职率2.13%。
薪资水平共有3个等级, 最多的是低等, 多达7316人。员工岗位有10种, 其中最多的是销售, 多达4140人。
Ⅲ 数据预处理
a.缺失值处理
之前已经在查看数据中看到本次数据比较“干净”,没有缺失值,所以不需要进行这个操作。如果有缺失值的话,缺失的数据较少则总结删除,如果比较多的话那就均值或中值填充。
b.异常值处理
#利用箱线图查看异常值
fig, ax = plt.subplots(1,5, figsize=(12, 2))
sns.boxplot(x=hr.columns[0], data=hr, ax=ax[0])
sns.boxplot(x=hr.columns[1], data=hr, ax=ax[1])
sns.boxplot(x=hr.columns[2], data=hr, ax=ax[2])
sns.boxplot(x=hr.columns[3], data=hr, ax=ax[3])
sns.boxplot(x=hr.columns[4], data=hr, ax=ax[4])
除了工作年限外, 其他均无异常值。大部分工作年限都在五年以下,"新鲜血液"较多,侧面反映年轻人较多。
c.重复值处理
数据不存在要哪个特征是唯一值的,所以不需要进行重复值处理。
三、数据分析
Ⅰ 可视化分析
a.离职率
利用pyecharts包可视化,pyecharts包动态显示,十分方便。在安装包的时候有很多坑需要避开,很容易某些图表就不可用。
from pyecharts import Pie
attr = ["离职", "在职"]
v1 =[hr.left.value_counts()[1], hr.left.value_counts()[0]]
pie = Pie("该公司人力资源总体情况", title_pos='center')
pie.add(
"",
attr,
v1,
radius=[35, 65],
label_text_color=None,
is_label_show=True,
legend_orient="vertical",
legend_pos="left",
)
pie.render("./公司人力资源总体情况.html")
离职3571人, 在职11428人, 离职率为23.81%
b.公司满意度
sns.set(style="ticks", color_codes=True,font="simhei")
sns.boxplot(x=hr['left'],y=hr['satisfaction_level'], data=hr)
plt.title('对公司满意度与是否离职关系图',fontsize=10)
plt.show()
就中位数而言, 离职人员对公司满意度相对较低, 且离职人员对公司满意度整体波动较大. 另外离职人员中没有满意度为1的评价,大部分都在0.4左右,最低在0.1左右。
c.工作时长
.set(style="ticks", color_codes=True,font="simhei")
sns.boxplot(x=hr['left'],y=hr['average_montly_hours'], data=hr)
plt.title('平均每月工作时长与是否离职关系图',fontsize=10)
plt.show()
就中位数而言, 离职人员工作时长比较多, 且离职人员工作时长整体波动较大. 另外离职人员最高工作时长比在职人员高,最低工作时长比其低。
d.考核标准
sns.set(style="ticks", color_codes=True,font="simhei")
sns.boxplot(x=hr['left'],y=hr['last_evaluation'], data=hr)
plt.title('考核标准与是否离职关系图',fontsize=10)
plt.show()
就中位数而言, 离职人员考核标准比较高, 且离职人员工作时长整体波动较大. 另外离职人员最高考核标准比在职人员高,最低考核标准比其低。
- 侧面反映凡事过犹不及,只有在正常的范围内才能更好的留住人才,高端人才能力强也不能安排过多,应该多培养一些高端人才平分任务,过高过低都会造成流失。
e.项目数
from pyecharts import Bar, Pie
#按照项目数分组分别求离职人数和所有人数
project_left_1 = hr[hr.left == 1].groupby('number_project')['left'].count()
project_all = hr.groupby('number_project')['left'].count()
#分别计算离职人数和在职人数所占比例
project_left1_rate = project_left_1 / project_all
project_left0_rate = 1 - project_left1_rate
attr = project_left1_rate.index
bar = Bar("所参加项目数与是否离职的关系图", title_pos='10%')
bar.add("离职", attr, project_left1_rate, is_stack=True)
bar.add("在职", attr, project_left0_rate, is_stack=True, legend_pos="left", legend_orient="vertical")
bar.render("./所参加项目数与是否离职的关系图.html")
from pyecharts import Pie
pie = Pie("各项目数所占百分比", title_pos='center')
pie.add('', project_all.index, project_all, radius=[35, 60], label_text_color=None,
is_label_show=True, legend_orient="vertical", legend_pos="67%")
pie.render("./各项目数所占百分比.html")
离职率随着项目数的增多而增大,但 2个项目数是特例,可能是2这部分人可能是工作能力不被认可,离职人数就多了。
离职率较高的项目数2, 6, 7在总项目数中所占百分比相对较少。大部分还是3,4,5个项目。
f.工作年限
from pyecharts import Bar
#按照工作年限分别求离职人数和所有人数
years_left_0 = hr[hr.left == 0].groupby('time_spend_company')['left'].count()
years_all = hr.groupby('time_spend_company')['left'].count()
#分别计算离职人数和在职人数所占比例
years_left0_rate = years_left_0 / years_all
years_left1_rate = 1 - years_left0_rate
attr = years_all.index
bar = Bar("工作年限与是否离职的关系图", title_pos='10%')
bar.add("离职", attr, years_left1_rate, is_stack=True)
bar.add("在职", attr, years_left0_rate, is_stack=True, legend_pos="left" , legend_orient="vertical")
bar.render("./工作年限与是否离职的关系图.html")
#绘制圆环图
from pyecharts import Pie
pie = Pie("各工作年限所占百分比", title_pos='center')
pie.add('', years_all.index, years_all, radius=[30, 60], label_text_color=None,
is_label_show=True, legend_orient="vertical", legend_pos="67%")
pie.render("./各工作年限所占百分比.html")
在各工作年限中, 离职人员较集中于3, 4, 5, 6年, 而6年以上则相对稳定,企业中工作年限为3年的人数所占百分比最多, 其次是2年, 加起来占到60%多,公司员工主要以年轻人为主
g.工作岗位
department_left_0 = hr[hr.left == 0].groupby('promotion_last_5years')['left'].count()
department_all = hr.groupby('promotion_last_5years')['left'].count()
department_left0_rate = department_left_0 / department_all
department_left1_rate = 1 - department_left0_rate
attr = department_all.index
bar = Bar("岗位与离职比例的关系图", title_top='10%')
bar.add("离职", attr, department_left1_rate, is_stack=True)
bar.add("在职", attr, department_left0_rate, is_stack=True, is_datazoom_show=True,
xaxis_interval=0, xaxis_rotate=30, legend_top="45%", legend_pos="80%")
bar.render("./岗位与离职比例的关系图.html")
#绘制圆环图
pie = Pie("各个岗位所占百分比", title_pos='left')
pie.add('', department_all.index, department_all,center=[50, 23], radius=[18, 35], label_text_color=None,
is_label_show=True, legend_orient="vertical", legend_pos="80%", legend_top="4%")
pie.render("./各个岗位所占百分比.html")
在所有岗位中销售岗位人数所占百分比最多, 达到27.6%, 最少是管理层, 其所占百分比是4.2%,令人意外的是岗位中hr离职率最高,其次是accounting,最低的是管理层。
h.薪资水平
from pyecharts import Bar
#按照薪资水平分别求离职人数和所有人数
salary_left = pd.crosstab(hr.salary, hr.left).sort_values(0, ascending = False)
attr = salary_left.index
bar = Bar("薪资水平和是否离职的关系图", title_pos='center')
bar.add("离职", attr, salary_left[1], is_stack=True)
bar.add("在职", attr, salary_left[0], is_stack=True, legend_pos="left" , legend_orient="vertical", is_label_show=True)
bar.render("./薪资水平和是否离职的关系图.html")
薪资水平为low级别的离职率最高,high的离职率最低。两者成正比关系。
i.工作事故
from pyecharts import Bar
#按照项目数分组分别求离职人数和所有人数
project_left_1 = hr[hr.left == 1].groupby('Work_accident')['left'].count()
project_all = hr.groupby('Work_accident')['left'].count()
#分别计算离职人数和在职人数所占比例
project_left1_rate = project_left_1 / project_all
project_left0_rate = 1 - project_left1_rate
attr = project_left1_rate.index
bar = Bar("是否发生工作事故与是否离职的关系图", title_pos='10%')
bar.add("离职", attr, project_left1_rate, is_stack=True)
bar.add("在职", attr, project_left0_rate, is_stack=True, legend_pos="left", legend_orient="vertical")
bar.render("./是否发生工作事故与是否离职的关系图.html")
发生事故的离职率更低,推测可能是无法找到更好的工作,侧面也可以反映出该司人文关怀还可以。
Ⅱ 预测分析
a.特征工程
1)离散型数据处理
离散型数据可分为两种: 一种是定序, 一种是定类。
a.定序
薪资水平其含有顺序意义, 因此将其字符型转化为数值型。
hr['salary'] = hr.salary.map({"low": 0, "medium": 1, "high": 2})
hr.salary.unique()
b.定类
岗位是定类型变量, 对其进行one-hot编码, 这里直接利用pandas的get_dummies方法。
hr_one_hot = pd.get_dummies(hr, prefix="dep")
hr_one_hot.shape
2)连续型数据处理
逻辑回归模型能够适应连续型变量, 因此可以不用进行离散化处理, 又由于多个特征之间差异差异较大会造成梯度下降算法收敛速度变慢, 故进行归一化处理
hours = hr_one_hot['average_montly_hours']
hr_one_hot['average_montly_hours'] = hr_one_hot.average_montly_hours.apply(lambda x: (x-hours.min()) / (hours.max()-hours.min()))
3)相关系数
两个变量均是连续型且具有线性关系, 则可以使用皮尔逊相关系数, 否则使用斯皮尔曼相关系数, 这里采用斯皮尔曼相关系数
#计算相关系数
correlation = hr_one_hot.corr(method = "spearman")
plt.figure(figsize=(18, 10))
#绘制热力图
sns.heatmap(correlation, linewidths=0.2, vmax=1, vmin=-1, linecolor='w',fmt='.2f',
annot=True,annot_kws={'size':10},square=True)
b.逻辑回归模型
- 划分数据集
from sklearn.model_selection import train_test_split
#划分训练集和测试集
X = hr_one_hot.drop(['left'], axis=1)
y = hr_one_hot['left']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
- 训练模型
from sklearn.linear_model import LogisticRegression
LR = LogisticRegression()
print(LR.fit(X_train, y_train))
print("训练集准确率: ", LR.score(X_train, y_train))
print("测试集准确率: ", LR.score(X_test, y_test))
#指定随机梯度下降优化算法
LR = LogisticRegression(solver='saga')
print(LR.fit(X_train, y_train))
print("训练集准确率: ", LR.score(X_train, y_train))
print("测试集准确率: ", LR.score(X_test, y_test))
在选择随机梯度下降法后, 训练集和测试集准确率上涨了一点
3. 调参
#用准确率进行10折交叉验证选择合适的参数C
from sklearn.linear_model import LogisticRegressionCV
Cs = 10**np.linspace(-10, 10, 400)
lr_cv = LogisticRegressionCV(Cs=Cs, cv=10, penalty='l2', solver='saga', max_iter=10000, scoring='accuracy')
lr_cv.fit(X_train, y_train)
lr_cv.C_
用该参数进行预测,此时需要将之前的LR模型注释,不然会报错。
LR = LogisticRegression(solver='saga', penalty='l2', C=25.52908068)
print(LR.fit(X_train, y_train))
print("训练集准确率: ", LR.score(X_train, y_train))
print("测试集准确率: ", LR.score(X_test, y_test))
训练集和测试集准确率均有所提升, 对于二分类问题, 准确率有时不是很好的评估方法, 这时需要用到混淆矩阵
4. 混淆矩阵
from sklearn import metrics
X_train_pred = LR.predict(X_train)
X_test_pred = LR.predict(X_test)
print('训练集混淆矩阵:')
print(metrics.confusion_matrix(y_train, X_train_pred))
print('测试集混淆矩阵:')
print(metrics.confusion_matrix(y_test, X_test_pred))
from sklearn.metrics import classification_report
print('训练集:')
print(classification_report(y_train, X_train_pred))
print('测试集:')
print(classification_report(y_test, X_test_pred))
该模型在训练集有0.83的精准率和0.93的召回率, 在测试集上有0.83的精准率和0.92的召回率。
c.朴素贝叶斯模型
朴素贝叶斯模型是基于特征条件独立假设和贝叶斯理论.
from sklearn.naive_bayes import GaussianNB
from sklearn.model_selection import cross_val_score
#构建高斯朴素贝叶斯模型
gnb = GaussianNB()
gnb.fit(X_train, y_train)
print("训练集准确率: ", gnb.score(X_train, y_train))
print("测试集准确率: ", gnb.score(X_test, y_test))
X_train_pred =gnb.predict(X_train)
X_test_pred = gnb.predict(X_test)
print('训练集混淆矩阵:')
print(metrics.confusion_matrix(y_train, X_train_pred))
print('测试集混淆矩阵:')
print(metrics.confusion_matrix(y_test, X_test_pred))
print('训练集:')
print(classification_report(y_train, X_train_pred))
print('测试集:')
print(classification_report(y_test, X_test_pred))
可以看到其准确率较逻辑回归低, 但是精准率高于逻辑回归.
d.模型评估之ROC曲线
from sklearn import metrics
from sklearn.metrics import roc_curve
#将逻辑回归模型和高斯朴素贝叶斯模型预测出的概率均与实际值通过roc_curve比较返回假正率, 真正率, 阈值
lr_fpr, lr_tpr, lr_thresholds = roc_curve(y_test, LR.predict_proba(X_test)[:,1])
gnb_fpr, gnb_tpr, gnb_thresholds = roc_curve(y_test, gnb.predict_proba(X_test)[:,1])
#分别计算这两个模型的auc的值, auc值就是roc曲线下的面积
lr_roc_auc = metrics.auc(lr_fpr, lr_tpr)
gnb_roc_auc = metrics.auc(gnb_fpr, gnb_tpr)
plt.figure(figsize=(8, 5))
plt.plot([0, 1], [0, 1],'--', color='r')
plt.plot(lr_fpr, lr_tpr, label='LogisticRegression(area = %0.2f)' % lr_roc_auc)
plt.plot(gnb_fpr, gnb_tpr, label='GaussianNB(area = %0.2f)' % gnb_roc_auc)
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.0])
plt.title('ROC')
plt.xlabel('FPR')
plt.ylabel('TPR')
plt.legend()
plt.show()
ROC曲线越靠近左上角说明分类效果越好, 与之对应的auc的值就越大. 对于该数据集来说, 高斯朴素贝叶斯模型略优于逻辑回归模型
四、总结
该公司的员工离职现状:
1.当前离职率为23.81%;
2.离职人员对公司满意度普遍较低,在0.4左右,应该反思具体哪里做的不对,有则改之4,无则加勉;
3.离职人员的考核成绩相对较高, 说明离职人员多数为优秀人才;优秀人才承受了更大的压力,绷太紧导致离职,应该加快人才的培养,不会出现断层情况,一旦有离职立马有人接上;
4.项目数范围为2~7个, 其中参加7个项目的离职率最高,其次是2个的; 7个的工作能力较强, 在其他企业有更好的发展, 2个的可能是在该公司中工作能力不被认可;项目数的安排应该控制在一个合理的范围,既能让公司盈利,又能让员工实现价值,达到双赢的目的;
5.离职人员的平均每月工作时长较长
6.离职人员的工作年限集中在3到6年
7.5年内未升职的离职率较高
8.hr岗位的离职率最高, 目前企业普遍存在"留人难, 招人难”,这可能是导致该岗位的离职率高的主要原因
9.低等薪资水平的离职率最高
由于企业培养人才是需要大量的成本, 为了防止人才再次流失, 因此应当注重解决人才的流失问题, 也就是留人, 另外如果在招人时注意某些问题, 也能在一定程度上减少人才流失. 因此, 这里可将对策分为两种, 一种是留人对策, 一种是招人对策。
- 留人对策:
1.建立良好的薪酬制度, 至少不得低于市场平均水平
2.建立明朗的晋升机制,公平公开公正晋升
3.完善奖惩机制, 能者多劳, 也应多得
4.实现福利多样化, 增加员工对企业的忠诚度
5.重视企业文化建设, 树立共同的价值观,鼓励员工在建设过程中提意见
6.改善办公环境以及营造良好的工作氛围,及时纾解员工心理问题
7.鼓励员工自我提升,设立学习奖 - 招人对策:
1.明确企业招聘需求, 员工的能力应当与岗位需求相匹配,而不是为指标而招
2.与应聘者坦诚相见,不得为了完成指标哄骗应聘者
3.招聘期间给予的相关承诺必须实现
4.欢迎优秀流失人才回归