
简介一套基于数据挖掘的人员流失预测与分析系统源码面向企业HR与数据分析人员利用机器学习对员工历史数据建模实现流失风险预测、因素分析与可视化展示辅助管理层制定留人策略。压缩包共74个文件总大小约10.7MB主要包含Python脚本、JavaScript脚本、CSS样式、HTML页面、XML配置、pickle模型文件及PNG/JPG图片等覆盖数据预处理、特征选择、模型训练与前端交互等环节。其中决策树、KNN、SVM、随机森林等模型结果已打包便于直接对照学习。目前已有254人学习适合具备一定Python基础、希望掌握数据挖掘在人员管理领域应用的开发者参考。1. 人员流失预测系统的技术结构拆解Django 与 pickle 模型的组合形态满意度、考核分、项目数和月均工时往往比 HR 访谈更能暴露离职风险。这套基于人员流失预测与数据挖掘分析系统的设计源码把这类问题做成了一个可运行的 Django 工具四个 scikit-learn 分类器以 pickle 文件固化Web 端负责表单录入和结果展示静态图与 ECharts 承担数据可视化。源码包包含 14 个 Python 脚本、13 个 JS、6 个 CSS、3 个 HTML 模板和 4 个模型 pickle以及按满意度、加班时长、晋升潜力等维度拆分的分组统计图。人员流失预测源码常见的短板是只有 Notebook 没有界面这个项目把训练结果与展示串在了一起适合做课设扩展或 HR 分析平台原型。读者有一点 Django 和 scikit-learn 概念即可下面按选特征、请求链路、可视化、验证的顺序拆解。2. 特征工程与数据挖掘featureSelection.py 究竟选了哪些特征2.1 从素材文件名反推特征字典源码包里没有直接给出 CSV但 static 目录下 19 张 PNG 的命名已经暴露了建模时使用的特征satisfaction_level满意度、last_evaluation最近考核分、number_project参与项目数、average_montly_hours月均工时、accident_risk事故风险、departmental_performance部门绩效、promotion_potential晋升潜力、salary_fairness薪酬公平感、teamwork_ability团队协作能力。加上 predict 输出结果标签就是员工是否流失。这个结构接近常见的 HR Analytics 数据集目标变量是二分类样本里流失与留存两类数量通常接近 1:4所以模型评估不能只看 accuracy还要看 recall 和 AUC。把这些特征整理成一张表方便后端对齐字段特征名类型建议处理方式典型影响satisfaction_levelfloat 0~1直接归一化低满意度对应流失概率升高last_evaluationfloat 0~1直接使用与绩效、晋升交叉后出现双峰number_projectint标准化或分桶项目数过多或过少都容易离职average_montly_hoursint标准化明显高于同事平均工时的人更容易走accident_risk0/1保留工作风险高会放大离职倾向departmental_performancefloat直接使用部门绩效差时留存意愿下降promotion_potential0/1保留晋升潜力低的人流失率高salary_fairness分类LabelEncoder薪酬公平感与离职相关显著teamwork_abilityfloat直接使用团队协作维度辅助模型分开边界注意 salary_fairness 是离散等级而不是布尔值训练前必须编码决策树对编码方式不敏感但 SVM 和 KNN 对距离敏感所以标准做法是先把所有列转成 float再用 StandardScaler 统一尺度。这里排在前面的 satisfaction 和 average_montly_hours 是 HR 业务里最常见的两个信号模型最终给出的特征重要性也往往集中在它们身上。2.2 特征选择逻辑为什么是 9 维而不是 30 维featureSelection.py 这个文件在源码里承担的是数据挖掘前期的降维工作。常见的做法不是把全部字段塞进去而是先用缺失率、方差和相关性做一轮粗筛再交给互信息或卡方选择器排序。下面这段代码模拟了该文件应包含的核心流程import pandas as pd from sklearn.feature_selection import SelectKBest, mutual_info_classif from sklearn.preprocessing import LabelEncoder, StandardScaler # 假设训练数据已经过清洗left 是离职标签 df pd.read_csv(hr_attrition.csv) categorical_cols [salary_fairness, promotion_potential] for col in categorical_cols: df[col] LabelEncoder().fit_transform(df[col].astype(str)) y df[left] X df.drop(columns[left, employee_id]) # 统一量纲避免数值大的特征抢占距离计算 scaler StandardScaler() X_scaled scaler.fit_transform(X) # 用互信息选 top-k selector SelectKBest(mutual_info_classif, k9) selector.fit(X_scaled, y) selected X.columns[selector.get_support()] print(selected)这段代码先对类别特征做 LabelEncoder再用 StandardScaler 统一量纲最后用 mutual_info_classif 选出 k 个特征。选择互信息而不是 F 检验是因为人员流失数据里特征与标签的关系基本是非线性的比如 average_montly_hours 在 150 小时以下流失很低超过 160 小时流失率迅速上升这种区间关系用相关系数很难抓住而互信息可以捕捉到分组差异。k 设置为 9 是业务约束等于保留了原始字段中最重要的 9 个维度如果你要复现这个源码可以先用selector.scores_打印每个特征的得分再决定是否把 k 调到 8 或 10。选完特征后再训练四个模型分别保存为 decisionTreeModelResult.pickle、knnClfResult.pickle、SVMResult.pickle、randomForestResult.pickle。观察文件名可以发现训练脚本和 Web 系统是分离的模型是在本地或 Notebook 里跑完用 pickle 把分类器对象持久化Django 在启动后的第一个请求里加载进内存。这样页面响应不会因为重新训练而阻塞也方便替换模型版本。2.3 四个模型并存不是炫技是给业务方留选择空间同一个数据集同时保存决策树、KNN、SVM 和随机森林四个 pickle是这类人员流失预测系统的常规做法它们各自承担不同角色模型pickle 文件名为什么保留它使用注意决策树decisionTreeModelResult.pickle可解释能直接转成 IF-THEN 规则容易过拟合注意 max_depthKNNknnClfResult.pickle实现简单适合验证特征尺度特征必须做标准化否则距离被大数值列主导SVMSVMResult.pickle高维小样本下决策边界干净对核参数敏感耗时比树模型高随机森林randomForestResult.pickle默认效果稳能输出特征重要性模型文件较大加载后占内存实际使用时优先用随机森林作为主预测器因为它对异常值不敏感也不需要精心调参但业务方如果追问“到底为什么这个人会走”就换决策树跑一版把树结构导出成规则。KNN 在这个场景里更多是作为基线模型用来检查特征工程是否做偏了如果 KNN 的准确率明显低于树模型说明特征尺度或异常值处理还有问题。SVM 适合在样本量不超过几千条时作为对比项样本量上去以后训练成本会显著增加。从数据挖掘的角度看源码包附带 4 个 pickle 和 featureSelection.py 的价值在于不用重新写训练脚本只需要把新一批员工数据整理成相同列顺序加载模型就能输出流失概率。但要注意 sklearn 版本的兼容性pickle 序列化的是 Python 对象跨版本加载偶尔会报ModuleNotFoundError或TypeError因此部署环境最好固定scikit-learn1.0.x这一类具体版本。3. 模型请求链路从视图函数到决策树、随机森林预测输出3.1 如何避免每个请求都读一次 pickle 文件Django 的 views.py 是整个系统的请求入口但资源里最容易被忽略的是模型加载方式。如果每次 POST 都调用joblib.load磁盘 I/O 会成为瓶颈页面在并发访问时会明显变慢。常见做法是在模块层维护一个字典缓存模型只在进程启动后第一次使用前加载一次。import os import joblib from django.conf import settings _model_cache {} def get_model(model_key): if model_key not in _model_cache: model_name { tree: decisionTreeModelResult.pickle, knn: knnClfResult.pickle, svm: SVMResult.pickle, rf: randomForestResult.pickle }[model_key] model_path os.path.join(settings.BASE_DIR, model_name) _model_cache[model_key] joblib.load(model_path) return _model_cache[model_key]这段代码里model_key是调用方传入的模型别名_model_cache是模块级字典加载过的模型对象不会释放。由于 pickle 文件放在 Django 项目根目录所以用settings.BASE_DIR拼接绝对路径。如果你的模型文件在static/model/子目录记得把model_name改成对应路径。另外Django 的 debug 模式下开发服务器会启动两个进程缓存会分别存在各自的进程里所以不会互相共享这不影响功能只是在修改模型文件后需要重启服务才能生效。3.2 视图层如何把表单特征转成模型输入预测页面的核心是 views.py 里的处理函数。它需要从request.POST中读取 9 个字段按照训练时的特征顺序组成一个二维数组再调用predict_proba得到流失概率。字段名必须与 HTML 表单的name属性完全一致少一个或多一个都会导致特征顺序错位。import numpy as np from django.shortcuts import render FEATURE_NAMES [ satisfaction_level, last_evaluation, number_project, average_montly_hours, accident_risk, departmental_performance, promotion_potential, salary_fairness, teamwork_ability ] def predict_view(request): if request.method POST: values [] for name in FEATURE_NAMES: raw request.POST.get(name, 0) try: values.append(float(raw)) except ValueError: values.append(0.0) X np.array([values]) model get_model(rf) pred int(model.predict(X)[0]) prob float(model.predict_proba(X)[0][1]) return render(request, predicted.html, { pred: pred, prob: round(prob, 4), feature_values: dict(zip(FEATURE_NAMES, values)) }) return render(request, index.html)这里的FEATURE_NAMES是特征顺序的唯一真相。request.POST.get(name, 0)为缺失字段提供了默认值避免因为前端漏传字段导致接口报 500predict_proba返回的形状是(样本数, 类别数)因为标签是二分类所以[0][1]就是流失概率。输出时要把 numpy 的 int64 和 float64 转成 Python 原生类型否则把结果放进模板或 JSON 响应时会遇到序列化问题。一个很容易踩的坑是训练时如果用了 StandardScaler 或 OneHotEncoder预测前必须使用同一套转换器对输入做 transform而不是直接传给模型。这套源码里的 pickle 只存了分类器对象说明特征在训练前已经手工完成编码或者在featureSelection.py里把转换器也一并存进去了。二次开发时如果要新增一个独热编码特征最好用joblib.dump把 preprocessor 和 model 一起保存而不是只存模型。3.3 URL 设置和模板联动predicted.html 如何拿到预测结果人员流失预测系统的 Django 工程里urls.py 把/predict/映射到上面的predict_view。模板侧只需要在 index.html 中放一个 9 字段的 formmethod 为 postaction 指向/predict/。predicted.html 通过 Django 模板变量直接显示结果p流失概率{{ prob }}/p p预测结果 {% if pred 1 %}建议关注{% else %}暂时稳定{% endif %} /p这里的{{ prob }}是视图里传入的 float已经 round 到 4 位小数{% if pred 1 %}是模板条件判断用于把 0/1 结果翻译成业务语言。注意如果模板里没有显示概率优先检查视图返回的字典 key 是否和模板里一致。另一个常见问题是用request.GET提交表单时刷新页面会导致表单重复提交所以这里必须用 POST并且在处理完逻辑后返回渲染后的模板生产环境一般会配合redirect和消息提示避免重复提交。4. 数据可视化与前端联动ECharts 图表和静态 PNG 的生成逻辑4.1 静态 PNG 是怎么来的用 matplotlib 先画分布图再丢进 static源码 static 目录下的图片文件名非常有规律satisfaction_level0.png、satisfaction_level1.png、average_montly_hours_0.png、average_montly_hours_1.png括号里的 0/1 就是离职标签。也就是说这些图是在训练前用 matplotlib 对样本做分组统计生成的一个文件一组便于对比“留存的员工”和“流失的员工”在同一特征上的分布差异。这种静态图的好处是加载快、不依赖后端实时计算适合放进报告或演示文稿。生成这类对比图的脚本通常长这样import matplotlib.pyplot as plt import pandas as pd df pd.read_csv(hr_attrition.csv) fig, axes plt.subplots(1, 2, figsize(10, 4), shareyTrue) for i, label in enumerate([0, 1]): subset df[df[left] label] axes[i].hist(subset[satisfaction_level], bins30, edgecolorwhite, color#4C72B0) axes[i].set_title(fleft{label}) axes[i].set_xlabel(satisfaction_level) axes[i].set_ylabel(count) plt.tight_layout() plt.savefig(static/satisfaction_level_{0}.png.format(label), dpi100)这里的bins30控制直方图的柱子数量dpi100决定了输出图片在网页上的清晰度。shareyTrue让两个子图共用 y 轴尺度避免因为其中一个分组样本量小导致视觉上被夸大。如果你发现自己生成的图片在浏览器里发虚检查两件事一是 savefig 的 dpi 是否小于 96二是 HTML 里 img 标签是否设置了超出原图尺寸的宽高。另外中文标签会默认显示成方块需要在脚本开头设置plt.rcParams[font.sans-serif] [SimHei]并加上plt.rcParams[axes.unicode_minus] False处理负号。4.2 用 ECharts 做交互式图表数据从后端 JSON 到前端 setOption静态图适合固定维度而 data_view.html 里的交互图表则需要 ECharts。js 目录里有 echarts.js、charts-pie.js、charts-bars.js、charts-lines.js说明模板按图表类型拆分了初始化脚本。数据绑定方式是在模板里用 Django 模板变量或 fetch 拿到 JSON再调用setOption渲染。下面是一段典型的离职原因占比图配置div idpieChart styleheight: 360px;/div script src{% static js/echarts.js %}/script script const chart echarts.init(document.getElementById(pieChart)); fetch(/api/attrition/summary) .then(res res.json()) .then(data { chart.setOption({ tooltip: { trigger: item, formatter: {b}: {c} ({d}%) }, legend: { bottom: 0 }, series: [{ type: pie, radius: [40%, 70%], itemStyle: { borderRadius: 6, borderColor: #fff, borderWidth: 2 }, data: data.pie }] }); }); /scripttrigger: item表示鼠标悬停在某个扇区时显示提示框formatter里的{b}是名称、{c}是数值、{d}是百分比。radius: [40%, 70%]设置的是内径和外径这种写法会把饼图变成环形图视觉上更现代。data.pie需要是[{name: 薪资不满, value: 82}, ...]这种格式如果后端返回的是 Pandas 分组结果要先用to_dict(records)转换。很多前端白屏问题都出在这里视图直接返回 DataFrame 或 numpy 数组ECharts 无法解析。data_view.html 中还可能用到 Tailwind CSS 和 Alpine.js。Alpine 负责一些轻量交互比如标签页切换和筛选器绑定不需要额外引入 Vue 或 React。Tailwind 的输出样式在tailwind.output.css中源码包为了部署方便已经提前编译好了不需要在服务器上跑 npm。4.3 人员流失预测系统的图表选型什么场景用 bars什么场景用 pie分析目标推荐图表数据字段不同部门流失人数对比横向柱状图 barsdepartment, left流失与留存人数占比环形饼图 pieleft 计数满意度、工时时长分布直方图 / 密度图satisfaction_level 等连续特征月度离职趋势折线图 lines时间字段按月聚合不建议把所有特征都放进一张雷达图人员流失数据里特征量纲差异太大雷达图会变成一团乱麻。做这类系统时一般是静态图做主报告ECharts 做筛选和下钻两者共用同一份特征口径。data_view.html 页面如果能从 URL 参数读取feature比如?featureaverage_montly_hours再用 JavaScript 切换对应的 PNG会让这套源码的可用性提升不少。5. 二次开发与模型验证替换特征后如何在 Django shell 中核对预测拿到人员流失预测源码后第一步不是直接跑而是先确认这几个 pickle 文件能被当前环境加载并且输出与页面一致。因为 pickle 序列化对 sklearn 版本敏感很多报错都发生在joblib.load这一步。建议在项目根目录执行python manage.py shell -c import joblib import numpy as np from pathlib import Path model joblib.load(Path(randomForestResult.pickle)) sample np.array([[0.38, 0.53, 2, 157, 0, 0.42, 0, 0.42, 0.61]]) print(model.predict(sample)) print(model.predict_proba(sample)) 上面的 sample 顺序对应第 3 章中的 FEATURE_NAMES分别是满意度、考核分、项目数、月均工时、事故风险、部门绩效、晋升潜力、薪酬公平感和团队协作能力。如果执行后输出类似[1]和[0.8432 0.1568]说明模型能加载并完成推理如果报错说维度不匹配说明你的 sklearn 版本下的模型特征数量不同或者 pickle 文件本身是在不同特征集合上训练的。这时不要急着改代码先用model.n_features_in_查看模型期望的特征数。5.1 用 Django 测试客户端模拟完整表单提交只验证模型不够还需要验证视图、表单和模板是否串起来。Django 自带测试客户端可以直接模拟浏览器 POST而不需要启动 runserverfrom django.test import Client c Client() response c.post(/predict/, { satisfaction_level: 0.38, last_evaluation: 0.53, number_project: 2, average_montly_hours: 157, accident_risk: 0, departmental_performance: 0.42, promotion_potential: 0, salary_fairness: 0.42, teamwork_ability: 0.61 }) print(response.status_code) print(response.context.get(prob))如果response.status_code不是 200先看 Django 日志里的 Traceback最常见的是ImproperlyConfigured表明模板目录没配置或者KeyError说明特征名没对齐。response.context.get(prob)能直接读取视图传给模板的上下文免去从 HTML 字符串里正则匹配的麻烦。如果这个值等于上一步 shell 里算出的概率就说明视图层没有擅自修改数据。5.2 特征顺序自检技巧最后分享一个调试技巧不要相信代码注释里的特征顺序直接打印随机森林的model.feature_importances_。特征重要性数组长度与训练特征数一致顺序就是训练数据输入时的列顺序。如果要新增特征必须同步改FEATURE_NAMES、重训模型并覆盖 pickle然后重新跑上面的 shell 验证。可以在项目根目录放一份 requirements.txt并在 featureSelection.py 入口打印 FEATURE_NAMES配合 shell 脚本每次改完跑一遍就不会出现页面与模型各说各话的情况。本文还有配套的精品资源点击获取