
简介这套源码围绕人员流失预测与数据挖掘分析设计面向企业数据分析人员、人力资源管理者及机器学习入门开发者旨在通过历史员工数据识别离职关键因素为降低流失率提供决策依据。资源压缩包内共有七十四份文件整体大小约十点七兆字节主要包含Python脚本、JavaScript脚本、CSS样式、HTML页面、XML配置、pickle模型结果以及多张数据可视化图片。其中Python脚本用于数据预处理、特征选择与模型训练JavaScript与CSS/HTML负责前端交互和页面展示XML文件用来定义运行参数pickle文件则保存了决策树、K近邻、支持向量机、随机森林等多种算法的训练结果。该资源已有二百五十四人学习下载。借助这套源码读者可以完整走通从数据清洗、特征工程、多算法建模到结果可视化的全流程了解Django框架下系统模块的组织方式还能直接利用保存好的模型文件对比不同算法效果并针对自身企业数据快速修改与复用搭建一套可用的员工流失预警与决策支持工具。1. 人员流失预测与数据挖掘分析系统先搞清楚这个系统到底在解决什么问题同期入职的两个算法工程师一个把流失预测模型的AUC跑到了0.93另一个只做到0.86。半年后前者写的模型脚本躺在仓库里没人再看后者的预测名单被HR部门当作月度绩效访谈的依据。差别不在学过而在后者把数据挖掘分析做成了系统目标变量定义清楚、特征工程能解释、模型输出直接落到风险名单上。人员流失预测与数据挖掘分析系统就是这类工具——它面对的不是论文数据集而是一张真实的员工表。它要解决三类问题哪些人未来半年可能离职、为什么、以及下一步该找谁聊。适合正在做HR数据分析、或想用Python给组织部门提效的从业者前提是你能拿到带离职标记的历史员工数据。这篇笔记我从特征工程讲到模型评估再讲到系统落地和避坑照着做能跑出一版可用的源码方案。2. 从HR表到训练集人员流失预测的数据清洗与特征工程2.1 流失预测的本质先决定你是在做分类还是做风险排序人员流失预测在机器学习层面是标准的二分类问题员工在某个时间窗口内是否离职。但业务上要的往往不是“会/不会”的标签而是风险排序——下个月最可能离职的Top 50人是谁。这两个目标对应完全不同的评估方式和阈值策略搞混了后面全白做。常用的是公开的员工流失样例数据通常是几千行带离职标记的结构化表格常见字段包括年龄、月收入、岗位级别、满意度打分、加班标记、入职年限、上次晋升间隔等。这类数据挖掘项目的第一件事不是建模而是给目标变量做定义标记为1的是“已离职”的人0是“在职”的人。看起来简单但真实项目里最阴险的问题藏在时间上——离职面谈评分、离职当月的绩效考核、最后半年调薪记录这些字段在预测时点根本拿不到放进去就是数据泄漏。所以资深做法是设计“观察期—表现期”窗口用观察期比如过去6个月里能够稳定采集到的特征去预测表现期内是否离职。没有时间列的静态截面数据只能做基线模型上线后定期重建这是后话。提示如果数据只有某一年截面的员工状态加离职标记没有时间列模型只能做静态预测。真实生产环境里这种模型过期很快需要每个月重新训练。2.2 用pandas把原始员工表清洗成建模样本原始数据最常见的三种问题离职标记是文本“Yes/No”、有整列缺失、存在员工编号重复。先写一个数据体检脚本。import pandas as pd import numpy as np df pd.read_csv(hr_attrition.csv, encodingutf-8) print(df.shape) print(df.info()) # 目标变量转成 0/1流失记为1 df[target] df[Attrition].map({Yes: 1, No: 0}) print(df[target].value_counts()) # 删除完全没数值变化的列比如员工编号、无意义标志位 nunique df.nunique() constant_cols nunique[nunique 1].index.tolist() df df.drop(columnsconstant_cols, errorsignore) # 数值列中位数填充分类列众数填充 num_cols df.select_dtypes(include[np.number]).columns.tolist() cat_cols df.select_dtypes(include[object]).columns.tolist() df[num_cols] df[num_cols].fillna(df[num_cols].median()) df[cat_cols] df[cat_cols].fillna(df[cat_cols].mode().iloc[0])value_counts用来确认正负样本比例流失率在很多企业里就是10%20%的量级这个比例决定了后续要不要处理不平衡。nunique() 1会把只有单一值的列全部筛掉像员工编号这种每一行都不同的字段不会触发这个条件因为它nunique很大所以真正的坑在另一种情况模型拿ID当特征训练时AUC虚高、上线后彻底失效后面避坑章节会专门讲。缺失值填中位数和众数是保守做法胜在稳定可复现。如果样本够多还可以给每条缺失记录补一个“是否缺失”的标记列给树模型去挖掘缺失模式。第一次做这个系统不建议引入太复杂的填充逻辑先把数据跑通。2.3 特征工程五个在流失预测里几乎必用的维度流失预测特征可以归成五类这五个维度在公开样例和内部HR系统里基本都存在按这个框架整理不会漏。维度代表字段说明满意度JobSatisfaction、EnvironmentSatisfaction、WorkLifeBalance有序分类直接做序数编码付出与回报MonthlyIncome、OverTime、StockOptionLevel相对值比绝对值更有意义稳定性YearsAtCompany、YearsInCurrentRole、JobLevel构成员工在职时间画像晋升YearsSinceLastPromotion长期不晋升是强信号工作负荷TotalWorkingYears、PercentSalaryHike需警惕与离职目标的时间顺序有序分类列如满意度直接映射成数值1-4不要做one-hot否则序数信息被拆散成多个哑变量模型反而学不到“满意度越低风险越高”的单调关系。无序分类列如部门、岗位保持字符串交给管道里的OneHotEncoder处理。派生特征我通常会做两个晋升等待比和相对收入差。这两个特征带业务语义比单纯堆原始字段更能得到可解释的预测结果。# 晋升等待比上次晋升间隔 占 总工龄 的比例 df[promo_wait_ratio] df[YearsSinceLastPromotion] / (df[YearsAtCompany] 1e-3) # 相对收入个人月收入 减去 同岗位平均月收入 df[income_gap] df[MonthlyIncome] - df.groupby(JobRole)[MonthlyIncome].transform(mean)加1e-3是为了防止工龄为0时除零报错。收入差用的是岗位均值如果你手里的数据有部门、职级两级结构建议先按“部门职级”分组再算均值这个分组口径比单纯岗位更贴近员工真实感知的公平性。这里JobRole是字段名内部数据换了名字就在这一行改。这些清洗和特征逻辑写到后面会越来越长所以第一版就要把预处理封成一个函数所有实验都从同一入口过数据。改特征时只改一处模型训练、名单导出、月度报告全部复用同一个预处理版本这是整个源码工程后期能不能维护住的关键。3. 流失预测建模从基线模型到调参评估的完整可跑代码3.1 为什么先跑逻辑回归它和树模型一样值得认真对待HR流失数据大部分是几千行的小样本特征是表格结构。这种数据上逻辑回归和随机森林是绕不开的两个模型LightGBM排第三。逻辑回归的优点是线性可解释、训练快、系数天然能给业务讲权重缺点是学不到非线性交互需要手工构造交叉特征。随机森林不需要归一化就能抓到非线性关系但它是黑匣子特征重要性还有数值型变量偏高的毛病。模型优点缺点适用场景逻辑回归解释强、稳定、快需手工加交互项基线模型、制度效果评估随机森林非线性、不用归一化黑匣子、重要性有偏第一版主力模型LightGBM准确率高、缺值友好数据少时极易过拟合数据大于5000行且特征稳定我的习惯是先跑逻辑回归作为基线几乎不调参确认数据管道没毛病之后跑随机森林。LightGBM留到最后在HR这种千级样本上它很容易在验证集上表现惊艳、上线就翻车只做参考对比。3.2 一次训练两个模型标准化、交叉验证和评估指标一起跑用sklearn的Pipeline把预处理和模型包在一起避免在训练测试拆分时泄漏预处理参数。这里有一个容易错的地方StandardScaler必须在训练集上fit再transform验证集和测试集直接对全量数据fit再拆分验证集就脏了。Pipeline能强制保证这个顺序。import numpy as np from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline from sklearn.preprocessing import OneHotEncoder, StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split, cross_val_score from sklearn.metrics import roc_auc_score, precision_recall_curve # 预处理统一入口 X df.drop(columns[Attrition, target]) y df[target] num_cols X.select_dtypes(include[np.number]).columns.tolist() cat_cols X.select_dtypes(include[object]).columns.tolist() preprocessor ColumnTransformer([ (num, StandardScaler(), num_cols), (cat, OneHotEncoder(handle_unknownignore), cat_cols), ]) models { lr: LogisticRegression(max_iter1000, class_weightbalanced, random_state42), rf: RandomForestClassifier(n_estimators300, min_samples_leaf10, class_weightbalanced, random_state42), } X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) probas {} for name, model in models.items(): pipe Pipeline([(prep, preprocessor), (clf, model)]) pipe.fit(X_train, y_train) probas[name] pipe.predict_proba(X_test)[:, 1] auc roc_auc_score(y_test, probas[name]) print(f{name} AUC: {auc:.3f})这里的两个模型都用了class_weightbalanced因为流失样本天然少不处理的话模型会把所有人判成在职准确率照样能到85%。handle_unknownignore是OneHotEncoder的必备参数不设置的话线上预测出现新部门直接报错。随机森林的min_samples_leaf10是防止单棵树过深过拟合HR数据量小叶子节点设10比较稳。注意循环里的probas字典保存了两个模型的预测概率后面阈值搜索要分别用。如果不存字典只在循环里打印AUC后面想要逻辑回归的概率就得重训一遍费时间。3.3 指标看哪几个准确率最没用召回率与命中率的平衡流失预测里准确率是个陷阱。流失率15%的数据集把所有员工判成“不流失”准确率85%但这个模型没有任何决策价值。业务真正盯的是给出的高风险名单里到底有多少人真的走了。所以核心指标只有三个AUC、Top名单命中率、以及设定阈值后的召回率。其中AUC评估排序能力命中率和召回率评估名单质量。下面的代码从概率里找能用的阈值。# 以随机森林的预测概率为例选择业务可用的阈值 precisions, recalls, thresholds precision_recall_curve(y_test, probas[rf]) # 目标名单召回率至少60%同时precision尽量高 for t, p, r in zip(thresholds, precisions[:-1], recalls[:-1]): if r 0.6 and p 0.3: print(f阈值 {t:.3f} - precision {p:.3f}, recall {r:.3f}) break阈值搜索的定价是两个业务约束召回率决定了系统能发现多少真实流失者 precision决定了HR拿到名单里有多少是误报。HR每周能访谈的名单是有限的如果名单里十个人只走了一个业务就不会再用。常见做法是和业务确定“召回不低于60%、命中率不低于30%”一类的目标再回代找阈值。这里用测试集选阈值在严格流程上是偷懒的正确做法是单独切验证集选阈值、测试集只做最终评测数据量小时可先按这个能跑的版本做。3.4 LightGBM要不要上数据量说了算如果手头数据超过5000行、字段稳定可以加一个LightGBM做对照。注意要在交叉验证里看它的稳定性单次切分的结果容易骗人。from sklearn.ensemble import HistGradientBoostingClassifier hgb HistGradientBoostingClassifier( max_iter200, learning_rate0.05, max_leaf_nodes15, class_weightbalanced, random_state42, ) pipe_hgb Pipeline([(prep, preprocessor), (clf, hgb)]) cv_auc cross_val_score(pipe_hgb, X_train, y_train, cv5, scoringroc_auc) print(fHGB CV AUC: {cv_auc.mean():.3f} ± {cv_auc.std():.3f})我这里选HistGradientBoostingClassifier而不是LightGBM原库因为sklearn接口不需要额外装包样本少时更不容易过拟合。max_leaf_nodes15严格限制树复杂度learning_rate0.05配合max_iter200做慢速学习。交叉验证的标准差如果超过0.03说明模型对数据划分很敏感不建议上线。4. 数据挖掘分析把预测黑匣子变成业务能看懂的几张图4.1 用KMeans给员工群体分群哪类小群人流失风险最高模型给的是概率业务需要的是认知。常见做法是对核心特征做KMeans聚类把员工分成几个群体再交叉模型预测概率看哪个人群风险最高。这才能回答“哪种员工最容易流失”而不是“这个人概率多少”。from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler # 只选取带业务解释的连续特征参与聚类 cluster_features [JobSatisfaction, MonthlyIncome, YearsAtCompany, WorkLifeBalance] X_cluster df[cluster_features].copy() scaled StandardScaler().fit_transform(X_cluster) kmeans KMeans(n_clusters4, n_init10, random_state42) df[cluster] kmeans.fit_predict(scaled) # 每个簇的成员数、平均流失率、平均满意度 for c in sorted(df[cluster].unique()): sub df[df[cluster] c] print(c, 人数, len(sub), 流失率, round(sub[target].mean(), 3), 满意度, round(sub[JobSatisfaction].mean(), 2))KMeans的标准化必须做否则MonthlyIncome几千的数值会主导距离计算满意度1到4的量纲直接被淹没。簇数量不是越多越好一般先试3到5个看每个簇是否能在业务上概括成一句话比如“高收入、高工龄、满意度低的老员工”“低收入、高加班、频繁换岗的新人”。概括不出来的簇说明特征选得不对或者k值不合理。n_init10是sklearn新版本的默认行为显式写出来是为了防止旧版本陷入局部最优。跑完聚类后把每个簇的流失率和全公司基线比。如果某个簇流失率是整体的2倍这就是分析系统最该输出的洞察比单条预测概率更有决策价值。4.2 特征重要性随机森林的importances和逻辑回归系数要分开看随机森林的feature_importances和逻辑回归的系数是两套逻辑。树模型的重要性偏向取值跨度大的数值特征而逻辑回归系数反映的是单位边际影响。两者都排在前面的特征才是真正值得写进报告的东西。# 取出已训练的随机森林管道 rf_pipe models[rf] importances pd.Series( rf_pipe.named_steps[clf].feature_importances_, indexrf_pipe.named_steps[prep].get_feature_names_out() ).sort_values(ascendingFalse).head(10) print(importances)用Pipeline训练后取特征名要调用get_feature_names_out()这个方法返回的列名里OneHot编码的字段会带后缀比如“OverTime_Yes”“OverTime_No”直接看会很碎。我的习惯是先按原始字段名聚合把所有以“OverTime”开头的列加总再和逻辑回归的系数绝对值排序放在同一张表里对比。# 逻辑回归系数的绝对值排序 lr_pipe models[lr] coef_abs pd.Series( np.abs(lr_pipe.named_steps[clf].coef_[0]), indexlr_pipe.named_steps[prep].get_feature_names_out() ).sort_values(ascendingFalse).head(10) # 两列对比取两边Top10的并集 top_feats set(importances.head(10).index) | set(coef_abs.head(10).index) print(top_feats)两边同时上榜的特征说明既有区分度又稳定可以直接给业务写“建议重点监测”。只在一侧上榜的特征要小心解释树上重要可能是数值分布造成的假象逻辑回归系数大可能是多重共线性放大——所以两个方法交叉印证就是数据分析这块最朴素也最有效的技巧。注意不要用feature_importances去谈因果它只反映预测贡献不反映干预效果。这点在后面避坑章节里还会强调。4.3 输出风险名单与画像从模型到分析系统的最后一步模型的价值最终体现在名单上。把全量在职员工的预测概率落成带风险等级的表导出成Excel可打开的csv这是分析系统每天要被业务方看到的东西。result X_test.copy() result[prob] probas[rf] # 用分位数定档不用固定阈值 low, high result[prob].quantile([0.6, 0.85]) result[risk_level] pd.cut( result[prob], bins[0, low, high, 1], labels[低, 中, 高] ) result result.sort_values(prob, ascendingFalse) # 按部门和风险等级汇总 pivot result.groupby([Department, risk_level], observedFalse).size().unstack() print(pivot) result.to_csv(attrition_risk_list.csv, indexFalse, encodingutf-8-sig)编码用utf-8-sig是让生成的csv在Excel直接打开不乱码这是中文数据落地的老经验。风险等级的分位线每次训练完都要重新计算——样本分布变了固定阈值0.8很可能一个高危险名单都出不来用分位数能保证每次输出名单的规模稳定符合业务对“每月固定访谈多少人”的预期。整套源码工程做到这里一般就可以拆成四个独立模块数据预处理、模型训练、名单预测、报告导出。每个模块一个脚本输入输出都是标准csv互不耦合。这种结构看起来简单但实际维护比一个大而全的notebook省心得多因为HR数据每个月都在变只重跑数据预处理到名单预测这一段就够了。5. 流失预测系统的5个常见坑现象、原因与解决办法坑1测试集AUC很高名单却没人用现象模型在测试集上AUC达到0.9以上但输出给HR的名单里访谈命中的人寥寥无几。原因目标泄漏。最常见是把离职当年的绩效评分、离职访谈记录、当年调薪幅度当成了特征。这些信息在预测时点根本不存在——你不可能在1月份预测6月离职时用上6月才会填写的绩效数据。解决做特征时逐个字段确认“这个值在预测截止日那天是否已经产生”。拿不准的字段宁可删掉。粗筛规则是凡是带“离职”“访谈”“当年”字样的字段一律不给模型用只保留观察期截止日之前能稳定采集到的信息。坑2模型把所有员工都判成“不流失”现象训练的模型预测概率全部低于阈值输出的高风险名单是空的或者名单里全是同一类边缘案例。原因典型的不平衡问题。流失率只有10%左右模型发现全部判负能拿到很高的准确率于是放弃了正样本。解决两个手段同时用。模型侧加class_weightbalanced数据处理侧做阈值搜索而不是用默认0.5做切分。默认0.5是基于正负样本均衡的假设在流失预测里完全不适用必须用第3.3节的方法按业务目标找阈值。坑3过采样后验证指标虚高上线回测直接翻车现象用SMOTE把训练集平衡后测试集AUC大幅上升但上线后实际命中率远低于预期。原因SMOTE被错误地应用到了全量数据上。过采样生成的合成样本和真实样本在特征空间高度重合如果先过采样再切分训练测试集合成样本会同时出现在两边验证结果严重失真。解决过采样只能放在每一折交叉验证内部只对训练折做过采样验证折保持原始分布。如果工程上不好实现干脆不用SMOTE直接用class_weight加阈值搜索效果差距远没有想象中大。坑4模型上线第二个月开始失效现象第一个月预测名单命中率尚可第二个月明显下滑第三个月基本没法用。原因特征分布漂移。公司招聘策略变了、晋升通道调整了、或者某个部门大规模重组员工特征的整体分布跟着变旧模型学到的规律过期了。解决每月出名单时同时计算当月特征均值与训练集特征均值的差异可以简单看PSI群体稳定性指数。PSI超过0.25就触发重训。如果HR系统每个月自动更新数据就把训练脚本挂成月度任务每次用最近12个月的数据重训一次不要抱着一个模型跑半年。坑5把“相关”说成“因果”报告被管理层挑战现象分析报告写“加班多的员工离职风险高2倍建议禁止加班”结果业务部门反弹因为真正的原因可能是项目压力和团队管理而不是加班本身。原因数据挖掘发现的是相关性不是因果。加班和离职可能同时受第三个变量影响比如项目烂、管理者风格差。解决报告里只写“高风险人群的特征是加班多、满意度低、长期未晋升”要落实到管理动作必须做访谈验证或准实验设计。分析系统输出的是线索不是结论。给业务建议时用“建议优先调研”而不是“必须禁止”。6. 让分析系统可验证、可迭代一份月度流失预测报告怎么落地模型做出来只是上半段分析系统的下半段是循环每月出名单、记录预测时间、三个月后回测命中率、修正特征和参数。系统价值不在预测概率有多准而在“预测排名前20%的人占了真实流失人数的多少”——这个数字必须被度量否则整个系统就只是自我感动。一份可落地的月度报告至少包含四个sheet风险名单、部门分布、特征对比、上期回测。最小实现用pandas就可以完成。import pandas as pd with pd.ExcelWriter(monthly_report.xlsx) as writer: result.to_excel(writer, sheet_name风险名单, indexFalse) pivot.to_excel(writer, sheet_name部门分布) # 上期名单回测预测Top20%与实际离职名单的交集 last_ids pd.read_csv(last_month_risk_list.csv)[EmployeeID] actual_left df[df[target] 1][EmployeeID] top_n int(len(last_ids) * 0.2) hit_rate len(set(last_ids[:top_n]) set(actual_left)) / top_n coverage len(set(last_ids[:top_n]) set(actual_left)) / len(actual_left) print(fTop20%名单命中率: {hit_rate:.2f}, 覆盖真实流失: {coverage:.2f})回测时最好按排名切名单而不是按风险等级。比如取上次预测概率最高的20%作为Top名单再看这20%里真实离职的人占全部离职的百分比。这种做法能稳定回答“模型排序到底有没有用”HR也能通过这个数字判断要不要继续信任这套分析系统。我这些年做类似系统的习惯是每次模型迭代都留下一个带日期的预测文件哪怕模型没变化也照常存一份。三到五个月后回测脚本把这些历史名单和真实离职数据一匹配哪个版本的模型真正有效就一目了然。没有这份回测记录所有的调参和特征工程都是玄学业务不会再相信第二版。预测名单本身不产生价值名单变成访谈动作再被回测验证整个闭环才成立。每月做一次回测比把AUC往上抠0.01重要得多。希望帮到你。本文还有配套的精品资源点击获取