Rubrics-Scotty: “There is no drivers!”
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
## 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
## Warning: package 'padr' was built under R version 4.3.1
## 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
## Warning: package 'rsample' was built under R version 4.3.1
## 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
## Warning: package 'ggthemes' was built under R version 4.3.1
## 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
## Warning: package 'e1071' was built under R version 4.3.1
##
## Attaching package: 'e1071'
##
## The following object is masked from 'package:rsample':
##
## permutations
## 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
## 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
## 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
## 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_train2 %>%
mutate(datetime = floor_date(start_time, unit = "hour"))
head(data_train2)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.
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
Mengganti data NA pada kolom count menjadi 0
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")))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
)## .
## 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
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 :
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
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.
Data awal di split menjadi 2, yaitu data Train dan data Validasi.
Proporsi yang digunakan 80:20
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.
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”.
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”.
Baca data test
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:
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:
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.
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.