Regression and Classification Trees

Description

This page reproduces the steps of rwf’s report_rtree() using rpart directly; both models are fit on the training part of the first of 10 folds created by rwf’s k_fold(). Installation instructions for rwf can be found here report_rtree() is not installed by default; it is in /rwf/working_functions/OTHER/ML_TREE_RPART.R

Theoretical Background

Recursive partitioning

rpart implements CART (Classification and Regression Trees, Breiman et al., 1984). Starting from the full dataset at the root node, the algorithm searches every predictor and every possible split point on that predictor, and picks the single split that most improves node purity. Each resulting child node is then split again, recursively, until a stopping rule is reached (a minimum number of observations per node, a minimum improvement threshold, or a maximum depth). The result is a binary tree: every internal node asks a yes/no question about one predictor, and every terminal node (“leaf”) gives a prediction.

Splitting criteria

The measure of “improvement” depends on the outcome type:

  • Classification (method = "class", used below for case) — by default rpart uses the Gini impurity of the class distribution within a node; a split is chosen to make the two child nodes as class-pure as possible.
  • Regression (method = "anova", used below for medv) — the criterion is the reduction in within-node sum of squares, i.e. the split that most reduces the variance of the outcome within each child node.

Cost-complexity pruning

A tree grown to full depth almost always overfits — it captures noise along with signal. rpart addresses this with cost-complexity pruning: every subtree \(T\) is scored as

\[R_\alpha(T) = R(T) + \alpha \, |T|\]

where \(R(T)\) is the tree’s error (relative to the root), \(|T|\) is the number of terminal nodes, and \(\alpha\) (called CP in rpart’s output) penalizes tree size. Varying \(\alpha\) from 0 to 1 produces a nested sequence of subtrees, from the grown tree (stopped at rpart’s default cp = 0.01) down to a single root node — this sequence is stored in model$cptable.

Cross-validated tree size

Because the apparent error of a tree always decreases as it grows (a larger tree can always fit the training data at least as well), rpart estimates each subtree’s error via internal k-fold cross-validation. cptable reports, for every candidate CP value: the relative error on the training data (rel error), the cross-validated error (xerror), and its standard deviation (xstd). The code below selects the tree whose CP gives the minimum xerror — a common, more conservative alternative is the “1-SE rule” (the smallest tree whose xerror is within one standard error of the minimum), which favors a simpler tree.

Variable importance

A predictor’s importance is the sum of the goodness-of-split (improvement in Gini impurity for classification, or in sum-of-squares for regression) for every split where it is the primary splitter, plus, for every split where it serves as a surrogate (a stand-in used when the primary splitting variable is missing), the primary split’s improvement weighted by the surrogate’s adjusted agreement. This is why a variable can show non-trivial importance even if it never appears as a labeled split in the final tree diagram.

Code
infert_formula<-formula(factor(case)~age+parity+education+spontaneous+induced)
boston_formula<-formula(c("medv~",paste(names(MASS::Boston)[1:13],collapse="+")))

print(infert_formula)
factor(case) ~ age + parity + education + spontaneous + induced
Code
print(boston_formula)
medv ~ crim + zn + indus + chas + nox + rm + age + dis + rad + 
    tax + ptratio + black + lstat
Code
# kfolding (seed fixed so folds, rpart's internal cross-validation and the prose below are reproducible)
set.seed(12345)
train_test_classification<-k_fold(df=infert,
                                  model_formula=infert_formula)
Fold Cases: 1 Train: 223 Test: 25 Total: 248 Unique Train: 223 Unique Test: 25 
Fold Cases: 2 Train: 223 Test: 25 Total: 248 Unique Train: 223 Unique Test: 25 
Fold Cases: 3 Train: 223 Test: 25 Total: 248 Unique Train: 223 Unique Test: 25 
Fold Cases: 4 Train: 224 Test: 24 Total: 248 Unique Train: 224 Unique Test: 24 
Fold Cases: 5 Train: 223 Test: 25 Total: 248 Unique Train: 223 Unique Test: 25 
Fold Cases: 6 Train: 223 Test: 25 Total: 248 Unique Train: 223 Unique Test: 25 
Fold Cases: 7 Train: 224 Test: 24 Total: 248 Unique Train: 224 Unique Test: 24 
Fold Cases: 8 Train: 223 Test: 25 Total: 248 Unique Train: 223 Unique Test: 25 
Fold Cases: 9 Train: 223 Test: 25 Total: 248 Unique Train: 223 Unique Test: 25 
Fold Cases: 10 Train: 223 Test: 25 Total: 248 Unique Train: 223 Unique Test: 25 
Code
train_test_regression<-k_fold(df=MASS::Boston,
                              model_formula=boston_formula)
Fold Cases: 1 Train: 455 Test: 51 Total: 506 Unique Train: 455 Unique Test: 51 
Fold Cases: 2 Train: 455 Test: 51 Total: 506 Unique Train: 455 Unique Test: 51 
Fold Cases: 3 Train: 456 Test: 50 Total: 506 Unique Train: 456 Unique Test: 50 
Fold Cases: 4 Train: 455 Test: 51 Total: 506 Unique Train: 455 Unique Test: 51 
Fold Cases: 5 Train: 456 Test: 50 Total: 506 Unique Train: 456 Unique Test: 50 
Fold Cases: 6 Train: 455 Test: 51 Total: 506 Unique Train: 455 Unique Test: 51 
Fold Cases: 7 Train: 456 Test: 50 Total: 506 Unique Train: 456 Unique Test: 50 
Fold Cases: 8 Train: 455 Test: 51 Total: 506 Unique Train: 455 Unique Test: 51 
Fold Cases: 9 Train: 456 Test: 50 Total: 506 Unique Train: 456 Unique Test: 50 
Fold Cases: 10 Train: 455 Test: 51 Total: 506 Unique Train: 455 Unique Test: 51 
Code
# rpart models
rtree_classification<-rpart::rpart(infert_formula,
                                   train_test_classification$f$train$f1,
                                   model=TRUE,x=TRUE,y=TRUE)
rtree_regression<-rpart::rpart(boston_formula,
                               train_test_regression$f$train$f1,
                               model=TRUE,x=TRUE,y=TRUE)

Rpart Classification

The classification tree predicts case (secondary infertility, from the classic infert case-control study) from age, parity, education, spontaneous, and induced (prior abortions).

Growing and pruning the tree

rtree_classification was grown with rpart’s default stopping rules (cp = 0.01, minsplit = 20, minbucket = 7) in the create_models chunk above. Here it is pruned back to the CP value that minimizes the cross-validated error (xerror) in its cptable.

Code
result<-data.frame(rtree_classification$cptable)
result$nsplit<-factor(result$nsplit+1)
minimun_size<-as.numeric(as.character(result[which.min(result[,"xerror"]),"nsplit"]))
initial_model<-rtree_classification
model<-rpart::prune(rtree_classification,
                    cp=rtree_classification$cptable[which.min(rtree_classification$cptable[,"xerror"]),"CP"])

Variable importance

Code
importance<-model$variable.importance
importance<-data.frame(names=names(importance),importance=importance)
importance$names<-factor(importance$names,levels=rev(as.character(importance$names)))
plot_importance<-ggplot(importance,aes(x=names,y=importance))+
  geom_bar(stat='identity')+
  labs(title="Importance Plot",y="Importance (sum of split improvements)",x="Predictor")+
  theme_bw(base_size=10)+
  scale_x_discrete(limits=rev(levels(names)))+
  coord_flip()
plot_importance

Longer bars mean the predictor contributed more to reducing impurity across the tree’s splits (as primary splitter or as a surrogate). Here spontaneous dominates (importance 13.01), followed by parity and age, with education contributing least. Note that infert is a matched case-control study in which cases and controls were matched on age, parity and education, so the substantive signal is in the prior-abortion variables (spontaneous, induced); splits on age or parity largely reflect sampling noise in this training fold.

The full, unpruned tree

Code
rtree_classification
n= 223 

node), split, n, loss, yval, (yprob)
      * denotes terminal node

 1) root 223 72 0 (0.6771300 0.3228700)  
   2) spontaneous< 0.5 128 24 0 (0.8125000 0.1875000) *
   3) spontaneous>=0.5 95 47 1 (0.4947368 0.5052632)  
     6) parity>=3.5 21  5 0 (0.7619048 0.2380952) *
     7) parity< 3.5 74 31 1 (0.4189189 0.5810811)  
      14) age< 30.5 40 17 0 (0.5750000 0.4250000)  
        28) spontaneous< 1.5 28  9 0 (0.6785714 0.3214286) *
        29) spontaneous>=1.5 12  4 1 (0.3333333 0.6666667) *
      15) age>=30.5 34  8 1 (0.2352941 0.7647059) *

This is the text representation of the tree before pruning — every line is one node: its splitting rule, the number of observations that reach it (n), the misclassification loss, and the predicted class with its probability. Compare its size to the pruned model used in the plots below: in this run the full tree has 5 terminal nodes and the pruned tree 5. When the minimum xerror occurs at the largest tree in the cptable, pruning leaves the tree unchanged; with other data or a smaller cp, pruning would collapse some of these nodes.

Choosing the tree size via cross-validation

Code
rpart::plotcp(model)

Code
rpart::rsq.rpart(model)

Classification tree:
rpart::rpart(formula = infert_formula, data = train_test_classification$f$train$f1, 
    model = TRUE, x = TRUE, y = TRUE)

Variables actually used in tree construction:
[1] age         parity      spontaneous

Root node error: 72/223 = 0.32287

n= 223 

        CP nsplit rel error  xerror     xstd
1 0.083333      0   1.00000 1.00000 0.096977
2 0.055556      3   0.75000 1.00000 0.096977
3 0.010000      4   0.69444 0.86111 0.092923

plotcp() plots cross-validated relative error (xerror, with one-SE error bars) against CP (bottom axis) and tree size (top axis) — the dotted horizontal line marks one standard error above the minimum, the usual reference for the “1-SE rule”. rsq.rpart() prints the cptable and adds two more panels: apparent vs. cross-validated R-squared, and cross-validated relative error, against the number of splits. Because this is a classification tree, these values are 1 minus the relative misclassification error (relative to the root node’s error of 72/223), not explained variance, so read them as relative predictive error rather than a literal R²; rsq.rpart() itself warns that it “may not be applicable for this method”. Here cross-validated error drops only from 1.00 to 0.86 of the root error (about 28% vs 32% misclassification) and is lowest at the largest tree in the cptable, which is why the minimum-xerror rule keeps the full tree.

Error-by-size summary

Code
error<-data.frame(model$cptable)
error$nsplit<-factor(error$nsplit+1)
tree_size<-error[which.min(error[,"xerror"]),"nsplit"]
error<-reshape2::melt(error,id.vars="nsplit")
names(error)<-c("Split","Metric","value")
plot_prune<-ggplot(error,aes(x=Split,y=value,color=Metric))+
  geom_line(aes(group=Metric))+
  geom_point()+
  labs(title=paste("Error Plot","Suggested Size:",tree_size),y="Metric value",x="Size of Tree")+
  theme_bw(base_size=10)
plot_prune

This re-plots all four cptable columns (CP, rel error, xerror, xstd) on one axis against tree size (number of terminal nodes = nsplit + 1), from a single node onward. The title reports the tree size at which xerror is minimized — the size the pruned model retains (here 5 terminal nodes, i.e. the full tree).

The pruned tree diagram

Code
rpart.plot::rpart.plot(model,type=1)

Each box is a node showing the predicted class (top), the fitted probability that case = 1 (middle), and the percentage of the training sample in that node (bottom). Following the branches down from the root shows the sequence of yes/no rules — here the splits use, in order from the root, spontaneous, parity, age: first whether the woman had any prior spontaneous abortion, then parity, then age — that separate higher- from lower-risk groups.

Raw model components

Code
frame<-data.frame(model$frame)
cp<-data.frame(model$cptable)
parameters<-data.frame(parameters=unlist(model$control))
splits<-data.frame(name=row.names(model$splits),model$splits,row.names=NULL)
importance<-data.frame(importance=model$variable.importance)
ordered<-data.frame(ordered=model$ordered)
data<-data.frame(y=model$y,x=model$x,model=model$model)
call<-data.frame(call=call_to_string(model))
result<-list(frame=frame,cp=cp,parameters=parameters,splits=splits,importance=importance,ordered=ordered,call=call)

print(result)
$frame
           var   n  wt dev yval complexity ncompete nsurrogate     yval2.V1     yval2.V2     yval2.V3     yval2.V4     yval2.V5 yval2.nodeprob
1  spontaneous 223 223  72    1 0.08333333        4          3   1.00000000 151.00000000  72.00000000   0.67713004   0.32286996     1.00000000
2       <leaf> 128 128  24    1 0.00000000        0          0   1.00000000 104.00000000  24.00000000   0.81250000   0.18750000     0.57399103
3       parity  95  95  47    2 0.08333333        4          2   2.00000000  47.00000000  48.00000000   0.49473684   0.50526316     0.42600897
6       <leaf>  21  21   5    1 0.00000000        0          0   1.00000000  16.00000000   5.00000000   0.76190476   0.23809524     0.09417040
7          age  74  74  31    2 0.08333333        4          2   2.00000000  31.00000000  43.00000000   0.41891892   0.58108108     0.33183857
14 spontaneous  40  40  17    1 0.05555556        4          0   1.00000000  23.00000000  17.00000000   0.57500000   0.42500000     0.17937220
28      <leaf>  28  28   9    1 0.00000000        0          0   1.00000000  19.00000000   9.00000000   0.67857143   0.32142857     0.12556054
29      <leaf>  12  12   4    2 0.01000000        0          0   2.00000000   4.00000000   8.00000000   0.33333333   0.66666667     0.05381166
15      <leaf>  34  34   8    2 0.00000000        0          0   2.00000000   8.00000000  26.00000000   0.23529412   0.76470588     0.15246637

$cp
          CP nsplit rel.error    xerror       xstd
1 0.08333333      0 1.0000000 1.0000000 0.09697723
2 0.05555556      3 0.7500000 1.0000000 0.09697723
3 0.01000000      4 0.6944444 0.8611111 0.09292314

$parameters
               parameters
minsplit            20.00
minbucket            7.00
cp                   0.01
maxcompete           4.00
maxsurrogate         5.00
usesurrogate         2.00
surrogatestyle       0.00
maxdepth            30.00
xval                10.00

$splits
          name count ncat      improve index        adj
1  spontaneous   223   -1 1.101199e+01   0.5 0.00000000
2          age   223    1 4.012376e-01  24.5 0.00000000
3       parity   223   -1 1.614884e-01   5.5 0.00000000
4      induced   223    1 1.074566e-01   0.5 0.00000000
5    education   223    3 5.389627e-02   1.0 0.00000000
6       parity     0   -1 6.367713e-01   3.5 0.14736842
7      induced     0    1 6.143498e-01   0.5 0.09473684
8          age     0    1 5.874439e-01  22.0 0.03157895
9       parity    95    1 3.848662e+00   3.5 0.00000000
10 spontaneous    95   -1 3.205054e+00   1.5 0.00000000
11         age    95   -1 2.794648e+00  30.5 0.00000000
12     induced    95    1 2.890226e-01   0.5 0.00000000
13   education    95    3 2.547368e-01   2.0 0.00000000
14     induced     0    1 8.210526e-01   1.5 0.19047619
15   education     0    3 8.105263e-01   3.0 0.14285714
16         age    74   -1 4.241733e+00  30.5 0.00000000
17 spontaneous    74   -1 4.006567e+00   1.5 0.00000000
18      parity    74    1 1.496324e-01   1.5 0.00000000
19   education    74    3 3.611794e-02   4.0 0.00000000
20     induced    74   -1 1.453066e-04   0.5 0.00000000
21   education     0    3 5.675676e-01   5.0 0.05882353
22      parity     0    1 5.540541e-01   1.5 0.02941176
23 spontaneous    40   -1 2.002381e+00   1.5 0.00000000
24         age    40    1 1.800000e+00  28.5 0.00000000
25   education    40    3 8.357143e-01   6.0 0.00000000
26      parity    40    1 2.423077e-01   1.5 0.00000000
27     induced    40   -1 1.125000e-01   0.5 0.00000000

$importance
            importance
spontaneous 13.0143706
parity       5.5962386
age          4.5894799
induced      1.7763196
education    0.7993226

$ordered
            ordered
age           FALSE
parity        FALSE
education     FALSE
spontaneous   FALSE
induced       FALSE

$call
                                                                                                      call
1 rpart::rpart(formula=infert_formula,data=train_test_classification$f$train$f1,,model=TRUE,x=TRUE,y=TRUE)

result bundles the pruned model’s internals — node frame (one row per node), the cp table, control parameters, raw splits, importance, factor-ordered flags, and the model call — for programmatic inspection or export, without needing to pick through model’s S3 structure directly.

Rpart Regression

The regression tree predicts medv (median owner-occupied home value, in $1000s) from the 13 other variables in the classic MASS::Boston housing dataset.

Growing and pruning the tree

Code
result<-data.frame(rtree_regression$cptable)
result$nsplit<-factor(result$nsplit+1)
minimun_size<-as.numeric(as.character(result[which.min(result[,"xerror"]),"nsplit"]))
initial_model<-rtree_regression
model<-rpart::prune(rtree_regression,cp=rtree_regression$cptable[which.min(rtree_regression$cptable[,"xerror"]),"CP"])

As with the classification tree, rtree_regression is pruned to the CP value that minimizes the cross-validated error — here, cross-validated sum-of-squares error rather than a classification loss. In this run the minimum xerror is again at the largest tree in the cptable, so the pruned tree keeps all 9 terminal nodes of the grown tree.

Variable importance

Code
importance<-model$variable.importance
importance<-data.frame(names=names(importance),importance=importance)
importance$names<-factor(importance$names,levels=rev(as.character(importance$names)))
plot_importance<-ggplot(importance,aes(x=names,y=importance))+
  geom_bar(stat='identity')+
  labs(title="Importance Plot",y="Importance (sum of split improvements)",x="Predictor")+
  theme_bw(base_size=10)+
  scale_x_discrete(limits=rev(levels(names)))+
  coord_flip()
plot_importance

For Boston housing, rm (average rooms per dwelling) and lstat (% lower-status population) are almost always the two dominant predictors of medv — this importance ranking is the tree-based analogue of the strong linear associations well known from OLS models fit to this same dataset.

The full, unpruned tree

Code
rtree_regression
n= 455 

node), split, n, deviance, yval
      * denotes terminal node

 1) root 455 39028.0200 22.62110  
   2) rm< 6.941 383 14615.6000 19.86214  
     4) lstat>=14.4 154  2953.9890 14.98182  
       8) crim>=6.99237 64  1004.3600 12.05156 *
       9) crim< 6.99237 90  1009.3230 17.06556 *
     5) lstat< 14.4 229  5527.0850 23.14410  
      10) lstat>=9.715 105   658.5320 20.74000 *
      11) lstat< 9.715 124  3747.8000 25.17984  
        22) age< 89.45 116  1750.8600 24.44052  
          44) rm< 6.543 73   676.5392 22.67945 *
          45) rm>=6.543 43   463.5707 27.43023 *
        23) age>=89.45 8  1014.1600 35.90000 *
   3) rm>=6.941 72  5989.0990 37.29722  
     6) rm< 7.435 43  1888.1360 31.99070  
      12) lstat>=9.65 7   432.9971 23.05714 *
      13) lstat< 9.65 36   787.8522 33.72778 *
     7) rm>=7.435 29  1094.7260 45.16552 *

The unpruned tree’s text form: each node reports its split rule, the number of training observations reaching it (n), the deviance (residual sum of squares) within the node, and the predicted medv (the node mean).

Choosing the tree size via cross-validation

Code
rpart::plotcp(model)

Code
rpart::rsq.rpart(model)

Regression tree:
rpart::rpart(formula = boston_formula, data = train_test_regression$f$train$f1, 
    model = TRUE, x = TRUE, y = TRUE)

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

Root node error: 39028/455 = 85.776

n= 455 

        CP nsplit rel error  xerror     xstd
1 0.472054      0   1.00000 1.00432 0.087594
2 0.157183      1   0.52795 0.58725 0.058026
3 0.077028      2   0.37076 0.43127 0.050213
4 0.028717      3   0.29374 0.33234 0.043749
5 0.025181      4   0.26502 0.34388 0.049606
6 0.024093      5   0.23984 0.33471 0.049507
7 0.017098      6   0.21574 0.31787 0.049487
8 0.015649      7   0.19865 0.32133 0.049491
9 0.010000      8   0.18300 0.30027 0.049051

Here rsq.rpart()’s R² panel is a literal R²: the proportion of medv’s variance explained, both on the training data (apparent) and under cross-validation. The gap between the two lines is the model’s optimism — how much worse the tree performs on unseen data than on the data it was fit to. Here it grows from about 0.06 after one split to about 0.12 at the largest tree (apparent R² ≈ 0.82 vs. cross-validated ≈ 0.70), while cross-validated R² is still at its maximum at the largest tree, so no pruning was needed.

Error-by-size summary

Code
error<-data.frame(model$cptable)
error$nsplit<-factor(error$nsplit+1)
tree_size<-error[which.min(error[,"xerror"]),"nsplit"]
error<-reshape2::melt(error,id.vars="nsplit")
names(error)<-c("Split","Metric","value")
plot_prune<-ggplot(error,aes(x=Split,y=value,color=Metric))+
  geom_line(aes(group=Metric))+
  geom_point()+
  labs(title=paste("Error Plot","Suggested Size:",tree_size),y="Metric value",x="Size of Tree")+
  theme_bw(base_size=10)
plot_prune

Same four cptable series (CP, rel error, xerror, xstd) as in the classification section, plotted against tree size (nsplit + 1), with the size at minimum xerror (9 terminal nodes) called out in the title.

The pruned tree diagram

Code
rpart.plot::rpart.plot(model,type=1)

Each box shows the node’s predicted medv and the percentage of observations it contains. Following the splits down — typically starting with rm or lstat — shows how the tree partitions the housing stock into progressively more homogeneous price segments, e.g. dwellings with the most rooms (the right-most branch on rm) land in the highest-value leaf (mean medv ≈ 45.2), while smaller homes in high-lstat areas land in the lowest (≈ 12.1).

Raw model components

Code
frame<-data.frame(model$frame)
cp<-data.frame(model$cptable)
parameters<-data.frame(parameters=unlist(model$control))
splits<-data.frame(name=row.names(model$splits),model$splits,row.names=NULL)
importance<-data.frame(importance=model$variable.importance)
ordered<-data.frame(ordered=model$ordered)
data<-data.frame(y=model$y,x=model$x,model=model$model)
call<-data.frame(call=call_to_string(model))
result<-list(frame=frame,cp=cp,parameters=parameters,splits=splits,importance=importance,ordered=ordered,call=call)

print(result)
$frame
      var   n  wt        dev     yval  complexity ncompete nsurrogate
1      rm 455 455 39028.0175 22.62110 0.472053621        4          5
2   lstat 383 383 14615.6010 19.86214 0.157182655        4          5
4    crim 154 154  2953.9891 14.98182 0.024093103        4          5
8  <leaf>  64  64  1004.3598 12.05156 0.008611929        0          0
9  <leaf>  90  90  1009.3232 17.06556 0.005709086        0          0
5   lstat 229 229  5527.0845 23.14410 0.028716625        4          5
10 <leaf> 105 105   658.5320 20.74000 0.001051925        0          0
11    age 124 124  3747.7996 25.17984 0.025181398        4          5
22     rm 116 116  1750.8596 24.44052 0.015649006        4          5
44 <leaf>  73  73   676.5392 22.67945 0.004513536        0          0
45 <leaf>  43  43   463.5707 27.43023 0.003416614        0          0
23 <leaf>   8   8  1014.1600 35.90000 0.010000000        0          0
3      rm  72  72  5989.0994 37.29722 0.077027680        4          5
6   lstat  43  43  1888.1363 31.99070 0.017097638        4          5
12 <leaf>   7   7   432.9971 23.05714 0.010000000        0          0
13 <leaf>  36  36   787.8522 33.72778 0.003580613        0          0
7  <leaf>  29  29  1094.7255 45.16552 0.009323713        0          0

$cp
          CP nsplit rel.error    xerror       xstd
1 0.47205362      0 1.0000000 1.0043157 0.08759429
2 0.15718266      1 0.5279464 0.5872495 0.05802625
3 0.07702768      2 0.3707637 0.4312720 0.05021296
4 0.02871663      3 0.2937360 0.3323424 0.04374889
5 0.02518140      4 0.2650194 0.3438781 0.04960597
6 0.02409310      5 0.2398380 0.3347075 0.04950675
7 0.01709764      6 0.2157449 0.3178695 0.04948660
8 0.01564901      7 0.1986473 0.3213327 0.04949119
9 0.01000000      8 0.1829983 0.3002653 0.04905131

$parameters
               parameters
minsplit            20.00
minbucket            7.00
cp                   0.01
maxcompete           4.00
maxsurrogate         5.00
usesurrogate         2.00
surrogatestyle       0.00
maxdepth            30.00
xval                10.00

$splits
      name count ncat    improve      index        adj
1       rm   455   -1 0.47205362   6.941000 0.00000000
2    lstat   455    1 0.43775919   9.725000 0.00000000
3    indus   455    1 0.26019021   6.660000 0.00000000
4  ptratio   455    1 0.24287674  19.650000 0.00000000
5      nox   455    1 0.21408796   0.669500 0.00000000
6    lstat     0    1 0.89010989   4.150000 0.30555556
7  ptratio     0    1 0.86593407  14.550000 0.15277778
8       zn     0   -1 0.85274725  81.250000 0.06944444
9    indus     0    1 0.85274725   1.605000 0.06944444
10    crim     0    1 0.84615385   0.013355 0.02777778
11   lstat   383    1 0.41972461  14.400000 0.00000000
12     nox   383    1 0.26937201   0.669500 0.00000000
13    crim   383    1 0.23890675   9.280695 0.00000000
14 ptratio   383    1 0.21640988  19.900000 0.00000000
15     age   383    1 0.21263549  75.750000 0.00000000
16     age     0    1 0.80939948  88.100000 0.52597403
17     dis     0   -1 0.77806789   2.239350 0.44805195
18     nox     0    1 0.77545692   0.576500 0.44155844
19   indus     0    1 0.77284595  16.570000 0.43506494
20     tax     0    1 0.77284595 434.500000 0.43506494
21    crim   154    1 0.31831737   6.992370 0.00000000
22     nox   154    1 0.31053788   0.607000 0.00000000
23     dis   154   -1 0.29028800   2.003700 0.00000000
24     tax   154    1 0.25291593 567.500000 0.00000000
25   lstat   154    1 0.24584057  19.830000 0.00000000
26     rad     0    1 0.87012987  16.000000 0.68750000
27     tax     0    1 0.85714286 567.500000 0.65625000
28     nox     0    1 0.74675325   0.661500 0.39062500
29     dis     0   -1 0.74025974   2.202000 0.37500000
30     age     0    1 0.69480519  98.600000 0.26562500
31   lstat   229    1 0.20277471   9.715000 0.00000000
32      rm   229   -1 0.18504482   6.543000 0.00000000
33     dis   229    1 0.14439126   1.600900 0.00000000
34   indus   229    1 0.06429180   4.100000 0.00000000
35     tax   229    1 0.06289683 222.500000 0.00000000
36     nox     0    1 0.73799127   0.519000 0.42857143
37   indus     0    1 0.72489083   7.625000 0.40000000
38      rm     0   -1 0.72052402   6.260000 0.39047619
39     dis     0   -1 0.69868996   4.480250 0.34285714
40    crim     0    1 0.68995633   0.130245 0.32380952
41     age   124   -1 0.26222854  89.450000 0.00000000
42     nox   124   -1 0.25623370   0.618000 0.00000000
43     dis   124    1 0.23254331   2.149100 0.00000000
44    crim   124   -1 0.21670627   1.383930 0.00000000
45     tax   124   -1 0.19648951 426.000000 0.00000000
46     dis     0    1 0.98387097   2.149100 0.75000000
47     nox     0   -1 0.97580645   0.589000 0.62500000
48 ptratio     0    1 0.96774194  14.750000 0.50000000
49    crim     0   -1 0.95967742   1.163695 0.37500000
50   indus     0   -1 0.95967742  16.570000 0.37500000
51      rm   116   -1 0.34882849   6.543000 0.00000000
52   lstat   116    1 0.25126720   5.410000 0.00000000
53 ptratio   116    1 0.14390779  15.750000 0.00000000
54     tax   116    1 0.07743844 267.500000 0.00000000
55     nox   116    1 0.07722395   0.512500 0.00000000
56   lstat     0    1 0.74137931   5.055000 0.30232558
57      zn     0   -1 0.68965517  31.500000 0.16279070
58 ptratio     0    1 0.68965517  15.750000 0.16279070
59    crim     0    1 0.67241379   0.017895 0.11627907
60     age     0   -1 0.66379310  68.650000 0.09302326
61      rm    72   -1 0.50195153   7.435000 0.00000000
62   lstat    72    1 0.37062504   4.680000 0.00000000
63 ptratio    72    1 0.25679102  19.700000 0.00000000
64     rad    72    1 0.21966661  16.000000 0.00000000
65    crim    72    1 0.21966661   2.742235 0.00000000
66   lstat     0    1 0.79166667   4.330000 0.48275862
67 ptratio     0    1 0.66666667  14.750000 0.17241379
68   indus     0   -1 0.63888889  18.840000 0.10344828
69    chas     0   -1 0.63888889   0.500000 0.10344828
70   black     0    1 0.63888889 389.885000 0.10344828
71   lstat    43    1 0.35341036   9.650000 0.00000000
72 ptratio    43    1 0.24469424  19.700000 0.00000000
73     rad    43    1 0.17534867   7.500000 0.00000000
74     nox    43    1 0.16270057   0.639000 0.00000000
75   indus    43    1 0.14847490   9.500000 0.00000000
76    crim     0    1 0.90697674   0.724605 0.42857143
77     nox     0    1 0.90697674   0.659000 0.42857143
78     rad     0    1 0.88372093  16.000000 0.28571429
79     tax     0    1 0.88372093 534.500000 0.28571429
80   indus     0    1 0.86046512  15.015000 0.14285714

$importance
        importance
rm      22477.9316
lstat   15187.8465
indus    5171.4753
age      4515.9648
nox      4456.5997
dis      4222.5449
ptratio  3923.8046
tax      3476.6470
crim     2540.5153
zn       1378.8214
rad       837.1138
black     310.9901
chas      310.9901

$ordered
        ordered
crim      FALSE
zn        FALSE
indus     FALSE
chas      FALSE
nox       FALSE
rm        FALSE
age       FALSE
dis       FALSE
rad       FALSE
tax       FALSE
ptratio   FALSE
black     FALSE
lstat     FALSE

$call
                                                                                                  call
1 rpart::rpart(formula=boston_formula,data=train_test_regression$f$train$f1,,model=TRUE,x=TRUE,y=TRUE)

Same bundling as the classification section — the pruned regression model’s frame, cp table, parameters, splits, importance, ordered flags, and call.