Bangalore Online Food Delivery Preferences - Prediksi

Pengantar

Latar Belakang

Di era perkembangan teknologi sekarang ini berdampak ke seluruh elemen kehidupan masyarakat termasuk industri kuliner. Jika kita ingin memesan sebuah makanan kita tak perlu lagi datang ke restoran tersebut. Cukup hanya dengan menggunakan aplikasi layanan pesan antar makanan yang berbasis online. Banyak negara sudah menggunakan aplikasi tersebut. Salah satunya India di kota Bangalore. Permintaan akan layanan pesan antar makanan online meningkat di sana.

Kita dapat menggunakan dataset Bangalore Online Food Delivery Preferences untuk mengetahui konsumen yang akan melakukan pemesanan kembali dengan menggunakan model machine learning. Kita akan membuat model Decision Tree dan Random Forest untuk mengetahui performa kedua model tersebut.

Tentang Dataset

Dataset Bangalore Online Food Delivery Preferences merupakan data hasil survei yang mencakup data pribadi dan pendapat konsumen pengguna aplikasi layanan pesan antar makanan online di kota Bangalore. Dataset tersebut memiliki 388 baris dan 55 kolom yang beberapa di antaranya.

Age : Umur dalam tahun
Gender : Jenis kelamin
Marital.Status : Status
Occupation : Pekerjaan
Monthly.Income : Pendapatan per bulan
Educational.Qualifications : Pendidikan terakhir
Family.size : Jumlah anggota keluarga
latitude : Garis lintang tempat tinggal
longitude : Garis bujur tempat tinggal
Pin.code : Kode pos tempat tinggal

Set Up

Yang pertama kita lakukan adalah memanggil library yang dibutuhkan.

library(tidyverse)
library(GGally)
library(reactable)
library(MLmetrics)
library(lmtest)
library(car)
library(caret)
library(class)
library(e1071)
library(partykit)
library(randomForest)

Data Wrangling

Selanjutnya kita input dataset Bangalore Online Food Delivery Preferences menggunakan fungsi read.csv() dan menyimpannya ke dalam variabel deliv. Kita perlu menggunakan parameter stringsAsFactors untuk mengubah tipe data character menjadi factor.

deliv <- read.csv("onlinedeliverydata.csv",
                  stringsAsFactors = T)

reactable(deliv, defaultPageSize = 5)

Kita menggunakan fungsi glimpse() untuk melihat apakah masih ada tipe data yang belum sesuai.

glimpse(deliv)
## Rows: 388
## Columns: 55
## $ Age                                       <int> 20, 24, 22, 22, 22, 27, 22, …
## $ Gender                                    <fct> Female, Female, Male, Female…
## $ Marital.Status                            <fct> Single, Single, Single, Sing…
## $ Occupation                                <fct> Student, Student, Student, S…
## $ Monthly.Income                            <fct> No Income, Below Rs.10000, B…
## $ Educational.Qualifications                <fct> Post Graduate, Graduate, Pos…
## $ Family.size                               <int> 4, 3, 3, 6, 4, 2, 3, 3, 2, 4…
## $ latitude                                  <dbl> 12.9766, 12.9770, 12.9551, 1…
## $ longitude                                 <dbl> 77.5993, 77.5773, 77.6593, 7…
## $ Pin.code                                  <int> 560001, 560009, 560017, 5600…
## $ Medium..P1.                               <fct> Food delivery apps, Food del…
## $ Medium..P2.                               <fct>  Web browser,  Web browser, …
## $ Meal.P1.                                  <fct> Breakfast, Snacks, Lunch, Sn…
## $ Meal.P2.                                  <fct>  Lunch,  Dinner,  Snacks,  D…
## $ Perference.P1.                            <fct> Non Veg foods (Lunch / Dinne…
## $ Perference.P2.                            <fct>  Bakery items (snacks),  Veg…
## $ Ease.and.convenient                       <fct> Neutral, Strongly agree, Str…
## $ Time.saving                               <fct> Neutral, Strongly agree, Str…
## $ More.restaurant.choices                   <fct> Neutral, Strongly agree, Str…
## $ Easy.Payment.option                       <fct> Neutral, Strongly agree, Neu…
## $ More.Offers.and.Discount                  <fct> Neutral, Strongly agree, Neu…
## $ Good.Food.quality                         <fct> Neutral, Neutral, Disagree, …
## $ Good.Tracking.system                      <fct> Neutral, Agree, Neutral, Agr…
## $ Self.Cooking                              <fct> Neutral, Strongly agree, Dis…
## $ Health.Concern                            <fct> Neutral, Strongly agree, Neu…
## $ Late.Delivery                             <fct> Neutral, Agree, Neutral, Neu…
## $ Poor.Hygiene                              <fct> Neutral, Strongly agree, Agr…
## $ Bad.past.experience                       <fct> Neutral, Strongly agree, Agr…
## $ Unavailability                            <fct> Neutral, Strongly agree, Agr…
## $ Unaffordable                              <fct> Neutral, Strongly agree, Agr…
## $ Long.delivery.time                        <fct> Agree, Strongly agree, Agree…
## $ Delay.of.delivery.person.getting.assigned <fct> Agree, Strongly agree, Agree…
## $ Delay.of.delivery.person.picking.up.food  <fct> Agree, Strongly agree, Agree…
## $ Wrong.order.delivered                     <fct> Agree, Strongly agree, Stron…
## $ Missing.item                              <fct> Agree, Strongly agree, Agree…
## $ Order.placed.by.mistake                   <fct> Agree, Strongly agree, Neutr…
## $ Influence.of.time                         <fct> Yes, Yes, Yes, Yes, Yes, Yes…
## $ Order.Time                                <fct> Weekend (Sat & Sun), Anytime…
## $ Maximum.wait.time                         <fct> 30 minutes, 30 minutes, 45 m…
## $ Residence.in.busy.location                <fct> Agree, Strongly Agree, Agree…
## $ Google.Maps.Accuracy                      <fct> Neutral, Neutral, Strongly A…
## $ Good.Road.Condition                       <fct> Neutral, Disagree, Neutral, …
## $ Low.quantity.low.time                     <fct> Neutral, Strongly disagree, …
## $ Delivery.person.ability                   <fct> Neutral, Agree, Agree, Agree…
## $ Influence.of.rating                       <fct> Yes, Yes, Yes, Yes, Yes, Yes…
## $ Less.Delivery.time                        <fct> Moderately Important, Very I…
## $ High.Quality.of.package                   <fct> Moderately Important, Very I…
## $ Number.of.calls                           <fct> Moderately Important, Very I…
## $ Politeness                                <fct> Moderately Important, Very I…
## $ Freshness                                 <fct> Moderately Important, Very I…
## $ Temperature                               <fct> Moderately Important, Very I…
## $ Good.Taste                                <fct> Moderately Important, Very I…
## $ Good.Quantity                             <fct> Moderately Important, Very I…
## $ Output                                    <fct> Yes, Yes, Yes, Yes, Yes, Yes…
## $ Reviews                                   <fct> "Nil\n", "Nil", "Many a time…

Kita perlu mengubah tipe data kolom Family.size dan Pin.code menjadi factor untuk mempermudah model dalam melakukan prediksi.

deliv <-
  deliv %>% 
  mutate(Family.size = as.factor(Family.size),
         Pin.code = as.factor(Pin.code))

Selanjutnya kita perlu membuang beberapa kolom yaitu latitude, longitude, dan Reviews karena kita tidak melakukan analisa teks dan geospasial.

deliv <-
  deliv %>% 
  select(-c(55, 8, 9))

Kita juga perlu melihat apakah terdapat missing value pada dataset kita dengan menggunakan fungsi anyNA().

anyNA(deliv)
## [1] FALSE

Tidak terdapat missing value pada dataset kita.

Exploratory Data Analysis

Sebelum kita melakukan Cross Validation kita perlu melihat apakah variabel target sudah seimbang atau belum.

table(deliv$Output)
## 
##  No Yes 
##  87 301

Hasil di atas menunjukkan jika variabel target yang kita punya belum seimbang. Untuk mengatasi hal tersebut kita akan menggunakan metode Down Sample. Kita akan mengurangi kelas mayoritas hingga seimbang dengan kelas minoritas dengan menggunakan fungsi downSample().

RNGkind(sample.kind = "Rounding")
set.seed(100)

deliv <- downSample(x = deliv %>% select(-Output),
                    y = deliv$Output,
                    yname = "Output")

Kita perlu melihat kembali proporsi variabel target yang kita punya.

table(deliv$Output)
## 
##  No Yes 
##  87  87

Variabel target pada data kita sudah seimbang.

Data Preprocessing

Kita akan melakukan Cross Validation dengan proporsi data train 80% dan sisanya menjadi data test.

RNGkind(sample.kind = "Rounding")
set.seed(100)

index <- sample(nrow(deliv), nrow(deliv)*0.8)

deliv_train <- deliv[index,]
deliv_test <- deliv[-index,]

Modeling

Model pertama yang akan kita buat adalah Decision Tree. Decision Tree merupakan model yang memiliki bentuk seperti pohon keputusan yang dapat digunakan untuk regresi maupun klasifikasi. Kita akan membuat model Decision Tree dengan menggunakan fungsi ctree() dan menyimpannya ke dalam variabel dtree_model. Di dalam fungsi ctree() terdapat parameter control yang harus kita isi seperti mincriterion, minsplit, dan minbucket. Kita akan mengisi 0,95 pada mincriterion karena kita ingin memilih prediktor signifikan yang memiliki nilai p kurang dari 0,05. Pada parameter minsplit dan minbucket kita isi nol agar model tidak memiliki nilai minimal jumlah observasi dalam membuat cabang pada interior node dan terminal node.

dtree_model <- ctree(formula = Output ~.,
                     data = deliv_train,
                     control = ctree_control(mincriterion = 0.95,
                                             minsplit = 0,
                                             minbucket = 0))

Selanjutnya kita membuat plot dari model yang telah kita buat.

plot(dtree_model, type = "simple")

Kita dapat melihat nilai p, n, dan err. Nilai p adalah tingkat signifikansi variabel prediktor. Nilai n adalah jumlah observasi pada terminal node. Dan nilai err adalah jumlah error dalam persentasi.

Kita akan melakukan prediksi dengan menggunakan fungsi predict() pada data deliv_train dan deliv_test. Hal ini kita lakukan untuk mengetahui apakah model overfitting, underfitting, atau sudah baik. Prediksi pada data train dan data test kita simpan ke dalam variabel train_pred dan test_pred.

train_pred <- predict(object = dtree_model, 
                      newdata = deliv_train, 
                      type = "response")

test_pred <- predict(object = dtree_model, 
                     newdata = deliv_test, 
                     type = "response")

Untuk mengetahui apakah model overfitting, underfitting, atau sudah baik bisa dengan menggunakan fungsi confusionMatrix() pada hasil kedua prediksi yang tadi telah kita buat.

confusionMatrix(data = train_pred,
                reference = deliv_train$Output,
                positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No  51   1
##        Yes 21  66
##                                           
##                Accuracy : 0.8417          
##                  95% CI : (0.7702, 0.8981)
##     No Information Rate : 0.518           
##     P-Value [Acc > NIR] : 1.076e-15       
##                                           
##                   Kappa : 0.6863          
##                                           
##  Mcnemar's Test P-Value : 5.104e-05       
##                                           
##             Sensitivity : 0.9851          
##             Specificity : 0.7083          
##          Pos Pred Value : 0.7586          
##          Neg Pred Value : 0.9808          
##              Prevalence : 0.4820          
##          Detection Rate : 0.4748          
##    Detection Prevalence : 0.6259          
##       Balanced Accuracy : 0.8467          
##                                           
##        'Positive' Class : Yes             
## 
confusionMatrix(data = test_pred,
                reference = deliv_test$Output,
                positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No   9   1
##        Yes  6  19
##                                           
##                Accuracy : 0.8             
##                  95% CI : (0.6306, 0.9156)
##     No Information Rate : 0.5714          
##     P-Value [Acc > NIR] : 0.003999        
##                                           
##                   Kappa : 0.5739          
##                                           
##  Mcnemar's Test P-Value : 0.130570        
##                                           
##             Sensitivity : 0.9500          
##             Specificity : 0.6000          
##          Pos Pred Value : 0.7600          
##          Neg Pred Value : 0.9000          
##              Prevalence : 0.5714          
##          Detection Rate : 0.5429          
##    Detection Prevalence : 0.7143          
##       Balanced Accuracy : 0.7750          
##                                           
##        'Positive' Class : Yes             
## 

Kita dapat melihat nilai Accuracy pada kedua hasil prediksi relatif cukup dekat. Hal ini mengindikasikan model yang sudah kita buat tidak mengalami overfitting maupun underfitting atau sudah baik.

Tetapi kita coba mengevaluasi model dengan mengisi parameter minsplit 20 dan minbucket tujuh. Kita ingin memberi minimal batasan observasi pada model untuk membuat cabang di interior node maupun terminal node. Model tersebut kita simpan ke dalam variabel dtree_model2.

dtree_model2 <- ctree(formula = Output ~.,
                      data = deliv_train,
                      control = ctree_control(mincriterion = 0.95,
                                              minsplit = 20,
                                              minbucket = 7))

Kita melakukan prediksi kembali pada data train dan data test. Kita simpan ke dalam variabel train_pred2 dan test_pred2.

train_pred2 <- predict(object = dtree_model2, 
                       newdata = deliv_train, 
                       type = "response")

test_pred2 <- predict(object = dtree_model2, 
                      newdata = deliv_test, 
                      type = "response")

Kita akan melihat performa model yang telah kita buat dengan menggunakan fungsi confusionMatrix() sekaligus memastikan model kita tidak overfitting maupun underfitting.

confusionMatrix(data = train_pred2,
                reference = deliv_train$Output,
                positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No  51   5
##        Yes 21  62
##                                          
##                Accuracy : 0.8129         
##                  95% CI : (0.7381, 0.874)
##     No Information Rate : 0.518          
##     P-Value [Acc > NIR] : 4.23e-13       
##                                          
##                   Kappa : 0.6285         
##                                          
##  Mcnemar's Test P-Value : 0.003264       
##                                          
##             Sensitivity : 0.9254         
##             Specificity : 0.7083         
##          Pos Pred Value : 0.7470         
##          Neg Pred Value : 0.9107         
##              Prevalence : 0.4820         
##          Detection Rate : 0.4460         
##    Detection Prevalence : 0.5971         
##       Balanced Accuracy : 0.8169         
##                                          
##        'Positive' Class : Yes            
## 
confusionMatrix(data = test_pred2,
                reference = deliv_test$Output,
                positive = "Yes")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No  10   1
##        Yes  5  19
##                                           
##                Accuracy : 0.8286          
##                  95% CI : (0.6635, 0.9344)
##     No Information Rate : 0.5714          
##     P-Value [Acc > NIR] : 0.001202        
##                                           
##                   Kappa : 0.6379          
##                                           
##  Mcnemar's Test P-Value : 0.220671        
##                                           
##             Sensitivity : 0.9500          
##             Specificity : 0.6667          
##          Pos Pred Value : 0.7917          
##          Neg Pred Value : 0.9091          
##              Prevalence : 0.5714          
##          Detection Rate : 0.5429          
##    Detection Prevalence : 0.6857          
##       Balanced Accuracy : 0.8083          
##                                           
##        'Positive' Class : Yes             
## 

Berdasarkan hasil di atas selisih nilai Accuracy pada kedua hasil prediksi semakin kecil. Ini menandakan model yang kedua yang telah kita buat lebih baik dari model yang pertama. Kita juga dapat melihat nilai Accuracy, Specificity, dan Pos Pred Value pada prediksi data test model kedua juga lebih baik dari model yang pertama.

Model Evaluation

Setelah membuat model Decision Tree kita akan membuat model Random Forest. Model Random Forest adalah model yang terdiri dari banyak Decision Tree. Sama seperti Decision Tree, Random Forest juga dapat digunakan pada klasifikasi maupun regresi.

Sebelum membuat model kita perlu melakukan K-Fold Cross Validation terlebih dahulu. Kita melakukan K-Fold Cross Validation menggunakan fungsi trainControl() dengan beberapa parameter yang perlu diisi. Parameter method kita isi dengan “repeatedcv” yang merupakan metode K-Fold Cross Validation yang ingin kita gunakan. Parameter number kita isi lima karena kita ingin model melakukan cross validation sebanyak lima kali. Dan parameter repeats kita isi tiga karena kita ingin model melakukan tiga kali repetisi. Setelah itu kita simpan ke dalam variabel ctrl.

RNGkind(sample.kind = "Rounding")
set.seed(100)

ctrl <- trainControl(method = "repeatedcv", 
                     number = 5, 
                     repeats = 3) 

Kita akan membuat model Random Forest menggunakan fungsi train() pada data deliv_train dengan semua variabel selain Output untuk dijadikan prediktor. Pada parameter method kita isi dengan “rf” karena kita ingin membuat sebuah model Random Forest. Sedangkan parameter trControl kita isi ctrl hasil K-Fold Cross Validation sebelumnya. Model yang sudah kita buat kita simpan ke dalam variabel train_rf.

train_rf <- train(Output ~ .,
                  data = deliv_train,
                  method = "rf", 
                  trControl = ctrl)

train_rf
## Random Forest 
## 
## 139 samples
##  51 predictor
##   2 classes: 'No', 'Yes' 
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold, repeated 3 times) 
## Summary of sample sizes: 111, 111, 112, 112, 110, 111, ... 
## Resampling results across tuning parameters:
## 
##   mtry  Accuracy   Kappa    
##     2   0.8272639  0.6534159
##   128   0.8825731  0.7658541
##   254   0.8682692  0.7373202
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 128.

Setelah membuat model kita akan melakukan prediksi menggunakan data deliv_test dan menyimpannya ke dalam variabel train_pred_rf.

train_pred_rf <- predict(object = train_rf, 
                         newdata = deliv_test, 
                         type = "raw")

head(train_pred_rf)
## [1] No  No  No  No  No  Yes
## Levels: No Yes

Kita dapat melihat hasil prediksi pada enam data teratas.

Kita kembali menggunakan confusionMatrix() untuk melihat performa model yang telah kita buat.

confusionMatrix(data = train_pred_rf, 
                reference = as.factor(deliv_test$Output))
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No  13   2
##        Yes  2  18
##                                          
##                Accuracy : 0.8857         
##                  95% CI : (0.7326, 0.968)
##     No Information Rate : 0.5714         
##     P-Value [Acc > NIR] : 6.136e-05      
##                                          
##                   Kappa : 0.7667         
##                                          
##  Mcnemar's Test P-Value : 1              
##                                          
##             Sensitivity : 0.8667         
##             Specificity : 0.9000         
##          Pos Pred Value : 0.8667         
##          Neg Pred Value : 0.9000         
##              Prevalence : 0.4286         
##          Detection Rate : 0.3714         
##    Detection Prevalence : 0.4286         
##       Balanced Accuracy : 0.8833         
##                                          
##        'Positive' Class : No             
## 

Nilai Accuracy, Specificity, dan Pos Pred Value pada model Random Forest lebih baik dari model Decision Tree sebelumnya. Tetapi pada model Decision Tree nilai Sensitivity yang didapat adalah 0,95 sedangkan pada model Random Forest 0,87.

Kita akan melakukan interpretasi menggunakan metode Variable Importance dengan fungsi varImp(). Dengan metode tersebut kita dapat melihat variabel prediktor yang berperan penting dalam pembuatan model Random Forest kita.

varImp(train_rf)
## rf variable importance
## 
##   only 20 most important variables shown (out of 254)
## 
##                                                  Overall
## Ease.and.convenientDisagree                      100.000
## Time.savingDisagree                               77.622
## More.restaurant.choicesDisagree                   47.950
## Age                                               17.424
## PolitenessVery Important                          12.567
## More.Offers.and.DiscountDisagree                  11.249
## Missing.itemNeutral                               10.922
## Pin.code560024                                     7.108
## Pin.code560064                                     6.151
## Delay.of.delivery.person.getting.assignedNeutral   5.516
## Good.Road.ConditionStrongly Agree                  5.239
## Pin.code560029                                     5.059
## Pin.code560066                                     4.658
## Good.Food.qualityStrongly agree                    4.325
## Pin.code560041                                     4.079
## Good.Tracking.systemNeutral                        3.950
## Late.DeliveryStrongly agree                        3.858
## Perference.P2. Sweets                              3.853
## Pin.code560028                                     3.510
## Poor.HygieneNeutral                                3.503

Kita dapat melihat dua variabel prediktor yang berperan penting dalam pembuatan model kita yaitu Ease.and.convenient untuk kelas Disagree dan Time.saving untuk kelas Disagree. Sedangkan variabel prediktor yang berperan cukup penting yaitu More.restaurant.choices untuk kelas Disagree dan variabel numerik Age.

Kesimpulan

Kita sudah membuat model Decision Tree dan Random Forest. Model Decision Tree sudah cukup robust dalam melakukan prediksi. Model tersebut juga dapat diinterpretasi. Tetapi model Random Forest lebih robust dalam melakukan prediksi karena merupakan gabungan dari banyak Decision Tree. Kita dapat menginterpretasi model Random Forest dengan menggunakan metode Variable Importance.