ARTICLE DETAIL

资讯详情

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

Python二手房数据分析预测:从数据清洗到房价模型实战

Python二手房数据分析预测:从数据清洗到房价模型实战 简介Python二手房数据分析预测系统完整项目流程实战资源面向计算机专业正在准备课程设计、期末大作业的学生以及需要完整项目练手的数据分析学习者。项目为个人98分期末大作业成果已经严格调试、下载即可运行包含全部源码、数据与文档报告覆盖二手房数据清洗、特征分析与房价预测的完整流程。压缩包共156个文件、约40.03MB以Python源码、CSV数据集、HTML/JS前端页面和PNG可视化图表为主另含PPT报告、说明文档与少量字体/配置文件适合直接作为可运行的完整参考项目。已有831人学习读者既能结合原始版与清洗版多份CSV梳理数据分析思路也能复用前端界面与预测代码在毕业设计、课程设计或同类实战练习中快速搭建系统并支撑文档撰写。1. 二手房数据分析预测系统先把“能跑的代码”和“能信的数据”分清楚如果你在为期末大作业或课程设计找一套 Python 二手房数据分析预测系统别急着打开 .py 就跑先看看压缩包里的 CSV 到底有哪些。数据文件一多真正的门槛往往不是模型多玄而是编码错位、脏字段、以及你手里的数据能不能对上模型的输入格式。这套资源正好把原始 CSV 和清洗后 CSV 都放了进去是一份从数据处理一路做到房价预测的完整项目流程适合计算机相关专业学生赶课程设计也适合想系统跑一遍 python 数据分析与数据挖掘实战的开发者。这份资源的核心身份是一套已经调试过、下载即可运行的 Python 源码加配套文档报告。你不需要从零搭架构只需要把“编码修正→数据清洗→特征工程→模型对比”这条线走通再回答老师最常问的三句话数据怎么洗的价格特征怎么提的模型为什么选它。下面我就按这个顺序拆。2. 先算数据账多份 CSV 混着编码哪份能直接进模型打开压缩包先别急着跑train.py先在同一级目录下看文件清单。这份资源同时给了原始数据、分片数据和清洗后的数据表面上是重复文件实际上每种文件的用途不同。刚开始最容易犯的错是选了带origin的文件直接做建模最后发现列里全是NaN和乱码白白浪费一晚上。2.1 五份 CSV 的角色定位压缩包里几个 CSV 的命名规则本身就是一个版本管理的思路。我按我自己的使用习惯整理成一张表文件名定位建议用途ershoufang-origin-utf8.csv原始 UTF-8 数据最接近爬虫导出的一手数据适合做数据清洗演示ershoufang-origin-ansi.csv原始 ANSI 数据Windows 下用 Excel 另存常见编码专门用来演示乱码坑ershoufang - 20000.csv2 万条数据分片数据量大时先拿它调代码跑通再切全量ershoufang-clean-utf8-v1.1.csv清洗后 UTF-8 数据可直接进特征工程的底表ershoufang-clean-ansi-v1.1.csv清洗后 ANSI 数据给习惯 GBK 环境的同学复用第一遍跑项目我一般建议用ershoufang - 20000.csv做逻辑验证。因为数据集不大pandas 处理速度够快调参试错成本低确认无误后再切到clean-utf8-v1.1.csv出正式结果。这样写报告时能解释清楚“为什么先小后大”而不是一上来就甩一个几十万行的黑匣子。2.2 用 Python 快速核对编码与列结构拿到手先不要奢望直接读对。原始数据的编码来源不可控可能是爬虫脚本用 UTF-8 存的也可能是陈年 Excel 用 ANSI 转存的。我每次都会先写一个探针脚本把文件头和 pandas 的读取结果对比确认encoding到底该填哪个值。import pandas as pd path ershoufang-origin-utf8.csv # 第一次先看前 20 个字节判断有没有 BOM 头 with open(path, rb) as f: head f.read(20) print(二进制头:, head) df pd.read_csv(path, encodingutf-8-sig, enginepython) print(数据形状:, df.shape) print(列名:, df.columns.tolist()) print(df.head(3).T)这里有两个参数值得单独说明。第一是encodingutf-8-sig如果文件开头带 BOMutf-8会把\ufeff一起吞进第一个列名导致后面用列名取值时各种不匹配utf-8-sig会自动剥掉 BOM。第二是enginepython它比默认的 C 引擎慢但处理分隔符混乱、文件里有非法字符时容错更强。数据处理阶段我用 Python 引擎确认格式稳定后再换回默认引擎提速。如果读到一堆乱码或者识别失败就把encoding换成gbk或ansi再试。在实际项目里同目录既存在ori-utf8又存在ori-ansi原因基本就是有人用不同工具导了两次所以不要觉得两个文件内容一定相同。2.3 字段口径检查和缺失值分布编码解决后先检查对象列和数值列是否分对了。二手房的原始表里经常出现“总价”“面积”被读成 object因为某些行混入了“暂无”“面议”之类文本。这时候强行pd.to_numeric会报错如果不处理后面建模全部白做。print(df.dtypes) # 数值列疑似被读成 object 时用 errorscoerce 统一转换 for col in [总价, 面积, 单价]: if col in df.columns: converted pd.to_numeric(df[col], errorscoerce) df.loc[:, col] converted print(col, 转换后异常值数量:, converted.isna().sum())errorscoerce的作用是把无法解析的值变成NaN这样一眼就能看到异常比例。如果“总价”列变成NaN的比例超过 10%就要回源头看爬虫字段是不是错位而不是直接靠 fillna 补否则模型学到的全是你自己编的分布。拿到列类型后再看缺失值分布。我会用一句代码输出每个列的缺失率并排序这是整个项目流程里最值得留下来的过程证据miss_ratio df.isna().mean().sort_values(ascendingFalse) print(miss_ratio[miss_ratio 0.05])缺失率超过 40% 的列我会直接放弃而不是填充。因为在二手房数据里这种高缺失列往往是爬虫字段本身不稳定比如“建筑年代”大量缺失补众数反而造出虚假规律。缺失率在 5% 到 40% 之间的列才值得用中位数、众数或按小区分组填充。3. 数据清洗与特征工程把房价信号从噪声里提出来二手房数据最值钱的信息都在“区域、面积、户型、朝向、楼层、总价、单价”这几个字段里。但原始 CSV 里的文本格式五花八门户型写“3室2厅1卫”楼层写“低楼层/共18层”朝向甚至会出现“南向”“东南”混用。这一章要做两件事先把数据弄干净再把自然语言变成模型能吃的数字特征。3.1 先做去重、缺失值过滤和异常值收缩重复行很好理解直接用drop_duplicates()就能清掉。但真正的坑在于同一个房源被爬虫抓到多次特征里只是总价微调了几万块这种行不能按整行完全一致来判断重复得用业务键去重。常见做法是选择“小区户型面积朝向”组合键保留最后一次采集的记录。df df.copy() df df.drop_duplicates( subset[小区, 户型, 面积, 朝向], keeplast ) # 基础业务过滤面积 20 平以下或 500 平以上多为录入错误 df df[(df[面积] 20) (df[面积] 500)] df df[(df[总价] 0) (df[总价] 10000)]这里我用的是业务经验阈值而不是四分位数。有些课程设计会用zscore切异常值但二手房价格分布本身偏态很强一刀切会误杀很多真实的大户型房源。20 到 500 平米是我对住宅类数据的经验范围如果成交样本里有车库、商铺要单独过滤。清洗之后最好做一次“单价一致性检验”。这是文档报告里非常加分的细节# 总价单位通常是万面积单位是平米计算出理论单价再与表内单价比对 df[理论单价] df[总价] * 10000 / df[面积] df[单价偏差] (df[理论单价] - df[单价]).abs() / df[单价] print(df[单价偏差].describe())如果中位偏差超过 0.1说明“单价”字段不可信后续建模直接用“总价”和“面积”当特征别再单独塞一个矛盾特征进去。做这个检验一是防止数据录入错误二是在答辩时能很自信地说“我清洗的维度是按利益相关方口径来校验的”。3.2 文本字段的结构化提取户型字段是最典型的非结构文本。常见的写法是“3室2厅1卫”偶尔有“5室3厅3卫”甚至“4室2厅”。我用正则一次性拆出室、厅两个数字room_info df[户型].str.extract(r(?P室数\d)室.*?(?P厅数\d)厅) df[室数] pd.to_numeric(room_info[室数], errorscoerce) df[厅数] pd.to_numeric(room_info[厅数], errorscoerce)这里的正则写法值得解释一下(?P室数\d)是命名捕获组:pandas会把匹配结果直接变成列名.*?是非贪婪匹配用来跳过“室”和“厅”之间的其他字符。如果遇到“1室0厅”或者“开间”这类特殊房型errorscoerce会转成NaN后续模型会自动忽略缺失样本不拖累整体。朝向字段的合并思路更简单宁可少分几类也不要让模型去学“西南、东南、南北、北”这种细粒度组合。样本少时类别太多会让 one-hot 编码后出现稀疏矩阵模型训练不稳定。我一般只保留三类def unify_orientation(s): if pd.isna(s): return 未知 s str(s) if 南 in s and 北 in s: return 南北 if 南 in s: return 南 if 北 in s: return 北 return 其他 df[朝向分组] df[朝向].apply(unify_orientation) print(df[朝向分组].value_counts())南北通透的房子在成交价上一般有溢价这个分桶能保住最重要的信息又不会把学习难度拉高。数据量够大的场景下可以保留“东南”“西南”等中高频类别但课程设计用三类就够了。3.3 相关性粗筛和特征矩阵成型特征工程不是越多越好尤其对线性模型来说高度相关的特征会造成多重共线性。清洗后先跑一遍相关性矩阵观察每个数值特征与“单价”的关系再决定进不进模型num_cols [面积, 室数, 厅数, 总价, 单价] corr_matrix df[num_cols].corr() print(corr_matrix[单价].sort_values(ascendingFalse))一般二手房数据里“总价”和“单价”的相关系数会非常高但它俩本质是同一个价格信号的两面。预测单价时把“总价”存在特征矩阵里看似合理实际会让模型作弊因为测试集已经知道了总价总价的计算公式里包含单价。我会把“总价”排除在预测单价的模型之外只保留面积、室数、厅数、朝向分组等基础特征让模型真正靠房源物理属性做预测。这一步是很多课程设计翻车的地方。老师如果看到你的R²高达 0.98马上会追问特征里是不是混进了目标变量的线性组合。在线下项目里这叫标签泄露在作业答辩里叫“模型结果不可解释”。4. 预测模型落地线性回归和随机森林的对比实验数据清洗做扎实之后模型部分反而可以反着写先用最傻的线性回归跑一遍拿到一个差的基准分数再用随机森林去追整套项目流程立刻显得有说服力。上来就调 XGBoost 不是不可以但打分时缺少对照老师会怀疑你是记了个现成答案。4.1 准备特征矩阵和划分训练集把清洗好的表切出特征和标签。这里要注意分类特征不能直接丢给 sklearn先做 one-hot 编码数值特征保持原始单位最终用train_test_split随机划分一套训练测试集。import pandas as pd from sklearn.model_selection import train_test_split df pd.read_csv(ershoufang-clean-utf8-v1.1.csv) # 假设已经做了特征工程并保留以下字段 feature_cols [面积, 室数, 厅数] orientation_dummies pd.get_dummies(df[朝向分组], prefix朝向) X pd.concat([df[feature_cols], orientation_dummies], axis1) y df[单价] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 )random_state42不是固定写法它的作用是让随机划分可复现。答辩时老师如果让你重新跑一遍你不会因为随机种子不同而得到两套差异巨大的指标。训练集和测试集的比例用 8:2 是常规操作如果数据量很大也可以改成 9:1但课程设计项目用 8:2 更稳因为测试集样本更多指标波动更小。4.2 线性回归基线先看误差数量级线性回归最大的价值不是拿它做最终预测而是给你一个“误差应该是什么量级”的参照。如果线性回归的 MAE 已经很小说明价格模式和面积强线性相关这反而不太符合真实二手房的市场特征。from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score model_lr LinearRegression() model_lr.fit(X_train, y_train) pred_lr model_lr.predict(X_test) print(MAE:, mean_absolute_error(y_test, pred_lr)) print(RMSE:, mean_squared_error(y_test, pred_lr) ** 0.5) print(R2:, r2_score(y_test, pred_lr))MAE 是平均绝对误差直接翻译成人话就是“平均每平米预测误差多少钱”RMSE 因为把误差平方后再开方会把个别离谱样本放得更大可以用来检测尖峰噪声。实际跑二手房项目MAE 往往在几千块一平的范围RMSE 会明显高于 MAE说明数据里有少数极端成交拉低了整体精度。这是正常现象在报告里如实写就行不要为了好看硬删。4.3 随机森林微调从 n_estimators 到 max_depth随机森林不要求特征线性关系对异常值的容忍度也更高。但参数一多就有人瞎调我的建议是先固定两个最影响结果的参数别一上来就网格搜索一堆无用项。from sklearn.ensemble import RandomForestRegressor model_rf RandomForestRegressor( n_estimators200, max_depth10, min_samples_leaf5, random_state42, n_jobs-1 ) model_rf.fit(X_train, y_train) pred_rf model_rf.predict(X_test) print(RF MAE:, mean_absolute_error(y_test, pred_rf)) print(RF RMSE:, mean_squared_error(y_test, pred_rf) ** 0.5) print(RF R2:, r2_score(y_test, pred_rf))n_estimators200表示生成 200 棵决策树树越多越稳定但超过一定数量后收益递减max_depth10限制每棵树的深度防止单棵树把训练集背下来min_samples_leaf5强制叶子节点至少 5 个样本降低过拟合。这里明显能看到的对比效果是随机森林的R²通常会比线性回归高一截但MAE未必赢很多因为随机森林对高价房和低价房的预测都偏向中位数区域。跑完两个模型后如果时间允许可以再画一张真实值和预测值的散点图横轴是真实单价纵轴是预测单价理想情况下点都落在 yx 对角线上。如果随机森林的对角线聚成一团而线性回归的点散成一片报告里放这张图就比放一百个参数表更有说服力。5. 项目排错与避坑这些常见问题我替你试过一遍这章写的全是跑这套资源最容易踩的坑。每一条我都按“现象、原因、解决”的顺序整理你遇到问题时直接对号入座就行。5.1 用 UTF-8 读 ANSI 文件小区名满屏“锟斤拷”现象read_csv跑完不报错但打印head()时小区名全是乱码看起来像“锟斤拷”之类的重复字符。原因文件本身是 ANSIGBK编码却用encodingutf-8去解码。GBK 字节流在某些 UTF-8 解码器下不一定触发异常而是被强行映射成一堆替代字符过程吃哑巴亏。解决不要靠报错判断编码直接用chardet或二进制头探测。更快的做法是同一份数据交叉试utf-8-sig、gbk、ansi。在正式写代码前先用 2.2 节那个探针脚本读前 20 字节确定encoding参数后再进主流程。我自己的习惯是统一把所有 CSV 转成 UTF-8 存一份后续pandas全程用同一套编码从源头上消灭乱码。5.2 重复行去重后价格记录反而少了现象drop_duplicates()之后行数从 2 万降到 1.2 万可抽查发现有些小区同户型只保留了一条记录价格还是最老的那条。原因drop_duplicates()默认对所有列判断重复如果爬虫在不同时间抓到同一房源但“单价”字段被更新过那么整行并不完全重复去重逻辑拦不住它。反过来如果用户用subset[小区, 户型, 面积, 朝向]去重确实会删掉多个价格版本但keep参数没设对留下的是旧价格。解决去重时要按业务键加时间排序。若数据里有“抓取时间”或“更新时间”字段先按该字段排序再drop_duplicates(subset..., keeplast)。如果没有任何时间字段就保留单价最小或最大的版本并在报告里注明口径。最怕的是什么都不写去重逻辑变成黑匣子答辩老师一问就露馅。5.3 对缺失值统一 fillna建模效果反而更差现象“建筑年代”“楼层数”这类列缺失不少我用该列中位数填充后随机森林的R²明显下降。原因填充本身不是问题问题是中位数把大量缺失值堆成一个平滑值模型会把这个平滑值当成一种真实规律。比如“建筑年代”缺失的房源集中在老小区你用整体中位数填充等于把老房子全部伪装成中年房反而抹掉了区域差异。解决先看缺失值比例再决定策略。超过 40% 的直接删列5% 到 40% 的按小区或区域分组填充因为同一个板块的建筑年代分布更接近低于 5% 的可以直接用中位数。更稳妥的做法是把“是否缺失”作为一个 0/1 特征单独加进去让模型自己学缺失模式。我在二手房项目里常用这个办法效果明显好于无脑 fillna。5.4 文件名里的空格让read_csv找不到文件现象明明文件在目录里但pd.read_csv(ershoufang - 20000.csv)报FileNotFoundError。仔细看文件名的中间和末尾有空格路径复制得一模一样还是找不到。原因这份资源里的文件名“ershoufang - 20000.csv”在“-”前后带空格。很多人的代码编辑器或终端会自动折叠连续空格复制路径时可能丢失还有一些脚本对路径做strip()也会把末尾空格切掉。解决在项目代码里先定义文件路径变量不要靠肉眼复制。用os.listdir()打印目录下所有文件名再把实际字符串赋给变量。如果文件名确实带空格就写成ershoufang - 20000.csv原样引用如果嫌麻烦直接在脚本里os.rename()把空格去掉重命名为规范文件后再做后续处理。5.5 模型分数高得不正常先查特征是不是“吃了未来数据”现象线性回归在测试集上R²接近 0.98训练集和测试集都准得离谱但换一批新数据就崩盘。原因最常见的是特征矩阵里混入了“总价”字段或者你预测“单价”时把“总价”作为特征。总价 单价 × 面积里面本身就包含目标变量的未来信息。另一种可能是测试集来自同一时间段和训练集样本高度重复等于模型已经背过答案。解决建特征列前检查所有特征里有没有目标变量的算术组合。预测单价就不放总价预测总价就不放单价。同时确认train_test_split是随机切分的而不是按小区名称排序切分。更严格的做法是按时间前 80% 做训练、后 20% 做测试模拟真实预测场景。这一条是你报告里最能体现工程经验的地方写进去就是加分项。6. 从结果到报告让答辩时的每一步都能复现跑完模型不算完期末大作业或课程设计最终交的是“源码 报告 过程证据”。很多同学最后只有一张结果截图老师一问“中间这份数据是怎么来的”答不上来。我的做法是把整个项目流程拆成几个独立脚本再写一个入口串起来每个脚本落盘一份结果文件。具体到这份资源我会分成01_explore.py、02_clean.py、03_feature.py、04_model.py四个脚本每个脚本只做一件事且把中间产物保存到output/目录。这样答辩演示时不用重跑全流程只要展示文件生成时间老师就知道每一步都是真实跑出来的。图表保存也是一样先设置中文字体再输出import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False pred_df pd.DataFrame({实际单价: y_test, 预测单价: pred_rf}) pred_df.plot.scatter(x实际单价, y预测单价, alpha0.4) plt.plot([0, pred_df.max().max()], [0, pred_df.max().max()], r--) plt.xlabel(实际单价) plt.ylabel(预测单价) plt.title(随机森林预测对比) plt.savefig(output/predict_compare.png, dpi150, bbox_inchestight)这两行rcParams是中文绘图的后悔药第一行指定 Sans Serif 字体第二行解决负号显示成方块的问题。不设置的话图上所有中文都会变成回字框答辩时极其尴尬。dpi150是为论文或报告截图的清晰度准备的bbox_inchestight会自动裁掉图表边缘空白让图片大小更紧凑。报告里不要写“我用了随机森林所以效果很好”要写“我先做基线模型再逐步加特征、换模型最终确定为什么选它”。这份资源的 CSV 文件名里已经带了v1.1版本号说明它天然适合做版本迭代实验。你可以在报告里留一张表格对比原始数据行数、清洗后行数、特征数量、训练集大小这些数字让整份报告显得是亲手跑出来的而不是从网上下个结论充数。从那以后我无论做哪一类的数据分析课程设计都强制自己走一遍“原始数据→清洗→特征→模型→复现脚本”的完整流程每一步都生成中间产物和版本号。结果就是答辩时不用背稿因为每张图、每个指标都能当场重新生成。希望帮到你。本文还有配套的精品资源点击获取
返回列表