library(ISLR2)
library(tree)
library(randomForest)
library(BART)

set.seed(123)

Question 3

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.

Question 8

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.

8(a)

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

8(b)

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.

8(c)

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.

8(d)

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.

8(e)

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"
)

8(f)

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.

Question 9

9(a)

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

9(b)

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.

9(c)

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 ) *

9(d)

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.

9(e)

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.

9(f)

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"

9(g)

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"
)

9(h)

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.

9(i)

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

9(j)

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.

9(k)

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.