该比赛为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离职率很高
Q2: 18岁-23岁员工离职率超过40%,23岁-33岁员工离职率在20%-40%
Q3: 工作投入度为1等级的员工离职率有近40%,达到了38%!!!
Q4: 加班的员工离职率是不加班员工的三倍!!!
进一步探索
收入在较低水平的销售部门员工在加班的况下离职率达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等方法