This data was collected via a survey on Amazon Mechanical Turk. The survey describes different driving scenarios including the destination, current time, weather, passenger, etc., and then ask the person whether he will accept the coupon if he is the driver.
1) Load Libraries & Read Dataset
2) Missing & unique value check
## Columns with missing value
## [1] "car :100%" "Bar :0.84%"
## [3] "CoffeeHouse :1.71%" "CarryAway :1.19%"
## [5] "RestaurantLessThan20 :1.02%" "Restaurant20To50 :1.49%"
## Column with unique value
## toCoupon_GEQ5min
## 20
3) Creating New Variables - occupation class - Expiration weightage (numeric, scaled)
| occupation_class | Actual_occupation |
|---|---|
| Craft and related trades workers | Installation Maintenance & Repair |
| Craft and related trades workers | Transportation & Material Moving |
| Craft and related trades workers | Food Preparation & Serving Related |
| Craft and related trades workers | Building & Grounds Cleaning & Maintenance |
| Others | Office & Administrative Support |
| Others | Production Occupations |
| Others | Farming Fishing & Forestry |
| Professionals | Architecture & Engineering |
| Professionals | Education&Training&Library |
| Professionals | Healthcare Practitioners & Technical |
| Professionals | Management |
| Professionals | Arts Design Entertainment Sports & Media |
| Professionals | Computer & Mathematical |
| Professionals | Legal |
| Professionals | Business & Financial |
| Retired | Retired |
| Service and sales | Sales & Related |
| Service and sales | Personal Care & Service |
| Service and sales | Protective Service |
| Student | Student |
| Technicians & prof | Healthcare Support |
| Technicians & prof | Life Physical Social Science |
| Technicians & prof | Community & Social Services |
| Technicians & prof | Construction & Extraction |
| Unemployed | Unemployed |
4) Missing imputation knn approach
library(VIM)
cleaned_data <- kNN(
coupon_data,
variable = c("Bar","CoffeeHouse","CarryAway","RestaurantLessThan20","Restaurant20To50")
, k = 5)
cleaned_data <- cleaned_data[,1:ncol(coupon_data)]
# coupon_data_final %>% map(table)
# colMeans(is.na(cleaned_data))*100
## 'data.frame': 12684 obs. of 22 variables:
## $ destination : chr "No Urgent Place" "No Urgent Place" "No Urgent Place" "No Urgent Place" ...
## $ passanger : chr "Alone" "Friends" "Friends" "Friends" ...
## $ weather : chr "Sunny" "Sunny" "Sunny" "Sunny" ...
## $ temperature : chr "55" "80" "80" "80" ...
## $ coupon : chr "Restaurant(<20)" "Coffee House" "Carry out & Take away" "Coffee House" ...
## $ gender : chr "Female" "Female" "Female" "Female" ...
## $ age : chr "21" "21" "21" "21" ...
## $ maritalStatus : chr "Unmarried partner" "Unmarried partner" "Unmarried partner" "Unmarried partner" ...
## $ has_children : Factor w/ 2 levels "0","1": 2 2 2 2 2 2 2 2 2 2 ...
## $ education : chr "Some college - no degree" "Some college - no degree" "Some college - no degree" "Some college - no degree" ...
## $ income : chr "$37500 - $49999" "$37500 - $49999" "$37500 - $49999" "$37500 - $49999" ...
## $ Bar : chr "never" "never" "never" "never" ...
## $ CoffeeHouse : chr "never" "never" "never" "never" ...
## $ CarryAway : chr "1~3" "less1" "less1" "less1" ...
## $ RestaurantLessThan20: chr "4~8" "4~8" "4~8" "4~8" ...
## $ Restaurant20To50 : chr "1~3" "1~3" "1~3" "1~3" ...
## $ toCoupon_GEQ15min : Factor w/ 2 levels "0","1": 1 1 2 2 2 2 2 2 2 2 ...
## $ toCoupon_GEQ25min : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
## $ direction_same : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
## $ Y : Factor w/ 2 levels "0","1": 2 1 2 1 1 2 2 2 2 1 ...
## $ occupation_class : chr "Unemployed" "Unemployed" "Unemployed" "Unemployed" ...
## $ expiration_weightage: num 1.334 0.111 0.111 0.111 1.334 ...
The multiple logistic regression is used to predict the probability of class membership based on multiple predictor variables.
The coefficient estimate of the variable destinationNo Urgent Place is b = 0.72: means that an No UrgentPlace is associated with increase in the probability of accepting coupon more than baseline dummy destinationWork.
Being Male, for example, increases the probability of accepting coupon more than being Female. Being age50plus decreases the probability of accepting coupon more than being 21-26.
If we have at least one significant dummy, we can not exclude the variable.
set.seed(123)
full_log_model <- glm(Y ~., data = cleaned_data, family=binomial(link='logit'))
summary(full_log_model)
##
## Call:
## glm(formula = Y ~ ., family = binomial(link = "logit"), data = cleaned_data)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.5150 -1.0280 0.5651 0.9452 2.4271
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -1.842614 0.243444 -7.569 3.76e-14 ***
## destinationNo Urgent Place 0.728894 0.079446 9.175 < 2e-16 ***
## destinationWork -0.097692 0.057683 -1.694 0.090341 .
## passangerFriends 0.198204 0.072183 2.746 0.006036 **
## passangerKids -0.373699 0.093329 -4.004 6.23e-05 ***
## passangerPartner 0.244527 0.087174 2.805 0.005031 **
## weatherSnowy 0.146718 0.131864 1.113 0.265859
## weatherSunny 0.572450 0.079709 7.182 6.88e-13 ***
## temperature55 0.350954 0.095173 3.688 0.000226 ***
## temperature80 0.186184 0.085180 2.186 0.028833 *
## couponCarry out & Take away 1.718958 0.072760 23.625 < 2e-16 ***
## couponCoffee House 0.540722 0.064185 8.424 < 2e-16 ***
## couponRestaurant(<20) 1.522600 0.070056 21.734 < 2e-16 ***
## couponRestaurant(20-50) 0.418126 0.079787 5.241 1.60e-07 ***
## genderMale 0.219835 0.043048 5.107 3.28e-07 ***
## age26 0.004076 0.069442 0.059 0.953196
## age31 -0.158493 0.075461 -2.100 0.035700 *
## age36 -0.075832 0.086472 -0.877 0.380513
## age41 0.025218 0.096172 0.262 0.793151
## age46 0.013301 0.105949 0.126 0.900096
## age50plus -0.213604 0.086328 -2.474 0.013348 *
## agebelow21 0.003143 0.119334 0.026 0.978987
## maritalStatusMarried partner 0.154955 0.112572 1.376 0.168667
## maritalStatusSingle 0.305867 0.117405 2.605 0.009181 **
## maritalStatusUnmarried partner 0.110064 0.120425 0.914 0.360736
## maritalStatusWidowed 0.261376 0.230633 1.133 0.257088
## has_children1 0.092140 0.058092 1.586 0.112716
## educationBachelors degree -0.157473 0.077792 -2.024 0.042943 *
## educationGraduate degree -0.323460 0.090673 -3.567 0.000361 ***
## educationHigh School Graduate 0.174644 0.104765 1.667 0.095513 .
## educationSome college - no degree 0.048642 0.078520 0.619 0.535596
## educationSome High School 0.654488 0.285999 2.288 0.022113 *
## income$12500 - $24999 -0.093274 0.084546 -1.103 0.269926
## income$25000 - $37499 0.045049 0.079759 0.565 0.572199
## income$37500 - $49999 -0.081985 0.081278 -1.009 0.313119
## income$50000 - $62499 0.162556 0.081009 2.007 0.044788 *
## income$62500 - $74999 -0.322062 0.096822 -3.326 0.000880 ***
## income$75000 - $87499 -0.232408 0.095601 -2.431 0.015056 *
## income$87500 - $99999 -0.275381 0.096396 -2.857 0.004280 **
## incomeLess than $12500 -0.108887 0.099748 -1.092 0.275000
## Bar4~8 -0.112211 0.086349 -1.300 0.193770
## Bargt8 -0.416026 0.143365 -2.902 0.003710 **
## Barless1 -0.198242 0.063643 -3.115 0.001840 **
## Barnever -0.181372 0.061517 -2.948 0.003195 **
## CoffeeHouse4~8 -0.032526 0.070143 -0.464 0.642854
## CoffeeHousegt8 -0.388980 0.085206 -4.565 4.99e-06 ***
## CoffeeHouseless1 -0.447871 0.057515 -7.787 6.86e-15 ***
## CoffeeHousenever -0.943201 0.063176 -14.930 < 2e-16 ***
## CarryAway4~8 -0.016472 0.050861 -0.324 0.746042
## CarryAwaygt8 -0.071545 0.074833 -0.956 0.339039
## CarryAwayless1 -0.141711 0.063818 -2.221 0.026382 *
## CarryAwaynever 0.018829 0.188179 0.100 0.920298
## RestaurantLessThan204~8 0.060047 0.052078 1.153 0.248901
## RestaurantLessThan20gt8 0.182197 0.085667 2.127 0.033437 *
## RestaurantLessThan20less1 0.092708 0.062651 1.480 0.138940
## RestaurantLessThan20never 0.289231 0.164846 1.755 0.079336 .
## Restaurant20To504~8 0.071296 0.099458 0.717 0.473466
## Restaurant20To50gt8 0.075333 0.178821 0.421 0.673551
## Restaurant20To50less1 -0.127060 0.050898 -2.496 0.012547 *
## Restaurant20To50never -0.300305 0.068515 -4.383 1.17e-05 ***
## toCoupon_GEQ15min1 -0.101592 0.045957 -2.211 0.027064 *
## toCoupon_GEQ25min1 0.183414 0.082056 2.235 0.025402 *
## direction_same1 0.536336 0.069056 7.767 8.06e-15 ***
## occupation_classOthers 0.244455 0.122058 2.003 0.045202 *
## occupation_classProfessionals 0.076542 0.098637 0.776 0.437750
## occupation_classRetired -0.073831 0.143914 -0.513 0.607939
## occupation_classService and sales 0.140400 0.109172 1.286 0.198429
## occupation_classStudent 0.054337 0.115699 0.470 0.638611
## occupation_classTechnicians & prof 0.333932 0.122577 2.724 0.006445 **
## occupation_classUnemployed 0.037047 0.106977 0.346 0.729112
## expiration_weightage 0.684226 0.035094 19.497 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 17345 on 12683 degrees of freedom
## Residual deviance: 14991 on 12613 degrees of freedom
## AIC: 15133
##
## Number of Fisher Scoring iterations: 3
An odds ratio measures the association between a x and y. It represents the ratio of the odds that an event will occur given the presence of the predictor x, compared to the odds of the event occurring in the absence of that predictor. The associated beta coefficient (b) in the logistic regression function corresponds to the log of the odds ratio for that predictor. If the odds ratio is 0.5, then the odds that the event = 1 are 0.5 higher when the predictor x is present (x = 1) versus x is absent (x = 0).
Chi-square test to check the overall effect of variables on the dependent variable
## Analysis of Deviance Table
##
## Model: binomial, link: logit
##
## Response: Y
##
## Terms added sequentially (first to last)
##
##
## Df Deviance Resid. Df Resid. Dev Pr(>Chi)
## NULL 12683 17345
## destination 2 217.51 12681 17128 < 2.2e-16 ***
## passanger 3 77.17 12678 17051 < 2.2e-16 ***
## weather 2 104.77 12676 16946 < 2.2e-16 ***
## temperature 2 6.24 12674 16940 0.044132 *
## coupon 4 897.31 12670 16042 < 2.2e-16 ***
## gender 1 25.80 12669 16017 3.784e-07 ***
## age 7 52.77 12662 15964 4.109e-09 ***
## maritalStatus 4 10.99 12658 15953 0.026729 *
## has_children 1 1.39 12657 15952 0.239020
## education 5 35.14 12652 15916 1.414e-06 ***
## income 8 39.17 12644 15877 4.579e-06 ***
## Bar 4 61.01 12640 15816 1.777e-12 ***
## CoffeeHouse 4 296.51 12636 15520 < 2.2e-16 ***
## CarryAway 4 5.34 12632 15514 0.254013
## RestaurantLessThan20 4 10.39 12628 15504 0.034317 *
## Restaurant20To50 4 24.51 12624 15479 6.315e-05 ***
## toCoupon_GEQ15min 1 38.38 12623 15441 5.833e-10 ***
## toCoupon_GEQ25min 1 3.11 12622 15438 0.077887 .
## direction_same 1 33.88 12621 15404 5.868e-09 ***
## occupation_class 7 19.09 12614 15385 0.007896 **
## expiration_weightage 1 393.52 12613 14991 < 2.2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Insignificant coefficients (with p-value > 0.05) according to chi2 check are:
Multicollinearity check
car::vif(full_log_model)
## GVIF Df GVIF^(1/(2*Df))
## destination 4.226976 2 1.433862
## passanger 3.711907 3 1.244322
## weather 3.718154 2 1.388615
## temperature 4.020295 2 1.416004
## coupon 1.663346 4 1.065670
## gender 1.189669 1 1.090719
## age 5.084769 7 1.123176
## maritalStatus 3.363088 4 1.163703
## has_children 2.110278 1 1.452680
## education 2.228544 5 1.083433
## income 3.173868 8 1.074854
## Bar 2.410418 4 1.116250
## CoffeeHouse 2.160243 4 1.101065
## CarryAway 1.996979 4 1.090302
## RestaurantLessThan20 2.686835 4 1.131501
## Restaurant20To50 2.525245 4 1.122763
## toCoupon_GEQ15min 1.335446 1 1.155615
## toCoupon_GEQ25min 1.861952 1 1.364534
## direction_same 2.114196 1 1.454028
## occupation_class 4.523701 7 1.113835
## expiration_weightage 1.176458 1 1.084646
There is no problem of mulricollinearity.
Keeping insignificant variables in the model may contribute to overfitting. Eliminated manually, using stepwise regression and penalized regression methods.
Both directions
## Stepwise Model Path
## Analysis of Deviance Table
##
## Initial Model:
## Y ~ destination + passanger + weather + temperature + coupon +
## gender + age + maritalStatus + has_children + education +
## income + Bar + CoffeeHouse + CarryAway + RestaurantLessThan20 +
## Restaurant20To50 + toCoupon_GEQ15min + toCoupon_GEQ25min +
## direction_same + occupation_class + expiration_weightage
##
## Final Model:
## Y ~ destination + passanger + weather + temperature + coupon +
## gender + age + maritalStatus + has_children + education +
## income + Bar + CoffeeHouse + Restaurant20To50 + toCoupon_GEQ15min +
## toCoupon_GEQ25min + direction_same + occupation_class + expiration_weightage
##
##
## Step Df Deviance Resid. Df Resid. Dev AIC
## 1 12613 14991.42 15133.42
## 2 - CarryAway 4 5.724530 12617 14997.14 15131.14
## 3 - RestaurantLessThan20 4 7.139874 12621 15004.28 15130.28
Forward direction
## Stepwise Model Path
## Analysis of Deviance Table
##
## Initial Model:
## Y ~ destination + passanger + weather + temperature + coupon +
## gender + age + maritalStatus + has_children + education +
## income + Bar + CoffeeHouse + CarryAway + RestaurantLessThan20 +
## Restaurant20To50 + toCoupon_GEQ15min + toCoupon_GEQ25min +
## direction_same + occupation_class + expiration_weightage
##
## Final Model:
## Y ~ destination + passanger + weather + temperature + coupon +
## gender + age + maritalStatus + has_children + education +
## income + Bar + CoffeeHouse + CarryAway + RestaurantLessThan20 +
## Restaurant20To50 + toCoupon_GEQ15min + toCoupon_GEQ25min +
## direction_same + occupation_class + expiration_weightage
##
##
## Step Df Deviance Resid. Df Resid. Dev AIC
## 1 12613 14991.42 15133.42
With forward direction we get the same result as we had with a complete model, forward begins with a Null model (intercept only model)
Backward direction
## Stepwise Model Path
## Analysis of Deviance Table
##
## Initial Model:
## Y ~ destination + passanger + weather + temperature + coupon +
## gender + age + maritalStatus + has_children + education +
## income + Bar + CoffeeHouse + CarryAway + RestaurantLessThan20 +
## Restaurant20To50 + toCoupon_GEQ15min + toCoupon_GEQ25min +
## direction_same + occupation_class + expiration_weightage
##
## Final Model:
## Y ~ destination + passanger + weather + temperature + coupon +
## gender + age + maritalStatus + has_children + education +
## income + Bar + CoffeeHouse + Restaurant20To50 + toCoupon_GEQ15min +
## toCoupon_GEQ25min + direction_same + occupation_class + expiration_weightage
##
##
## Step Df Deviance Resid. Df Resid. Dev AIC
## 1 12613 14991.42 15133.42
## 2 - CarryAway 4 5.724530 12617 14997.14 15131.14
## 3 - RestaurantLessThan20 4 7.139874 12621 15004.28 15130.28
The backward procedure eliminated exactly the same variables as the “both” procedure.
Different criteria can be assigned to the stepAIC() function for stepwise selection. The default is AIC, which is performed by assigning the argument k to 2.
We also tried running bestglm, but there is a hard-coded constraint in bestglm (15 predictors means there are 2^15 = 32768 candidate models). In our case we have around 70 variables counting dummies.
Initially splitting data into train (0.67) and test sets to avoid overfitting
1) Model on training set with all variables
## Generalized Linear Model
##
## 8499 samples
## 21 predictor
## 2 classes: '0', '1'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 7650, 7649, 7649, 7650, 7649, 7648, ...
## Resampling results:
##
## Accuracy Kappa
## 0.6862035 0.3511017
2) Model on training set by dropping columns suggested by stepwise model (CarryAway, RestaurantLessThan20)
## Generalized Linear Model
##
## 8499 samples
## 19 predictor
## 2 classes: '0', '1'
##
## No pre-processing
## Resampling: Cross-Validated (10 fold)
## Summary of sample sizes: 7649, 7649, 7649, 7649, 7649, 7650, ...
## Resampling results:
##
## Accuracy Kappa
## 0.6887878 0.3564379
Full model AIC: 10139
Stepwise model AIC: 10134
According to the bias-variance trade-off, all things equal, simpler model should be always preferred because it is less likely to overfit the training data (less variance).
Logistic Complete Model Accuracy
log_reg_prob1 <- predict(log_reg_full, test, type = 'prob')
log_reg_pred1 <- ifelse(log_reg_prob1[2] > 0.5, 1, 0)
mean(log_reg_pred1 == test$Y)
## [1] 0.6824373
Stepwise Model Accuracy
## [1] 0.6862605
Stepwise model Confusion matrix
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 1039 546
## 1 767 1833
##
## Accuracy : 0.6863
## 95% CI : (0.672, 0.7003)
## No Information Rate : 0.5685
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.351
##
## Mcnemar's Test P-Value : 1.268e-09
##
## Sensitivity : 0.7705
## Specificity : 0.5753
## Pos Pred Value : 0.7050
## Neg Pred Value : 0.6555
## Prevalence : 0.5685
## Detection Rate : 0.4380
## Detection Prevalence : 0.6213
## Balanced Accuracy : 0.6729
##
## 'Positive' Class : 1
##
High sensitivity: fewer False Negative errors, which are severe types of error for us.
Low specificity: Many False Positive.
Sensitivity = TP/TP+FN
The ability of a test to correctly identify people with a coupon
Speficity = TN/TP+FN
We can change the threshold from 0.5 to 0.45 in order to decrease FN. Focus on sensitivity which is more important for business opportunities.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 884 415
## 1 922 1964
##
## Accuracy : 0.6805
## 95% CI : (0.6662, 0.6946)
## No Information Rate : 0.5685
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.3261
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Sensitivity : 0.8256
## Specificity : 0.4895
## Pos Pred Value : 0.6805
## Neg Pred Value : 0.6805
## Prevalence : 0.5685
## Detection Rate : 0.4693
## Detection Prevalence : 0.6896
## Balanced Accuracy : 0.6575
##
## 'Positive' Class : 1
##
ROC curve
test_roc = roc(test$Y ~ log_reg_prob1$"1", plot = TRUE, print.auc = TRUE)
With dummy variables we have 70 variables, some of the coefficients are already shrinked. We tried to apply Lasso and Elastic Net to find a reduced set of variables resulting to an optimal performing mode (penalty for having too many variables - regularization).
Lasso regression: the coefficients of some less contributive variables are zero. Only the most significant variables are kept in the final model.
Elastic net regression: the combination of ridge and lasso regression. It shrinks some coefficients toward zero and set some coefficients to exactly zero.
set.seed(123)
x=model.matrix(Y~., data=train)[,-20]
y=as.numeric(train$Y)
x.test <- model.matrix(Y ~., test)[,-20]
fit.lasso= glmnet(x, y, family = "binomial", alpha = 1) #lambda = NULL
plot(fit.lasso,xvar="lambda",label=TRUE)
The log of the optimal value of lambda is around -6, which is the one that minimizes the prediction error. The purpose of regularization is to balance accuracy and simplicity.
lasso_model <- glmnet(x, y, family = "binomial", alpha = 1, lambda = cv.lasso$lambda.min)
# Display regression coefficients
print('The value of the optimal lambda:')
## [1] "The value of the optimal lambda:"
cv.lasso$lambda.min
## [1] 0.001394676
coef(lasso_model)
## 71 x 1 sparse Matrix of class "dgCMatrix"
## s0
## (Intercept) -1.395440651
## (Intercept) .
## destinationNo Urgent Place 0.687677416
## destinationWork -0.029055342
## passangerFriends 0.161064848
## passangerKids -0.346999778
## passangerPartner 0.205737684
## weatherSnowy .
## weatherSunny 0.494608065
## temperature55 0.231844081
## temperature80 0.107032017
## couponCarry out & Take away 1.658855726
## couponCoffee House 0.463145232
## couponRestaurant(<20) 1.446256175
## couponRestaurant(20-50) 0.311719740
## genderMale 0.207668395
## age26 0.015276996
## age31 -0.157707910
## age36 -0.069876006
## age41 .
## age50plus -0.172175907
## agebelow21 .
## maritalStatusMarried partner .
## maritalStatusSingle 0.059809522
## maritalStatusUnmarried partner -0.096114244
## maritalStatusWidowed -0.053111199
## has_children1 0.007920614
## educationBachelors degree -0.107266369
## educationGraduate degree -0.288333043
## educationHigh School Graduate 0.215209216
## educationSome college - no degree 0.067963413
## educationSome High School 0.527705570
## income$12500 - $24999 -0.007796724
## income$25000 - $37499 0.087179080
## income$37500 - $49999 .
## income$50000 - $62499 0.182097445
## income$62500 - $74999 -0.273247786
## income$75000 - $87499 -0.151325805
## income$87500 - $99999 -0.180826363
## incomeLess than $12500 -0.076945301
## Bar4~8 .
## Bargt8 -0.201588609
## Barless1 -0.110958718
## Barnever -0.134647931
## CoffeeHouse4~8 0.007775047
## CoffeeHousegt8 -0.304124375
## CoffeeHouseless1 -0.412770446
## CoffeeHousenever -0.903520517
## CarryAway4~8 -0.025069505
## CarryAwaygt8 -0.136041211
## CarryAwayless1 -0.107593147
## CarryAwaynever -0.134109570
## RestaurantLessThan204~8 0.063105560
## RestaurantLessThan20gt8 0.090030765
## RestaurantLessThan20less1 0.017064766
## RestaurantLessThan20never 0.281946770
## Restaurant20To504~8 0.117941942
## Restaurant20To50gt8 0.103178598
## Restaurant20To50less1 -0.170603732
## Restaurant20To50never -0.270385703
## toCoupon_GEQ15min1 -0.074680603
## toCoupon_GEQ25min1 0.041470774
## direction_same1 0.478435414
## occupation_classOthers 0.126544850
## occupation_classProfessionals 0.028604913
## occupation_classRetired -0.068766314
## occupation_classService and sales .
## occupation_classStudent -0.019337819
## occupation_classTechnicians & prof 0.156277125
## occupation_classUnemployed -0.010457848
## expiration_weightage 0.637153176
#Make predictions on the test data
probabilities <- lasso_model %>% predict(x.test)
predicted.classes <- ifelse(probabilities > 0.5, "1", "0")
#Model accuracy
observed.classes <- test$Y
mean(predicted.classes == observed.classes)
## [1] 0.6551971
Results do not show any improvement in the the model performance on the test data.
ELASTIC NET
For elastic net regression, alpha is between 0 and 1.
We automatically select the best tuning parameters alpha and lambda
# Build the model
set.seed(123)
elastic <- train(
Y ~., data = train, method = "glmnet",
family = "binomial",
trControl = trainControl("cv", number = 10),
tuneLength = 5
)
# Best tuning parameter
elastic$bestTune
## alpha lambda
## 13 0.55 0.001672172
Training model with the best parameters and predicting on the test set
elastic_model <- glmnet(x, y, family = "binomial", alpha = elastic$bestTune$alpha, lambda = elastic$bestTune$lambda) #lambda = NULL
# Display regression coefficients
#coef(lasso_model)
#Make predictions on the test data
probabilities <- elastic_model %>% predict(x.test)
predicted.classes <- ifelse(probabilities > 0.5, "1", "0")
#Model accuracy
observed.classes <- test$Y
mean(predicted.classes == observed.classes)
## [1] 0.6568698
Elastic Net performs a bit better than Lasso.
lda.fit=lda(Y~.,data = train)
lda.fit
## Call:
## lda(Y ~ ., data = train)
##
## Prior probabilities of groups:
## 0 1
## 0.4315802 0.5684198
##
## Group means:
## destinationNo Urgent Place destinationWork passangerFriends passangerKids
## 0 0.4209378 0.2870774 0.1984733 0.08969466
## 1 0.5549576 0.2196233 0.3044918 0.07120679
## passangerPartner weatherSnowy weatherSunny temperature55 temperature80
## 0 0.08069793 0.13331516 0.7489095 0.3290622 0.4738277
## 1 0.09314842 0.09232043 0.8298489 0.2862761 0.5435728
## couponCarry out & Take away couponCoffee House couponRestaurant(<20)
## 0 0.1090513 0.3710469 0.1496728
## 1 0.2411509 0.2777893 0.2769613
## couponRestaurant(20-50) genderMale age26 age31 age36 age41
## 0 0.15130862 0.4612868 0.1870229 0.1723010 0.11068702 0.08560523
## 1 0.09169944 0.5056924 0.2119644 0.1506934 0.09728835 0.08818050
## age46 age50plus agebelow21 maritalStatusMarried partner
## 0 0.05261723 0.1589422 0.03489640 0.4201200
## 1 0.05237011 0.1275098 0.04595322 0.3877044
## maritalStatusSingle maritalStatusUnmarried partner maritalStatusWidowed
## 0 0.3454198 0.1761178 0.015267176
## 1 0.3930863 0.1718071 0.008693852
## has_children1 educationBachelors degree educationGraduate degree
## 0 0.4375682 0.3546892 0.1559433
## 1 0.3968123 0.3361623 0.1324777
## educationHigh School Graduate educationSome college - no degree
## 0 0.06733915 0.3241549
## 1 0.07555372 0.3595529
## educationSome High School income$12500 - $24999 income$25000 - $37499
## 0 0.005179935 0.1420393 0.1488550
## 1 0.009107845 0.1440696 0.1629062
## income$37500 - $49999 income$50000 - $62499 income$62500 - $74999
## 0 0.1442203 0.1251363 0.07279171
## 1 0.1446905 0.1397226 0.06085697
## income$75000 - $87499 income$87500 - $99999 incomeLess than $12500 Bar4~8
## 0 0.08015267 0.07360960 0.08042530 0.07142857
## 1 0.05733803 0.06706686 0.08528255 0.09356241
## Bargt8 Barless1 Barnever CoffeeHouse4~8 CoffeeHousegt8 CoffeeHouseless1
## 0 0.02671756 0.2824427 0.4449291 0.1185932 0.08315158 0.2876227
## 1 0.02960050 0.2829642 0.3792176 0.1575243 0.09066446 0.2651625
## CoffeeHousenever CarryAway4~8 CarryAwaygt8 CarryAwayless1 CarryAwaynever
## 0 0.3009815 0.3369684 0.1286805 0.1682116 0.012540894
## 1 0.1900228 0.3498241 0.1270958 0.1330987 0.009107845
## RestaurantLessThan204~8 RestaurantLessThan20gt8 RestaurantLessThan20less1
## 0 0.271265 0.09351145 0.1782988
## 1 0.294349 0.10908715 0.1535914
## RestaurantLessThan20never Restaurant20To504~8 Restaurant20To50gt8
## 0 0.01826609 0.04280262 0.01635769
## 1 0.01676671 0.06603188 0.02649555
## Restaurant20To50less1 Restaurant20To50never toCoupon_GEQ15min1
## 0 0.5016358 0.1949291 0.6090513
## 1 0.4713310 0.1598013 0.5261851
## toCoupon_GEQ25min1 direction_same1 occupation_classOthers
## 0 0.16248637 0.2003817 0.05806979
## 1 0.09045746 0.2171393 0.06582488
## occupation_classProfessionals occupation_classRetired
## 0 0.3977644 0.04770992
## 1 0.3914303 0.03311944
## occupation_classService and sales occupation_classStudent
## 0 0.1164122 0.1115049
## 1 0.1128131 0.1314428
## occupation_classTechnicians & prof occupation_classUnemployed
## 0 0.05916031 0.1567612
## 1 0.06375492 0.1457255
## expiration_weightage
## 0 0.7111097
## 1 0.8655550
##
## Coefficients of linear discriminants:
## LD1
## destinationNo Urgent Place 0.813177729
## destinationWork -0.070080891
## passangerFriends 0.208370081
## passangerKids -0.403107630
## passangerPartner 0.321473973
## weatherSnowy 0.187316197
## weatherSunny 0.607018816
## temperature55 0.400603495
## temperature80 0.245265120
## couponCarry out & Take away 1.994767404
## couponCoffee House 0.637269307
## couponRestaurant(<20) 1.735688877
## couponRestaurant(20-50) 0.494646962
## genderMale 0.236254826
## age26 0.008815704
## age31 -0.214391396
## age36 -0.121976162
## age41 -0.022040646
## age46 0.030075786
## age50plus -0.219693586
## agebelow21 -0.002374899
## maritalStatusMarried partner 0.064875722
## maritalStatusSingle 0.168788119
## maritalStatusUnmarried partner -0.038418038
## maritalStatusWidowed -0.033118049
## has_children1 0.085937730
## educationBachelors degree -0.162979134
## educationGraduate degree -0.361950647
## educationHigh School Graduate 0.285132939
## educationSome college - no degree 0.069865151
## educationSome High School 0.537923590
## income$12500 - $24999 -0.077022823
## income$25000 - $37499 0.050644130
## income$37500 - $49999 -0.072436049
## income$50000 - $62499 0.171546333
## income$62500 - $74999 -0.384698313
## income$75000 - $87499 -0.231434583
## income$87500 - $99999 -0.297018677
## incomeLess than $12500 -0.155226888
## Bar4~8 -0.088871251
## Bargt8 -0.370006357
## Barless1 -0.190356486
## Barnever -0.206910019
## CoffeeHouse4~8 0.000917293
## CoffeeHousegt8 -0.378030794
## CoffeeHouseless1 -0.483216073
## CoffeeHousenever -1.047960002
## CarryAway4~8 -0.064923378
## CarryAwaygt8 -0.196022289
## CarryAwayless1 -0.151099339
## CarryAwaynever -0.265174018
## RestaurantLessThan204~8 0.108687206
## RestaurantLessThan20gt8 0.169837647
## RestaurantLessThan20less1 0.077156231
## RestaurantLessThan20never 0.395936973
## Restaurant20To504~8 0.103955408
## Restaurant20To50gt8 0.163389659
## Restaurant20To50less1 -0.188367799
## Restaurant20To50never -0.326505292
## toCoupon_GEQ15min1 -0.117267708
## toCoupon_GEQ25min1 0.154603029
## direction_same1 0.635661820
## occupation_classOthers 0.264532648
## occupation_classProfessionals 0.115526192
## occupation_classRetired -0.021122386
## occupation_classService and sales 0.104580884
## occupation_classStudent 0.019922008
## occupation_classTechnicians & prof 0.281574402
## occupation_classUnemployed 0.039960347
## expiration_weightage 0.729621403
plot(lda.fit)
lda.pred=predict(lda.fit,test)$class
table(lda.pred,test$Y)
##
## lda.pred 0 1
## 0 1037 554
## 1 769 1825
mean(lda.pred==test$Y)
## [1] 0.683871
LDA predicts more False Positives as well, accuracy is similar to Logistic Regression (Robust result).
Improving the accuracy: - Remove potential outliers (Robust Logistic Regression, remove outliers within categorical variables and combinations of categorical variables) - Random forest and boosting models to capture non-linearities and interactions between the variables - Another option to apply Generalised Additive Models.
## 'data.frame': 12684 obs. of 22 variables:
## $ destination : Factor w/ 3 levels "Home","No Urgent Place",..: 2 2 2 2 2 2 2 2 2 2 ...
## $ passanger : Factor w/ 4 levels "Alone","Friends",..: 1 2 2 2 2 2 2 3 3 3 ...
## $ weather : Factor w/ 3 levels "Rainy","Snowy",..: 3 3 3 3 3 3 3 3 3 3 ...
## $ temperature : Factor w/ 3 levels "30","55","80": 2 3 3 3 3 3 2 3 3 3 ...
## $ coupon : Factor w/ 5 levels "Bar","Carry out & Take away",..: 4 3 2 3 3 4 2 4 2 1 ...
## $ gender : Factor w/ 2 levels "Female","Male": 1 1 1 1 1 1 1 1 1 1 ...
## $ age : Factor w/ 8 levels "21","26","31",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ maritalStatus : Factor w/ 5 levels "Divorced","Married partner",..: 4 4 4 4 4 4 4 4 4 4 ...
## $ has_children : Factor w/ 2 levels "0","1": 2 2 2 2 2 2 2 2 2 2 ...
## $ education : Factor w/ 6 levels "Associates degree",..: 5 5 5 5 5 5 5 5 5 5 ...
## $ income : Factor w/ 9 levels "$100000 or More",..: 4 4 4 4 4 4 4 4 4 4 ...
## $ Bar : Factor w/ 5 levels "1~3","4~8","gt8",..: 5 5 5 5 5 5 5 5 5 5 ...
## $ CoffeeHouse : Factor w/ 5 levels "1~3","4~8","gt8",..: 5 5 5 5 5 5 5 5 5 5 ...
## $ CarryAway : Factor w/ 5 levels "1~3","4~8","gt8",..: 1 4 4 4 4 4 1 4 4 4 ...
## $ RestaurantLessThan20: Factor w/ 5 levels "1~3","4~8","gt8",..: 2 2 2 2 2 2 2 2 2 2 ...
## $ Restaurant20To50 : Factor w/ 5 levels "1~3","4~8","gt8",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ toCoupon_GEQ15min : Factor w/ 2 levels "0","1": 1 1 2 2 2 2 2 2 2 2 ...
## $ toCoupon_GEQ25min : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
## $ direction_same : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
## $ Y : Factor w/ 2 levels "0","1": 2 1 2 1 1 2 2 2 2 1 ...
## $ occupation_class : Factor w/ 8 levels "Craft and related trades workers",..: 8 8 8 8 8 8 8 8 8 8 ...
## $ expiration_weightage: num [1:12684, 1] 1.334 0.111 0.111 0.111 1.334 ...
## ..- attr(*, "scaled:scale")= num 18
##
## Call:
## randomForest(formula = Y ~ ., data = train)
## Type of random forest: classification
## Number of trees: 500
## No. of variables tried at each split: 4
##
## OOB estimate of error rate: 25.19%
## Confusion matrix:
## 0 1 class.error
## 0 2452 1216 0.3315158
## 1 925 3906 0.1914717
# Perform training with parameters
rf_classifier = randomForest(Y ~., data = train, ntree=200, mtry=5, importance=TRUE)
rf_classifier
##
## Call:
## randomForest(formula = Y ~ ., data = train, ntree = 200, mtry = 5, importance = TRUE)
## Type of random forest: classification
## Number of trees: 200
## No. of variables tried at each split: 5
##
## OOB estimate of error rate: 25.17%
## Confusion matrix:
## 0 1 class.error
## 0 2477 1191 0.3247001
## 1 948 3883 0.1962327
# plot(rf_classifier)
varImpPlot(rf_classifier)
Most important variables: Coupon, coffee house, age, income, occupation, bar, restaurantlessthan20, expiration
MeanDecreaseAccuracy: gives a rough estimate of the loss in prediction performance when that particular variable is omitted from the training set. Caveat: if two variables are somewhat redundant, then omitting one of them may not lead to massive gains in prediction performance, but would make the second variable more important.
MeanDecreaseGini: GINI is a measure of node impurity. Think of it like this, if you use this feature to split the data, how pure will the nodes be? Highest purity means that each node contains only elements of a single class. Assessing the decrease in GINI when that feature is omitted leads to an understanding of how important that feature is to split the data correctly.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 1212 410
## 1 594 1969
##
## Accuracy : 0.7601
## 95% CI : (0.7469, 0.773)
## No Information Rate : 0.5685
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.505
##
## Mcnemar's Test P-Value : 7.676e-09
##
## Sensitivity : 0.8277
## Specificity : 0.6711
## Pos Pred Value : 0.7682
## Neg Pred Value : 0.7472
## Prevalence : 0.5685
## Detection Rate : 0.4705
## Detection Prevalence : 0.6124
## Balanced Accuracy : 0.7494
##
## 'Positive' Class : 1
##
# classification perfomance
# auc(test$Y,as.numeric(as.character(prediction_for_table)))
test_roc = roc(test$Y , as.numeric(as.character(prediction_for_table)), plot = TRUE, print.auc = TRUE)
# gaussian - regression, bernoulli - classification
rf_boost <- gbm(Y~.,data=train_boost,distribution="bernoulli",n.trees=5000,shrinkage=0.01,interaction.depth=1, cv.folds=3) #, verbose=F
best.iter = gbm.perf(rf_boost, method="cv") #Check the best iteration number
# plot - Summary of the model results, with the importance plot of predictors
par(mar = c(5, 8, 1, 1))
summary(rf_boost, cBars = 10,las = 2)
## var rel.inf
## coupon coupon 30.1105941
## CoffeeHouse CoffeeHouse 11.3105901
## expiration_weightage expiration_weightage 9.7430308
## income income 6.9154155
## destination destination 5.6196868
## weather weather 5.5998788
## passanger passanger 5.2079665
## occupation_class occupation_class 4.0144568
## age age 3.7807999
## education education 3.1227566
## Restaurant20To50 Restaurant20To50 1.9246713
## toCoupon_GEQ25min toCoupon_GEQ25min 1.7884993
## Bar Bar 1.7506133
## direction_same direction_same 1.7388944
## maritalStatus maritalStatus 1.5690136
## RestaurantLessThan20 RestaurantLessThan20 1.2921685
## gender gender 1.2840792
## CarryAway CarryAway 1.1991101
## temperature temperature 0.9763819
## toCoupon_GEQ15min toCoupon_GEQ15min 0.9205985
## has_children has_children 0.1307938
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 1010 546
## 1 796 1833
##
## Accuracy : 0.6793
## 95% CI : (0.6649, 0.6935)
## No Information Rate : 0.5685
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.3353
##
## Mcnemar's Test P-Value : 1.068e-11
##
## Sensitivity : 0.7705
## Specificity : 0.5592
## Pos Pred Value : 0.6972
## Neg Pred Value : 0.6491
## Prevalence : 0.5685
## Detection Rate : 0.4380
## Detection Prevalence : 0.6282
## Balanced Accuracy : 0.6649
##
## 'Positive' Class : 1
##
# Using the caret package the get the model preformance in the best iteration
set.seed(123)
fitControl = trainControl(method="cv", number=3, returnResamp = "all")
train_boost$Y <- as.factor(train_boost$Y)
test_boost$Y <- as.factor(test_boost$Y)
model2 = train(Y~., data=train_boost, method="gbm",distribution="bernoulli", trControl=fitControl,
verbose=F, tuneGrid=data.frame(.n.trees=best.iter, .shrinkage=0.01, .interaction.depth=1, .n.minobsinnode=1)) #
# model2
# confusionMatrix(model2)
mPred = predict(model2, test_boost[,-y_col]) #, na.action = na.pass
# postResample(mPred, test_boost$Y)
# confusionMatrix(mPred, test_boost$Y)
confusionMatrix(
as.factor(mPred),
as.factor(test_boost$Y),
positive = "1"
)
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 996 549
## 1 810 1830
##
## Accuracy : 0.6753
## 95% CI : (0.6608, 0.6894)
## No Information Rate : 0.5685
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.3264
##
## Mcnemar's Test P-Value : 1.753e-12
##
## Sensitivity : 0.7692
## Specificity : 0.5515
## Pos Pred Value : 0.6932
## Neg Pred Value : 0.6447
## Prevalence : 0.5685
## Detection Rate : 0.4373
## Detection Prevalence : 0.6308
## Balanced Accuracy : 0.6604
##
## 'Positive' Class : 1
##
## 'data.frame': 12684 obs. of 23 variables:
## $ destination : Factor w/ 3 levels "Home","No Urgent Place",..: 2 2 2 2 2 2 2 2 2 2 ...
## $ passanger : Factor w/ 4 levels "Alone","Friends",..: 1 2 2 2 2 2 2 3 3 3 ...
## $ weather : Factor w/ 3 levels "Rainy","Snowy",..: 3 3 3 3 3 3 3 3 3 3 ...
## $ temperature : Factor w/ 3 levels "30","55","80": 2 3 3 3 3 3 2 3 3 3 ...
## $ coupon : Factor w/ 5 levels "Bar","Carry out & Take away",..: 4 3 2 3 3 4 2 4 2 1 ...
## $ gender : Factor w/ 2 levels "Female","Male": 1 1 1 1 1 1 1 1 1 1 ...
## $ age : Factor w/ 8 levels "21","26","31",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ maritalStatus : Factor w/ 5 levels "Divorced","Married partner",..: 4 4 4 4 4 4 4 4 4 4 ...
## $ has_children : Factor w/ 2 levels "0","1": 2 2 2 2 2 2 2 2 2 2 ...
## $ education : Factor w/ 6 levels "Associates degree",..: 5 5 5 5 5 5 5 5 5 5 ...
## $ income : Factor w/ 9 levels "$100000 or More",..: 4 4 4 4 4 4 4 4 4 4 ...
## $ Bar : Factor w/ 5 levels "1~3","4~8","gt8",..: 5 5 5 5 5 5 5 5 5 5 ...
## $ CoffeeHouse : Factor w/ 5 levels "1~3","4~8","gt8",..: 5 5 5 5 5 5 5 5 5 5 ...
## $ CarryAway : Factor w/ 5 levels "1~3","4~8","gt8",..: 1 4 4 4 2 4 1 4 4 4 ...
## $ RestaurantLessThan20: Factor w/ 5 levels "1~3","4~8","gt8",..: 2 2 2 2 2 2 2 2 2 2 ...
## $ Restaurant20To50 : Factor w/ 5 levels "1~3","4~8","gt8",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ toCoupon_GEQ15min : Factor w/ 2 levels "0","1": 1 1 2 2 2 2 2 2 2 2 ...
## $ toCoupon_GEQ25min : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
## $ direction_same : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
## $ Y : Factor w/ 2 levels "0","1": 2 1 2 1 1 2 2 2 2 1 ...
## $ occupation_class : Factor w/ 8 levels "Craft and related trades workers",..: 8 8 8 8 8 8 8 8 8 8 ...
## $ expiration_weightage: num 1.334 0.111 0.111 0.111 1.334 ...
## $ ID : int 1 2 3 4 5 6 7 8 9 10 ...
(for mixed type data - categorical and numerical)
Cons: Requires an NxN distance matrix to be calculated, which is computationally intensive. One possible solution for this is to sample data, cluster the smaller sample, then treat the clustered sample as training data for k Nearest Neighbors and “classify” the rest of the data.
We create a sample of 1000 observations.
library(cluster)
library(Rtsne)
gower_distance <- function(datafr) {
gower_dist <- daisy(datafr,
metric = "gower",
type = list(logratio = 3))
return(gower_dist)
}
Clustering for entire driving scenario (with all the variables) Gower distance
set.seed(123)
#removing Y and ID
gower_dist <- gower_distance(sample_clust[,-c(20,23)])
## Warning in daisy(datafr, metric = "gower", type = list(logratio = 3)): binary
## variable(s) 21 treated as interval scaled
summary(gower_dist)
## 499500 dissimilarities, summarized :
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.0000 0.5238 0.6190 0.6140 0.7143 0.9524
## Metric : mixed ; Types = N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, N, I
## Number of objects : 1000
Gower distance = 0 minimum. 1 - maximum distance.
We have more dissimilar observaions in the dataset.
# Output most similar pair
gower_mat <- as.matrix(gower_dist)
sample_clust[
which(gower_mat == min(gower_mat[gower_mat != min(gower_mat)]),
arr.ind = TRUE)[1, ], ]
## destination passanger weather temperature coupon gender
## 2777 Work Alone Sunny 55 Restaurant(<20) Female
## 2757 No Urgent Place Alone Sunny 55 Restaurant(<20) Female
## age maritalStatus has_children education income Bar
## 2777 50plus Married partner 1 Bachelors degree $50000 - $62499 never
## 2757 50plus Married partner 1 Bachelors degree $50000 - $62499 never
## CoffeeHouse CarryAway RestaurantLessThan20 Restaurant20To50
## 2777 never less1 less1 less1
## 2757 never less1 less1 less1
## toCoupon_GEQ15min toCoupon_GEQ25min direction_same Y occupation_class
## 2777 0 0 0 1 Professionals
## 2757 0 0 0 1 Professionals
## expiration_weightage ID
## 2777 1.33374 2777
## 2757 1.33374 2757
# Output most dissimilar pair
sample_clust[
which(gower_mat == max(gower_mat[gower_mat != max(gower_mat)]),
arr.ind = TRUE)[1, ], ]
## destination passanger weather temperature coupon gender age
## 466 Home Alone Sunny 55 Bar Male 31
## 2463 No Urgent Place Partner Sunny 80 Coffee House Female 21
## maritalStatus has_children education income
## 466 Single 1 High School Graduate $50000 - $62499
## 2463 Unmarried partner 0 Some college - no degree $12500 - $24999
## Bar CoffeeHouse CarryAway RestaurantLessThan20 Restaurant20To50
## 466 gt8 gt8 gt8 gt8 gt8
## 2463 1~3 less1 1~3 1~3 less1
## toCoupon_GEQ15min toCoupon_GEQ25min direction_same Y occupation_class
## 466 0 0 1 1 Professionals
## 2463 1 0 0 1 Unemployed
## expiration_weightage ID
## 466 1.333740 466
## 2463 0.111145 2463
K-medoids Clustering
Partitioning around medoids with custom distance matrix
The k-medoids problem is a clustering problem similar to k-means.In contrast to the k-means algorithm, k-medoids chooses actual data points as centers (medoids or exemplars), and thereby allows for greater interpretability of the cluster centers than in k-means.
k-medoids can be used with arbitrary dissimilarity measures, whereas k-means generally requires Euclidean distance for efficient solutions. Because k-medoids minimizes a sum of pairwise dissimilarities instead of a sum of squared Euclidean distances, it is more robust to noise and outliers than k-means.
Selecting the number of clusters k using silhouette width, an internal validation metric which is an aggregated measure of how similar an observation is to its own cluster compared its closest neighboring cluster. The metric can range from -1 to 1, where higher values are better.
library(Rtsne)
# Calculate silhouette width for many k using PAM
sil_width <- c(NA)
for(i in 2:10){
pam_fit <- pam(gower_dist,
diss = TRUE,
k = i)
sil_width[i] <- pam_fit$silinfo$avg.width
}
# Plot sihouette width (higher is better)
plot(1:10, sil_width,
xlab = "Number of clusters",
ylab = "Silhouette Width")
lines(1:10, sil_width)
Interpret the clusters by running summary on each cluster:
In cluster 1 (430): higher frequency for Alone, Male, 21, Single, no_children, Some college - no degree/Bachelors degree, lower income, Student
In cluster 2 (570): higher frequency for No Urgent Place, Friends/Alone, Female, 50plus, Married_Partner, has_children, Bachelors degree/Graduate, higher income,Restaurant20To50, toCoupon_GEQ15min, Professionals
Other characteristics are overlapping for both the clusters, so we can explore if only the personal characteriscts and not the driving scenario lead to the cluster separation.
Distribution of Y is the same in the clusters (58% accepted in the first group and 55% in the second).
set.seed(123)
pam_fit <- pam(gower_dist, diss = TRUE, k = 2)
pam_results <- sample_clust %>%
dplyr::select(-ID) %>%
mutate(cluster = pam_fit$clustering) %>%
group_by(cluster) %>%
do(the_summary = summary(.))
#pam_results$the_summary
sample_clust[pam_fit$medoids, ]
## destination passanger weather temperature coupon gender
## 12584 Home Alone Sunny 80 Restaurant(20-50) Male
## 9753 No Urgent Place Friends Sunny 80 Coffee House Female
## age maritalStatus has_children education
## 12584 21 Single 0 Some college - no degree
## 9753 50plus Married partner 1 Bachelors degree
## income Bar CoffeeHouse CarryAway RestaurantLessThan20
## 12584 $12500 - $24999 never never 1~3 1~3
## 9753 $100000 or More never less1 4~8 1~3
## Restaurant20To50 toCoupon_GEQ15min toCoupon_GEQ25min direction_same Y
## 12584 less1 0 0 0 0
## 9753 1~3 1 0 0 1
## occupation_class expiration_weightage ID
## 12584 Student 1.33374 12584
## 9753 Professionals 1.33374 9753
One way to visualize many variables in a lower dimensional space is with t-distributed stochastic neighborhood embedding, or t-SNE. A dimensionality reduction technique that tries to preserve local structure so as to make clusters visible in a 2D or 3D visualization.
tsne_obj <- Rtsne(gower_dist, is_distance = TRUE)
tsne_data <- tsne_obj$Y %>%
data.frame() %>%
setNames(c("X", "Y")) %>%
mutate(cluster = factor(pam_fit$clustering),
name = sample_clust$ID)
ggplot(aes(x = X, y = Y), data = tsne_data) +
geom_point(aes(color = cluster))
Clusters are overlapping. Blue points are closer to blue cluster medoid for the metrics, however, it’s impossible to show it in a 2D space due to the fact that only 18% of variance of the data explained after dim reduction.
aggl.clust.c <- hclust(gower_dist, method = "complete")
plot(aggl.clust.c,
main = "Agglomerative, complete linkages")
#
# aggl.clust.c <- hclust(gower_dist, method = "average")
# plot(aggl.clust.c,
# main = "Agglomerative, average linkages")
#
# aggl.clust.c <- hclust(gower_dist, method = "single")
# plot(aggl.clust.c,
# main = "Agglomerative, single linkages")
aggl.clust.w <- hclust(gower_dist, method = "ward.D2")
plot(aggl.clust.w,
main = "Agglomerative, ward linkages")
# hierarchical clustering using Ward linkage
dendro <- as.dendrogram(aggl.clust.w)
dendro.col <- dendro %>%
set("branches_k_color", k = 2, value = c( "gold3", "darkcyan")) %>%
set("branches_lwd", 0.6) %>%
set("labels_colors",
value = c("darkslategray")) %>%
set("labels_cex", 0.5)
ggd1 <- as.ggdend(dendro.col)
ggplot(ggd1, theme = theme_minimal()) +
labs(x = "Num. observations", y = "Height", title = "Dendrogram, k = 2")
groups <- cutree(aggl.clust.w, k=2) # cut tree into 5 clusters
clusplot(sample_clust, groups, color=TRUE, shade=TRUE,
labels=2, lines=0, main= 'Driving scenario clusters')
Rand Index looks at similarity between any two clustering methods. The Rand Index gives a value between 0 and 1,where 1 means the two clustering outcomes match identically
Dataset Information
## 'data.frame': 1000 obs. of 9 variables:
## $ gender : Factor w/ 2 levels "Female","Male": 1 2 1 1 1 2 2 1 2 2 ...
## $ age : Factor w/ 8 levels "21","26","31",..: 1 1 2 3 2 1 5 6 2 1 ...
## $ maritalStatus : Factor w/ 5 levels "Divorced","Married partner",..: 4 3 4 2 4 4 3 1 3 3 ...
## $ has_children : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 2 1 1 ...
## $ education : Factor w/ 6 levels "Associates degree",..: 5 5 2 5 5 5 4 2 4 5 ...
## $ income : Factor w/ 9 levels "$100000 or More",..: 2 1 4 1 5 3 2 3 2 9 ...
## $ Y : Factor w/ 2 levels "0","1": 2 2 2 1 2 1 2 2 2 2 ...
## $ occupation_class: Factor w/ 8 levels "Craft and related trades workers",..: 8 6 7 3 2 5 1 2 5 6 ...
## $ ID : int 2463 2511 10419 8718 12483 2986 1842 9334 3371 11638 ...
## 'data.frame': 1000 obs. of 6 variables:
## $ coupon : Factor w/ 5 levels "Bar","Carry out & Take away",..: 3 3 3 5 1 2 3 4 3 1 ...
## $ toCoupon_GEQ15min : Factor w/ 2 levels "0","1": 2 2 1 2 2 2 1 1 1 2 ...
## $ toCoupon_GEQ25min : Factor w/ 2 levels "0","1": 1 2 1 1 1 1 1 1 1 1 ...
## $ Y : Factor w/ 2 levels "0","1": 2 2 2 1 2 1 2 2 2 2 ...
## $ expiration_weightage: num 0.111 0.111 1.334 0.111 0.111 ...
## $ ID : int 2463 2511 10419 8718 12483 2986 1842 9334 3371 11638 ...
pam_results$the_summary
## [[1]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0: 0 0:59 0: 0
## Carry out & Take away: 0 1:66 1: 7 1:66
## Coffee House :66
## Restaurant(<20) : 0
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :1
## 1st Qu.:0.1111 1st Qu.:1
## Median :1.3337 Median :1
## Mean :0.7595 Mean :1
## 3rd Qu.:1.3337 3rd Qu.:1
## Max. :1.3337 Max. :1
##
## [[2]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0:85 0:85 0: 0
## Carry out & Take away: 0 1: 0 1: 0 1:85
## Coffee House :85
## Restaurant(<20) : 0
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :2
## 1st Qu.:0.1111 1st Qu.:2
## Median :0.1111 Median :2
## Mean :0.6865 Mean :2
## 3rd Qu.:1.3337 3rd Qu.:2
## Max. :1.3337 Max. :2
##
## [[3]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0: 0 0:43 0:47
## Carry out & Take away: 0 1:47 1: 4 1: 0
## Coffee House : 0
## Restaurant(<20) : 0
## Restaurant(20-50) :47
##
## expiration_weightage cluster
## Min. :0.1111 Min. :3
## 1st Qu.:0.1111 1st Qu.:3
## Median :1.3337 Median :3
## Mean :0.9956 Mean :3
## 3rd Qu.:1.3337 3rd Qu.:3
## Max. :1.3337 Max. :3
##
## [[4]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar :30 0: 0 0:20 0: 0
## Carry out & Take away: 0 1:30 1:10 1:30
## Coffee House : 0
## Restaurant(<20) : 0
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :4
## 1st Qu.:0.4168 1st Qu.:4
## Median :1.3337 Median :4
## Mean :1.0077 Mean :4
## 3rd Qu.:1.3337 3rd Qu.:4
## Max. :1.3337 Max. :4
##
## [[5]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0: 0 0:25 0:28
## Carry out & Take away:28 1:28 1: 3 1: 0
## Coffee House : 0
## Restaurant(<20) : 0
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :5
## 1st Qu.:0.1111 1st Qu.:5
## Median :0.1111 Median :5
## Mean :0.5041 Mean :5
## 3rd Qu.:1.3337 3rd Qu.:5
## Max. :1.3337 Max. :5
##
## [[6]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0:95 0:95 0: 0
## Carry out & Take away: 0 1: 0 1: 0 1:95
## Coffee House : 0
## Restaurant(<20) :94
## Restaurant(20-50) : 1
##
## expiration_weightage cluster
## Min. :0.1111 Min. :6
## 1st Qu.:0.1111 1st Qu.:6
## Median :1.3337 Median :6
## Mean :0.9090 Mean :6
## 3rd Qu.:1.3337 3rd Qu.:6
## Max. :1.3337 Max. :6
##
## [[7]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0: 0 0:76 0: 0
## Carry out & Take away:101 1:101 1:25 1:101
## Coffee House : 0
## Restaurant(<20) : 0
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :7
## 1st Qu.:0.1111 1st Qu.:7
## Median :1.3337 Median :7
## Mean :0.9464 Mean :7
## 3rd Qu.:1.3337 3rd Qu.:7
## Max. :1.3337 Max. :7
##
## [[8]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0: 0 0:85 0:97
## Carry out & Take away: 0 1:97 1:12 1: 0
## Coffee House :97
## Restaurant(<20) : 0
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :8
## 1st Qu.:0.1111 1st Qu.:8
## Median :0.1111 Median :8
## Mean :0.5397 Mean :8
## 3rd Qu.:1.3337 3rd Qu.:8
## Max. :1.3337 Max. :8
##
## [[9]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar :27 0:28 0:28 0: 0
## Carry out & Take away: 0 1: 0 1: 0 1:28
## Coffee House : 0
## Restaurant(<20) : 0
## Restaurant(20-50) : 1
##
## expiration_weightage cluster
## Min. :0.1111 Min. :9
## 1st Qu.:1.3337 1st Qu.:9
## Median :1.3337 Median :9
## Mean :1.2027 Mean :9
## 3rd Qu.:1.3337 3rd Qu.:9
## Max. :1.3337 Max. :9
##
## [[10]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar :23 0:30 0:30 0:30
## Carry out & Take away: 7 1: 0 1: 0 1: 0
## Coffee House : 0
## Restaurant(<20) : 0
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :10
## 1st Qu.:0.1111 1st Qu.:10
## Median :1.3337 Median :10
## Mean :0.8855 Mean :10
## 3rd Qu.:1.3337 3rd Qu.:10
## Max. :1.3337 Max. :10
##
## [[11]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0:61 0:61 0: 0
## Carry out & Take away:60 1: 0 1: 0 1:61
## Coffee House : 0
## Restaurant(<20) : 0
## Restaurant(20-50) : 1
##
## expiration_weightage cluster
## Min. :0.1111 Min. :11
## 1st Qu.:0.1111 1st Qu.:11
## Median :1.3337 Median :11
## Mean :0.8527 Mean :11
## 3rd Qu.:1.3337 3rd Qu.:11
## Max. :1.3337 Max. :11
##
## [[12]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0: 0 0:52 0: 0
## Carry out & Take away: 0 1:58 1: 6 1:58
## Coffee House : 0
## Restaurant(<20) :58
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :12
## 1st Qu.:0.1111 1st Qu.:12
## Median :0.1111 Median :12
## Mean :0.6170 Mean :12
## 3rd Qu.:1.3337 3rd Qu.:12
## Max. :1.3337 Max. :12
##
## [[13]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0:30 0:30 0:30
## Carry out & Take away: 1 1: 0 1: 0 1: 0
## Coffee House : 0
## Restaurant(<20) :29
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :13
## 1st Qu.:0.1111 1st Qu.:13
## Median :0.1111 Median :13
## Mean :0.5187 Mean :13
## 3rd Qu.:1.3337 3rd Qu.:13
## Max. :1.3337 Max. :13
##
## [[14]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0:42 0:42 0:30
## Carry out & Take away: 8 1: 0 1: 0 1:12
## Coffee House : 0
## Restaurant(<20) : 0
## Restaurant(20-50) :34
##
## expiration_weightage cluster
## Min. :0.1111 Min. :14
## 1st Qu.:0.1111 1st Qu.:14
## Median :1.3337 Median :14
## Mean :0.8389 Mean :14
## 3rd Qu.:1.3337 3rd Qu.:14
## Max. :1.3337 Max. :14
##
## [[15]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar :74 0: 0 0:45 0:74
## Carry out & Take away: 0 1:74 1:29 1: 0
## Coffee House : 0
## Restaurant(<20) : 0
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :15
## 1st Qu.:0.1111 1st Qu.:15
## Median :1.3337 Median :15
## Mean :0.9868 Mean :15
## 3rd Qu.:1.3337 3rd Qu.:15
## Max. :1.3337 Max. :15
##
## [[16]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0: 2 0:24 0: 0
## Carry out & Take away: 0 1:26 1: 4 1:28
## Coffee House : 0
## Restaurant(<20) : 0
## Restaurant(20-50) :28
##
## expiration_weightage cluster
## Min. :0.1111 Min. :16
## 1st Qu.:1.3337 1st Qu.:16
## Median :1.3337 Median :16
## Mean :1.1154 Mean :16
## 3rd Qu.:1.3337 3rd Qu.:16
## Max. :1.3337 Max. :16
##
## [[17]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0: 0 0:17 0:34
## Carry out & Take away: 0 1:34 1:17 1: 0
## Coffee House : 0
## Restaurant(<20) :34
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :17
## 1st Qu.:0.1111 1st Qu.:17
## Median :0.1111 Median :17
## Mean :0.3269 Mean :17
## 3rd Qu.:0.1111 3rd Qu.:17
## Max. :1.3337 Max. :17
##
## [[18]]
## coupon toCoupon_GEQ15min toCoupon_GEQ25min Y
## Bar : 0 0:66 0:66 0:66
## Carry out & Take away: 3 1: 0 1: 0 1: 0
## Coffee House :63
## Restaurant(<20) : 0
## Restaurant(20-50) : 0
##
## expiration_weightage cluster
## Min. :0.1111 Min. :18
## 1st Qu.:0.1111 1st Qu.:18
## Median :1.3337 Median :18
## Mean :0.8336 Mean :18
## 3rd Qu.:1.3337 3rd Qu.:18
## Max. :1.3337 Max. :18