尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

二手房价格预测全流程实战:从乱码CSV到可交付报告

二手房价格预测全流程实战:从乱码CSV到可交付报告 简介这是一套面向计算机专业本科生的二手房数据分析与价格预测实战项目专为课程设计、期末大作业及Python数据科学入门实践打造。资源完整覆盖数据采集、清洗、探索性分析、特征工程、模型训练含线性回归、随机森林等、可视化展示及报告撰写全流程助学习者系统掌握真实业务场景下的数据分析闭环能力。压缩包共156个文件含18个核心Python脚本实现数据处理与建模逻辑、18个CSV数据集如ershoufang-clean-utf8-v1.1.csv等多版本清洗数据、65张分析图表PNG、15个HTML交互式报告页及配套JS前端组件整体40.03MB结构清晰、模块解耦支持开箱即用。已有830人学习下载所有代码均经严格调试附带完整文档报告与可直接运行环境配置说明显著降低部署门槛是少有的兼顾教学规范性与工程可用性的高质量教学级项目源码。1. 用真实二手房数据跑通一个端到端预测流程不是调包演示而是可交付的课程设计级项目你手头有一份带价格、面积、楼层、朝向、装修、建成年份等字段的二手房原始 CSV 文件但打开后发现乱码、空值扎堆、文本混数字、区域名称不统一——这不是数据清洗的起点而是很多同学交期末作业时卡死的第一关。这个项目不是教你怎么画个折线图就收工的“数据分析入门案例”而是一个从原始编码混乱的ershoufang-origin-ansi.csv开始经历编码修复、缺失值策略选择、特征工程建模、模型对比验证、结果可视化与报告生成的完整闭环。它专为计算机/信管/统计类专业学生设计所有代码在 Python 3.8 环境下实测通过依赖明确pandas 1.5.3、scikit-learn 1.2.2、lightgbm 3.3.5文档含数据字典、每步输出样例、常见报错及修复方案。如果你正被“课程设计要交系统报告答辩PPT”压得喘不过气这个包里main.py运行后自动生成prediction_report.html和model_evaluation.xlsx直接复制进答辩材料即可——它不追求 SOTA 指标但每一步都经得起老师问“你为什么用 Random Forest 而不是 XGBoost”。2. 从 ANSI 编码乱码到 UTF-8 清洗数据解决原始 CSV 的三重编码陷阱2.1 识别并定位真实编码类型避免盲目encodingutf-8报错原始文件ershoufang-origin-ansi.csv在 Windows 记事本中显示正常但在 pandas 中读取时报UnicodeDecodeError: utf-8 codec cant decode byte 0xd6 in position 0。这不是文件损坏而是典型的 GBK/GB2312 编码被误判为 UTF-8。正确做法是先用chardet探测真实编码import chardet def detect_encoding(file_path): with open(file_path, rb) as f: raw_data f.read(10000) # 只读前10KB提高速度 result chardet.detect(raw_data) return result[encoding] print(detect_encoding(ershoufang-origin-ansi.csv)) # 输出GB2312提示chardet探测结果是概率值若返回None或置信度0.7需手动验证。此处GB2312是 Windows 简体中文默认编码与cp936Python 别名等价实际读取时应使用encodinggbk兼容性更好。2.2 分步清洗处理混合编码、列名中文乱码、数值型字段中的非数字字符原始 CSV 存在三类典型问题1部分行用 UTF-8 编码写入其余用 GBK2列名如小区名称在某些行变成小区名称3总价字段含320万、约285万、面议等文本。清洗必须分阶段进行不可一步到位import pandas as pd import re # 第一步以 gbk 编码读取容忍错误并记录异常行 df_raw pd.read_csv(ershoufang-origin-ansi.csv, encodinggbk, on_bad_lineswarn, # Python 1.4 支持旧版用 error_handlingskip dtypestr) # 全部按字符串读入避免自动类型转换污染 # 第二步修复列名乱码针对已知的 UTF-8 误解码 bad_col_names [小区名称, 户型, 总价] correct_col_names [小区名称, 户型, 总价] for bad, good in zip(bad_col_names, correct_col_names): if bad in df_raw.columns: df_raw.rename(columns{bad: good}, inplaceTrue) # 第三步清洗总价字段——提取数字并转为万元单位 def clean_price(text): if pd.isna(text) or not isinstance(text, str): return None # 匹配数字万/元如320万→3202850000元→285 match re.search(r(\d\.?\d*)[万|元], text) if match: val float(match.group(1)) return val if 万 in text else val / 10000 # 处理面议、待定等 if any(kw in text for kw in [面议, 待定, 协商]): return None return None df_raw[总价_万元] df_raw[总价].apply(clean_price)2.2.1 为什么不用encodinggbk直接读取全部列因为ershoufang-origin-ansi.csv实际是混合编码前1000行用 GBK后续插入的几行用 UTF-8。pandas.read_csv()无法动态切换编码强行指定会中断或丢行。上述dtypestron_bad_lineswarn方案能保留所有行再用正则和字符串操作逐字段清洗虽多写20行代码但保证数据完整性——这是课程设计答辩时老师最看重的“数据可信度”。2.3 构建标准化清洗流水线cleaner.py模块化封装将清洗逻辑封装为可复用模块避免在main.py中堆砌重复代码。关键设计点输入支持.csv和.xlsx适配不同来源数据输出固定为ershoufang-clean-utf8-v1.1.csvUTF-8 编码无 BOM自动记录清洗日志删除行数、填充策略、异常字段# cleaner.py import pandas as pd import numpy as np from pathlib import Path class DataCleaner: def __init__(self, input_path: str): self.input_path Path(input_path) self.df None def load_and_detect(self): # 尝试多种编码优先 gbk失败则 utf-8 encodings [gbk, utf-8-sig, latin-1] for enc in encodings: try: self.df pd.read_csv(self.input_path, encodingenc, dtypestr) print(fSuccess with encoding: {enc}) break except UnicodeDecodeError: continue if self.df is None: raise ValueError(All encodings failed) def standardize_columns(self): # 统一列名映射表来自 data_dict.json col_mapping { 小区名称: community, 户型: layout, 总价: total_price, 单价: unit_price, 面积: area, 楼层: floor, 朝向: orientation, 装修: decoration, 建成年份: build_year } self.df.columns [col_mapping.get(col.strip(), col.strip()) for col in self.df.columns] def clean_numeric_fields(self): # 对数值字段执行强类型转换失败则设为 NaN numeric_cols [total_price, unit_price, area, build_year] for col in numeric_cols: if col in self.df.columns: self.df[col] pd.to_numeric(self.df[col], errorscoerce) def save_cleaned(self, output_path: str None): if output_path is None: output_path self.input_path.parent / f{self.input_path.stem}-clean-utf8-v1.1.csv self.df.to_csv(output_path, indexFalse, encodingutf-8-sig) print(fCleaned data saved to {output_path}) # 使用示例 cleaner DataCleaner(ershoufang-origin-ansi.csv) cleaner.load_and_detect() cleaner.standardize_columns() cleaner.clean_numeric_fields() cleaner.save_cleaned()注意encodingutf-8-sig用于写入时避免 Excel 打开乱码它会在文件开头添加 BOM 标记而纯utf-8在 Windows Excel 中可能显示为乱码。这是课程设计交付物必须考虑的细节。3. 特征工程与模型训练从原始字段到可解释预测的关键转化3.1 基于业务逻辑构造高价值衍生特征二手房价格受“硬指标”面积、楼层和“软指标”学区、地铁、商圈共同影响。本项目不依赖外部 API 获取地理信息而是从已有字段挖掘隐含价值# feature_engineer.py import pandas as pd import numpy as np def create_features(df: pd.DataFrame) - pd.DataFrame: df_new df.copy() # 1. 楼层分级底层1-2F、中层3-12F、高层13F def floor_level(floor_str): if pd.isna(floor_str): return unknown # 提取数字如15/32层→15低楼层→1 nums re.findall(r\d, str(floor_str)) if nums: floor_num int(nums[0]) if floor_num 2: return low elif floor_num 12: return mid else: return high return unknown df_new[floor_level] df_new[floor].apply(floor_level) # 2. 房龄计算2023年为基准年 current_year 2023 df_new[age] current_year - df_new[build_year].fillna(df_new[build_year].median()) # 3. 性价比指标单价 vs 同小区均价分组聚合 community_mean_price df_new.groupby(community)[unit_price].transform(mean) df_new[price_ratio_to_community] df_new[unit_price] / community_mean_price # 4. 面积段标签小户型60㎡、标准60-120㎡、大户型120㎡ df_new[area_segment] pd.cut(df_new[area], bins[0, 60, 120, np.inf], labels[small, medium, large]) return df_new # 应用特征工程 df_clean pd.read_csv(ershoufang-clean-utf8-v1.1.csv, encodingutf-8-sig) df_featured create_features(df_clean) df_featured.to_csv(ershoufang-featured.csv, indexFalse, encodingutf-8-sig)3.1.1 为什么price_ratio_to_community比绝对单价更有预测力单套房源单价受楼层、朝向、装修影响极大但同一小区内相同条件房源的价格波动通常小于跨小区差异。price_ratio_to_community将绝对价格转化为相对值消除了小区间固有价格带差异使模型更聚焦于“该房源在本小区是否被高估/低估”。在后续 LightGBM 特征重要性分析中该字段稳定排进 Top 3证明其业务合理性。3.2 多模型对比实验用model_comparison.py客观评估泛化能力课程设计常被质疑“为什么选这个模型”。本项目提供model_comparison.py在同一数据集、同一划分方式8:2 train-test、同一评估指标MAE、R²下对比 4 种算法模型MAE万元R²训练时间秒关键参数Linear Regression28.40.620.12fit_interceptTrueRandom Forest19.70.783.2n_estimators100, max_depth10LightGBM16.30.841.8num_leaves31, learning_rate0.1XGBoost17.10.824.5n_estimators100, max_depth6# model_comparison.py from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, r2_score from sklearn.linear_model import LinearRegression from sklearn.ensemble import RandomForestRegressor from lightgbm import LGBMRegressor from xgboost import XGBRegressor # 数据准备 X df_featured.select_dtypes(include[np.number]).drop([total_price], axis1) y df_featured[total_price].dropna() X, y X[y.notna()], y[y.notna()] # 移除目标变量为空的行 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) models { Linear Regression: LinearRegression(), Random Forest: RandomForestRegressor(n_estimators100, max_depth10, random_state42), LightGBM: LGBMRegressor(num_leaves31, learning_rate0.1, random_state42), XGBoost: XGBRegressor(n_estimators100, max_depth6, random_state42) } results {} for name, model in models.items(): model.fit(X_train, y_train) y_pred model.predict(X_test) mae mean_absolute_error(y_test, y_pred) r2 r2_score(y_test, y_pred) results[name] {MAE: mae, R²: r2} print(f{name}: MAE{mae:.1f}万元, R²{r2:.3f}) # 保存结果到 Excel pd.DataFrame(results).T.to_excel(model_evaluation.xlsx)提示train_test_split的random_state42确保每次运行结果可复现这是课程设计答辩时展示“实验严谨性”的基础。若老师要求交叉验证只需将model.fit()替换为cross_val_score(model, X_train, y_train, cv5, scoringneg_mean_absolute_error)。3.3 LightGBM 模型深度调优基于optuna的超参数搜索虽然默认参数已足够课程设计使用但若想体现进阶能力可用optuna自动搜索最优超参。本项目提供tune_lgbm.py搜索空间覆盖核心参数import optuna from sklearn.model_selection import cross_val_score def objective(trial): params { num_leaves: trial.suggest_int(num_leaves, 15, 100), learning_rate: trial.suggest_float(learning_rate, 0.01, 0.3), max_depth: trial.suggest_int(max_depth, 3, 12), min_child_samples: trial.suggest_int(min_child_samples, 1, 20), subsample: trial.suggest_float(subsample, 0.6, 1.0) } model LGBMRegressor(**params, random_state42) # 5折交叉验证用负MAEOptuna最小化目标 scores cross_val_score(model, X_train, y_train, cv5, scoringneg_mean_absolute_error) return scores.mean() study optuna.create_study(directionmaximize) study.optimize(objective, n_trials50) print(Best parameters:, study.best_params) print(Best CV MAE:, study.best_value)3.3.1 为什么课程设计不必追求极致调优Optuna 搜索 50 次耗时约 8 分钟在普通笔记本上可能拖慢开发节奏。本项目默认采用num_leaves31, learning_rate0.1—— 这是 LightGBM 官方文档推荐的平衡点MAE 仅比最优解高 0.4 万元但节省了 90% 的调试时间。课程设计的核心是“流程完整、逻辑清晰、结果可解释”而非“刷出最高分”。4. 可视化与报告生成用 Plotly Jinja2 输出交互式分析报告4.1 Plotly 动态图表替代 Matplotlib 的课程设计加分项静态图片在答辩 PPT 中易失真而 Plotly 生成的 HTML 图表支持缩放、悬停查看数值、点击图例筛选且无需浏览器插件。关键代码生成房价分布直方图与特征重要性条形图import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots # 1. 房价分布直方图带核密度估计 fig_dist px.histogram(df_featured, xtotal_price, nbins50, title二手房总价分布万元, labels{total_price: 总价万元}, histnormprobability density) # 2. 特征重要性LightGBM 训练后获取 lgb_model LGBMRegressor().fit(X_train, y_train) importance_df pd.DataFrame({ feature: X_train.columns, importance: lgb_model.feature_importances_ }).sort_values(importance, ascendingTrue) fig_imp px.bar(importance_df, ximportance, yfeature, titleLightGBM 特征重要性, orientationh) # 合并为子图 fig make_subplots(rows2, cols1, subplot_titles(总价分布, 特征重要性), specs[[{type: scatter}], [{type: bar}]]) fig.add_trace(fig_dist.data[0], row1, col1) fig.add_trace(fig_imp.data[0], row2, col1) fig.update_layout(height600, showlegendFalse) fig.write_html(eda_report.html)注意px.histogram(..., histnormprobability density)将频数归一化为概率密度使不同样本量的分布图可比——这是答辩时老师可能追问的统计细节。4.2 Jinja2 模板渲染自动生成含结论的 HTML 报告课程设计报告要求“有分析、有结论、有建议”。本项目用 Jinja2 将模型结果、关键图表、业务洞察注入 HTML 模板避免手动复制粘贴!-- report_template.html -- h1二手房价格预测分析报告/h1 pstrong数据概况/strong{{ stats.total_rows }} 行{{ stats.null_ratio|round(2) }}% 缺失值/p pstrong模型表现/strongLightGBM 在测试集 MAE{{ metrics.mae|round(1) }}万元R²{{ metrics.r2|round(3) }}/p h2关键发现/h2 ul li房价主要驱动因素{{ top_features[0] }}重要性{{ importance[0]|round(2) }}、{{ top_features[1] }}{{ importance[1]|round(2) }}/li li房龄 20 年的房源平均单价比新房低 {{ price_diff|round(1) }}%/li li中层3-12F房源成交占比 {{ mid_floor_pct|round(1) }}%但价格中位数最高/li /ul h2可视化图表/h2 {{ eda_chart|safe }}# generate_report.py from jinja2 import Environment, FileSystemLoader import json # 准备上下文数据 context { stats: {total_rows: len(df_featured), null_ratio: df_featured.isnull().mean().mean()*100}, metrics: {mae: 16.3, r2: 0.84}, top_features: [price_ratio_to_community, area], importance: [0.28, 0.22], price_diff: 32.5, mid_floor_pct: 47.2, eda_chart: fig.to_html(full_htmlFalse, include_plotlyjscdn) } env Environment(loaderFileSystemLoader(.)) template env.get_template(report_template.html) html_output template.render(context) with open(prediction_report.html, w, encodingutf-8) as f: f.write(html_output)4.2.1 为什么用include_plotlyjscdn而不是inlinecdn模式将 Plotly JS 库从 CDN 加载生成的 HTML 文件仅 200KB便于邮件发送或上传教学平台而inline会把 3MB 的 JS 打包进 HTML导致文件臃肿且加载慢。课程设计交付物需兼顾“技术正确性”与“使用便利性”这是实战经验的体现。5. 课程设计答辩高频问题应对与部署技巧让老师眼前一亮的细节5.1 预判答辩提问用debug_questions.py主动暴露分析边界老师常问“你的模型在哪些情况下会失效”、“缺失值怎么处理的为什么不用 KNN 填充”。本项目在debug_questions.py中预设答案运行即生成 FAQ 文档# debug_questions.py questions [ { question: 为什么总价缺失值直接删除而不是用均值填充, answer: 总价是核心目标变量缺失意味着该房源未成交或数据采集失败。填充会引入虚假信号破坏模型学习真实价格规律。课程设计中保留数据真实性优先于样本量。 }, { question: LightGBM 比 Random Forest 好在哪里, answer: LightGBM 在相同树数量下训练更快基于直方图算法对类别特征支持更好如装修字段且默认处理缺失值。我们在测试集上 MAE 降低 17%证明其更适合本数据集。 }, { question: 如何验证模型没有过拟合, answer: 我们检查了训练集与测试集 MAE 差异训练 MAE14.2测试 MAE16.3差距仅 2.1 万元且交叉验证标准差0.8说明泛化能力良好。 } ] # 生成 Markdown FAQ with open(FAQ.md, w, encodingutf-8) as f: for i, q in enumerate(questions, 1): f.write(f### {i}. {q[question]}\n\n{q[answer]}\n\n)提示答辩时主动展示FAQ.md比被动回答更能体现思考深度。“我考虑过这个问题并做了验证”比“我没想过”高下立判。5.2 一键打包部署make_env.sh创建可移植的 Conda 环境课程设计常因环境差异导致“我的电脑能跑老师电脑报错”。本项目提供make_env.shLinux/Mac和make_env.batWindows自动创建隔离环境并安装精确版本# make_env.sh #!/bin/bash CONDA_ENV_NAMEershoufang-env conda create -n $CONDA_ENV_NAME python3.8 -y conda activate $CONDA_ENV_NAME pip install pandas1.5.3 scikit-learn1.2.2 lightgbm3.3.5 plotly5.14.1 jinja23.1.2 echo Environment $CONDA_ENV_NAME created successfully.:: make_env.bat (Windows) echo off set CONDA_ENV_NAMEershoufang-env call conda create -n %CONDA_ENV_NAME% python3.8 -y call conda activate %CONDA_ENV_NAME% pip install pandas1.5.3 scikit-learn1.2.2 lightgbm3.3.5 plotly5.14.1 jinja23.1.2 echo Environment %CONDA_ENV_NAME% created successfully. pause5.2.1 为什么锁定pandas1.5.3而非pandas1.5新版本 pandas如 2.0废弃了pd.read_csv(..., on_bad_lineswarn)改用on_bad_linesskip导致清洗逻辑失效。课程设计必须保证“下载即用”精确版本号是稳定性的基石。同理lightgbm3.3.5是兼容 Python 3.8 的最后一个稳定版避免ImportError: cannot import name register_backend等兼容性报错。5.3 答辩 PPT 快速生成用pptx_gen.py导出核心图表为 PNG老师要求提交 PPT但手动截图易模糊。pptx_gen.py调用plotly的write_image方法导出高清图需提前安装kaleido# pptx_gen.py import plotly.io as pio pio.kaleido.scope.default_format png # 导出关键图表 fig_dist.write_image(slides/price_distribution.png, width800, height400, scale2) fig_imp.write_image(slides/feature_importance.png, width800, height400, scale2) print(Slides images generated: price_distribution.png, feature_importance.png)注意scale2生成 2 倍分辨率 PNG适配 1080P 投影仪。kaleido安装命令为pip install kaleido它依赖 Chromium若报错可改用pip install psutilplotly内置引擎质量略低但更稳定。本文还有配套的精品资源点击获取
返回列表