library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.3.2
library(reshape2)
library(ISLR2)
library(tree)
library(rpart)
library(rpart.plot)
## Warning: package 'rpart.plot' was built under R version 4.3.2
library(caret)
## Loading required package: lattice
library(randomForest)
## randomForest 4.7-1.1
## Type rfNews() to see new features/changes/bug fixes.
##
## Attaching package: 'randomForest'
## The following object is masked from 'package:ggplot2':
##
## margin
library(dbarts)
## Warning: package 'dbarts' was built under R version 4.3.2
Consider the Gini index, classification error, and entropy in a simple classification setting with two classes. Create a single plot that dis- plays 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.
pm1 <- seq(0, 1, length.out = 500)
gini_index <- 2 * pm1 * (1 - pm1)
classification_error <- pmin(pm1, 1 - pm1)
entropy <- -pm1 * log2(pm1 + 1e-10) - (1 - pm1) * log2(1 - pm1 + 1e-10)
data <- data.frame(pm1, gini_index, classification_error, entropy)
data_melted <- melt(data, id.vars = 'pm1', variable.name = 'Measure', value.name = 'Value')
# Plot
ggplot(data_melted, aes(x = pm1, y = Value, color = Measure)) +
geom_line() +
labs(x = expression(hat(p)[m1]), y = "Value",
title = paste("Gini Index, Classification Error, and Entropy as a function of", expression(hat(p)[m1]))) +
theme_minimal() +
scale_color_brewer(palette = "Dark2") +
theme(text = element_text(size=12))
In the lab, a classification tree was applied to the Carseats data set af- ter 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.
data("Carseats")
head(Carseats)
## Sales CompPrice Income Advertising Population Price ShelveLoc Age Education
## 1 9.50 138 73 11 276 120 Bad 42 17
## 2 11.22 111 48 16 260 83 Good 65 10
## 3 10.06 113 35 10 269 80 Medium 59 12
## 4 7.40 117 100 4 466 97 Medium 55 14
## 5 4.15 141 64 3 340 128 Bad 38 13
## 6 10.81 124 113 13 501 72 Bad 78 16
## Urban US
## 1 Yes Yes
## 2 Yes Yes
## 3 Yes Yes
## 4 Yes Yes
## 5 Yes No
## 6 No Yes
set.seed(1)
train = sample(dim(Carseats)[1], dim(Carseats)[1]/2)
Carseats.train = Carseats[train, ]
Carseats.test = Carseats[-train, ]
According to my results, my Test Mean Squared Error was 5.16518178751299
# Fit a regression tree model
tree_model <- rpart(Sales ~ ., data = Carseats.train, method = "anova")
# Plot the regression tree
rpart.plot(tree_model, main="Regression Tree for Carseats Sales", extra=1)
predictions <- predict(tree_model, Carseats.test)
# Calculate Mean Squared Error (MSE)
mse <- mean((Carseats.test$Sales - predictions)^2)
print(paste("Test Mean Squared Error:", mse))
## [1] "Test Mean Squared Error: 5.16518178751299"
According to my results, my Test Mean Squared Error went from 5.16518178751299 to 8.0613310725
# cross-validation
set.seed(123) # For reproducibility
control <- trainControl(method="cv", number=10)
cpGrid <- expand.grid(.cp=seq(0.001, 0.1, by=0.001))
cv_model <- train(Sales ~ ., data=Carseats.train, method="rpart",
trControl=control, tuneGrid=cpGrid)
best_cp <- cv_model$bestTune$.cp
pruned_tree <- rpart(Sales ~ ., data = Carseats.train, method="anova", cp=best_cp)
pruned_predictions <- predict(pruned_tree, Carseats.test)
pruned_mse <- mean((Carseats.test$Sales - pruned_predictions)^2)
print(paste("Pruned Tree Test Mean Squared Error:", pruned_mse))
## [1] "Pruned Tree Test Mean Squared Error: 8.0613310725"
I saw the my Test Mean Squared Error was 2.62216953572064
# bagging model
set.seed(123)
bagging_model <- randomForest(Sales ~ ., data = Carseats.train, mtry = ncol(Carseats.train) - 1, importance = TRUE)
bagging_predictions <- predict(bagging_model, Carseats.test)
bagging_mse <- mean((Carseats.test$Sales - bagging_predictions)^2)
print(paste("Bagging Test Mean Squared Error:", bagging_mse))
## [1] "Bagging Test Mean Squared Error: 2.62216953572064"
variable_importance <- importance(bagging_model)
print(variable_importance)
## %IncMSE IncNodePurity
## CompPrice 26.4012250 171.833476
## Income 5.2986947 91.287059
## Advertising 14.8653864 106.322362
## Population -1.3016770 58.450270
## Price 61.8972031 508.651053
## ShelveLoc 48.6813291 366.772122
## Age 16.7438295 154.355128
## Education 2.1458731 43.548804
## Urban -0.4139116 7.678707
## US 5.8826617 15.815135
varImpPlot(bagging_model)
set.seed(123)
rf_model <- randomForest(Sales ~ ., data = Carseats.train, mtry = floor(sqrt(ncol(Carseats.train) - 1)), importance = TRUE)
rf_predictions <- predict(rf_model, Carseats.test)
rf_mse <- mean((Carseats.test$Sales - rf_predictions)^2)
print(paste("Random Forest Test Mean Squared Error:", rf_mse))
## [1] "Random Forest Test Mean Squared Error: 3.0346790509252"
rf_variable_importance <- importance(rf_model)
print(rf_variable_importance)
## %IncMSE IncNodePurity
## CompPrice 15.9798090 154.74702
## Income 3.9262250 133.27867
## Advertising 9.5034904 104.02220
## Population -1.9168315 93.90404
## Price 34.7891781 396.52698
## ShelveLoc 36.6558536 291.06552
## Age 13.4679974 173.48227
## Education 0.3123937 75.68286
## Urban 0.4711800 15.93681
## US 5.8566680 32.89546
varImpPlot(rf_model)
head(OJ)
## Purchase WeekofPurchase StoreID PriceCH PriceMM DiscCH DiscMM SpecialCH
## 1 CH 237 1 1.75 1.99 0.00 0.0 0
## 2 CH 239 1 1.75 1.99 0.00 0.3 0
## 3 CH 245 1 1.86 2.09 0.17 0.0 0
## 4 MM 227 1 1.69 1.69 0.00 0.0 0
## 5 CH 228 7 1.69 1.69 0.00 0.0 0
## 6 CH 230 7 1.69 1.99 0.00 0.0 0
## SpecialMM LoyalCH SalePriceMM SalePriceCH PriceDiff Store7 PctDiscMM
## 1 0 0.500000 1.99 1.75 0.24 No 0.000000
## 2 1 0.600000 1.69 1.75 -0.06 No 0.150754
## 3 0 0.680000 2.09 1.69 0.40 No 0.000000
## 4 0 0.400000 1.69 1.69 0.00 No 0.000000
## 5 0 0.956535 1.69 1.69 0.00 Yes 0.000000
## 6 1 0.965228 1.99 1.69 0.30 Yes 0.000000
## PctDiscCH ListPriceDiff STORE
## 1 0.000000 0.24 1
## 2 0.000000 0.24 1
## 3 0.091398 0.23 1
## 4 0.000000 0.00 1
## 5 0.000000 0.00 0
## 6 0.000000 0.30 0
set.seed(123)
train_indices <- sample(nrow(OJ), 800)
OJ_train <- OJ[train_indices, ]
OJ_test <- OJ[-train_indices, ]
According to my results, I got 8 terminal nodes and I got my training error was 16.5%. The tree model suggests that loyalty and price difference are key determinants.
oj_tree <- tree(Purchase ~ ., data = OJ_train)
summary(oj_tree)
##
## 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
I picked terminal node 8. My ‘LoyalCH’ is < 0.0356415, I had 56 observations fall into it. The goodness of fit is 10.03. My low LoyalCH suggests customers have no loyalty to Citrus Hill. This node’s low deviance suggests that the model fits very well for this particular group of customers.
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 ) *
I see that LoyalCH is the root node. It means that it’s the most critical factor in determining whether a customer will purchase Citrus Hill or Minute Maid. If split is 0.5036. If LoyalCH is < that then the tree moves to the left child node, otherwise to the right child node.
plot(oj_tree, main = "Decision Tree for OJ Purchase Data")
text(oj_tree, pretty = 0)
My test error rate was 0.185185185185185.
oj_test_pred <- predict(oj_tree, OJ_test, type = "class")
conf_matrix <- table(OJ_test$Purchase, oj_test_pred)
print(conf_matrix)
## oj_test_pred
## CH MM
## CH 150 16
## MM 34 70
test_error_rate <- 1 - sum(diag(conf_matrix)) / sum(conf_matrix)
print(paste("Test error rate:", test_error_rate))
## [1] "Test error rate: 0.185185185185185"
cv_oj_tree <- cv.tree(oj_tree, FUN = prune.tree)
print(cv_oj_tree)
## $size
## [1] 8 7 6 5 4 3 2 1
##
## $dev
## [1] 690.2346 692.8691 683.0423 717.4446 717.4446 743.3604 793.1266
## [8] 1073.0916
##
## $k
## [1] -Inf 12.03823 14.92474 25.76707 26.02613 38.91686 50.61655
## [8] 298.68751
##
## $method
## [1] "deviance"
##
## attr(,"class")
## [1] "prune" "tree.sequence"
plot(cv_oj_tree$size, cv_oj_tree$dev, type = "b", xlab = "Tree Size", ylab = "CV Deviance")
According to my result, size 6 gives the lowest cross-validation error.
pruned_oj_tree <- prune.tree(oj_tree, best = 6)
summary(pruned_oj_tree)
##
## Classification tree:
## snip.tree(tree = oj_tree, nodes = c(4L, 14L))
## Variables actually used in tree construction:
## [1] "LoyalCH" "PriceDiff"
## Number of terminal nodes: 6
## Residual mean deviance: 0.7945 = 630.9 / 794
## Misclassification error rate: 0.165 = 132 / 800
I got the same results for both 0.165
# Predictions on the training data using the unpruned tree
unpruned_train_pred <- predict(oj_tree, OJ_train, type = "class")
# Calculate training error rate for the unpruned tree
unpruned_train_error <- mean(unpruned_train_pred != OJ_train$Purchase)
print(paste("Unpruned Tree Training Error Rate:", unpruned_train_error))
## [1] "Unpruned Tree Training Error Rate: 0.165"
# Predictions on the training data using the pruned tree
pruned_train_pred <- predict(pruned_oj_tree, OJ_train, type = "class")
# Calculate training error rate for the pruned tree
pruned_train_error <- mean(pruned_train_pred != OJ_train$Purchase)
print(paste("Pruned Tree Training Error Rate:", pruned_train_error))
## [1] "Pruned Tree Training Error Rate: 0.165"
I also got the same results for test error rates 0.185185185185185
# Predictions on the test data using the unpruned tree
unpruned_test_pred <- predict(oj_tree, OJ_test, type = "class")
# Calculate test error rate for the unpruned tree
unpruned_test_error <- mean(unpruned_test_pred != OJ_test$Purchase)
print(paste("Unpruned Tree Test Error Rate:", unpruned_test_error))
## [1] "Unpruned Tree Test Error Rate: 0.185185185185185"
# Predictions on the test data using the pruned tree
pruned_test_pred <- predict(pruned_oj_tree, OJ_test, type = "class")
# Calculate test error rate for the pruned tree
pruned_test_error <- mean(pruned_test_pred != OJ_test$Purchase)
print(paste("Pruned Tree Test Error Rate:", pruned_test_error))
## [1] "Pruned Tree Test Error Rate: 0.185185185185185"