本报告旨在通过对美国爱荷华州埃姆斯市(Ames, Iowa)的房地产数据集(AmesHousing)进行深入的统计分析和机器学习建模,以识别影响房屋售价的关键因素,并构建高精度的预测模型。报告首先对数据进行探索性分析(EDA),揭示了房价呈右偏分布以及其与多个核心变量(如整体质量、居住面积)的强相关性。然后通过假设检验验证了关键特征(如房屋翻新)对房价的显著影响在此基础上,我们进行了特征工程,构造了如房龄、总面积等更具解释力的变量。随后,本报告分别构建并评估了多元线性回归模型和随机森林模型。结果表明,随机森林模型在预测精度上显著优于线性回归模型。变量重要性分析进一步证实,房屋的整体质量(Overall Qual)和总面积(Total SF)是决定房价的最关键因素。
房地产作为国民经济的重要支柱和居民家庭的主要资产,其价格的波动和决定因素一直是学术界和业界关注的焦点。本研究采用由 De Cock (2011) 整理的 AmesHousing 数据集,旨在通过数据科学方法,深入探索影响埃姆斯市房价的关键驱动因素,并构建、比较不同的预测模型,以实现对房价的精确预测。
本研究使用 R 语言及相关数据分析包。首先,我们从
AmesHousing 包中加载经过预处理的数据。
load("D:/课程作业/Ames.RData")
# 1. 加载所需程序包
library(AmesHousing)
library(ggplot2)
library(dplyr)
library(corrplot)
library(caret)
library(randomForest)
library(DT)
# 2. 加载数据并查看基本信息
ames <- make_ames()
cat("数据集维度 (观测, 变量):", dim(ames), "\n")
## 数据集维度 (观测, 变量): 2930 81
我们首先检验因变量 Sale_Price 的分布特征。
p1 <- ggplot(ames, aes(x = Sale_Price)) +
geom_histogram(aes(y = ..density..), binwidth = 20000, fill = "#56B4E9", color = "white") +
geom_density(alpha = .2, fill="#FF6666") +
labs(title = "原始销售价格分布", x = "销售价格", y = "密度") +
scale_x_continuous(labels = scales::dollar)
p2 <- ggplot(ames, aes(x = log(Sale_Price))) +
geom_histogram(aes(y = ..density..), binwidth = 0.1, fill = "#009E73", color = "white") +
geom_density(alpha = .2, fill="#FF6666") +
labs(title = "对数变换后销售价格分布", x = "log(销售价格)", y = "密度")
# 并排显示两个图形
gridExtra::grid.arrange(p1, p2, ncol = 2)
图1:销售价格分布图(左:原始;右:对数变换后)
分析: 如图1所示,原始的 sale_price
呈现显著的右偏态。通过对数变换后,log(sale_price)
的分布近似于正态分布,更适合用于线性模型。
# 筛选数值变量
numeric_vars <- ames %>% select_if(is.numeric)
# 计算相关性矩阵
cor_matrix <- cor(numeric_vars, use = "complete.obs")
# 找到与售价相关性最高的10个变量
top_10_vars_names <- names(sort(abs(cor_matrix[, "Sale_Price"]), decreasing = TRUE)[1:11])
# 绘制热力图
corrplot(cor(numeric_vars[, top_10_vars_names]), method = "color", type = "upper",
addCoef.col = "black", number.cex = 0.7, tl.col = "black", tl.srt = 45)
图2:Top 10 数值变量相关性热力图
分析: 如图2所示,
Gr_Liv_Area(地上生活面积)与
sale_price的相关系数高达
0.71,是影响房价的最重要数值变量。其次是 garage_cars
(车库容量) ,Garage_Area等。
ggplot(ames, aes(x = factor(Overall_Qual), y = Sale_Price)) +
geom_boxplot(fill = "#F0E442") +
labs(title = "整体质量 vs. 销售价格", x = "整体质量 (1-10)", y = "销售价格") +
scale_y_continuous(labels = scales::dollar)
图3:整体质量 vs. 销售价格
分析: 如图3所示,随着 overall_qual
等级的升高,房价的中位数和分布区间也随之系统性地上升。
在探索性分析中,我们猜测某些基础配置对房价有决定性影响。现在,我们使用统计检验来量化验证其中一个假设:房屋是否配备中央空调,对平均售价是否会产生显著影响?
我们将使用双样本 t 检验来比较“有中央空调”和“无中央空调”两组房屋的平均售价。
我们设定显著性水平 α = 0.05。
我们首先通过箱线图直观比较两组的价格分布。变量
Central_Air 的值为 ‘Y’ (是) 和 ‘N’ (否)。
# 我们在这里创建后续检验和建模都会用到的增强版数据集
ames_enhanced <- ames %>%
mutate(
house_age = Year_Sold - Year_Built,
is_remodeled = ifelse(Year_Remod_Add != Year_Built, 1, 0),
total_sf = Gr_Liv_Area + Total_Bsmt_SF,
total_baths = Bsmt_Full_Bath + 0.5 * Bsmt_Half_Bath + Full_Bath + 0.5 * Half_Bath
)
# 可视化:中央空调对房价的影响
ggplot(ames_enhanced, aes(x = Central_Air, y = Sale_Price, fill = Central_Air)) +
geom_boxplot(alpha = 0.7) +
labs(title = "中央空调对房屋售价的影响", x = "是否配备中央空调 (N=否, Y=是)", y = "销售价格") +
scale_y_continuous(labels = scales::dollar) +
scale_fill_manual(values = c("N" = "#D55E00", "Y" = "#0072B2"), name = "配备中央空调") +
theme_minimal()
图3a: 中央空调对房屋售价的影响
初步观察:
从箱线图中可以极其清晰地看到,配备中央空调(Y)的房屋,其售价的中位数和整体分布都远高于没有配备(N)的房屋。
现在我们正式进行 t 检验。
# 执行 t 检验, 注意 R 会自动处理 'Y'/'N' 这种因子
t_test_result_air <- t.test(Sale_Price ~ Central_Air, data = ames_enhanced)
# 显示检验结果
t_test_result_air
##
## Welch Two Sample t-test
##
## data: Sale_Price by Central_Air
## t = -27.433, df = 336.06, p-value < 2.2e-16
## alternative hypothesis: true difference in means between group N and group Y is not equal to 0
## 95 percent confidence interval:
## -90625.69 -78498.92
## sample estimates:
## mean in group N mean in group Y
## 101890.5 186452.8
p-value 远小于
2.2e-16,这是一个极小的数值,远小于我们设定的显著性水平 α = 0.05。95 percent confidence interval: 结果中的95%置信区间为
[-90625.69, -78498.92]。这表示我们有95%的信心认为,没有中央空调的房屋(group
N)比有中央空调的房屋(group Y)平均价格低 78498.92 美元到 90625.69
美元。sample estimates:
结果显示,没有中央空调的房屋(mean in group N)样本均价约为
101890.5
美元,而有中央空调的房屋(mean in group Y)样本均价约为
186452.8 美元。差价巨大,符合预期。本章节小结: 通过 t 检验,我们从统计学上证实了中央空调是影响房屋市场价值的一个极其重要的因素。
将增强后的数据集按 80:20 的比例分割为训练集和测试集。
set.seed(123) # 保证结果可复现
train_indices <- createDataPartition(ames_enhanced$Sale_Price, p = 0.8, list = FALSE)
train_data <- ames_enhanced[train_indices, ]
test_data <- ames_enhanced[-train_indices, ]
构建一个基准的线性回归模型。
lm_model <- lm(log(Sale_Price) ~ Overall_Qual + total_sf + Garage_Cars +
Neighborhood + house_age + is_remodeled,
data = train_data)
# 使用 kableExtra 包美化摘要输出
knitr::kable(summary(lm_model)$coefficients, caption = "线性回归模型系数摘要")
| Estimate | Std. Error | t value | Pr(>|t|) | |
|---|---|---|---|---|
| (Intercept) | 10.3429421 | 0.0946413 | 109.2856988 | 0.0000000 |
| Overall_QualPoor | 0.3866725 | 0.1072644 | 3.6048549 | 0.0003190 |
| Overall_QualFair | 0.7805938 | 0.0966559 | 8.0760116 | 0.0000000 |
| Overall_QualBelow_Average | 0.9379587 | 0.0938559 | 9.9936081 | 0.0000000 |
| Overall_QualAverage | 1.0727585 | 0.0935203 | 11.4708662 | 0.0000000 |
| Overall_QualAbove_Average | 1.1507217 | 0.0937377 | 12.2759793 | 0.0000000 |
| Overall_QualGood | 1.2279083 | 0.0942387 | 13.0297717 | 0.0000000 |
| Overall_QualVery_Good | 1.3146550 | 0.0952237 | 13.8059634 | 0.0000000 |
| Overall_QualExcellent | 1.4603783 | 0.0972529 | 15.0162900 | 0.0000000 |
| Overall_QualVery_Excellent | 1.4099912 | 0.1018213 | 13.8477030 | 0.0000000 |
| total_sf | 0.0001631 | 0.0000060 | 27.1046246 | 0.0000000 |
| Garage_Cars | 0.0794114 | 0.0062058 | 12.7962397 | 0.0000000 |
| NeighborhoodCollege_Creek | 0.0359814 | 0.0168273 | 2.1382746 | 0.0325993 |
| NeighborhoodOld_Town | -0.0954623 | 0.0174650 | -5.4659288 | 0.0000001 |
| NeighborhoodEdwards | -0.0682351 | 0.0157986 | -4.3190619 | 0.0000163 |
| NeighborhoodSomerset | 0.0696534 | 0.0197522 | 3.5263599 | 0.0004295 |
| NeighborhoodNorthridge_Heights | 0.1321074 | 0.0222444 | 5.9388916 | 0.0000000 |
| NeighborhoodGilbert | 0.0337624 | 0.0192734 | 1.7517649 | 0.0799472 |
| NeighborhoodSawyer | -0.0062567 | 0.0172549 | -0.3626066 | 0.7169320 |
| NeighborhoodNorthwest_Ames | 0.0219757 | 0.0189621 | 1.1589285 | 0.2466054 |
| NeighborhoodSawyer_West | 0.0061139 | 0.0193351 | 0.3162075 | 0.7518736 |
| NeighborhoodMitchell | 0.0001566 | 0.0197571 | 0.0079270 | 0.9936759 |
| NeighborhoodBrookside | -0.0297345 | 0.0210261 | -1.4141764 | 0.1574452 |
| NeighborhoodCrawford | 0.1762219 | 0.0205745 | 8.5650846 | 0.0000000 |
| NeighborhoodIowa_DOT_and_Rail_Road | -0.1590445 | 0.0214363 | -7.4194143 | 0.0000000 |
| NeighborhoodTimberland | 0.0978448 | 0.0248891 | 3.9312260 | 0.0000870 |
| NeighborhoodNorthridge | 0.1754018 | 0.0261793 | 6.7000144 | 0.0000000 |
| NeighborhoodStone_Brook | 0.1542719 | 0.0299661 | 5.1482202 | 0.0000003 |
| NeighborhoodSouth_and_West_of_Iowa_State_University | -0.0373775 | 0.0308920 | -1.2099441 | 0.2264244 |
| NeighborhoodClear_Creek | 0.1682288 | 0.0283781 | 5.9281238 | 0.0000000 |
| NeighborhoodMeadow_Village | -0.1483016 | 0.0318372 | -4.6581291 | 0.0000034 |
| NeighborhoodBriardale | -0.2127056 | 0.0326649 | -6.5117426 | 0.0000000 |
| NeighborhoodBloomington_Heights | -0.0316426 | 0.0375250 | -0.8432398 | 0.3991818 |
| NeighborhoodVeenker | 0.1093190 | 0.0397202 | 2.7522298 | 0.0059654 |
| NeighborhoodNorthpark_Villa | -0.1065580 | 0.0374955 | -2.8418880 | 0.0045242 |
| NeighborhoodBlueste | -0.0873542 | 0.0581462 | -1.5023210 | 0.1331512 |
| NeighborhoodGreens | 0.0147037 | 0.0678416 | 0.2167362 | 0.8284331 |
| NeighborhoodGreen_Hills | 0.5210631 | 0.1612816 | 3.2307660 | 0.0012520 |
| NeighborhoodLandmark | -0.1630614 | 0.1611416 | -1.0119142 | 0.3116853 |
| house_age | -0.0018321 | 0.0002467 | -7.4255709 | 0.0000000 |
| is_remodeled | 0.0441775 | 0.0078509 | 5.6270628 | 0.0000000 |
# 预测与评估
lm_predictions_log <- predict(lm_model, newdata = test_data)
lm_predictions_actual <- exp(lm_predictions_log)
lm_rmse <- sqrt(mean((lm_predictions_actual - test_data$Sale_Price)^2))
print(lm_rmse)
## [1] 29468.4
构建一个非线性的集成模型——随机森林。
# 随机森林可以直接使用原始 Sale_Price 作为因变量
model_vars <- c("Sale_Price", "Overall_Qual", "total_sf", "Garage_Cars",
"Neighborhood", "house_age", "is_remodeled", "total_baths")
set.seed(123)
rf_model <- randomForest(
Sale_Price ~ .,
data = train_data[, model_vars],
ntree = 500,
importance = TRUE
)
# 预测与评估
rf_predictions <- predict(rf_model, newdata = test_data)
rf_rmse <- sqrt(mean((rf_predictions - test_data$Sale_Price)^2))
print(rf_rmse)
## [1] 27110.5
# 绘制变量重要性图
varImpPlot(rf_model, main = "随机森林变量重要性排序")
图4:随机森林变量重要性排序
分析: 如图4所示,overall_qual 和
total_sf 再次被证明是两个最重要的预测因子。
为了直观地比较两个模型的性能,我们绘制预测值与真实值的对比图,并汇总其关键性能指标。
# 创建一个比较表格
comparison_df <- data.frame(
Model = c("多元线性回归", "随机森林"),
RMSE = c(scales::dollar(lm_rmse), scales::dollar(rf_rmse))
)
knitr::kable(comparison_df, caption = "表1:模型性能比较")
| Model | RMSE |
|---|---|
| 多元线性回归 | $29,468.40 |
| 随机森林 | $27,110.50 |
eval_results <- data.frame(
Actual = test_data$Sale_Price,
LM_Predicted = lm_predictions_actual,
RF_Predicted = rf_predictions
)
ggplot(eval_results) +
geom_point(aes(x = Actual, y = LM_Predicted, color = "线性回归"), alpha = 0.5) +
geom_point(aes(x = Actual, y = RF_Predicted, color = "随机森林"), alpha = 0.5) +
geom_abline(intercept = 0, slope = 1, color = "black", linetype = "dashed", size = 1) +
scale_color_manual(name = "模型", values = c("线性回归" = "blue", "随机森林" = "red")) +
labs(title = "模型预测效果对比", x = "真实价格", y = "预测价格") +
theme_minimal() +
scale_x_continuous(labels = scales::dollar) +
scale_y_continuous(labels = scales::dollar)
图5:模型预测效果对比图
分析: 如表1和图5所示,随机森林模型的散点更紧密地聚集在 y=x 对角线周围,且其 RMSE 值显著更低,证明其整体预测性能优于多元线性回归模型。
本研究通过对 AmesHousing 数据集的系统性分析,成功地揭示了影响房价的关键因素并构建了高精度的预测模型。主要结论如下:
overall_qual) 和总居住面积
(total_sf) 是决定房价的最核心因素。