Rubrics-Scotty: “There is no drivers!”

Pendahuluan

Scotty adalah aplikasi ride-sharing sepeda motor di Turki. Seiring dengan berkembangnya popularitas scotty di Turki, seringkali pelanggan tidak mendapatkan driver, dikarenakan jumlah driver yang masih belum mencukupi.

Pada laporan ini, akan dibuat sebuah model klasifikasi untuk memprediksi status cakupan driver tiap area dan tiap jam, apakah mencukupi (sufficient) atau tidak mencukupi (insufficient) selama 7 hari dari tanggal 3 Desember 2017 - 9 Desember 2017

Library

options(scipen = 999)

library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.3.1
## Warning: package 'tidyr' was built under R version 4.3.1
## Warning: package 'readr' was built under R version 4.3.1
## Warning: package 'purrr' was built under R version 4.3.1
## Warning: package 'forcats' was built under R version 4.3.1
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.2     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   3.4.2     ✔ tibble    3.2.1
## ✔ lubridate 1.9.2     ✔ tidyr     1.3.0
## ✔ purrr     1.0.1     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(lubridate)
library(stringr)
library(padr)
## Warning: package 'padr' was built under R version 4.3.1
library(ggplot2)
library(caret)
## Warning: package 'caret' was built under R version 4.3.1
## Loading required package: lattice
## 
## Attaching package: 'caret'
## 
## The following object is masked from 'package:purrr':
## 
##     lift
library(rsample)
## Warning: package 'rsample' was built under R version 4.3.1
library(randomForest)
## Warning: package 'randomForest' was built under R version 4.3.1
## randomForest 4.7-1.1
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## 
## The following object is masked from 'package:dplyr':
## 
##     combine
## 
## The following object is masked from 'package:ggplot2':
## 
##     margin
library(ggthemes)
## Warning: package 'ggthemes' was built under R version 4.3.1
library(DALEX)
## Warning: package 'DALEX' was built under R version 4.3.1
## Welcome to DALEX (version: 2.4.3).
## Find examples and detailed introduction at: http://ema.drwhy.ai/
## 
## 
## Attaching package: 'DALEX'
## 
## The following object is masked from 'package:dplyr':
## 
##     explain
library(e1071)
## Warning: package 'e1071' was built under R version 4.3.1
## 
## Attaching package: 'e1071'
## 
## The following object is masked from 'package:rsample':
## 
##     permutations
library(partykit)
## Warning: package 'partykit' was built under R version 4.3.1
## Loading required package: grid
## Loading required package: libcoin
## Warning: package 'libcoin' was built under R version 4.3.1
## Loading required package: mvtnorm
## Warning: package 'mvtnorm' was built under R version 4.3.1
library(pROC)
## Warning: package 'pROC' was built under R version 4.3.1
## Type 'citation("pROC")' for a citation.
## 
## Attaching package: 'pROC'
## 
## The following objects are masked from 'package:stats':
## 
##     cov, smooth, var
library(lime)
## Warning: package 'lime' was built under R version 4.3.1
## 
## Attaching package: 'lime'
## 
## The following object is masked from 'package:DALEX':
## 
##     explain
## 
## The following object is masked from 'package:dplyr':
## 
##     explain

Data Preprocessing

Read Data

data_train <- read.csv("datainput/data-train.csv")
glimpse(data_train)
## Rows: 229,532
## Columns: 16
## $ id                 <chr> "59d005e1ffcfa261708ce9cd", "59d0066a3d32b861760d4a…
## $ trip_id            <chr> "59d005e9cb564761a8fe5d3e", "59d00678ffcfa261708ceb…
## $ driver_id          <chr> "59a892c5568be44b2734f276", "59a135565e88a24b11f119…
## $ rider_id           <chr> "59ad2d6efba75a581666b506", "59ce930f3d32b861760a46…
## $ start_time         <chr> "2017-10-01T00:00:17Z", "2017-10-01T00:02:34Z", "20…
## $ src_lat            <dbl> 41.07047, 40.94157, 41.07487, 41.04995, 41.05287, 4…
## $ src_lon            <dbl> 29.01945, 29.11484, 28.99528, 29.03107, 28.99522, 2…
## $ src_area           <chr> "sxk9", "sxk8", "sxk9", "sxk9", "sxk9", "sxk9", "sx…
## $ src_sub_area       <chr> "sxk9s", "sxk8y", "sxk9e", "sxk9s", "sxk9e", "sxk90…
## $ dest_lat           <dbl> 41.11716, 41.06151, 41.08351, 41.04495, 41.08140, 4…
## $ dest_lon           <dbl> 29.03650, 29.02068, 29.00228, 28.98192, 28.98197, 2…
## $ dest_area          <chr> "sxk9", "sxk9", "sxk9", "sxk9", "sxk9", "sxk9", "sx…
## $ dest_sub_area      <chr> "sxk9u", "sxk9s", "sxk9e", "sxk9e", "sxk9e", "sxk97…
## $ distance           <dbl> 5.379250, 15.497130, 1.126098, 4.169492, 3.358296, …
## $ status             <chr> "confirmed", "confirmed", "nodrivers", "confirmed",…
## $ confirmed_time_sec <int> 8, 14, 0, 32, 65, 110, 0, 49, 27, 21, 23, 46, 185, …

Karena yang akan diklasifikasikan adalah perkiraan sufficient atau insufficient dari cakupan driver berdasarkan jam dan hari, maka data start_time diganti menjadi datetime dan untuk data datetime sendiri perlu di floor kan terlebih dahulu mengikuti Jamnya

data_train2 <- data_train %>% 
  mutate(start_time = ymd_hms(start_time))
data_train2 <- data_train2 %>% 
  mutate(datetime = floor_date(start_time, unit = "hour"))
head(data_train2)

Aggregasi dan Padding Tanggal

Selanjutnya dilakukan aggregasi data berdasarkan area, waktu, dan status

train_agg <- data_train2 %>% 
  group_by(src_area, datetime, status) %>% 
  summarise(count = n()) %>% 
  ungroup()
## `summarise()` has grouped output by 'src_area', 'datetime'. You can override
## using the `.groups` argument.
head(train_agg)

selanjutnya dilakukan padding pada kolom datetime, agar interval tanggal pada setiap area sama. Interval dibuat setiap Jam

min_date <- min(train_agg$datetime)
max_date <- max(train_agg$datetime)

start_val <- make_datetime(
  year = year(min_date),
  month = month(min_date),
  day = day(min_date),
  hour = 0
)


end_val <- make_datetime(
  year = year(max_date),
  month = month(max_date),
  day = day(max_date),
  hour = 23
)

train_pad <- train_agg %>% 
  group_by(src_area, status) %>% 
  pad(start_val = start_val, end_val = end_val) %>% 
  ungroup()
## pad applied on the interval: hour

Replace NA

Mengganti data NA pada kolom count menjadi 0

train_pad <- train_pad %>% 
  replace_na(list(count = 0))
head(train_pad)

Mutasi Kolom dan Labelling Target

Jika status nodrivers = 0 maka coverage = sufficient, jika status nodrivers > 1 maka coverage = insufficient. Sementara jika status confirmed = 0, belum berarti tidak ada drivernya. bisa saja memang tidak ada pesanan. Dengan business rule ini status yang relevan dalam menentukan target adalah nodrivers, maka akan difilter status = nodrivers

dari kolom datetime, diekstrak beberapa kolom tambahan untuk membantu visualisasi data

train_clean1 <- train_pad %>% 
  filter(status == "nodrivers") %>% 
  mutate(weekday = factor(weekdays(datetime),
                          levels = c("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday")),
         hour = as.factor(hour(datetime)),
         src_area = as.factor(src_area)
         ) %>% 
  mutate(coverage = as.factor(ifelse(count > 0, "insufficient", "sufficient")))
head(train_clean1)

Exploratory Data Analysis

Cek proporsi secara umum

train_clean2<- train_clean1 %>% 
  select(src_area, weekday, hour, coverage)
head(train_clean2)
train_clean2 %>% 
  group_by(coverage) %>% 
  summarise(count = n()) %>% 
  ggplot(aes(x = coverage, y = count)) +
  geom_col(fill = "#3498db") +  # Warna grafik batang
  geom_point(color = "#e74c3c") +  # Warna titik nilai
  labs(y = NULL) +
  theme_economist() +
  theme(
    panel.background = element_rect(fill = "#ecf0f1"),  # Latar belakang panel
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    axis.title.y = element_text(vjust = 1)  # Mengatur posisi label sumbu Y
  )

train_clean2$coverage %>% 
  table() %>% 
  prop.table()
## .
## insufficient   sufficient 
##    0.5410053    0.4589947

Berdasarkan proporsi secara umum diatas, dapat disimpulkan jumlah kejadian Insufficient dan sufficient cukup seimbang,

selanjutnya akan dicek berdasarkan area nya

Cek Proporsi berdasarkan area

train_clean2 %>% 
  group_by(src_area, coverage) %>% 
  summarise(count = n()) %>% 
  ggplot(aes(x = src_area, y = count, fill = coverage)) +
  geom_col(position = "dodge") +
  labs(y = NULL,
       x = "Area") +
  theme_economist() +
  theme(legend.position = "bottom",
        legend.title = element_blank()) +
  scale_fill_manual(values = c("#3498db", "#e74c3c"))
## `summarise()` has grouped output by 'src_area'. You can override using the
## `.groups` argument.

Berdasarkan grafik diatas, dapat disimpulkan sebagai berikut :

  • Pada area sxk3, jumlah kejadian insufficient sedikit lebih banyak dibandingkan sufficient.
  • Pada area sxk8, jumlah kejadian sufficient jauh lebih banyak dibandingkan dengan jumlah kejadian insufficient
  • Pada area sxk9, kebalikan dengan area sxk8, jumlah kejadian insufficient jauh lebih banyak dibandingkan dengan kejadian sufficient

Heatmap

ggplot(train_clean2, aes(x = hour, y = weekday)) + 
  geom_tile(aes(fill = coverage)) +
  theme_economist() +
  scale_fill_economist() +
  labs(x = "Hour",
       y = NULL) +
  theme(legend.position = "bottom",
        legend.title = element_blank()) +
  facet_wrap(~ src_area, nrow = 3)

Pada heatmap diatas dapat disimpulkan, bahwa tiap jam, pada hari, dan pada tiap area memiliki pola berbeda dalam hal cakupan driver

Model Fitting dan Evaluation

Kita akan menggunakan score metrik pada prediksi data test sebagai pembanding agar score yang dibandingkan bukanlah hasil overfitting. Untuk kasus ini, karena saya berpendapat bahwa kedua kondisi (FN & FP) sama-sama penting, maka saya akan menggunakan accuracy sebagai metrik utama. Selanjutnya, saya akan melihat keseimbangan antara sensitivity dan specificity.

  • Accuracy mengukur sejauh mana model klasifikasi benar dalam memprediksi semua jenis kasus, baik yang positif maupun negatif.
  • Sensitivity mengukur kemampuan model untuk mengidentifikasi dengan benar kasus dari kelas positif (“insufficient”). Sensitivity yang tinggi berarti model efektif dalam menangkap sebagian besar kasus “insufficient yang akurat. Sensitivity yang rendah berarti model kurang mampu memprediksi kelas positif
  • Specificity mengukur kemampuan model untuk mengidentifikasi dengan benar kasus dari kelas negatif. Spesicificity yang tinggi menunjukkan bahwa model bagus dalam mengenali kasus-kasus kelas negatif (“sufficient”), tetapi mungkin tidak sebaik itu dalam kasus positif.

Cross Validation

Data awal di split menjadi 2, yaitu data Train dan data Validasi.

Proporsi yang digunakan 80:20

# Set seed, biar acakannya tidak berubah-ubah
set.seed(1)
# Ambil index untuk data train
indices <- sample(nrow(train_clean2), nrow(train_clean2) * 0.8)
# Subset data train dan test
train_data <- train_clean2[indices, ]
validasi_data <- train_clean2[-indices, ]

# Split features dan target
X_train <- train_data %>% select(-coverage)
Y_train <- train_data$coverage
X_validasi <- validasi_data %>% select(-coverage)
Y_validasi <- validasi_data$coverage

Modelling Algoritma Naive Bayes

Karena cocok dengan kasus kita dan komputasi yang sangat cepat, kita akan mencoba algoritma naive bayes sebagai percobaan pertama,

# Train model naive bayes
model_nb <- naiveBayes(x = X_train, # Data features
                       y = Y_train, # Data target
                       laplace = 1) # Set laplace = 1 untuk smoothing untuk mencegah terdapat probabilitas nol

# Prediksi data test menggunakan model naive bayes kita
pred_nb <- predict(model_nb, X_validasi)

# Evaluasi model naive bayes kita menggunakan confusion matrix
confusionMatrix(pred_nb, Y_validasi)
## Confusion Matrix and Statistics
## 
##               Reference
## Prediction     insufficient sufficient
##   insufficient          431        132
##   sufficient             63        282
##                                                
##                Accuracy : 0.7852               
##                  95% CI : (0.7571, 0.8115)     
##     No Information Rate : 0.5441               
##     P-Value [Acc > NIR] : < 0.00000000000000022
##                                                
##                   Kappa : 0.5612               
##                                                
##  Mcnemar's Test P-Value : 0.000001118          
##                                                
##             Sensitivity : 0.8725               
##             Specificity : 0.6812               
##          Pos Pred Value : 0.7655               
##          Neg Pred Value : 0.8174               
##              Prevalence : 0.5441               
##          Detection Rate : 0.4747               
##    Detection Prevalence : 0.6200               
##       Balanced Accuracy : 0.7768               
##                                                
##        'Positive' Class : insufficient         
## 

Model Naive Bayes saya mencapai Accuracy sekitar 78,52%. Dengan sensitivitas yang kuat dalam mendeteksi kasus “insufficient” pengemudi.

Modelling Algoritma Decision Tree

Selain algoritma naive bayes, algoritma yang juga cocok dengan kasus kita adalah decision tree. Mari kita coba!

# Train model decision tree dengan mengatur control-nya agar modelnya lebih spesifik
model_dt <- ctree(coverage ~ .,
                  train_data,
                  control = ctree_control(mincriterion = 0.35,  # Set mincriterion = 0.35 agar node lebih mudah terbagi
                                          minsplit = 5,      # Set minsplit = 5 agar untuk syarat minimun split
                                          minbucket = 3))    # Set minbucket = 3 sebagai syarat minimum pembuatan node baru

# Prediksi data test mengguanakan model decision tree kita
pred_dt <- predict(model_dt, X_validasi)

# Evaluasi model decision tee kita menggunakan confusion matrix
confusionMatrix(pred_dt, Y_validasi)
## Confusion Matrix and Statistics
## 
##               Reference
## Prediction     insufficient sufficient
##   insufficient          421        122
##   sufficient             73        292
##                                                
##                Accuracy : 0.7852               
##                  95% CI : (0.7571, 0.8115)     
##     No Information Rate : 0.5441               
##     P-Value [Acc > NIR] : < 0.00000000000000022
##                                                
##                   Kappa : 0.5629               
##                                                
##  Mcnemar's Test P-Value : 0.0005874            
##                                                
##             Sensitivity : 0.8522               
##             Specificity : 0.7053               
##          Pos Pred Value : 0.7753               
##          Neg Pred Value : 0.8000               
##              Prevalence : 0.5441               
##          Detection Rate : 0.4637               
##    Detection Prevalence : 0.5980               
##       Balanced Accuracy : 0.7788               
##                                                
##        'Positive' Class : insufficient         
## 

Model Decision Tree saya mencapai Accuracy sekitar 78.52%, sedikit lebih baik dari model Naive Bayes. Juga dengan sensitivitas yang kuat dalam mendeteksi kasus “insufficient”.

Modelling Algoritma Random Forest

Karena model decision tree kita memiliki performa yang cukup bagus, langkah selanjutnya yang baik adalah mencoba algoritma random forest

set.seed(1)

# Atur metode k-fold cross validation
ctrl <- trainControl(method = "repeatedcv",
                     number = 5, # Jumlah folds
                     repeats = 7) # Jumlah repetisi pelaksanaan cross-validation

# Awalnya, saya mengatur parameter number = 3 dan repeats = 5. Setelah saya atur number = 5 dan repeats = 7, performanya sedikit meningkat

# Train model random forest kita dengan control k-fold yang sudah kita atur
model_rf <- train(x = X_train, # Features
                  y = Y_train, # Target
                  trControl = ctrl) # Control
## note: only 2 unique complexity parameters in default grid. Truncating the grid to 2 .
## Warning: Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
## Setting row names on a tibble is deprecated.
# Prediksi data test menggunakan model random forest kita
pred_rf <- predict(model_rf, X_validasi)

# Evaluasi model random forest kita menggunakan confusion matrix
confusionMatrix(pred_rf, Y_validasi)
## Confusion Matrix and Statistics
## 
##               Reference
## Prediction     insufficient sufficient
##   insufficient          423        114
##   sufficient             71        300
##                                                
##                Accuracy : 0.7963               
##                  95% CI : (0.7686, 0.822)      
##     No Information Rate : 0.5441               
##     P-Value [Acc > NIR] : < 0.00000000000000022
##                                                
##                   Kappa : 0.5858               
##                                                
##  Mcnemar's Test P-Value : 0.002016             
##                                                
##             Sensitivity : 0.8563               
##             Specificity : 0.7246               
##          Pos Pred Value : 0.7877               
##          Neg Pred Value : 0.8086               
##              Prevalence : 0.5441               
##          Detection Rate : 0.4659               
##    Detection Prevalence : 0.5914               
##       Balanced Accuracy : 0.7905               
##                                                
##        'Positive' Class : insufficient         
## 

Model Random Forest saya mencapai Accuracy sekitar 79.63%, sedikit lebih baik dari model Decision Tree. Juga dengan sensitivitas yang kuat dalam mendeteksi kasus “insufficient”.

Submission Evaluation

Baca data test

data_test <- read.csv("datainput/data-test.csv")
head(data_test)
data_test <- data_test %>% 
  mutate(datetime = ymd_hms(datetime))
head(data_test)

Mutasi Kolom

test <- data_test %>% 
  mutate(src_area = as.factor(src_area),
         weekday = factor(weekdays(datetime),
                          levels = c("Sunday", 
                                     "Monday", "Tuesday", 
                                     "Wednesday", "Thursday", 
                                     "Friday", "Saturday")),
         hour = as.factor(hour(datetime))) %>% 
  select(src_area, datetime, weekday, hour)
head(test)

Prediksi dengan 3 model kita

test$coverage_nb <- predict(model_nb, test)
test$coverage_dt <- predict(model_dt, test)
test$coverage_rf <- predict(model_rf, test)

test %>% head()

Saya akan membandingkan performa setiap model pada submisi algoritma

Masukkan ke csv submission dan save untuk disubmit

# Prediksi naive bayes
submission <- data_test %>% mutate(coverage = test$coverage_nb)
write.csv(submission, "submission-carli-nb.csv")

# Predikisi decision tree
submission <- data_test %>% mutate(coverage = test$coverage_dt)
write.csv(submission, "submission-carli-dt.csv")

# Prediksi random forest
submission <- data_test %>% mutate(coverage = test$coverage_rf)
write.csv(submission, "submission-carli-rf.csv")

Setelah dibandingkan di submisi, model decission tree menjadi model terbaik dengan score berikut:

  • Accuracy: 82%
  • Recall: 88%
  • Precision: 82%
  • Specificity: 76%

Interpretation Lime Method

Untuk interpretasi menggunakan Lime method, kita tidak perlu melakukan pre-processing apa-apa, kita hanya perlu menjalankan code berikut agar Lime method bisa bekerja dengan baik:

model_type.party <- function(x){
  return("classification")
}

Karena kita tidak memiliki banyak feature, kita akan meggunakan semua/tiga features, yaitu src_area, hour, dan, weekday. Dengan Lime Method, kita memiliki kemampuan untuk mengidentifikasi variabel yang berkontribusi terhadap hasil prediksi pada setiap kasus secara individu. Ini memungkinkan kita untuk memahami secara rinci bagaimana setiap variabel memengaruhi hasil prediksi untuk kasus tertentu.

set.seed(1)

explainer <- lime(x = X_validasi, model = model_dt)

explanation <- explain(test %>% select(src_area, weekday, hour) %>% slice(1:4),
                       labels = "insufficient",
                       explainer = explainer,
                       n_features = 3)

explanation %>% plot_features()

Pada 4 prediksi pertama bisa dilihat bahwa semuanya berada di area dan week day yang sama(area : sxk3 dan weekday : Sunday), namun terdapat perbedaan prediksi berdasarkan jam, yang di mana model memprediksi area sxk3 memiliki jumlah driver yang tidak cukup di week day tersebut pada jam 0-1, namun cukup pada jam 2-3.

Interpretasi ini cukup bagus, di mana kita bisa melihat bahwa sesignifikannya variable src_area, tetap saja variable lain memegang peran yang juga penting pada ke-4 kasus prediksi ini yaitu hour.

Conclusion

Tujuan saya tercapai dengan menghasilkan model dengan performa yang memuaskan. Model yang dibuat memiliki akurasi yang baik (82%), serta sensitivitas (recall) yang tinggi (88%), dan presisi yang layak (82%). Selain itu, spesifisitas model juga sebesar 76%, yang menunjukkan kemampuannya dalam mengidentifikasi dengan baik kasus-kasus negatif.

Masalah prediksi kecukupan driver pada lokasi tertentu dan waktu tertentu merupakan masalah yang dapat diselesaikan dengan baik menggunakan machine learning. Model decision tree yang digunakan telah memberikan hasil yang bagus dalam mengklasifikasikan kasus-kasus “insufficient” dan “sufficient” pada lokasi dan waktu tertentu

Model yang digunakan adalah Decision Tree Classifier. Model ini berhasil mencapai akurasi 82%, yang berarti sekitar 82% prediksi yang dilakukan oleh model sesuai dengan kenyataan. Performa yang lebih penting lagi terlihat dalam recall (88%), yang mengindikasikan kemampuan model dalam menangkap sebagian besar kasus “insufficient” yang sebenarnya. Presisi yang mencapai 82% juga menunjukkan bahwa ketika model memprediksi “insufficient,” prediksinya cenderung benar. Spesifisitas sebesar 76% menunjukkan kemampuan model dalam mengidentifikasi “sufficient” dengan baik.

Potensi implementasi bisnis dari proyek Capstone ini sangat menjanjikan. Dengan model ini, Anda dapat membantu mengoptimalkan alokasi driver pada lokasi dan waktu tertentu. Misalnya, dengan memprediksi kecukupan driver, perusahaan dapat mengambil langkah-langkah yang lebih baik dalam mengatur penjadwalan driver, menghindari kekurangan driver, dan meningkatkan efisiensi layanan. Hal ini dapat mengurangi ketidaknyamanan pelanggan akibat keterlambatan atau ketidaktersediaan driver. Selain itu, analisis lebih lanjut dari output model ini juga dapat memberikan wawasan berharga untuk pengambilan keputusan dalam mengoptimalkan sumber daya dan meningkatkan pengalaman pelanggan.