Clustering

Clustering adalah pengelompokkan data berdasarkan karakteristiknya. Tujuan clustering:

K-means Clustering

K-means adalah salah satu algoritma centroid-based clustering, artinya tiap cluster memiliki satu centroid yang mewakili cluster tersebut.

Additonal Notes: Centroid adalah titik pusat dari sebuah cluster, kenapa kita membutuhkan titik pusat di setiap cluster? karena titik pusat akan menjadi basis perhitungan jarak antara setiap observasi di sekitarnya, harapannya di setiap cluster terdiri dari observasi yg semirip mungkin.

Banyaknya cluster/centroid/ \(k\) ditentukan oleh user.

Algoritma

  1. Random initialization: meletakkan \(k\) centroid secara random
  2. Cluster assignment: assign masing-masing observasi ke cluster terdekat, berdasarkan perhitungan jarak (jarak Euclidean)
  • Maka untuk value yang bisa kita input ke dalam metode k-means harus numerik
  1. Centroid update: menggeser centroid ke rata-rata (means) dari cluster yang terbentuk
  2. Ulangi langkah 2 dan 3 sampai tidak ada observasi yang clusternya berubah lagi

https://www.naftaliharris.com/blog/visualizing-k-means-clustering/

Kebaikan hasil clustering dapat dilihat dari 3 nilai:

  • Within Sum of Squares ($withinss): jumlah jarak kuadrat dari tiap observasi ke centroid tiap cluster.
    • Harapannya jarak antara tiap observasi ke centroidnya sedekat mungkin, hal itu akan mengindikasikan bahwa cluster tersebut memiliki karakteristik yang sama
    • Nilai WSS-nya rendah
  • Between Sum of Squares ($betweenss): jumlah jarak kuadrat terbobot dari tiap centroid ke rata-rata global. Bobotnya berdasarkan banyaknya observasi pada cluster.
    • Harapannya nilai dari BSS nya tinggi/besar, hal tersebut akan mengindikasikan bahwa jarak antar tiap centroid saling berjauhan (Agar posisi dari setiap cluster tidak berdekatan).
  • Total Sum of Squares ($totss): jumlah jarak kuadrat dari tiap observasi ke rata-rata global.
    • Nilai dari TSS tidak bisa berdiri sendiri, melainkan harus disandingkan dengan nilai BSS
      • BSS / TSS = … (Harapannya hasil dari pembagian tersebut akan mendekati 1)
      • Harapannya nilai dari TSS sedekat mungkin dengan nilai BSS
        Jika nilai TSS sangat tinggi/menjaduhi BSS,hal tersebut mengindikasikan bahwa terdapat beebrapa observasi yang dianggap sebagai outlier

K-means Workflow

Business Question: Whisky Recommendation

Sebagai seorang data scientist di sebuah toko whisky, kita diminta untuk membuat product recommendation untuk whisky berdasarkan preferensi rasa masing-masing customer!

Tujuan: membentuk kelompok whisky yang memiliki karakteristik rasa khas pada tiap clusternya.

Read Data

Data yang digunakan berupa data penyulingan Malt Whisky dari 86 pabrik penyulingan, diperoleh dari penelitian Dr. Wisehart (Universitas St. Andrews). Setiap whisky diberi skor 0-4 dari 12 kategori cita rasa berdasarkan uji organoleptik:

  • Body: tingkat kekuatan rasa (light/heavy)
  • Sweetness: tingkat rasa manis
  • Smoky: tingkat rasa asap
  • Medicinal: tingkat rasa pahit (obat)
  • Tobacco: tingkat rasa tembakau
  • Honey: tingkat rasa madu
  • Spicy: tingkat rasa pedas
  • Winey: tingkat rasa anggur
  • Nutty: tingkat rasa kacang
  • Malty: tingkat rasa gandum
  • Fruity: tingkat rasa buah
  • Floral: tingkat rasa bunga
whisky <- read.csv("data_input/whiskies.txt")
head(whisky)
#>   RowID  Distillery Body Sweetness Smoky Medicinal Tobacco Honey Spicy Winey
#> 1     1   Aberfeldy    2         2     2         0       0     2     1     2
#> 2     2    Aberlour    3         3     1         0       0     4     3     2
#> 3     3      AnCnoc    1         3     2         0       0     2     0     0
#> 4     4      Ardbeg    4         1     4         4       0     0     2     0
#> 5     5     Ardmore    2         2     2         0       0     1     1     1
#> 6     6 ArranIsleOf    2         3     1         1       0     1     1     1
#>   Nutty Malty Fruity Floral    Postcode Latitude Longitude
#> 1     2     2      2      2  \tPH15 2EB   286580    749680
#> 2     2     3      3      2  \tAB38 9PJ   326340    842570
#> 3     2     2      3      2   \tAB5 5LI   352960    839320
#> 4     1     2      1      0  \tPA42 7EB   141560    646220
#> 5     2     3      1      1  \tAB54 4NH   355350    829140
#> 6     0     1      1      2    KA27 8HJ   194050    649950

Data Cleansing

library(tidyverse)
# meng-assign nilai dari kolom Distillery menjadi rownames
whisky <- whisky %>% 
  column_to_rownames(var = "Distillery")

# membuang kolom yang tidak digunakan
whisky <- whisky %>% 
  select(-c("RowID", "Postcode", "Latitude", "Longitude"))

head(whisky)
#>             Body Sweetness Smoky Medicinal Tobacco Honey Spicy Winey Nutty
#> Aberfeldy      2         2     2         0       0     2     1     2     2
#> Aberlour       3         3     1         0       0     4     3     2     2
#> AnCnoc         1         3     2         0       0     2     0     0     2
#> Ardbeg         4         1     4         4       0     0     2     0     1
#> Ardmore        2         2     2         0       0     1     1     1     2
#> ArranIsleOf    2         3     1         1       0     1     1     1     0
#>             Malty Fruity Floral
#> Aberfeldy       2      2      2
#> Aberlour        3      3      2
#> AnCnoc          2      3      2
#> Ardbeg          2      1      0
#> Ardmore         3      1      1
#> ArranIsleOf     1      1      2

Cek missing values:

anyNA(whisky)
#> [1] FALSE

Cek tipe data kolom:

# pastikan semua kolom bertipe numerik, karena k-means clustering dilakukan berdasarkan jarak
str(whisky)
#> 'data.frame':    86 obs. of  12 variables:
#>  $ Body     : int  2 3 1 4 2 2 0 2 2 2 ...
#>  $ Sweetness: int  2 3 3 1 2 3 2 3 2 3 ...
#>  $ Smoky    : int  2 1 2 4 2 1 0 1 1 2 ...
#>  $ Medicinal: int  0 0 0 4 0 1 0 0 0 1 ...
#>  $ Tobacco  : int  0 0 0 0 0 0 0 0 0 0 ...
#>  $ Honey    : int  2 4 2 0 1 1 1 2 1 0 ...
#>  $ Spicy    : int  1 3 0 2 1 1 1 1 0 2 ...
#>  $ Winey    : int  2 2 0 0 1 1 0 2 0 0 ...
#>  $ Nutty    : int  2 2 2 1 2 0 2 2 2 2 ...
#>  $ Malty    : int  2 3 2 2 3 1 2 2 2 1 ...
#>  $ Fruity   : int  2 3 3 1 1 1 3 2 2 2 ...
#>  $ Floral   : int  2 2 2 0 1 2 3 1 2 1 ...

Exploratory Data Analysis (EDA)

Cek skala antar variabel:

summary(whisky)
#>       Body        Sweetness         Smoky         Medicinal     
#>  Min.   :0.00   Min.   :1.000   Min.   :0.000   Min.   :0.0000  
#>  1st Qu.:2.00   1st Qu.:2.000   1st Qu.:1.000   1st Qu.:0.0000  
#>  Median :2.00   Median :2.000   Median :1.000   Median :0.0000  
#>  Mean   :2.07   Mean   :2.291   Mean   :1.535   Mean   :0.5465  
#>  3rd Qu.:2.00   3rd Qu.:3.000   3rd Qu.:2.000   3rd Qu.:1.0000  
#>  Max.   :4.00   Max.   :4.000   Max.   :4.000   Max.   :4.0000  
#>     Tobacco           Honey           Spicy           Winey       
#>  Min.   :0.0000   Min.   :0.000   Min.   :0.000   Min.   :0.0000  
#>  1st Qu.:0.0000   1st Qu.:1.000   1st Qu.:1.000   1st Qu.:0.0000  
#>  Median :0.0000   Median :1.000   Median :1.000   Median :1.0000  
#>  Mean   :0.1163   Mean   :1.244   Mean   :1.384   Mean   :0.9767  
#>  3rd Qu.:0.0000   3rd Qu.:2.000   3rd Qu.:2.000   3rd Qu.:1.0000  
#>  Max.   :1.0000   Max.   :4.000   Max.   :3.000   Max.   :4.0000  
#>      Nutty           Malty           Fruity          Floral     
#>  Min.   :0.000   Min.   :0.000   Min.   :0.000   Min.   :0.000  
#>  1st Qu.:1.000   1st Qu.:1.000   1st Qu.:1.000   1st Qu.:1.000  
#>  Median :2.000   Median :2.000   Median :2.000   Median :2.000  
#>  Mean   :1.465   Mean   :1.802   Mean   :1.802   Mean   :1.698  
#>  3rd Qu.:2.000   3rd Qu.:2.000   3rd Qu.:2.000   3rd Qu.:2.000  
#>  Max.   :4.000   Max.   :3.000   Max.   :3.000   Max.   :4.000

Diskusi: Pada data whisky, apakah skala nilai antar variable berbeda? Apakah perlu dilakukan scaling?

Determine Optimum k

Semakin tinggi k, maka:

  • WSS semakin mendekati 0

Kalau begitu apakah kita selalu memilih k = banyak observasi? Bagaimana menentukan k optimum?

Kalau begitu apakah kita selalu memilih k = banyak observasi? Bagaimana menentukan k optimum?

  1. Kebutuhan dari segi bisnis, data dibutuhkan menjadi berapa kelompok; atau
  • Tujuannya adalah ingin melihat customer yang memiliki karakterisik penggunaan yang
    • Paling Aktif
    • Kurang Aktif
    • Tidak pernah menggunakan sama sekali
    • Bapak/Ibu bisa langsung menggunakan K = 3
  1. Secara statistik: Elbow method, visualisasi dengan fviz_nbclust() dari package factoextra
library(factoextra)

elbow <- 
fviz_nbclust(x = whisky, 
             FUNcluster = kmeans,
             method = "wss")

# melihat nili wss
elbow$data 
#>    clusters        y
#> 1         1 665.8372
#> 2         2 531.2145
#> 3         3 447.1970
#> 4         4 403.1981
#> 5         5 378.8929
#> 6         6 360.5815
#> 7         7 335.5016
#> 8         8 313.3365
#> 9         9 308.2105
#> 10       10 287.8028

Membuat Clustering K-means

Kita bisa melakukan K-means Clustering menggunakan fungsi kmeans().

Parameter:

  • x: dataset
  • centers: banyaknya centroid (nilai k)

Penting: perlu dilakukan set.seed() karena terdapat random initialization pada tahap awal k-means

# Please type your code down here
RNGkind(sample.kind = "Rounding")
set.seed(100)

whisky_kmeans <- kmeans(x = whisky,
                        centers = 4)

Hasil dari kmeans():

  1. Banyaknya pengulangan (iterasi) algoritma k-means sampai dihasilkan cluster yang stabil
# Please run the code down below
whisky_kmeans$iter
#> [1] 3
  1. Banyaknya observasi pada tiap cluster
# Please run the code down below
whisky_kmeans$size
#> [1] 28  6 37 15
  1. Letak pusat cluster/centroid, biasa digunakan untuk profiling cluster (akan dibahas lebih dalam di bagian akhir)
# Please run the code down below
whisky_kmeans$centers
#>       Body Sweetness    Smoky  Medicinal    Tobacco     Honey    Spicy
#> 1 2.678571  2.392857 1.428571 0.07142857 0.03571429 1.8928571 1.642857
#> 2 3.666667  1.500000 3.666667 3.33333333 0.66666667 0.1666667 1.666667
#> 3 1.432432  2.486486 1.054054 0.24324324 0.05405405 0.9729730 1.108108
#> 4 1.866667  1.933333 2.066667 1.06666667 0.20000000 1.1333333 1.466667
#>       Winey    Nutty    Malty   Fruity    Floral
#> 1 1.8214286 1.892857 2.071429 2.107143 1.7857143
#> 2 0.5000000 1.166667 1.333333 1.166667 0.1666667
#> 3 0.4594595 1.162162 1.675676 1.972973 2.1081081
#> 4 0.8666667 1.533333 1.800000 1.066667 1.1333333
  1. Label cluster untuk tiap observasi
# Please run the code down below
head(whisky_kmeans$cluster)
#>   Aberfeldy    Aberlour      AnCnoc      Ardbeg     Ardmore ArranIsleOf 
#>           1           1           3           2           4           3
  1. Withinss, Betweenss, Totss
# Please run the code down below
whisky_kmeans$withinss
#> [1] 137.60714  24.33333 162.32432  78.93333
whisky_kmeans$betweenss
#> [1] 262.6391
whisky_kmeans$totss
#> [1] 665.8372
whisky_kmeans$betweenss / whisky_kmeans$totss
#> [1] 0.3944494

Interpretation/Cluster Profiling

# memasukkan label cluster ke data awal dengan nama kolom kelompok
whisky$kelompok <- whisky_kmeans$cluster

# melakukan profiling dengan summarise data
whisky_centroid <- whisky %>% 
  group_by(kelompok) %>% # melakukan grouping pada setiap cluster
  summarise_all(mean) # dihitung rata2nya pada tiap cluster
whisky_centroid
#> # A tibble: 4 × 13
#>   kelompok  Body Sweetness Smoky Medicinal Tobacco Honey Spicy Winey Nutty Malty
#>      <int> <dbl>     <dbl> <dbl>     <dbl>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1        1  2.68      2.39  1.43    0.0714  0.0357 1.89   1.64 1.82   1.89  2.07
#> 2        2  3.67      1.5   3.67    3.33    0.667  0.167  1.67 0.5    1.17  1.33
#> 3        3  1.43      2.49  1.05    0.243   0.0541 0.973  1.11 0.459  1.16  1.68
#> 4        4  1.87      1.93  2.07    1.07    0.2    1.13   1.47 0.867  1.53  1.8 
#> # … with 2 more variables: Fruity <dbl>, Floral <dbl>
# optional: mempermudah profiling
# menampilkan cluster dengan nilai terendah dan tertinggi untuk masing-masing karakteristik whisky
whisky_centroid %>% 
  tidyr::pivot_longer(-kelompok) %>% 
  group_by(name) %>% 
  summarize(cluster_min_val = which.min(value),
            cluster_max_val = which.max(value))
#> # A tibble: 12 × 3
#>    name      cluster_min_val cluster_max_val
#>    <chr>               <int>           <int>
#>  1 Body                    3               2
#>  2 Floral                  2               3
#>  3 Fruity                  4               1
#>  4 Honey                   2               1
#>  5 Malty                   2               1
#>  6 Medicinal               1               2
#>  7 Nutty                   3               1
#>  8 Smoky                   3               2
#>  9 Spicy                   3               2
#> 10 Sweetness               2               3
#> 11 Tobacco                 1               2
#> 12 Winey                   3               1

Profiling:

Cluster 1: - Paling tinggi di cita rasa: _____ - Paling rendah di cita rasa: _____ - Label: _____

Cluster 2: - Paling tinggi di cita rasa: _____ - Paling rendah di cita rasa: _____ - Label: _____

Cluster 3: - Paling tinggi di cita rasa: _____ - Paling rendah di cita rasa: _____ - Label: _____

Cluster 4: - Paling tinggi di cita rasa: _____ - Paling rendah di cita rasa: _____ - Label: _____

Cluster 5: - Paling tinggi di cita rasa: _____ - Paling rendah di cita rasa: _____ - Label: _____

Product Recommender

Misal ada 1 pelanggan yang menyukai whisky Laphroig datang ke toko kita, namun stok whisky tersebut sedang kosong. Kira-kira whisky apa yang akan kita rekomendasikan?

# cek Laphroig ada di kelompok mana?
whisky["Laphroig",]
#>          Body Sweetness Smoky Medicinal Tobacco Honey Spicy Winey Nutty Malty
#> Laphroig    4         2     4         4       1     0     0     1     1     1
#>          Fruity Floral kelompok
#> Laphroig      0      0        2
# cek whisky apa saja yang masuk ke kelompok tersebut
whisky %>% 
  filter(kelompok == 2)
#>           Body Sweetness Smoky Medicinal Tobacco Honey Spicy Winey Nutty Malty
#> Ardbeg       4         1     4         4       0     0     2     0     1     2
#> Caol Ila     3         1     4         2       1     0     2     0     2     1
#> Clynelish    3         2     3         3       1     0     2     0     1     1
#> Lagavulin    4         1     4         4       1     0     1     2     1     1
#> Laphroig     4         2     4         4       1     0     0     1     1     1
#> Talisker     4         2     3         3       0     1     3     0     1     2
#>           Fruity Floral kelompok
#> Ardbeg         1      0        2
#> Caol Ila       1      1        2
#> Clynelish      2      0        2
#> Lagavulin      1      0        2
#> Laphroig       0      0        2
#> Talisker       2      0        2

Jawaban:

Combining PCA with K-means

Untuk visualisasi clustering kita dapat menggunakan biplot. Hal ini akan membantu dalam mengetahui pola data yang ada pada data, sehingga kita bisa lebih yakin tentang hasil clustering yang di dapat (bentuk persebaran cluster nya seperti apa).

Dalam melakukan hal tersebut, kita akan menggunakan fungsi fviz_cluster() dari package factoextra. Berikut beberapa parameter yang kita akan digunakan

  • object: object kmeans
  • data: data variable numerik
# Please type your code down here

library(factoextra)

fviz_cluster(object = whisky_kmeans, # object kmeans
             data = whisky %>% select(-kelompok)) # data variable numerik