广州二手房房价预测:Python数据清洗到模型解释全流程
2026/9/11 23:41:41 网站建设 项目流程

简介:面向房地产数据分析初学者与价格预测爱好者,这份广州市二手房价预测资源将原始数据、Python建模代码和结果可视化整合在一起,便于快速理解房价回归分析全流程。压缩包共19个文件,包含1个CSV数据集、1个Python脚本和17张PNG图表;CSV提供原始房价相关数据,脚本覆盖预处理、特征选择、模型训练与评估,图表直观展示区域、朝向、楼层、面积、装修、建成时间与总价的关系。资源整体仅1.05MB,已有984人学习使用。从代码和图中可看到Pandas清洗、Scikit-learn建模、Matplotlib/Seaborn绘图的具体实践,项目涵盖缺失值处理、特征相关性分析、回归模型对比、交叉验证及MSE/R²等指标评估,也包含对总价取对数、多维度交叉分析等细节;对希望提升数据分析与机器学习落地能力,或在二手房场景中开展预测训练的学习者,这是一份内容紧凑、可直接对照运行的完整样例。

1. 二手房价预测这件事,数据比模型更值钱

看到“广州市二手房价预测——数据+python代码.rar”这个压缩包名字,大多数人的第一反应是“又一份网盘里吃灰的资料”。但真正做过房价预测项目的人会知道,这个标题其实戳中了两个要害:第一,广州二手房市场是典型的“强区位驱动”市场,同一条马路两侧的单价能差出一倍,没有精细到小区和楼龄的数据,任何模型都是空中楼阁;第二,Python 代码的价值不在于调包,而在于把“数据清洗—特征工程—模型对比—误差分析”这条链路走通,让预测结果能解释、可验证。

这篇文章不评价那份压缩包里的具体内容,而是基于“广州市二手房数据 + Python 代码”这个组合,把一套可复现的房价预测方案讲透。适合三类人:准备做毕业设计或课程项目的学生,想用公开数据练手的数据分析初学者,以及想评估广州特定板块房价水平的从业者。需要说明的是,这里的方案不依赖任何私有数据源,使用公开的链家、贝壳历史挂牌记录即可复现,核心是讲清楚“为什么这样做”和“代码参数该怎么设”。

2. 数据获取与预处理:先解决“脏数据”,再谈模型精度

2.1 公开房源数据的字段构成和采集方式

二手房挂牌数据的典型字段包括:小区名称、所在城区、板块、户型(室厅卫)、建筑面积、朝向、装修情况、电梯有无、楼龄、总价、单价、挂牌时间。其中“单价 = 总价 / 面积”这个派生字段在原始数据里往往直接给出,但建议自己重新计算一次,因为原始数据里偶尔会出现总价和面积对不上的情况。

采集方式上,不建议直接爬取链家或贝壳的实时页面,反爬策略复杂且容易触发封禁。更稳妥的思路是使用 GitHub 上定期更新的历史数据集,或者使用第三方数据平台的导出功能。如果决定自己爬,需要控制请求频率,并在代码中设置 User-Agent 和延时。

import pandas as pd import numpy as np # 假设已经从 CSV 文件读取原始数据 df = pd.read_csv('guangzhou_house.csv', encoding='utf-8-sig') # 检查缺失值情况 print("缺失值统计:") print(df.isnull().sum()) # 去掉关键字段为空的记录 df = df.dropna(subset=['total_price', 'area', 'district']) # 重新计算单价,避免原始数据错误 df['unit_price'] = df['total_price'] * 10000 / df['area']

提示:总价单位通常是“万元”,面积单位是“平方米”,算出的单价单位是“元/平方米”。这一步一定要自己重算而不是直接信任原字段。

2.2 异常值处理:广州房价数据的“合理区间”如何划定

广州二手房的真实成交单价区间,2024 年前后大致在 1.5 万到 10 万/平方米之间。但挂牌数据里会出现两种极端情况:一是车位、商铺混入住宅数据,单价低至几千元;二是别墅或豪宅的单价和总价远超普通住宅。处理策略不能只看单价,要结合面积和总价做交叉过滤。

# 过滤异常单价:广州住宅合理区间 df = df[(df['unit_price'] >= 15000) & (df['unit_price'] <= 100000)] # 面积过滤:住宅面积通常 20-300 平方米 df = df[(df['area'] >= 20) & (df['area'] <= 300)] # 总价过滤:避免车位混入 df = df[df['total_price'] >= 50] print(f"过滤后剩余样本量: {len(df)}")

这里的阈值设定有讲究。单价下限 1.5 万是广州外围区域(如从化、增城部分板块)的真实水平,低于这个数大概率是车位或工业产权;上限 10 万覆盖珠江新城核心区的高端住宅,超过这个数多半是录入错误或者是独栋别墅。如果是做学术研究,可以把别墅单独建模,而不是硬塞进普通住宅模型里。

2.3 特征工程:把“地段”量化成模型能吃的数字

房价预测里最重要的特征是区位,但“天河区”“越秀区”这种字符串不能直接喂给模型。常见做法是把行政区映射为数值标签,但更好的方式是引入“到市中心距离”或“到地铁站步行时间”这类连续变量。如果原始数据没有这些字段,可以从小区名称反查经纬度后计算。

# 行政区映射为类别编码 district_map = { '天河': 0, '越秀': 1, '海珠': 2, '荔湾': 3, '白云': 4, '番禺': 5, '黄埔': 6, '花都': 7, '南沙': 8, '增城': 9, '从化': 10 } df['district_code'] = df['district'].map(district_map) # 楼龄计算 df['house_age'] = 2025 - df['build_year'] # 户型字段拆分成室、厅数量 df[['bedrooms', 'hall']] = df['layout'].str.extract(r'(\d)室(\d)厅').astype(float) # 是否电梯房 df['has_elevator'] = df['elevator'].apply(lambda x: 1 if x == '有' else 0)

提示:楼龄对房价的影响是非线性的,5 年以内的次新房和 20 年以上的老破小,单价差异巨大。如果有精力,可以对楼龄做分段处理(小于 5 年、5-10 年、10-20 年、20 年以上),而不是直接用连续数值。

3. 模型选择与 Python 代码实现:为什么先用线性回归打底

3.1 房价预测的模型选型逻辑

房价预测常见的模型有三类:线性回归(包含岭回归、Lasso)、树模型(随机森林、XGBoost、LightGBM)、神经网络。先说结论:如果数据集在几千条的量级,优先用 LightGBM 和随机森林;如果只有几百条,线性回归加正则化反而更稳。

原因很直接,树模型能自动捕捉非线性关系和特征交互,比如“天河区 + 楼龄小于 5 年”这种组合效应,线性模型需要手动构造交叉特征才能表达。但树模型在样本量不足时容易过拟合,而且调参复杂度高。作为项目演示,先用线性回归建立基线,再用树模型提升精度,是最合理的路径。

from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score # 选特征列 feature_cols = ['district_code', 'area', 'bedrooms', 'hall', 'house_age', 'has_elevator', 'total_price'] X = df[feature_cols].drop('total_price', axis=1) y = df['total_price'] # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) # 训练线性回归 lr = LinearRegression() lr.fit(X_train, y_train) # 预测与评估 y_pred = lr.predict(X_test) print(f"MAE: {mean_absolute_error(y_test, y_pred):.2f} 万元") print(f"RMSE: {mean_squared_error(y_test, y_pred, squared=False):.2f} 万元") print(f"R2: {r2_score(y_test, y_pred):.4f}")

提示:total_price 单位是万元,MAE 输出如果是个位数,说明平均误差在几万块,对二手房这种总价几百万的标的来说可以接受。

3.2 特征重要性分析:找出影响广州房价的隐藏变量

线性回归的系数能直接反映特征的影响方向和幅度,但树模型更擅长给出特征重要性排序。如果只做一种分析,建议用随机森林的重要性输出,配合 SHAP 值解释具体预测结果。

from sklearn.ensemble import RandomForestRegressor rf = RandomForestRegressor( n_estimators=300, max_depth=15, min_samples_leaf=3, random_state=42, n_jobs=-1 ) rf.fit(X_train, y_train) # 特征重要性排序 importance = pd.DataFrame({ 'feature': X.columns, 'importance': rf.feature_importances_ }).sort_values('importance', ascending=False) print(importance)

参数说明:n_estimators 设为 300 是精度和速度的折中,低于 100 时偏差偏大,超过 500 收益递减;max_depth 限制为 15 防止过拟合,因为特征数量不多,深度太大没有实际意义;min_samples_leaf 设为 3 让叶子节点至少包含 3 个样本,减少极端预测。运行后大概率看到面积和 district_code 排在前两位,这两个特征对房价方差的解释力通常在 60% 以上。

3.3 树模型参数调优:网格搜索的三个必调参数

随机森林的默认参数对房价预测这类中等规模回归任务通常不是最优的。三个必调参数是 n_estimators、max_features 和 min_samples_split。max_features 默认是“auto”(即特征总数的平方根),但在特征少于 10 个时,建议直接设为 1.0 让每棵树都用全部特征,反而减少随机性带来的方差。

from sklearn.model_selection import GridSearchCV param_grid = { 'n_estimators': [200, 300, 500], 'max_depth': [10, 15, 20], 'min_samples_split': [2, 5, 10] } grid_search = GridSearchCV( RandomForestRegressor(random_state=42, n_jobs=-1), param_grid, cv=5, scoring='neg_mean_absolute_error', verbose=1 ) grid_search.fit(X_train, y_train) print(f"最优参数: {grid_search.best_params_}") print(f"最优 MAE: {-grid_search.best_score_:.2f} 万元")

注意:GridSearchCV 的 scoring 参数我用的是 neg_mean_absolute_error 而不是默认的 R2。房价预测场景中 MAE 更贴近业务理解——你就知道“平均下来预测差 8 万”,这比 R2=0.85 这种指标直观得多。cv=5 指的是 5 折交叉验证,目的是避免单次划分的运气成分。

4. 训练、验证与误差分析:预测准不准,要看误差分布在哪里

4.1 训练集和测试集怎么切才符合业务场景

房价预测的数据切分有两种思路:随机切分和按时间切分。随机切分假设所有样本独立同分布,适合课程项目;按时间切分则模拟真实场景——用历史数据训练,预测未来挂牌价。实际业务中我推荐按时间切分,因为二手房市场的价格走势有明显的阶段性和周期性。

# 按挂牌时间排序后切分 df = df.sort_values('listing_date') split_idx = int(len(df) * 0.8) train_data = df.iloc[:split_idx] test_data = df.iloc[split_idx:] # 重新整理特征和标签 X_train = train_data[feature_cols].drop('total_price', axis=1) y_train = train_data['total_price'] X_test = test_data[feature_cols].drop('total_price', axis=1) y_test = test_data['total_price']

提示:如果原始数据里的 listing_date 字段格式不统一,需要先用 pd.to_datetime 统一转换。按时间切分的好处是保证测试集的时间晚于训练集,避免“未来数据泄漏”到训练过程,这是房价预测项目里最容易犯的隐蔽错误。

4.2 残差分析:哪些房子预测偏差最大

模型评估指标只能给一个平均概念,真正找到问题要靠残差分析。把测试集每套房子的真实价格和预测价格相减,然后按区域、面积段、楼龄分组看残差的分布特征。

# 计算残差 test_data = test_data.copy() test_data['pred_price'] = rf.predict(X_test) test_data['residual'] = test_data['total_price'] - test_data['pred_price'] # 按区域看平均残差 district_residual = test_data.groupby('district')['residual'].agg(['mean', 'count']) print(district_residual.sort_values('mean')) # 按面积段看误差 test_data['area_bin'] = pd.cut(test_data['area'], bins=[0, 60, 90, 120, 200, 500]) area_residual = test_data.groupby('area_bin', observed=False)['residual'].agg(['mean', 'count']) print(area_residual)

这里有个规律:残差均值绝对值最大的区域,往往是学位房溢价最明显的板块(比如越秀区的老旧小户型)。原因在于挂牌价里包含了大量模型看不到的信息——学区政策、业主心理预期、小区物业管理质量。这些信息在结构化数据里缺失,表现为系统性低估或高估。

4.3 模型对比:和 XGBoost、LightGBM 的差距有多大

随机森林是稳健的基线,但 Gradient Boosting 系列模型在表格数据上通常表现更好。LightGBM 对房价这种连续值回归任务的优势在于直方图算法能处理特征的分布偏移,而且训练速度比 XGBoost 快 3 到 5 倍。

import lightgbm as lgb lgb_model = lgb.LGBMRegressor( n_estimators=1000, learning_rate=0.05, num_leaves=31, max_depth=7, subsample=0.8, colsample_bytree=0.8, random_state=42, verbose=-1 ) lgb_model.fit( X_train, y_train, eval_set=[(X_test, y_test)], eval_metric='mae' ) lgb_pred = lgb_model.predict(X_test) lgb_mae = mean_absolute_error(y_test, lgb_pred) print(f"LightGBM MAE: {lgb_mae:.2f} 万元") print(f"随机森林 MAE: {mean_absolute_error(y_test, y_pred_rf):.2f} 万元")

参数说明:learning_rate 设 0.05 配合 n_estimators=1000,是防止过拟合的标准组合——学习率越低,需要的树越多,但精度更高;num_leaves=31 对应 5 层深度的叶子数,过大容易过拟合;subsample 和 colsample_bytree 都设为 0.8,让每棵树只用 80% 的样本和特征,增加多样性。

4.4 处理类别不平衡与样本量不足的应急方案

广州二手房数据如果遇到了“天河区样本 5000 条,从化区只有 50 条”这种极端不平衡,模型会对天河区过拟合而完全忽略从化。解决方案有两种:一是按区域分组训练独立模型,二是使用分层采样保证每折验证都包含各区样本。

from sklearn.model_selection import StratifiedKFold # 先对区域做分层,再训练 df['district_code'] = df['district_code'].astype(int) skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) mae_scores = [] for train_idx, val_idx in skf.split(X, y, df['district_code']): X_tr, X_val = X.iloc[train_idx], X.iloc[val_idx] y_tr, y_val = y.iloc[train_idx], y.iloc[val_idx] model = RandomForestRegressor(n_estimators=300, random_state=42, n_jobs=-1) model.fit(X_tr, y_tr) pred = model.predict(X_val) mae_scores.append(mean_absolute_error(y_val, pred)) print(f"分层交叉验证 MAE: {np.mean(mae_scores):.2f} ± {np.std(mae_scores):.2f} 万元")

5. 落地技巧:用 SHAP 解释预测结果,让代码跑出业务价值

模型训练完成只是第一步,真正让“数据 + Python 代码”这套东西有用武之地的,是解释每个预测结果为什么是这个价格。SHAP 是目前最成熟的特征归因工具,能告诉我们“这套房子比区域均价高 30 万,主要是楼层和装修贡献的”。

import shap # 用随机森林模型解释 explainer = shap.TreeExplainer(rf) shap_values = explainer.shap_values(X_test) # 可视化第一个样本的预测解释 shap.initjs() shap.force_plot( explainer.expected_value, shap_values[0, :], X_test.iloc[0, :] )

注意:TreeExplainer 对所有树模型都适用,包括 LightGBM 和 XGBoost,但对线性回归要换成 LinearExplainer。如果数据集超过 5 万条,建议先采样 1000 条再计算 SHAP 值,否则会有明显的内存开销。

进一步可以做一个全局分析:把所有测试样本的 SHAP 值按特征汇总,得到每个特征对预测结果的“方向性影响”。比如结果是“面积每增加 1 平方米,SHAP 值平均增加 0.8 万”“楼龄每增加 1 年,SHAP 值平均减少 0.3 万”。这类量化结论比单纯的特征重要性排序更有说服力,可以直接写进项目报告或答辩 PPT。

最后一个实践建议:把模型导出为 joblib 或 pickle 文件,配合 Flask 写一个简单的 API 接口,输入户型、面积、区域、楼龄,输出预测价格区间。这样的完整交付物才是“数据 + Python 代码”压缩包的正确打开方式——既能跑出结果,也能让别人看懂每一个数字是怎么来的。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询