简介:针对高校宿舍分配场景,这份基于KMeans聚类算法的Python源码包提供了从数据预处理、模型训练到结果可视化的完整实现,适合需要将无监督学习落地到实际管理问题的数据科学初学者或高校信息管理相关技术人员。压缩包共13个文件,主要包含Python源码、CSV聚类结果数据、MP4操作与文档演示视频、README说明及XML配置等,整体大小约10.71MB,既可直接运行查看代码逻辑,也可借助录屏理解整个算法流程。目前已有820人学习下载。资源特别讲解了KMeans的质心初始化、K值选取(如肘部法则)以及用scikit-learn完成宿舍分组的实践要点,同时提供最终聚类结果与占比展示,便于验证算法效果并迁移到其他聚类场景。无论用于课程设计、项目参考,还是算法原理学习,都能获得可复用的代码框架与操作指引。
1. 从一纸名单到聚类分组:为什么宿舍分配需要KMeans
每年开学季,高校后勤最头疼的不是床位不够,而是“怎么分才不出矛盾”。按学号顺序排、按专业排、按新生报到顺序排,看起来公平,实际结果往往是打游戏的遇上了要考研的,夜猫子和晨型人住进同一间屋子,第一周就开始申请换寝。原因很直接:人工分寝只考虑了“哪个班、哪个专业”,没有考虑学生的生活习惯、作息偏好和兴趣特征,而这些恰恰是矛盾的主要来源。
KMeans聚类算法解决的就是这类“没有标准答案的划分问题”。它把每个学生抽象成一组特征向量,比如作息时间、噪音容忍度、清洁习惯、是否熬夜、是否沉迷游戏,然后用无监督学习把这些特征相似的人聚合到同一个簇里,最后把同一个簇的学生分到同一间宿舍。相比“按学号取模”的硬编码思路,KMeans做的是数据驱动的相似度划分,属于一类可复用的源码设计,不是一次性脚本。这篇文章从原理讲到代码实现,再到带演示视频的完整项目结构,路径是:特征工程、K值选择、聚类训练、结果导出、可视化核对。适合正在做课设、准备毕业设计、以及在高校信息化部门做数据应用开发的读者。
2. 数据侧的准备工作:从学生信息表到可聚类的特征矩阵
KMeans终究是个距离算法,它不关心你的字段叫“是否熬夜”还是“sleep_at_02”,它只认数值。这决定了宿舍分配项目的第一步不是调库,而是做特征数值化、量纲统一和缺失值处理。
2.1 宿舍分配场景下的特征如何定义
从项目源码的角度看,“宿舍分配”不是一个模糊概念,而是要明确哪些特征参与距离计算。你至少需要三类特征:
第一类是作息规律。比如“通常几点睡觉”“早上几点起床”“会不会午休”,这些可以直接映射为小时数,22.5、23.0、7.0 这样的浮点数。第二类是行为偏好。比如“是否打游戏”“是否喜欢安静”“是否经常带朋友回寝室”,可以用 0/1 编码,也可以做成 1 到 5 的等级制。第三类是个人属性。比如年级、专业、是否为烟民,这类特征用来约束聚类结果。注意,专业和年级如果直接丢进 KMeans,数值化后会产生“文科生和理科生天然分开”的硬边界,反而可能不是你要的效果。
我一般会把特征分成“参与聚类的特征”和“约束条件”两类。参与聚类的只保留对生活冲突有实际影响的变量,比如睡眠时间、起床时间、噪音敏感度、清洁频率。约束条件则在聚类之后单独处理,比如同一个宿舍尽量同一个年级,或者抽烟的同学优先分到通风好的楼层。
2.2 用pandas把原始数据转成特征矩阵
这个项目里我们面对的是 CSV 数据,内容大致是学生学号、睡眠时间、起床时间、是否熬夜、对噪音的敏感程度等。先用 pandas 读进来,再做编码和转换。
import pandas as pd import numpy as np # 原始学生数据,列名按实际文件调整 df = pd.read_csv('student_data.csv', encoding='utf-8-sig') # 对分类型特征做数值映射,这里以“是否熬夜”为例 df['is_night_owl'] = df['是否熬夜'].map({'是': 1, '否': 0}) # 时间特征统一转成 24 小时制小数,23:30 -> 23.5 def time_to_float(x): if pd.isna(x): return np.nan parts = str(x).split(':') return int(parts[0]) + int(parts[1]) / 60 df['sleep_time'] = df['就寝时间'].apply(time_to_float) df['wake_time'] = df['起床时间'].apply(time_to_float) # 构造参与聚类的特征矩阵 features = df[['sleep_time', 'wake_time', 'is_night_owl', 'noise_sensitivity']].copy() print(features.head())这段代码做了两件事:一是把“是否熬夜”这种中文文本字段映射成 0/1,二是把“23:30”这种字符串时间转成 23.5 的数值。之所以要转成小数,是因为 KMeans 的欧氏距离对数值的绝对大小非常敏感,字符串类型无法参与距离计算。
2.3 标准化不是可选项,是必选项
看一下特征矩阵的数值范围:睡眠时间取值在 20 到 26 之间,噪音敏感度取值可能只有 1 到 5。如果直接做 KMeans,睡眠时间的差异会在距离计算中占绝对主导地位,噪音敏感度基本被忽略。正确做法是做标准化处理,让每个特征的平均值为 0,标准差为 1。
from sklearn.preprocessing import StandardScaler # 先填充缺失值,用中位数处理更稳妥 features = features.apply(lambda col: col.fillna(col.median())) # 标准化,注意要用 fit 后的 scaler 去做后续任何新数据的转换 scaler = StandardScaler() features_scaled = scaler.fit_transform(features) print(features_scaled.shape)标准化之后,每个特征都在同一个尺度上,聚类的意义才成立。有一点需要提醒:StandardScaler是对整个数据集做的全局变换,实际部署到新生数据时,要保存这个 scaler 对象,用它的transform方法处理新数据,而不是重新fit,否则分布变了,聚类结果就不可比了。
3. KMeans实现与K值确定:肘部法则与轮廓系数的完整代码
特征矩阵准备好之后,核心问题变成:到底分几类?这里的“类”对应到宿舍场景,就是你希望宿舍楼里存在几种不同的生活方式。K值太小,混住冲突多;K值太大,每个宿舍的成员特征过于单一,管理难度上升。通常的做法是结合肘部法则和轮廓系数一起选K。
3.1 sklearn中KMeans的关键参数怎么设置
sklearn 的 KMeans 提供了几个直接决定结果质量的参数:n_clusters、init、n_init、random_state、max_iter。
from sklearn.cluster import KMeans # 常规范式:k-means++ 初始化,20次随机启动,固定随机种子 model = KMeans( n_clusters=4, init='k-means++', n_init=20, max_iter=500, random_state=42 ) model.fit(features_scaled)init='k-means++'是 sklearn 的默认值,它通过让初始质心彼此尽量远离来降低陷入局部最优的概率,比完全随机初始化稳定得多。n_init表示用不同的质心初始状态跑多少轮,选其中惯性最小的结果。random_state必须固定,否则每次运行结果不同,演示视频里的复现能力就没了。
3.2 手写一个K值扫描脚本,同时输出肘部图和轮廓系数
只算一次 KMeans 不够,你需要在 K 从 2 到 10 的范围里循环训练,记录每个 K 对应的 SSE(簇内平方和)和轮廓系数。
from sklearn.cluster import KMeans from sklearn.metrics import silhouette_score import matplotlib.pyplot as plt sse = [] silhouette_scores = [] k_range = range(2, 11) for k in k_range: km = KMeans(n_clusters=k, init='k-means++', n_init=20, random_state=42) labels = km.fit_predict(features_scaled) sse.append(km.inertia_) sil = silhouette_score(features_scaled, labels) silhouette_scores.append(sil) print(f'K={k}, SSE={km.inertia_:.2f}, sil={sil:.4f}') # 画出肘部曲线 plt.figure(figsize=(12, 4)) plt.subplot(1, 2, 1) plt.plot(k_range, sse, marker='o') plt.xlabel('K') plt.ylabel('SSE') plt.title('Elbow Method') plt.subplot(1, 2, 2) plt.plot(k_range, silhouette_scores, marker='s', color='green') plt.xlabel('K') plt.ylabel('Silhouette Score') plt.title('Silhouette Analysis') plt.tight_layout() plt.show()km.inertia_就是残差平方和,每个样本到所属簇质心的距离平方之和。K 增加时 SSE 必然下降,你要找的是“下降趋势出现明显拐点”的位置,在宿舍分配这个场景里通常是 3 到 5 之间。轮廓系数范围是 -1 到 1,越接近 1 表示样本离自己簇的距离远远小于离其他簇的距离,聚类结构越清晰。从这段代码跑出的数据里,挑“手肘处且轮廓系数高”的 K 值,比拍脑袋定参数靠谱。
4. 从聚类标签到宿舍名单:结果落地与规则映射
聚类本身只生产“标签”,不生产“宿舍号”。中间还差一个映射层,这是项目源码里最容易被忽略、也是最需要实用技巧的部分。
4.1 给每个簇分配具体的宿舍楼和房号
假设学校宿舍是6人间,KMeans分出了 4 个簇,那么做法是:把每个簇的学生列表按学号排序,每 6 人切成一个房间,房间号按宿舍楼和楼层预先配置。输出 CSV 时保留学号、姓名、簇编号、宿舍号。
import pandas as pd # features_scaled 对应 df 的行 df['cluster'] = model.fit_predict(features_scaled) # 预定义房号池:比如 1号楼301到305,2号楼201到203 room_pool = ['1-301', '1-302', '1-303', '1-304', '1-305', '2-201', '2-202', '2-203', '2-204', '2-205'] def assign_room(group): group = group.sort_values('student_id') # 每组最多 6 人 room_list = [] for i in range(0, len(group), 6): room = room_pool[i // 6 % len(room_pool)] room_list.extend([room] * min(6, len(group) - i)) group['room_id'] = room_list return group df = df.groupby('cluster', group_keys=False).apply(assign_room) df.to_csv('final_assignments.csv', index=False, encoding='utf-8-sig')这段代码的逻辑是:把同一个簇的学生按学号排序后,每六个人依次落进预先定义的房号池。room_pool里既有楼栋号也有楼层房号,实际使用时可以把宿舍容量、男女分楼、楼层限电规则都抽出来做成配置。注意group_keys=False是为了避免 groupby 在结果里多出 cluster 列索引。
4.2 宿舍分配里的硬约束与 KMeans 的边界
KMeans 追求的是特征距离最小化,但它不懂“同专业优先同楼”“不同性别不能同寝”“研修室不能安排大三以下学生”这些规则。所以纯聚类结果只能作为候选方案,不能直接作为最终制度。实际操作中,你需要先做一次硬约束过滤,再做聚类。
常见的硬约束有四个:性别隔离、同院系优先、身体原因(如睡眠障碍需要单间)、年级差异控制。这四个条件适合在预处理阶段过滤掉特殊情况后,剩余学生才进入 KMeans 计算。如果直接拿全部学生跑聚类,性别字段一旦进特征,结果会强行走成“男女两簇”,其他特征全部失效。
另外,簇的大小和宿舍容量不一致的情况经常出现。比如某个簇有 25 人,六人间只能装 24 人,剩下 1 人必须去别的簇。处理办法是把“溢出”的学生按欧氏距离分配到最近的簇,距离相等的再按学号递补。这个逻辑在源码里可以用cdist或跟第二近的质心比较来实现,属于一个很实用的小技巧。
5. 可视化核对与项目排错:让聚类结果经得起检查
班主任和后勤主任不会只看聚类报告,他们要看到“为什么这个人分到了这间宿舍”。可视化就是把抽象的距离变成可解释的图形,同时也是你排查特征工程错误最直接的手段。
5.1 用散点图和雷达图检查聚类质量
KMeans 处理高维特征时,直观的方法是先做 PCA 降维到二维,再画散点图。宿舍分配场景里特征本身不超过 6 个,PCA 后的前两个主成分通常能解释 70% 以上的方差,可以放心用。
from sklearn.decomposition import PCA pca = PCA(n_components=2) coords = pca.fit_transform(features_scaled) plt.figure(figsize=(10, 7)) colors = plt.cm.Set1(np.unique(model.labels_) / max(model.labels_)) scatter = plt.scatter(coords[:, 0], coords[:, 1], c=model.labels_, cmap='Set1', s=30) plt.colorbar(scatter) plt.xlabel('PC1') plt.ylabel('PC2') plt.title('Cluster Visualization after PCA') plt.show()核心是看两件事:一是簇与簇之间是否有明显的分离边界,如果所有簇都混在一起,说明特征选得不够有区分度;二是每个簇内是否有离群点,离群点往往对应数据录入错误、缺失值没处理干净或者特征严重偏离常模的学生。
5.2 常见坑位:标准化顺序、随机种子、空簇与数据顺序
第一,标准化和填充缺失值的顺序不能颠倒。先填充再标准化,否则填充值会拉低方差,影响后续变换。第二,KMeans 对初始化敏感,忘了设random_state会让两次运行得到完全不同的宿舍名单,这是演示视频和实际部署最容易翻车的地方。第三,如果 K 值设得过大,可能出现空簇,即某个初始质心没有分配到任何样本。此时 sklearn 会把空簇中心的样本重新随机初始化,但仍可能影响整体稳定性,所以 K 上限不要超过样本量的十分之一。
另一个隐藏坑在“数据顺序”。有的源码在读入 CSV 后没做随机打乱,导致聚类结果里每个簇的样本几乎按学号连续排列,如果学号前缀包含了院系编码,聚类行为就会被院系字段偷跑,表现成“分簇效果好”其实是字段泄漏。排查方法很简单:打印每个簇的学号前缀分布,如果出现某一簇集中在某个院系,说明特征里有不该进模型的信息。
6. 把整套流程封装成可复现脚本,对齐演示视频的操作路径
拿到带演示视频的源码包后,第一步不是看代码,而是看如何从零跑通。演示视频里展示的操作本质上是三个台阶:环境准备、数据替换、一键出结果。
6.1 用 requirements.txt 和脚本入口固定整个实验环境
项目里有requirements.txt,这是整个实验能够在一台新电脑上复现的关键。在干净环境里执行下面的命令就能装齐依赖:
pip install -r requirements.txtrequirements.txt 内容至少需要scikit-learn、pandas、numpy、matplotlib、openpyxl,因为openpyxl负责把结果写到 xlsx 格式。源码里的宿舍分配.py应该暴露一个核心入口函数,推荐用main()接收 CSV 路径和 K 值参数,这样既方便 IDE 调试,也方便在命令行里批处理多批次数据。
6.2 建议把单次运行的完整流程固化成函数链
下面的代码结构可以直接对照演示视频里的步骤,每一步都是一个函数,顺序和命名都能对上。
def build_feature_matrix(csv_path): # 读取、映射、标准化 pass def find_best_k(data, k_range): # 肘部法则 + 轮廓系数 pass def run_clustering(data, n_clusters): # 固定随机种子训练 pass def assign_rooms(df, labels, room_pool): # 聚类标签映射到宿舍号 pass def save_results(df, output_path): # 输出最终 CSV 和聚类中心占比 CSV passrun_clustering 里还需要把聚类中心及占比一并输出,也就是项目自带的聚类中心及占比展示.csv。聚类中心就是每个簇的特征均值,占比就是每个簇的人数占总人数的百分比。这两个指标能直接告诉公寓管理员:你们学校生活习惯相似的人群大致分为几类,每一类大概多少人,从而提前规划宿舍楼的功能分区。
6.3 针对演示视频的录屏级操作建议
演示视频一般会当场跑出最终聚类结果展示.csv和聚类中心及占比展示.csv两个文件。实际操作时注意两点:一是演示前先删掉旧输出文件,避免观众分不清哪些是本次生成的;二是跑 K 值扫描时加上print输出进度,比如每个 K 值的 SSE 和轮廓系数,视频里滚动得比较自然,同时也能让技术面试官一眼看到你的算法分析过程。
最后补一个自己常用的验证技巧:生成最终的宿舍分配表后,随机抽三间宿舍,人工核对这十几个学生的原始特征是否真的相似。比如抽查的一间宿舍是否都集中在“睡眠时间接近零点以后、对噪音不敏感、不抽烟”的画像上。如果抽查有超过两间宿舍违和,优先回去查标准化和硬约束过滤是否调反了顺序,而不是去调 K 值。这类人工抽检在数据处理项目里属于交付前的安全网,每次跑完批量分配都必须执行一遍。
本文还有配套的精品资源,点击获取