1 Introduction

1.1 Our Purpose

Data wholesale.csv merupakan data yang merujuk terkait klien distributor wholesale (grosir). Data ini menjelaskan terkait pengeluaran tahunan dalam satuan moneter (monetary unit / m.u) untuk beragam kategori produk.

Data wholesale.csv dapat dilihat diambil dari link berikut ini : https://www.kaggle.com/datasets/sahistapatel96/wholesale-customer-datacsv.

Pada data ini, terdapat target variabel Channel yang merupakan tempat belanja (channel) customer membeli produk-produk yang dijual oleh distributor wholesale tersebut. Dengan kata lain, target variabel ini juga menandakan segmentasi customer terhadap produk-produk yang menjadi variabel prediktor di data ini nantinya.

Pada kesempatan kali ini, saya akan mencoba melakukan analisis segmentasi customer terkait data wholesale ini. Tujuannya adalah untuk memprediksi apakah customer tersebut termasuk dalam segmen / channel yang mana berdasarkan data-data penjualan produk-produk yang ada.

Algoritma yang akan saya gunakan yaitu menggunakan Logistic Regression dan K-Nearest Neighbor yang termasuk dalam supervised learning.

Kita juga akan evaluasi kedua model tersebut dan bandingkan, manakah model yang lebih baik evaluasinya dan berdasarkan dari sisi pemahaman bisnis nantinya, nilai evaluasi apa (accuracy, recall atau precision) yang akan kita jadikan acuan dalam memilih model untuk analisis segmentasi customer wholesale ini.

1.2 Preparation

Kita panggil setiap library yang dibutuhkan dalam proses pembuatan model klasifikasi hingga evaluasi dan hasil interpretasi model nantinya.

# for wrangling
library(dplyr) 
library(tidyr) 
# for EDA
library(inspectdf)
# for ML modeling
library(gtools)
# for ML model evaluation & assumption
library(caret) 
library(rsample) 
library(caret)
library(class)

2 Data Preparation

2.1 Read Dataset

Kita mulai dengan membaca dataset wholesale.csv dan kita simpan dalam variabel wholesale.

wholesale <- read.csv("wholesale.csv")
wholesale

Cek struktur data:

glimpse(wholesale)
#> Rows: 440
#> Columns: 8
#> $ Channel          <int> 2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1, 2, 1,…
#> $ Region           <int> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,…
#> $ Fresh            <int> 12669, 7057, 6353, 13265, 22615, 9413, 12126, 7579, 5…
#> $ Milk             <int> 9656, 9810, 8808, 1196, 5410, 8259, 3199, 4956, 3648,…
#> $ Grocery          <int> 7561, 9568, 7684, 4221, 7198, 5126, 6975, 9426, 6192,…
#> $ Frozen           <int> 214, 1762, 2405, 6404, 3915, 666, 480, 1669, 425, 115…
#> $ Detergents_Paper <int> 2674, 3293, 3516, 507, 1777, 1795, 3140, 3321, 1716, …
#> $ Delicassen       <int> 1338, 1776, 7844, 1788, 5185, 1451, 545, 2566, 750, 2…

Berikut ini adalah penjelasan terkait variabel-variabel tersebut:

  • Channel : Channel / segmen pelanggan yang dituju, terbagi menjadi 2 kelas: 1. Horeca -> hotel, restoran, cafe 2. Retail -> supermarket, toko, dsb
  • Region : Wilayah pembelanjaan customer, dibagi menjadi 3 kelas: 1 (Lisbon), 2 (Porto), 3 (Wilayah lain)
  • Fresh : Pengeluaran tahunan untuk produk sayuran / buah-buahan
  • Milk : Pengeluaran tahunan untuk produk susu dan olahan susu
  • Grocery : Pengeluaran tahunan untuk produk bahan kebutuhan sehari-hari
  • Frozen : Pengeluaran tahunan untuk produk makanan beku
  • Detergents_Paper : Pengeluaran tahunan untuk produk seperti sabun / detergen dan produk alat tulis / kertas
  • Delicassen : Pengeluaran tahunan untuk produk makanan-makanan khusus untuk restoran yang khusus, seperti sosis, ham, dsb

2.2 Data Wrangling

Kita mencoba untuk melihat persebaran data dari setiap variabel pada data tersebut.

wholesale %>% inspect_num()

Terdapat 2 kelas yang akan menjadi target variabel yang merupakan segmen customer nantinya akan kita petakan. Kita lihat pembagian / proporsi dari masing-masing kelas pada variabel Channel tersebut.

table(wholesale$Channel)
#> 
#>   1   2 
#> 298 142

Ternyata kelas 1 (Horeca) lebih besar / banyak jumlahnya dibandingkan kelas 2 (Retail).

Selain itu, terdapat variabel Region yang terdiri dari 3 nilai. Kita lihat pembagian / proporsi dari masing-masing kelas pada variabel tersebut.

table(wholesale$Region)
#> 
#>   1   2   3 
#>  77  47 316

Terlihat bahwa kelas 3 yaitu wilayah lain, lebih banyak sebaran datanya dibandingkan wilayah 1 (Lisbon) dan 2 (Porto).

Berdasarkan penjelasan dari setiap variabel tersebut, kita perlu melakukan konversi terhadap 2 variabel yaitu Channel dan Region yang dapat kita ubah menjadi tipe data factor.

wholesale <- wholesale %>% 
  mutate(Channel = as.factor(Channel),
         Region = as.factor (Region))

glimpse(wholesale)
#> Rows: 440
#> Columns: 8
#> $ Channel          <fct> 2, 2, 2, 1, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1, 2, 1,…
#> $ Region           <fct> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,…
#> $ Fresh            <int> 12669, 7057, 6353, 13265, 22615, 9413, 12126, 7579, 5…
#> $ Milk             <int> 9656, 9810, 8808, 1196, 5410, 8259, 3199, 4956, 3648,…
#> $ Grocery          <int> 7561, 9568, 7684, 4221, 7198, 5126, 6975, 9426, 6192,…
#> $ Frozen           <int> 214, 1762, 2405, 6404, 3915, 666, 480, 1669, 425, 115…
#> $ Detergents_Paper <int> 2674, 3293, 3516, 507, 1777, 1795, 3140, 3321, 1716, …
#> $ Delicassen       <int> 1338, 1776, 7844, 1788, 5185, 1451, 545, 2566, 750, 2…

3 Pre-processing Data

Sebelum melakukan pemodelan, kita akan pecah data wholesale dengan pembagian proporsi 80% untuk data train dan 20% untuk data test menggunakan fungsi sample(). Menggunakan set.seed() dengan besaran 123. Menyimpan hasil pemisahan data pada object train_wholesale dan test_wholesale.

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

splitter <- initial_split(data = wholesale, prop = 0.8)

train_wholesale <- training(splitter)
test_wholesale <- testing(splitter)

Kita lihat lagi proporsi dari target variabel yang kita miliki pada data train kita.

table(train_wholesale$Channel) %>% prop.table()
#> 
#>         1         2 
#> 0.6846591 0.3153409

Ternyata proporsi variabel target pada data train tidak seimbang, yaitu 68 : 32.

Karena perbedaan banyaknya data antar kelas pada variabel target yaitu 1 dan 2 cukup jauh, kita akan melakukan upsampling.

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

train_up_wholesale <- upSample(
  x = train_wholesale %>% select(-Channel),
  y = train_wholesale$Channel,
  yname = "Channel"
)

head(train_up_wholesale)

Cek proporsi kelas target setelah melakukan upsampling.

table(train_up_wholesale$Channel) %>% prop.table()
#> 
#>   1   2 
#> 0.5 0.5

4 Modeling

4.1 Logistic Regression

Melakukan pemodelan menggunakan logistic regression. Pemodelan menggunakan fungsi glm() dalam memodelkan menggunakan logistic regression dengan menggunakan semua variabel prediktor yang ada.

model_logistic <- glm(formula = Channel ~ .,
                      data = train_up_wholesale,
                      family = "binomial")

summary(model_logistic)
#> 
#> Call:
#> glm(formula = Channel ~ ., family = "binomial", data = train_up_wholesale)
#> 
#> Coefficients:
#>                     Estimate  Std. Error z value         Pr(>|z|)    
#> (Intercept)      -3.97918426  0.66868359  -5.951 0.00000000266879 ***
#> Region2           1.66198008  0.75002926   2.216           0.0267 *  
#> Region3           0.61084806  0.52854272   1.156           0.2478    
#> Fresh             0.00001234  0.00001821   0.678           0.4979    
#> Milk              0.00013537  0.00005855   2.312           0.0208 *  
#> Grocery           0.00009577  0.00005756   1.664           0.0961 .  
#> Frozen           -0.00023761  0.00010138  -2.344           0.0191 *  
#> Detergents_Paper  0.00097888  0.00013930   7.027 0.00000000000211 ***
#> Delicassen       -0.00022091  0.00013069  -1.690           0.0910 .  
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> (Dispersion parameter for binomial family taken to be 1)
#> 
#>     Null deviance: 668.19  on 481  degrees of freedom
#> Residual deviance: 206.70  on 473  degrees of freedom
#> AIC: 224.7
#> 
#> Number of Fisher Scoring iterations: 8

Interpretasi model:

  • Signifikansi : terdapat 4 variabel yang secara statistik signifikan (p-value < 0.05), yaitu: Region2, Milk, Frozen, Detergents_Paper.

4.2 KNN

Sekarang mari kita mengeksplorasi algoritma klasifikasi K-Nearest Neighbor. Pada algoritma K-Nearest Neighbor, kita perlu melakukan satu tahap data pre-process tambahan. Untuk setiap data train dan test yang kita miliki, hilangkan variabel kategorik kecuali variabel Channel. Pisahkan variabel prediktor dan target dari data train dan test.

# variabel prediktor pada data train
train_x <- train_up_wholesale %>%
  select(-c("Region", "Channel"))

# variabel prediktor pada data test
test_x <- test_wholesale %>%
  select(-c("Region", "Channel"))

# variabel target pada data train
train_y <- train_up_wholesale$Channel

# variabel target pada data test
test_y <- test_wholesale$Channel

Kita cek data train_x.

glimpse(train_x)
#> Rows: 482
#> Columns: 6
#> $ Fresh            <int> 19219, 3157, 11635, 6633, 7780, 8656, 1206, 7363, 306…
#> $ Milk             <int> 1840, 4888, 922, 2096, 2495, 2746, 3620, 475, 7209, 3…
#> $ Grocery          <int> 1658, 2500, 1614, 4563, 9464, 2501, 2857, 585, 4897, …
#> $ Frozen           <int> 8195, 4477, 2583, 1389, 669, 6845, 1945, 1112, 18711,…
#> $ Detergents_Paper <int> 349, 273, 192, 1860, 2518, 694, 353, 72, 763, 235, 35…
#> $ Delicassen       <int> 483, 2165, 1068, 1892, 501, 980, 967, 216, 2876, 4365…

Ingatlah bahwa pengukuran jarak pada KNN sangat bergantung pada skala data dari variabel prediktor yang dimasukkan sebagai input model. Adanya prediktor yang memiliki range nilai yang amat berbeda dari prediktor lainnya dapat menyebabkan masalah pada model klasifikasi. Oleh karena itu, mari lakukan normalisasi data untuk menyamakan skala dari tiap variabel prediktor agar memiliki range nilai yang standar.

Untuk menormalisasi data train_x, maka kita menggunakan fungsi scale(). Sementara itu, untuk menormalisasi data test, menggunakan fungsi yang sama namun menggunakan atribut center dan scale yang didapat dari data train_x.

# scale train_x data
train_x_scaled <- scale(train_x)

# scale test_x data
test_x_scaled <- scale(test_x,
                       center = attr(train_x_scaled, "scaled:center"),
                       scale = attr(train_x_scaled, "scaled:center"))

Setelah kita selesai menormalisasi data, kita perlu menemukan nilai k yang optimum untuk digunakan pada model KNN. Pada praktiknya, memilih nilai k bergantung pada kompleksitas data yang sedang dianalisis dan banyaknya observasi/baris yang terdapat pada data train.

# mencari nilai k optimum -> akar dari jumlah data
round(sqrt(nrow(train_x)))
#> [1] 22

Karena hasil akarnya adalah 22 yang merupakan angka genap, maka untuk nilai k nya kita tentukan antara 21 atau 23. Disini saya memilih menggunakan 23.

Dengan menggunakan nilai k yang telah kita dapatkan, sekarang kita coba untuk memprediksi test_y dengan menggunakan data train_x dan train_y. Untuk membuat model KNN, saya menggunakan fungsi knn() dan menyimpan hasil prediksi pada object model_knn.

model_knn <- knn(train = train_x_scaled,
                 test = test_x_scaled,
                 cl = train_y,
                 k = 23)

head(model_knn)
#> [1] 2 1 1 2 2 2
#> Levels: 1 2

5 Prediksi

Sekarang, kita kembali pada model_logistic. Pada bagian ini, kita coba untuk memprediksi data test menggunakan model_logistic untuk menghasilkan nilai probabilitas. Menggunakan fungsi predict() dengan mengatur parameter type = "response" kemudian menyimpan hasilnya ke dalam object prob_value.

prob_value <- predict(object = model_logistic,
                      newdata = test_wholesale,
                      type = "response")
head(prob_value)
#>         1         2         3         4         5         6 
#> 0.4441820 0.1188120 0.4097629 0.6729572 0.9923522 0.9585477

Karena hasil prediksi pada model logistic regression berupa probabilitas, kita harus mengubah nilai tersebut menjadi kategori / kelas target kita. Dengan menggunakan threshold 0.51, kita coba untuk mengklasifikasikan customer tesebut termasuk dalam channel yang mana apakah horeca atau retail. Kita akan menggunakan fungsi ifelse() dan menyimpan hasil prediksi pada object pred_value.

pred_value <- ifelse(prob_value > 0.51, yes = 2, no = 1)
table(pred_value)
#> pred_value
#>  1  2 
#> 59 29

6 Evaluasi Model

Pada bagian sebelumnya, kita telah melakukan prediksi menggunakan model Logistic Regression maupun algoritma KNN. Namun, kita juga perlu mengevaluasi kebaikan model dalam memprediksi data baru (unseen data). Pada tahap ini, membuat confusion matrix dari model logistic regression menggunakan label aktual dari data test dan hasil prediksi (pred_value). Karena kita ingin lebih melihat / mengamati channel retail dibandingkan horeca, oleh karena itu, kita memilih kelas retail menjadi kelas positif. Sehingga kita atur kelas positif yaitu “2”.

6.1 Model Logistic Regression

Evaluasi model logistic regression, kita membuat confusion matrix dengan label aktual Channel pada data test_wholesale. Berikut hasil evaluasinya:

confusionMatrix(data = as.factor(pred_value),
                reference = test_wholesale$Channel,
                positive = "2")
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction  1  2
#>          1 54  5
#>          2  3 26
#>                                           
#>                Accuracy : 0.9091          
#>                  95% CI : (0.8287, 0.9599)
#>     No Information Rate : 0.6477          
#>     P-Value [Acc > NIR] : 0.00000001509   
#>                                           
#>                   Kappa : 0.7978          
#>                                           
#>  Mcnemar's Test P-Value : 0.7237          
#>                                           
#>             Sensitivity : 0.8387          
#>             Specificity : 0.9474          
#>          Pos Pred Value : 0.8966          
#>          Neg Pred Value : 0.9153          
#>              Prevalence : 0.3523          
#>          Detection Rate : 0.2955          
#>    Detection Prevalence : 0.3295          
#>       Balanced Accuracy : 0.8930          
#>                                           
#>        'Positive' Class : 2               
#> 

Berdasarkan hasil confusion matrix di atas, dapat kita ketahui bahwa Model Logistic Regression dalam melakukan prediksi untuk segmentasi ini adalah:

  • Nilai Accuracy : 90.91%
  • Nilai Recall / Sensitivity : 83.87%
  • Nilai Precision / Pos Pred Value : 89.66%
  • Nilai Specificity : 94.74%

6.2 Model KNN

Evaluasi model KNN kita membuat confusion matrix dengan label aktual test_y. Berikut hasil evaluasinya:

confusionMatrix(data = model_knn,
                reference = test_y,
                positive = "2")
#> Confusion Matrix and Statistics
#> 
#>           Reference
#> Prediction  1  2
#>          1 54  7
#>          2  3 24
#>                                           
#>                Accuracy : 0.8864          
#>                  95% CI : (0.8009, 0.9441)
#>     No Information Rate : 0.6477          
#>     P-Value [Acc > NIR] : 0.0000003334    
#>                                           
#>                   Kappa : 0.7434          
#>                                           
#>  Mcnemar's Test P-Value : 0.3428          
#>                                           
#>             Sensitivity : 0.7742          
#>             Specificity : 0.9474          
#>          Pos Pred Value : 0.8889          
#>          Neg Pred Value : 0.8852          
#>              Prevalence : 0.3523          
#>          Detection Rate : 0.2727          
#>    Detection Prevalence : 0.3068          
#>       Balanced Accuracy : 0.8608          
#>                                           
#>        'Positive' Class : 2               
#> 

Berdasarkan hasil confusion matrix di atas, dapat kita ketahui bahwa Model KNN dalam melakukan prediksi untuk segmentasi ini adalah:

  • Nilai Accuracy : 88.64%
  • Nilai Recall / Sensitivity : 77.42%
  • Nilai Precision / Pos Pred Value : 88.89%
  • Nilai Specificity : 94.74%

7 Conclusion

Jika saya adalah seorang agen distributor wholesale, dimana saya akan mengirimkan jenis-jenis produk ke channel / segmen yang sesuai dengan target customer / pelanggan, maka saya ingin memastikan bahwa setiap produk saya akan diterima oleh channel / segmen customer yang tepat. Hal ini penting, agar produk yang saya jual sebisa mungkin diterima oleh customer yang tepat sehingga tidak ada produk yang tidak terbeli karena salah market. Oleh karena itu saya lebih ingin memastikan agar model yang saya buat tidak salah mengklasifikasikan pelanggan ke dalam segmen yang salah, atau dengan kata lain saya meminimalisir terjadinya False Positive.

Oleh karena itu saya akan fokus pada nilai precision untuk memastikan bahwa model saya mengidentifikasi channel pelanggan yang benar dan menghindari kesalahan klasifikasi.

Dengan demikian, berdasarkan hasil evaluasi model, Model Logistic Regression lebih baik untuk digunakan karena menghasilkan nilai precision yang lebih baik yaitu sebesar 89.66%.