library(tidyverse)
library(ggplot2)
library(randomForest)
library(tree)
library(caret)
setwd("C:/Users/tarra/Documents/Intro to Analytics Modeling/Fall2020hw7/data 10.1")
uscrime_data <- read.table("uscrime.txt", header = TRUE)
# Build the regression tree model
uscrime_treemod <- tree(Crime~., data = uscrime_data)
summary(uscrime_treemod)
##
## Regression tree:
## tree(formula = Crime ~ ., data = uscrime_data)
## Variables actually used in tree construction:
## [1] "Po1" "Pop" "LF" "NW"
## Number of terminal nodes: 7
## Residual mean deviance: 47390 = 1896000 / 40
## Distribution of residuals:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -573.900 -98.300 -1.545 0.000 110.600 490.100
# Let's see how exactly the tree was split
uscrime_treemod$frame
## var n dev yval splits.cutleft splits.cutright
## 1 Po1 47 6880927.66 905.0851 <7.65 >7.65
## 2 Pop 23 779243.48 669.6087 <22.5 >22.5
## 4 LF 12 243811.00 550.5000 <0.5675 >0.5675
## 8 <leaf> 7 48518.86 466.8571
## 9 <leaf> 5 77757.20 667.6000
## 5 <leaf> 11 179470.73 799.5455
## 3 NW 24 3604162.50 1130.7500 <7.65 >7.65
## 6 Pop 10 557574.90 886.9000 <21.5 >21.5
## 12 <leaf> 5 146390.80 1049.2000
## 13 <leaf> 5 147771.20 724.6000
## 7 Po1 14 2027224.93 1304.9286 <9.65 >9.65
## 14 <leaf> 6 170828.00 1041.0000
## 15 <leaf> 8 1124984.88 1502.8750
plot(uscrime_treemod)
text(uscrime_treemod)
# Now let's prune the tree
termnodes <- 5
prune_uscrime_treemod <- prune.tree(uscrime_treemod, best = termnodes)
plot(prune_uscrime_treemod)
text(prune_uscrime_treemod)
summary(prune_uscrime_treemod)
##
## Regression tree:
## snip.tree(tree = uscrime_treemod, nodes = c(4L, 6L))
## Variables actually used in tree construction:
## [1] "Po1" "Pop" "NW"
## Number of terminal nodes: 5
## Residual mean deviance: 54210 = 2277000 / 42
## Distribution of residuals:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -573.9 -107.5 15.5 0.0 122.8 490.1
# pruning the tree is showing that it's probably a worse fit, and there might be some overfitting of the model. We should just go ahead and use the 7 nodes as it has less error.
termnodes <- 7
prune_uscrime_treemod2 <- prune.tree(uscrime_treemod, best = termnodes)
plot(prune_uscrime_treemod2)
text(prune_uscrime_treemod2)
summary(prune_uscrime_treemod2)
##
## Regression tree:
## tree(formula = Crime ~ ., data = uscrime_data)
## Variables actually used in tree construction:
## [1] "Po1" "Pop" "LF" "NW"
## Number of terminal nodes: 7
## Residual mean deviance: 47390 = 1896000 / 40
## Distribution of residuals:
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## -573.900 -98.300 -1.545 0.000 110.600 490.100
# Calculate quaility of fit
uscrime_treepred <- predict(prune_uscrime_treemod2, data = uscrime_data[,1:15])
RSS <- sum((uscrime_treepred - uscrime_data[,16])^2)
TSS <- sum((uscrime_data[,16] - mean(uscrime_data[,16]))^2)
R2 <- 1 - RSS/TSS
R2
## [1] 0.7244962
Our R^2, at 0.7245 is high. We can also see that over fitting may be a problem with this model.
# Let's move onto the random forest model
# creating the baseline model.
uscrime_rf <- randomForest(Crime ~ ., data=uscrime_data, importance = TRUE, nodesize = 5)
uscrime_rfpred <- predict(uscrime_rf, data=uscrime_data[,-16])
RSS <- sum((uscrime_rfpred - uscrime_data[,16])^2)
R2 <- 1 - RSS/TSS
R2
## [1] 0.4122103
With the random forest model, we get a worse R^2. With this information we can see that the random forest model is not as accurate as the tree.
My close friend works in IT security at The University of Alabama at Birmingham. One of the biggest problems they deal with is spam emails. Logistic regression can be used to tell whether or not an email is a spam. Some identifies could be the email sender, occurrences of certain key words, and occurrences of special links in the email.
setwd("C:/Users/tarra/Documents/Intro to Analytics Modeling/Fall2020hw7/data 10.3")
germ_cred <- read.table("germancredit.txt", header = FALSE)
#with this we can see what holds what. V21 holds the responses.
str(germ_cred)
## 'data.frame': 1000 obs. of 21 variables:
## $ V1 : chr "A11" "A12" "A14" "A11" ...
## $ V2 : int 6 48 12 42 24 36 24 36 12 30 ...
## $ V3 : chr "A34" "A32" "A34" "A32" ...
## $ V4 : chr "A43" "A43" "A46" "A42" ...
## $ V5 : int 1169 5951 2096 7882 4870 9055 2835 6948 3059 5234 ...
## $ V6 : chr "A65" "A61" "A61" "A61" ...
## $ V7 : chr "A75" "A73" "A74" "A74" ...
## $ V8 : int 4 2 2 2 3 2 3 2 2 4 ...
## $ V9 : chr "A93" "A92" "A93" "A93" ...
## $ V10: chr "A101" "A101" "A101" "A103" ...
## $ V11: int 4 2 3 4 4 4 4 2 4 2 ...
## $ V12: chr "A121" "A121" "A121" "A122" ...
## $ V13: int 67 22 49 45 53 35 53 35 61 28 ...
## $ V14: chr "A143" "A143" "A143" "A143" ...
## $ V15: chr "A152" "A152" "A152" "A153" ...
## $ V16: int 2 1 1 1 2 1 1 1 1 2 ...
## $ V17: chr "A173" "A173" "A172" "A173" ...
## $ V18: int 1 1 2 2 2 2 1 1 1 1 ...
## $ V19: chr "A192" "A191" "A191" "A191" ...
## $ V20: chr "A201" "A201" "A201" "A201" ...
## $ V21: int 1 2 1 1 2 1 1 1 1 2 ...
table(germ_cred$V21)
##
## 1 2
## 700 300
#we want to replace 1 and 2 with 0 and 1
germ_cred$V21[germ_cred$V21==1] <- 0
germ_cred$V21[germ_cred$V21==2] <- 1
#what we want to do is measure the model using truePositive + trueNegative /(total predictions + actual values)
#so lets split the data into a training set and a validation set
germcredit_split <- createDataPartition(germ_cred$V21, times = 1, p = 0.7, list=FALSE)
head(germcredit_split)
## Resample1
## [1,] 2
## [2,] 3
## [3,] 4
## [4,] 5
## [5,] 6
## [6,] 7
germcredit_train <- germ_cred[germcredit_split,]
germcredit_val <- germ_cred[-germcredit_split,]
table(germcredit_train$V21)
##
## 0 1
## 486 214
table(germcredit_val$V21)
##
## 0 1
## 214 86
germcred_logmod <- glm(V21 ~ ., data = germcredit_train, family=binomial(link="logit"))
summary(germcred_logmod)
##
## Call:
## glm(formula = V21 ~ ., family = binomial(link = "logit"), data = germcredit_train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.2960 -0.7145 -0.3640 0.7161 2.5515
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 2.669e-01 1.307e+00 0.204 0.838152
## V1A12 -3.171e-01 2.614e-01 -1.213 0.225104
## V1A13 -1.434e+00 5.016e-01 -2.859 0.004252 **
## V1A14 -1.606e+00 2.757e-01 -5.824 5.73e-09 ***
## V2 2.346e-02 1.178e-02 1.991 0.046454 *
## V3A31 4.163e-01 6.637e-01 0.627 0.530515
## V3A32 -2.644e-01 5.204e-01 -0.508 0.611423
## V3A33 -7.901e-01 5.659e-01 -1.396 0.162703
## V3A34 -1.316e+00 5.350e-01 -2.461 0.013871 *
## V4A41 -2.129e+00 4.675e-01 -4.553 5.28e-06 ***
## V4A410 -1.003e+00 1.003e+00 -1.000 0.317545
## V4A42 -7.033e-01 3.125e-01 -2.251 0.024401 *
## V4A43 -1.110e+00 2.945e-01 -3.769 0.000164 ***
## V4A44 -2.341e-01 8.866e-01 -0.264 0.791764
## V4A45 1.704e-01 6.909e-01 0.247 0.805221
## V4A46 -7.129e-02 4.914e-01 -0.145 0.884651
## V4A48 -1.575e+01 4.735e+02 -0.033 0.973461
## V4A49 -7.666e-01 4.094e-01 -1.872 0.061157 .
## V5 1.138e-04 5.580e-05 2.039 0.041430 *
## V6A62 -2.642e-01 3.512e-01 -0.752 0.451860
## V6A63 -6.222e-02 4.288e-01 -0.145 0.884646
## V6A64 -1.288e+00 6.086e-01 -2.117 0.034297 *
## V6A65 -8.564e-01 3.122e-01 -2.743 0.006090 **
## V7A72 6.433e-01 5.099e-01 1.262 0.207118
## V7A73 2.517e-01 4.872e-01 0.517 0.605438
## V7A74 -4.155e-01 5.315e-01 -0.782 0.434433
## V7A75 4.195e-01 4.897e-01 0.857 0.391656
## V8 2.634e-01 1.045e-01 2.520 0.011743 *
## V9A92 -1.342e-01 4.829e-01 -0.278 0.781112
## V9A93 -9.991e-01 4.775e-01 -2.092 0.036401 *
## V9A94 -3.581e-01 5.760e-01 -0.622 0.534142
## V10A102 6.158e-01 4.900e-01 1.257 0.208866
## V10A103 -6.144e-01 4.952e-01 -1.241 0.214713
## V11 -7.023e-02 1.056e-01 -0.665 0.505908
## V12A122 2.408e-01 3.087e-01 0.780 0.435344
## V12A123 2.955e-01 2.784e-01 1.062 0.288398
## V12A124 7.883e-01 4.804e-01 1.641 0.100782
## V13 -1.209e-02 1.122e-02 -1.077 0.281299
## V14A142 2.694e-01 5.231e-01 0.515 0.606486
## V14A143 -5.978e-01 2.919e-01 -2.048 0.040532 *
## V15A152 -3.062e-01 2.833e-01 -1.081 0.279852
## V15A153 -2.719e-01 5.561e-01 -0.489 0.624875
## V16 4.637e-01 2.316e-01 2.002 0.045305 *
## V17A172 1.974e-01 8.268e-01 0.239 0.811313
## V17A173 1.781e-01 7.986e-01 0.223 0.823497
## V17A174 5.796e-01 8.157e-01 0.711 0.477367
## V18 6.567e-02 3.195e-01 0.206 0.837132
## V19A192 -4.408e-01 2.529e-01 -1.743 0.081291 .
## V20A202 -1.313e+00 7.811e-01 -1.681 0.092783 .
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 861.88 on 699 degrees of freedom
## Residual deviance: 623.89 on 651 degrees of freedom
## AIC: 721.89
##
## Number of Fisher Scoring iterations: 14
#now lets do the prediction
germcred_predict <- predict(germcred_logmod, newdata=germcredit_val[,-21], type="response")
table(germcredit_val$V21, round(germcred_predict))
##
## 0 1
## 0 179 35
## 1 43 43
So our baseline prediction model correctly classifies a good bit of the good borrowers, but heavily misclassifies the bad borrowers. To deal with the fact that the cost of bad borrowers is 5x that of misclassifying good borrowers, lets look for ways to minimize the misclassifying of bad borrowers. We can do this by only including variables that had a signficance of at least p <0.1
# Because there are multiple cat variables in a single column lets manually remove the non-significant variables.
germcredit_train$V1A14[germcredit_train$V1 == "A14"] <- 1
germcredit_train$V1A14[germcredit_train$V1 != "A14"] <- 0
germcredit_train$V3A34[germcredit_train$V3 == "A34"] <- 1
germcredit_train$V3A34[germcredit_train$V3 != "A34"] <- 0
germcredit_train$V4A41[germcredit_train$V4 == "A41"] <- 1
germcredit_train$V4A41[germcredit_train$V4 != "A41"] <- 0
germcredit_train$V4A43[germcredit_train$V4 == "A43"] <- 1
germcredit_train$V4A43[germcredit_train$V4 != "A43"] <- 0
germcred_logmod2 <- glm(V21 ~ V1A14+V2+V3A34+V4A41+V4A43, data = germcredit_train, family=binomial(link="logit"))
summary(germcred_logmod2)
##
## Call:
## glm(formula = V21 ~ V1A14 + V2 + V3A34 + V4A41 + V4A43, family = binomial(link = "logit"),
## data = germcredit_train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -1.6221 -0.8019 -0.4871 1.0727 2.5475
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -0.50046 0.20328 -2.462 0.013821 *
## V1A14 -1.43483 0.21168 -6.778 1.22e-11 ***
## V2 0.03132 0.00770 4.068 4.74e-05 ***
## V3A34 -0.78889 0.22460 -3.512 0.000444 ***
## V4A41 -1.35802 0.36507 -3.720 0.000199 ***
## V4A43 -0.85684 0.21638 -3.960 7.50e-05 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 861.88 on 699 degrees of freedom
## Residual deviance: 733.45 on 694 degrees of freedom
## AIC: 745.45
##
## Number of Fisher Scoring iterations: 4
# Now lets do that with the validation data
germcredit_val$V1A14[germcredit_val$V1 == "A14"] <- 1
germcredit_val$V1A14[germcredit_val$V1 != "A14"] <- 0
germcredit_val$V3A34[germcredit_val$V3 == "A34"] <- 1
germcredit_val$V3A34[germcredit_val$V3 != "A34"] <- 0
germcredit_val$V4A41[germcredit_val$V4 == "A41"] <- 1
germcredit_val$V4A41[germcredit_val$V4 != "A41"] <- 0
germcredit_val$V4A43[germcredit_val$V4 == "A43"] <- 1
germcredit_val$V4A43[germcredit_val$V4 != "A43"] <- 0
# Now lets create a confusion matrix of predicted vs observed on the validation data set
# This will help to calculate the accuracy and specificity
germcred_predict2 <- predict(germcred_logmod2, newdata=germcredit_val[,-21], type="response")
m <- as.matrix(table(round(germcred_predict2), germcredit_val$V21))
names(dimnames(m)) <- c("Predicted", "Observed")
m
## Observed
## Predicted 0 1
## 0 186 51
## 1 28 35
threshold <- 0.7
m2 <- as.matrix(table(round(germcred_predict2 > threshold), germcredit_val$V21))
names(dimnames(m2)) <- c("Predicted", "Observed")
m2
## Observed
## Predicted 0 1
## 0 210 74
## 1 4 12
accuracy <- (m2[1,1]+m2[2,2])/(m2[1,1]+m2[1,2]+m2[2,1]+m2[2,2])
accuracy
## [1] 0.74
specificity <- (m2[1,1])/(m2[1,1]+m2[2,1])
specificity
## [1] 0.9813084
After messing around with the threshold, I concluded that 0.7 was a good fit, providing a specificity of 98.54% and an accuracy of 68.67%, which overall is much better.