install.packages("rattle")
install.packages("MVN")
install.packages("heplots")
Analisis Diskriminan dengan tidymodels
library(lares)
library(discrim)
library(tidyverse)
library(tidymodels)
library(themis)
library(tidyposterior)
library(SmartEDA)
library(DataExplorer)
library(skimr)
library(ggpubr)
library(workflowsets)
Deskripsi singkat data
Dataset wine berisi hasil analisis kimiawi dari anggur yang ditanam di daerah tertentu di Italia. Tiga jenis anggur diwakili dalam 178 sampel, dengan hasil 13 analisis kimia yang dicatat untuk setiap sampel. Variabel Type telah diubah menjadi variabel kategorik.
data ini bisa diperoleh dari package rattle
Import data di R
data(wine, package='rattle')
<- wine
df rm(wine)
glimpse(df)
Rows: 178
Columns: 14
$ Type <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
$ Alcohol <dbl> 14.23, 13.20, 13.16, 14.37, 13.24, 14.20, 14.39, 14.06…
$ Malic <dbl> 1.71, 1.78, 2.36, 1.95, 2.59, 1.76, 1.87, 2.15, 1.64, …
$ Ash <dbl> 2.43, 2.14, 2.67, 2.50, 2.87, 2.45, 2.45, 2.61, 2.17, …
$ Alcalinity <dbl> 15.6, 11.2, 18.6, 16.8, 21.0, 15.2, 14.6, 17.6, 14.0, …
$ Magnesium <int> 127, 100, 101, 113, 118, 112, 96, 121, 97, 98, 105, 95…
$ Phenols <dbl> 2.80, 2.65, 2.80, 3.85, 2.80, 3.27, 2.50, 2.60, 2.80, …
$ Flavanoids <dbl> 3.06, 2.76, 3.24, 3.49, 2.69, 3.39, 2.52, 2.51, 2.98, …
$ Nonflavanoids <dbl> 0.28, 0.26, 0.30, 0.24, 0.39, 0.34, 0.30, 0.31, 0.29, …
$ Proanthocyanins <dbl> 2.29, 1.28, 2.81, 2.18, 1.82, 1.97, 1.98, 1.25, 1.98, …
$ Color <dbl> 5.64, 4.38, 5.68, 7.80, 4.32, 6.75, 5.25, 5.05, 5.20, …
$ Hue <dbl> 1.04, 1.05, 1.03, 0.86, 1.04, 1.05, 1.02, 1.06, 1.08, …
$ Dilution <dbl> 3.92, 3.40, 3.17, 3.45, 2.93, 2.85, 3.58, 3.58, 2.85, …
$ Proline <int> 1065, 1050, 1185, 1480, 735, 1450, 1290, 1295, 1045, 1…
Eksplorasi Data
%>%
df skim_without_charts()
Name | Piped data |
Number of rows | 178 |
Number of columns | 14 |
_______________________ | |
Column type frequency: | |
factor | 1 |
numeric | 13 |
________________________ | |
Group variables | None |
Variable type: factor
skim_variable | n_missing | complete_rate | ordered | n_unique | top_counts |
---|---|---|---|---|---|
Type | 0 | 1 | FALSE | 3 | 2: 71, 1: 59, 3: 48 |
Variable type: numeric
skim_variable | n_missing | complete_rate | mean | sd | p0 | p25 | p50 | p75 | p100 |
---|---|---|---|---|---|---|---|---|---|
Alcohol | 0 | 1 | 13.00 | 0.81 | 11.03 | 12.36 | 13.05 | 13.68 | 14.83 |
Malic | 0 | 1 | 2.34 | 1.12 | 0.74 | 1.60 | 1.87 | 3.08 | 5.80 |
Ash | 0 | 1 | 2.37 | 0.27 | 1.36 | 2.21 | 2.36 | 2.56 | 3.23 |
Alcalinity | 0 | 1 | 19.49 | 3.34 | 10.60 | 17.20 | 19.50 | 21.50 | 30.00 |
Magnesium | 0 | 1 | 99.74 | 14.28 | 70.00 | 88.00 | 98.00 | 107.00 | 162.00 |
Phenols | 0 | 1 | 2.30 | 0.63 | 0.98 | 1.74 | 2.36 | 2.80 | 3.88 |
Flavanoids | 0 | 1 | 2.03 | 1.00 | 0.34 | 1.20 | 2.13 | 2.88 | 5.08 |
Nonflavanoids | 0 | 1 | 0.36 | 0.12 | 0.13 | 0.27 | 0.34 | 0.44 | 0.66 |
Proanthocyanins | 0 | 1 | 1.59 | 0.57 | 0.41 | 1.25 | 1.56 | 1.95 | 3.58 |
Color | 0 | 1 | 5.06 | 2.32 | 1.28 | 3.22 | 4.69 | 6.20 | 13.00 |
Hue | 0 | 1 | 0.96 | 0.23 | 0.48 | 0.78 | 0.96 | 1.12 | 1.71 |
Dilution | 0 | 1 | 2.61 | 0.71 | 1.27 | 1.94 | 2.78 | 3.17 | 4.00 |
Proline | 0 | 1 | 746.89 | 314.91 | 278.00 | 500.50 | 673.50 | 985.00 | 1680.00 |
Distribusi Variabel Kategorik
%>%
df ExpCatViz(clim = 10,
col = "#107896",
Page = NULL,
Flip = TRUE)
[[1]]
Distribusi Variabel Numerik
%>%
df plot_histogram(
ggtheme = theme_lares(),
geom_histogram_args = list(bins=30,
col="black",
fill="#107896"),
ncol = 1,nrow = 1)
Ekplorasi Kategorik vs Numerik
%>%
df ExpNumViz(target = "Type",
type = 2)
[[1]]
[[2]]
[[3]]
[[4]]
[[5]]
[[6]]
[[7]]
[[8]]
[[9]]
[[10]]
[[11]]
[[12]]
[[13]]
Deteksi Missing Value
%>%
df plot_missing(missing_only = FALSE,
ggtheme = theme_lares())
Analisis Diskirminant
Fungsi diskriminan dari Linear Discriminant Analysis (LDA) dapat dituliskan sebagai berikut:
\[ \boldsymbol{d}(\boldsymbol{x})=\log{P(\boldsymbol{y}=k|\boldsymbol{x})}=b_{0}+b_{1}\boldsymbol{x}_{1}+b_{2}\boldsymbol{x}_{2}+\ldots+b_{p}\boldsymbol{x}_{p} \]
dengan:
- \(\boldsymbol{y}\) adalah variabel respon dengan \(k\) kelas (kategori)
- \(\boldsymbol{d}(\boldsymbol{x})\) adalah skor diskriminan
- \(b_{0},b_{1},b_{2},\dots,b_{p}\) adalah koefisien atau bobot diskriminan
- \(\boldsymbol{x}_{1},\boldsymbol{x}_{2},\ldots,\boldsymbol{x}_{p}\) adalah variabel prediktor
Asumsi dari LDA antara lain adalah:
1 Setiap amatan harus dikelompokkan hanya ke dalam satu kelas (kategori) saja 2 Ragam dalam setiap kelas (kategori) adalah sama (equal variances) 3 \(\boldsymbol{x}_{1},\boldsymbol{x}_{2},\ldots,\boldsymbol{x}_{p}\) berdistribusi normal ganda (multivariate normal distribution)
LDA dapat dilakukan bila terdapat perbedaan yang nyata antar kelas (kategori), sehingga pada tahap awal yang harus dilakukan adalah uji hipotesis bahwa tidak ada perbedaan kelas (kategori) di antara amatan.
Jika asumsi Ragam dalam setiap kelas (kategori) adalah sama tidak terpenuhi maka maka gunakan Quadratic Discriminant Analysis (QDA).
Note : Menurut Mattjik dan Sumertajaya pada buku Sidik Peubah Ganda, “umumnya sangat sulit sekali untuk dapat memenuhi persyaratan 2 dan 3, yang dalam praktek sering kali tidak diuji; hal mana akan membuat akurasi dari analisis dengan fungsi diskriminan akan berkurang. Namun demikian, fungsi diskriminan selalu menghasilkan estimasi yang kokoh (robust estimates) terutama yang berkaitan dengan prediksi kelas”.
Pemeriksaan Awal
Pada tahap ini kita akan memeriksa asumsi 2 dan 3 LDA serta memeriksa apakah ada perbedaan kelas (kategori) di antara amatan.
Uji Normal Ganda (Multivariate Normal) setiap kelas variabel respon
Hipotesis yang diuji adalah sebagai berikut
\[ \begin{aligned} H_0 &: \text{data menyebar normal ganda} \\ H_1 &: \text{data tidak menyebar normal ganda} \end{aligned} \]
Untuk menguji kenormalan ganda di R, bisa menggunakan fungsi mvn
dari package MVN
. fungsi mvn memiliki beberapa uji normal ganda yang bisa dilakukan. Pemilihan uji normal ganda bisa dilakukan melauli argumen mvnTest, seperti uji Mardia (mvnTest="mardia"
), uji Henze-Zirkler(mvnTest="hz"
), uji Royston (mvnTest="royston"
),uji Doornik-Hansen (mvnTest="dh"
) dan uji energy mvnTest="energy"
. Argumen subset
diisi dengan kolom data yang dijadikan sebagai variabel respon.
<- MVN::mvn(data = df,subset="Type",mvnTest = "hz")
uji_normalGanda $multivariateNormality uji_normalGanda
$`1`
Test HZ p value MVN
1 Henze-Zirkler 0.9927946 0.198733 YES
$`2`
Test HZ p value MVN
1 Henze-Zirkler 1.014508 4.160195e-06 NO
$`3`
Test HZ p value MVN
1 Henze-Zirkler 0.9885863 0.3721148 YES
Karena nilai dari p-value dari $1
dan $3
adalah 0.198733 dan 0.3721148, yang mana lebih besar dari nilai \(\alpha= 0.05\) maka dapat disimpulkan bahwa tidak cukup bukti untuk menolak \(H_0\). Artinya untuk peubah-puebah prediktor pada wine tipe 1 dan tipe 3 berdistribusi normal ganda. Sementera itu, p-value dari $2
sangat kecil yaitu 4.160195e-06 yang mana lebih kecil dari nilai \(\alpha=0.05\). Artinya peubah-puebah penjelas pada wine tipe 2 tidak berdistribusi normal ganda.
Walaupun ada satu kelas yang tidak memenuhi asumsi normal ganda kita akan tetap lanjutkan menggunakan analisis diskriminan karena berdasarkan Matjik dan Sumertajaya fungsi diskriminan masih dapat menghasilkan kemampuan klasifikasi yang baik.
Uji Asumsi Kesamaan Ragam setiap kelas variabel respon
Hipotesis asumsi kesamaan ragam
\[ \begin{aligned} H_0 &: \text{ragam antar populasi sama} \\ H_1 &: \text{ragam antar populasi tidak sama} \end{aligned} \]
Uji kesamaan ragam bisa dilakukan dengan menggunkan fungsi boxM
yang berasal dari package heplots
. Fungsi ini hanya membutuhkan 2 argumen, yaitu data dalam bentuk data.frame
atau matrix
tanpa kolom gerombol (dalam hal ini kolom Type) dan vektor gerombol yang diperoleh dari data (dalam hal ini kolom Type).
<- heplots::boxM(Y=df %>% dplyr::select(-Type),
uji_ragam group=df$Type)
tidy(uji_ragam)
Karena nilai dari p-value dari uji Box’s M adalah kurang dari 2.891851e-59, yang mana lebih kecil dari nilai \(\alpha\) 0.05 maka dapat disimpulkan bahwa cukup bukti untuk menolak \(H_0\). Artinya ragam setiap kelas variabel respon tidak sama. Hal ini berarti model yang lebih cocok digunakan adalah QDA.
Namun pada tutorial ini kita akan mencoba menggunakan keduanya (LDA dan QDA) dan akan kita bandingkan menggunakan metric yang lain.
Menyiapkan Pembagian Data
K-fold Cross Validation
K-fold Cross Validation adalah prosedur pengambilan sampel berulang yang melibatkan pembagian dataset menjadi K bagian yang sama besar atau hampir sama besar, yang disebut fold (lipatan). Ide dari metode ini adalah untuk training dan testing model klasifikasi sebanyak K kali, menggunakan fold(lipatan) yang berbeda sebagai testing data dalam setiap ulangan sambil menggunakan lipatan K-1 yang lain sebagai training data.
Pemilihan nilai K tergantung pada ukuran dataset dan trade-off antara waktu komputasi dan bias validasi. Nilai umum untuk K adalah 5 dan 10, tetapi kita dapat bereksperimen dengan nilai yang berbeda untuk menemukan keseimbangan terbaik untuk masalah spesifik.
set.seed(345)
<- vfold_cv(df, v = 10,strata = "Type" ) folds
Mendefinisikan Model
Linear Discriminant
<- discrim_linear()%>%
disc_linear set_engine(engine = "MASS") %>%
set_mode("classification")
Quadratic Discriminant
<- discrim_quad() %>%
disc_quad set_engine(engine = "MASS") %>%
set_mode("classification")
Mendefinisikan praproses data
Pendefinisikan praproses data dapat dilakukan dengan fungsi recipe
.
<- recipe(Type~.,data =df)
basic_recipe basic_recipe
── Recipe ──────────────────────────────────────────────────────────────────────
── Inputs
Number of variables by role
outcome: 1
predictor: 13
Menggabungkan tahap praproses data dan model
<- list(disc_linear=disc_linear,
models disc_quad =disc_quad)
<- list(basic_recipe=basic_recipe)
preproc <- workflow_set(preproc = preproc,models = models,
wf_set cross = TRUE)
Training Models
<- workflow_map(wf_set,
train_models fn="fit_resamples",
resamples = folds,
control=control_resamples(save_workflow = TRUE),
metrics=metric_set(sensitivity,
specificity,
bal_accuracy,
f_meas),seed = 2123)
Evaluasi Model
autoplot(train_models,type="wflow_id",
rank_metric = "bal_accuracy")+
scale_y_continuous(limits = c(0,1)) +
theme_lares() +
# memindahkan legend ke bawah
theme(legend.position = "bottom")+
# legend menjadi 2 baris
guides(color = guide_legend(nrow = 2))
rank_results(train_models,rank_metric = "bal_accuracy") %>%
filter(.metric=="bal_accuracy") %>%
::select(wflow_id,.metric,mean,std_err,rank) dplyr
rank_results(train_models,rank_metric = "f_meas") %>%
filter(.metric=="f_meas") %>%
::select(wflow_id,.metric,mean,std_err,rank) dplyr
Menentukan Model terbaik dari Training
<- fit_best(x = train_models,metric = "bal_accuracy")
mod_best1 mod_best1
══ Workflow [trained] ══════════════════════════════════════════════════════════
Preprocessor: Recipe
Model: discrim_linear()
── Preprocessor ────────────────────────────────────────────────────────────────
0 Recipe Steps
── Model ───────────────────────────────────────────────────────────────────────
Call:
lda(..y ~ ., data = data)
Prior probabilities of groups:
1 2 3
0.3314607 0.3988764 0.2696629
Group means:
Alcohol Malic Ash Alcalinity Magnesium Phenols Flavanoids
1 13.74475 2.010678 2.455593 17.03729 106.3390 2.840169 2.9823729
2 12.27873 1.932676 2.244789 20.23803 94.5493 2.258873 2.0808451
3 13.15375 3.333750 2.437083 21.41667 99.3125 1.678750 0.7814583
Nonflavanoids Proanthocyanins Color Hue Dilution Proline
1 0.290000 1.899322 5.528305 1.0620339 3.157797 1115.7119
2 0.363662 1.630282 3.086620 1.0562817 2.785352 519.5070
3 0.447500 1.153542 7.396250 0.6827083 1.683542 629.8958
Coefficients of linear discriminants:
LD1 LD2
Alcohol -0.403399781 0.8717930699
Malic 0.165254596 0.3053797325
Ash -0.369075256 2.3458497486
Alcalinity 0.154797889 -0.1463807654
Magnesium -0.002163496 -0.0004627565
Phenols 0.618052068 -0.0322128171
Flavanoids -1.661191235 -0.4919980543
Nonflavanoids -1.495818440 -1.6309537953
Proanthocyanins 0.134092628 -0.3070875776
Color 0.355055710 0.2532306865
Hue -0.818036073 -1.5156344987
Dilution -1.157559376 0.0511839665
Proline -0.002691206 0.0028529846
Proportion of trace:
LD1 LD2
0.6875 0.3125
Interpretasi Model Terbaik
<- mod_best1 %>%
coefficient extract_fit_engine() %>%
coef() %>%
as.data.frame() %>%
rownames_to_column(var = "Predictors")
coefficient
## custom function
<-function(x,names){
lda_equation str_c(str_c("(",x,")",
collapse = "+"
names,
)
) }
%>%
coefficient mutate(across(where(is.numeric),function(x) round(x,digits = 2))) %>%
summarise(D1=lda_equation(LD1,Predictors),
D2=lda_equation(LD2,Predictors)) %>%
pivot_longer(c(D1,D2),names_to = "discriminant",
values_to="equation"
)
## custom function
<-function(object,response,data){
autoplot.lda%>%
object predict(newdata = data) %>%
::extract2("x") %>%
magrittras_tibble() %>%
bind_cols(data %>% dplyr::select(all_of(response))) %>%
ggscatter(x="LD1",y="LD2",color = response)
}
%>%
mod_best1 extract_fit_engine() %>%
autoplot(response="Type",data=df)
Prediksi Data baru
Berikut kita generate data baru dummy
set.seed(1234)
<- df %>%
data_baru slice_sample(n = 2,by = Type) %>%
::select(-Type)
dplyr data_baru
<- mod_best1%>%
pred_data_baru1 predict(new_data = data_baru)
pred_data_baru1