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:
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
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 bungalibrary(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.2 ──
## ✔ ggplot2 3.3.6 ✔ purrr 0.3.5
## ✔ tibble 3.1.8 ✔ dplyr 1.0.10
## ✔ tidyr 1.2.1 ✔ stringr 1.4.1
## ✔ readr 2.1.3 ✔ forcats 0.5.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
# meng-assign nilai dari kolom Distillery menjadi rownames
whisky <- whisky %>%
column_to_rownames(var = "Distillery")
# membuang kolom yang tidak digunakan
whisky_clean <-
whisky %>%
select(-c("RowID", "Postcode", "Latitude", "Longitude"))
head(whisky_clean)
## 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:
colSums(is.na(whisky_clean))
## Body Sweetness Smoky Medicinal Tobacco Honey Spicy Winey
## 0 0 0 0 0 0 0 0
## Nutty Malty Fruity Floral
## 0 0 0 0
anyNA(whisky_clean)
## [1] FALSE
Cek tipe data kolom:
# pastikan semua kolom bertipe numerik, karena k-means clustering dilakukan berdasarkan jarak
whisky_clean %>%
glimpse()
## Rows: 86
## Columns: 12
## $ Body <int> 2, 3, 1, 4, 2, 2, 0, 2, 2, 2, 4, 3, 4, 2, 3, 2, 1, 2, 2, 1, …
## $ Sweetness <int> 2, 3, 3, 1, 2, 3, 2, 3, 2, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 1, …
## $ Smoky <int> 2, 1, 2, 4, 2, 1, 0, 1, 1, 2, 2, 1, 2, 1, 2, 2, 1, 2, 3, 2, …
## $ Medicinal <int> 0, 0, 0, 4, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, …
## $ Tobacco <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ Honey <int> 2, 4, 2, 0, 1, 1, 1, 2, 1, 0, 2, 3, 2, 2, 3, 2, 0, 1, 2, 2, …
## $ Spicy <int> 1, 3, 0, 2, 1, 1, 1, 1, 0, 2, 1, 2, 2, 2, 1, 2, 1, 2, 2, 2, …
## $ Winey <int> 2, 2, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0, 0, 1, 1, 1, 2, 1, 1, …
## $ Nutty <int> 2, 2, 2, 1, 2, 0, 2, 2, 2, 2, 3, 0, 2, 0, 2, 2, 0, 2, 1, 2, …
## $ Malty <int> 2, 3, 2, 2, 3, 1, 2, 2, 2, 1, 0, 2, 2, 2, 3, 2, 2, 2, 1, 2, …
## $ Fruity <int> 2, 3, 3, 1, 1, 1, 3, 2, 2, 2, 1, 2, 2, 3, 2, 2, 2, 2, 1, 2, …
## $ Floral <int> 2, 2, 2, 0, 1, 2, 3, 1, 2, 1, 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, …
Cek skala antar variabel:
summary(whisky_clean)
## 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
whisky_clean <-
whisky_clean %>%
select(-Tobacco)
Kenapa kita coba hilangkan kolom Tobacco, karena jika kita lihat dari hasil summarynya, nilai mean sangat kecil dan bahkan sampai 3rd Q nilainya 0.
Semakin tinggi k, maka:
Kalau begitu apakah kita selalu memilih k = banyak observasi? Bagaimana menentukan k optimum?
Kebutuhan dari segi bisnis, data dibutuhkan menjadi berapa kelompok; atau Contoh: Misalkan kita bekerja di sebuah Bank, dan kita diminta untuk mengelompokan data nasabah kita menjadi ke dua kelompok, Nasabah Prioritas & Nasaban Tidak Prioritas.
Secara statistik: Elbow method, visualisasi dengan
fviz_nbclust() dari package
factoextra
library(factoextra)
## Warning: package 'factoextra' was built under R version 4.2.2
## Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
fviz_nbclust(x = whisky_clean,
FUNcluster = kmeans,
method = "wss")
Nilai K yang akan kita coba manfaatkan adalah 4, berarti hasil akhir
dari clustering kita akan sebanyak 4 kelompok.
Catatan:
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")
## Warning in RNGkind(sample.kind = "Rounding"): non-uniform 'Rounding' sampler
## used
set.seed(100)
whisky_kmeans <- kmeans(x = whisky_clean,
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 Honey Spicy Winey Nutty
## 1 2.678571 2.392857 1.428571 0.07142857 1.8928571 1.642857 1.8214286 1.892857
## 2 3.666667 1.500000 3.666667 3.33333333 0.1666667 1.666667 0.5000000 1.166667
## 3 1.432432 2.486486 1.054054 0.24324324 0.9729730 1.108108 0.4594595 1.162162
## 4 1.866667 1.933333 2.066667 1.06666667 1.1333333 1.466667 0.8666667 1.533333
## Malty Fruity Floral
## 1 2.071429 2.107143 1.7857143
## 2 1.333333 1.166667 0.1666667
## 3 1.675676 1.972973 2.1081081
## 4 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] 136.64286 23.00000 160.43243 76.53333
whisky_kmeans$betweenss
## [1] 260.3914
whisky_kmeans$totss
## [1] 657
whisky_kmeans$betweenss / whisky_kmeans$totss
## [1] 0.3963339
# memasukkan label cluster ke data awal dengan nama kolom kelompok
whisky_clean$kelompok <- whisky_kmeans$cluster
# melakukan profiling dengan summarise data
whisky_centroid <- whisky_clean %>%
group_by(kelompok) %>% # melakukan grouping pada setiap cluster
summarise_all(mean) # dihitung rata2nya pada tiap cluster
whisky_centroid
## # A tibble: 4 × 12
## kelompok Body Sweetness Smoky Medicinal Honey Spicy Winey Nutty Malty Fruity
## <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1 2.68 2.39 1.43 0.0714 1.89 1.64 1.82 1.89 2.07 2.11
## 2 2 3.67 1.5 3.67 3.33 0.167 1.67 0.5 1.17 1.33 1.17
## 3 3 1.43 2.49 1.05 0.243 0.973 1.11 0.459 1.16 1.68 1.97
## 4 4 1.87 1.93 2.07 1.07 1.13 1.47 0.867 1.53 1.8 1.07
## # … with 1 more variable: Floral <dbl>
Profiling:
Cluster 1: - Paling tinggi di cita rasa: Body - Paling rendah di cita rasa: Medicinal
Cluster 2: - Paling tinggi di cita rasa: Body & Smoky - Paling rendah di cita rasa: Floral & Honey
Cluster 3: - Paling tinggi di cita rasa: Sweetness - Paling rendah di cita rasa: Medicinal
Cluster 4: - Paling tinggi di cita rasa: Smoky - Paling rendah di cita rasa: Winey
Profiling 2.0
Kita selain melihat nilai yang paling menonjol pada masing-masing clusternya, kita juga harus memperhatikan secara menyeluruh. Sebagai contoh, kita melihat rasa body yang paling kuat dari keseluruhan clusternya. (Ka Dicky)
# Optional
whisky_centroid %>%
pivot_longer(-kelompok) %>%
group_by(name) %>%
summarise(cluster_min = which.min(value),
cluster_max = which.max(value))
## # A tibble: 11 × 3
## name cluster_min cluster_max
## <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 Winey 3 1
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_clean["Laphroig",]
## Body Sweetness Smoky Medicinal Honey Spicy Winey Nutty Malty Fruity
## Laphroig 4 2 4 4 0 0 1 1 1 0
## Floral kelompok
## Laphroig 0 2
# cek whisky apa saja yang masuk ke kelompok tersebut
whisky_clean %>%
filter(kelompok %in% 2)
## Body Sweetness Smoky Medicinal Honey Spicy Winey Nutty Malty Fruity
## Ardbeg 4 1 4 4 0 2 0 1 2 1
## Caol Ila 3 1 4 2 0 2 0 2 1 1
## Clynelish 3 2 3 3 0 2 0 1 1 2
## Lagavulin 4 1 4 4 0 1 2 1 1 1
## Laphroig 4 2 4 4 0 0 1 1 1 0
## Talisker 4 2 3 3 1 3 0 1 2 2
## Floral kelompok
## Ardbeg 0 2
## Caol Ila 1 2
## Clynelish 0 2
## Lagavulin 0 2
## Laphroig 0 2
## Talisker 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_clean %>% select(-kelompok)) # data variable numerik
Additional: Kita akan membuat K-Means baru dengan nilai K = 3
Step 1: Membuat K-Means
set.seed(100)
RNGkind(sample.kind = "Rounding")
## Warning in RNGkind(sample.kind = "Rounding"): non-uniform 'Rounding' sampler
## used
whisky_kmeans2 <- kmeans(x = whisky_clean %>% select(-kelompok),
centers = 3)
fviz_cluster(object = whisky_kmeans2, # object kmeans
data = whisky_clean %>% select(-kelompok))
Decision Tree merupakan tree-based model yang cukup sederhana dengan performa yang robust/powerful untuk prediksi. Decision Tree menghasilkan visualisasi berupa pohon keputusan yang dapat diinterpretasi dengan mudah.
library(rpart)
library(rpart.plot)
## Warning: package 'rpart.plot' was built under R version 4.2.2
# Cross Validation
RNGkind(sample.kind = "Rounding")
## Warning in RNGkind(sample.kind = "Rounding"): non-uniform 'Rounding' sampler
## used
set.seed(100)
data <- whisky_clean
data <- data %>% mutate(kelompok = as.factor(kelompok))
index <- sample(nrow(data), nrow(data) * 0.8)
train_tree <- data[index,]
test_tree <- data[-index,]
library(rpart)
library(rpart.plot)
m<-rpart(kelompok~.,data=train_tree)
rpart.plot(m)
plotcp(m)
### Modelling
Buat model untuk memprediksi diabetes menggunakan seluruh prediktor,
dengan menggunakan fungsi ctree() dari
library(partykit). Pada fungsi tersebut terdapat 2
parameter wajib, yaitu parameter formula &
data.
# Please type your code down below
library(partykit)
## Warning: package 'partykit' was built under R version 4.2.2
## Loading required package: grid
## Loading required package: libcoin
## Warning: package 'libcoin' was built under R version 4.2.2
## Loading required package: mvtnorm
model <-
ctree(formula = kelompok ~ .,
data = train_tree)
Lakukan prediksi ke data train dan test, dengan menampilkan label prediksinya!
predict()"response" = label kelas (default threshold 0.5)"prob" = peluang ke kelas negatif dan positif# prediksi ke data train
train_pred <- predict(object = model,
newdata = train_tree,
type = "response")
# prediksi ke data test\
test_pred <- predict(object = model,
newdata = test_tree,
type = "response")
Pada evaluasi model kali ini, kita akan membandingkan peforma pada data train dan data test.
library(caret)
## Loading required package: lattice
##
## Attaching package: 'caret'
## The following object is masked from 'package:purrr':
##
## lift
# Confusion Matrix: data train
confusionMatrix(data = train_pred,
reference = train_tree$kelompok,
positive = "pos")
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3 4
## 1 12 0 0 0
## 2 0 5 0 5
## 3 10 0 25 2
## 4 2 0 2 5
##
## Overall Statistics
##
## Accuracy : 0.6912
## 95% CI : (0.5674, 0.7976)
## No Information Rate : 0.3971
## P-Value [Acc > NIR] : 8.884e-07
##
## Kappa : 0.5508
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity 0.5000 1.00000 0.9259 0.41667
## Specificity 1.0000 0.92063 0.7073 0.92857
## Pos Pred Value 1.0000 0.50000 0.6757 0.55556
## Neg Pred Value 0.7857 1.00000 0.9355 0.88136
## Prevalence 0.3529 0.07353 0.3971 0.17647
## Detection Rate 0.1765 0.07353 0.3676 0.07353
## Detection Prevalence 0.1765 0.14706 0.5441 0.13235
## Balanced Accuracy 0.7500 0.96032 0.8166 0.67262
# Confusion Matrix: data test
confusionMatrix(data = test_pred,
reference = test_tree$kelompok,
positive = "pos")
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3 4
## 1 2 0 0 0
## 2 0 1 0 1
## 3 0 0 6 0
## 4 2 0 4 2
##
## Overall Statistics
##
## Accuracy : 0.6111
## 95% CI : (0.3575, 0.827)
## No Information Rate : 0.5556
## P-Value [Acc > NIR] : 0.41
##
## Kappa : 0.4522
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity 0.5000 1.00000 0.6000 0.6667
## Specificity 1.0000 0.94118 1.0000 0.6000
## Pos Pred Value 1.0000 0.50000 1.0000 0.2500
## Neg Pred Value 0.8750 1.00000 0.6667 0.9000
## Prevalence 0.2222 0.05556 0.5556 0.1667
## Detection Rate 0.1111 0.05556 0.3333 0.1111
## Detection Prevalence 0.1111 0.11111 0.3333 0.4444
## Balanced Accuracy 0.7500 0.97059 0.8000 0.6333
Decision Tree perlu tahu kapan berhenti membuat cabang sehingga pohon lebih sederhana. Pemotongan cabang disebut sebagai Pruning. Secara umum, terbagi atas 2 cara:
model_tuned <- ctree(formula = kelompok ~ .,
data = train_tree,
control = ctree_control(mincriterion = 0.30,
minsplit = 9,
minbucket = 2))
# prediksi ke data train
train_pred_tuned <- predict(object = model_tuned,
newdata = train_tree,
type = "response")
# prediksi ke data test
test_pred_tuned <- predict(object = model_tuned,
newdata = test_tree,
type = "response")
# Data train
confusionMatrix(data = train_pred_tuned,
reference = train_tree$kelompok,
positive = "pos")
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3 4
## 1 23 0 1 1
## 2 0 4 0 0
## 3 1 0 26 3
## 4 0 1 0 8
##
## Overall Statistics
##
## Accuracy : 0.8971
## 95% CI : (0.7993, 0.9576)
## No Information Rate : 0.3971
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.8458
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity 0.9583 0.80000 0.9630 0.6667
## Specificity 0.9545 1.00000 0.9024 0.9821
## Pos Pred Value 0.9200 1.00000 0.8667 0.8889
## Neg Pred Value 0.9767 0.98438 0.9737 0.9322
## Prevalence 0.3529 0.07353 0.3971 0.1765
## Detection Rate 0.3382 0.05882 0.3824 0.1176
## Detection Prevalence 0.3676 0.05882 0.4412 0.1324
## Balanced Accuracy 0.9564 0.90000 0.9327 0.8244
# Data test
confusionMatrix(data = test_pred_tuned,
reference = test_tree$kelompok,
positive = "pos")
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2 3 4
## 1 4 0 1 1
## 2 0 1 0 0
## 3 0 0 9 1
## 4 0 0 0 1
##
## Overall Statistics
##
## Accuracy : 0.8333
## 95% CI : (0.5858, 0.9642)
## No Information Rate : 0.5556
## P-Value [Acc > NIR] : 0.0135
##
## Kappa : 0.7245
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: 1 Class: 2 Class: 3 Class: 4
## Sensitivity 1.0000 1.00000 0.9000 0.33333
## Specificity 0.8571 1.00000 0.8750 1.00000
## Pos Pred Value 0.6667 1.00000 0.9000 1.00000
## Neg Pred Value 1.0000 1.00000 0.8750 0.88235
## Prevalence 0.2222 0.05556 0.5556 0.16667
## Detection Rate 0.2222 0.05556 0.5000 0.05556
## Detection Prevalence 0.3333 0.05556 0.5556 0.05556
## Balanced Accuracy 0.9286 1.00000 0.8875 0.66667