Data historis suatu perusahaan yang diolah dengan baik, dapat memberi peluang baru agar perusahaan tersebut bisa beroperasi dengan lebih optimal. Hal tersebut yang ingin dicapai oleh perusahaan pesan-antar makanan, yakni ingin mengoptimasi aktivitas penjualan dan pelayanan mereka. Perusahaan ini memiliki banyak cabang di beberapa kota dan mempunyai data historis yang mencatat seluruh aktivitas pesanan dalam pola mingguan. Data diperoleh dari Kaggle : https://www.kaggle.com/ghoshsaptarshi/av-genpact-hack-dec2018?select=train.csv sebagai bagian dari Machine Learning Hackathon yang diselenggarakan oleh Analytics Vidhya & Genpact pada Desember 2018.
Setidaknya ada 4 data yang disediakan untuk dieksplor dan dibuat model machine learning: train, test, mealinfo, dan fcenter. Keempatnya bisa diolah masalah utama yang ingin dipecahkan, yaitu memprediksi jumlah penjualan selama 10 minggu mendatang (minggu ke 146 hingga 155). Uniknya dalam dataset yang tersedia, tidak ada variabel yang secara langsung mencatat historis waktu pada tiap observasi. Informasi waktu (mingguan), tercatat dalam bentuk urutan minggu kesekian. Ini adalah salah satu hal yang menarik, apakah pendekatan machine learning forecasting mampu membaca tipe dataset seperti ini? Atau perlu menggunakan model lain?
Eksplorasi dataset secara mendalam bisa memberi informasi penting terkait perlu tidaknya sebuah menu dipromosikan melaui email atau platform digital. Di sisi lain, hasil prediksi jumlah pesanan (variabel : num_orders) dari pemodelan machine learning akan digunakan untuk mengoptimasi rencana persediaan bahan makanan dan penempatan pekerja untuk tiap cabang pesan-antar.
Data train menyimpan data historik terkait aktivitas delivery untuk semua cabang. Adanya data train adalah supaya model machine learning yang nanti dibuat bisa mempelajari banyak data.
train:
id: unique IDweek: week numbercenter_id: unique ID for fulfillment centermeal_id: unique ID for mealcheckout_price: Final price including discount, taxes and delivery chargesbase_price: Base price of the mealemailer_for_promotion: Email sent for promotionhomepage_featured: Meal featured at homepagenum_orders: Orders count (target)Dimensi data:
## [1] 456548 9
Data test punya variabel yang sama dengan train, hanya berbeda di variabel target yang nantinya perlu diisi dengan hasil prediksi menggunakan machine learning.
test:
id: unique IDweek: week numbercenter_id: unique ID for fulfillment centermeal_id: unique ID for mealcheckout_price: Final price including discount, taxes and delivery chargesbase_price: Base price of the mealemailer_for_promotion: Email sent for promotionhomepage_featured: Meal featured at homepageDimensi data:
## [1] 32573 8
Data mealinfo mencatat seluruh informasi tentang makanan yang disajikan untuk kemudian diantar ke tempat tujuan.
mealinfo:
meal_id: unique ID for mealcategory: type of meal (beverages/snacks/soup/etc)cuisine: meal cuisine (Indian/Italian/etc)Dimensi data:
## [1] 51 3
Data fcenter mencatat informasi dari setiap cabang yang melayani suatu pesanan.
fcenter:
center_id: unique ID for fulfillment centercity_code: unique code for cityregion_code: unique code for regioncenter_type: anonymized center typeop_area: area of operation (in km^2) Dimensi data:## [1] 77 5
## Rows: 456,548
## Columns: 9
## $ id <int> 1379560, 1466964, 1346989, 1338232, 1448490, ...
## $ week <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
## $ center_id <int> 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 5...
## $ meal_id <int> 1885, 1993, 2539, 2139, 2631, 1248, 1778, 106...
## $ checkout_price <dbl> 136.83, 136.83, 134.86, 339.50, 243.50, 251.2...
## $ base_price <dbl> 152.29, 135.83, 135.86, 437.53, 242.50, 252.2...
## $ emailer_for_promotion <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ...
## $ homepage_featured <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, ...
## $ num_orders <int> 177, 270, 189, 54, 40, 28, 190, 391, 472, 676...
train <- train %>%
mutate_at(vars(id, center_id, meal_id,emailer_for_promotion, homepage_featured), as.factor)
glimpse(train)## Rows: 456,548
## Columns: 9
## $ id <fct> 1379560, 1466964, 1346989, 1338232, 1448490, ...
## $ week <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
## $ center_id <fct> 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 5...
## $ meal_id <fct> 1885, 1993, 2539, 2139, 2631, 1248, 1778, 106...
## $ checkout_price <dbl> 136.83, 136.83, 134.86, 339.50, 243.50, 251.2...
## $ base_price <dbl> 152.29, 135.83, 135.86, 437.53, 242.50, 252.2...
## $ emailer_for_promotion <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ...
## $ homepage_featured <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, ...
## $ num_orders <int> 177, 270, 189, 54, 40, 28, 190, 391, 472, 676...
## id week center_id
## 0 0 0
## meal_id checkout_price base_price
## 0 0 0
## emailer_for_promotion homepage_featured num_orders
## 0 0 0
Tidak ada missing value pada data train.
Tidak ada duplicated value pada data train.
## Rows: 32,573
## Columns: 8
## $ id <int> 1028232, 1127204, 1212707, 1082698, 1400926, ...
## $ week <int> 146, 146, 146, 146, 146, 146, 146, 146, 146, ...
## $ center_id <int> 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 5...
## $ meal_id <int> 1885, 1993, 2539, 2631, 1248, 1778, 1062, 270...
## $ checkout_price <dbl> 158.11, 160.11, 157.14, 162.02, 163.93, 190.1...
## $ base_price <dbl> 159.11, 159.11, 159.14, 162.02, 163.93, 190.1...
## $ emailer_for_promotion <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
## $ homepage_featured <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ...
test <- test %>%
mutate_at(vars(id, center_id, meal_id,emailer_for_promotion, homepage_featured), as.factor)
glimpse(test)## Rows: 32,573
## Columns: 8
## $ id <fct> 1028232, 1127204, 1212707, 1082698, 1400926, ...
## $ week <int> 146, 146, 146, 146, 146, 146, 146, 146, 146, ...
## $ center_id <fct> 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, 5...
## $ meal_id <fct> 1885, 1993, 2539, 2631, 1248, 1778, 1062, 270...
## $ checkout_price <dbl> 158.11, 160.11, 157.14, 162.02, 163.93, 190.1...
## $ base_price <dbl> 159.11, 159.11, 159.14, 162.02, 163.93, 190.1...
## $ emailer_for_promotion <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
## $ homepage_featured <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, ...
## id week center_id
## 0 0 0
## meal_id checkout_price base_price
## 0 0 0
## emailer_for_promotion homepage_featured
## 0 0
Tidak ada missing value pada data test.
Tidak ada duplicated value pada data test.
## Rows: 51
## Columns: 3
## $ meal_id <int> 1885, 1993, 2539, 1248, 2631, 1311, 1062, 1778, 1803, 1198...
## $ category <chr> "Beverages", "Beverages", "Beverages", "Beverages", "Bever...
## $ cuisine <chr> "Thai", "Thai", "Thai", "Indian", "Indian", "Thai", "Itali...
## Rows: 51
## Columns: 3
## $ meal_id <fct> 1885, 1993, 2539, 1248, 2631, 1311, 1062, 1778, 1803, 1198...
## $ category <fct> Beverages, Beverages, Beverages, Beverages, Beverages, Ext...
## $ cuisine <fct> Thai, Thai, Thai, Indian, Indian, Thai, Italian, Italian, ...
## meal_id category cuisine
## 0 0 0
Tidak ada missing value pada data mealinfo.
Tidak ada duplicated value pada data mealinfo.
## Rows: 77
## Columns: 5
## $ center_id <int> 11, 13, 124, 66, 94, 64, 129, 139, 88, 143, 101, 86, 32...
## $ city_code <int> 679, 590, 590, 648, 632, 553, 593, 693, 526, 562, 699, ...
## $ region_code <int> 56, 56, 56, 34, 34, 77, 77, 34, 34, 77, 85, 85, 34, 77,...
## $ center_type <chr> "TYPE_A", "TYPE_B", "TYPE_C", "TYPE_A", "TYPE_C", "TYPE...
## $ op_area <dbl> 3.7, 6.7, 4.0, 4.1, 3.6, 4.4, 3.9, 2.8, 4.1, 3.8, 2.8, ...
## Rows: 77
## Columns: 5
## $ center_id <int> 11, 13, 124, 66, 94, 64, 129, 139, 88, 143, 101, 86, 32...
## $ city_code <int> 679, 590, 590, 648, 632, 553, 593, 693, 526, 562, 699, ...
## $ region_code <int> 56, 56, 56, 34, 34, 77, 77, 34, 34, 77, 85, 85, 34, 77,...
## $ center_type <chr> "TYPE_A", "TYPE_B", "TYPE_C", "TYPE_A", "TYPE_C", "TYPE...
## $ op_area <dbl> 3.7, 6.7, 4.0, 4.1, 3.6, 4.4, 3.9, 2.8, 4.1, 3.8, 2.8, ...
## center_id city_code region_code center_type op_area
## 0 0 0 0 0
Tidak ada missing value pada data fcenter.
Tidak ada duplicated value pada data fcenter.
datatrainKita perlu menggabungkan (merge) beberapa dataset yang disediakan menjadi satu data tabular yang siap diproses. Data train dan test masing-masing akan digabung dengan mealinfo (berdasarkan meal_id) dan fcenter (berdasarkan center_id).
# data train
datatrain <- merge(train, mealinfo, by = "meal_id")
datatrain <- merge(datatrain, fcenter, by = "center_id")
# data test
datatest <- merge(test, mealinfo, by = "meal_id")
datatest <- merge(datatest, fcenter, by = "center_id")
# urutkan berdasarkan minggu
datatrain <- datatrain %>%
arrange(week)
head(datatrain)## Rows: 456,548
## Columns: 15
## $ center_id <fct> 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1...
## $ meal_id <fct> 2707, 1525, 1311, 2581, 1207, 2704, 1445, 230...
## $ id <fct> 1347181, 1016940, 1378864, 1223760, 1260561, ...
## $ week <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
## $ checkout_price <dbl> 196.00, 243.50, 174.66, 581.03, 322.07, 243.5...
## $ base_price <dbl> 196.00, 281.33, 173.66, 610.13, 382.18, 281.3...
## $ emailer_for_promotion <fct> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, ...
## $ homepage_featured <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
## $ num_orders <int> 1350, 824, 595, 485, 769, 473, 175, 323, 717,...
## $ category <fct> Beverages, Other Snacks, Extras, Pizza, Bever...
## $ cuisine <fct> Italian, Thai, Thai, Continental, Continental...
## $ city_code <int> 590, 590, 590, 590, 590, 590, 590, 590, 590, ...
## $ region_code <int> 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 5...
## $ center_type <chr> "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE...
## $ op_area <dbl> 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, ...
## Rows: 456,548
## Columns: 15
## $ center_id <fct> 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 1...
## $ meal_id <fct> 2707, 1525, 1311, 2581, 1207, 2704, 1445, 230...
## $ id <fct> 1347181, 1016940, 1378864, 1223760, 1260561, ...
## $ week <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ...
## $ checkout_price <dbl> 196.00, 243.50, 174.66, 581.03, 322.07, 243.5...
## $ base_price <dbl> 196.00, 281.33, 173.66, 610.13, 382.18, 281.3...
## $ emailer_for_promotion <fct> 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, ...
## $ homepage_featured <fct> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
## $ num_orders <int> 1350, 824, 595, 485, 769, 473, 175, 323, 717,...
## $ category <fct> Beverages, Other Snacks, Extras, Pizza, Bever...
## $ cuisine <fct> Italian, Thai, Thai, Continental, Continental...
## $ city_code <fct> 590, 590, 590, 590, 590, 590, 590, 590, 590, ...
## $ region_code <fct> 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 5...
## $ center_type <chr> "TYPE_B", "TYPE_B", "TYPE_B", "TYPE_B", "TYPE...
## $ op_area <dbl> 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, 6.3, ...
## center_id meal_id id
## 0 0 0
## week checkout_price base_price
## 0 0 0
## emailer_for_promotion homepage_featured num_orders
## 0 0 0
## category cuisine city_code
## 0 0 0
## region_code center_type op_area
## 0 0 0
## [1] 32937
## [1] 306
Data outlier pada target variabel mencapai puluhan ribu. Untuk itu, beberapa variabel numerik–termasuk target–perlu ditransformasi menggunakan scale/log. Tampak perbedaan jumlah data outlier yang cukup signifikan setelah target variabel ditransformasi menggunakan log. Lebih jauh lagi, beberapa variabel numerik lainnya juga akan diberi perlakuan sama. Untuk sekarang sekarang kita sudah memiliki datatrain yang siap untuk diekplor dan divisualisasikan agar mendapat informasi/insight yang berguna. Berikutnya akan dibuat model machine learning supaya bisa menghasilkan prediksi yang tepat pada variabel num_orders.
Kita coba buat kolom baru yang menyimpan informasi penjualan sebagai hasil perkalian kolom checkout_price dan num_orders. Kolom sales nantinya akan diolah untuk menghasilkan visualisasi jenis makanan apa saya yang cukup diminati dan berkontribusi paling besar pada kenaikan angka penjualan.
avrgsales <- datatrain2 %>%
group_by(week) %>%
summarise(sale = mean(sale)) %>%
ungroup() %>%
mutate(text = paste(week,":",round(sale,2)))
plot.avrgsales <- ggplot(avrgsales) +
geom_point(aes(x = week,
y = sale,
color = sale,
fill = "#ff2a00")) +
geom_area(aes(x = week, y = sale),
fill = "#53bbbd", alpha = 0.5) +
labs(title = "Average of Sales",
subtitle = "on week 1-145") +
theme_minimal()
ggplotly(plot.avrgsales, tooltip = "text") %>%
config(displayModeBar = F)datatrain. Namun keadaan ini belum bisa diinterpretasikan lebih jauh, yang jelas rata-rata penjualan sejauh ini berada di angka 60.000 hingga 70.000 setiap minggu.
cat <- as.data.frame(table(datatrain$category)) %>%
mutate(text = paste(Var1, ":", Freq))
plot.topcat <- ggplot(cat, aes(Freq, Var1)) +
geom_col(aes(fill = Var1)) +
labs(title = "Most Favourite Meal Category",
y = "Category") +
scale_x_continuous(
limits = c(0, 130000), breaks = seq(0, 130000, 40000)
) +
theme_minimal() +
theme(legend.position = "none")
ggplotly(plot.topcat, tooltip = "text") %>%
config(displayModeBar = F)datatrain, dimana Beverages adalah jenis yang paling sering dipesan oleh pembeli. Posisinya sangat unggul jauh dibanding jenis makanan lain. Keadaan ini bisa dipengaruhi karena kecenderungan orang-orang yang lebih suka mengonsumsi makanan ringan (camilan) ketimbang makanan berat. Namun dengan penjualan Beverages sebanyak lebih dari 120.000 pesanan, apakah berarti jenis makanan ini berkontribusi paling besar dalam meningkatkan angka penjualan?
topsalescat <- datatrain2 %>%
select(category, center_id, sale) %>%
group_by(category) %>%
summarise(sale = mean(sale)) %>%
mutate(text = paste(category, ":", round(sale,2)))
plot.topsales <- ggplot(topsalescat, aes(sale, category)) +
geom_col(aes(fill = category)) +
labs(title = "Top Sales Based on Meal Category") +
theme_minimal() +
theme(legend.position = "none")
ggplotly(plot.topsales, tooltip = "text") %>%
config(displayModeBar = F)Rice Bowl membawa keuntungan paling besar dari seluruh data penjualan, yaitu sekitar 170.000. Kemudian ada Sandwich di peringkat dua dan Pizza di peringkat tiga. Sedangkan kontribusi Beveragescenderung kecil, sekitar 6.000 saja. Maka dari itu, perusahaan tetap perlu memperhatikan ketersediaan bahan bukan hanya makanan yang paling sering dipesan, tapi juga makanan yang punya angka penjualan tinggi.
bev <- datatrain[datatrain$category == "Beverages",]
beverages <- as.data.frame(table(bev$cuisine)) %>%
mutate(text = paste(Var1, ":", Freq))
plot.topcat <- ggplot(beverages, aes(Freq, Var1)) +
geom_col(aes(fill = Var1)) +
labs(title = "Cuisine for Beverages Category",
y = "Cuisine") +
theme_minimal() +
theme(legend.position = "none")
ggplotly(plot.topcat, tooltip = "text") %>%
config(displayModeBar = F)Mengingat tingginya permintaan Beverages maka penting bagi perusahaan bisa memenuhi permintaan pembeli dengan baik. Walau kontribusinya tidak terlalu signifikan, Beverages bisa menjadi kunci untuk membangun kepuasan berbelanja bagi pembeli. Maka dari itu, plot di atas menjelaskan cuisine Italian dan Continental sebagai favorit pembeli yang memesan Beverages.