#setup
library(caret)
## Warning: package 'caret' was built under R version 4.3.3
## Loading required package: ggplot2
## Warning: package 'ggplot2' was built under R version 4.3.3
## Loading required package: lattice
bank <- read.csv("UniversalBank.csv")
names(bank)
##  [1] "ID"                 "Age"                "Experience"        
##  [4] "Income"             "ZIP.Code"           "Family"            
##  [7] "CCAvg"              "Education"          "Mortgage"          
## [10] "Personal.Loan"      "Securities.Account" "CD.Account"        
## [13] "Online"             "CreditCard"
head(bank,10)
##    ID Age Experience Income ZIP.Code Family CCAvg Education Mortgage
## 1   1  25          1     49    91107      4   1.6         1        0
## 2   2  45         19     34    90089      3   1.5         1        0
## 3   3  39         15     11    94720      1   1.0         1        0
## 4   4  35          9    100    94112      1   2.7         2        0
## 5   5  35          8     45    91330      4   1.0         2        0
## 6   6  37         13     29    92121      4   0.4         2      155
## 7   7  53         27     72    91711      2   1.5         2        0
## 8   8  50         24     22    93943      1   0.3         3        0
## 9   9  35         10     81    90089      3   0.6         2      104
## 10 10  34          9    180    93023      1   8.9         3        0
##    Personal.Loan Securities.Account CD.Account Online CreditCard
## 1              0                  1          0      0          0
## 2              0                  1          0      0          0
## 3              0                  0          0      0          0
## 4              0                  0          0      0          0
## 5              0                  0          0      0          1
## 6              0                  0          0      1          0
## 7              0                  0          0      1          0
## 8              0                  0          0      0          1
## 9              0                  0          0      1          0
## 10             1                  0          0      0          0
str(bank)
## 'data.frame':    5000 obs. of  14 variables:
##  $ ID                : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ Age               : int  25 45 39 35 35 37 53 50 35 34 ...
##  $ Experience        : int  1 19 15 9 8 13 27 24 10 9 ...
##  $ Income            : int  49 34 11 100 45 29 72 22 81 180 ...
##  $ ZIP.Code          : int  91107 90089 94720 94112 91330 92121 91711 93943 90089 93023 ...
##  $ Family            : int  4 3 1 1 4 4 2 1 3 1 ...
##  $ CCAvg             : num  1.6 1.5 1 2.7 1 0.4 1.5 0.3 0.6 8.9 ...
##  $ Education         : int  1 1 1 2 2 2 2 3 2 3 ...
##  $ Mortgage          : int  0 0 0 0 0 155 0 0 104 0 ...
##  $ Personal.Loan     : int  0 0 0 0 0 0 0 0 0 1 ...
##  $ Securities.Account: int  1 1 0 0 0 0 0 0 0 0 ...
##  $ CD.Account        : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ Online            : int  0 0 0 0 0 1 1 0 1 0 ...
##  $ CreditCard        : int  0 0 0 0 1 0 0 1 0 0 ...
# Drop ID and zip code columns.

bank <- bank[ , -c(1, 5)]
names(bank)
##  [1] "Age"                "Experience"         "Income"            
##  [4] "Family"             "CCAvg"              "Education"         
##  [7] "Mortgage"           "Personal.Loan"      "Securities.Account"
## [10] "CD.Account"         "Online"             "CreditCard"
# Reorder variables. Put the response last.

bank <- bank[ , c(1:7, 9:12, 8)]

head(bank)
##   Age Experience Income Family CCAvg Education Mortgage Securities.Account
## 1  25          1     49      4   1.6         1        0                  1
## 2  45         19     34      3   1.5         1        0                  1
## 3  39         15     11      1   1.0         1        0                  0
## 4  35          9    100      1   2.7         2        0                  0
## 5  35          8     45      4   1.0         2        0                  0
## 6  37         13     29      4   0.4         2      155                  0
##   CD.Account Online CreditCard Personal.Loan
## 1          0      0          0             0
## 2          0      0          0             0
## 3          0      0          0             0
## 4          0      0          0             0
## 5          0      0          1             0
## 6          0      1          0             0
# Set categorical variables as factor.

bank$Education <- as.factor(bank$Education)
bank$Securities.Account <- as.factor(bank$Securities.Account)
bank$CD.Account <- as.factor(bank$CD.Account) 
bank$Online <- as.factor(bank$Online) 
bank$CreditCard <- as.factor(bank$CreditCard) 

# Rename outcome variable values

bank$Personal.Loan <- factor(bank$Personal.Loan,
                              levels = c("0", "1"),
                              labels = c("No", "Yes"))

table(bank$Personal.Loan)
## 
##   No  Yes 
## 4520  480
#Set training and validation sets 
set.seed(8)

train_index <- sample(1:nrow(bank), 0.6 * nrow(bank))
valid_index <- setdiff(1:nrow(bank), train_index)

train <- bank[train_index, ]
valid <- bank[valid_index, ]

train_norm <- train
valid_norm <- valid

norm_values <- preProcess(train[, -c(12)],
                          method = c("center",
                                     "scale"))
train[, -c(12)] <- predict(norm_values,
                               train[, -c(12)])


nrow(train)
## [1] 3000
nrow(valid)
## [1] 2000
#Define new customer

new_cust <- data.frame(Age = 40,
                       Experience = 10,
                       Income = 84,
                       Family = 2,
                       CCAvg = 2,
                       Education = 2,
                       Mortgage = 0,
                       Securities.Account = 0,
                       CD.Account = 0,
                       Online = 1,
                       CreditCard = 1)
new_cust
##   Age Experience Income Family CCAvg Education Mortgage Securities.Account
## 1  40         10     84      2     2         2        0                  0
##   CD.Account Online CreditCard
## 1          0      1          1
# Set categorical variables as factor.

new_cust$Education <- as.factor(new_cust$Education)
new_cust$Securities.Account <- as.factor(new_cust$Securities.Account)
new_cust$CD.Account <- as.factor(new_cust$CD.Account) 
new_cust$Online <- as.factor(new_cust$Online) 
new_cust$CreditCard <- as.factor(new_cust$CreditCard) 

new_cust
##   Age Experience Income Family CCAvg Education Mortgage Securities.Account
## 1  40         10     84      2     2         2        0                  0
##   CD.Account Online CreditCard
## 1          0      1          1
# 5. prepare for kNN. -------------------------------------------------------------

# Normalisation, only for numerical variables

norm_values <- preProcess(train[, -c(6, 8:12)],
                          method = c("center",
                                     "scale"))
# Train k = 5
knn_model_k5 <- caret::knn3(Personal.Loan ~ ., 
                            data = train_norm, k = 5)
knn_model_k5
## 5-nearest neighbor model
## Training set outcome distribution:
## 
##   No  Yes 
## 2722  278
#Predict training set

knn_pred_k5_train <- predict(knn_model_k5, 
                             newdata = train_norm[, -c(12)], 
                             type = "class")
head(knn_pred_k5_train)
## [1] No No No No No No
## Levels: No Yes
# Evaluate training confusion matrix
confusionMatrix(knn_pred_k5_train, as.factor(train_norm[, 12]),
                positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   No  Yes
##        No  2677  150
##        Yes   45  128
##                                           
##                Accuracy : 0.935           
##                  95% CI : (0.9256, 0.9436)
##     No Information Rate : 0.9073          
##     P-Value [Acc > NIR] : 2.496e-08       
##                                           
##                   Kappa : 0.5345          
##                                           
##  Mcnemar's Test P-Value : 9.506e-14       
##                                           
##             Sensitivity : 0.46043         
##             Specificity : 0.98347         
##          Pos Pred Value : 0.73988         
##          Neg Pred Value : 0.94694         
##              Prevalence : 0.09267         
##          Detection Rate : 0.04267         
##    Detection Prevalence : 0.05767         
##       Balanced Accuracy : 0.72195         
##                                           
##        'Positive' Class : Yes             
## 
# Train k = 7
knn_model_k7 <- caret::knn3(Personal.Loan ~ ., 
                            data = train_norm, k = 7)
knn_model_k7
## 7-nearest neighbor model
## Training set outcome distribution:
## 
##   No  Yes 
## 2722  278
#Predict training set

knn_pred_k7_train <- predict(knn_model_k7, 
                             newdata = train_norm[, -c(12)], 
                             type = "class")
head(knn_pred_k7_train)
## [1] No No No No No No
## Levels: No Yes
# Evaluate training confusion matrix
confusionMatrix(knn_pred_k7_train, as.factor(train_norm[, 12]),
                positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   No  Yes
##        No  2673  158
##        Yes   49  120
##                                           
##                Accuracy : 0.931           
##                  95% CI : (0.9213, 0.9398)
##     No Information Rate : 0.9073          
##     P-Value [Acc > NIR] : 1.920e-06       
##                                           
##                   Kappa : 0.502           
##                                           
##  Mcnemar's Test P-Value : 6.072e-14       
##                                           
##             Sensitivity : 0.43165         
##             Specificity : 0.98200         
##          Pos Pred Value : 0.71006         
##          Neg Pred Value : 0.94419         
##              Prevalence : 0.09267         
##          Detection Rate : 0.04000         
##    Detection Prevalence : 0.05633         
##       Balanced Accuracy : 0.70683         
##                                           
##        'Positive' Class : Yes             
## 
# Train k = 3
knn_model_k3 <- caret::knn3(Personal.Loan ~ ., 
                            data = train_norm, k = 3)
knn_model_k3
## 3-nearest neighbor model
## Training set outcome distribution:
## 
##   No  Yes 
## 2722  278
#Predict training set

knn_pred_k3_train <- predict(knn_model_k3, 
                             newdata = train_norm[, -c(12)], 
                             type = "class")
head(knn_pred_k3_train)
## [1] No No No No No No
## Levels: No Yes
# Evaluate training confusion matrix (BEST RESULT IN TRAINING)
confusionMatrix(knn_pred_k3_train, as.factor(train_norm[, 12]),
                positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   No  Yes
##        No  2667  109
##        Yes   55  169
##                                           
##                Accuracy : 0.9453          
##                  95% CI : (0.9366, 0.9532)
##     No Information Rate : 0.9073          
##     P-Value [Acc > NIR] : 8.322e-15       
##                                           
##                   Kappa : 0.6439          
##                                           
##  Mcnemar's Test P-Value : 3.494e-05       
##                                           
##             Sensitivity : 0.60791         
##             Specificity : 0.97979         
##          Pos Pred Value : 0.75446         
##          Neg Pred Value : 0.96073         
##              Prevalence : 0.09267         
##          Detection Rate : 0.05633         
##    Detection Prevalence : 0.07467         
##       Balanced Accuracy : 0.79385         
##                                           
##        'Positive' Class : Yes             
## 
# 7.4 predict validation set
# use k = 5

# Predict training set

knn_pred_k5_valid <- predict(knn_model_k5, 
                             newdata = valid_norm[, -c(12)], 
                             type = "class")
head(knn_pred_k5_valid)
## [1] No No No No No No
## Levels: No Yes
# Evaluate
confusionMatrix(knn_pred_k5_valid, as.factor(valid_norm[, 12]),
                positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   No  Yes
##        No  1746  138
##        Yes   52   64
##                                           
##                Accuracy : 0.905           
##                  95% CI : (0.8913, 0.9175)
##     No Information Rate : 0.899           
##     P-Value [Acc > NIR] : 0.1975          
##                                           
##                   Kappa : 0.355           
##                                           
##  Mcnemar's Test P-Value : 6.98e-10        
##                                           
##             Sensitivity : 0.3168          
##             Specificity : 0.9711          
##          Pos Pred Value : 0.5517          
##          Neg Pred Value : 0.9268          
##              Prevalence : 0.1010          
##          Detection Rate : 0.0320          
##    Detection Prevalence : 0.0580          
##       Balanced Accuracy : 0.6440          
##                                           
##        'Positive' Class : Yes             
## 
library(ROSE)
## Warning: package 'ROSE' was built under R version 4.3.3
## Loaded ROSE 0.0-4
ROSE::roc.curve(valid_norm$Personal.Loan, 
                knn_pred_k5_valid)

## Area under the curve (AUC): 0.644
# 7.4 predict validation set ----------------------------------------------


# use k = 3 (BEST RESULT IN VALIDATION)

# Predict training set

knn_pred_k3_valid <- predict(knn_model_k3, 
                             newdata = valid_norm[, -c(12)], 
                             type = "class")
head(knn_pred_k3_valid)
## [1] No No No No No No
## Levels: No Yes
# Evaluate
confusionMatrix(knn_pred_k3_valid, as.factor(valid_norm[, 12]),
                positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   No  Yes
##        No  1732  123
##        Yes   66   79
##                                          
##                Accuracy : 0.9055         
##                  95% CI : (0.8918, 0.918)
##     No Information Rate : 0.899          
##     P-Value [Acc > NIR] : 0.1771         
##                                          
##                   Kappa : 0.4051         
##                                          
##  Mcnemar's Test P-Value : 4.633e-05      
##                                          
##             Sensitivity : 0.3911         
##             Specificity : 0.9633         
##          Pos Pred Value : 0.5448         
##          Neg Pred Value : 0.9337         
##              Prevalence : 0.1010         
##          Detection Rate : 0.0395         
##    Detection Prevalence : 0.0725         
##       Balanced Accuracy : 0.6772         
##                                          
##        'Positive' Class : Yes            
## 
ROSE::roc.curve(valid_norm$Personal.Loan, 
                knn_pred_k3_valid)

## Area under the curve (AUC): 0.677
# 7.4 predict validation set ----------------------------------------------


# use k = 7

# Predict training set

knn_pred_k7_valid <- predict(knn_model_k7, 
                             newdata = valid_norm[, -c(12)], 
                             type = "class")
head(knn_pred_k7_valid)
## [1] No No No No No No
## Levels: No Yes
# Evaluate
confusionMatrix(knn_pred_k7_valid, as.factor(valid_norm[, 12]),
                positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   No  Yes
##        No  1753  139
##        Yes   45   63
##                                           
##                Accuracy : 0.908           
##                  95% CI : (0.8945, 0.9203)
##     No Information Rate : 0.899           
##     P-Value [Acc > NIR] : 0.0958          
##                                           
##                   Kappa : 0.3615          
##                                           
##  Mcnemar's Test P-Value : 7.079e-12       
##                                           
##             Sensitivity : 0.3119          
##             Specificity : 0.9750          
##          Pos Pred Value : 0.5833          
##          Neg Pred Value : 0.9265          
##              Prevalence : 0.1010          
##          Detection Rate : 0.0315          
##    Detection Prevalence : 0.0540          
##       Balanced Accuracy : 0.6434          
##                                           
##        'Positive' Class : Yes             
## 
ROSE::roc.curve(valid_norm$Personal.Loan, 
                knn_pred_k7_valid)

## Area under the curve (AUC): 0.643

The best choice of k in this situation was 5. Overall, it had similar values to 3 and 7 for sensitivity, specificity, and pos pred value, but ultimately it had the highest positive pred value in the validation confusion matrices. The pos pred score tells us how good the model is at predicting the positive values. It also has a high neg pred value, which means that it is good at predicting the negative.

Based on my evaluation, the model is fairly good at predicting the negative with the specificity and neg pred value both being high in the 90s, but it is not the best at predicting the positives with a low sensitivity and relatively low pos pred value.