DC员工离职预测训练赛

该比赛为DC练习赛,要求使用逻辑回归的方法,从给定的影响员工离职的因素和员工是否离职的记录,建立一个逻辑回归模型预测有可能离职的员工。

相关数据的介绍,请参考:比赛地址

1.探索数据

import numpy as np
import pandas as pd

# 读取数据
train = pd.read_csv('pfm_train.csv')
test = pd.read_csv('/pfm_test.csv')
print('train size:{}'.format(train.shape))  # train size:(1100, 31)
print('test size:{}'.format(test.shape))  #test size:(350, 30)
# 查看数据集中是否含有缺失值:无缺失值
# train.isnull().mean()

1.1 数据分析

# EmployeeNumber为员工ID,将其删除
train.drop(['EmployeeNumber'], axis = 1, inplace = True)

# 将Attrition(该字段为标签)移至最后一列,方便索引
Attrition = train['Attrition']
train.drop(['Attrition'], axis = 1, inplace = True)
train.insert(0, 'Attrition', Attrition)

使用pyecharts从各维度上对离职人数以及离职率进行分析

from pyecharts import Bar,Line,Grid
from pyecharts import Overlap

# 通过图表分析哪些因素是主要影响员工离职的因素
def get_chatrs(train, col):
    data = train.groupby([col])['Attrition']
    data_sum = data.sum() # 离职人数
    data_mean = data.mean()  # 离职率
    
    bar = Bar(col, title_pos="45%")
    bar.add('离职人数', data_sum.index, data_sum.values, mark_point = ['max'],
            yaxis_formatter =  '人', yaxis_max = 200 , legend_pos="40%", legend_orient="vertical", 
            legend_top="95%", bar_category_gap = '25%')

    line = Line()
    line.add('离职率', data_mean.index, data_mean.values, mark_point = ['max'], mark_line = ['average'],
        yaxis_max = 0.8)

    overlap = Overlap(width=900, height=400)
    overlap.add(bar)
    overlap.add(line, is_add_yaxis=True, yaxis_index=1)

    return overlap
    

from pyecharts import Page
page = Page()
for col in train.columns[1:]:
    page.add(get_chatrs(train, col))
page.render('pages.html')
page

运行此段代码后,发现图表数据显示有错误,检查代码没有发现问题,手动的在图表中刷新数据后,问题得到解决

# 公司总体的离职率在16.2%
train['Attrition'].mean()

通过观察图表发现以下问题
Q1研发部门离职人数最多,这主要是因为该公司研发部门人数最多的原因,虽然人数多,但是研发部门离职率最低,离职率最高的部门是HR,该部门也是公司人数最少的部门,人员架构不太稳定?(综合其他因素如’Education‘、’JobRole‘等,也发现,HRD离职率很高

Department.png

Q2: 18岁-23岁员工离职率超过40%,23岁-33岁员工离职率在20%-40%

Age.png

Q3: 工作投入度为1等级的员工离职率有近40%,达到了38%!!!

JobInvolvement.png

Q4: 加班的员工离职率是不加班员工的三倍!!!

OverTime.png

进一步探索

部门&加班&收入

收入在较低水平的销售部门员工在加班的况下离职率达80%,且该部门员工在加班情况下,无论收入水平如何,离职率都高于公司的整体离职率

部门工作满意度

HRD员工离职的原因之一:工作满意度比较低,是否存在办公室政治的原因?

2.数据处理&特征处理

2.1 数据处理

# 在分析中发现有一些字段的值是单一的,进一步验证
single_value_feature = []
for col in train.columns:
    lenght = len(train[col].unique())
    if lenght == 1:
        single_value_feature.append(col)

single_value_feature  # ['Over18', 'StandardHours']

'Over18', 'StandardHours'这两个字段的值是唯一的,删除这两个字段

# 删除这两个字段
train.drop(['Over18', 'StandardHours'], axis = 1, inplace = True)
train.shape  # (1100, 28)

由于数据集中没有缺失值,这里不需要对缺失值做处理

2.2 特征处理

主要是对部分特征进行分组以及one-hot编码

# 对收入进行分箱
print(train['MonthlyIncome'].min())  # 1009
print(train['MonthlyIncome'].max())  # 19999
print(test['MonthlyIncome'].min())  # 1051
print(test['MonthlyIncome'].max())  # 19973

为了在train和test中的MonthlyIncome进行分组后的区间一致,需要保持两个数据集中MonthlyIncome的最大值和最小值一致,这里使用等宽分组

由于test数据集中MonthlyIncome的最小值比train数据集中的最小值大,最大值比train数据集中的最大值小,需要人工插入最大值最小值后才能进行分组,这样在test数据集中MonthlyIncome的分组区间才能与train中MonthlyIncome分组一致,这个后面会进行具体操作

# 使用pandas的cut进行分组,分为10组
train['MonthlyIncome'] = pd.cut(train['MonthlyIncome'], bins=10)
# 将数据类型为‘object’的字段名提取出来,并使用one-hot-encode对其进行编码
col_object = []
for col in train.columns[1:]:
    if train[col].dtype == 'object':
        col_object.append(col)
col_object

对train数据集进行one-hot编码

train_encode = pd.get_dummies(train)

保存数据集,方便日后使用

train.to_csv('trainwithoutencode.csv')
train_encode.to_csv('train.csv')

2.3 特征共线性处理

corr = train.corr()

import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

sns.heatmap(corr, xticklabels=corr.columns.values, yticklabels=corr.columns.values)
plt.show()

特征共线性

'TotalWorkingYears' & 'JobLevel'
'YearsAtCompany' & 'YearsWithCurrManager'存在共线性,选择删除其中一个特征即可

train_encode.drop(['TotalWorkingYears', 'YearsWithCurrManager'], axis = 1, inplace = True)

3. 建模预测

3.1 建模

from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

X = train_encode.iloc[:, 1:]
y = train_encode.iloc[:, 0]

# 划分训练集以及测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2)

lr = LogisticRegression()
lr.fit(X_train, y_train)
lr.score(X_train, y_train)  # 0.8886363636363637

由于存在随机性,最终在训练集上的score大约在0.88~0.9波动

pred = lr.predict(X_test)
np.mean(pred == y_test)  # 0.8863636363636364

结果在测试集上的结果与训练集差不多,下面看一下预测结果的混淆矩阵是怎样的

from sklearn.metrics import confusion_matrix
from sklearn.metrics import precision_score, recall_score, f1_score

#对整个train数据集的混淆矩阵
y_pred = lr.predict(X)
confmat= confusion_matrix(y_true=y,y_pred=y_pred)#输出混淆矩阵
fig,ax = plt.subplots(figsize=(2.5,2.5))
ax.matshow(confmat,cmap=plt.cm.Blues,alpha=0.3)
for i in range(confmat.shape[0]):
    for j in range(confmat.shape[1]):
        ax.text(x=j,y=i,s=confmat[i,j],va='center',ha='center')
plt.xlabel('predicted label')
plt.ylabel('true label')
plt.show()

#召回率、准确率、F1
print ('precision:%.3f' %precision_score(y_true=y,y_pred=y_pred))
print ('recall:%.3f' %recall_score(y_true=y,y_pred=y_pred))
print ('F1:%.3f' %f1_score(y_true=y,y_pred=y_pred))

混淆矩阵

发现准确率和召回率都是很很满意,然后尝试调参,lr中可调整的参数不多,调整后发现模型的精度提高不是很大

3.2 预测

# test数据集处理
test.drop(['EmployeeNumber', 'Over18', 'StandardHours'], axis = 1, inplace = True)
test_MonthlyIncome = pd.concat((pd.Series([1009, 19999]), test['MonthlyIncome'])) 
# 在指定位置插入与train中MonthlyIncome的max、min一致的数值,之后再删除
test['MonthlyIncome'] = pd.cut(test_MonthlyIncome, bins=10)[2:]  # 分组并去除对应的值
test_encode = pd.get_dummies(test)
test_encode.drop(['TotalWorkingYears', 'YearsWithCurrManager'], axis = 1, inplace = True)# 输出结果
sample = pd.DataFrame(lr.predict(test_encode))
sample.to_csv('sample.csv')

按照要求修改sample的格式后上传

结果排名

上传结果后,排在60名,top5%,还算可以的结果

4. 反思

  • 对于逻辑回归,在参数上调整空间比较小,应该注重在特征工程上的处理,除了使用one-hot编码的方法外,还可以尝试使用归一化、标准表等等,使用交叉验证的方式查看模型的稳定性
  • 也可以使用随机森林、GBDT等方法

你可能感兴趣的:(DC员工离职预测训练赛)