基于Python的房屋信息可视化与价格预测系统实战

发布时间:2026/9/28 23:58:11
基于Python的房屋信息可视化与价格预测系统实战 简介这是一套面向高校计算机相关专业学生的Python房屋信息可视化及价格预测系统完整项目可直接用于毕业设计或课程设计场景。项目采用Python 3.7/3.8开发配合PyCharm与Navicat后端逻辑与前端页面代码齐全并附带MySQL数据库脚本导入依赖、配置数据库密码后即可运行适合需要完整实战案例来理解数据可视化与价格预测流程的学习者。压缩包共307个文件约10.07MB其中46个py文件承载核心业务与预测逻辑43个js与17个html、17个css构成前端交互界面另有gif、png、jpg等图片素材及csv数据文件、sql建库脚本、xml配置等目录结构清晰便于按模块查阅。目前已有90人学习关注。资源涵盖源码、数据库与部署说明能帮助读者快速掌握从数据存储、可视化展示到价格预测的完整实现思路并可作为二次开发与功能扩展的参考基础。1. 从一份房屋数据说起这个系统到底解决什么问题很多人第一次接触「基于 Python 的房屋信息可视化及价格预测系统」是在毕业设计选题表上看到它的。它听起来像两个不相干的东西拼在一起一边是画图一边是预测价格。但真正动手做过的人会明白这两件事其实是一条链上的上下游——先把散落在表格里的房源信息变成能看懂的图再让模型从这些图背后的数据里学出「什么样的房子值多少钱」。这个系统要解决的核心痛点很具体一份二手房或新房数据集动辄几万行字段包括面积、户型、楼层、朝向、区域、单价、总价。直接看表格人眼只能看到前几十行直接上模型又不知道哪些字段在起作用。所以合理的路径是先用可视化把数据分布、区域差异、相关性摸清楚再基于清洗后的特征训练回归模型最后把预测结果和可视化面板接到一起形成一个能演示、能交互、能解释的系统。它适合三类人正在做计算机毕业设计、需要一套完整可复现方案的学生刚学完 pandas 和 sklearn、想找一个真实数据集练手的新手以及需要快速搭一个数据演示原型、用于汇报或内部评审的从业者。下面我按「数据怎么来 → 图怎么画 → 模型怎么训 → 系统怎么串 → 坑在哪」的顺序把这条链路拆开讲清楚。2. 数据获取与清洗房源字段的取舍和三个必做处理2.1 房源数据从哪来字段怎么定做这个系统第一步不是写代码而是确定数据来源。常见做法有三种一是用公开的二手房数据集很多毕业设计资料包里会附带 CSV 或 SQL 文件二是自己用 requests BeautifulSoup 抓取公开房源页面但要注意目标站点的 robots 协议和访问频率三是用模拟数据生成器造一批结构合理的房源记录适合只验证系统流程的场景。不管哪种来源字段设计要围绕「可视化能分组、模型能回归」两个目标。我一般会保留这些列字段名类型用途是否入模district字符串区域分组可视化是编码后area浮点面积分布、散点图是rooms整数户型统计是floor字符串楼层区间分析是编码后orientation字符串朝向分布是编码后year_built整数房龄计算是unit_price浮点单价分布否避免泄漏total_price浮点预测目标标签注意unit_price不能作为特征因为它和total_price存在直接换算关系放进去会让模型「作弊」评估指标虚高实际预测时又拿不到。这是血泪经验很多人第一次跑出 R² 0.98 还以为是模型强其实是标签泄漏。2.2 缺失值、异常值和重复记录的处理拿到数据后先跑一段探查代码看清楚缺失比例和数值范围。import pandas as pd import numpy as np df pd.read_csv(house.csv, encodingutf-8) # 1. 查看缺失比例 missing df.isnull().mean().sort_values(ascendingFalse) print(missing[missing 0]) # 2. 数值列描述重点看 min/max 是否离谱 print(df[[area, total_price, unit_price]].describe()) # 3. 重复记录 print(重复行数:, df.duplicated().sum()) df df.drop_duplicates() # 4. 缺失处理数值列用中位数类别列用众数 num_cols [area, year_built] cat_cols [district, floor, orientation] for c in num_cols: df[c] df[c].fillna(df[c].median()) for c in cat_cols: df[c] df[c].fillna(df[c].mode()[0]) # 5. 异常值面积小于 10 或大于 1000 的视为录入错误 df df[(df[area] 10) (df[area] 1000)] df df[df[total_price] 0]这段代码的逻辑是先量化缺失再决定填充策略。数值列用中位数而不是均值是因为房价数据天然右偏均值会被高价房源拉偏。类别列用众数填充保证不引入新类别。异常值用业务常识卡边界面积 10 平米以下或 1000 平米以上基本是录入错误直接剔除比强行修正更安全。参数上encoding要根据实际文件调整中文 CSV 常见 utf-8 或 gbk读进来乱码就换。describe()里的 max 如果出现 999999 这类值基本是占位符要单独处理。2.3 特征工程把字符串变成模型能吃的数字区域、楼层、朝向都是字符串模型不认。常见做法是独热编码或有序编码。区域数量不多时用独热楼层和朝向有明显顺序关系时可以用映射。# 区域独热编码 df pd.get_dummies(df, columns[district], prefixdist) # 楼层映射为有序数值 floor_map {低楼层: 1, 中楼层: 2, 高楼层: 3} df[floor_level] df[floor].map(floor_map).fillna(2) # 朝向简化南北通透记 3南记 2其他记 1 def orient_score(x): if 南北 in str(x): return 3 if 南 in str(x): return 2 return 1 df[orient_score] df[orientation].apply(orient_score) # 房龄 df[house_age] 2024 - df[year_built] df[house_age] df[house_age].clip(lower0) # 保存清洗后数据 df.to_csv(house_clean.csv, indexFalse)独热编码会让特征维度增加如果区域有几十个建议改用目标编码或只保留高频区域。楼层映射的fillna(2)是兜底防止出现映射表外的值导致整列 NaN。房龄用当前年份减建造年份clip(lower0)防止未来年份导致的负数。这些处理做完数据才算真正能进模型。3. 可视化面板用 pyecharts 把房源分布画成能交互的图3.1 为什么选 pyecharts 而不是 matplotlibmatplotlib 适合静态出图写论文截图够用但毕业设计答辩时老师往往想看交互。pyecharts 生成 HTML鼠标悬停能看数值区域能缩放放到浏览器里演示效果更直观。而且它和 pandas 配合简单几行代码就能出一张图。安装很简单pip install pyecharts pandas如果要做 Web 面板再装 Flask 或 Streamlit。我一般用 Streamlit 做快速原型因为它不需要写前端Python 脚本直接跑成页面。3.2 四张核心图分布、区域对比、相关性、地理散点第一张是价格分布直方图看目标变量是否右偏。from pyecharts.charts import Bar, Scatter, HeatMap, Geo from pyecharts import options as opts import pandas as pd import numpy as np df pd.read_csv(house_clean.csv) # 1. 总价分布分箱统计 bins [0, 100, 200, 300, 500, 800, 2000] labels [100万以下, 100-200万, 200-300万, 300-500万, 500-800万, 800万以上] df[price_bin] pd.cut(df[total_price], binsbins, labelslabels) count df[price_bin].value_counts().reindex(labels) bar ( Bar() .add_xaxis(labels) .add_yaxis(房源数量, count.tolist()) .set_global_opts( title_optsopts.TitleOpts(title总价区间分布), xaxis_optsopts.AxisOpts(name总价区间), yaxis_optsopts.AxisOpts(name数量), ) ) bar.render(price_dist.html)pd.cut的分箱边界要根据实际数据调整如果大部分房源在 200 万以内就把 300 以上的箱合并。reindex(labels)保证没有数据的区间也显示为 0图不会缺柱子。第二张是区域均价对比柱状图直接看出哪个区贵。# 2. 各区域均价 dist_price df.groupby(district)[unit_price].mean().sort_values(ascendingFalse) bar2 ( Bar() .add_xaxis(dist_price.index.tolist()) .add_yaxis(均价(元/平), [round(x, 1) for x in dist_price.tolist()]) .set_global_opts( title_optsopts.TitleOpts(title各区域单价对比), xaxis_optsopts.AxisOpts(axislabel_optsopts.LabelOpts(rotate30)), ) ) bar2.render(district_price.html)如果区域名太长rotate30让标签倾斜避免重叠。均价用unit_price而不是total_price因为总价受面积影响单价更能反映区域真实水平。第三张是面积和总价的散点图看线性关系强不强。# 3. 面积-总价散点抽样 500 条避免卡顿 sample df.sample(nmin(500, len(df)), random_state42) scatter ( Scatter() .add_xaxis(sample[area].round(1).tolist()) .add_yaxis(总价, sample[total_price].round(1).tolist()) .set_global_opts( title_optsopts.TitleOpts(title面积与总价关系), xaxis_optsopts.AxisOpts(name面积(平米), type_value), yaxis_optsopts.AxisOpts(name总价(万)), ) ) scatter.render(area_price.html)散点图数据量大时浏览器会卡抽样 500 条足够看出趋势。type_value让 x 轴按数值连续排列而不是按字符串等距排列否则点会挤在一起。第四张是相关性热力图帮你在建模前筛特征。# 4. 相关性热力图 corr_cols [area, rooms, house_age, orient_score, floor_level, total_price] corr df[corr_cols].corr().round(2) heatmap ( HeatMap() .add_xaxis(corr_cols) .add_yaxis(, corr_cols, [[i, j, corr.iloc[i, j]] for i in range(len(corr_cols)) for j in range(len(corr_cols))]) .set_global_opts( title_optsopts.TitleOpts(title特征相关性), visualmap_optsopts.VisualMapOpts(min_-1, max_1, is_calculableTrue), ) ) heatmap.render(corr.html)热力图里如果某个特征和目标相关性接近 0可以考虑剔除。但注意相关性只反映线性关系非线性关系要靠模型自己学不能只看这个就删特征。4. 价格预测模型从线性回归到随机森林的选型与调参4.1 基线模型线性回归先跑通流程不要一上来就上 XGBoost。先用线性回归把「切分数据 → 训练 → 评估」的流程跑通确认没有标签泄漏、没有数据格式错误再换复杂模型。from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, r2_score import pandas as pd import numpy as np df pd.read_csv(house_clean.csv) # 特征列去掉标签和原始字符串列 drop_cols [total_price, unit_price, district, floor, orientation, price_bin] X df.drop(columns[c for c in drop_cols if c in df.columns]) y df[total_price] # 只保留数值列 X X.select_dtypes(include[np.number]) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) lr LinearRegression() lr.fit(X_train, y_train) pred lr.predict(X_test) print(MAE:, round(mean_absolute_error(y_test, pred), 2)) print(R2:, round(r2_score(y_test, pred), 4))select_dtypes保证只留数值列避免独热后的布尔列或残留字符串导致报错。random_state42固定切分方便复现。MAE 的单位是万元比如 MAE 30 表示平均预测偏差 30 万比 R² 更直观。4.2 随机森林参数怎么设为什么这么设线性回归跑通后换随机森林通常能明显提升。关键是三个参数n_estimators、max_depth、min_samples_leaf。from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import GridSearchCV rf RandomForestRegressor(random_state42) param_grid { n_estimators: [100, 200], max_depth: [10, 20, None], min_samples_leaf: [1, 3, 5], } grid GridSearchCV( rf, param_grid, cv5, scoringneg_mean_absolute_error, n_jobs-1 ) grid.fit(X_train, y_train) best grid.best_estimator_ pred_rf best.predict(X_test) print(最优参数:, grid.best_params_) print(MAE:, round(mean_absolute_error(y_test, pred_rf), 2)) print(R2:, round(r2_score(y_test, pred_rf), 4))n_estimators是树的数量100 到 200 通常够用再多收益递减但训练变慢。max_depth控制树深不限制容易过拟合限制太小又欠拟合所以放进网格搜索。min_samples_leaf是叶子节点最少样本数设大一点能防止模型记住噪声。cv5是五折交叉验证比单次切分更稳。n_jobs-1用满 CPU 核数不然网格搜索会很慢。如果数据量超过十万行网格搜索会非常耗时可以改用RandomizedSearchCV随机采样参数组合或者手动指定几组参数试。4.3 模型评估别只看 R²残差图更能暴露问题R² 高不代表模型可用。要画残差图看预测误差是否随机分布。import matplotlib.pyplot as plt residual y_test - pred_rf plt.figure(figsize(8, 5)) plt.scatter(pred_rf, residual, alpha0.5) plt.axhline(0, colorred, linestyle--) plt.xlabel(预测总价) plt.ylabel(残差) plt.title(残差分布) plt.savefig(residual.png, dpi150)如果残差呈现喇叭形预测值越大误差越大说明模型对高价房源拟合不好可能需要对数变换目标变量或者对高价样本单独建模。如果残差有系统性偏移说明特征里缺了重要变量。这一步很多人跳过直接拿 R² 写进论文答辩时被问「误差分布怎么样」就答不上来。5. 系统集成与避坑Flask 接口、Streamlit 面板和五个真实翻车点5.1 用 Flask 把模型包成接口模型训练完要能被系统调用。最简单的方式是用 Flask 暴露一个预测接口。from flask import Flask, request, jsonify import joblib import numpy as np app Flask(__name__) model joblib.load(rf_model.pkl) feature_names joblib.load(feature_names.pkl) app.route(/predict, methods[POST]) def predict(): data request.get_json() # 按训练时的特征顺序组装 row [data.get(f, 0) for f in feature_names] X np.array(row).reshape(1, -1) price model.predict(X)[0] return jsonify({total_price: round(float(price), 2)}) if __name__ __main__: app.run(host0.0.0.0, port5000)训练完模型后要用joblib.dump保存模型和特征名列表接口里按同样顺序组装输入顺序错了预测结果会完全离谱。data.get(f, 0)是兜底防止前端漏传字段导致报错。5.2 Streamlit 快速搭可视化面板如果不想写前端Streamlit 是最省事的方案。import streamlit as st import pandas as pd import joblib st.title(房屋信息可视化与价格预测) df pd.read_csv(house_clean.csv) model joblib.load(rf_model.pkl) features joblib.load(feature_names.pkl) st.subheader(区域均价) st.bar_chart(df.groupby(district)[unit_price].mean()) st.subheader(价格预测) area st.number_input(面积(平米), 30.0, 500.0, 90.0) rooms st.number_input(房间数, 1, 10, 3) age st.number_input(房龄(年), 0, 50, 5) if st.button(预测): row {f: 0 for f in features} row[area] area row[rooms] rooms row[house_age] age X pd.DataFrame([row])[features] price model.predict(X)[0] st.success(f预测总价{price:.1f} 万)st.bar_chart直接吃 pandas 的 groupby 结果不用手动转列表。输入控件用number_input默认值给一个合理中位数用户不填也能跑。pd.DataFrame([row])[features]保证列顺序和训练时一致这是接口和面板都容易翻车的地方。5.3 五个真实踩坑记录坑一中文乱码。现象是图表标题和区域名显示成方块。原因是 pyecharts 默认字体不含中文或者 CSV 读取时编码不对。解决读取时指定encodingutf-8或gbkpyecharts 在init_opts里设置ThemeType并确保 HTML 头部有 UTF-8 声明。坑二预测结果全是同一个值。现象是无论输入什么面积预测价格都一样。原因是特征组装时列顺序错了或者所有输入都被填成了 0。解决保存训练时的feature_names预测时严格按这个顺序组装并在接口里打印输入向量做核对。坑三R² 高得离谱。现象是 R² 0.99 以上。原因几乎都是标签泄漏比如把unit_price或total_price的衍生列放进了特征。解决检查特征列任何和标签有直接换算关系的字段一律剔除。坑四网格搜索跑了一小时没结果。现象是GridSearchCV卡住。原因是参数组合太多加数据量大。解决先用RandomizedSearchCV缩小范围或者先在小样本上试参数确定大致区间再全量跑。坑五Streamlit 页面每次操作都重新训练。现象是点按钮后等很久。原因是模型加载写在了脚本顶层但没有缓存。解决用st.cache_resource装饰模型加载函数保证只加载一次。6. 让预测更可信特征重要性分析和分区域建模的取舍模型跑通之后真正决定这个系统能不能拿得出手的是你能不能解释「为什么预测出这个价格」。随机森林自带feature_importances_但直接用会有偏差因为独热编码后的区域特征会把重要性分散到多个列上。我一般会先把独热列合并回原始区域再看整体重要性。import pandas as pd import numpy as np import joblib model joblib.load(rf_model.pkl) features joblib.load(feature_names.pkl) imp pd.Series(model.feature_importances_, indexfeatures) # 把 dist_ 开头的独热列合并为一个「区域」重要性 dist_imp imp[imp.index.str.startswith(dist_)].sum() imp imp[~imp.index.str.startswith(dist_)] imp[district] dist_imp imp imp.sort_values(ascendingFalse) print(imp.head(10).round(4))合并之后通常会发现面积、区域、房龄排前三这和业务直觉一致。如果某个不相关的字段排进前三就要回头检查数据里是不是混入了泄漏列。这个分析结果可以直接放进毕业设计论文的「实验结果」章节比只贴一个 R² 有说服力得多。另一个进阶技巧是分区域建模。如果各区域价格差异很大全局模型可能被高价区域主导低价区域预测偏差大。做法是按区域分组每个区域单独训一个模型预测时先判断区域再调对应模型。from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error results {} for dist, group in df.groupby(district): if len(group) 100: continue # 样本太少不单独建模 Xg group[features].select_dtypes(include[np.number]) yg group[total_price] Xtr, Xte, ytr, yte train_test_split(Xg, yg, test_size0.2, random_state42) m RandomForestRegressor(n_estimators100, random_state42) m.fit(Xtr, ytr) mae mean_absolute_error(yte, m.predict(Xte)) results[dist] round(mae, 2) print(results)分区域建模的代价是维护多个模型文件系统复杂度上升。判断标准很简单如果全局模型的残差按区域分组后差异明显就值得分如果各区域残差分布差不多就没必要。我自己的习惯是先跑全局画一张「区域-残差箱线图」差异大再拆。最后说一个验证技巧留出最近一年的数据做时间外推测试。很多人随机切分数据模型在测试集上表现好但实际用历史数据预测未来房价时会差很多。如果数据有时间字段按时间切分比随机切分更接近真实场景。这个细节在毕业设计里是加分项答辩老师一问「你怎么验证模型时效性」就能接住。我自己做这类系统最大的教训是不要等系统全部写完才第一次跑通预测。先把「读数据 → 训一个最简模型 → 输出一个预测值」这条最短路径跑通再往上加可视化和接口。否则很容易在调图表样式的时候发现模型特征列对不上回头返工。希望帮到你。本文还有配套的精品资源点击获取