
简介针对高校宿舍分配场景基于K均值聚类算法的Python源码项目面向数据挖掘学习者、开发者和高校信息化管理人员演示如何用机器学习库完成学生特征聚类将年龄、性别、专业、生活习惯等多维数据纳入分析从而优化住宿分组、减少生活习惯冲突。压缩包共13个文件包含主程序、依赖清单、CSV聚类结果、Markdown说明文档、XML工程配置以及两段MP4演示视频整体约10.71MB结构清晰便于按需查阅已有819人学习。资源完整展示了从数据预处理、特征标准化、模型训练到结果可视化的项目流程并讨论K值选择方法肘部法则、轮廓系数及KMeans初始化策略。附带的CSV文件提供最终聚类结果和聚类中心占比可直接用于分析两段录屏分别演示程序运行与文档操作配合源码注释即使初学者也能快速复现并迁移到其他聚类场景作为课程设计或宿舍分配系统的参考实现。1. 宿舍分配不是排班问题而是聚类问题每年迎新季最头疼的不是床位不够而是“怎么把一群人凑进同一间房”。手工按学号顺序排或者按报到先后抽签结果往往是夜猫子和早睡党同寝室爱打游戏的和备考考研的共用一张桌子不到一个月就有人申请调宿。另一个反直觉的点是宿舍分配本质上不是分配问题而是一个聚类问题。目标不是“给每个人找一个位置”而是“让住在同一房间的人尽可能相似”。KMeans 聚类算法把每位学生的行为特征数字化后按相似度聚成若干簇每个簇对应一个宿舍的候选人群。这个思路对宿舍管理员和 IT 从业者都适用前者拿到的是可理解的报表后者看到的是标准 sklearn 流程如何在真实业务里落地。下面从算法原理开始拆。2. KMeans 的距离世界从欧氏距离到标准化输入KMeans 的核心假设是簇内样本距离尽可能小簇间距离尽可能大。这里的“距离”默认是欧氏距离但真实宿舍数据里有均值、方差差距极大的字段直接扔进算法会让高量纲特征主导结果。所以这章先把距离和标准化讲清楚再给最小可运行代码验证效果。2.1 距离度量为什么默认选欧氏距离欧氏距离是 KMeans 的原生语言。算法在每次迭代中计算样本到质心的欧氏距离把样本归到最近质心然后重新计算质心坐标。这个过程收敛性稳定计算成本低在特征独立时效果最直接。相比曼哈顿距离和余弦相似度欧氏距离对特征之间相关性有一定容忍度但量纲差异会直接污染距离值这是宿舍分配场景里最大的坑。距离公式两个向量 x, y适用场景宿舍分配中的注意点欧氏距离sqrt(sum((x_i-y_i)^2))连续数值特征如作息时间、自习时长必须先标准化否则量纲大的特征权重失控曼哈顿距离sum(abs(x_i-y_i))稀疏或整数特征如卫生评分对异常值更鲁棒但 sklearn KMeans 默认不支持余弦相似度1 - x·y/(x宿舍数据里就寝时间、学习时长、晚归次数都是绝对数值方向意义不大所以默认用欧氏距离。后面的特征工程和代码也都围绕它展开。2.2 标准化不标准化等于只用一个特征一个常见错误是直接把“每日学习时长0.510小时”和“晚归次数030次”丢进 KMeans。晚归次数数值大会在距离计算里碾压学习时长聚类结果基本相当于按晚归次数单维度排序。解决办法是标准化常见做法是 Z-Score 或 MinMaxScaler。Z-Score 更适合有正态趋势的连续值MinMax 保留原始分布形状适合成绩这种有明确边界的分数。下面是最小可运行示例用随机数据走一遍标准 KMeans 流程import numpy as np import pandas as pd from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler # 模拟 60 个学生特征是就寝时间和自习时长 np.random.seed(42) data pd.DataFrame({ sleep_time: np.random.normal(23, 1, 60), # 就寝时间均值 23 点 study_hours: np.random.normal(5, 2, 60), # 每天自习小时数 }) scaler StandardScaler() X_scaled scaler.fit_transform(data) model KMeans(n_clusters4, n_init10, random_state0) model.fit(X_scaled) data[cluster] model.labels_ print(data.groupby(cluster).mean())代码逻辑很简单先用 StandardScaler 做 Z-Score 标准化再把标准化后的特征矩阵传给 KMeans。n_clusters4表示聚成 4 个簇n_init10让算法用 10 个不同初始化质心各跑一遍再选收敛结果最好的那次避免随机初始化带来的局部最优。random_state0让结果可复现在排宿舍时需要保证每次运行输出一致。输出groupby(cluster).mean()能看到每个簇的作息和自习均值比如 0 号簇平均 23:30 睡觉、自习 3 小时3 号簇平均 1 点睡觉、自习 8 小时。这个矩阵就是后面分配宿舍的中间依据。没做标准化时study_hours取值范围小sleep_time离散程度大簇边界就会偏移验证时经常出现“两簇学生学习习惯完全不同”的假聚类。2.3 质心初始化与迭代次数的影响真正影响结果的是初始化方式。sklearn 新版默认initk-means让初始质心互相较远比完全随机初始化稳定得多。max_iter300是默认值宿舍数据规模在几百到几千人几十次迭代就收敛了一般不用调。但如果发现不同random_state下簇成员变化明显多半是数据里存在离群值或者 K 选得不合适。极端离群值比如某个学生填了就寝时间 3:00标准化后这个点会变成七八个标准差的离群点吸引一个质心单成一簇宿舍里就只分到一个人。处理方法是特征清洗阶段直接剔除答卷异常值或者标准化后手动 clip 到合理区间不要留给聚类算法处理。宿舍分配讲究的是“尽量相似”不是“孤立异常”离群点应该单独走人工分配通道。3. 宿舍分配的特征工程与 K 值选择聚类算法吃的是数字但宿舍场景里的原始信息是问卷和考勤记录。这一章解决两个问题把学生画像转成数值矩阵以及选多少个簇才匹配宿舍楼实际床位。3.1 特征编码把性格和习惯变成数值宿舍分配关心的核心维度是作息、学习、卫生和社交。设计特征时不要贪多特征越少越可控每加一个特征就要确认它对聚类结果有区分度。下面是宿舍项目常用的一套特征模板特征名原始形式编码方式说明sleep_time就寝时间“23:30”换算成小时小数 23.5连续值标准化后参与距离计算wake_time起床时间“07:00”换算成 23 7 30 再归一化避免跨零点问题睡眠类特征建议线性变换study_hours自习时长 010 小时直接整型连续值反映勤奋程度hygiene_score15 分整型数量级较小但标准化后无影响game_freq每周打游戏次数05 定序定序特征转整型不要 one-hot避免稀疏music_type安静/偏噪/摇滚0/1/2 三值类别少时直接编号类别多时用 one-hot一个容易踩的坑是两个与时间相关的特征。如果就寝时间直接用“23:30”字符串或者把 0:30 理解成 0.5 而不是 24.5聚类时就会把“凌晨睡”和“傍晚睡”混在一起。我一般统一把时间转换成自正午 12:00 起的连续小时数例如 23:30 转成 23.500:30 转成 24.5这样距离是连续的符合人的直觉。特征标准化和前面一样用 StandardScaler 或 MinMaxScaler。宿舍数据通常规模小几百条样本不需要太复杂的降维把特征控制在一双手数得过来的范围内即可。3.2 肘部法则用簇内平方和选 KK 值对应的是“想分成多少类人”但在宿舍场景里K 更像是“先分成几类人再把每类人里的个体平铺到各宿舍”。一般步骤是先跑肘部法则确定拐点再用宿舍总容量反推 K 的下限最终取一个既能解释又能操作的数。下面代码用上一章的模拟数据画肘部图import matplotlib.pyplot as plt from sklearn.cluster import KMeans inertia [] K_range range(2, 11) for k in K_range: km KMeans(n_clustersk, n_init10, random_state0) km.fit(X_scaled) inertia.append(km.inertia_) # 簇内平方和越小越紧凑 plt.plot(K_range, inertia, markero) plt.xlabel(K) plt.ylabel(Inertia) plt.show()inertia_是每个样本到所属质心距离的平方和。当 K 增加时惯性指数下降下降幅度从某个 K 开始明显放缓那个拐点就是信息量收益最大处。实际经验是宿舍分配取拐点附近 数据业务约束共同决定比如每栋楼有 10 层、每层 20 间四人房那 K 至少要为 10 的整数倍或者与班级数对齐。3.3 容量约束聚类不等于分房聚类结果是“几类人”不是“几个宿舍”。假设一共 600 人、每间 4 人先聚成 5 类每一类有 120 人再把这 120 人按某种规则分成 30 个四人组。这里的“按某种规则”通常是随机排列后按床位窗滑动或者按某个次要特征比如学号后两位均匀打散避免同班的扎堆或班长全部落在同一间。但聚类本质不保证每个簇的人数正好是 4 的整数倍所以需要在簇内做一次二次划分。常见做法是对每个簇内样本按重要特征如睡眠时间排序用洗牌策略对排序后的序列做分段段内 4 人成舍如果簇人数余 23 人就和另一个余员类似的簇合并后再分段。这一段逻辑属于业务规则直接写进源码里通常叫assign_rooms。KMeans 只负责把“脾气相投”的人归到一起真正落到门牌号需要单独的约束处理函数这个在下一章代码里会完整出现。4. 源码工程落地从 Excel 到分房名单的完整流程这章按实际项目结构走一遍。源码包里带演示视频演示的就是下面这个流程从点击运行到输出宿舍名单的完整过程。工程一般分成data/、src/、output/三个目录核心脚本只做四件事读数据、清洗、聚类、按容量分房。4.1 工程结构与依赖项目根目录下通常包含dormitory-kmeans/ ├── data/ │ └── students.xlsx # 学生信息及特征问卷 ├── src/ │ ├── preprocess.py # 数据清洗与特征转换 │ ├── cluster.py # KMeans 聚类与 K 值选择 │ └── assign.py # 簇内二次分配生成宿舍名单 ├── output/ │ └── room_assign.csv # 最终分配结果 └── demo.mp4 # 演示视频依赖只有pandas、numpy、scikit-learn、openpyxl。如果是在课程设计环境通常再补一个matplotlib画聚类散点图和惯性曲线。演示视频里一般会展示从命令行运行python main.py到终端打印聚类中心再到生成 CSV 的一系列输出方便评审时快速确认程序确实跑通了。4.2 核心实现读数据、标准化、聚类下面是一个可以直接扩展的主流程代码片段import pandas as pd from sklearn.preprocessing import StandardScaler from sklearn.cluster import KMeans def load_and_prepare(path): 读 Excel选出聚类特征列并做清洗 df pd.read_excel(path, engineopenpyxl) # 剔除关键特征缺失或异常的行 df df[pd.to_numeric(df[sleep_time], errorscoerce).notna()] features [sleep_time, wake_time, study_hours, hygiene_score, game_freq] for col in features: df[col] pd.to_numeric(df[col], errorscoerce).fillna(df[col].median()) scaler StandardScaler() X scaler.fit_transform(df[features]) return df, X, scaler def run_clustering(df, X, k6, seed42): 执行 KMeans并把簇标签写回原表 model KMeans(n_clustersk, random_stateseed, n_init10) labels model.fit_predict(X) df[cluster] labels return df, modelload_and_prepare先读 Excel然后强制把特征列转成数值遇到缺失用中位数填充。这样躲开了“问卷提交人填了文本”的脏数据问题。run_clustering里fit_predict一步完成训练和打标签簇号直接落回 DataFrame。标准化器scaler必须保留下来后面如果在演示界面里输入单个新学生的特征需要调用scaler.transform才能得到同样尺度下的坐标否则新样本会和原有数据不在同一空间。4.3 簇内二次分配让 4 人间的名单真正落定聚类完成后要按宿舍容量拆分。以四人寝为例做一份带规则的分房函数import numpy as np def assign_rooms(df, room_capacity4): 在每个聚类簇内按特征均匀分段生成宿舍号 df df.sort_values([cluster, sleep_time]) # 簇内按睡眠时间排序 df[room_id] rooms [] for cluster_id in df[cluster].unique(): cluster_df df[df[cluster] cluster_id].copy() n_stu len(cluster_df) n_full n_stu // room_capacity # 完整宿舍数 remainder n_stu % room_capacity # 余数人数 for i in range(n_full): room_members cluster_df.iloc[i*room_capacity:(i1)*room_capacity] room_name f{cluster_id:02d}-{i1:03d} df.loc[room_members.index, room_id] room_name rooms.append((room_name, room_members.index.tolist())) if remainder 0: # 余数人员先标记后续用独立逻辑并入相邻簇宿舍 room_name f{cluster_id:02d}-X df.loc[cluster_df.iloc[n_full*room_capacity:].index, room_id] room_name return df, rooms这个函数先按cluster和sleep_time排序使每个簇内成员按作息时间首尾相接。接着基于room_capacity做滑窗分段每 4 人一段宿舍号由簇编号和段序号组成。remainder是凑不够一间的散客标记成X后在主流程外另行合并。为什么要在簇内按sleep_time排序再做滑窗因为 KMeans 只保证同一个簇里的人整体相似不保证同一簇里 12 点和 1 点睡觉的人一定分不开。按睡眠时间排序后相邻 4 人之间的作息差异最小这比随机抽样更容易让同宿舍的人步调一致。排序字段可以根据实际需求换成成绩排名或卫生分逻辑相同。4.4 输出结果字段与演示视频对照最终 CSV 输出至少包含以下字段字段来源作用student_id原始 Excel学生主键clusterKMeans 结果社交/作息类型标识room_idassign_rooms 结果最终宿舍号sleep_time原始特征供人工抽查排布是否合理study_hours原始特征供人工抽查排布是否合理演示视频里常见三个镜头第一段拍 Excel 输入文件的表头确认特征列和脚本读取的一致第二段拍命令行运行main.py展示训练日志和聚类中心第三段打开room_assign.csv现场按宿舍号筛选核对人数。对照视频时最容易发现的问题是脚本里读的列名和实际 Excel 表头不一致一个下划线或大小写差异就会让KeyError中断流程。5. 验证聚类质量与调参的最后一公里宿舍分配完成后不能只看“分配出来没”还要回答“分得合理吗”。这里给三个具体检查方法。第一个是轮廓系数。计算每个样本到同簇其他样本的平均距离 a到最近邻簇所有样本的平均距离 b轮廓系数是 (b-a)/max(a,b)。范围在 [-1,1]越接近 1 说明同类紧凑、异类分离。用silhouette_score对所有 K 值做批量扫描一般 K 在 48 之间时轮廓系数最高超过 8 曲线开始下滑那个位置就是合理上限。from sklearn.metrics import silhouette_score scores [] for k in range(2, 11): km KMeans(n_clustersk, n_init10, random_state42) labels km.fit_predict(X_scaled) scores.append(silhouette_score(X_scaled, labels)) print(list(zip(range(2, 11), scores)))第二个是检查宿舍人数均匀性。分配函数的余数逻辑容易出现“某个宿舍只有 2 人”的情况这时需要在主流程里加一个校验对所有room_id做value_counts()找出不等于 4 的宿舍。如果数量超过总宿舍数的 5%说明簇内二次分配规则有问题应该把余数人员提前合并到其他簇再分而不是全部留在 X 屋。我一般会把余数人员按最近簇质心重新归类再对目标簇做扰动更新。第三个是固定随机种子并保存模型参数。宿舍名单需要可追溯去掉random_state后每次运行结果可能不同这会让现场答辩时难以解释“为什么这次分到的宿舍和上次不一样”。把model.json保存 K 值、特征列、标准化均值、簇质心和簇内人数复核时就能用KMeans(*params)完全复现。最后一个技巧是把聚类中心输出成一张“宿舍风格表”。每个簇中心对应一种学生画像比如 1 号簇“23 点睡、自习 6 小时、游戏频率 1”8 号簇“0 点睡、自习 2 小时、游戏频率 5”。在分配结果文件里附带这张表管理员一看就知道每层楼的住宿风格大致是什么样。关于离群学生不要塞进任何簇单独用NearestNeighbors找最近质心再人工放入容量有余的宿舍整个过程维持一个观点聚类负责相似规则负责治安。本文还有配套的精品资源点击获取