
简介面向计算机相关专业学生与数据分析初学者的Python二手房数据分析项目资料整套资源针对课程大作业、毕业设计及实战练习场景设计覆盖数据采集、清洗、可视化与建模分析全流程。包内共有114个文件压缩包约29.31MB主要包含Python脚本py实现核心分析逻辑多份csv格式的原始与清洗后数据集png可视化结果图html/js构成的交互展示页面以及txt/md说明文档和pptx讲解PPT便于对照代码、文档和演示文稿系统学习。项目已通过导师审核并获98分高分评价源码经本地调试可运行配套解析文档对每个步骤的目的、实现方法与常见问题解决方案做了详细说明讲解PPT则以图文并茂方式梳理数据分析整体思路帮助学习者从数据读取、特征处理到可视化输出逐步掌握完整项目流程。目前已有42人学习使用适合具备一定Python基础的学生作为期末大作业或毕业设计的可靠参考也可用于提升真实数据场景下的实战能力。1. 二手房数据分析项目从数据到答辩的一次完整闭环课程设计或毕业设计里Python 数据分析永远是最稳妥的选题但能拿到高分的并不多。多数人卡在同一个地方代码能跑、图能出答辩时却说不清每一步为什么这么做。二手房分析恰好是数据语义清晰、又带真实业务味道的场景——总价、单价、面积、户型、朝向、区域每一列都对应普通人买房的一个决策点。这篇文章按一条可复现的流程走数据获取与清洗、探索性可视化、特征工程与回归建模最后落到 PPT 讲解如何把这四步串成让老师挑不出毛病的叙事线。适合正在做课程设计、毕业设计或想用一份完整 python 数据分析项目练手的人pandas、seaborn、scikit-learn 三个库足够。2. 数据准备房源字段设计与 pandas 清洗的完整代码2.1 数据来源两条路线爬虫采集与本地数据集常见做法是爬取链家或贝壳的公开挂牌页用 requests 加 BeautifulSoup 就能拿到。但写爬虫在答辩里只是获取数据的手段分数权重很低真正拉开差距的是拿到数据之后做了什么。如果你的目标是把项目跑完整直接用现成的二手房 CSV 数据集更省时间网上很多免费 python 源码大全里都有类似的北京、上海房源数据。我一般建议数据集大于 3000 条、字段不少于 8 列就够用重点是必须有总价、单价、面积、区域、户型、朝向这几项。下面是一份典型的字段口径表做爬虫解析时直接按这个对齐能省掉大量后续返工字段名原始格式示例清洗后类型说明region朝阳区str挂牌所在行政区做分组对比的主键layout3室1厅str户型建模时拆出室和厅size89.5平米float建筑面积单位统一为平米floor低楼层/共18层str楼层区间加总层数可拆出两层信息toward南北str朝向低频值合并为其他decoration精装str装修程度存在缺失total_price620万float挂牌总价单位统一为万元unit_price69273元/平float挂牌单价建模的核心目标写爬虫时最典型的坑是低楼层/共18层这类复合字段直接用正则(\d)层把总层数提出来低/中/高楼层单独映射成 0/1/2。单价字段里单位混着元/平和万元/平一旦混进来后面建模的 RMSE 会大得离谱。清洗前先打印df.dtypes确认每一列有没有被 pandas 解析成预期类型这是最容易被跳过的调试步骤——字符串型数值列参与计算时报错往往出在建模阶段回头查数据要浪费一倍时间。2.2 用 pandas 完成清洗的最小完整代码import pandas as pd import numpy as np import re df pd.read_csv(ershoufang.csv, encodingutf-8-sig) print(df.shape, df.dtypes) # 先看形状和类型确认列有没有解析错 # 1. 按关键列去重同一套房源可能被抓了两遍 df df.drop_duplicates(subset[region, size, layout, total_price]) # 2. 去掉价格和面积为空的行装修缺失保留 df df.dropna(subset[total_price, unit_price, size]) # 3. 把620万变成数值这里总价单位统一为万 def parse_price(x): if isinstance(x, str): num re.search(r([\d.])万, x) return float(num.group(1)) if num else np.nan return x df[total_price] df[total_price].map(parse_price) # 4. 面积字段提取数字并过滤掉异常区间 df[size] df[size].str.replace(平米, ).astype(float) df df[(df[size] 30) (df[size] 300)] # 5. 单价单位统一防止元/平和万元/平混用 def parse_unit(x): x str(x) if 万 in x: return float(re.search(r([\d.]), x).group(1)) * 10000 return float(re.search(r([\d.]), x).group(1)) df[unit_price] df[unit_price].map(parse_unit) # 6. 楼层字段拆分出楼层区间和总层数 floor_map {低: 0, 中: 1, 高: 2} df[floor_level] df[floor].str.extract(r([低中高]))[0].map(floor_map) df[total_floors] df[floor].str.extract(r共(\d)层)[0].astype(float) # 7. 户型字段拆出室和厅两个数值 df[rooms] df[layout].str.extract(r(\d)室)[0].astype(float) df[halls] df[layout].str.extract(r(\d)厅)[0].astype(float) df df.dropna(subset[rooms, floor_level]) print(df.isnull().sum()) # 清洗完再核对一次缺失值 df.to_csv(ershoufang_clean.csv, indexFalse, encodingutf-8-sig)这段代码的每一步都有对应的业务理由。drop_duplicates(subset...)用四列联合判断重复单用一列会误删面积相同但楼栋不同的房源。dropna(subset...)只删关键列缺失的行保留装修信息缺失但价格面积齐全的记录后面建模时还能用。面积过滤在 30 到 300 平之间是为了把车位、地下室和别墅这类极端值挡在门外避免回归模型被个别样本带偏。楼层拆成floor_level和total_floors两个特征是有意的floor_level是类别语义total_floors是连续语义模型可以自己决定用哪一个而不是替它做死。文件导出用utf-8-sig而不是utf-8这样在 Excel 里打开不会乱码答辩现场展示数据时能少一个尴尬点。2.3 清洗结果的验收标准和三个高频坑清洗做没做干净别用看着还行判断。我会打印每个数值列的describe()重点看 min、max、mean 三个值是否符合常识单价 min 低于 5000 或 max 高于 20 万几乎可以断定单位没统一或混进了商铺数据。total_floors出现 0 或 99说明正则没匹配上需要回头检查原始字符串格式。另一个高频坑是 CSV 编码问题爬虫存文件时用utf-8-sig否则中文列名在 Excel 里打开是乱码。第三个坑是房源里混入车位和商铺判断依据通常是面积小于 20 平或者单价异常低这类记录在清洗阶段就该剔除而不是留给模型去学习。3. 探索性数据分析让图表自己讲出房价规律3.1 单价分布的可视化与偏态处理数据清洗完先别急着建模第一步永远是看单变量的分布形态。用 seaborn 画单价直方图绝大多数城市的二手房单价都会呈现明显的右偏——低单价区间一堆房高单价尾巴拖得很长。log 变换之后近似正态这个观察在答辩里是加分项说明你理解为什么后面建模要用 log1p 做目标变换。import matplotlib.pyplot as plt import seaborn as sns plt.rcParams[font.sans-serif] [SimHei] # 解决中文乱码 plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(12, 4)) sns.histplot(df[unit_price], bins50, kdeTrue, axaxes[0]) axes[0].set_title(单价原始分布) df[unit_price_log] np.log1p(df[unit_price]) sns.histplot(df[unit_price_log], bins50, kdeTrue, axaxes[1]) axes[1].set_title(单价 log 变换后分布) plt.tight_layout() plt.savefig(price_dist.png, dpi150)中文字体配置这段不能省Windows 下 SimHei 通常可用Mac 或 Linux 上要换成[PingFang SC]或[WenQuanYi Zen Hei]否则图上全是方块。np.log1p用1 x避免价格为 0 时取对数得负无穷。subplots 左右对比的画法很适合答辩放一张变换前后对比比放十张直方图有说服力。dpi150是给 PPT 用的最低标准低于这个值投影出来边缘发虚。3.2 区域与户型的分组对比用箱线图代替文字fig, axes plt.subplots(1, 2, figsize(14, 5)) # 各区域单价箱线图按中位数排序 region_order (df.groupby(region)[unit_price] .median().sort_values(ascendingFalse).index) sns.boxplot(datadf, xregion, yunit_price, orderregion_order, axaxes[0]) axes[0].set_xticklabels(axes[0].get_xticklabels(), rotation45) # 户型室数对比 sns.boxplot(datadf, xrooms, yunit_price, axaxes[1]) plt.savefig(region_box.png, dpi150)boxplot 的 order 参数按中位数排序让图从左到右自然形成贵到便宜的视觉层级比默认按字母排更容易让老师一眼看出结论。如果区域超过 15 个我会先取中位数 top10 再画避免横轴标签挤成一团。朝向字段不要直接画八个类把东西、西北这些低频值合并成其他否则箱线图里会出现大量空箱。箱线图里的点和须要能讲清楚盒子是中位数和四分位距须以外的是离群房源这部分可以自然引出哪些因素让个别房源贵出这么多的建模动机。3.3 相关性矩阵找到价格的真实驱动因子corr_cols [unit_price, total_price, size, rooms, halls, total_floors, floor_level] fig, ax plt.subplots(figsize(8, 6)) sns.heatmap(df[corr_cols].corr(), annotTrue, fmt.2f, cmapRdYlBu_r, axax) plt.savefig(corr_heatmap.png, dpi150)注意结论的表述顺序。和单价相关性最高的往往是 size但方向是负的——面积越大单价越低这是总价约束下的一种结构性现象而不是房子越大越不值钱。这个点拿出来讲能明显区别于只会复述相关性高/低的作业。区域、朝向是类别变量进不了这个相关矩阵正确做法是回到箱线图用组间中位数差去表述。相关性矩阵只放数值型字段混入 one-hot 编码后的区域列会把图撑得没法看。fmt.2f让热力图里只显示两位小数图面干净投影出来也清晰。4. 特征工程与回归建模把结论变成可解释的数字4.1 类别特征编码与数据划分建模目标选 unit_price 而不是 total_price理由很直接总价 单价 × 面积如果预测总价模型只要学会乘面积就够了单价才真正体现房屋各属性的权重。特征工程做四步region 按 top10 之外合并为其他再做 one-hottoward 低频合并后同样 one-hotrooms 和 halls 保留数值size 和 total_floors 标准化。from sklearn.model_selection import train_test_split from sklearn.preprocessing import OneHotEncoder, StandardScaler # 低频区域合并避免 one-hot 后维度爆炸 top_regions df[region].value_counts().head(10).index df[region2] df[region].where(df[region].isin(top_regions), 其他) num_cols [size, total_floors, floor_level, rooms, halls] cat_cols [region2, toward, decoration] # 数值列标准化 scaler StandardScaler() X_num pd.DataFrame(scaler.fit_transform(df[num_cols]), columnsnum_cols, indexdf.index) # 类别列 one-hotdrop_first 去掉各自的第一列 X_cat pd.get_dummies(df[cat_cols], drop_firstTrue) X pd.concat([X_num, X_cat], axis1) y df[unit_price_log] # 用 log 后的单价做目标 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) print(X_train.shape, X_test.shape)drop_firstTrue去掉每个类别字段的第一列避免哑变量共线性这在线性回归的系数解读里尤其重要。区域 one-hot 之后字段数等于 top10 加其他再减一也就是 10 列左右维度完全可控。标准化只 fit 在训练集上测试集用同一个 scaler 做 transform这是防止数据泄露的基本要求——如果对全量数据 fit测试集的信息就提前进了模型。随机种子固定成 42 是惯例答辩被问到时可以说固定随机种子是为了实验可复现这个回答比随便设的体面得多。4.2 线性回归与随机森林的对比训练from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score models { LinearRegression: LinearRegression(), RandomForest: RandomForestRegressor( n_estimators300, max_depth12, random_state42, n_jobs-1 ), } results {} for name, model in models.items(): model.fit(X_train, y_train) pred_log model.predict(X_test) # 还原到原始单价口径RMSE 才具有元/平的物理意义 pred np.expm1(pred_log) y_true np.expm1(y_test) results[name] { MAE: mean_absolute_error(y_true, pred), RMSE: np.sqrt(mean_squared_error(y_true, pred)), R2: r2_score(y_true, pred), } print(name, results[name]) # 特征重要性只在树模型上有意义 rf models[RandomForest] importance pd.Series(rf.feature_importances_, indexX.columns).sort_values(ascendingFalse) print(importance.head(10))评估顺序有个关键细节模型在 log 空间训练预测出来也是 log 值必须np.expm1还原后再算 RMSE单位才是元/平。直接在 log 空间算 RMSE数值会小到零点几看起来很好但没法向老师解释物理意义。随机森林的n_estimators300、max_depth12是对几千条数据足够收敛又不至于过拟合的组合n_jobs-1让所有 CPU 核参与训练数据量大时速度差异明显。线性回归不需要调参如果它的 R² 异常高先回头查是不是把 total_price 或 size 的倍数关系漏进特征里了这是过拟合最常见的伪装。4.3 模型效果解读的参数与表述模型MAE元/平RMSE元/平R²线性回归约 6200约 9500约 0.55随机森林约 5100约 7800约 0.68表格里的数值是给你对照量级的参考不同城市差异很大别拿它当标准答案。结果的表述分三层第一层说 MAE 的物理意义预测单价平均偏差 5000 元左右在单价 5 万的市场里约 10% 的误差第二层说随机森林比线性回归好说明特征之间存在非线性关系和交互作用第三层看特征重要性 top3通常是 size、区域、floor_level这个结论要和第 3 章的可视化对得上形成闭环。特征重要性的排序比具体数值更值得讲因为它是相对的。如果答辩老师追问为什么 size 最重要回看相关性矩阵里 size 与 unit_price 的负相关用总价约束下的面积折扣来解释逻辑就完整了。5. PPT讲解把代码过程改写成数据叙事PPT 是拿分的关键环节。项目做得再完整讲不清楚等于白做。固定做法是 PPT 只放 8 到 10 页按问题—数据—发现—模型—结论五段走。第 1 页直接抛出问题挂牌价到底由什么决定面积和区域谁的影响更大 第 2 页放字段口径表和清洗前后的行数对比清洗前后行数变化必须截图而不是现场跑。第 3 到 5 页放第 3 章的直方图、箱线图和相关性热力图每张图配一句话结论字要少。第 6 页放模型对比表和特征重要性条形图第 7 页收束到单价由什么决定的最终答案呼应开头。图表排版有个硬性经验PPT 里文字不要小于 18 号图不要小于半页。答辩现场老师坐最后一排图缩在一角、字小到看不清是最常见的失分点。每页只讲一个结论一页塞三张图等于三张都没讲。配色别用默认的蓝底白字白底深色字投影效果最稳定。答辩被问得最多的是三个问题提前准备答案。第一数据从哪来的覆盖什么时间段——如实回答来源说明采集或下载时间再补一句这是挂牌价而非成交价反映的是供给方定价逻辑。第二为什么预测单价而不是总价——用总价等于单价乘面积一句话回答强调单价才能体现房屋属性权重。第三模型误差能接受吗——不要辩解承认误差存在说明 MAE 在真实市场中的量级再补一句如果加入地铁距离、楼龄等特征误差还会下降这是给后续工作留口子。还有一个容易被忽略的技巧答辩前把关键数字记在本子上比如从 4200 行清洗到 3856 行随机森林 max_depth12log 变换后的 RMSE 还原为 7800 元/平。老师问细节时你能说出具体数字而不用翻代码整个项目的可信度立刻不一样。同时把控制台输出和 error 信息截图准备两张备用现场演示代码万一报错直接切截图讲别在台上调 bug也不要在演示时现跑完整的训练流程把时间留给结论和图表解释。本文还有配套的精品资源点击获取