library(nnet)       # multinom()
library(car)        # vif()
library(tidyverse)  # data wrangling
library(ggplot2)    # visualisasi
library(corrplot)   # plot korelasi
library(caret)      # confusionMatrix

Load Data

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

Feature Selection

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 Asumsi

1. Uji Independensi

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.


2. Multikolinearitas

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.


3. Deteksi Outlier

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.


Pemodelan Regresi Logistik Multinomial

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:

  • Logit 1: membandingkan peluang Enrolled terhadap Dropout (referensi)
  • Logit 2: membandingkan peluang Graduate terhadap Dropout (referensi)

Masing-masing persamaan memiliki koefisien β tersendiri untuk setiap variabel prediktor.


Pengujian Signifikansi Parameter

4. Uji Serentak (Likelihood Ratio Test)

Uji serentak dilakukan untuk mengetahui apakah secara bersama-sama variabel prediktor berpengaruh signifikan terhadap variabel Target.

Hipotesis:

  • H₀ : β₁ = β₂ = … = βₖ = 0 (tidak ada variabel prediktor yang berpengaruh)
  • H₁ : minimal ada satu βᵢ ≠ 0
# 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.


5. Uji Parsial (Wald Test)

Uji parsial dilakukan untuk mengetahui signifikansi masing-masing parameter secara individual menggunakan statistik uji Wald.

Hipotesis:

  • H₀ : βᵢ = 0
  • H₁ : βᵢ ≠ 0
# 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

6. Goodness of Fit

Uji kesesuaian model dilakukan untuk mengetahui apakah model yang terbentuk sudah sesuai dengan data observasi.

Hipotesis:

  • H₀ : Model sesuai (tidak ada perbedaan signifikan antara observasi dan prediksi)
  • H₁ : Model tidak sesuai
# 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 dan Interpretasi

7. Odds Ratio

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:

  • OR > 1: variabel prediktor meningkatkan kecenderungan masuk ke kelas tersebut dibandingkan Dropout
  • OR < 1: variabel prediktor menurunkan kecenderungan tersebut
  • OR = 1: variabel prediktor tidak berpengaruh

Confidence interval (CI) 95% yang tidak mencakup angka 1 mengindikasikan bahwa OR tersebut signifikan secara statistik.


Evaluasi Model

8. Confusion Matrix dan Akurasi

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.


Kesimpulan

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.