前言
員工離職,似乎已經成為每一家企業都要面對的問題,特別是優秀人才離職的問題會讓領導特別頭疼。今天我們就通過kaggle上某一家企業員工離職的真實數據來對離職率進行分析建模,並對預測結果顯示要離職的員工提出挽留建議。
目錄
1. 數據來源及背景
2. 明確分析目的
3. 數據探索分析
4. 數據預處理
5. 可視化分析
6. 特征工程
7. 邏輯回歸模型
8. 朴素貝葉斯模型
9. 模型評估之ROC曲線
正文
1. 數據來源及背景
數據來源: https://www.kaggle.com/jiangzuo/hr-comma-sep/version/1
數據背景: 該數據集是指某公司員工的離職數據, 其包含14999個樣本以及10個特征, 這10個特征分別為: 員工對公司滿意度, 最新考核評估, 項目數, 平均每月工作時長, 工作年限, 是否出現工作事故, 是否離職, 過去5年是否升職, 崗位, 薪資水平.
2. 明確分析目的
該數據集討論的是公司員工離職的問題, 那么, 我們首先需要對影響員工離職的因素進行分析:
將上述影響因素與現有的數據相結合來提出問題,進而明確我們的分析目的:
1) 員工對公司滿意度平均水平如何?員工的最新考核情況又是如何?員工所參加項目數是怎樣?員工平均每月工作時長以及平均工作年限分別是多少?
2) 當前離職率是多少?工作事故發生率?過去5年升職率?薪資水平又如何?共有多少種崗位?
3) 是否離職和其他9個特征的關系如何?
4) 根據現有數據, 如何對某個員工是否離職進行預測?
5) 針對當前的員工離職情況,企業該如何對待呢?
3. 數據探索分析
1) 查看前2行和后2行數據
import pandas as pd df = pd.read_csv(r'D:\Data\HR_comma_sep.csv') pd.set_option('display.max_rows', 4) df
數據維度14999行×10列, 除過崗位和薪資水平是字符型外, 其余均是數字 (具體是什么類型還需要進一步確定).
2) 查看數據類型等信息
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 14999 entries, 0 to 14998 Data columns (total 10 columns): satisfaction_level 14999 non-null float64 last_evaluation 14999 non-null float64 number_project 14999 non-null int64 average_montly_hours 14999 non-null int64 time_spend_company 14999 non-null int64 Work_accident 14999 non-null int64 left 14999 non-null int64 promotion_last_5years 14999 non-null int64 sales 14999 non-null object salary 14999 non-null object dtypes: float64(2), int64(6), object(2) memory usage: 1.1+ MB
前兩個特征為64位浮點型, 后兩個為字符型, 其余為64位整型, 且均無缺失值.
3). 描述性統計
df.describe() df.describe(include=['O']).T
1) 員工對公司滿意度平均水平如何?員工的最新考核情況又是如何?員工所參加項目數是怎樣?員工平均每月工作時長以及平均工作年限分別是多少?
員工對公司的滿意度: 范圍 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
2) 當前離職率是多少?工作事故發生率?過去5年升職率?薪資水平又如何?共有多少種崗位?
當前離職率為23.81%
工作事故發生率14.46%.
過去5年升職率2.13%.
薪資水平共有3個等級, 最多的是低等, 多達7316人.
員工崗位有10種, 其中最多的是銷售, 多達4140人.
4. 數據預處理
沒有缺失值, 因此不用處理缺失值.
1. 異常值
通過箱線圖查看異常值.
import seaborn as sns fig, ax = plt.subplots(1,5, figsize=(12, 2)) sns.boxplot(x=df.columns[0], data=df, ax=ax[0]) sns.boxplot(x=df.columns[1], data=df, ax=ax[1]) sns.boxplot(x=df.columns[2], data=df, ax=ax[2]) sns.boxplot(x=df.columns[3], data=df, ax=ax[3]) sns.boxplot(x=df.columns[4], data=df, ax=ax[4])
除了工作年限外, 其他均無異常值. 該異常值也反映了該公司員工中以年輕人為主
5. 可視化分析
在這里通過可視化的形式對第三個問題“是否離職和其他9個特征的關系如何?”進行回答.
1. 人力資源總體情況
from pyecharts import Pie attr = ["離職", "在職"] v1 =[df.left.value_counts()[1], df.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()
離職3571人, 在職11428人, 離職率為23.81%
2. 對公司滿意度與是否離職的關系
from pyecharts import Boxplot #字段重命名 df.columns=['satisfaction', 'evaluation', 'project', 'hours', 'years_work','work_accident', 'left', 'promotion', 'department', 'salary'] #繪制箱線圖 boxplot = Boxplot("對公司滿意度與是否離職關系圖", title_pos='center') x_axis = ['在職', '離職'] y_axis = [df[df.left == 0].satisfaction.values, df[df.left == 1].satisfaction.values] boxplot.add("", x_axis, boxplot.prepare_data(y_axis)) boxplot.render()
就中位數而言, 離職人員對公司滿意度相對較低, 且離職人員對公司滿意度整體波動較大. 另外離職人員中沒有滿意度為1的評價.
3. 最新考核評估與是否離職的關系
boxplot = Boxplot("最新評估與是否離職關系圖", title_pos='center') x_axis = ['在職', '離職'] y_axis = [df[df.left == 0].evaluation.values, df[df.left == 1].evaluation.values] boxplot.add("", x_axis, boxplot.prepare_data(y_axis)) boxplot.render()
就中位數而言, 離職人員的最新考核評估相對較高, 但其波動也大.
4. 所參加項目與是否離職的關系
from pyecharts import Bar, Pie, Grid #按照項目數分組分別求離職人數和所有人數 project_left_1 = df[df.left == 1].groupby('project')['left'].count() project_all = df.groupby('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") #繪制圓環圖 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%") grid = Grid(width=1200) grid.add(bar, grid_right="67%") grid.add(pie) grid.render()
通過下圖可以發現以下2點:
- 離職率隨着項目數的增多而增大, 2個項目數是特例
- 離職率較高的項目數2, 6, 7在總項目數中所占百分比相對較少. 項目數為2的這部分人可能是工作能力不被認可, 其離職人數也相對較多; 項目數為6, 7的這部分人一方面體現的是工作能力較強, 另一方面也說明了工作強度大, 其可能在其他企業能有更好的發展, 自然離職率也相對較高.
5. 平均每月工作時長和是否離職的關系
boxplot = Boxplot("平均每月工作時長與是否離職關系圖", title_pos='center') x_axis = ['在職', '離職'] y_axis = [df[df.left == 0].hours.values, df[df.left == 1].hours.values] boxplot.add("", x_axis, boxplot.prepare_data(y_axis)) boxplot.render()
通過下圖可以看到: 離職人員的平均每月工作時長相對較長, 每月按照22個工作日計算, 每日工作時數的中位數為10.18小時, 最大值為14.09小時.
6. 工作年限和是否離職的關系
from pyecharts import Bar, Pie, Grid #按照工作年限分別求離職人數和所有人數 years_left_0 = df[df.left == 0].groupby('years_work')['left'].count() years_all = df.groupby('years_work')['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") #繪制圓環圖 pie = Pie("各工作年限所占百分比", title_pos='center') pie.add('', years_all.index, years_all, radius=[35, 60], label_text_color=None, is_label_show=True, legend_orient="vertical", legend_pos="67%") grid = Grid(width=1200) grid.add(bar, grid_right="67%") grid.add(pie) grid.render()
通過下圖可以得出:
- 在各工作年限中, 離職人員較集中於3, 4, 5, 6年, 而6年以上則相對穩定
- 企業中工作年限為3年的人數所占百分比最多, 其次是2年, 主要以年輕人為主
7. 是否發生工作事故與是否離職的關系
from pyecharts import Bar accident_left = pd.crosstab(df.work_accident, df.left) attr = accident_left.index bar = Bar("是否發生工作事故與是否離職的關系圖", title_pos='center') bar.add("離職", attr, accident_left[1], is_stack=True) bar.add("在職", attr, accident_left[0], is_stack=True, legend_pos="left" , legend_orient="vertical", is_label_show=True) bar.render()
可以看到少部分出現工作事故, 且其中有較少部分人離職.
8. 5年內是否升職與是否離職的關系
promotion_left = pd.crosstab(df.promotion, df.left) attr = promotion_left.index bar = Bar("5年內是否升職與是否離職的關系圖", title_pos='center') bar.add("離職", attr, promotion_left[1], is_stack=True) bar.add("在職", attr, promotion_left[0], is_stack=True, legend_pos="left" , legend_orient="vertical", is_label_show=True) bar.render()
5年內多數人沒有升職, 離職率就相對較高.
9. 崗位與是否離職的關系
#分別計算各崗位離職人員比例和各崗位占總體百分比 department_left_0 = df[df.left == 0].groupby('department')['left'].count() department_all = df.groupby('department')['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='40%') 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%") #繪制圓環圖 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%") grid = Grid(width=1200, height=700) grid.add(bar, grid_top="50%", grid_bottom="25%") grid.add(pie) grid.render()
通過下圖可以看出:
- 在所有崗位中銷售崗位人數所占百分比最多, 達到27.6%, 最少是管理層, 其所占百分比是4.2%
- 令人意外的是崗位中hr離職率最高.
10. 薪資水平和是否離職的關系
from pyecharts import Bar #按照薪資水平分別求離職人數和所有人數 salary_left = pd.crosstab(df.salary, df.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()
薪資分為三個水平: 低等, 中等, 高等. 低等水平離職人數最多, 所占比例也最大, 而高等則最少.
在回答第4個問題之前,需要對數據進行特征處理工作,以滿足模型對數據的要求
6. 特征工程
1. 離散型數據處理
離散型數據可分為兩種: 一種是定序, 一種是定類.
1) 定序
薪資水平其含有順序意義, 因此將其字符型轉化為數值型
df['salary'] = df.salary.map({"low": 0, "medium": 1, "high": 2}) df.salary.unique()
array([0, 1, 2], dtype=int64)
2) 定類
崗位是定類型變量, 對其進行one-hot編碼, 這里直接利用pandas的get_dummies方法.
df_one_hot = pd.get_dummies(df, prefix="dep") df_one_hot.shape
(14999, 19)
2. 連續型數據處理
邏輯回歸模型能夠適應連續型變量, 因此可以不用進行離散化處理, 又由於多個特征之間差異差異較大會造成梯度下降算法收斂速度變慢, 故進行歸一化處理
#采用max-min歸一化方法 hours = df_one_hot['hours'] df_one_hot['hours'] = df_one_hot.hours.apply(lambda x: (x-hours.min()) / (hours.max()-hours.min()))
3. 相關系數
兩個變量均是連續型且具有線性關系, 則可以使用皮爾遜相關系數, 否則使用斯皮爾曼相關系數, 這里采用斯皮爾曼相關系數
#計算相關系數 correlation = df_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)
接下來就到了解釋第四個問題的環節了, 下面將采用兩種模型來對員工是否離職進行預測, 這兩個模型分別是邏輯回歸模型和朴素貝葉斯模型
7. 邏輯回歸模型
1. 划分數據集
from sklearn.model_selection import train_test_split #划分訓練集和測試集 X = df_one_hot.drop(['left'], axis=1) y = df_one_hot['left'] X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=1)
2. 訓練模型
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))
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=100, multi_class='warn', n_jobs=None, penalty='l2', random_state=None, solver='warn', tol=0.0001, verbose=0, warm_start=False) 訓練集准確率: 0.7978998249854155 測試集准確率: 0.7966666666666666
參考官方文檔說明, 參數C是正則化項參數的倒數, C的數值越小, 懲罰的力度越大. penalty可選L1, L2正則化項, 默認是L2正則化.
參數solver可選{‘newton-cg’, ‘lbfgs’, ‘liblinear’, ‘sag’, ‘saga’}這5個優化算法:
newton-cg, lbfgs是擬牛頓法, liblinear是坐標軸下降法, sag, saga是隨機梯度下降法, saga可以適用於L1和L2正則化項, 而sag只能用於L2正則化項.
#指定隨機梯度下降優化算法 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))
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=100, multi_class='warn', n_jobs=None, penalty='l2', random_state=None, solver='saga', tol=0.0001, verbose=0, warm_start=False) 訓練集准確率: 0.7980665055421285 測試集准確率: 0.7973333333333333
在選擇隨機梯度下降法后, 訓練集和測試集准確率均略有提升.
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_
array([25.52908068])
用該參數進行預測
LR = LogisticRegression(solver='saga', penalty='l2', C=25.52908068) print("訓練集准確率: ", LR.score(X_train, y_train)) print("測試集准確率: ", LR.score(X_test, y_test))
訓練集准確率: 0.7984832069339112 測試集准確率: 0.798
訓練集和測試集准確率均有所提升, 對於二分類問題, 准確率有時不是很好的評估方法, 這時需要用到混淆矩陣
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))
訓練集混淆矩陣: [[8494 647] [1771 1087]] 測試集混淆矩陣: [[2112 175] [ 431 282]]
from sklearn.metrics import classification_report print('訓練集:') print(classification_report(y_train, X_train_pred)) print('測試集:') print(classification_report(y_test, X_test_pred))
訓練集: precision recall f1-score support 0 0.83 0.93 0.88 9141 1 0.63 0.38 0.47 2858 micro avg 0.80 0.80 0.80 11999 macro avg 0.73 0.65 0.67 11999 weighted avg 0.78 0.80 0.78 11999 測試集: precision recall f1-score support 0 0.83 0.92 0.87 2287 1 0.62 0.40 0.48 713 micro avg 0.80 0.80 0.80 3000 macro avg 0.72 0.66 0.68 3000 weighted avg 0.78 0.80 0.78 3000
在訓練集有0.83的精准率和0.93的召回率, 在測試集上有0.83的精准率和0.92的召回率.
8. 朴素貝葉斯模型
朴素貝葉斯模型是基於特征條件獨立假設和貝葉斯理論.
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))
訓練集准確率: 0.7440620051670973 測試集准確率: 0.741 訓練集混淆矩陣: [[6791 2350] [ 721 2137]] 測試集混淆矩陣: [[1680 607] [ 170 543]] 訓練集: precision recall f1-score support 0 0.90 0.74 0.82 9141 1 0.48 0.75 0.58 2858 micro avg 0.74 0.74 0.74 11999 macro avg 0.69 0.75 0.70 11999 weighted avg 0.80 0.74 0.76 11999 測試集: precision recall f1-score support 0 0.91 0.73 0.81 2287 1 0.47 0.76 0.58 713 micro avg 0.74 0.74 0.74 3000 macro avg 0.69 0.75 0.70 3000 weighted avg 0.80 0.74 0.76 3000
可以看到其准確率較邏輯回歸低, 但是精准率高於邏輯回歸.
9. 模型評估之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的值就越大. 對於該數據集來說, 高斯朴素貝葉斯模型略優於邏輯回歸模型
以上就是對第四題的解釋, 下面進入第五題
5) 針對當前的員工離職情況,企業該如何對待呢?
該公司的員工離職現狀:
1.當前離職率為23.81%
2.離職人員對公司滿意度普遍較低
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.歡迎優秀流失人才回歸
總結
本文從邏輯回歸模型的原理開始介紹, 並通過實際案例對邏輯回歸模型進行應用, 但是結果還不是很好, 一方面是模型表現不是很好(當然, 也僅僅用到了邏輯回歸和朴素貝葉斯) ; 另一方面是特征工程沒有處理好(朴素貝葉斯模型對特征條件獨立假設較敏感), 應當進行特征選擇, 比如主成分分析.
參考資料:
網易雲課堂《吳恩達機器學習》
《Python數據分析與挖掘實戰》
聲明: 僅用於學習交流