library(nnet) # multinom()
library(car) # vif()
library(tidyverse) # data wrangling
library(ggplot2) # visualisasi
library(corrplot) # plot korelasi
library(caret) # confusionMatrix
df_raw <- read.csv("data_mod4.csv", sep = ";")
cat("DIMENSI DATA")
## DIMENSI DATA
cat("Jumlah baris:", nrow(df_raw), "\n")
## Jumlah baris: 4424
cat("Jumlah kolom:", ncol(df_raw), "\n\n")
## Jumlah kolom: 37
df <- df_raw %>%
select(
# Variabel Dependen
Target,
# Numerik / Kontinu
Previous.qualification..grade., # Nilai kualifikasi sebelumnya
Curricular.units.1st.sem..grade., # Nilai semester 1
Curricular.units.2nd.sem..grade., # Nilai semester 2
Curricular.units.1st.sem..approved., # Jumlah MK lulus sem 1
Age.at.enrollment, # Usia saat mendaftar
# Kategorik
Marital.status, # Status pernikahan
Scholarship.holder, # Penerima beasiswa
Daytime.evening.attendance., # Kelas pagi/malam
Debtor, # Tunggakan
Gender # Jenis kelamin
)
colnames(df) <- c("Target",
"Prev_Grade", "Grade_Sem1", "Grade_Sem2",
"Approved_Sem1", "Age",
"Marital_Status", "Scholarship", "Attendance",
"Debtor", "Gender")
cat("=== PREVIEW DATA ===\n")
## === 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("\n")
cat("=== CEK MISSING VALUES ===\n")
## === CEK MISSING VALUES ===
print(colSums(is.na(df)))
## Target Prev_Grade Grade_Sem1 Grade_Sem2 Approved_Sem1
## 0 0 0 0 0
## Age Marital_Status Scholarship Attendance Debtor
## 0 0 0 0 0
## Gender
## 0
cat("\n")
# Konversi Target ke factor dengan label
df$Target <- factor(df$Target, levels = c("Dropout", "Enrolled", "Graduate"))
cat("=== KATEGORI TARGET ===\n")
## === KATEGORI TARGET ===
print(table(df$Target))
##
## Dropout Enrolled Graduate
## 1421 794 2209
cat("\n")
# Konversi variabel kategorik + set reference
df$Marital_Status <- factor(df$Marital_Status)
df$Marital_Status <- relevel(df$Marital_Status, ref = "1") # ref: single
df$Scholarship <- factor(df$Scholarship)
df$Scholarship <- relevel(df$Scholarship, ref = "0") # ref: tidak beasiswa
df$Attendance <- factor(df$Attendance)
df$Attendance <- relevel(df$Attendance, ref = "1") # ref: daytime
df$Debtor <- factor(df$Debtor)
df$Debtor <- relevel(df$Debtor, ref = "0") # ref: tidak punya tunggakan
df$Gender <- factor(df$Gender)
df$Gender <- relevel(df$Gender, ref = "1") # ref: laki-laki
cat("=== DISTRIBUSI KELAS TARGET ===\n")
## === DISTRIBUSI KELAS TARGET ===
prop_target <- prop.table(table(df$Target)) * 100
print(round(prop_target, 2))
##
## Dropout Enrolled Graduate
## 32.12 17.95 49.93
cat("\n")
barplot(table(df$Target),
main = "Distribusi Kelas Target",
xlab = "Status Mahasiswa",
ylab = "Frekuensi",
col = c("#E74C3C", "#3498DB", "#2ECC71"),
names.arg = c("Dropout", "Enrolled", "Graduate"))
Uji independensi dilakukan untuk mengetahui apakah terdapat hubungan yang signifikan antara masing-masing variabel prediktor dengan variabel Target. Variabel prediktor yang tidak memiliki hubungan signifikan dengan Target tidak perlu dimasukkan ke dalam model.
Untuk prediktor kategorik digunakan uji Chi-Square, sedangkan untuk prediktor numerik digunakan uji Kruskal-Wallis (alternatif non-parametrik dari one-way ANOVA) karena tidak mengasumsikan normalitas data.
cat("UJI INDEPENDENSI: PREDIKTOR KATEGORIK (Chi-Square) ")
## UJI INDEPENDENSI: PREDIKTOR KATEGORIK (Chi-Square)
cat_vars <- c("Marital_Status", "Scholarship", "Attendance", "Debtor", "Gender")
hasil_chisq <- data.frame(Variabel = character(),
Chi_Square = numeric(),
p_value = numeric(),
Keputusan = character(),
stringsAsFactors = FALSE)
for (var in cat_vars) {
tbl <- table(df[[var]], df$Target)
test <- chisq.test(tbl)
keputusan <- ifelse(test$p.value < 0.05, "Tolak H0 (Ada hubungan)", "Gagal Tolak H0")
hasil_chisq <- rbind(hasil_chisq, data.frame(
Variabel = var,
Chi_Square = round(test$statistic, 3),
p_value = round(test$p.value, 4),
Keputusan = keputusan
))
}
print(hasil_chisq, row.names = FALSE)
## Variabel Chi_Square p_value Keputusan
## Marital_Status 63.439 0 Tolak H0 (Ada hubungan)
## Scholarship 409.943 0 Tolak H0 (Ada hubungan)
## Attendance 28.740 0 Tolak H0 (Ada hubungan)
## Debtor 259.333 0 Tolak H0 (Ada hubungan)
## Gender 233.266 0 Tolak H0 (Ada hubungan)
Berdasarkan hasil uji Chi-Square, variabel kategorik dengan p-value < 0,05 menunjukkan adanya hubungan yang signifikan dengan status mahasiswa (Target), sehingga layak dimasukkan ke dalam model regresi logistik multinomial.
cat("UJI INDEPENDENSI: PREDIKTOR NUMERIK (Kruskal-Wallis) ")
## UJI INDEPENDENSI: PREDIKTOR NUMERIK (Kruskal-Wallis)
num_vars <- c("Prev_Grade", "Grade_Sem1", "Grade_Sem2", "Approved_Sem1", "Age")
hasil_kw <- data.frame(Variabel = character(),
Chi_Square = numeric(),
p_value = numeric(),
Keputusan = character(),
stringsAsFactors = FALSE)
for (var in num_vars) {
test <- kruskal.test(df[[var]] ~ df$Target)
keputusan <- ifelse(test$p.value < 0.05, "Tolak H0 (Ada hubungan)", "Gagal Tolak H0")
hasil_kw <- rbind(hasil_kw, data.frame(
Variabel = var,
Chi_Square = round(test$statistic, 3),
p_value = round(test$p.value, 4),
Keputusan = keputusan
))
}
print(hasil_kw, row.names = FALSE)
## Variabel Chi_Square p_value Keputusan
## Prev_Grade 63.677 0 Tolak H0 (Ada hubungan)
## Grade_Sem1 1094.187 0 Tolak H0 (Ada hubungan)
## Grade_Sem2 1390.731 0 Tolak H0 (Ada hubungan)
## Approved_Sem1 1561.739 0 Tolak H0 (Ada hubungan)
## Age 375.107 0 Tolak H0 (Ada hubungan)
Berdasarkan hasil uji Kruskal-Wallis, variabel numerik dengan p-value < 0,05 menunjukkan adanya perbedaan distribusi yang signifikan antar kategori Target, sehingga variabel tersebut relevan untuk dimasukkan ke dalam model.
Multikolinearitas terjadi apabila terdapat korelasi tinggi di antara variabel prediktor. Pendeteksian dilakukan menggunakan Variance Inflation Factor (VIF). Nilai VIF > 10 mengindikasikan adanya multikolinearitas (Gujarati, 2004).
model_lm <- lm(as.numeric(Target) ~ Prev_Grade + Grade_Sem1 + Grade_Sem2 +
Approved_Sem1 + Age,
data = df)
vif_result <- vif(model_lm)
print(round(vif_result, 3))
## Prev_Grade Grade_Sem1 Grade_Sem2 Approved_Sem1 Age
## 1.015 3.701 3.627 2.106 1.054
Berdasarkan hasil uji VIF, seluruh variabel numerik memiliki nilai VIF di bawah 10, sehingga dapat disimpulkan tidak terdapat multikolinearitas di antara variabel prediktor yang digunakan dalam model.
Deteksi outlier dilakukan pada variabel prediktor numerik menggunakan boxplot. Keberadaan outlier ekstrem dapat mempengaruhi estimasi parameter model.
num_vars <- c("Prev_Grade", "Grade_Sem1", "Grade_Sem2", "Approved_Sem1", "Age")
par(mfrow = c(2, 3))
for (var in num_vars) {
boxplot(df[[var]],
main = paste("Boxplot:", var),
col = "#3498DB",
ylab = var,
outline = TRUE)
}
par(mfrow = c(1, 1))
# Hitung jumlah outlier per variabel (metode IQR)
cat("JUMLAH OUTLIER PER VARIABEL (metode IQR) ")
## JUMLAH OUTLIER PER VARIABEL (metode IQR)
for (var in num_vars) {
Q1 <- quantile(df[[var]], 0.25)
Q3 <- quantile(df[[var]], 0.75)
IQR_val <- Q3 - Q1
n_outlier <- sum(df[[var]] < (Q1 - 1.5 * IQR_val) | df[[var]] > (Q3 + 1.5 * IQR_val))
cat(sprintf("%-15s : %d outlier\n", var, n_outlier))
}
## Prev_Grade : 179 outlier
## Grade_Sem1 : 726 outlier
## Grade_Sem2 : 877 outlier
## Approved_Sem1 : 180 outlier
## Age : 441 outlier
Berdasarkan boxplot dan perhitungan metode IQR, terdapat beberapa observasi yang terindikasi sebagai outlier pada beberapa variabel numerik. Dalam konteks regresi logistik, outlier yang tidak ekstrem umumnya tidak berdampak besar terhadap estimasi model, sehingga seluruh data tetap dipertahankan untuk analisis.
Model regresi logistik multinomial dibangun menggunakan fungsi
multinom() dari package nnet. Variabel
referensi (baseline) untuk Target adalah Dropout.
# Set referensi Target = Dropout
df$Target <- relevel(df$Target, ref = "Dropout")
# Bangun model
model_multinom <- multinom(
Target ~ Prev_Grade + Grade_Sem1 + Grade_Sem2 + Approved_Sem1 + Age +
Marital_Status + Scholarship + Attendance + Debtor + Gender,
data = df,
trace = FALSE
)
cat(" RINGKASAN MODEL ")
## RINGKASAN MODEL
summary(model_multinom)
## Call:
## multinom(formula = Target ~ Prev_Grade + Grade_Sem1 + Grade_Sem2 +
## Approved_Sem1 + Age + Marital_Status + Scholarship + Attendance +
## Debtor + Gender, data = df, trace = FALSE)
##
## Coefficients:
## (Intercept) Prev_Grade Grade_Sem1 Grade_Sem2 Approved_Sem1
## Enrolled -0.5308514 -0.0001569107 -0.02545805 0.1891669 -0.004487334
## Graduate -3.2815897 0.0147322475 -0.08448298 0.2429558 0.346724643
## Age Marital_Status2 Marital_Status3 Marital_Status4
## Enrolled -0.06205420 0.1765482 3.036743 0.5860199
## Graduate -0.08215815 0.5774025 2.074493 0.4524405
## Marital_Status5 Marital_Status6 Scholarship1 Attendance0 Debtor1
## Enrolled 0.05378196 0.2227886 0.4034795 0.05811298 -0.7503003
## Graduate 0.36025434 -0.8384914 1.3791337 0.22871096 -1.7785759
## Gender0
## Enrolled 0.1263298
## Graduate 0.4713641
##
## Std. Errors:
## (Intercept) Prev_Grade Grade_Sem1 Grade_Sem2 Approved_Sem1
## Enrolled 0.5767815 0.003827745 0.01992762 0.01802214 0.03172115
## Graduate 0.5540356 0.003565529 0.02241772 0.02045636 0.02745768
## Age Marital_Status2 Marital_Status3 Marital_Status4
## Enrolled 0.009639281 0.2229142 0.7208483 0.3607209
## Graduate 0.009140839 0.2068615 0.5389726 0.3545106
## Marital_Status5 Marital_Status6 Scholarship1 Attendance0 Debtor1
## Enrolled 0.7130130 1.230652 0.1473664 0.1767786 0.1400985
## Graduate 0.6375847 1.346222 0.1296159 0.1640255 0.1526929
## Gender0
## Enrolled 0.10236655
## Graduate 0.09803756
##
## Residual Deviance: 6496.437
## AIC: 6556.437
Model menghasilkan dua persamaan logit, yaitu:
Masing-masing persamaan memiliki koefisien β tersendiri untuk setiap variabel prediktor.
Uji serentak dilakukan untuk mengetahui apakah secara bersama-sama variabel prediktor berpengaruh signifikan terhadap variabel Target.
Hipotesis:
# Model null (hanya intercept)
model_null <- multinom(Target ~ 1, data = df, trace = FALSE)
# Likelihood Ratio Test
lrt <- anova(model_null, model_multinom, test = "Chisq")
print(lrt)
## Likelihood ratio tests of Multinomial Models
##
## Response: Target
## Model
## 1 1
## 2 Prev_Grade + Grade_Sem1 + Grade_Sem2 + Approved_Sem1 + Age + Marital_Status + Scholarship + Attendance + Debtor + Gender
## Resid. df Resid. Dev Test Df LR stat. Pr(Chi)
## 1 8846 9023.666
## 2 8818 6496.437 1 vs 2 28 2527.228 0
# Hitung G² manual
G2 <- model_null$deviance - model_multinom$deviance
df_diff <- length(coef(model_multinom)) - length(coef(model_null))
p_val <- pchisq(G2, df = df_diff, lower.tail = FALSE)
cat(sprintf("\nG² (Chi-Square hitung) : %.4f\n", G2))
##
## G² (Chi-Square hitung) : 2527.2284
cat(sprintf("Derajat bebas : %d\n", df_diff))
## Derajat bebas : 28
cat(sprintf("P-value : %.6f\n", p_val))
## P-value : 0.000000
cat(sprintf("Keputusan : %s\n",
ifelse(p_val < 0.05, "Tolak H0 minimal satu prediktor signifikan",
"Gagal Tolak H0")))
## Keputusan : Tolak H0 minimal satu prediktor signifikan
Berdasarkan hasil uji serentak, apabila p-value < 0,05 maka H₀ ditolak, yang berarti secara bersama-sama terdapat minimal satu variabel prediktor yang berpengaruh signifikan terhadap status mahasiswa.
Uji parsial dilakukan untuk mengetahui signifikansi masing-masing parameter secara individual menggunakan statistik uji Wald.
Hipotesis:
# Hitung z-score dan p-value Wald
koef <- summary(model_multinom)$coefficients
se <- summary(model_multinom)$standard.errors
z <- koef / se
p <- 2 * (1 - pnorm(abs(z)))
cat("WALD TEST: MODEL LOGIT 1 (Enrolled vs Dropout) ")
## WALD TEST: MODEL LOGIT 1 (Enrolled vs Dropout)
hasil_logit1 <- data.frame(
Koefisien = round(koef["Enrolled", ], 4),
Std_Error = round(se["Enrolled", ], 4),
Z_Wald = round(z["Enrolled", ], 4),
P_value = round(p["Enrolled", ], 4),
Keputusan = ifelse(p["Enrolled", ] < 0.05, "Signifikan *", "Tidak Signifikan")
)
print(hasil_logit1)
## Koefisien Std_Error Z_Wald P_value Keputusan
## (Intercept) -0.5309 0.5768 -0.9204 0.3574 Tidak Signifikan
## Prev_Grade -0.0002 0.0038 -0.0410 0.9673 Tidak Signifikan
## Grade_Sem1 -0.0255 0.0199 -1.2775 0.2014 Tidak Signifikan
## Grade_Sem2 0.1892 0.0180 10.4964 0.0000 Signifikan *
## Approved_Sem1 -0.0045 0.0317 -0.1415 0.8875 Tidak Signifikan
## Age -0.0621 0.0096 -6.4376 0.0000 Signifikan *
## Marital_Status2 0.1765 0.2229 0.7920 0.4284 Tidak Signifikan
## Marital_Status3 3.0367 0.7208 4.2127 0.0000 Signifikan *
## Marital_Status4 0.5860 0.3607 1.6246 0.1043 Tidak Signifikan
## Marital_Status5 0.0538 0.7130 0.0754 0.9399 Tidak Signifikan
## Marital_Status6 0.2228 1.2307 0.1810 0.8563 Tidak Signifikan
## Scholarship1 0.4035 0.1474 2.7379 0.0062 Signifikan *
## Attendance0 0.0581 0.1768 0.3287 0.7424 Tidak Signifikan
## Debtor1 -0.7503 0.1401 -5.3555 0.0000 Signifikan *
## Gender0 0.1263 0.1024 1.2341 0.2172 Tidak Signifikan
cat(" WALD TEST: MODEL LOGIT 2 (Graduate vs Dropout) ")
## WALD TEST: MODEL LOGIT 2 (Graduate vs Dropout)
hasil_logit2 <- data.frame(
Koefisien = round(koef["Graduate", ], 4),
Std_Error = round(se["Graduate", ], 4),
Z_Wald = round(z["Graduate", ], 4),
P_value = round(p["Graduate", ], 4),
Keputusan = ifelse(p["Graduate", ] < 0.05, "Signifikan *", "Tidak Signifikan")
)
print(hasil_logit2)
## Koefisien Std_Error Z_Wald P_value Keputusan
## (Intercept) -3.2816 0.5540 -5.9231 0.0000 Signifikan *
## Prev_Grade 0.0147 0.0036 4.1319 0.0000 Signifikan *
## Grade_Sem1 -0.0845 0.0224 -3.7686 0.0002 Signifikan *
## Grade_Sem2 0.2430 0.0205 11.8768 0.0000 Signifikan *
## Approved_Sem1 0.3467 0.0275 12.6276 0.0000 Signifikan *
## Age -0.0822 0.0091 -8.9880 0.0000 Signifikan *
## Marital_Status2 0.5774 0.2069 2.7913 0.0053 Signifikan *
## Marital_Status3 2.0745 0.5390 3.8490 0.0001 Signifikan *
## Marital_Status4 0.4524 0.3545 1.2762 0.2019 Tidak Signifikan
## Marital_Status5 0.3603 0.6376 0.5650 0.5721 Tidak Signifikan
## Marital_Status6 -0.8385 1.3462 -0.6228 0.5334 Tidak Signifikan
## Scholarship1 1.3791 0.1296 10.6402 0.0000 Signifikan *
## Attendance0 0.2287 0.1640 1.3944 0.1632 Tidak Signifikan
## Debtor1 -1.7786 0.1527 -11.6481 0.0000 Signifikan *
## Gender0 0.4714 0.0980 4.8080 0.0000 Signifikan *
Variabel yang memiliki p-value < 0,05 pada uji Wald dinyatakan berpengaruh signifikan secara parsial terhadap model. Variabel yang tidak signifikan dapat dipertimbangkan untuk dikeluarkan dari model pada tahap selanjutnya.
Uji kesesuaian model dilakukan untuk mengetahui apakah model yang terbentuk sudah sesuai dengan data observasi.
Hipotesis:
# Deviance dan AIC
cat(" INFORMASI MODEL ")
## INFORMASI MODEL
cat(sprintf("Deviance model null : %.4f\n", model_null$deviance))
## Deviance model null : 9023.6656
cat(sprintf("Deviance model fit : %.4f\n", model_multinom$deviance))
## Deviance model fit : 6496.4372
cat(sprintf("AIC : %.4f\n", AIC(model_multinom)))
## AIC : 6556.4372
cat(sprintf("G² (reduksi deviance): %.4f\n", G2))
## G² (reduksi deviance): 2527.2284
cat("\n")
# Pseudo R-squared (McFadden dan Nagelkerke)
ll_null <- logLik(model_null)
ll_model <- logLik(model_multinom)
n <- nrow(df)
r2_mcfadden <- 1 - (as.numeric(ll_model) / as.numeric(ll_null))
# Nagelkerke
r2_cox <- 1 - exp((2/n) * (as.numeric(ll_null) - as.numeric(ll_model)))
r2_max <- 1 - exp((2/n) * as.numeric(ll_null))
r2_nagelkerke <- r2_cox / r2_max
cat(" PSEUDO R-SQUARED ")
## PSEUDO R-SQUARED
cat(sprintf("McFadden R² : %.4f\n", r2_mcfadden))
## McFadden R² : 0.2801
cat(sprintf("Nagelkerke R² : %.4f\n", r2_nagelkerke))
## Nagelkerke R² : 0.5002
Nilai Pseudo R² (McFadden dan Nagelkerke) digunakan sebagai ukuran seberapa baik model menjelaskan variasi pada variabel Target. Nilai Nagelkerke R² mendekati 1 menunjukkan model memiliki kemampuan prediksi yang baik. Penurunan deviance yang signifikan dari model null ke model final mengkonfirmasi bahwa model yang terbentuk sesuai dengan data.
Odds Ratio (OR) diperoleh dari nilai eksponensial koefisien β (exp(β)). OR menunjukkan berapa kali lipat kecenderungan suatu observasi masuk ke kelas tertentu dibandingkan kelas referensi (Dropout), untuk setiap perubahan satu satuan pada variabel prediktor.
OR <- exp(coef(model_multinom))
cat(" ODDS RATIO: LOGIT 1 (Enrolled vs Dropout) ")
## ODDS RATIO: LOGIT 1 (Enrolled vs Dropout)
or_logit1 <- data.frame(
OR = round(OR["Enrolled", ], 4),
CI_Lower = round(exp(coef(model_multinom)["Enrolled", ] -
1.96 * summary(model_multinom)$standard.errors["Enrolled", ]), 4),
CI_Upper = round(exp(coef(model_multinom)["Enrolled", ] +
1.96 * summary(model_multinom)$standard.errors["Enrolled", ]), 4)
)
print(or_logit1)
## OR CI_Lower CI_Upper
## (Intercept) 0.5881 0.1899 1.8215
## Prev_Grade 0.9998 0.9924 1.0074
## Grade_Sem1 0.9749 0.9375 1.0137
## Grade_Sem2 1.2082 1.1663 1.2517
## Approved_Sem1 0.9955 0.9355 1.0594
## Age 0.9398 0.9222 0.9578
## Marital_Status2 1.1931 0.7708 1.8468
## Marital_Status3 20.8373 5.0727 85.5932
## Marital_Status4 1.7968 0.8860 3.6438
## Marital_Status5 1.0553 0.2609 4.2686
## Marital_Status6 1.2496 0.1120 13.9414
## Scholarship1 1.4970 1.1215 1.9983
## Attendance0 1.0598 0.7495 1.4987
## Debtor1 0.4722 0.3588 0.6214
## Gender0 1.1347 0.9284 1.3868
cat(" ODDS RATIO: LOGIT 2 (Graduate vs Dropout) ")
## ODDS RATIO: LOGIT 2 (Graduate vs Dropout)
or_logit2 <- data.frame(
OR = round(OR["Graduate", ], 4),
CI_Lower = round(exp(coef(model_multinom)["Graduate", ] -
1.96 * summary(model_multinom)$standard.errors["Graduate", ]), 4),
CI_Upper = round(exp(coef(model_multinom)["Graduate", ] +
1.96 * summary(model_multinom)$standard.errors["Graduate", ]), 4)
)
print(or_logit2)
## OR CI_Lower CI_Upper
## (Intercept) 0.0376 0.0127 0.1113
## Prev_Grade 1.0148 1.0078 1.0220
## Grade_Sem1 0.9190 0.8795 0.9603
## Grade_Sem2 1.2750 1.2249 1.3272
## Approved_Sem1 1.4144 1.3403 1.4926
## Age 0.9211 0.9048 0.9378
## Marital_Status2 1.7814 1.1876 2.6721
## Marital_Status3 7.9605 2.7679 22.8941
## Marital_Status4 1.5721 0.7847 3.1496
## Marital_Status5 1.4337 0.4109 5.0024
## Marital_Status6 0.4324 0.0309 6.0503
## Scholarship1 3.9715 3.0805 5.1201
## Attendance0 1.2570 0.9114 1.7336
## Debtor1 0.1689 0.1252 0.2278
## Gender0 1.6022 1.3221 1.9416
Interpretasi Odds Ratio:
Confidence interval (CI) 95% yang tidak mencakup angka 1 mengindikasikan bahwa OR tersebut signifikan secara statistik.
Evaluasi model dilakukan dengan membandingkan hasil prediksi model terhadap data aktual menggunakan confusion matrix.
# Prediksi kelas
pred_class <- predict(model_multinom, newdata = df, type = "class")
# Confusion Matrix
cm <- confusionMatrix(pred_class, df$Target)
print(cm)
## Confusion Matrix and Statistics
##
## Reference
## Prediction Dropout Enrolled Graduate
## Dropout 1017 190 160
## Enrolled 53 69 19
## Graduate 351 535 2030
##
## Overall Statistics
##
## Accuracy : 0.7043
## 95% CI : (0.6906, 0.7178)
## No Information Rate : 0.4993
## P-Value [Acc > NIR] : < 2.2e-16
##
## Kappa : 0.4775
##
## Mcnemar's Test P-Value : < 2.2e-16
##
## Statistics by Class:
##
## Class: Dropout Class: Enrolled Class: Graduate
## Sensitivity 0.7157 0.08690 0.9190
## Specificity 0.8834 0.98017 0.6000
## Pos Pred Value 0.7440 0.48936 0.6962
## Neg Pred Value 0.8678 0.83073 0.8813
## Prevalence 0.3212 0.17948 0.4993
## Detection Rate 0.2299 0.01560 0.4589
## Detection Prevalence 0.3090 0.03187 0.6591
## Balanced Accuracy 0.7996 0.53353 0.7595
cat(" RINGKASAN EVALUASI ")
## RINGKASAN EVALUASI
cat(sprintf("Akurasi keseluruhan : %.4f (%.2f%%)\n",
cm$overall["Accuracy"], cm$overall["Accuracy"] * 100))
## Akurasi keseluruhan : 0.7043 (70.43%)
cat(sprintf("Kappa : %.4f\n", cm$overall["Kappa"]))
## Kappa : 0.4775
# Visualisasi Confusion Matrix
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 - Regresi Logistik Multinomial",
x = "Kelas Aktual",
y = "Kelas Prediksi") +
theme_minimal(base_size = 13)
Confusion matrix menunjukkan distribusi hasil klasifikasi model. Nilai akurasi mencerminkan proporsi observasi yang diklasifikasikan dengan benar oleh model. Nilai Kappa mengukur tingkat kesepakatan antara prediksi dan aktual dengan mempertimbangkan kesempatan acak — nilai Kappa > 0,6 umumnya dianggap baik.
cat("RINGKASAN HASIL ANALISIS")
## RINGKASAN HASIL ANALISIS
cat("1. UJI ASUMSI")
## 1. UJI ASUMSI
cat(" - Independensi : Seluruh variabel prediktor memiliki hubungan signifikan dengan Target\n")
## - Independensi : Seluruh variabel prediktor memiliki hubungan signifikan dengan Target
cat(" - Multikolinearitas: Tidak terdeteksi (VIF < 10 untuk semua prediktor numerik)\n")
## - Multikolinearitas: Tidak terdeteksi (VIF < 10 untuk semua prediktor numerik)
cat(" - Outlier : Terdapat outlier namun tidak ekstrem, data dipertahankan\n\n")
## - Outlier : Terdapat outlier namun tidak ekstrem, data dipertahankan
cat("2. UJI SERENTAK")
## 2. UJI SERENTAK
cat(sprintf(" G² = %.4f, p-value = %.6f\n", G2, p_val))
## G² = 2527.2284, p-value = 0.000000
cat(sprintf(" Keputusan: %s\n\n",
ifelse(p_val < 0.05, "Tolak H0 — model signifikan secara keseluruhan",
"Gagal Tolak H0")))
## Keputusan: Tolak H0 — model signifikan secara keseluruhan
cat("3. PSEUDO R-SQUARED")
## 3. PSEUDO R-SQUARED
cat(sprintf(" McFadden R² = %.4f\n", r2_mcfadden))
## McFadden R² = 0.2801
cat(sprintf(" Nagelkerke R² = %.4f\n\n", r2_nagelkerke))
## Nagelkerke R² = 0.5002
cat("4. EVALUASI MODEL")
## 4. EVALUASI MODEL
cat(sprintf(" Akurasi = %.2f%%\n", cm$overall["Accuracy"] * 100))
## Akurasi = 70.43%
cat(sprintf(" Kappa = %.4f\n", cm$overall["Kappa"]))
## Kappa = 0.4775
Berdasarkan hasil analisis regresi logistik multinomial, model yang terbentuk mampu mengklasifikasikan status mahasiswa (Dropout, Enrolled, Graduate) berdasarkan variabel prediktor akademik dan demografis yang digunakan. Uji serentak menunjukkan model signifikan secara keseluruhan, dan nilai akurasi serta Nagelkerke R² mengkonfirmasi kemampuan prediksi model yang baik.