p <- seq(0, 1, length.out = 200)
gini <- 2 * p * (1 - p)
entropy <- -(p * log(p) + (1 - p) * log(1 - p))
entropy[is.nan(entropy)] <- 0
class.error <- 1 - pmax(p, 1 - p)
plot(p, gini, type = "l", col = "red", lwd = 2, ylim = c(0, 0.75),
xlab = expression(hat(p)[m1]), ylab = "Value",
main = "Gini, Entropy, and Classification Error")
lines(p, entropy, col = "blue", lwd = 2)
lines(p, class.error, col = "darkgreen", lwd = 2)
legend("topright", legend = c("Gini index", "Entropy", "Classification error"),
col = c("red", "blue", "darkgreen"), lwd = 2)
## Warning: package 'ISLR2' was built under R version 4.5.3
## Warning: package 'tree' was built under R version 4.5.3
## Warning: package 'randomForest' was built under R version 4.5.3
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
train <- sample(1:nrow(Carseats), nrow(Carseats) / 2)
Carseats.train <- Carseats[train, ]
Carseats.test <- Carseats[-train, ]
tree.carseats <- tree(Sales ~ ., data = Carseats.train)
summary(tree.carseats)
##
## Regression tree:
## tree(formula = Sales ~ ., data = Carseats.train)
## Variables actually used in tree construction:
## [1] "ShelveLoc" "Price" "Age" "Advertising" "CompPrice"
## [6] "US"
## Number of terminal nodes: 18
## Residual mean deviance: 2.167 = 394.3 / 182
## Distribution of residuals:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -3.88200 -0.88200 -0.08712 0.00000 0.89590 4.09900
plot(tree.carseats)
text(tree.carseats, pretty = 0, cex = 0.7)
pred <- predict(tree.carseats, newdata = Carseats.test)
tree.mse <- mean((Carseats.test$Sales - pred)^2)
tree.mse
## [1] 4.922039
set.seed(1)
cv.carseats <- cv.tree(tree.carseats)
plot(cv.carseats$size, cv.carseats$dev, type = "b",
xlab = "Tree size", ylab = "CV deviance")
best.size <- cv.carseats$size[which.min(cv.carseats$dev)]
best.size
## [1] 18
prune.carseats <- prune.tree(tree.carseats, best = best.size)
plot(prune.carseats)
text(prune.carseats, pretty = 0, cex = 0.7)
pred.prune <- predict(prune.carseats, newdata = Carseats.test)
prune.mse <- mean((Carseats.test$Sales - pred.prune)^2)
prune.mse
## [1] 4.922039
set.seed(1)
bag.carseats <- randomForest(Sales ~ ., data = Carseats.train,
mtry = ncol(Carseats.train) - 1,
importance = TRUE)
pred.bag <- predict(bag.carseats, newdata = Carseats.test)
bag.mse <- mean((Carseats.test$Sales - pred.bag)^2)
bag.mse
## [1] 2.605253
importance(bag.carseats)
## %IncMSE IncNodePurity
## CompPrice 24.8888481 170.182937
## Income 4.7121131 91.264880
## Advertising 12.7692401 97.164338
## Population -1.8074075 58.244596
## Price 56.3326252 502.903407
## ShelveLoc 48.8886689 380.032715
## Age 17.7275460 157.846774
## Education 0.5962186 44.598731
## Urban 0.1728373 9.822082
## US 4.2172102 18.073863
varImpPlot(bag.carseats)
set.seed(1)
rf.carseats <- randomForest(Sales ~ ., data = Carseats.train,
mtry = (ncol(Carseats.train) - 1) / 3,
importance = TRUE)
pred.rf <- predict(rf.carseats, newdata = Carseats.test)
rf.mse <- mean((Carseats.test$Sales - pred.rf)^2)
rf.mse
## [1] 2.960559
importance(rf.carseats)
## %IncMSE IncNodePurity
## CompPrice 14.8840765 158.82956
## Income 4.3293950 125.64850
## Advertising 8.2215192 107.51700
## Population -0.9488134 97.06024
## Price 34.9793386 385.93142
## ShelveLoc 34.9248499 298.54210
## Age 14.3055912 178.42061
## Education 1.3117842 70.49202
## Urban -1.2680807 17.39986
## US 6.1139696 33.98963
varImpPlot(rf.carseats)
mtry.vals <- 1:10
mse.vals <- rep(NA, length(mtry.vals))
for (m in mtry.vals) {
set.seed(1)
fit <- randomForest(Sales ~ ., data = Carseats.train, mtry = m)
pred <- predict(fit, newdata = Carseats.test)
mse.vals[m] <- mean((Carseats.test$Sales - pred)^2)
}
plot(mtry.vals, mse.vals, type = "b", xlab = "mtry", ylab = "Test MSE")
The random forest with m = p/3 ≈ 3 achieved a test MSE of 2.961, which
is better than the single pruned tree (4.922) but slightly worse than
full bagging with m = p = 10 (2.605). importance() shows Price and
ShelveLoc are by far the most important predictors, followed by
CompPrice and Age, with Population and Urban not contributing much. Test
MSE generally decreases as m increases. Restricting the number of
candidate variables at each split hurts more than it helps, because
Price and ShelveLoc are very dominant, so excluding them from many
splits costs more in individual tree quality than is gained from
decorrelating the trees.
library(BART)
## Warning: package 'BART' was built under R version 4.5.3
## Loading required package: nlme
## Loading required package: survival
x <- Carseats[, -1]
y <- Carseats[, "Sales"]
xtrain <- x[train, ]
ytrain <- y[train]
xtest <- x[-train, ]
ytest <- y[-train]
set.seed(1)
bart.fit <- gbart(xtrain, ytrain, x.test = xtest)
## *****Calling gbart: type=1
## *****Data:
## data:n,p,np: 200, 14, 200
## y1,yn: 2.781850, 1.091850
## x1,x[n*p]: 107.000000, 1.000000
## xp1,xp[np*p]: 111.000000, 1.000000
## *****Number of Trees: 200
## *****Number of Cut Points: 63 ... 1
## *****burn,nd,thin: 100,1000,1
## *****Prior:beta,alpha,tau,nu,lambda,offset: 2,0.95,0.273474,3,0.23074,7.57815
## *****sigma: 1.088371
## *****w (weights): 1.000000 ... 1.000000
## *****Dirichlet:sparse,theta,omega,a,b,rho,augment: 0,0,1,0.5,1,14,0
## *****printevery: 100
##
## MCMC
## done 0 (out of 1100)
## done 100 (out of 1100)
## done 200 (out of 1100)
## done 300 (out of 1100)
## done 400 (out of 1100)
## done 500 (out of 1100)
## done 600 (out of 1100)
## done 700 (out of 1100)
## done 800 (out of 1100)
## done 900 (out of 1100)
## done 1000 (out of 1100)
## time: 3s
## trcnt,tecnt: 1000,1000
pred.bart <- bart.fit$yhat.test.mean
bart.mse <- mean((ytest - pred.bart)^2)
bart.mse
## [1] 1.450842
Fitting BART with gbart() yielded a test MSE of 1.451, which is the best result. Rather than growing many independent trees on bootstrap samples, BART fits an ensemble of intentionally weak, shallow trees, where each tree explains only the residual left by the others. It also discourages any single tree from becoming too influential, so this produces a more efficient, better-generalizing model here.
data(OJ)
set.seed(1)
train <- sample(1:nrow(OJ), 800)
OJ.train <- OJ[train, ]
OJ.test <- OJ[-train, ]
tree.oj <- tree(Purchase ~ ., data = OJ.train)
summary(tree.oj)
##
## Classification tree:
## tree(formula = Purchase ~ ., data = OJ.train)
## Variables actually used in tree construction:
## [1] "LoyalCH" "PriceDiff" "SpecialCH" "ListPriceDiff"
## [5] "PctDiscMM"
## Number of terminal nodes: 9
## Residual mean deviance: 0.7432 = 587.8 / 791
## Misclassification error rate: 0.1588 = 127 / 800
tree.oj
## node), split, n, deviance, yval, (yprob)
## * denotes terminal node
##
## 1) root 800 1073.00 CH ( 0.60625 0.39375 )
## 2) LoyalCH < 0.5036 365 441.60 MM ( 0.29315 0.70685 )
## 4) LoyalCH < 0.280875 177 140.50 MM ( 0.13559 0.86441 )
## 8) LoyalCH < 0.0356415 59 10.14 MM ( 0.01695 0.98305 ) *
## 9) LoyalCH > 0.0356415 118 116.40 MM ( 0.19492 0.80508 ) *
## 5) LoyalCH > 0.280875 188 258.00 MM ( 0.44149 0.55851 )
## 10) PriceDiff < 0.05 79 84.79 MM ( 0.22785 0.77215 )
## 20) SpecialCH < 0.5 64 51.98 MM ( 0.14062 0.85938 ) *
## 21) SpecialCH > 0.5 15 20.19 CH ( 0.60000 0.40000 ) *
## 11) PriceDiff > 0.05 109 147.00 CH ( 0.59633 0.40367 ) *
## 3) LoyalCH > 0.5036 435 337.90 CH ( 0.86897 0.13103 )
## 6) LoyalCH < 0.764572 174 201.00 CH ( 0.73563 0.26437 )
## 12) ListPriceDiff < 0.235 72 99.81 MM ( 0.50000 0.50000 )
## 24) PctDiscMM < 0.196196 55 73.14 CH ( 0.61818 0.38182 ) *
## 25) PctDiscMM > 0.196196 17 12.32 MM ( 0.11765 0.88235 ) *
## 13) ListPriceDiff > 0.235 102 65.43 CH ( 0.90196 0.09804 ) *
## 7) LoyalCH > 0.764572 261 91.20 CH ( 0.95785 0.04215 ) *
plot(tree.oj)
text(tree.oj, pretty = 0, cex = 0.7)
##9e
tree.pred <- predict(tree.oj, newdata = OJ.test, type = "class")
table(Predicted = tree.pred, Actual = OJ.test$Purchase)
## Actual
## Predicted CH MM
## CH 160 38
## MM 8 64
test.error <- mean(tree.pred != OJ.test$Purchase)
test.error
## [1] 0.1703704
set.seed(1)
cv.oj <- cv.tree(tree.oj, FUN = prune.misclass)
cv.oj
## $size
## [1] 9 8 7 4 2 1
##
## $dev
## [1] 145 145 146 146 167 315
##
## $k
## [1] -Inf 0.000000 3.000000 4.333333 10.500000 151.000000
##
## $method
## [1] "misclass"
##
## attr(,"class")
## [1] "prune" "tree.sequence"
plot(cv.oj$size, cv.oj$dev, type = "b",
xlab = "Tree size", ylab = "CV classification errors")
Cross-validation showed sizes 9 and 8 tied for the lowest classification error of 145 misclassifications, with all smaller sizes performing worse. Size 8 was chosen for pruning since it achieves the same CV performance as the full tree with one fewer terminal node.
best.size <- 8
best.size
## [1] 8
prune.oj <- prune.misclass(tree.oj, best = best.size)
plot(prune.oj)
text(prune.oj, pretty = 0, cex = 0.7)
The pruned 8-node tree’s training error of 0.15875 is identical to the unpruned 9-node tree’s training error of 0.15875.
pruned.train.pred <- predict(prune.oj, newdata = OJ.train, type = "class")
pruned.train.error <- mean(pruned.train.pred != OJ.train$Purchase)
pruned.train.error
## [1] 0.15875
unpruned.train.pred <- predict(tree.oj, newdata = OJ.train, type = "class")
unpruned.train.error <- mean(unpruned.train.pred != OJ.train$Purchase)
unpruned.train.error
## [1] 0.15875
Likewise, the pruned tree’s test error of 0.1703704 equals the unpruned tree’s test error of 0.1703704.
pruned.test.pred <- predict(prune.oj, newdata = OJ.test, type = "class")
pruned.test.error <- mean(pruned.test.pred != OJ.test$Purchase)
pruned.test.error
## [1] 0.1703704
unpruned.test.pred <- predict(tree.oj, newdata = OJ.test, type = "class")
unpruned.test.error <- mean(unpruned.test.pred != OJ.test$Purchase)
unpruned.test.error
## [1] 0.1703704