library(MASS) # lda()
library(tidyverse) # data wrangling
library(ggplot2) # visualisasi
library(car) # leveneTest()
library(MVN) # uji normalitas multivariat (mardia)
library(heplots) # boxM() — Box's M test
library(caret) # confusionMatrix
library(knitr) # kable
df_raw <- read.csv("data_mod4.csv", sep = ";")
cat("Dimensi data:", nrow(df_raw), "baris x", ncol(df_raw), "kolom\n")
## Dimensi data: 4424 baris x 37 kolom
LDA hanya dapat menggunakan variabel prediktor numerik secara langsung. Variabel kategorik dikonversi menjadi numerik (dummy/integer).
df <- df_raw %>%
dplyr::select(
Target,
Previous.qualification..grade.,
Curricular.units.1st.sem..grade.,
Curricular.units.2nd.sem..grade.,
Curricular.units.1st.sem..approved.,
Age.at.enrollment,
Marital.status,
Scholarship.holder,
Daytime.evening.attendance.,
Debtor,
Gender
)
colnames(df) <- c("Target",
"Prev_Grade", "Grade_Sem1", "Grade_Sem2",
"Approved_Sem1", "Age",
"Marital_Status", "Scholarship", "Attendance",
"Debtor", "Gender")
# Konversi Target ke factor
df$Target <- factor(df$Target, levels = c("Dropout", "Enrolled", "Graduate"))
cat("PREVIEW DATA ")
## PREVIEW DATA
print(head(df))
## Target Prev_Grade Grade_Sem1 Grade_Sem2 Approved_Sem1 Age Marital_Status
## 1 Dropout 122.0 0.00000 0.00000 0 20 1
## 2 Graduate 160.0 14.00000 13.66667 6 19 1
## 3 Dropout 122.0 0.00000 0.00000 0 19 1
## 4 Graduate 122.0 13.42857 12.40000 6 20 1
## 5 Graduate 100.0 12.33333 13.00000 5 45 2
## 6 Graduate 133.1 11.85714 11.50000 5 50 2
## Scholarship Attendance Debtor Gender
## 1 0 1 0 1
## 2 0 1 0 1
## 3 0 1 0 1
## 4 0 1 0 0
## 5 0 0 0 0
## 6 0 0 1 1
cat("DISTRIBUSI TARGET ")
## DISTRIBUSI TARGET
print(table(df$Target))
##
## Dropout Enrolled Graduate
## 1421 794 2209
cat("PROPORSI TARGET ")
## PROPORSI TARGET
print(round(prop.table(table(df$Target)) * 100, 2))
##
## Dropout Enrolled Graduate
## 32.12 17.95 49.93
barplot(table(df$Target),
main = "Distribusi Kelas Target",
xlab = "Status Mahasiswa",
ylab = "Frekuensi",
col = c("#E74C3C", "#3498DB", "#2ECC71"),
names.arg = c("Dropout", "Enrolled", "Graduate"))
Analisis Diskriminan Linear memiliki tiga asumsi utama yang harus dipenuhi:
Uji Mardia digunakan untuk mendeteksi normalitas multivariat berdasarkan skewness dan kurtosis multivariat.
Hipotesis:
num_vars <- c("Prev_Grade", "Grade_Sem1", "Grade_Sem2", "Approved_Sem1", "Age")
hasil_mardia <- data.frame(
Kelas = character(),
Skewness = numeric(),
p_Skew = numeric(),
Kurtosis = numeric(),
p_Kurt = numeric(),
Keputusan = character(),
stringsAsFactors = FALSE
)
for (kelas in levels(df$Target)) {
subset_data <- df[df$Target == kelas, num_vars]
mv_test <- mvn(data = subset_data, mvn_test = "mardia")
tbl <- mv_test$multivariate_normality
skew_stat <- as.numeric(tbl$Statistic[1])
kurt_stat <- as.numeric(tbl$Statistic[2])
skew_p_num <- as.numeric(tbl$p.value[1])
kurt_p_num <- as.numeric(tbl$p.value[2])
keputusan <- ifelse(
is.na(skew_p_num) | is.na(kurt_p_num) |
skew_p_num < 0.05 | kurt_p_num < 0.05,
"Tidak Normal Multivariat",
"Normal Multivariat"
)
hasil_mardia <- rbind(hasil_mardia, data.frame(
Kelas = kelas,
Skewness = round(skew_stat, 4),
p_Skew = round(skew_p_num, 4),
Kurtosis = round(kurt_stat, 4),
p_Kurt = round(kurt_p_num, 4),
Keputusan = keputusan
))
}
print(hasil_mardia, row.names = FALSE)
## Kelas Skewness p_Skew Kurtosis p_Kurt Keputusan
## Dropout 3065.634 NA 36.875 NA Tidak Normal Multivariat
## Enrolled 2505.339 NA 58.361 NA Tidak Normal Multivariat
## Graduate 13608.783 NA 177.627 NA Tidak Normal Multivariat
Apabila p-value uji Mardia < 0,05 maka asumsi normalitas multivariat tidak terpenuhi. Meskipun demikian, LDA dikenal cukup robust terhadap pelanggaran normalitas multivariat apabila ukuran sampel tiap kelas cukup besar (n > 30), sehingga analisis dapat tetap dilanjutkan.
Box’s M test digunakan untuk menguji apakah matriks kovarians antar kelas bersifat homogen — asumsi kunci LDA.
Hipotesis:
boxM_result <- boxM(df[, num_vars], df$Target)
print(boxM_result)
##
## Box's M-test for Homogeneity of Covariance Matrices
##
## data: df[, num_vars] by df$Target
## Chi-Sq (approx.) = 4806.255, df = 30, p-value = < 2.2e-16
cat(sprintf("\nChi-Square hitung : %.4f\n", boxM_result$statistic))
##
## Chi-Square hitung : 4806.2553
cat(sprintf("Derajat bebas : %d\n", boxM_result$parameter))
## Derajat bebas : 30
cat(sprintf("P-value : %.6f\n", boxM_result$p.value))
## P-value : 0.000000
cat(sprintf("Keputusan : %s\n",
ifelse(boxM_result$p.value < 0.05,
"Tolak H0 — Matriks kovarians tidak homogen",
"Gagal Tolak H0 — Matriks kovarians homogen")))
## Keputusan : Tolak H0 — Matriks kovarians tidak homogen
Apabila Box’s M test signifikan (p-value < 0,05), asumsi homogenitas matriks kovarians tidak terpenuhi. Dalam kasus ini, Quadratic Discriminant Analysis (QDA) lebih tepat digunakan sebagai alternatif LDA karena QDA tidak mengasumsikan kesamaan matriks kovarians antar kelas. Namun untuk tujuan pembelajaran, LDA tetap dijalankan.
model_lm <- lm(as.numeric(Target) ~ Prev_Grade + Grade_Sem1 + Grade_Sem2 +
Approved_Sem1 + Age, data = df)
vif_result <- vif(model_lm)
cat("NILAI VIF ")
## NILAI VIF
print(round(vif_result, 3))
## Prev_Grade Grade_Sem1 Grade_Sem2 Approved_Sem1 Age
## 1.015 3.701 3.627 2.106 1.054
cat(sprintf("Kesimpulan: %s\n",
ifelse(all(vif_result < 10),
"Tidak ada multikolinearitas (semua VIF < 10)",
"Terdeteksi multikolinearitas pada beberapa variabel")))
## Kesimpulan: Tidak ada multikolinearitas (semua VIF < 10)
Nilai VIF > 10 mengindikasikan adanya multikolinearitas yang perlu ditangani. Jika semua VIF < 10, asumsi ini terpenuhi dan seluruh prediktor dapat dimasukkan ke dalam model.
Model LDA dibangun menggunakan fungsi lda() dari package
MASS. LDA mencari kombinasi linear dari variabel prediktor
yang memaksimalkan separasi antar kelas.
model_lda <- lda(
Target ~ Prev_Grade + Grade_Sem1 + Grade_Sem2 + Approved_Sem1 + Age +
Marital_Status + Scholarship + Attendance + Debtor + Gender,
data = df
)
cat("HASIL MODEL LDA ")
## HASIL MODEL LDA
cat("Prior Probabilities (Proporsi tiap kelas) ")
## Prior Probabilities (Proporsi tiap kelas)
print(round(model_lda$prior, 4))
## Dropout Enrolled Graduate
## 0.3212 0.1795 0.4993
cat("Group Means (Rata-rata tiap kelas per prediktor) ")
## Group Means (Rata-rata tiap kelas per prediktor)
print(round(model_lda$means, 4))
## Prev_Grade Grade_Sem1 Grade_Sem2 Approved_Sem1 Age Marital_Status
## Dropout 131.1141 7.2567 5.8993 2.5517 26.0690 1.2611
## Enrolled 131.2084 11.1253 11.1174 4.3186 22.3690 1.1524
## Graduate 134.0827 12.6437 12.6973 6.2322 21.7836 1.1349
## Scholarship Attendance Debtor Gender
## Dropout 0.0943 0.8543 0.2196 0.4933
## Enrolled 0.1637 0.9055 0.1134 0.3866
## Graduate 0.3780 0.9090 0.0457 0.2481
cat("Koefisien Fungsi Diskriminan ")
## Koefisien Fungsi Diskriminan
print(round(model_lda$scaling, 4))
## LD1 LD2
## Prev_Grade 0.0061 0.0187
## Grade_Sem1 -0.0385 -0.0236
## Grade_Sem2 0.1498 -0.1962
## Approved_Sem1 0.1732 0.3081
## Age -0.0392 0.0406
## Marital_Status 0.0776 -0.1429
## Scholarship 0.6846 1.1669
## Attendance -0.1185 -0.0143
## Debtor -0.8902 -0.0871
## Gender -0.2601 -0.3389
cat(" Proporsi Trace (Variansi yang dijelaskan tiap fungsi) ")
## Proporsi Trace (Variansi yang dijelaskan tiap fungsi)
prop_trace <- model_lda$svd^2 / sum(model_lda$svd^2)
for (i in seq_along(prop_trace)) {
cat(sprintf(" LD%d : %.4f (%.2f%%)\n", i, prop_trace[i], prop_trace[i] * 100))
}
## LD1 : 0.9424 (94.24%)
## LD2 : 0.0576 (5.76%)
LDA dengan 3 kelas menghasilkan 2 fungsi diskriminan (LD1 dan LD2), karena jumlah fungsi = min(jumlah kelas − 1, jumlah prediktor). Proporsi trace menunjukkan seberapa besar variansi antar kelas yang dijelaskan oleh masing-masing fungsi diskriminan.
# Proyeksikan data ke ruang diskriminan
lda_pred_train <- predict(model_lda, df)
lda_scores <- as.data.frame(lda_pred_train$x)
lda_scores$Target <- df$Target
# Plot LD1 vs LD2
ggplot(lda_scores, aes(x = LD1, y = LD2, color = Target)) +
geom_point(alpha = 0.4, size = 1.5) +
stat_ellipse(level = 0.90, linewidth = 1) +
scale_color_manual(values = c("#E74C3C", "#3498DB", "#2ECC71")) +
labs(title = "Proyeksi LDA: LD1 vs LD2",
x = "Fungsi Diskriminan 1 (LD1)",
y = "Fungsi Diskriminan 2 (LD2)",
color = "Status Mahasiswa") +
theme_minimal(base_size = 13)
# Distribusi LD1 per kelas
ggplot(lda_scores, aes(x = LD1, fill = Target)) +
geom_density(alpha = 0.5) +
scale_fill_manual(values = c("#E74C3C", "#3498DB", "#2ECC71")) +
labs(title = "Distribusi Skor LD1 per Kelas",
x = "Skor LD1", y = "Densitas",
fill = "Status Mahasiswa") +
theme_minimal(base_size = 13)
Plot LD1 vs LD2 menunjukkan sejauh mana ketiga kelas dapat dipisahkan di ruang diskriminan. Ellips 90% menggambarkan sebaran tiap kelas. Semakin sedikit tumpang tindih antar ellips, semakin baik kemampuan separasi model.
# Prediksi pada data training
pred_class <- lda_pred_train$class
# Confusion Matrix
cm <- confusionMatrix(pred_class, df$Target)
print(cm)
## Confusion Matrix and Statistics
##
## Reference
## Prediction Dropout Enrolled Graduate
## Dropout 917 137 121
## Enrolled 125 108 38
## Graduate 379 549 2050
##
## Overall Statistics
##
## Accuracy : 0.6951
## 95% CI : (0.6813, 0.7086)
## No Information Rate : 0.4993
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.4628
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Statistics by Class:
##
## Class: Dropout Class: Enrolled Class: Graduate
## Sensitivity 0.6453 0.13602 0.9280
## Specificity 0.9141 0.95510 0.5810
## Pos Pred Value 0.7804 0.39852 0.6884
## Neg Pred Value 0.8449 0.83482 0.8900
## Prevalence 0.3212 0.17948 0.4993
## Detection Rate 0.2073 0.02441 0.4634
## Detection Prevalence 0.2656 0.06126 0.6731
## Balanced Accuracy 0.7797 0.54556 0.7545
cat("RINGKASAN EVALUASI ")
## RINGKASAN EVALUASI
cat(sprintf("Akurasi keseluruhan : %.4f (%.2f%%)\n",
cm$overall["Accuracy"], cm$overall["Accuracy"] * 100))
## Akurasi keseluruhan : 0.6951 (69.51%)
cat(sprintf("Kappa : %.4f\n", cm$overall["Kappa"]))
## Kappa : 0.4628
cm_table <- as.data.frame(cm$table)
colnames(cm_table) <- c("Prediksi", "Aktual", "Frekuensi")
ggplot(cm_table, aes(x = Aktual, y = Prediksi, fill = Frekuensi)) +
geom_tile(color = "white") +
geom_text(aes(label = Frekuensi), size = 5, fontface = "bold") +
scale_fill_gradient(low = "#D6EAF8", high = "#1A5276") +
labs(title = "Confusion Matrix - LDA",
x = "Kelas Aktual", y = "Kelas Prediksi") +
theme_minimal(base_size = 13)
prop_trace <- model_lda$svd^2 / sum(model_lda$svd^2)
cat(" RINGKASAN HASIL ANALISIS DISKRIMINAN LINEAR ")
## RINGKASAN HASIL ANALISIS DISKRIMINAN LINEAR
cat("1. UJI ASUMSI\n")
## 1. UJI ASUMSI
cat(" - Normalitas Multivariat : Dievaluasi per kelas menggunakan Uji Mardia\n")
## - Normalitas Multivariat : Dievaluasi per kelas menggunakan Uji Mardia
cat(" - Homogenitas Kovarians : Dievaluasi menggunakan Box's M Test\n")
## - Homogenitas Kovarians : Dievaluasi menggunakan Box's M Test
cat(" - Multikolinearitas : Dievaluasi menggunakan VIF\n\n")
## - Multikolinearitas : Dievaluasi menggunakan VIF
cat("2. FUNGSI DISKRIMINAN\n")
## 2. FUNGSI DISKRIMINAN
cat(sprintf(" Jumlah fungsi diskriminan : %d\n", length(model_lda$svd)))
## Jumlah fungsi diskriminan : 2
for (i in seq_along(prop_trace)) {
cat(sprintf(" LD%d menjelaskan : %.2f%% variansi antar kelas\n",
i, prop_trace[i] * 100))
}
## LD1 menjelaskan : 94.24% variansi antar kelas
## LD2 menjelaskan : 5.76% variansi antar kelas
cat("\n")
cat("3. EVALUASI MODEL\n")
## 3. EVALUASI MODEL
cat(sprintf(" Akurasi : %.2f%%\n", cm$overall["Accuracy"] * 100))
## Akurasi : 69.51%
cat(sprintf(" Kappa : %.4f\n", cm$overall["Kappa"]))
## Kappa : 0.4628
cat(sprintf(" Interpretasi Kappa: %s\n",
ifelse(cm$overall["Kappa"] >= 0.8, "Sangat Baik",
ifelse(cm$overall["Kappa"] >= 0.6, "Baik (Substantial)",
ifelse(cm$overall["Kappa"] >= 0.4, "Sedang (Moderate)",
"Lemah")))))
## Interpretasi Kappa: Sedang (Moderate)
Analisis Diskriminan Linear berhasil membangun fungsi diskriminan yang memisahkan status mahasiswa (Dropout, Enrolled, Graduate) berdasarkan variabel prediktor akademik dan demografis. Fungsi LD1 menjelaskan sebagian besar variansi antar kelas. Nilai akurasi dan Kappa mencerminkan kemampuan model dalam mengklasifikasikan data secara keseluruhan.