Clustering adalah pengelompokkan data berdasarkan karakteristiknya. Tujuan 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.
https://www.naftaliharris.com/blog/visualizing-k-means-clustering/
Kebaikan hasil clustering dapat dilihat dari 3 nilai:
$withinss
): jumlah
jarak kuadrat dari tiap observasi ke centroid tiap cluster.
$betweenss
): jumlah
jarak kuadrat terbobot dari tiap centroid ke rata-rata global. Bobotnya
berdasarkan banyaknya observasi pada cluster.
$totss
): jumlah jarak
kuadrat dari tiap observasi ke rata-rata global.
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.
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 manisSmoky
: tingkat rasa asapMedicinal
: tingkat rasa pahit (obat)Tobacco
: tingkat rasa tembakauHoney
: tingkat rasa maduSpicy
: tingkat rasa pedasWiney
: tingkat rasa anggurNutty
: tingkat rasa kacangMalty
: tingkat rasa gandumFruity
: tingkat rasa buahFloral
: tingkat rasa bungawhisky <- 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
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 ...
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?
Semakin tinggi k, maka:
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?
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
Kita bisa melakukan K-means Clustering menggunakan fungsi
kmeans()
.
Parameter:
x
: datasetcenters
: 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()
:
# Please run the code down below
whisky_kmeans$iter
#> [1] 3
# Please run the code down below
whisky_kmeans$size
#> [1] 28 6 37 15
# 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
# Please run the code down below
head(whisky_kmeans$cluster)
#> Aberfeldy Aberlour AnCnoc Ardbeg Ardmore ArranIsleOf
#> 1 1 3 2 4 3
# 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
# 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: _____
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:
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 kmeansdata
: 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