library(ISLR2)
library(tree)
library(randomForest)
library(BART)
set.seed(123)
Consider the Gini index, classification error, and entropy in a simple classification setting with two classes. Create a single plot that displays each of these quantities as a function of \(\hat{p}_{m1}\). The x-axis should display \(\hat{p}_{m1}\), ranging from 0 to 1, and the y-axis should display the value of the Gini index, classification error, and entropy.
p <- seq(0.001, 0.999, length.out = 1000)
gini <- 2 * p * (1 - p)
classification_error <- 1 - pmax(p, 1 - p)
entropy <- -(p * log(p) + (1 - p) * log(1 - p))
plot(
p,
gini,
type = "l",
lwd = 2,
ylim = c(0, max(entropy)),
xlab = expression(hat(p)[m1]),
ylab = "Value",
main = "Gini Index, Classification Error, and Entropy"
)
lines(p, classification_error, lwd = 2, lty = 2)
lines(p, entropy, lwd = 2, lty = 3)
legend(
"topright",
legend = c("Gini Index", "Classification Error", "Entropy"),
lty = c(1, 2, 3),
lwd = 2
)
The three measures are lowest when one class has a probability close to 0 or 1. They are highest when both classes have similar probabilities near 0.5.
In the lab, a classification tree was applied to the Carseats data set after converting Sales into a qualitative response variable. Now we will seek to predict Sales using regression trees and related approaches, treating the response as a quantitative variable.
Split the data set into a training set and a test set.
data("Carseats")
set.seed(123)
train_rows <- sample(
1:nrow(Carseats),
size = floor(0.70 * nrow(Carseats))
)
carseats_train <- Carseats[train_rows, ]
carseats_test <- Carseats[-train_rows, ]
nrow(carseats_train)
## [1] 280
nrow(carseats_test)
## [1] 120
Fit a regression tree to the training set. Plot the tree, and interpret the results. What test MSE do you obtain?
carseats_tree <- tree(
Sales ~ .,
data = carseats_train
)
summary(carseats_tree)
##
## Regression tree:
## tree(formula = Sales ~ ., data = carseats_train)
## Variables actually used in tree construction:
## [1] "ShelveLoc" "Price" "CompPrice" "Age" "Advertising"
## Number of terminal nodes: 19
## Residual mean deviance: 2.373 = 619.2 / 261
## Distribution of residuals:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -4.1570 -1.0160 0.1123 0.0000 0.8903 4.0310
plot(carseats_tree)
text(carseats_tree, pretty = 0)
tree_predictions <- predict(
carseats_tree,
newdata = carseats_test
)
tree_test_mse <- mean(
(carseats_test$Sales - tree_predictions)^2
)
tree_test_mse
## [1] 3.602818
The tree uses variables such as price, shelving location, advertising, and age to divide the observations into groups. The value at each terminal node is the predicted Sales value for that group.
The test MSE is 3.603.
Use cross-validation in order to determine the optimal level of tree complexity. Does pruning the tree improve the test MSE?
set.seed(123)
carseats_cv <- cv.tree(carseats_tree)
plot(
carseats_cv$size,
carseats_cv$dev,
type = "b",
xlab = "Tree Size",
ylab = "Cross-Validation Error"
)
best_tree_size <- carseats_cv$size[
which.min(carseats_cv$dev)
]
best_tree_size
## [1] 8
carseats_pruned <- prune.tree(
carseats_tree,
best = best_tree_size
)
plot(carseats_pruned)
text(carseats_pruned, pretty = 0)
pruned_predictions <- predict(
carseats_pruned,
newdata = carseats_test
)
pruned_test_mse <- mean(
(carseats_test$Sales - pruned_predictions)^2
)
tree_test_mse
## [1] 3.602818
pruned_test_mse
## [1] 4.353022
## Pruning did not improve the test MSE because the pruned tree did not have a lower test MSE.
Use the bagging approach in order to analyze this data. What test MSE do you obtain? Use the importance() function to determine which variables are most important.
set.seed(123)
bag_model <- randomForest(
Sales ~ .,
data = carseats_train,
mtry = ncol(carseats_train) - 1,
importance = TRUE
)
bag_predictions <- predict(
bag_model,
newdata = carseats_test
)
bag_test_mse <- mean(
(carseats_test$Sales - bag_predictions)^2
)
bag_test_mse
## [1] 2.28171
importance(bag_model)
## %IncMSE IncNodePurity
## CompPrice 34.4262904 257.760843
## Income 7.4645949 113.727916
## Advertising 20.4003141 147.788939
## Population -3.0115418 66.776729
## Price 64.3523306 630.500603
## ShelveLoc 72.8822146 689.811838
## Age 17.7747876 182.686692
## Education 3.4693027 63.408518
## Urban -0.8360222 8.755624
## US 0.2868891 8.107184
varImpPlot(bag_model)
The bagging test MSE is 2.282.
The most important variables are the variables at the top of the importance output and importance plot.
Use random forests to analyze this data. What test MSE do you obtain? Use the importance() function to determine which variables are most important. Describe the effect of m, the number of variables considered at each split, on the error rate obtained.
set.seed(123)
rf_model <- randomForest(
Sales ~ .,
data = carseats_train,
mtry = 4,
importance = TRUE
)
rf_predictions <- predict(
rf_model,
newdata = carseats_test
)
rf_test_mse <- mean(
(carseats_test$Sales - rf_predictions)^2
)
rf_test_mse
## [1] 2.438521
importance(rf_model)
## %IncMSE IncNodePurity
## CompPrice 22.41459009 233.29077
## Income 7.98697808 148.74815
## Advertising 16.92611716 181.07934
## Population 0.07259484 110.88734
## Price 48.34218151 520.49480
## ShelveLoc 53.83185741 567.09233
## Age 18.33155353 262.17839
## Education 2.54933667 89.38007
## Urban 0.33859583 16.45825
## US 4.38150375 18.87120
varImpPlot(rf_model)
The random forest test MSE is 2.439.
m_values <- 1:(ncol(carseats_train) - 1)
mtry_mse <- rep(NA, length(m_values))
for (i in seq_along(m_values)) {
set.seed(123)
model_m <- randomForest(
Sales ~ .,
data = carseats_train,
mtry = m_values[i]
)
predictions_m <- predict(
model_m,
newdata = carseats_test
)
mtry_mse[i] <- mean(
(carseats_test$Sales - predictions_m)^2
)
}
mtry_results <- data.frame(
m = m_values,
Test_MSE = mtry_mse
)
mtry_results
## m Test_MSE
## 1 1 4.529333
## 2 2 3.253462
## 3 3 2.783991
## 4 4 2.451195
## 5 5 2.310888
## 6 6 2.253665
## 7 7 2.252327
## 8 8 2.216917
## 9 9 2.242438
## 10 10 2.306931
plot(
mtry_results$m,
mtry_results$Test_MSE,
type = "b",
xlab = "Number of Variables Considered at Each Split",
ylab = "Test MSE"
)
Now analyze the data using BART, and report your results.
x_train <- model.matrix(
Sales ~ .,
data = carseats_train
)[, -1]
x_test <- model.matrix(
Sales ~ .,
data = carseats_test
)[, -1]
y_train <- carseats_train$Sales
y_test <- carseats_test$Sales
set.seed(123)
bart_model <- gbart(
x.train = x_train,
y.train = y_train,
x.test = x_test,
ntree = 200,
ndpost = 1000,
nskip = 250,
printevery = 1000
)
## *****Calling gbart: type=1
## *****Data:
## data:n,p,np: 280, 11, 120
## y1,yn: 3.222214, -2.907786
## x1,x[n*p]: 104.000000, 1.000000
## xp1,xp[np*p]: 138.000000, 1.000000
## *****Number of Trees: 200
## *****Number of Cut Points: 67 ... 1
## *****burn,nd,thin: 250,1000,1
## *****Prior:beta,alpha,tau,nu,lambda,offset: 2,0.95,0.284787,3,0.198596,7.43779
## *****sigma: 1.009719
## *****w (weights): 1.000000 ... 1.000000
## *****Dirichlet:sparse,theta,omega,a,b,rho,augment: 0,0,1,0.5,1,11,0
## *****printevery: 1000
##
## MCMC
## done 0 (out of 1250)
## done 1000 (out of 1250)
## time: 2s
## trcnt,tecnt: 1000,1000
bart_predictions <- colMeans(
bart_model$yhat.test
)
bart_test_mse <- mean(
(y_test - bart_predictions)^2
)
bart_test_mse
## [1] 1.652097
The BART test MSE is 1.652.
Create a training set containing a random sample of 800 observations, and a test set containing the remaining observations.
data("OJ")
set.seed(123)
oj_train_rows <- sample(
1:nrow(OJ),
size = 800
)
oj_train <- OJ[oj_train_rows, ]
oj_test <- OJ[-oj_train_rows, ]
nrow(oj_train)
## [1] 800
nrow(oj_test)
## [1] 270
Fit a tree to the training data, with Purchase as the response and the other variables as predictors. Use the summary() function to produce summary statistics about the tree, and describe the results obtained. What is the training error rate? How many terminal nodes does the tree have?
oj_tree <- tree(
Purchase ~ .,
data = oj_train
)
oj_tree_summary <- summary(oj_tree)
oj_tree_summary
##
## Classification tree:
## tree(formula = Purchase ~ ., data = oj_train)
## Variables actually used in tree construction:
## [1] "LoyalCH" "PriceDiff"
## Number of terminal nodes: 8
## Residual mean deviance: 0.7625 = 603.9 / 792
## Misclassification error rate: 0.165 = 132 / 800
oj_train_predictions <- predict(
oj_tree,
newdata = oj_train,
type = "class"
)
oj_train_error <- mean(
oj_train_predictions != oj_train$Purchase
)
oj_terminal_nodes <- sum(
oj_tree$frame$var == "<leaf>"
)
oj_train_error
## [1] 0.165
oj_terminal_nodes
## [1] 8
The training error rate is 0.165.
The tree has 8 terminal nodes.
Type in the name of the tree object in order to get a detailed text output. Pick one of the terminal nodes, and interpret the information displayed.
oj_tree
## node), split, n, deviance, yval, (yprob)
## * denotes terminal node
##
## 1) root 800 1071.00 CH ( 0.60875 0.39125 )
## 2) LoyalCH < 0.5036 350 415.10 MM ( 0.28000 0.72000 )
## 4) LoyalCH < 0.276142 170 131.00 MM ( 0.12941 0.87059 )
## 8) LoyalCH < 0.0356415 56 10.03 MM ( 0.01786 0.98214 ) *
## 9) LoyalCH > 0.0356415 114 108.90 MM ( 0.18421 0.81579 ) *
## 5) LoyalCH > 0.276142 180 245.20 MM ( 0.42222 0.57778 )
## 10) PriceDiff < 0.05 74 74.61 MM ( 0.20270 0.79730 ) *
## 11) PriceDiff > 0.05 106 144.50 CH ( 0.57547 0.42453 ) *
## 3) LoyalCH > 0.5036 450 357.10 CH ( 0.86444 0.13556 )
## 6) PriceDiff < -0.39 27 32.82 MM ( 0.29630 0.70370 ) *
## 7) PriceDiff > -0.39 423 273.70 CH ( 0.90071 0.09929 )
## 14) LoyalCH < 0.705326 130 135.50 CH ( 0.78462 0.21538 )
## 28) PriceDiff < 0.145 43 58.47 CH ( 0.58140 0.41860 ) *
## 29) PriceDiff > 0.145 87 62.07 CH ( 0.88506 0.11494 ) *
## 15) LoyalCH > 0.705326 293 112.50 CH ( 0.95222 0.04778 ) *
Create a plot of the tree, and interpret the results.
plot(oj_tree)
text(oj_tree, pretty = 0)
The top splits are the variables that the tree considers most useful. The branches divide customers into smaller groups, and each terminal node predicts either CH or MM.
Predict the response on the test data, and produce a confusion matrix comparing the test labels to the predicted test labels. What is the test error rate?
oj_test_predictions <- predict(
oj_tree,
newdata = oj_test,
type = "class"
)
oj_confusion_matrix <- table(
Actual = oj_test$Purchase,
Predicted = oj_test_predictions
)
oj_test_error <- mean(
oj_test_predictions != oj_test$Purchase
)
oj_confusion_matrix
## Predicted
## Actual CH MM
## CH 150 16
## MM 34 70
oj_test_error
## [1] 0.1851852
The test error rate is 0.185.
Apply the cv.tree() function to the training set in order to determine the optimal tree size.
set.seed(123)
oj_cv <- cv.tree(
oj_tree,
FUN = prune.misclass
)
oj_cv
## $size
## [1] 8 5 3 2 1
##
## $dev
## [1] 139 139 157 167 313
##
## $k
## [1] -Inf 0 8 11 154
##
## $method
## [1] "misclass"
##
## attr(,"class")
## [1] "prune" "tree.sequence"
Produce a plot with tree size on the x-axis and cross-validated classification error rate on the y-axis.
plot(
oj_cv$size,
oj_cv$dev,
type = "b",
xlab = "Tree Size",
ylab = "Cross-Validated Classification Error"
)
Which tree size corresponds to the lowest cross-validated classification error rate?
optimal_oj_size <- oj_cv$size[
which.min(oj_cv$dev)
]
optimal_oj_size
## [1] 8
The tree size with the lowest cross-validated error is 8.
Produce a pruned tree corresponding to the optimal tree size obtained using cross-validation. If cross-validation does not lead to selection of a pruned tree, then create a pruned tree with five terminal nodes.
unpruned_size <- sum(
oj_tree$frame$var == "<leaf>"
)
prune_size <- ifelse(
optimal_oj_size < unpruned_size,
optimal_oj_size,
5
)
oj_pruned_tree <- prune.misclass(
oj_tree,
best = prune_size
)
plot(oj_pruned_tree)
text(oj_pruned_tree, pretty = 0)
prune_size
## [1] 5
Compare the training error rates between the pruned and unpruned trees. Which is higher?
oj_pruned_train_predictions <- predict(
oj_pruned_tree,
newdata = oj_train,
type = "class"
)
oj_pruned_train_error <- mean(
oj_pruned_train_predictions != oj_train$Purchase
)
training_error_comparison <- data.frame(
Model = c("Unpruned Tree", "Pruned Tree"),
Training_Error = c(
oj_train_error,
oj_pruned_train_error
)
)
training_error_comparison
## Model Training_Error
## 1 Unpruned Tree 0.165
## 2 Pruned Tree 0.165
## The two trees have the same training error rate.
Compare the test error rates between the pruned and unpruned trees. Which is higher?
oj_pruned_test_predictions <- predict(
oj_pruned_tree,
newdata = oj_test,
type = "class"
)
oj_pruned_test_error <- mean(
oj_pruned_test_predictions != oj_test$Purchase
)
test_error_comparison <- data.frame(
Model = c("Unpruned Tree", "Pruned Tree"),
Test_Error = c(
oj_test_error,
oj_pruned_test_error
)
)
test_error_comparison
## Model Test_Error
## 1 Unpruned Tree 0.1851852
## 2 Pruned Tree 0.1851852
## The two trees have the same test error rate.