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 pˆm1. The x-axis should display pˆm1, 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, pˆm1 = 1 − pˆm2. You could make this plot by hand, but it will be much easier to make in R.
proportion1 <- seq(0, 1, 0.001)
proportion2 <- 1 - proportion1
classification_error <- 1 - pmax(proportion1, proportion2)
gini <- proportion1 * (1-proportion1) + proportion2 * (1-proportion2)
entropy <- -proportion1 * log(proportion1) - proportion2 * log(proportion2)
data.frame(proportion1, proportion2, classification_error, gini, entropy) %>%
pivot_longer(cols = c(classification_error, gini, entropy), names_to = "metric") %>%
ggplot(aes(x = proportion1, y = value, col = factor(metric))) +
geom_line(linewidth = 1) +
scale_y_continuous(breaks = seq(0, 1, 0.1), minor_breaks = NULL) +
scale_color_hue(labels = c("Classification Error", "Entropy", "Gini")) +
labs(col = "Legend",
y = "Value",
x = "Proportion")
## Warning: Removed 2 rows containing missing values (`geom_line()`).
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.
# Split the data
set.seed(12345)
train <- sample(1:nrow(Carseats), nrow(Carseats) / 2)
Fit a regression tree to the training set. Plot the tree, and interpret the results. What test MSE do you obtain?
tree.Carseats <- tree(Sales ~ ., Carseats, subset = train)
summary(tree.Carseats)
##
## Regression tree:
## tree(formula = Sales ~ ., data = Carseats, subset = train)
## Variables actually used in tree construction:
## [1] "ShelveLoc" "Price" "CompPrice" "Age" "US"
## [6] "Income" "Population" "Advertising"
## Number of terminal nodes: 19
## Residual mean deviance: 1.841 = 333.2 / 181
## Distribution of residuals:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -5.3080 -0.6933 0.1642 0.0000 0.8924 3.4020
plot(tree.Carseats)
text(tree.Carseats, pretty = 0, cex = 0.5)
When looking at the tree, we can see where each split lies and at what value. The first split begins at ‘ShelveLoc’, the option of ‘Bad’ will lead you down the left side of the tree while the option ‘Medium’ will lead you down the right side of the tree. This process continues at each split of the tree, then finally arrives at a prediction for ‘Sales’ at the bottom of the tree.
yhat <- predict(tree.Carseats, newdata = Carseats[-train, ])
carseats.test <- Carseats[-train, "Sales"]
plot(yhat, carseats.test)
abline(0, 1)
mean((yhat - carseats.test)^2)
## [1] 5.236977
Based on the results of the model, it achieves a test MSE of 5.236977
Use cross-validation in order to determine the optimal level of tree complexity. Does pruning the tree improve the test MSE?
cv.carseats <- cv.tree(tree.Carseats)
plot(cv.carseats$size, cv.carseats$dev, type = "b")
We can see that the optimal point seems to be 9, as it is the simplest and has a low deviance.
prune.carseats <- prune.tree(tree.Carseats, best = 9)
plot(prune.carseats)
text(prune.carseats, pretty = 0)
yhat <- predict(prune.carseats, newdata = Carseats[-train, ])
carseats.test <- Carseats[-train, "Sales"]
plot(yhat, carseats.test)
abline(0, 1)
mean((yhat - carseats.test)^2)
## [1] 5.141645
Pruning the decision tree down to 16 help decrease the test MSE from 5.236977 to 5.141645. Therefore, pruning the tree did help improve the 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(12345)
bag.carseats <- randomForest(Sales ~ ., data = Carseats,
subset = train, mtry = 10, importance = TRUE)
bag.carseats
##
## Call:
## randomForest(formula = Sales ~ ., data = Carseats, mtry = 10, importance = TRUE, subset = train)
## Type of random forest: regression
## Number of trees: 500
## No. of variables tried at each split: 10
##
## Mean of squared residuals: 2.60257
## % Var explained: 63.73
importance(bag.carseats)
## %IncMSE IncNodePurity
## CompPrice 18.891751393 134.062837
## Income 2.451392651 70.848737
## Advertising 16.651146492 132.833223
## Population -1.441368637 59.141863
## Price 43.309642246 374.502459
## ShelveLoc 50.608908916 383.660180
## Age 19.903193742 192.799402
## Education 0.527296075 42.407971
## Urban -1.768327245 7.261618
## US -0.007330736 5.517387
yhat <- predict(bag.carseats, newdata = Carseats[-train, ])
carseats.test <- Carseats[-train, "Sales"]
plot(yhat, carseats.test)
abline(0, 1)
mean((yhat - carseats.test)^2)
## [1] 3.24628
From the results we can see that ‘CompPrice’, ‘Advertising’, ‘Price’, ‘ShelveLoc’, and ‘Age’ seem to be the most important predictors when attempting to predict ‘Sales’. We can also notice that bagging has helped improve our test MSE and reduced it down to 3.24628.
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(12345)
rf.carseats <- randomForest(Sales ~ ., data = Carseats,
subset = train, mtry = 9, importance = TRUE)
importance(rf.carseats)
## %IncMSE IncNodePurity
## CompPrice 17.6902322 140.319225
## Income 2.3971098 73.749587
## Advertising 17.9166740 138.691129
## Population -0.9718496 56.918853
## Price 44.3251074 368.017147
## ShelveLoc 48.8718686 373.194651
## Age 18.8405766 187.013211
## Education -1.0019776 44.245674
## Urban -4.0700278 8.101418
## US 0.9250337 6.356382
yhat.rf <- predict(rf.carseats, newdata = Carseats[-train, ])
mean((yhat.rf - carseats.test)^2)
## [1] 3.235668
We can see from the test MSE that Random Forest out performed the bagging method (3.24628 for bagging and 3.235668 for Random Forest), suggesting that Random Forest yields better results in this case. The importance of the predictors does not seem to vary that much from the bagging method. The effect of ‘m’ can play heavily into the accuracy of the model, a low ‘m’ can effect bias and cause the trees to be more correlated with one another, while a high ‘m’ will practically do the inverse effecting variance and lowering correlation between trees.
Now analyze the data using BART, and report your results.
set.seed(12345)
boost.carseats <- gbm(Sales ~ ., data = Carseats[train, ],
distribution = "gaussian", n.trees = 5000, interaction.depth = 4)
summary(boost.carseats)
## var rel.inf
## Price Price 24.0558395
## ShelveLoc ShelveLoc 20.0075801
## Age Age 14.2804000
## CompPrice CompPrice 12.1771605
## Advertising Advertising 9.0800697
## Income Income 8.9491531
## Population Population 7.2705051
## Education Education 3.0178871
## US US 0.8942359
## Urban Urban 0.2671691
yhat.boost <- predict(boost.carseats, newdata = Carseats[-train, ], n.trees = 5000)
mean((yhat.boost - carseats.test)^2)
## [1] 2.553012
We can see that by using BART, the most influential predictors are the same ones found when bagging and Random Forest. The test MSE has decreased significantly when comparing BART to any of the other models tested.
This problem involves the OJ data set which is part of the ISLR2 package.
Create a training set containing a random sample of 800 observations, and a test set containing the remaining observations.
set.seed(12345)
train_index <- sample(1:nrow(OJ), 800)
train <- OJ[train_index, ]
test <- OJ[-train_index, ]
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 = train)
summary(oj.tree)
##
## Classification tree:
## tree(formula = Purchase ~ ., data = train)
## Variables actually used in tree construction:
## [1] "LoyalCH" "PriceDiff"
## Number of terminal nodes: 7
## Residual mean deviance: 0.7724 = 612.5 / 793
## Misclassification error rate: 0.17 = 136 / 800
From the results of the classification tree, we can see that only 2 of the predictors were used when creating the splits, that being ‘LoyalCH’, ‘PriceDiff’. This model only has a missclassification error rate of 17% and only 7 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 1059.000 CH ( 0.62500 0.37500 )
## 2) LoyalCH < 0.5036 349 425.200 MM ( 0.29799 0.70201 )
## 4) LoyalCH < 0.276142 170 138.400 MM ( 0.14118 0.85882 )
## 8) LoyalCH < 0.0356415 55 9.996 MM ( 0.01818 0.98182 ) *
## 9) LoyalCH > 0.0356415 115 115.100 MM ( 0.20000 0.80000 ) *
## 5) LoyalCH > 0.276142 179 246.100 MM ( 0.44693 0.55307 )
## 10) PriceDiff < 0.065 76 80.790 MM ( 0.22368 0.77632 ) *
## 11) PriceDiff > 0.065 103 137.600 CH ( 0.61165 0.38835 ) *
## 3) LoyalCH > 0.5036 451 334.500 CH ( 0.87805 0.12195 )
## 6) LoyalCH < 0.705699 138 164.300 CH ( 0.71739 0.28261 )
## 12) PriceDiff < 0.265 89 120.100 CH ( 0.59551 0.40449 ) *
## 13) PriceDiff > 0.265 49 22.570 CH ( 0.93878 0.06122 ) *
## 7) LoyalCH > 0.705699 313 126.300 CH ( 0.94888 0.05112 ) *
Lets look at terminal node 9, which denoted by a “*”
If we start at the root node:
1) root 800 1059.000 CH ( 0.62500 0.37500 )
We can see that there are 800 observations, a deviance of 1059, with an overall prediction of CH(62.5% for CH and 37.5% for MM).
From that node we go down the following path:
LoyalCH = 0.5036
LoyalCH = 0.276142
LoyalCH = 0.0356415
Therefore, node 9 is a subset of purchases where ‘0.0356415 < LoyalCH < 0.276142’ and the overall prediction is MM. This node seems to be pretty good with a split of 80% MM vs 20% CH and 115 observations.
Create a plot of the tree, and interpret the results.
plot(oj.tree)
text(oj.tree, pretty = 0, cex = 0.5)
The plot matches the text output we did prior, both having a starting split at LoyalCH = 0.5036. We can see the node we selected in the bottom left corner of the plot, that node has the same prediction of ‘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?
predictions <- predict(oj.tree, test, type = "class")
table(predictions, test_labels = test$Purchase)
## test_labels
## predictions CH MM
## CH 145 47
## MM 8 70
1 - mean(predictions == test$Purchase)
## [1] 0.2037037
The model achieves a test error rate of 20.37%
Apply the cv.tree() function to the training set in order to determine the optimal tree size.
cv.oj <- cv.tree(oj.tree, K = 10, FUN = prune.misclass)
cv.oj
## $size
## [1] 7 4 2 1
##
## $dev
## [1] 160 160 169 300
##
## $k
## [1] -Inf 0.0 11.5 141.0
##
## $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(cv.oj$size, cv.oj$dev / nrow(train), type = "b")
Which tree size corresponds to the lowest cross-validated classification error rate?
From the plot, it seems as if a tree size of 4 corresponds to the lowest cross-validated classification error rate. So moving forward we will use a tree size of 4.
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.tree(oj.tree, best = 4)
pruned_oj
## node), split, n, deviance, yval, (yprob)
## * denotes terminal node
##
## 1) root 800 1059.0 CH ( 0.62500 0.37500 )
## 2) LoyalCH < 0.5036 349 425.2 MM ( 0.29799 0.70201 )
## 4) LoyalCH < 0.276142 170 138.4 MM ( 0.14118 0.85882 ) *
## 5) LoyalCH > 0.276142 179 246.1 MM ( 0.44693 0.55307 ) *
## 3) LoyalCH > 0.5036 451 334.5 CH ( 0.87805 0.12195 )
## 6) LoyalCH < 0.705699 138 164.3 CH ( 0.71739 0.28261 ) *
## 7) LoyalCH > 0.705699 313 126.3 CH ( 0.94888 0.05112 ) *
Compare the training error rates between the pruned and unpruned trees. Which is higher?
mean(predict(oj.tree, type = "class", newdata = train) != train$Purchase)
## [1] 0.17
mean(predict(pruned_oj, type = "class", newdata = train) != train$Purchase)
## [1] 0.19875
Our pruned tree has higher training error rates than the unpruned one. The unpruned tree seems to be out performing the pruned one.
Compare the test error rates between the pruned and unpruned trees. Which is higher?
mean(predict(oj.tree, type = "class", newdata = test) != test$Purchase)
## [1] 0.2037037
mean(predict(pruned_oj, type = "class", newdata = test) != test$Purchase)
## [1] 0.2037037
However, when obtaining the test error rates, both the pruned and unpruned trees seem to achieve the same value. Furthermore, changing the size of the trees has a negative impact on the accuracy of the pruned model.