数据挖掘与R语言

第16讲:决策树与回归树——回归决策树

2026年05月22日

上讲回顾

  • 分类决策树:因变量为类别型,用基尼指数衡量节点纯度,method = "class"
  • 树的生长:递归分裂 → 每次找 Gini 下降最大的特征与阈值
  • 剪枝cp 参数控制复杂度;用 xerror 最小原则科学选 cp
  • 评估:混淆矩阵、准确率、Kappa、灵敏度、特异度
  • 今天学习回归决策树——因变量为连续型数值,预测的不是类别,而是具体数值

本讲内容

  • Part 1:回归决策树基本概念 ——与分类树的本质区别
  • Part 2:方差缩减(RSS) ——回归树如何选择分裂点
  • Part 3:手工计算演示 ——以简单房价数据为例
  • Part 4:过拟合与剪枝 ——cp 在回归树中的作用
  • Part 5:房价实战演练 ——完整 R 操作流程
  • Part 6:分类树 vs 回归树 ——对比总结

Part 1:回归决策树基本概念

因变量是数值,预测的是一个具体数值

从分类到回归:一个类比

  • 分类树:把一批病人分成"患病"或"健康"两组,每个叶节点给出一个类别
  • 回归树:预测每位病人的具体指标值(如血糖值),每个叶节点给出一个平均数值

生活中的回归树例子:

这套房子值多少钱?
├─ 面积 > 150㎡
│   ├─ 地段是市中心? → 均价 ¥800万
│   └─ 地段是郊区?   → 均价 ¥450万
└─ 面积 ≤ 150㎡
    ├─ 楼层 > 10?    → 均价 ¥300万
    └─ 楼层 ≤ 10?    → 均价 ¥220万

提示

核心区别: 分类树的叶节点是众数类别;回归树的叶节点是均值。但树的生长逻辑完全相同:递归寻找最优分裂点。

回归树的三个基本要素

回归树 vs 线性回归

线性回归 回归决策树
预测函数 线性:\(\hat{y} = \beta_0 + \beta_1 x_1 + \ldots\) 分段常数:每个区域的均值
决策边界 全局线性,整体一条线 局部分段,不同区域不同规则
交互效应 需手动添加交叉项 自动捕获特征间的交互
可解释性 系数直接解读 树形规则直观可读
异常值敏感度 较敏感 较稳健
过拟合风险 较低 较高,需剪枝
输出 连续预测值 区域内的均值

注记

回归树的优势在于自动发现非线性关系特征间的交互。例如"高档住宅且面积大"才能大幅提价,这种组合效应线性回归很难直接捕捉。

Part 2:均方误差(MSE)

回归树如何选择分裂点

用均方误差(square error)代替基尼指数

分类树用基尼指数衡量"混乱程度";回归树用均方误差衡量"分散程度":

\[\text{MSE}(t) = {1\over n_t}\sum_{i \in t} (y_i - \bar{y}_t)^2\]

其中 \(\bar{y}_t\) 是节点 \(t\) 内所有样本的均值\(y_i\) 是第 \(i\) 个样本的真实值。

直觉理解:

节点情况 MSE 值 含义
所有 \(y\) 完全相同 0 叶节点极"纯",预测误差为 0
\(y\) 分散程度大 很大 该节点预测效果差,需要继续分裂

分裂准则: 在所有可能的特征 \(j\) 和阈值 \(s\) 中,选择使加权均方误差下降最多的组合:

\[\Delta\text{MSE} = \text{MSE}_{\text{父}} - \left(\frac{n_L}{n}\text{MSE}_L + \frac{n_R}{n}\text{MSE}_R\right)\]

MSE 与基尼指数的对比

回归树的叶节点预测值

重要

回归树预测的核心规则:

  1. 样本从根节点出发,按规则一路走到某个叶节点
  2. 该叶节点的预测值 = 该叶节点内所有训练样本的均值 \(\bar{y}_t\)
  3. 新样本的预测也走同一条路径,最终得到该叶节点的均值作为预测结果

\[\hat{y}_{\text{新样本}} = \bar{y}_t = \frac{1}{n_t}\sum_{i \in t} y_i\]

举例: 一套面积 2500、grade=9 的房子,走到"面积 > 2000 且 grade > 8"的叶节点,该节点内训练样本的均价是 ¥680万,则预测价格为 ¥680万。

Part 3:手工计算演示

以简单房价数据为例

手工数据:5套房子

为了演示回归树的分裂过程,用一个极简数据集:

房号 面积(sqft) 等级(grade) 价格(万元)
1 1000 6 150
2 1500 7 220
3 2000 8 310
4 2800 9 480
5 3500 10 650

根节点 RSS(全部5条):

\(\bar{y} = \frac{150+220+310+480+650}{5} = 362\)

\[\text{RSS}_{\text{根}} = (150-362)^2 + (220-362)^2 + (310-362)^2 + (480-362)^2 + (650-362)^2 = 193680\] 根节点 \[MSE = 193680 / 5 = 38736 \]

用"面积 ≤ 2000"分裂

左子节点(面积 ≤ 2000,房号 1、2、3):

\(\bar{y}_L = \frac{150+220+310}{3} = 226.7\)

\[\text{RSS}_L = (150-226.7)^2 + (220-226.7)^2 + (310-226.7)^2 = 13533\] \[MSE =13533/3=4511\] . . .

右子节点(面积 > 2000,房号 4、5):

\(\bar{y}_R = \frac{480+650}{2} = 565\)

\[\text{RSS}_R = (480-565)^2 + (650-565)^2 = 14450\] \[MSE= 14450/2=7225\] . . .

加权 RSS 下降量:

\[\Delta\text{MSE} = 38736 - \left(\frac{3}{5} \times 4511 + \frac{2}{5} \times 7225\right) = 38736 - (2706.6+2890) = \mathbf{33139.4}\]

对比两种分裂方式

提示

结论: "面积 ≤ 2000" 使 MSE 下降最大,因此被选为根节点的分裂变量。这与分类树选择"Gini 下降最大"的逻辑完全对应,只是评价指标换成了均方误差MSE。

回归树的完整分裂逻辑

Part 4:过拟合与剪枝

cp 在回归树中的作用

回归树的过拟合风险

回归树的评估指标:RMSE

重要

回归树评估用 RMSE,而非准确率!

\[\text{RMSE} = \sqrt{\frac{1}{n}\sum_{i=1}^n (\hat{y}_i - y_i)^2}\]

指标 含义 越小越好
RMSE 均方根误差,与 \(y\) 单位相同(如元)
MAE 平均绝对误差,对离群值更稳健
决定系数,解释方差的比例(0~1) ❌(越大越好)

与分类树的对比:

分类决策树 回归决策树
分裂准则 Gini 指数下降 MSE(均方误差)下降
叶节点输出 众数类别 均值
评估指标 准确率、Kappa RMSE、MAE、R²

cp 在回归树中的含义

重要

rpart 回归树的分裂判断标准(与分类树相同):

\[\text{只有当某次分裂能使相对 MSE 下降} \geq cp \text{ 时,才执行该分裂}\]

cp 值 树的形态 对房价预测的影响
cp = 0.01(默认) 较深,节点多 捕捉细微的价格规律
cp = 0.1 中等深度 只保留主要的价格规律
cp = 0.5 极浅,1–2层 只用最重要的一两个特征

选择 cp 的科学方法(与分类树完全相同):

printcp() → 找 xerror 最小行 → 取对应 CPprune(fit, cp = ?)

Part 5:房价实战演练

用房价数据完整走一遍

数据介绍:Boston房屋价格数据集

变量 类型 说明
medv 连续(因变量) 自住房屋价格中位数(单位:千美元)
crim 连续 人均犯罪率
zn 连续 超过 25000 平方英尺的住宅用地比例
indus 连续 城镇非零售商业用地比例
chas 分类 是否毗邻查尔斯河(1=是,0=否)
nox 连续 氮氧化物浓度(百万分之一)
rm 连续 每套住宅的平均房间数
age 连续 1940 年以前建造的自住房屋比例
dis 连续 到波士顿五个就业中心的加权距离
rad 连续 径向公路可达性指数
tax 连续 每 10 万美元价值的不动产税率
ptratio 连续 城镇师生比例
black 连续 城镇黑人比例
lstat 连续 低收入人口比例

数据来源:Harrison & Rubinfeld (1978),经典波士顿房价数据集

步骤一:读入数据与划分数据集

▶️ 查看代码
Boston <- read.csv("Boston.csv")
# 查看数据结构
str(Boston)
'data.frame':   506 obs. of  14 variables:
 $ crim   : num  0.00632 0.02731 0.02729 0.03237 0.06905 ...
 $ zn     : num  18 0 0 0 0 0 12.5 12.5 12.5 12.5 ...
 $ indus  : num  2.31 7.07 7.07 2.18 2.18 2.18 7.87 7.87 7.87 7.87 ...
 $ chas   : int  0 0 0 0 0 0 0 0 0 0 ...
 $ nox    : num  0.538 0.469 0.469 0.458 0.458 0.458 0.524 0.524 0.524 0.524 ...
 $ rm     : num  6.58 6.42 7.18 7 7.15 ...
 $ age    : num  65.2 78.9 61.1 45.8 54.2 58.7 66.6 96.1 100 85.9 ...
 $ dis    : num  4.09 4.97 4.97 6.06 6.06 ...
 $ rad    : int  1 2 2 3 3 3 5 5 5 5 ...
 $ tax    : int  296 242 242 222 222 222 311 311 311 311 ...
 $ ptratio: num  15.3 17.8 17.8 18.7 18.7 18.7 15.2 15.2 15.2 15.2 ...
 $ black  : num  397 397 393 395 397 ...
 $ lstat  : num  4.98 9.14 4.03 2.94 5.33 ...
 $ medv   : num  24 21.6 34.7 33.4 36.2 28.7 22.9 27.1 16.5 18.9 ...
▶️ 查看代码
summary(Boston$medv)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    5.0    17.0    21.2    22.5    25.0    50.0 

训练集基本信息

▶️ 查看代码
library(rsample)

set.seed(42)

# 70% 训练,30% 测试(回归问题无需分层)
split    <- initial_split(Boston, prop = 0.7)
train_df <- training(split)
test_df  <- testing(split)
▶️ 查看代码
# 查看训练集因变量分布
summary(train_df$medv)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    5.0    17.2    21.2    22.6    25.0    50.0 

训练集数据分析

▶️ 查看代码
# 方法一:将数据准备与绘图分离(推荐)
library(tidyverse)
# 准备数据
plot_data <- train_df  %>% 
  select(medv, rm, lstat, crim) %>%
  pivot_longer(-medv, names_to = "变量", values_to = "值")

# 绘图
p1 <- ggplot(train_df, aes(x = medv)) +
  geom_histogram(bins = 40, fill = "#4472C4", alpha = 0.75, color = "white") +
  labs(title = "Boston 房价分布(训练集)",
       subtitle = "右偏分布:少数高价房拉高均值",
       x = "房价中位数(千美元)", y = "频数")

p2 <- ggplot(plot_data, aes(x = 值, y = medv)) +
  geom_point(alpha = 0.2, size = 0.8, color = "#4472C4") +
  geom_smooth(method = "loess", se = FALSE, color = "#e05c2a", linewidth = 1) +
  facet_wrap(~变量, scales = "free_x", nrow = 1) +
  labs(title = "主要特征与房价的关系(训练集)",
       subtitle = "rm(房间数)与房价正相关;lstat(低收入比例)与房价负相关",
       x = NULL, y = "房价(千美元)")

p1 + p2 + plot_layout(widths = c(1, 2))

注记

rm(平均房间数)与房价正相关关系最强;lstat(低收入人口比例)与房价负相关。预计决策树会优先选这两个变量进行分裂。

步骤二:建立回归决策树

▶️ 查看代码
library(rpart)

# method = "anova" 表示回归树(因变量为连续型)
fit1 <- rpart(medv ~ .,
              data   = train_df,
              method = "anova")

# 查看含 CP 值的模型摘要
printcp(fit1)

Regression tree:
rpart(formula = medv ~ ., data = train_df, method = "anova")

Variables actually used in tree construction:
[1] dis   lstat rm   

Root node error: 29208/354 = 83

n= 354 

     CP nsplit rel error xerror  xstd
1 0.483      0      1.00   1.01 0.101
2 0.156      1      0.52   0.57 0.072
3 0.069      2      0.36   0.41 0.062
4 0.030      3      0.29   0.34 0.057
5 0.026      4      0.26   0.33 0.057
6 0.019      5      0.24   0.30 0.054
7 0.010      6      0.22   0.28 0.051

注记

method = "anova" 是回归树的关键参数(对应 F检验/方差分析的思路)。与分类树 method = "class" 的唯一区别就在这里。xerror 仍然是交叉验证相对误差,越小越好。

步骤三:回归决策树可视化

▶️ 查看代码
library(rpart.plot)

rpart.plot(fit1,
           type         = 4,
           extra        = 101,     # 回归树:显示节点均值和样本数
           fallen.leaves = TRUE,
           box.palette  = "RdYlGn",
           cex          = 0.75,
           main         = "回归决策树:Boston 房价预测(fit1)")

注记

回归树可视化中 extra = 101 显示每个节点的预测均值样本数,而非类别概率。叶节点中的数值即为该区域内所有训练样本价格的均值。

解读决策路径

根据可视化结果,读出典型的决策路径:

路径一(预测低价房):

lstat > 某阈值 且 rm ≤ 某阈值 → 预测价格 ≈ $XX 千美元(该叶节点样本的均价)

路径二(预测高价房):

lstat ≤ 某阈值 且 rm > 某阈值 → 预测价格 ≈ $XXX 千美元(该叶节点样本均价更高)

路径三(中等价位):

lstat ≤ 某阈值 且 rm ≤ 某阈值 → 预测价格 ≈ $XX–XXX 千美元

提示

读树口诀与分类树完全相同: 从根节点出发,满足条件走,不满足走,到达叶节点时显示的均值即为预测房价。实际路径以 rpart.plot() 图形为准。

步骤四:在测试集上预测,计算 RMSE

▶️ 查看代码
# 回归树预测:直接输出数值(无需 type = "class")
pred1 <- predict(fit1, newdata = test_df)

# 查看前6条预测结果
head(data.frame(
  真实价格 = test_df$medv,
  预测价格 = round(pred1, 1),
  误差     = round(pred1 - test_df$medv, 1)
))
  真实价格 预测价格  误差
1     22.9     21.1  -1.8
2     27.1     17.0 -10.1
3     21.7     17.0  -4.7
4     18.2     21.1   2.9
5     23.1     24.9   1.8
6     17.5     21.1   3.6
▶️ 查看代码
# 计算 RMSE(均方根误差)
rmse1 <- sqrt(mean((pred1 - test_df$medv)^2))
cat("fit1 RMSE:$", round(rmse1, 1), "千美元\n")
fit1 RMSE:$ 5.1 千美元
▶️ 查看代码
# 也可以计算 R²
ss_res <- sum((pred1 - test_df$medv)^2)
ss_tot <- sum((test_df$medv - mean(test_df$medv))^2)
r2_1   <- 1 - ss_res/ss_tot
cat("fit1 R²:", round(r2_1, 4), "\n")
fit1 R²: 0.709 

预测准确性分析

注记

RMSE 的单位与因变量相同(美元)。例如 RMSE ≈ $4,800 意味着平均每套房的预测误差约 4800 美元。结合房价中位数(约 $21,000),可判断预测精度是否可接受。

步骤五:cp = 0.1 剪枝,建立 fit2

▶️ 查看代码
# cp = 0.1 剪枝(更温和)
fit2 <- rpart(medv ~ .,
              data    = train_df,
              method  = "anova",
              control = rpart.control(cp = 0.1))

printcp(fit2)

Regression tree:
rpart(formula = medv ~ ., data = train_df, method = "anova", 
    control = rpart.control(cp = 0.1))

Variables actually used in tree construction:
[1] lstat rm   

Root node error: 29208/354 = 83

n= 354 

    CP nsplit rel error xerror  xstd
1 0.48      0      1.00   1.00 0.100
2 0.16      1      0.52   0.60 0.076
3 0.10      2      0.36   0.45 0.067

注记

cp = 0.1 是一个中等程度的剪枝(与分类树中的 cp=0.5 激进剪枝不同)。它要求每次分裂必须使相对 MSE 下降 ≥ 10%,因此会过滤掉效果较弱的分裂,保留最主要的价格规律。

fit2 可视化

▶️ 查看代码
rpart.plot(fit2,
           type          = 4,
           extra         = 101,
           fallen.leaves = TRUE,
           box.palette   = "RdYlGn",
           cex           = 0.85,
           main          = "剪枝后的回归树(cp = 0.1)—— fit2")

步骤六:对比 fit1 与 fit2 的预测准确性

▶️ 查看代码
# fit2 在测试集上预测
pred2 <- predict(fit2, newdata = test_df)

# 计算两个模型的 RMSE 和 R²
rmse2 <- sqrt(mean((pred2 - test_df$medv)^2))

ss_res2 <- sum((pred2 - test_df$medv)^2)
r2_2    <- 1 - ss_res2/ss_tot

cat("=== 模型性能对比 ===\n")
=== 模型性能对比 ===
▶️ 查看代码
cat(sprintf("fit1(默认 cp)    RMSE:$%.1f   R²:%.4f\n", rmse1, r2_1))
fit1(默认 cp)    RMSE:$5.1   R²:0.7090
▶️ 查看代码
cat(sprintf("fit2(cp = 0.1)  RMSE:$%.1f   R²:%.4f\n", rmse2, r2_2))
fit2(cp = 0.1)  RMSE:$6.5   R²:0.5219

两模型对比分析

对比分析总结

  1. RMSE 变化:fit2 剪枝后 RMSE 是升高还是降低?若升高幅度不大,说明 fit1 有一定过拟合

  2. R² 变化:fit2 的 R² 是否明显低于 fit1?R² 降低说明模型解释能力减弱

  3. 树的结构对比:fit1 节点多(规则细);fit2 节点少(规则简),仅保留最重要的变量

  4. 业务视角:fit2 更简洁,便于向客户解释"为什么这套房价格高/低";fit1 预测更精准但不易解读

重要

结论参考: cp=0.1 是一个温和剪枝,通常 RMSE 上升幅度有限(5–15%),但树的节点数可能减少 50% 以上,大幅提升可解释性。若业务场景要求"说得清楚",fit2 是更好的选择。

科学选择最优 cp

▶️ 查看代码
# 找 xerror 最小的 cp
best_cp <- fit1$cptable[which.min(fit1$cptable[,"xerror"]), "CP"]
cat("交叉验证误差最小的 cp:", best_cp, "\n")
交叉验证误差最小的 cp: 0.01 
▶️ 查看代码
# 用最优 cp 剪枝
fit_best <- prune(fit1, cp = best_cp)
▶️ 查看代码
rpart.plot(fit_best,
           type=4, extra=101, fallen.leaves=TRUE,
           box.palette="RdYlGn", cex=0.85,
           main="最优剪枝回归树(xerror 最小)")

Part 6:分类树 vs 回归树

完整对比总结

核心对比表

对比维度 分类决策树 回归决策树
因变量类型 类别型(Factor) 连续型(Numeric)
method 参数 "class" "anova"
分裂准则 基尼指数(Gini)下降 均方误差(MSE)下降
叶节点输出 众数类别 样本均值
predict() 输出 类别标签(type="class" 直接数值(无需指定 type)
评估指标 准确率、Kappa、混淆矩阵 RMSE、MAE、R²
extra 参数(rpart.plot) 104(概率+样本比例) 101(均值+样本数)
剪枝方式 相同:prune(fit, cp=?) 相同:prune(fit, cp=?)
cp 选择 相同:xerror 最小原则 相同:xerror 最小原则
典型应用 垃圾邮件、疾病诊断、信用评分 房价、销售额、股价预测

相同点与不同点

选择分类树还是回归树?

提示

一个问题搞定选择:因变量是什么类型?

因变量 举例 选择
是/否、有/无、类别标签 是否违约、是否幸存、成绩等级 分类树
具体数值、金额、比例 房价、销售额、温度、概率值 回归树
# 一行代码的区别:
fit_class <- rpart(Grade ~ .,  data=train, method = "class")  # 分类树
fit_reg   <- rpart(price ~ .,  data=train, method = "anova")  # 回归树

重要

其余所有步骤——数据划分、printcp()rpart.plot()prune()、最优 cp 选择——两种树完全相同,只有评估步骤不同(分类用混淆矩阵,回归用 RMSE/R²)。

Part 7:你必须掌握什么?

学习路线图

完整建模流程回顾

必须掌握

重要

以下是本讲回归决策树的完整核心,期末考试必考:

  1. 回归树与分类树的本质区别:因变量类型不同;分裂准则不同(MSE vs Gini);叶节点输出不同(均值 vs 众数)

  2. MSE 的含义:衡量节点内数值的分散程度;每次分裂选加权MSE 下降最大的特征与阈值

  3. R 操作rpart(y ~ ., data, method = "anova") 建树;printcp() 查 CP 表;extra = 101 显示均值

  4. 预测predict(fit, newdata=test) 直接输出数值,无需 type = "class"

  5. 评估:计算 RMSE = sqrt(mean((pred - y)^2));R² = 1 - SS_res/SS_tot;RMSE 越小越好,R² 越大越好

  6. 剪枝:cp 的含义;cp=0.1 是中等剪枝;prune(fit, cp=?) 后剪枝;用 xerror 最小原则科学选 cp

  7. 分类树 vs 回归树:会区分使用场景;一行代码的区别在于 method

常见错误清单

警告

请对照检查:

错误 正确做法
回归树忘记写 method = "anova" 不写默认也是 "anova",但必须明确写出以避免混淆
回归预测后写 type = "class" 回归树不需要 type 参数;predict(fit, newdata) 直接返回数值
用准确率评估回归树 回归树用 RMSE / MAE / R²,不存在准确率概念
RMSE 越大说明模型越好 RMSE 是误差,越小越好
R² 越小说明模型越好 R² 是解释力,越大越好(最大为 1)
cp = 0.1 与 cp = 0.5 效果一样 cp=0.1 是温和剪枝;cp=0.5 是激进剪枝,树更矮
混淆 extra=104extra=101 分类树用 104(概率);回归树用 101(均值+样本数)

本讲小结

  • 回归树本质:将特征空间划分为多个矩形区域,每个区域的预测值 = 训练样本的均值;分裂准则是 MSE(均方误差)下降

  • 与分类树的唯一代码区别method = "anova"predict() 直接输出数值;评估用 RMSE 和 R²

  • 房价规律sqft_living(面积)和 grade(等级)是最强的价格预测变量,通常成为根节点和第二层的分裂变量

  • 剪枝对比:fit1(cp=0.01)精度较高但树复杂;fit2(cp=0.1)简化为主要规律,可解释性更强;实际应用用 xerror 最小原则选最优 cp

  • 选择原则:因变量是类别 → 分类树;因变量是数值 → 回归树;其余框架完全相同

课后作业

作业说明

项目介绍: 现有一组房屋信息,分别为房屋价格 price、卧室数量 bedrooms、卫生间数量 bathrooms、住房面积 sqft_living、房基地面积 sqft_lot、楼层数目 floors、房屋整体好坏 condition(1–5等级)、房屋等级 grade(1–13)、除地下室的住房面积 sqft_above、房屋建成年份 yr_built。通过房屋信息建立关于房屋价格 price 的回归决策树模型。将 70% 数据进行模型训练,30% 数据进行模型验证。

步骤 1:读入数据与划分数据集

▶️ 查看代码
library(dplyr)
library(rsample)
library(rpart)
library(rpart.plot)

# 读入数据
house <- read.csv("house.csv") %>%
  select(price, bedrooms, bathrooms, sqft_living, sqft_lot,
         floors, condition, grade, sqft_above, yr_built) %>%
  drop_na()

# 划分数据集(70% 训练,30% 测试)
set.seed(123)
split    <- initial_split(house, prop = 0.7)
train_df <- training(split)
test_df  <- testing(split)

# 查看训练集结构
str(train_df)
'data.frame':   15129 obs. of  10 variables:
 $ price      : num  475000 316000 802000 905000 700000 ...
 $ bedrooms   : int  4 4 4 4 4 3 7 3 4 3 ...
 $ bathrooms  : num  2.5 1.5 2.25 2.5 2.25 1 3.5 2.25 1.75 1.75 ...
 $ sqft_living: int  2040 2120 2130 3330 2440 900 3470 1580 2520 1570 ...
 $ sqft_lot   : int  16200 46173 8734 9557 9450 10511 16264 7941 15205 16817 ...
 $ floors     : num  2 2 2 2 1.5 1 2 2 1 2 ...
 $ condition  : int  3 3 4 3 3 4 4 4 4 3 ...
 $ grade      : int  8 7 8 10 7 6 9 7 7 7 ...
 $ sqft_above : int  2040 2120 2130 3330 2440 900 3470 1580 2040 1570 ...
 $ yr_built   : int  1997 1974 1961 1995 1947 1961 1980 1986 1954 1982 ...
▶️ 查看代码
summary(train_df$price)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  78000  322000  450000  541028  649800 7700000 

要求: 说明训练集有多少行、多少列;各变量类型;因变量 price 的均值、中位数、最大值是多少?价格分布是否存在右偏?

步骤 2:建立回归决策树模型

▶️ 查看代码
# 建立回归决策树 fit1(注意 method = "anova")
fit1 <- rpart(price ~ .,
              data   = train_df,
              method = "anova")

# 查看含 CP 值的模型摘要
printcp(fit1)

Regression tree:
rpart(formula = price ~ ., data = train_df, method = "anova")

Variables actually used in tree construction:
[1] grade       sqft_living yr_built   

Root node error: 2e+15/15129 = 1.4e+11

n= 15129 

     CP nsplit rel error xerror  xstd
1 0.318      0      1.00   1.00 0.051
2 0.117      1      0.68   0.69 0.041
3 0.042      2      0.57   0.58 0.034
4 0.038      3      0.52   0.57 0.033
5 0.026      4      0.49   0.54 0.033
6 0.019      5      0.46   0.52 0.030
7 0.018      6      0.44   0.49 0.030
8 0.012      7      0.42   0.48 0.030
9 0.010      8      0.41   0.45 0.023

要求: 截取 printcp() 输出,说明模型实际用到了哪些变量;根节点误差(Root node error)是多少;哪个 cp 对应最小 xerror?

步骤 3:可视化并总结决策路径

▶️ 查看代码
# 可视化(回归树用 extra = 101)
rpart.plot(fit1,
           type         = 4,
           extra        = 101,
           fallen.leaves = TRUE,
           box.palette  = "RdYlGn",
           cex          = 0.75,
           main         = "回归决策树:房价预测(fit1)")

要求: 根据可视化结果,至少总结 一条 完整的决策路径,描述"若……则预测房价约为……",说明该叶节点对应的均值以及样本数量。

步骤 4:测试集预测与 RMSE 计算

▶️ 查看代码
# 回归树预测(直接输出数值,无需 type 参数)
pred1 <- predict(fit1, newdata = test_df)

# 计算 RMSE
rmse1 <- sqrt(mean((pred1 - test_df$price)^2))
cat("fit1 RMSE:$", format(round(rmse1, 0), big.mark=","), "\n")
fit1 RMSE:$ 243,443 
▶️ 查看代码
# 计算 R²
ss_res1 <- sum((pred1 - test_df$price)^2)
ss_tot  <- sum((test_df$price - mean(test_df$price))^2)
r2_1    <- 1 - ss_res1/ss_tot
cat("fit1 R²:", round(r2_1, 4), "\n")
fit1 R²: 0.5572 

要求: 报告 RMSE 和 R² 的值;结合房价中位数,判断预测误差是否在合理范围内;绘制预测值 vs 真实值的散点图,判断有无系统性偏差。

步骤 5:cp = 0.1 剪枝,建立 fit2

▶️ 查看代码
# 以 cp = 0.1 建立剪枝模型 fit2
fit2 <- rpart(price ~ .,
              data    = train_df,
              method  = "anova",
              control = rpart.control(cp = 0.1))

# 可视化新模型
rpart.plot(fit2,
           type         = 4,
           extra        = 101,
           fallen.leaves = TRUE,
           box.palette  = "RdYlGn",
           cex          = 0.9,
           main         = "剪枝回归树(cp = 0.1)")

要求: 描述 fit2 的树形结构(节点数、叶节点数);与 fit1 对比说明 cp=0.1 的剪枝效果;fit2 使用了哪些变量?

步骤 6:对比 fit1 与 fit2 的预测准确性

▶️ 查看代码
# fit2 预测测试集
pred2 <- predict(fit2, newdata = test_df)

# 计算 RMSE 和 R²
rmse2   <- sqrt(mean((pred2 - test_df$price)^2))
r2_2    <- 1 - sum((pred2-test_df$price)^2) / ss_tot

cat("=== 模型对比 ===\n")
=== 模型对比 ===
▶️ 查看代码
cat(sprintf("fit1(cp=0.01)  RMSE:$%s   R²:%.4f\n",
            format(round(rmse1,0),big.mark=","), r2_1))
fit1(cp=0.01)  RMSE:$243,443   R²:0.5572
▶️ 查看代码
cat(sprintf("fit2(cp=0.10)  RMSE:$%s   R²:%.4f\n",
            format(round(rmse2,0),big.mark=","), r2_2))
fit2(cp=0.10)  RMSE:$274,913   R²:0.4354

要求: 对比两个模型的 RMSE 和 R²;分析剪枝后预测性能的变化;结合树的结构简洁度,说明在房价预测任务中你会选择哪个模型以及理由。

谢谢!

第16讲:决策树与回归树——回归决策树


「回归树把连续的世界切割成矩形的区域,在每个区域里,用均值代替复杂。」