DAY3:离职率预测(下)

人工智慧共创平台-离职率预测(下)

  • 资料清洗

ax = sns.countplot(x="PerStatus", data=train)

接续昨天的资料清洗,透过上图可以发现离职(1)和尚未离职(0)的比例太过悬殊,离职的笔数只有796笔,所以我们也随机将尚未离职的资料取出796笔,这样总共笔数就变为1592笔,将离职和未离职的比例变成各半。

df = train.sample(frac=1)
quit_df = df.loc[df['PerStatus'] == 1]
non_quit_df = df.loc[df['PerStatus'] == 0][:796]
normal_distributed_df = pd.concat([quit_df, non_quit_df])
new_df = normal_distributed_df.sample(frac=1, random_state=42)

另外我们将'PerStatus'这个应变数栏位取出成为我们的label

label = new_df.loc[:,"PerStatus"]
label.value_counts()
new_df_no_label = new_df.drop(columns="PerStatus")

我们把资料整理成要丢入模型的样子了,剩下就可以丢入建模啦!

这里我们简单总结一下,我们做了哪些资料清洗。

  1. 首先我们检查到了资料有没离职但却没有後续资料的员工
  2. 我们发现离职(0)和未离职员工(1)的资料比例太悬殊,所以我们将资料取成一样的比例。
  3. 这边我们没有做到特徵工程的部分,有兴趣的夥伴可以用视觉化的方式去做特徵的筛选。

  • 建模

讲到python建模,一定会用到Scikit-Learn这个套件,非常好用,里面包刮各种机器学习的模型,这里我会用到多种模型来做预测,

下面程序码的train_test_split是帮你把资料切成训练集和测试集,test_size可以设定你测试集的比例,random_state是让它固定,不会每次切的时候都是不一样的训练集和测试集。

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(new_df_no_label, label, test_size=0.2, random_state=2)

下面StratifiedKFold跟KFold交叉验证差别在於,StratifiedKFold做交叉验证,相较於KFold,StratifiedKFold会照比例在每个data set中抽取资料作验证。

from sklearn.model_selection import StratifiedKFold

sk_fold = StratifiedKFold(10,shuffle=True, random_state=42)

而GridSearchCV函数会自动作Cross Validation,可以省去大量时间帮我们找出最佳参数组合。

from sklearn.model_selection import GridSearchCV

接下来就来建立模型啦~先给定模型,然後clf是我设定的参数,会透过GridSearchCV这个套件帮我筛选每个模型的最佳参数。

关於机器学习模型,小弟这边有在github做一些整理,有需要也可以去看一下,不知道对你们有没有帮助XDDDD
GitHub连结

先import SKlearn的模型套件。

# NAIBE BAYES
from sklearn.naive_bayes import GaussianNB
#KNN
from sklearn.neighbors import KNeighborsClassifier
#RANDOM FOREST
from sklearn.ensemble import RandomForestClassifier
#LOGISTIC REGRESSION
from sklearn.linear_model import LogisticRegression
#SVM
from sklearn.svm import SVC
#DECISON TREE
from sklearn.tree import DecisionTreeClassifier
#XGBOOST
from xgboost import XGBClassifier
#AdaBoosting Classifier
from sklearn.ensemble import AdaBoostClassifier
#GradientBoosting Classifier
from sklearn.ensemble import GradientBoostingClassifier
#HistGradientBoostingClassifier
from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier, StackingClassifier
from sklearn.metrics import confusion_matrix
g_nb = GaussianNB()
knn = KNeighborsClassifier()  # 参数:n_neighbors(邻居数:预设为5)、weights(权重,预设为uniform)、leaf_size(叶的大小:预设为30)
ran_for  = RandomForestClassifier()
# n_estimators:树的颗数、max_depth:最大深度,剪枝用,超过全部剪掉。
# min_samples_leaf:搭配max_depth使用,一个节点在分枝後每个子节点都必须包含至少min_samples_leaf个训练样本
# bootstrap:重新取样原有Data产生新的Data,取样的过程是均匀且可以重复取样
log_reg = LogisticRegression() #penalty:惩罚函数(预设L2)、C:正则强度倒数,预设为1.0、solver:解决器(默认='lbfgs'),saga对所有惩罚都可以使用
tree= DecisionTreeClassifier()
xgb = XGBClassifier()#https://www.itread01.com/content/1536594984.html 参数详解
ada_boost = AdaBoostClassifier() # https://ask.hellobi.com/blog/zhangjunhong0428/12405 参数详解
grad_boost = GradientBoostingClassifier(n_estimators=100) # https://www.itread01.com/content/1514358146.html 参数详解
hist_grad_boost = HistGradientBoostingClassifier() # https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.HistGradientBoostingClassifier.html

clf = [("Naive Bayes", g_nb, {}), \
       ("K Nearest", knn, {"n_neighbors": [3, 5, 6, 7, 8, 9, 10], "leaf_size": [25, 30, 35]}), \
       ("Random Forest", ran_for, {"n_estimators": [10, 50, 100, 200, 400], "max_depth": [3, 10, 20, 40], "random_state": [99],"min_samples_leaf": [5, 10, 20, 40, 50], "bootstrap":[False]}), \
       ("Logistic Regression", log_reg, {"penalty": ['l2'], "C": [100, 10, 1.0, 0.1, 0.01],      "solver": ['saga']}), \
       ("Decision Tree", tree, {}), \
       ("XGBoost", xgb,{"n_estimators": [200], "max_depth": [3, 4, 5], "learning_rate": [.01,    .1, .2], "subsample": [.8],"colsample_bytree": [1], "gamma": [0, 1, 5], "lambda": [.01,.1, 1]}), \("Adapative Boost", ada_boost, {"n_estimators": [100], "learning_rate": [.6, .8, 1]}), \("Gradient Boost", grad_boost, {}), \
("Histogram GB", hist_grad_boost,{"loss": ["binary_crossentropy"], "min_samples_leaf":           [5, 10, 20, 40, 50], "l2_regularization": [0, .1, 1]})]

前置作业都准备好之後,我们可以来训练模型了,我透过GridSearchCV这个套件一次训练多个模型让他找出最佳参数组合。

stack_list = []
train_scores = pd.DataFrame(columns=["Name", "Train Score", "Test Score"])

i = 0
for name, clf1, param_grid in clf:
    clf = GridSearchCV(clf1, param_grid=param_grid, scoring="accuracy", cv=sk_fold,                       return_train_score=True)
    clf.fit(X_train, y_train)  # .reshape(-1,1)
    y_pred = clf.best_estimator_.predict(X_test)

    cm = confusion_matrix(y_test, y_pred)
    print(cm)
    print("=====================================")

    train_scores.loc[i] = [name, clf.best_score_, (cm[0, 0] + cm[1, 1,]) / (cm[0, 0] + cm[0, 1]+ cm[1, 0] + cm[1, 1])]
    stack_list.append(clf.best_estimator_)
    i = i + 1

est = [("g_nb", stack_list[0]), \
       ("knn", stack_list[1]), \
       ("ran_for", stack_list[2]), \
       ("log_reg", stack_list[3]), \
       ("dec_tree", stack_list[4]), \
       ("XGBoost", stack_list[5]), \
       ("ada_boost", stack_list[6]), \
       ("grad_boost", stack_list[7]), \
       ("hist_grad_boost", stack_list[8])]

这几个模型练完之後,我想再加上集成学习stacking堆叠法去做训练。

sc = StackingClassifier(estimators=est,final_estimator = None,cv=sk_fold,passthrough=False)
sc.fit(X_train,y_train)
y_pred = sc.predict(X_test)
cm1 = confusion_matrix(y_test,y_pred)
y_pred_train = sc.predict(X_train)
cm2 = confusion_matrix(y_train,y_pred_train)
train_scores.append(pd.Series(["Stacking",(cm2[0,0]+cm2[1,1,])/(cm2[0,0]+cm2[0,1]+cm2[1,0]+cm2[1,1]),(cm1[0,0]+cm1[1,1,])/(cm1[0,0]+cm1[0,1]+cm1[1,0]+cm1[1,1])],index=train_scores.columns),ignore_index=True)

因为这边我没有多做特徵的筛选,只有做一些简单的资料清洗,所以跑出来的准确度不是很好,如下图:

观察一下上述的准确度,Random Forest是我们Test Score最高,我选择用Random Forest来做为我最後要上传所使用的模型。

ran_for.fit(X_train,y_train)
test['PerStatus'] = ran_for.predict(test)
submission = test[['PerNo','PerStatus']]
submission.to_csv("./submission1.csv", index=False)

最後产出的CSV档我们就上传到AIdea的平台啦。

恩...29名...这排名和分数就先不要去在意他啦XDDDD至少我们自己从头到尾做了一次,也上传得到了成绩和名次,整个过程在资料清洗和特徵筛选的部分,有很多可以优化的,这次主要是想让刚接触的夥伴可以知道怎麽去完成一个平台的题目,如果有大大有得到更好的分数和名次也欢迎来一起分享给大家讨论喔!!


  • 资料视觉化

再来教大家做简单的资料视觉化,正常来说拿到资料一定要用资料视觉化去观察各个特徵喔!我们会用到matplotlib这个套件,另外还有seaborn这个套件,它是建立於matplotlib之上,因此seaborn可以直接与之产生的图互动。

import matplotlib.pyplot as plt
import seaborn as sns

首先import套件之後呢,来看看统计图表,我们可以透过图表来筛选哪些是重要特徵,每个特徵我们都可以用合理的方法去推断他为何重要或影响力较低,例如性别的离职人数比较:

sex_list = list(train['sex'].unique())
sex_sum_leave = []
for i in sex_list:
    x = train[train['sex']==i]
    sex_leave = sum(x.PerStatus)
    sex_sum_leave.append(sex_leave)

data = pd.DataFrame({'sex_list': sex_list,'sex_leave':sex_sum_leave})
new_index = (data['sex_leave'].sort_values(ascending=True)).index.values
sorted_data2 = data.reindex(new_index)

plt.figure(figsize=(15,10))
sns.barplot(x=sorted_data2['sex_list'], y=sorted_data2['sex_leave'])
plt.xticks(rotation= 90)
plt.xlabel('Sex')
plt.ylabel('sum of Leave')
plt.title("Sum of Leave of Sex")

可以观察到性别1比性别0要多出三倍的离职人数,可以说明性别这个自变数对於离职这个应变数是有影响力的,因此性别可以视为重要特徵

再来看看每年度的离职率~

year_list = list(train['yyyy'].unique())
year_leaverate = []
for i in year_list:
    x = train[train['yyyy']==i]
    year_leave_rate = sum(x.PerStatus)/len(x)
    year_leaverate.append(year_leave_rate)

data = pd.DataFrame({'year_list': year_list,'year_leave_ratio':year_leaverate})
new_index = (data['year_leave_ratio'].sort_values(ascending=True)).index.values
sorted_data2 = data.reindex(new_index)

plt.figure(figsize=(15,10))
sns.barplot(x=sorted_data2['year_list'], y=sorted_data2['year_leave_ratio'])
plt.xticks(rotation= 90)
plt.xlabel('Year')
plt.ylabel('Leave Rate')
plt.title("Leave rate of Year")
plt.show()

这看起来差异就并不是那麽大,那从图中可以了解到2014年的离职比率是最高的,也说明年份相较於性别,对於应变数离职的影响力就没那麽大。

各部门离职人数

department_list = list(train['归属部门'].unique())
department_leave_list = []
for i in department_list:
    x = train[train['归属部门']==i]
    department_leave = sum(x.PerStatus)
    department_leave_list.append(department_leave)

data = pd.DataFrame({'dep_list': department_list,'dep_leave':department_leave_list})
new_index = (data['dep_leave'].sort_values(ascending=True)).index.values
sorted_data2 = data.reindex(new_index)

plt.figure(figsize=(15,10))
sns.barplot(x=sorted_data2['dep_list'], y=sorted_data2['dep_leave'])
plt.xticks(rotation= 90)
plt.xlabel('dep')
plt.ylabel('sum of Leave')
plt.title("Sum of Leave of dep")

(抱歉座标的字有点小><)从图中可以发现部门代号20208为离职人数最多的,部门代号14040则为次之。另外也可以知道部门对於离职是有差异性的,我也会把部门视为重要特徵

可以一一的对每个特徵做统计图表的检视来筛选变数,这里就简单示范,後面就不一一赘述啦,读者可以去把每个变数都看一遍,把不重要的变数排除看看,最後建模看是否准确度会比较高,应该是多少有帮助的喔!!

今天就先到这边罗,如果过程有误也欢迎留言纠正,教学相长嘛哈哈哈,我相信这边有更多大神有更完善的处理方法,也欢迎分享给小弟,让小弟多多学习。


  • 今日小结

做资料分析,一定是资料清洗以及特徵筛选最为重要,我这边是有偷懒,但大家切记在帮客户做资料分析的时候,前面两个动作一定要做的完善且谨慎,这样得到得结果会越是理想。

另外要做机器学习建议先去了解机器学习的模型,甚麽情况适合用甚麽模型,二分类以及多分类等等,使用的模型都会影响到结果的好坏,但没有哪种模型是最好的,每个模型都有优缺点,建议多方尝试,找出最适合的模型。

参考资料:

https://www.itread01.com/content/1536594984.html
https://ask.hellobi.com/blog/zhangjunhong0428/12405
https://www.itread01.com/content/1514358146.html
https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.HistGradientBoostingClassifier.html


<<:  [Day 17] 针对网页的单元测试(三)

>>:  [Day-9] if-else小练习

有关多台网页伺器, 但对外IP才几个

各位大大 因企业运维来了一位大哥建议公司把各种网站服务放在VM上,VM可以切很多台服务器出来,这样子...

[Day 29] JS 实作练习 - YouTube API

前言 在练习 Ajax 串接实作时,馒头计画中就推荐就找了几个实作作业,找有开放资源的网站(如:Yo...

day 8 - 程序码也要断舍离

生活要断舍离, 程序码也要喔。 写Go只要一支main.go就可以开始写了, 想写多长就写多长, 要...

[Day 15]从零开始学习 JS 的连续-30 Days---forEach 的综合应用

forEach 的综合应用 如何整合 innerHTML 资料 范例一 如图所示一个物件阵列取里面的...

DAY 19 Big Data 5Vs – Variety(速度) EMR (2)

接续介绍昨天建立的EMR丛集: 建立的丛集可以在左方工具栏的丛集分页找到 步骤的状态可以到「步骤」分...