Mata Kuliah: Analisis Multivariat
Dosen Pengampu: Ike Fitriyaningsih, M.Si.
Analisis ini menggunakan kombinasi Principal Component Analysis (PCA) dan Ordinal Logistic Regression (OLR) untuk mengklasifikasikan nilai akhir siswa (G3) ke dalam kategori A–F. Data yang digunakan adalah student_performance.csv.
if (!require("MASS")) install.packages("MASS")
## Loading required package: MASS
if (!require("caret")) install.packages("caret")
## Loading required package: caret
## Loading required package: ggplot2
## Loading required package: lattice
if (!require("tidyverse")) install.packages("tidyverse")
## Loading required package: tidyverse
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.4 ✔ readr 2.1.5
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ lubridate 1.9.4 ✔ tibble 3.2.1
## ✔ purrr 1.0.4 ✔ tidyr 1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ✖ purrr::lift() masks caret::lift()
## ✖ dplyr::select() masks MASS::select()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
if (!require("Metrics")) install.packages("Metrics")
## Loading required package: Metrics
##
## Attaching package: 'Metrics'
##
## The following objects are masked from 'package:caret':
##
## precision, recall
library(MASS)
library(caret)
library(tidyverse)
library(Metrics)
Menginstal dan memuat library yang dibutuhkan untuk modeling, pra-pemrosesan data, dan evaluasi performa model.
df <- read.csv("d:/Documents/student_performance.csv")
head(df)
## school sex age address famsize Pstatus Medu Fedu Mjob Fjob reason
## 1 GP F 18 U GT3 A 4 4 at_home teacher course
## 2 GP F 17 U GT3 T 1 1 at_home other course
## 3 GP F 15 U LE3 T 1 1 at_home other other
## 4 GP F 15 U GT3 T 4 2 health services home
## 5 GP F 16 U GT3 T 3 3 other other home
## 6 GP M 16 U LE3 T 4 3 services other reputation
## guardian traveltime studytime failures schoolsup famsup paid activities
## 1 mother 2 2 0 yes no no no
## 2 father 1 2 0 no yes no no
## 3 mother 1 2 0 yes no no no
## 4 mother 1 3 0 no yes no yes
## 5 father 1 2 0 no yes no no
## 6 mother 1 2 0 no yes no yes
## nursery higher internet romantic famrel freetime goout Dalc Walc health
## 1 yes yes no no 4 3 4 1 1 3
## 2 no yes yes no 5 3 3 1 1 3
## 3 yes yes yes no 4 3 2 2 3 3
## 4 yes yes yes yes 3 2 2 1 1 5
## 5 yes yes no no 4 3 2 1 2 5
## 6 yes yes yes no 5 4 2 1 2 5
## absences G1 G2 G3
## 1 4 0 11 11
## 2 2 9 11 11
## 3 6 12 13 12
## 4 0 14 14 14
## 5 0 11 13 13
## 6 6 12 12 13
Dataset yang digunakan berisi informasi mengenai siswa sekolah menengah di Portugal dan performa akademis mereka, khususnya dalam pelajaran Matematika. Setiap baris mewakili satu siswa, dengan berbagai fitur demografis, sosial, dan akademik yang dapat memengaruhi nilai akhir (G3).
df$G3_kategori <- cut(
df$G3,
breaks = c(-1, 9, 11, 13, 15, 21),
labels = c("F", "D", "C", "B", "A"),
ordered_result = TRUE,
include.lowest = TRUE
)
df$G3_kategori <- factor(df$G3_kategori, ordered = TRUE)
df[sapply(df, is.character)] <- lapply(df[sapply(df, is.character)], as.factor)
cat("Distribusi kategori G3:\n")
## Distribusi kategori G3:
print(table(df$G3_kategori))
##
## F D C B A
## 100 201 154 112 82
cat("\nStruktur data setelah konversi karakter ke faktor:\n")
##
## Struktur data setelah konversi karakter ke faktor:
str(df[, sapply(df, is.factor)])
## 'data.frame': 649 obs. of 18 variables:
## $ school : Factor w/ 2 levels "GP","MS": 1 1 1 1 1 1 1 1 1 1 ...
## $ sex : Factor w/ 2 levels "F","M": 1 1 1 1 1 2 2 1 2 2 ...
## $ address : Factor w/ 2 levels "R","U": 2 2 2 2 2 2 2 2 2 2 ...
## $ famsize : Factor w/ 2 levels "GT3","LE3": 1 1 2 1 1 2 2 1 2 1 ...
## $ Pstatus : Factor w/ 2 levels "A","T": 1 2 2 2 2 2 2 1 1 2 ...
## $ Mjob : Factor w/ 5 levels "at_home","health",..: 1 1 1 2 3 4 3 3 4 3 ...
## $ Fjob : Factor w/ 5 levels "at_home","health",..: 5 3 3 4 3 3 3 5 3 3 ...
## $ reason : Factor w/ 4 levels "course","home",..: 1 1 3 2 2 4 2 2 2 2 ...
## $ guardian : Factor w/ 3 levels "father","mother",..: 2 1 2 2 1 2 2 2 2 2 ...
## $ schoolsup : Factor w/ 2 levels "no","yes": 2 1 2 1 1 1 1 2 1 1 ...
## $ famsup : Factor w/ 2 levels "no","yes": 1 2 1 2 2 2 1 2 2 2 ...
## $ paid : Factor w/ 2 levels "no","yes": 1 1 1 1 1 1 1 1 1 1 ...
## $ activities : Factor w/ 2 levels "no","yes": 1 1 1 2 1 2 1 1 1 2 ...
## $ nursery : Factor w/ 2 levels "no","yes": 2 1 2 2 2 2 2 2 2 2 ...
## $ higher : Factor w/ 2 levels "no","yes": 2 2 2 2 2 2 2 2 2 2 ...
## $ internet : Factor w/ 2 levels "no","yes": 1 2 2 2 1 2 2 1 2 2 ...
## $ romantic : Factor w/ 2 levels "no","yes": 1 1 1 2 1 1 1 1 1 1 ...
## $ G3_kategori: Ord.factor w/ 5 levels "F"<"D"<"C"<"B"<..: 2 2 3 4 3 3 3 3 5 3 ...
Nilai akhir siswa (G3) dikonversi menjadi kategori G3_kategori dengan label A (nilai sangat tinggi) hingga F (nilai sangat rendah) menggunakan fungsi cut, untuk memudahkan analisis klasifikasi. Selanjutnya, seluruh kolom bertipe karakter dikonversi menjadi faktor agar dapat diperlakukan sebagai variabel kategorikal dalam proses pemodelan. Distribusi kategori menunjukkan mayoritas siswa berada pada kategori D dan C.
set.seed(123)
train_index <- createDataPartition(df$G3_kategori, p = 0.8, list = FALSE)
train_data <- df[train_index, ]
test_data <- df[-train_index, ]
cat("Jumlah data train:", nrow(train_data), "\n")
## Jumlah data train: 521
cat("Jumlah data test:", nrow(test_data), "\n")
## Jumlah data test: 128
prop.table(table(train_data$G3_kategori))
##
## F D C B A
## 0.1535509 0.3090211 0.2380038 0.1727447 0.1266795
prop.table(table(test_data$G3_kategori))
##
## F D C B A
## 0.156250 0.312500 0.234375 0.171875 0.125000
Data dibagi menjadi 80% data latih (521 siswa) dan 20% data uji (128 siswa) secara acak namun tetap mempertahankan proporsi distribusi kategori nilai akhir (G3_kategori). Proporsi tiap kategori seperti F, D, C, B, dan A pada data latih dan uji tetap seimbang, sehingga model dapat dilatih dan diuji secara adil tanpa bias terhadap kategori tertentu.
numeric_features <- train_data %>%
select(where(is.numeric)) %>%
select(-c(G3, G1, G2))
pca_result <- prcomp(train_data[, names(numeric_features)], center = TRUE, scale. = TRUE)
cat("=== Ringkasan PCA Eksploratif ===\n")
## === Ringkasan PCA Eksploratif ===
print(summary(pca_result))
## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6 PC7
## Standard deviation 1.5044 1.3883 1.1456 1.06568 1.04091 0.96549 0.93794
## Proportion of Variance 0.1741 0.1483 0.1009 0.08736 0.08335 0.07171 0.06767
## Cumulative Proportion 0.1741 0.3224 0.4233 0.51066 0.59401 0.66572 0.73339
## PC8 PC9 PC10 PC11 PC12 PC13
## Standard deviation 0.90878 0.87294 0.80207 0.74444 0.6054 0.56037
## Proportion of Variance 0.06353 0.05862 0.04949 0.04263 0.0282 0.02415
## Cumulative Proportion 0.79692 0.85553 0.90502 0.94765 0.9758 1.00000
plot(pca_result, type = "l", main = "Scree Plot PCA")
PCA dilakukan pada fitur numerik non-target untuk mengeksplorasi seberapa besar variansi yang dapat dijelaskan oleh komponen utama. Plot Scree dan ringkasan PCA menunjukkan bahwa komponen utama pertama (PC1) hingga ketiga (PC3) menyumbang sekitar 42% variasi data, dan hingga komponen ketujuh (PC7) mencakup sekitar 73% variasi total. Sepuluh komponen utama pertama secara keseluruhan telah menjelaskan lebih dari 90% variansi. Grafik scree menampilkan penurunan tajam dari PC1 ke PC3 lalu melandai, yang mengindikasikan bahwa sebagian besar informasi dapat direpresentasikan dengan 3–7 komponen utama. PCA ini membantu mereduksi dimensi data numerik sebelum masuk ke tahap modeling.
preProc <- preProcess(train_data[, names(numeric_features)],
method = c("center", "scale", "pca"),
pcaComp = 10)
train_pca <- predict(preProc, train_data[, names(numeric_features)])
test_pca <- predict(preProc, test_data[, names(numeric_features)])
train_pca$G3_kategori <- train_data$G3_kategori
test_pca$G3_kategori <- test_data$G3_kategori
Kode ini melakukan preprocessing data numerik pada train_data dan test_data dengan menggunakan metode standarisasi (center dan scale) lalu mengaplikasikan Principal Component Analysis (PCA) untuk mereduksi dimensi menjadi 10 komponen utama. Setelah model PCA dibuat dengan fungsi preProcess, data latih dan data uji diproyeksikan ke ruang PCA tersebut menggunakan predict. Kemudian, variabel target G3_kategori ditambahkan kembali ke data hasil transformasi agar siap digunakan untuk modelling selanjutnya.
olr_pca_model <- polr(G3_kategori ~ ., data = train_pca, Hess = TRUE)
olr_pca_preds <- predict(olr_pca_model, newdata = test_pca)
Melatih model Ordinal Logistic Regression (OLR) menggunakan komponen PCA sebagai prediktor.
cat("\n=== Evaluasi OLR dengan PCA ===\n")
##
## === Evaluasi OLR dengan PCA ===
olr_cm <- confusionMatrix(olr_pca_preds, test_pca$G3_kategori)
print(olr_cm)
## Confusion Matrix and Statistics
##
## Reference
## Prediction F D C B A
## F 4 2 0 0 0
## D 12 22 13 10 4
## C 4 14 12 8 5
## B 0 1 2 4 3
## A 0 1 3 0 4
##
## Overall Statistics
##
## Accuracy : 0.3594
## 95% CI : (0.2765, 0.4489)
## No Information Rate : 0.3125
## P-Value [Acc > NIR] : 0.1473
##
## Kappa : 0.1387
##
## Mcnemar's Test P-Value : NA
##
## Statistics by Class:
##
## Class: F Class: D Class: C Class: B Class: A
## Sensitivity 0.20000 0.5500 0.40000 0.18182 0.25000
## Specificity 0.98148 0.5568 0.68367 0.94340 0.96429
## Pos Pred Value 0.66667 0.3607 0.27907 0.40000 0.50000
## Neg Pred Value 0.86885 0.7313 0.78824 0.84746 0.90000
## Prevalence 0.15625 0.3125 0.23438 0.17188 0.12500
## Detection Rate 0.03125 0.1719 0.09375 0.03125 0.03125
## Detection Prevalence 0.04688 0.4766 0.33594 0.07812 0.06250
## Balanced Accuracy 0.59074 0.5534 0.54184 0.56261 0.60714
ordinal_scores <- setNames(0:4, c("F", "D", "C", "B", "A"))
olr_true <- as.numeric(recode(as.character(test_pca$G3_kategori), !!!ordinal_scores))
olr_pred <- as.numeric(recode(as.character(olr_pca_preds), !!!ordinal_scores))
olr_mae <- mae(olr_true, olr_pred)
cat("OLR Mean Absolute Error (MAE):", olr_mae, "\n")
## OLR Mean Absolute Error (MAE): 0.8984375
Kode ini melakukan evaluasi model Ordinal Logistic Regression (OLR) yang diprediksi menggunakan data hasil transformasi PCA. Pertama, dibuat confusion matrix dengan fungsi confusionMatrix() untuk membandingkan prediksi model (olr_pca_preds) terhadap label asli (test_pca$G3_kategori), sehingga dapat dilihat performa klasifikasi dari segi akurasi dan statistik lainnya. Akurasi model tercatat sebesar 35.94%, sedikit lebih baik dari tebakan acak (No Information Rate/NIR sebesar 31.25%).
Selanjutnya, kategori kelas yang awalnya berupa huruf (F, D, C, B, A) diubah menjadi skor numerik 0 sampai 4 menggunakan recode(). Dari skor ini dihitung Mean Absolute Error (MAE) sebesar 0.898, yang menunjukkan rata-rata kesalahan prediksi ordinal kurang dari 1 kategori, artinya model cukup akurat dalam memprediksi urutan kelas.
Analisis lebih detail dari confusion matrix menunjukkan model lebih kuat mengenali kategori “D”, tetapi cenderung bingung membedakan kelas yang berdekatan seperti “C” dan “B”. Dengan demikian, meskipun model sudah mampu menangkap pola ordinal, ada kesulitan dalam membedakan kelas yang tingkatannya berdekatan.
Pendekatan kombinasi PCA dan Ordinal Logistic Regression mampu menangkap pola ordinal pada data nilai akhir siswa dengan performa yang sedikit lebih baik dibanding tebakan acak. Meskipun akurasi keseluruhan masih terbatas di sekitar 36%, model menunjukkan kemampuan yang cukup baik dalam memprediksi urutan kategori nilai dengan rata-rata kesalahan (MAE) kurang dari 1 tingkat kelas. Namun, model masih perlu perbaikan untuk membedakan kelas-kelas yang berdekatan agar prediksi lebih akurat dan konsisten.