ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于PyQt5的二手房价预测系统:数据清洗到模型界面化实战

基于PyQt5的二手房价预测系统:数据清洗到模型界面化实战 简介一份基于Python和PyQt5实现的二手房价分析与预测系统期末大作业面向学习Python数据分析、机器学习与GUI开发的高校学生可作为课程设计、毕业设计或入门实战项目。系统完整覆盖数据读取、缺失值处理、特征分析、模型训练与结果可视化全流程内置线性回归等经典算法通过Matplotlib绘制房价分布、趋势及预测对比图PyQt5搭建可交互界面支持加载数据集、切换模型与查看分析结果。包体共17个文件含6个py源码文件、6个png图表、1个ui界面文件、csv数据集、md说明文档及pyc缓存压缩包仅132KB结构清晰便于直接运行与二次开发。源码附详细注释涵盖Pandas数据处理、Scikit-Learn建模、Matplotlib绘图及PyQt5布局等关键知识点数据集中含真实二手房价特征字段可据此调整参数、扩展算法。目前已有343人学习下载能帮助快速贯通数据分析与机器学习在真实场景中的应用。1. 期末大作业里的二手房价预测难点到底在哪期末大作业只要沾上“房价预测”十个人里有八个把重心压在模型上真正拿高分的人却往往赢在数据和界面。基于 Python 和 PyQt5 实现的二手房价分析与预测系统核心不是某个高级算法而是把 pandas 清洗、sklearn 建模、PyQt5 桌面界面串成一条能现场演示的完整链路。它解决的是课程设计最典型的三个诉求能跑通、能演示、能讲清楚原理。适合拿源码做模板改造的学生也适合想快速复现一套 PyQt5 数据分析案例的初学者。下文按架构、数据、模型、界面、打包的顺序把能直接抄作业的代码和踩过的坑一次讲完。2. 拆解项目架构为什么选 PyQt5、目录怎么组织、界面怎么画2.1 为什么是 PyQt5 而不是 Flask 网页版做期末大作业最忌讳的是花时间在“环境跑不起来”这种和内容无关的事情上。PyQt5 的优势恰恰在于它是个纯本地桌面框架不依赖浏览器、不依赖后端服务启动一个 main.py 就能看到窗口适合答辩现场不联网的环境。另一个现实原因后端逻辑是 pandas 和 sklearnPyQt5 和它们同在 Python 生态里数据可以从 DataFrame 直接塞进表格控件不用像 Web 方案那样写 JSON 接口。用 FastAPI 或 Flask 也不是不行但你要多处理一整套请求、路由、渲染的复杂度还可能面对浏览器跨域之类的幺蛾子。课程设计的时间本来就紧把复杂度留在自己熟悉的 Python 这一层比把问题引到前端更划算。Flask 方案适合你后续想把它改造成线上产品如果目标只是“期末大作业”这四个字PyQt5 是性价比最高的选择。它在 PyCharm 里调试也顺手设置好 Python 解释器后直接运行不需要额外启动任何服务。提示Python 环境建议用 3.9 以上的版本PyQt5 用 pip install PyQt5 安装最省事。如果遇到慢的情况换国内镜像源即可不要自己去下安装包乱试。2.2 源码目录怎么拆每个文件干什么下载下来的 zip 解压后内部结构通常不是一个大文件堆在一起而是把界面、数据处理、模型分成独立模块。这种拆分不是为了好看而是为了答辩时能单独讲每个文件的作用。我建议用下面这种目录组织适合房价分析和预测系统的通病——数据、界面、模型互相纠缠。# 推荐目录结构解压后按这个思路整理 second_hand_house/ ├── data/ # 数据集目录 │ └── house_data.csv # 二手房价原始数据 ├── ui/ # 界面相关代码 │ ├── main_window.py # 主窗口负责布局与事件 │ └── charts.py # 封装 matplotlib 图表绘制 ├── core/ # 业务逻辑 │ ├── data_clean.py # 数据清洗函数 │ ├── feature.py # 特征工程函数 │ └── model.py # 模型训练与预测封装 ├── models/ # 训练好的模型文件 │ └── house_model.pkl └── main.py # 程序入口只做启动一件事这样拆的好处是答辩时你说“main.py 只负责启动数据清洗在 core/data_clean.py界面在 ui/main_window.py”结构上就比一个几百行的单文件体面得多。数据文件单独放 data 目录是为了避免和代码混在一起后面打包和替换数据集都方便。实际项目里我更推荐把每个模块都写成函数而不是脚本。例如 data_clean.py 里暴露 clean_data(df) 函数model.py 里暴露 train_model(X, y) 和 load_model(path)。这样你在 ui 层调用时只需要 import 进来不必关心内部实现。2.3 Qt Designer 画模板和纯代码手写界面做 PyQt5 界面有两条路用 Qt Designer 拖控件生成 .ui 文件再转成 .py或者直接手写代码。期末大作业我一般建议主窗口用手写代码画布和控件少的表单也用代码原因很实际手写代码在 merge 和改动时更可控答辩老师问“这个按钮怎么加上去的”你直接说 addWidget 比说“我用 Designer 拖的”更经得起追问。但对于控件数量超过十个的复杂窗口Qt Designer 省时间。常见做法是用 Designer 画好布局再用 pyuic5 命令把 .ui 转成 .py之后在代码里继承这个生成的类。两种方式可以混用核心窗口手写复杂表单用 Designer。下面给一个最小可运行的主窗口骨架包含了标题、按钮和布局这是整个系统的地基import sys from PyQt5.QtWidgets import ( QApplication, QMainWindow, QVBoxLayout, QPushButton, QLabel, QWidget ) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle(二手房价分析与预测系统) self.resize(1100, 700) # 宽 1100高 700适合展示表格和图表 # 中心控件QMainWindow 不能直接挂布局必须套一层 QWidget central_widget QWidget() self.setCentralWidget(central_widget) # 垂直布局从上到下依次放控件 layout QVBoxLayout(central_widget) self.title_label QLabel(区域均价统计) layout.addWidget(self.title_label) self.load_button QPushButton(加载数据集) layout.addWidget(self.load_button) # 这里预留的信号连接通常在另一个方法里写 # self.load_button.clicked.connect(self.on_load_data) if __name__ __main__: app QApplication(sys.argv) window MainWindow() window.show() sys.exit(app.exec_())这段代码的逻辑很简单QMainWindow 是主窗口基类setCentralWidget 设置中心控件QVBoxLayout 管理垂直排列。setWindowTitle 和 resize 决定了演示时的第一印象标题要写全称尺寸建议 1100x700 以上太小了表格和图表挤在一起不好看。QApplication(sys.argv) 是每个 PyQt5 程序的必经入口sys.exit(app.exec_()) 保证窗口关闭后进程正常退出这两行不要省略。从布局到业务还有最后一层按钮点击后要触发加载数据、画图、预测这些动作。我习惯把所有槽函数写在 MainWindow 类里用 clicked.connect 绑定。这样做的好处是界面层只负责调用 core 层的函数不直接操作 pandas 或 sklearn职责清楚答辩也容易讲。3. 处理二手房价数据集缺失值、异常值与特征工程3.1 先认清数据集里有哪些字段二手房价数据集的字段一般包含小区名称、区域、户型、面积、朝向、楼层、建造年份、总价、单价、关注度、发布时间等。拿到数据第一件事不是急着建模而是跑一遍 df.info() 和 df.head()看清每列类型和缺失情况别被“房价分析”四个字带偏。常见字段和用途对照如下字段名类型用途area数值面积核心特征total_price数值总价预测目标unit_price数值单价可作衍生特征build_year数值建造年份用于计算房龄orientation文本朝向需转数值floor文本楼层可分组district文本区域可用于分组对比如果数据里没有 unit_price可以用 total_price / area 自己算出来。房价分析里面积和总价是最基本的两个字段缺了任何一个模型都无从谈起。size 特别小的数据集比如只有几百行也能做完整的课程设计演示但训练出来的模型预测精度有限答辩时不要把它说成“精确预测”。3.2 缺失值与异常值清洗房价数据的缺失和异常是家常便饭。面积出现 5 平米或者 500 平米的记录不一定是真实房源很可能是录入错误总价低到离谱的可能是车位或者地下室混进来了。处理逻辑要写进一个可复用的函数后续每次启动都走同一套清洗流程。import pandas as pd def clean_house_data(df: pd.DataFrame) - pd.DataFrame: # 删除完全没有分析价值的列比如无意义的自增 id df df.drop(columns[id, url], errorsignore) # 面积缺失用中位数填充中位数比均值抗离群值 if area in df.columns: df[area] df[area].fillna(df[area].median()) # 楼层缺失不适合填数字单独标记为“未知” if floor in df.columns: df[floor] df[floor].fillna(未知) # 计算单价剔除单价异常区间的记录 # 单价单位是元/平米这里按 3000 到 120000 过滤 df[unit_price] df[total_price] / df[area] df df[(df[unit_price] 3000) (df[unit_price] 120000)] # 面积异常区间小于 20 平米或大于 300 平米的记录剔除 df df[(df[area] 20) (df[area] 300)] # 重置索引避免删除后出现空洞 df df.reset_index(dropTrue) return df这里的几个参数要注意fillna(df[area].median()) 用中位数而不是均值因为房价数据离群值多均值容易被极端值拉偏unit_price 的上下界 3000 到 120000 不是拍脑袋而是参考主流二手房源价格带如果你的数据集来自豪宅或特定城市需要先打印 describe() 再调区间。reset_index(dropTrue) 是为了清洗后再训练时不带原始行号否则 train_test_split 会出现几千个空索引位日志看着很难受。清洗完记得打印 df.shape 和 df.isnull().sum()确认删了多少行、还缺哪些值。这一步是后面所有工作的基石清洗逻辑讲不清楚答辩时数据环节就会被打穿。3.3 特征筛选与相关性分析二手房价预测的特征不需要太多常见有效的是面积、房龄、朝向、户型、所在楼层段。特征太多反而稀释模型课程设计的数据集本来就不大五个以内的数值特征已经足够。朝向是文本得先映射成数值。# 朝向映射优先处理主流朝向其他归为 0 orientation_map {南: 1, 南北: 2, 东: 3, 西: 4, 东南: 5, 西南: 6} df[orientation] df[orientation].map(orientation_map) df[orientation] df[orientation].fillna(0) # 未命中的朝向统一为 0 # 房龄用当前年份减去建造年份避免直接用年份导致模型学到无关的时间趋势 df[house_age] 2024 - df[build_year] # 户型拆成卧室数和客厅数卧室数更影响价格 df[bedrooms] df[bedrooms].astype(int) # 相关系数矩阵打印后可以判断哪些特征和总价相关 numeric_cols [area, house_age, orientation, bedrooms, total_price] print(df[numeric_cols].corr())朝向映射成数字的规则不是唯一的关键是保持训练和预测时用同一张映射表。如果你的数据里朝向更多样把映射表补全即可顺序不影响模型只要全部朝向都映射到数字不要留下 NaN。house_age 这一列很有价值直接用 build_year 会导致模型学到“年份越大越贵”的假象因为新房和旧房的价格差异本质上体现在房龄上。相关系数矩阵建议打印到控制台看而不只是画图。数值上如果 area 和 total_price 的相关系数在 0.5 以上说明面积是主导特征house_age 与总价负相关也正常老房子通常总价更低。这一步也能提前发现两个特征高度相关的情况比如面积和总价相关 0.9 会导致多重共线性线性回归解释起来会有问题。4. 用 sklearn 训练价格预测模型并保存成可调用接口4.1 期末作业场景下哪种模型性价比最高二手房价预测在课程设计里最常见的是线性回归和随机森林。线性回归优点是解释性强系数能直接讲“面积每增加一平米价格大约增加多少”答辩时特别加分缺点是它对特征和价格的非线性关系拟合有限遇到明显非线性数据会欠拟合。随机森林的预测精度通常比线性回归高尤其数据里有朝向、楼层这类非线性特征时。它的问题是不好解释特征重要性也只能给个大概方向。我的建议是两个模型都训练用测试集对比 R²哪个高就把它作为默认模型另外在界面上留一个模型切换下拉框。不要一上来就上 XGBoost、LightGBM。课程设计的数据集一般只有几千条几十棵树已经足够树模型反而要调参且打包后体积更大。xgboost 安装还会给你带来额外的环境问题这属于典型的“给自己找麻烦”。sklearn 自带的 RandomForestRegressor 对大家来说更熟答辩老师也更认可。4.2 训练、评估、保存模型训练过程要固定 random_state保证每次跑出来的结果一样。你不希望演示前换一个随机种子预测准确率就变了解释不清。下面这段代码把训练、评估、保存串在一起训练完自动输出两个指标。import joblib from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score, mean_absolute_error # 特征列顺序必须固定后续预测接口按同一顺序输入 feature_cols [area, house_age, orientation, bedrooms] X df[feature_cols] y df[total_price] # test_size0.2 表示留 20% 数据做测试random_state42 固定划分 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) model RandomForestRegressor( n_estimators200, # 树的数量200 在这个数据规模下够用 max_depth10, # 限制单棵树深度防止过拟合 random_state42 # 固定随机种子保证结果可复现 ) model.fit(X_train, y_train) y_pred model.predict(X_test) print(R2:, round(r2_score(y_test, y_pred), 4)) print(MAE:, round(mean_absolute_error(y_test, y_pred), 2)) # 保存模型PyQt5 界面启动时直接加载避免每回都重新训练 joblib.dump(model, models/house_model.pkl)train_test_split 的 test_size 和 random_state 是这里最值得讲的两个参数test_size 越大测试集越能代表整体但训练数据越少0.2 是常见折中random_state 的作用是让数据划分可复现答辩时连续跑三次结果一致这一点给老师的印象分很高。n_estimators 和 max_depth 是随机森林的两个核心超参200 棵树和深度 10 在几千条数据的场景下已经够用树太多只会拖慢训练和打包体积。R² 和 MAE 分别看拟合优度和误差绝对值。R² 在 0.5 到 0.8 之间是这类数据集的正常水平不必追求 0.9 以上——房价本身受很多未采集因素影响硬把树加深只会换成过拟合。这里的评估指标要由数据规模决定而不是拍脑袋追高分。4.3 封装预测接口供 PyQt5 界面调用模型训练好后界面不能直接操作 joblib 加载的裸模型。原因有两个特征输入顺序容易记错模型文件的路径在界面层是硬编码。封装一个 HousePredictor 类把“加载模型”和“预测”两件事包起来界面只管调 predict 方法。import joblib class HousePredictor: def __init__(self, model_pathmodels/house_model.pkl): # 加载模型文件路径缺失时抛出明确错误 self.model joblib.load(model_path) # 特征顺序和训练时保持一致 self.feature_cols [area, house_age, orientation, bedrooms] def predict(self, area: float, house_age: float, orientation: int, bedrooms: int) - float: # 输入顺序必须和 feature_cols 一致 features [[area, house_age, orientation, bedrooms]] result self.model.predict(features)[0] # 保留两位小数返回避免界面上显示一长串浮点 return round(float(result), 2)这个类的接口设计有讲究predict 接收四个普通参数而不是一个列表调用方不用关心特征顺序返回值做了 round避免界面上显示 243.5684231 这种毫无美感的数字。model_path 用相对路径配合打包时把 models 目录一起带上就能在 exe 环境里正常工作。如果演示时需要切换线性回归和随机森林可以在init里加一个参数或者提供 set_model(path) 方法。这属于进阶功能放在最后一章细说。界面代码里调用就两行两行代码调用十行封装这种取舍在项目里永远值得。5. PyQt5 界面集成与打包避坑4 个高频翻车现场5.1 matplotlib 嵌入后界面卡死现象在 PyQt5 窗口里用 FigureCanvasQTAgg 嵌入 matplotlib点按钮画图时窗口直接卡住拖动窗口也卡顿。原因matplotlib 默认后端是 TkAgg在 Qt 环境里会冲突或者你在绘图函数里用了 plt.show() 阻塞了事件循环。PyQt5 的事件循环和 matplotlib 的后端是两个线程在抢控制权。解决先明确声明使用 Qt 后端代码开头加上import matplotlib; matplotlib.use(Qt5Agg)然后所有图表都画在 FigureCanvasQTAgg 上不调用 plt.show()。如果图特别多把绘图逻辑放到单独线程里或者至少用 canvas.draw_idle() 替代 draw()。我一般是这样封装import matplotlib matplotlib.use(Qt5Agg) # 必须在导入 pyplot 之前设置 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg from matplotlib.figure import Figure class PriceChart(FigureCanvasQTAgg): def __init__(self, parentNone, width8, height4, dpi100): self.fig Figure(figsize(width, height), dpidpi) super().__init__(self.fig) self.setParent(parent) self.ax self.fig.add_subplot(111) def plot_area_price(self, data): # 每次绘图前清空坐标轴避免重复叠加 self.ax.clear() self.ax.scatter(data[area], data[total_price], s8) self.ax.set_xlabel(面积) self.ax.set_ylabel(总价) self.fig.tight_layout() self.draw_idle() # 异步刷新不阻塞界面图例里最关键的两行是 matplotlib.use(Qt5Agg) 和 draw_idle()。前者告诉 matplotlib 用 Qt 后端后者告诉画布“有更新了请尽快刷新”但不强制马上绘制界面不会等绘图阻塞。清空坐标轴是为了防止多按几次按钮后图上叠着一堆点。5.2 pip 环境冲突装别的库把 PyQt5 搞坏了现象本来界面能跑为了装 labelme 或其他工具pip 自动升级或降级了 PyQt5 的版本重新跑程序报一堆找不到模块的错误。原因pip 在装依赖的时候会做版本解析如果新库要求的 PyQt5 版本和已有版本冲突pip 可能直接替换掉。labelme 这类工具对 PyQt5 的版本要求很挑剔是典型的冲突源。解决项目用虚拟环境隔离不要往全局 Python 里乱装。创建环境后先把核心依赖一次装齐pip install PyQt5 pandas matplotlib scikit-learn joblib之后非必要不再执行可能改动 PyQt5 的 pip 操作。实验室机器或者答辩机器上没有虚拟环境时用 pip freeze requirements.txt 导出一份依赖清单换机器之后pip install -r requirements.txt一次复原。注意如果已经把 PyQt5 装坏了先pip uninstall PyQt5 PyQt5-sip再重装而不是继续 pip install PyQt5 往上叠。sip 版本和 PyQt5 版本不匹配是常见的“装完还是坏”的原因两个包要一并对齐。我在这上面翻过车后来凡是课程设计项目一律先建虚拟环境再动 pip。5.3 PyInstaller 打包后图标、数据集和模型文件丢失现象源码里运行一切正常打包成 exe 后双击能打开但一加载数据集就报文件找不到或者模型加载失败。原因PyInstaller 打包时只把 .py 文件收集进去data、models、ui 目录里的 pkl 和 csv 不会自动包含。运行时的工作目录也不是 exe 所在目录用 sys.path[0] 拿路径在打包后会指向临时解压目录。解决先明确告诉 PyInstaller 要收集哪些非代码文件其次在代码里用可执行文件目录来拼路径。一个可靠做法是import sys, os def resource_path(relative): # 打包后用 _MEIPASS 指向临时解压目录源码运行用项目目录 base getattr(sys, _MEIPASS, os.path.dirname(os.path.abspath(__file__))) return os.path.join(base, relative)然后把所有 data/models 引用都改成 resource_path(models/house_model.pkl)。打包命令加 --add-data 参数pyinstaller -F -w main.py \ --add-data models;models \ --add-data data;data \ --name house_price_gui参数说明-F 打成一个单文件-w 不显示黑色控制台窗口--add-data 把 models 和 data 目录原样带进包。注意 Windows 下 add-data 的源路径和目标路径用分号分隔Linux 用冒号。打包后先在当前目录双击测一遍再把 exe 移到桌面测一遍路径问题一般移动位置就暴露了。5.4 界面中文乱码或模型预测结果对不上现象PyQt5 界面上中文显示成方块或者模型预测的结果和训练时对同一输入的结果不一致。原因中文乱码多半是系统字体缺失或 CSV 编码不是 utf-8预测结果不一致通常是你把朝向、房龄的输入顺序或映射方式搞错了模型按训练时的特征顺序读数据预测时却换了一个顺序。解决CSV 读取时显式指定 encodingutf-8 或 encodinggbk读取后立刻打印前五行确认中文没有乱。界面字体统一设置from PyQt5.QtGui import QFont app.setFont(QFont(Microsoft YaHei, 10))预测对不上时先在训练脚本里打印 df[feature_cols].head() 和模型预测的样本值再和界面输入的极限值对比。最常见的就是朝向映射表不一致训练时南北映射为 2界面下拉框里南北却传成 3。解决方案是把 feature 的映射表统一放成一个公共常量文件train 和 UI 都引用同一个字典不要在两侧各写一份。6. 把验证和调参做进界面交叉验证与模型体检6.1 用交叉验证给出更可信的 R²单次 train_test_split 的结果受随机划分影响很大演示时最好在训练结束后顺便跑一次 5 折交叉验证把均值和标准差显示在界面上。老师问“你的模型稳定吗”你直接用数字回答比口头解释更有说服力。from sklearn.model_selection import cross_val_score cv_scores cross_val_score(model, X, y, cv5, scoringr2) print(CV R2:, cv_scores.mean().round(4), /-, cv_scores.std().round(4))6.2 把模型参数做成界面上的下拉框进阶一点的做法是在界面上加一个“模型设置”区域用 QComboBox 切换随机森林和线性回归用 QSpinBox 调 n_estimators。切换后重新训练并更新指标标签这样演示时就能现场展示“参数对结果的影响”比静态截图生动得多。调参范围不用给太大我一般限定在 50 到 500 之间否则点一次训练等十几秒现场气氛反而尴尬。我现在的习惯是不管多简单的课程设计拿到项目先跑通数据清洗和基础统计再谈界面和模型。因为数据一旦有脏值后面所有结论都可能推翻界面做得再好看也只是把错误放大展示。这套基于 Python 和 PyQt5 的二手房价分析与预测系统真正值钱的部分是把一条完整的数据分析链路串起来——从清洗到训练再到用户填几个数字就能看到预测结果。拿到源码和数据集以后先不要急着跑 main.py对照这篇的目录结构逐行读一遍 core 层代码。读懂了再动手改模型或界面你的答辩状态会完全不一样。希望帮到你。本文还有配套的精品资源点击获取
返回列表