ARTICLE DETAIL

资讯详情

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

Python房屋价格预测系统:数据清洗、可视化与模型实战

Python房屋价格预测系统:数据清洗、可视化与模型实战 简介这是一套面向高校计算机相关专业学生的Python房屋信息可视化及价格预测系统完整项目可直接用于毕业设计或课程设计。项目采用Python 3.7/3.8开发配合PyCharm与Navicat后端逻辑与前端页面代码齐全数据库脚本一并提供导入依赖、配置数据库密码后即可运行适合需要完整实战项目、希望快速搭建可视化与预测功能的中级学习者。压缩包共307个文件约10.07MB包含46个py源码文件、43个js脚本、17个html页面、17个css样式、1个sql数据库脚本以及gif、png、jpg等图片素材和csv数据文件覆盖前后端与数据资源。目前已有90人学习下载。项目功能完善、界面美观、操作简单涵盖房屋数据展示、可视化图表与价格预测等模块目录结构清晰便于按模块阅读与二次开发可作为毕业设计参考或课程设计模板帮助读者理解完整项目从数据到界面的组织方式。1. 从一份房屋数据到能跑通的预测系统这套 Python 方案到底解决什么问题很多人第一次接触「基于 Python 的房屋信息可视化及价格预测系统」是在毕业设计选题表上看到它。它听起来像两个不相干的东西拼在一起一边是画图一边是预测房价。但真正做过的人知道这两件事其实是一条流水线上的前后工序——先把散落在 CSV 或数据库里的房源字段清洗成能看的图表再把清洗后的结构化数据喂给回归模型输出一个可解释的价格估计。它解决的核心痛点很具体你手里有一批二手房或新房挂牌数据字段包括面积、户型、楼层、朝向、区域、单价但它们是脏的、散的、没法直接讲故事也没法直接算。这套系统要做的就是把「数据清洗 → 可视化分析 → 特征工程 → 模型训练 → 预测接口」串成一条能复现的链路。适合谁计算机、大数据、软件工程方向的毕业设计学生尤其是选题偏「数据分析 机器学习」但不想碰深度学习调参的人也适合刚学完 pandas 和 sklearn、想找一个完整项目练手的 Python 入门者。它不要求你会写爬虫因为数据可以来自公开数据集或自己整理的 Excel也不要求你会前端框架因为可视化可以用 matplotlib、seaborn 或 pyecharts 直接出图。真正要花时间的地方在于字段怎么选、缺失值怎么填、异常单价怎么处理、模型怎么评估才不会自欺欺人。下面按「先立住原理再动手复现」的顺序把这条链路拆开讲清楚。2. 房屋数据从哪来、怎么洗字段选择与清洗的四个关键决策2.1 数据来源的三种常见路径与字段取舍做房屋价格预测第一步不是写模型而是确定数据从哪来。常见做法有三种一是用公开的二手房数据集比如链家或安居客的历史挂牌数据注意只使用已公开、可合法获取的静态数据集不要实时爬取二是从学校或导师提供的脱敏数据中抽取三是自己整理一份小规模 Excel字段控制在 10 个以内。无论哪种来源字段选择都遵循一个原则只保留在预测时能拿到的信息。什么意思如果你用「成交总价」反推「成交单价」那单价就是标签泄漏训练时准确率虚高上线就翻车。我一般会保留这几类字段面积float、户型如「3室2厅」拆成室数和厅数、楼层总楼层和所在楼层、朝向类别、建筑年代int、区域类别、装修情况类别、单价标签float。像「房源标题」「经纪人姓名」这种文本字段除非做 NLP否则直接丢掉。字段确定后先看一眼数据量和缺失比例用下面这段代码快速摸底import pandas as pd # 读取原始数据注意编码可能是 gbk 或 utf-8 df pd.read_csv(house_raw.csv, encodingutf-8) # 查看前五行和字段类型 print(df.head()) print(df.dtypes) # 统计缺失值比例按从高到低排序 missing df.isnull().mean().sort_values(ascendingFalse) print(missing[missing 0])这段代码的逻辑很简单head()让你确认列名和样例值dtypes帮你区分数值列和对象列isnull().mean()算出每个字段的缺失比例。参数上唯一要注意的是encoding如果报UnicodeDecodeError换成gbk或gb18030再试。缺失比例超过 40% 的字段除非业务上特别重要否则直接删列比硬填更安全。2.2 缺失值、异常值和类别编码的处理顺序清洗顺序不能乱先处理异常值再填缺失值最后做类别编码。为什么因为异常值会污染填充的统计量。比如「单价」字段里混进了 1 元/平和 100 万/平的数据如果你先算均值去填缺失均值会被拉偏。我一般用 IQR 方法先框出合理区间# 用 IQR 识别单价异常值 Q1 df[单价].quantile(0.25) Q3 df[单价].quantile(0.75) IQR Q3 - Q1 lower Q1 - 1.5 * IQR upper Q3 1.5 * IQR # 只保留区间内的记录 df df[(df[单价] lower) (df[单价] upper)] print(f清洗后剩余 {len(df)} 条记录)quantile(0.25)和quantile(0.75)分别取上下四分位数1.5 * IQR是统计学上常用的边界系数。如果你的数据分布偏态严重可以把系数改成 3避免删掉太多真实高价房源。处理完异常值后数值列用中位数填充类别列用众数填充# 数值列中位数填充 num_cols [面积, 建筑年代, 楼层] for col in num_cols: df[col] df[col].fillna(df[col].median()) # 类别列众数填充 cat_cols [朝向, 区域, 装修] for col in cat_cols: df[col] df[col].fillna(df[col].mode()[0])中位数比均值抗异常值众数适合类别列。填充完成后用pd.get_dummies做独热编码或者用LabelEncoder做标签编码。区域这种无序类别用独热装修情况如果有「毛坯→简装→精装」的顺序可以用有序编码。这一步做完数据才算真正能喂给模型。2.3 把清洗后的数据落库SQLite 建表与写入很多毕业设计要求「附数据库」其实用 SQLite 就够了不需要装 MySQL。把清洗后的 DataFrame 写入 SQLite既方便后续可视化查询也能在论文里体现数据管理环节。建表时字段类型要和 pandas 对应float 对应 REALint 对应 INTEGERobject 对应 TEXT。import sqlite3 conn sqlite3.connect(house.db) # 将清洗后的数据写入表 house_clean如果表存在则替换 df.to_sql(house_clean, conn, if_existsreplace, indexFalse) # 验证写入结果 cursor conn.cursor() cursor.execute(SELECT COUNT(*) FROM house_clean) print(入库记录数, cursor.fetchone()[0]) conn.close()if_existsreplace表示每次运行都重建表适合开发阶段如果数据要增量追加改成append。indexFalse避免把 pandas 的行索引也写进去。入库后后续可视化可以直接用 SQL 查询比如按区域统计均价比每次读 CSV 快得多。3. 房屋信息可视化用 pyecharts 做区域均价与户型分布图3.1 区域均价柱状图与地图的选型理由可视化不是把数据随便画出来就行毕业设计里常见的翻车是图很花哨但看不出结论。房屋数据最值得展示的两个维度是「区域均价对比」和「户型分布」。区域均价用柱状图或地图户型分布用饼图或堆叠柱状图。选 pyecharts 而不是 matplotlib 的原因是它生成的 HTML 可以交互鼠标悬停能看到具体数值放在论文或答辩演示里更直观。先看区域均价的柱状图怎么画from pyecharts.charts import Bar from pyecharts import options as opts import pandas as pd import sqlite3 conn sqlite3.connect(house.db) # 按区域分组计算均价和房源数量 sql SELECT 区域, AVG(单价) AS 均价, COUNT(*) AS 数量 FROM house_clean GROUP BY 区域 df_area pd.read_sql(sql, conn) conn.close() bar ( Bar() .add_xaxis(df_area[区域].tolist()) .add_yaxis(均价元/平, df_area[均价].round(0).tolist()) .set_global_opts( title_optsopts.TitleOpts(title各区域房屋均价对比), xaxis_optsopts.AxisOpts(name区域), yaxis_optsopts.AxisOpts(name均价), ) ) bar.render(area_price.html)add_xaxis放区域名add_yaxis放均价round(0)保留整数避免小数太长。render生成 HTML 文件浏览器打开就能交互。如果要做地图把Bar换成Map区域名要和 pyecharts 内置的城市名对齐否则地图上不显示。3.2 户型分布与价格区间的联动分析单看户型分布意义不大把户型和均价放在一起才能看出「哪种户型更贵」。常见做法是先用groupby统计每个户型的房源数量和均价再用双轴图展示。下面这段代码先做聚合再画组合图from pyecharts.charts import Bar, Line from pyecharts.commons.utils import JsCode # 按户型分组计算数量和均价 df_type df.groupby(户型).agg(数量(单价, count), 均价(单价, mean)).reset_index() df_type df_type[df_type[数量] 5] # 过滤样本过少的户型 bar Bar() bar.add_xaxis(df_type[户型].tolist()) bar.add_yaxis(房源数量, df_type[数量].tolist(), yaxis_index0) bar.extend_axis(yaxisopts.AxisOpts(name均价元/平)) line Line() line.add_xaxis(df_type[户型].tolist()) line.add_yaxis(均价, df_type[均价].round(0).tolist(), yaxis_index1) bar.overlap(line) bar.render(type_price.html)agg里用字典指定不同列的聚合方式数量用 count均价用 mean。过滤掉样本少于 5 的户型避免统计噪声。extend_axis加右侧 Y 轴overlap把柱状图和折线图叠在一起。这样一张图既能看出哪种户型房源多又能看出哪种户型单价高答辩时一句话就能讲清楚。3.3 可视化结果如何反哺特征工程画图不只是为了好看它能帮你发现特征工程的方向。比如你发现「朝南」的房源均价明显高于「朝北」那朝向就不该随便编码而应该保留为有序或重点类别。再比如你发现某个区域的房源数量极少但均价极高那这个区域可能是别墅区和普通住宅混在一起训练会拉偏模型。我一般会把可视化阶段发现的异常区域单独标记训练时要么剔除要么加一个「是否别墅」的布尔特征。这一步没有固定代码靠的是你对着图多问几句「为什么这个点这么高」「为什么这个区域样本这么少」。可视化做到位后面的模型调参会省很多力气。4. 价格预测模型从线性回归到随机森林的参数与评估4.1 特征矩阵与标签的划分别让数据泄漏毁掉模型建模前最重要的一步是划分特征和标签并且确保训练集和测试集的划分是随机的、可复现的。很多人在这里踩坑先用全量数据做了标准化再划分训练测试结果测试集的均值和方差信息泄漏到了训练过程评估分数虚高。正确顺序是先划分再在训练集上 fit 标准化器然后 transform 测试集。from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # 特征列和标签列 feature_cols [面积, 室数, 厅数, 楼层, 建筑年代, 朝向_南, 朝向_北, 区域_A, 区域_B] X df[feature_cols] y df[单价] # 先划分random_state 固定保证可复现 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 标准化器只在训练集上 fit scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test)test_size0.2表示 20% 做测试random_state42是常用种子保证每次运行划分一致。fit_transform和transform的区别是血泪经验前者既计算均值方差又转换后者只用训练集的参数转换。如果搞反了模型在测试集上的表现会好得不真实。4.2 线性回归、随机森林和 XGBoost 的对比与参数设置房屋价格预测常用的三个模型线性回归基线、随机森林非线性、抗过拟合、XGBoost精度高但调参多。毕业设计里建议至少跑两个做对比论文里才有内容写。先看随机森林的核心参数from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, r2_score rf RandomForestRegressor( n_estimators200, # 树的数量越多越稳但越慢 max_depth12, # 最大深度控制过拟合 min_samples_leaf3, # 叶子节点最少样本防止噪声 random_state42, n_jobs-1 # 用满 CPU 核心 ) rf.fit(X_train_scaled, y_train) y_pred_rf rf.predict(X_test_scaled) print(MAE:, mean_absolute_error(y_test, y_pred_rf)) print(R2:, r2_score(y_test, y_pred_rf))n_estimators200是起点如果 MAE 还在降可以加到 500但训练时间会线性增长。max_depth12是我在房屋数据上常用的值太深会记住训练集噪声太浅欠拟合。min_samples_leaf3保证每个叶子至少有 3 条样本避免对个别房源过拟合。评估指标看两个MAE 表示平均预测误差多少元R2 表示模型解释了多大比例的方差。房屋数据 R2 在 0.7 到 0.85 之间算正常超过 0.95 就要怀疑数据泄漏。4.3 用网格搜索找最优参数与交叉验证手动调参靠感觉网格搜索靠穷举。但网格搜索很耗时参数组合不能太多。我一般只对max_depth和min_samples_leaf做搜索n_estimators固定 200。from sklearn.model_selection import GridSearchCV param_grid { max_depth: [8, 12, 16], min_samples_leaf: [1, 3, 5] } grid GridSearchCV( RandomForestRegressor(n_estimators200, random_state42, n_jobs-1), param_grid, cv5, # 5 折交叉验证 scoringneg_mean_absolute_error, n_jobs-1 ) grid.fit(X_train_scaled, y_train) print(最优参数, grid.best_params_) print(最优 MAE, -grid.best_score_)cv5表示把训练集分成 5 份轮流做验证比单次划分更可靠。scoring用负 MAE 是因为 sklearn 的评分函数默认越大越好MAE 越小越好所以取负。best_params_给出最优组合best_score_是交叉验证的平均 MAE。拿到最优参数后用最优模型在测试集上跑一次最终评估这个分数才是论文里该写的。5. 避坑与排查房屋预测系统最常见的五个翻车点5.1 单价字段混入总价模型直接学偏现象训练时 R2 很高但预测一套 90 平的房子给出 800 万单价明显不合理。原因数据里「单价」列混进了「总价」记录或者单位不统一元/平 vs 万元/平。解决在清洗阶段加一步单位检查用df[单价].describe()看最大值和最小值如果最大值超过 50 万/平大概率是总价混入。统一除以 10000 或剔除异常记录。5.2 区域独热编码后维度爆炸现象加入区域字段后特征矩阵从 10 列变成 200 列训练极慢且 R2 下降。原因区域类别太多每个区域一个独热列稀疏矩阵让树模型难以分裂。解决把低频区域合并成「其他」只保留房源数量前 10 的区域做独热其余归为一类。或者改用目标编码用区域均价代替区域名但要注意在训练集上计算均值避免泄漏。5.3 训练集和测试集划分前做了全局标准化现象测试集 R2 比交叉验证高出一大截换一组随机种子就掉下来。原因标准化器在全量数据上 fit测试集的统计信息泄漏到了训练过程。解决严格按「先划分再 fit 训练集再 transform 测试集」的顺序。如果用了 Pipeline把 scaler 和模型一起放进 Pipeline交叉验证时自动处理。5.4 缺失值填充用均值导致分布偏移现象填充后「面积」字段的分布比原始数据更集中模型对大面积房源预测偏差大。原因均值填充会把缺失值拉向中心缩小方差。解决改用中位数填充或者用随机森林等模型做多重插补。如果缺失比例低于 5%直接删掉缺失行也比均值填充安全。5.5 可视化图表中文乱码现象pyecharts 或 matplotlib 生成的图里中文显示成方框。原因默认字体不支持中文。解决matplotlib 需要设置plt.rcParams[font.sans-serif] [SimHei]pyecharts 一般不需要额外设置但如果用 Jupyter Notebook 显示确保 HTML 渲染正常。如果是在 Linux 服务器上生成图片需要安装中文字体包。6. 把预测接口封装成 Flask 服务并做输入校验6.1 用 Flask 暴露预测接口的最小实现毕业设计如果只停留在 Notebook 里答辩时容易被问「怎么用」。把训练好的模型保存下来用 Flask 包一个 POST 接口输入 JSON 特征返回预测单价整个系统就完整了。先保存模型和标准化器import joblib joblib.dump(rf, rf_model.pkl) joblib.dump(scaler, scaler.pkl)然后写 Flask 服务from flask import Flask, request, jsonify import joblib import numpy as np app Flask(__name__) model joblib.load(rf_model.pkl) scaler joblib.load(scaler.pkl) app.route(/predict, methods[POST]) def predict(): data request.get_json() # 按训练时的特征顺序组装输入 features np.array([[ data[面积], data[室数], data[厅数], data[楼层], data[建筑年代], data[朝向_南], data[朝向_北], data[区域_A], data[区域_B] ]]) features_scaled scaler.transform(features) price model.predict(features_scaled)[0] return jsonify({预测单价: round(float(price), 2)}) if __name__ __main__: app.run(host0.0.0.0, port5000)request.get_json()解析请求体np.array把输入转成二维数组scaler.transform用训练时的参数做标准化model.predict返回预测值。round(float(price), 2)保证 JSON 可序列化。启动后用 curl 测试curl -X POST http://127.0.0.1:5000/predict \ -H Content-Type: application/json \ -d {面积:90,室数:3,厅数:2,楼层:8,建筑年代:2010,朝向_南:1,朝向_北:0,区域_A:1,区域_B:0}如果返回{预测单价: 45230.5}这样的结果说明链路通了。6.2 输入校验与异常返回别让脏输入打穿服务接口暴露后最怕的是用户传了缺失字段或字符串。加一层校验字段不全或类型不对直接返回 400而不是让模型抛异常。下面是一个简单的校验逻辑REQUIRED_FIELDS [面积, 室数, 厅数, 楼层, 建筑年代, 朝向_南, 朝向_北, 区域_A, 区域_B] app.route(/predict, methods[POST]) def predict(): data request.get_json() if not data: return jsonify({error: 请求体为空}), 400 missing [f for f in REQUIRED_FIELDS if f not in data] if missing: return jsonify({error: f缺少字段{missing}}), 400 try: features np.array([[float(data[f]) for f in REQUIRED_FIELDS]]) except (ValueError, TypeError): return jsonify({error: 字段值必须为数字}), 400 features_scaled scaler.transform(features) price model.predict(features_scaled)[0] return jsonify({预测单价: round(float(price), 2)})missing列表推导找出缺失字段float()转换捕获非数字输入。返回 400 状态码让调用方知道是请求问题而不是服务崩溃。这套校验不复杂但能挡住 90% 的脏输入。6.3 模型持久化与版本管理的一个习惯我自己的习惯是每次训练完模型除了保存 pkl 文件还在文件名里带上日期和关键参数比如rf_20250101_depth12.pkl。同时在项目根目录放一个model_notes.md记录这次训练用了哪些特征、MAE 和 R2 是多少、数据量多大。这样过两周再回来不用翻 Notebook 就能知道哪个模型是最新的。毕业设计答辩时老师问「你这个模型是什么时候训练的、参数怎么定的」直接翻笔记就能答。这个习惯看起来小但能省掉很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
返回列表