# Mark Nelson, Assignment3
# load libraries
library(C50)
## Warning: package 'C50' was built under R version 4.4.3
library(gmodels)
## Warning: package 'gmodels' was built under R version 4.4.3
# load data and setup
bank <- read.csv("bank-additional-full.csv", sep = ";", stringsAsFactors = TRUE)

# drop $duration, prevent data leakage 
bank$duration <- NULL

# Create training and test split (90/10)
set.seed(12345)
train_sample <- sample(41188, 37069)
bank_train <- bank[train_sample, ]
bank_test  <- bank[-train_sample, ]

# vector to track successful identifications for evaluation/plotting
results_tracking <- c()


# Experiment1: baseline C5.0 model
# Hypothesis: a default model favors overall accuracy over the minority class
# 
m1_baseline <- C5.0(bank_train[-20], bank_train$y)

p1_baseline <- predict(m1_baseline, bank_test)
CrossTable(bank_test$y, p1_baseline, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('Actual', 'Predicted'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  4119 
## 
##  
##              | Predicted 
##       Actual |        no |       yes | Row Total | 
## -------------|-----------|-----------|-----------|
##           no |      3602 |        65 |      3667 | 
##              |     0.874 |     0.016 |           | 
## -------------|-----------|-----------|-----------|
##          yes |       342 |       110 |       452 | 
##              |     0.083 |     0.027 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      3944 |       175 |      4119 | 
## -------------|-----------|-----------|-----------|
## 
## 
# record true positives ('yes' is the second level)
results_tracking["1. Baseline"] <- sum(bank_test$y == "yes" & p1_baseline == "yes")

# plot a subset of the baseline tree to see the initial decision rules
plot(m1_baseline, subtree = 2, main = "Exp 1: Baseline C5.0 Decision Tree")

# Experiment2: cost matrix
# Hypothesis: 4x penalty on false negatives will capture more subscriptions
# Change: costs = error_cost_4x
# 
matrix_dim <- list(c("no", "yes"), c("no", "yes"))
names(matrix_dim) <- c("predicted", "actual")
error_cost_4x <- matrix(c(0, 1, 4, 0), nrow = 2, dimnames = matrix_dim)

m2_cost <- C5.0(bank_train[-20], bank_train$y, costs = error_cost_4x)

p2_cost <- predict(m2_cost, bank_test)
CrossTable(bank_test$y, p2_cost, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('Actual', 'Predicted'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  4119 
## 
##  
##              | Predicted 
##       Actual |        no |       yes | Row Total | 
## -------------|-----------|-----------|-----------|
##           no |      3343 |       324 |      3667 | 
##              |     0.812 |     0.079 |           | 
## -------------|-----------|-----------|-----------|
##          yes |       206 |       246 |       452 | 
##              |     0.050 |     0.060 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      3549 |       570 |      4119 | 
## -------------|-----------|-----------|-----------|
## 
## 
results_tracking["2. Cost (4x)"] <- sum(bank_test$y == "yes" & p2_cost == "yes")



# Experiment3: winnowing 
# Hypothesis: winnowing will simplify the tree by dropping noisy features
# Change: C5.0Control(winnow = TRUE)
# 
m3_winnow <- C5.0(bank_train[-20], bank_train$y, costs = error_cost_4x, control = C5.0Control(winnow = TRUE))

p3_winnow <- predict(m3_winnow, bank_test)
CrossTable(bank_test$y, p3_winnow, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('Actual', 'Predicted'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  4119 
## 
##  
##              | Predicted 
##       Actual |        no |       yes | Row Total | 
## -------------|-----------|-----------|-----------|
##           no |      3298 |       369 |      3667 | 
##              |     0.801 |     0.090 |           | 
## -------------|-----------|-----------|-----------|
##          yes |       187 |       265 |       452 | 
##              |     0.045 |     0.064 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      3485 |       634 |      4119 | 
## -------------|-----------|-----------|-----------|
## 
## 
results_tracking["3. Winnowing"] <- sum(bank_test$y == "yes" & p3_winnow == "yes")



# Experiment4: boosting
# Hypothesis: a 10-trial boosted ensemble will improve accuracy
# Change: removed winnowing, added trials = 10
# 
m4_boost <- C5.0(bank_train[-20], bank_train$y, trials = 10)

p4_boost <- predict(m4_boost, bank_test)
CrossTable(bank_test$y, p4_boost, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('Actual', 'Predicted'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  4119 
## 
##  
##              | Predicted 
##       Actual |        no |       yes | Row Total | 
## -------------|-----------|-----------|-----------|
##           no |      3554 |       113 |      3667 | 
##              |     0.863 |     0.027 |           | 
## -------------|-----------|-----------|-----------|
##          yes |       314 |       138 |       452 | 
##              |     0.076 |     0.034 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      3868 |       251 |      4119 | 
## -------------|-----------|-----------|-----------|
## 
## 
results_tracking["4. Boosting"] <- sum(bank_test$y == "yes" & p4_boost == "yes")



# Experiment5: restricting tree depth
# Hypothesis: forcing higher minimum case limit prevents boosted model overfitting
# Change: C5.0Control(minCases = 15)
# 
m5_mincases <- C5.0(bank_train[-20], bank_train$y, trials = 10, control = C5.0Control(minCases = 15))

p5_mincases <- predict(m5_mincases, bank_test)
CrossTable(bank_test$y, p5_mincases, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('Actual', 'Predicted'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  4119 
## 
##  
##              | Predicted 
##       Actual |        no |       yes | Row Total | 
## -------------|-----------|-----------|-----------|
##           no |      3591 |        76 |      3667 | 
##              |     0.872 |     0.018 |           | 
## -------------|-----------|-----------|-----------|
##          yes |       345 |       107 |       452 | 
##              |     0.084 |     0.026 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      3936 |       183 |      4119 | 
## -------------|-----------|-----------|-----------|
## 
## 
results_tracking["5. Min Cases (15)"] <- sum(bank_test$y == "yes" & p5_mincases == "yes")



# Experiment6: boosting + heavy cost penalty
# Hypothesis: boosting combined with 8x cost penalty will obtain highest subscription detection
# Change: added cost matrix (8x penalty), boosting
# 
error_cost_8x <- matrix(c(0, 1, 8, 0), nrow = 2, dimnames = matrix_dim)

m6_synthesis <- C5.0(bank_train[-20], bank_train$y, trials = 10, costs = error_cost_8x)

p6_synthesis <- predict(m6_synthesis, bank_test)
CrossTable(bank_test$y, p6_synthesis, prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, dnn = c('Actual', 'Predicted'))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  4119 
## 
##  
##              | Predicted 
##       Actual |        no |       yes | Row Total | 
## -------------|-----------|-----------|-----------|
##           no |      3586 |        81 |      3667 | 
##              |     0.871 |     0.020 |           | 
## -------------|-----------|-----------|-----------|
##          yes |       340 |       112 |       452 | 
##              |     0.083 |     0.027 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      3926 |       193 |      4119 | 
## -------------|-----------|-----------|-----------|
## 
## 
results_tracking["6. Boost + Cost (8x)"] <- sum(bank_test$y == "yes" & p6_synthesis == "yes")



# Evaluation
# create bar chart comparing successful subscriptions identified across 6 experiments 
#
barplot(results_tracking, 
        main = "Model Tuning: True Positives (Subscribers Identified)",
        ylab = "Number of Correctly Predicted Subscriptions",
        col = c("gray", "lightblue", "lightblue", "lightgreen", "lightgreen", "darkblue"),
        las = 2, # Make x-axis labels vertical for readability
        ylim = c(0, max(results_tracking) + 50))

# add numbers
text(x = barplot(results_tracking, plot = FALSE), 
     y = results_tracking + 15, 
     labels = results_tracking, 
     cex = 0.9, 
     font = 2)