ISLR Chapter 08 (page 361): 3, 8, 9
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 ˆpm1. The x-axis should display ˆpm1, ranging from 0 to 1, and the y-axis should display the value of the Gini index, classification error, and entropy. Hint: In a setting with two classes, ˆpm1 = 1 − ˆpm2. You could make this plot by hand, but it will be much easier to make in R.
p=seq(0,1,0.0001)
#Gini
G=2*p*(1-p)
#Classification Error
E=1-pmax(p,1-p)
#Entropy
D=-(p*log(p) + (1-p)*log(1-p))
plot(p,D, col="red",ylab="")
lines(p,E,col='green')
lines(p,G,col='blue')
legend(0.3,0.15,c("Entropy", "Missclassification","Gini"),lty=c(1,1,1),lwd=c(2.5,2.5,2.5),col=c('red','green','blue'))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.
(a) Split the data set into a training set and a test set.
After loading necessary libraries and our
carseatdataset, now we can split our data into train and test.
set.seed(210)
train_index <- createDataPartition(Carseats$Sales, p=0.5, list = FALSE)
train_set<- Carseats[train_index,]
test_set <- Carseats[-train_index, ]
dim(train_set)## [1] 201 11
## [1] 199 11
(b) Fit a regression tree to the training set. Plot the tree, and interpret the results. What test MSE do you obtain?
## Warning: package 'tree' was built under R version 4.5.3
##
## Regression tree:
## tree(formula = Sales ~ ., data = train_set)
## Variables actually used in tree construction:
## [1] "ShelveLoc" "Price" "CompPrice" "Advertising" "Age"
## [6] "Income" "Education"
## Number of terminal nodes: 17
## Residual mean deviance: 2.049 = 377 / 184
## Distribution of residuals:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -4.56500 -0.91250 -0.01929 0.00000 1.06100 3.78800
> Our tree model reveals that shelf location is the single most
critical factor driving sales; locations with “Good” shelving placement
are split off immediately to the right and yield much higher baseline
sales than “Bad” or “Medium” locations on the left. Within both
branches, price acts as the secondary dominant driver, showing that
lower prices consistently boost sales volumes, while secondary factors
like advertising budgets, competitor pricing, customer age, income
levels, and education refine the deeper branches of the model.
pred_tree <- predict(tree_model, newdata = test_set)
mse_tree <- mean((pred_tree - test_set$Sales)^2)
cat("Test MSE:", mse_tree, "\n")## Test MSE: 5.187061
cv_tree <- cv.tree(tree_model)
plot(cv_tree$size, cv_tree$dev, type = "b", xlab = "Tree Size", ylab = "Deviance")best_size <- cv_tree$size[which.min(cv_tree$dev)]
cat("Optimal Tree Size determined by CV:", best_size, "\n")## Optimal Tree Size determined by CV: 11
Our optimal Tree Size is determined y cross-validation method showing 17, next we will adjust our tree model with prunning.
pruned_tree <- prune.tree(tree_model, best = best_size)
plot(pruned_tree)
text(pruned_tree, pretty = 0, cex = 0.7)Now we can predict using our prunned model and observe the new
Test MSE.
pred_pruned <- predict(pruned_tree, newdata = test_set)
mse_pruned <- mean((pred_pruned - test_set$Sales)^2)
cat("Pruned Tree Test MSE:", mse_pruned, "\n")## Pruned Tree Test MSE: 5.220978
Prunning the previous tree model did not improve the Test MSE yet it showed identical results.
library(randomForest)
set.seed(210)
bag_model <- randomForest(Sales ~ ., data = train_set, mtry = 10, importance = TRUE)
bag_model##
## Call:
## randomForest(formula = Sales ~ ., data = train_set, mtry = 10, importance = TRUE)
## Type of random forest: regression
## Number of trees: 500
## No. of variables tried at each split: 10
##
## Mean of squared residuals: 2.722852
## % Var explained: 63.5
pred_bag <- predict(bag_model, newdata = test_set)
mse_bag <- mean((pred_bag - test_set$Sales)^2)
cat("Bagging Test MSE:", mse_bag, "\n")## Bagging Test MSE: 2.768769
Bagging method improved our model Test MSE by almost cutting in half.
## %IncMSE IncNodePurity
## CompPrice 26.447822 179.793092
## Income 6.082824 87.005853
## Advertising 19.814043 145.077596
## Population -1.500549 49.700263
## Price 55.362102 441.393933
## ShelveLoc 45.731828 339.744246
## Age 17.387162 150.602827
## Education 3.099620 44.343231
## Urban -2.078941 7.867465
## US 1.003939 9.021848
> Based on our model output,
Price and
ShelveLoc are the two most critical predictors of car seat
sales by a wide margin.
set.seed(210)
rf_model <- randomForest(Sales ~ ., data = train_set, mtry = 3, importance = TRUE)
rf_model##
## Call:
## randomForest(formula = Sales ~ ., data = train_set, mtry = 3, importance = TRUE)
## Type of random forest: regression
## Number of trees: 500
## No. of variables tried at each split: 3
##
## Mean of squared residuals: 3.193399
## % Var explained: 57.19
Overall, this model explains 57.19% of the variation in car seat sales and maintains an average squared error of 3.19 on the test data.
pred_rf <- predict(rf_model, newdata = test_set)
mse_rf <- mean((pred_rf - test_set$Sales)^2)
cat("Random Forest Test MSE:", mse_rf, "\n")## Random Forest Test MSE: 3.308276
Random forest model performed better than previous models, however test MSE is higher than bagging method.
## %IncMSE IncNodePurity
## CompPrice 9.34514938 140.13391
## Income 6.09792629 122.70316
## Advertising 14.98808150 140.68426
## Population -1.99147676 99.52757
## Price 37.19383666 350.85843
## ShelveLoc 32.18620982 268.28621
## Age 13.21162501 176.66196
## Education 0.08429424 70.67185
## Urban -2.18030644 13.40461
## US 1.43363795 18.83175
>
Price and ShelveLoc remain the top
variables to drive this model, but their dominance is slightly reduced
as other variables are included.
## Warning: package 'BART' was built under R version 4.5.3
## Loading required package: nlme
## Loading required package: survival
##
## Attaching package: 'survival'
## The following object is masked from 'package:caret':
##
## cluster
X_train <- model.matrix(Sales ~ . - 1, data = train_set)
y_train <- train_set$Sales
X_test <- model.matrix(Sales ~ . - 1, data = test_set)## *****Calling gbart: type=1
## *****Data:
## data:n,p,np: 201, 12, 199
## y1,yn: 2.013284, 2.223284
## x1,x[n*p]: 138.000000, 1.000000
## xp1,xp[np*p]: 111.000000, 1.000000
## *****Number of Trees: 200
## *****Number of Cut Points: 62 ... 1
## *****burn,nd,thin: 100,1000,1
## *****Prior:beta,alpha,tau,nu,lambda,offset: 2,0.95,0.2512,3,0.214239,7.48672
## *****sigma: 1.048731
## *****w (weights): 1.000000 ... 1.000000
## *****Dirichlet:sparse,theta,omega,a,b,rho,augment: 0,0,1,0.5,1,12,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: 4s
## trcnt,tecnt: 1000,1000
pred_bart <- bart_model$yhat.test.mean
mse_bart <- mean((pred_bart - test_set$Sales)^2)
cat("BART Test MSE:", mse_bart, "\n")## BART Test MSE: 1.624166
Among all the models tested, the BART method performed the best, achieving the lowest test error with a Test MSE of 1.62. In comparison, the single decision tree performed the worst MSE (5.19), while bagging method showed Test MSE (2.77) and random forests (3.31).
This problem involves the OJ data set which is part of the ISLR2 package.
## 'data.frame': 1070 obs. of 18 variables:
## $ Purchase : Factor w/ 2 levels "CH","MM": 1 1 1 2 1 1 1 1 1 1 ...
## $ WeekofPurchase: num 237 239 245 227 228 230 232 234 235 238 ...
## $ StoreID : num 1 1 1 1 7 7 7 7 7 7 ...
## $ PriceCH : num 1.75 1.75 1.86 1.69 1.69 1.69 1.69 1.75 1.75 1.75 ...
## $ PriceMM : num 1.99 1.99 2.09 1.69 1.69 1.99 1.99 1.99 1.99 1.99 ...
## $ DiscCH : num 0 0 0.17 0 0 0 0 0 0 0 ...
## $ DiscMM : num 0 0.3 0 0 0 0 0.4 0.4 0.4 0.4 ...
## $ SpecialCH : num 0 0 0 0 0 0 1 1 0 0 ...
## $ SpecialMM : num 0 1 0 0 0 1 1 0 0 0 ...
## $ LoyalCH : num 0.5 0.6 0.68 0.4 0.957 ...
## $ SalePriceMM : num 1.99 1.69 2.09 1.69 1.69 1.99 1.59 1.59 1.59 1.59 ...
## $ SalePriceCH : num 1.75 1.75 1.69 1.69 1.69 1.69 1.69 1.75 1.75 1.75 ...
## $ PriceDiff : num 0.24 -0.06 0.4 0 0 0.3 -0.1 -0.16 -0.16 -0.16 ...
## $ Store7 : Factor w/ 2 levels "No","Yes": 1 1 1 1 2 2 2 2 2 2 ...
## $ PctDiscMM : num 0 0.151 0 0 0 ...
## $ PctDiscCH : num 0 0 0.0914 0 0 ...
## $ ListPriceDiff : num 0.24 0.24 0.23 0 0 0.3 0.3 0.24 0.24 0.24 ...
## $ STORE : num 1 1 1 1 0 0 0 0 0 0 ...
(a) Create a training set containing a random sample of 800 observations, and a test set containing the remaining observations.
set.seed(210)
train_index_oj <- sample(1:nrow(OJ), 800)
train_set_oj<- OJ[train_index_oj,]
test_set_oj <- OJ[-train_index_oj, ]
dim(train_set_oj)## [1] 800 18
## [1] 270 18
(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?
##
## Classification tree:
## tree(formula = Purchase ~ ., data = train_set_oj)
## Variables actually used in tree construction:
## [1] "LoyalCH" "PriceDiff"
## Number of terminal nodes: 8
## Residual mean deviance: 0.7314 = 579.3 / 792
## Misclassification error rate: 0.1638 = 131 / 800
Our classification tree model creates exactly 8 terminal nodes using only two predictive variables: customer brand loyalty to Citrus Hill
LoyalCHand the price difference between the brandsPriceDiff. This structural simplicity results in a training misclassification error rate of 16.38%, meaning the model correctly identifies the purchase choice for 669 out of the 800 training observations while missing 131.
(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.
## node), split, n, deviance, yval, (yprob)
## * denotes terminal node
##
## 1) root 800 1073.00 CH ( 0.60625 0.39375 )
## 2) LoyalCH < 0.5036 353 413.10 MM ( 0.27195 0.72805 )
## 4) LoyalCH < 0.035047 53 0.00 MM ( 0.00000 1.00000 ) *
## 5) LoyalCH > 0.035047 300 376.10 MM ( 0.32000 0.68000 )
## 10) PriceDiff < 0.05 125 116.30 MM ( 0.17600 0.82400 ) *
## 11) PriceDiff > 0.05 175 238.40 MM ( 0.42286 0.57714 )
## 22) LoyalCH < 0.260429 64 64.60 MM ( 0.20312 0.79688 ) *
## 23) LoyalCH > 0.260429 111 152.80 CH ( 0.54955 0.45045 ) *
## 3) LoyalCH > 0.5036 447 345.00 CH ( 0.87025 0.12975 )
## 6) LoyalCH < 0.764572 180 208.80 CH ( 0.73333 0.26667 )
## 12) PriceDiff < 0.265 105 142.80 CH ( 0.58095 0.41905 )
## 24) PriceDiff < -0.165 28 33.50 MM ( 0.28571 0.71429 ) *
## 25) PriceDiff > -0.165 77 95.55 CH ( 0.68831 0.31169 ) *
## 13) PriceDiff > 0.265 75 31.23 CH ( 0.94667 0.05333 ) *
## 7) LoyalCH > 0.764572 267 85.31 CH ( 0.96255 0.03745 ) *
Terminal Node Interpretation (Node 4): This node represents 53 customers with extremely low brand loyalty to Citrus Hill (score below 0.035). Because this group is completely uniform, the error rate here is zero. The model predicts a Minute Maid (MM) purchase with 100% certainty, as nobody in this group bought Citrus Hill.
(d) Create a plot of the tree, and interpret the results.
> Our classification tree shows that customer loyalty
LoyalCH is the most important factor in deciding which
orange juice to buy, forming the first split at the top. Customers with
high brand loyalty track to the right and are generally predicted to buy
Citrus Hill (CH), while those with lower loyalty track to
the left and mostly choose Minute Maid (MM). Further down
the tree, smaller splits use price differences PriceDiff
and tighter loyalty ranges to fine-tune the final customer
predictions.
(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?
pred_oj <- predict(oj_tree, newdata = test_set_oj, type = "class")
conf_matrix <- table(Predicted = pred_oj, Actual = test_set_oj$Purchase)
print(conf_matrix)## Actual
## Predicted CH MM
## CH 151 30
## MM 17 72
## Test Error Rate: 0.1740741
(f) Apply the cv.tree() function to the training set in order to determine the optimal tree size.
(g) Produce a plot with tree size on the x-axis and cross-validated classification error rate on the y-axis.
(h) Which tree size corresponds to the lowest cross-validated classification error rate?
best_oj_size <- cv_oj$size[which.min(cv_oj$dev)]
cat("Optimal tree size with lowest CV error rate:", best_oj_size, "\n")## Optimal tree size with lowest CV error rate: 8
(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.
pruned_oj <- prune.misclass(oj_tree, best = 8)
plot(pruned_oj)
text(pruned_oj, pretty = 0, cex = 0.7)(j) Compare the training error rates between the pruned and unpruned trees. Which is higher?
## [1] 131 800
## [1] 131 800
The training error rates for the unpruned and pruned trees are identical at 16.38% (131/800). Neither is higher because the cross-validation process determined that the original tree size of 8 terminal nodes was already the optimal size, meaning no branches were removed during pruning.
(k) Compare the test error rates between the pruned and unpruned trees. Which is higher?
pred_pruned_oj <- predict(pruned_oj, newdata = test_set_oj, type = "class")
pruned_test_error <- mean(pred_pruned_oj != test_set_oj$Purchase)
cat("Unpruned Test Error Rate:", test_error, "\n")## Unpruned Test Error Rate: 0.1740741
## Pruned Test Error Rate: 0.1740741
Again, our test error rates are identical at 17.41%. Because the pruned tree is identical to the unpruned tree, their predictions on the test dataset are exactly the same, resulting in the same misclassification rate.