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等方法
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,862评论 2 89
  • 我很羡慕一些人的坚持,可是他这么告诉我,有些坚持,只是因为曾经太不努力;后来,我也竟然和他一样,有的坚持是曾经太不...
    遗弃小屋阅读 463评论 0 2
  • 它是调皮的.它在你面前出现的时候总是玩着隐身术,它一靠近你,就会令你莫名其妙的大笑又或者大哭,等到你发觉它曾经来过...
    真夏的素颜阅读 229评论 -2 3
  • 今天的文章,来肃正一些不正之风。 首当其冲的就是:【抑郁症的人,究竟是怎样的?】 每当我发表一篇文章,评论里总会冒...
    左灯右右右行阅读 6,871评论 97 122
  • 我们每天都会有很多问题。为什么交通这么堵?为什么孩子不听话?为什么工作这么多?为什么老板不待见我?为什么我每天都有...
    邓男神Sweety阅读 697评论 0 0