Pembacaan file

Pada tahapan awal kita membaca file csv menggunakan fungsi read_csv dari library tidyverse. Kemudian setelah pembacaan data kita menampilkan struktur data singkat dengan fungsi glimpse

library(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
## ✔ ggplot2   3.5.2     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
data <- read_csv("ship_fuel_efficiency.csv")
## Rows: 1440 Columns: 10
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (6): ship_id, ship_type, route_id, month, fuel_type, weather_conditions
## dbl (4): distance, fuel_consumption, CO2_emissions, engine_efficiency
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Melihat struktur awal
glimpse(data)
## Rows: 1,440
## Columns: 10
## $ ship_id            <chr> "NG001", "NG001", "NG001", "NG001", "NG001", "NG001…
## $ ship_type          <chr> "Oil Service Boat", "Oil Service Boat", "Oil Servic…
## $ route_id           <chr> "Warri-Bonny", "Port Harcourt-Lagos", "Port Harcour…
## $ month              <chr> "January", "February", "March", "April", "May", "Ju…
## $ distance           <dbl> 132.26, 128.52, 67.30, 71.68, 134.32, 85.93, 85.67,…
## $ fuel_type          <chr> "HFO", "HFO", "HFO", "Diesel", "HFO", "Diesel", "HF…
## $ fuel_consumption   <dbl> 3779.77, 4461.44, 1867.73, 2393.51, 4267.19, 2342.1…
## $ CO2_emissions      <dbl> 10625.76, 12779.73, 5353.01, 6506.52, 11617.03, 675…
## $ weather_conditions <chr> "Stormy", "Moderate", "Calm", "Stormy", "Calm", "St…
## $ engine_efficiency  <dbl> 92.14, 92.98, 87.61, 87.42, 85.61, 72.82, 93.93, 91…

EDA Sebelum Pre Processing

  1. Statistik Deskriptif : Ini menunjukkan ringkasan angka seperti rata-rata, nilai min/maks, dan sebaran data untuk konsumsi BBM, emisi CO2, dan efisiensi mesin. Misalnya, kita bisa lihat berapa rata-rata emisi CO2 atau seberapa besar perbedaan nilai tertinggi-terendah.

  2. Visualisasi Distribusi : Grafik histogram menampilkan sebaran data masing-masing variabel. Jika bentuknya simetris (seperti lonceng), artinya data terdistribusi normal. Jika miring, ada ketidakseimbangan (misalnya lebih banyak mobil boros BBM).

  3. Korelasi Awal : Angka korelasi (dari -1 sampai 1) menunjukkan hubungan antar variabel. Nilai mendekati 1 atau -1 berarti hubungan kuat (contoh: konsumsi BBM tinggi mungkin berkaitan dengan emisi CO2 tinggi). Nilai 0 berarti tidak ada hubungan.

# 1. Statistik Deskriptif 
cat("\n=== STATISTIK AWAL ===\n")
## 
## === STATISTIK AWAL ===
summary(data[, c("fuel_consumption", "CO2_emissions", "engine_efficiency")])
##  fuel_consumption  CO2_emissions     engine_efficiency
##  Min.   :  237.9   Min.   :  615.7   Min.   :70.01    
##  1st Qu.: 1838.0   1st Qu.: 4991.5   1st Qu.:76.25    
##  Median : 3060.9   Median : 8423.2   Median :82.78    
##  Mean   : 4844.2   Mean   :13365.5   Mean   :82.58    
##  3rd Qu.: 4870.7   3rd Qu.:13447.1   3rd Qu.:88.86    
##  Max.   :24648.5   Max.   :71871.2   Max.   :94.98
# 2. Visualisasi Distribusi Numerik
library(patchwork)

p1 <- ggplot(data, aes(x = fuel_consumption)) + 
  geom_histogram(fill = "steelblue", bins = 20) +
  labs(title = "Distribusi Konsumsi BBM (Raw)")

p2 <- ggplot(data, aes(x = CO2_emissions)) + 
  geom_histogram(fill = "firebrick", bins = 20) +
  labs(title = "Distribusi Emisi CO2 (Raw)")

p3 <- ggplot(data, aes(x = engine_efficiency)) + 
  geom_histogram(fill = "darkgreen", bins = 20) +
  labs(title = "Distribusi Efisiensi Mesin (Raw)")

(p1 + p2) / p3  # Menggabungkan plot

# 3. Korelasi Awal
cat("\n=== MATRIKS KORELASI AWAL ===\n")
## 
## === MATRIKS KORELASI AWAL ===
round(cor(data[, c("fuel_consumption", "CO2_emissions", "engine_efficiency")]), 3)
##                   fuel_consumption CO2_emissions engine_efficiency
## fuel_consumption             1.000         0.997            -0.031
## CO2_emissions                0.997         1.000            -0.030
## engine_efficiency           -0.031        -0.030             1.000

Pre-Processing Data

  1. Handling Missing Values : Mengecek dan menghapus data yang kosong (NA) serta data duplikat, agar analisis lebih akurat.

  2. Normalisasi : Mengubah skala variabel numerik (seperti konsumsi BBM dan emisi CO2) ke rentang 0–1 agar lebih mudah dibandingkan. Contoh: nilai 0 = terendah, 1 = tertinggi.

  3. Menangani Korelasi Sempurna : Menambahkan sedikit “noise” (gangguan acak yang sangat kecil) untuk menghindari masalah jika dua variabel terlalu mirip (korelasi sempurna), yang bisa mengganggu analisis.

  4. Membuat Target Kategorikal : Mengubah efisiensi mesin (numerik) menjadi kategori Rendah-Sedang-Tinggi berdasarkan pembagian 33% terendah, 33% tengah, dan 33% tertinggi. Ini memudahkan analisis klasifikasi.

# 1. Handling Missing Values
colSums(is.na(data))
##            ship_id          ship_type           route_id              month 
##                  0                  0                  0                  0 
##           distance          fuel_type   fuel_consumption      CO2_emissions 
##                  0                  0                  0                  0 
## weather_conditions  engine_efficiency 
##                  0                  0
data <- na.omit(data)
data <- distinct(data)

# 2. Normalisasi Variabel Numerik (Penting untuk LDA)
data <- data %>%
  mutate(across(c(fuel_consumption, CO2_emissions), 
                ~ (. - min(.)) / (max(.) - min(.))))

# 3. Menangani Korelasi Sempurna dengan Menambahkan Noise Kecil
set.seed(123)  # Pastikan reproducibility
data$fuel_consumption <- data$fuel_consumption + rnorm(nrow(data), sd = 0.0001)
data$CO2_emissions <- data$CO2_emissions + rnorm(nrow(data), sd = 0.0001)

# 4. Membuat Variabel Target Kategorikal
data <- data %>%
  mutate(engine_eff_cat = case_when(
    engine_efficiency <= quantile(engine_efficiency, 0.33, na.rm = TRUE) ~ "Rendah",
    engine_efficiency <= quantile(engine_efficiency, 0.66, na.rm = TRUE) ~ "Sedang",
    TRUE ~ "Tinggi"
  )) %>%
  mutate(engine_eff_cat = factor(engine_eff_cat,
                                 levels = c("Rendah", "Sedang", "Tinggi"),
                                 ordered = TRUE))

EDA Setelah Pre-Processing

  1. Statistik Setelah Preprocessing : Ringkasan angka (mean, min/max, dll.) diperbarui setelah normalisasi. Misalnya, konsumsi BBM dan emisi CO2 sekarang punya rentang 0–1, sedangkan efisiensi mesin tetap asli (karena tidak dinormalisasi).

  2. Visualisasi Perubahan : Histogram menunjukkan distribusi baru setelah normalisasi. Jika sebelumnya nilai sangat besar (misal ratusan), sekarang terkompresi ke skala 0–1. Pola sebarannya tetap sama, hanya skalanya berubah.

  3. Korelasi Final : Matriks korelasi terakhir memastikan apakah hubungan antar variabel (BBM, CO2, efisiensi) berubah setelah preprocessing. Nilai ~1 atau ~-1 berarti hubungan masih kuat, ~0 berarti tidak berkaitan.

library(ggplot2)
library(patchwork)  

# 1. Verifikasi Hasil Preprocessing
cat("\n=== STATISTIK SETELAH PREPROCESSING ===\n")
## 
## === STATISTIK SETELAH PREPROCESSING ===
summary(data[, c("fuel_consumption", "CO2_emissions", "engine_efficiency")])
##  fuel_consumption     CO2_emissions        engine_efficiency
##  Min.   :-0.0000747   Min.   :-0.0000597   Min.   :70.01    
##  1st Qu.: 0.0654484   1st Qu.: 0.0615179   1st Qu.:76.25    
##  Median : 0.1156271   Median : 0.1095825   Median :82.78    
##  Mean   : 0.1887047   Mean   : 0.1789309   Mean   :82.58    
##  3rd Qu.: 0.1896268   3rd Qu.: 0.1800620   3rd Qu.:88.86    
##  Max.   : 0.9999400   Max.   : 0.9998415   Max.   :94.98
# 2. Visualisasi Perubahan
p4 <- ggplot(data, aes(x = fuel_consumption)) + 
  geom_histogram(fill = "steelblue", bins = 20) +
  labs(title = "Distribusi Konsumsi BBM (Setelah Normalisasi)")

p5 <- ggplot(data, aes(x = CO2_emissions)) + 
  geom_histogram(fill = "firebrick", bins = 20) +
  labs(title = "Distribusi Emisi CO2 (Setelah Normalisasi)")

(p4 + p5)  # Side-by-side comparison

# 3. Korelasi Final
cat("\n=== MATRIKS KORELASI FINAL ===\n")
## 
## === MATRIKS KORELASI FINAL ===
round(cor(data[, c("fuel_consumption", "CO2_emissions", "engine_efficiency")]), 3)
##                   fuel_consumption CO2_emissions engine_efficiency
## fuel_consumption             1.000         0.997            -0.031
## CO2_emissions                0.997         1.000            -0.030
## engine_efficiency           -0.031        -0.030             1.000

Pemodelan (Analisis Diskriminan)

  1. Persiapan Data : Data dibagi menjadi 70% latih (untuk membangun model) dan 30% uji (untuk menguji akurasi). Pembagian acak tapi konsisten (set.seed(123)).

  2. Pembuatan Model LDA : Model klasifikasi yang memprediksi kategori efisiensi mesin (Rendah/Sedang/Tinggi) berdasarkan konsumsi BBM dan emisi CO₂.

  1. Evaluasi Model : Menunjukkan seberapa sering model benar/salah memprediksi yaitu Akurasi 80% = 80% prediksi benar sedangkan Sensitivity/Recall yaitu berapa % “Rendah” yang terprediksi sebagai “Rendah”

  2. Cross-Validation (CV) : Model diuji 5 kali dengan data berbeda untuk memastikan akurasinya stabil atau tidak overfitting.

library(MASS)
## 
## Attaching package: 'MASS'
## The following object is masked from 'package:patchwork':
## 
##     area
## The following object is masked from 'package:dplyr':
## 
##     select
library(caret)
## Loading required package: lattice
## 
## Attaching package: 'caret'
## The following object is masked from 'package:purrr':
## 
##     lift
# 1. Persiapan Data
set.seed(123)
train_index <- createDataPartition(data$engine_eff_cat, p = 0.7, list = FALSE)
train_data <- data[train_index, ]
test_data <- data[-train_index, ]

# 2. Model LDA dengan Regularisasi
model_lda <- lda(engine_eff_cat ~ fuel_consumption + CO2_emissions,
                data = train_data,
                method = "mve",
                prior = c(1,1,1)/3)

# 3. Evaluasi
predictions <- predict(model_lda, newdata = test_data)
conf_matrix <- confusionMatrix(predictions$class, test_data$engine_eff_cat)
print(conf_matrix)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction Rendah Sedang Tinggi
##     Rendah      6     16      8
##     Sedang    100     87    113
##     Tinggi     36     39     26
## 
## Overall Statistics
##                                           
##                Accuracy : 0.2761          
##                  95% CI : (0.2344, 0.3209)
##     No Information Rate : 0.3411          
##     P-Value [Acc > NIR] : 0.9984          
##                                           
##                   Kappa : -0.084          
##                                           
##  Mcnemar's Test P-Value : <2e-16          
## 
## Statistics by Class:
## 
##                      Class: Rendah Class: Sedang Class: Tinggi
## Sensitivity                0.04225        0.6127       0.17687
## Specificity                0.91696        0.2630       0.73592
## Pos Pred Value             0.20000        0.2900       0.25743
## Neg Pred Value             0.66085        0.5802       0.63333
## Prevalence                 0.32947        0.3295       0.34107
## Detection Rate             0.01392        0.2019       0.06032
## Detection Prevalence       0.06961        0.6961       0.23434
## Balanced Accuracy          0.47960        0.4378       0.45639
# 4. Cross-Validation
ctrl <- trainControl(method = "cv", number = 5)
cv_model <- train(engine_eff_cat ~ fuel_consumption + CO2_emissions,
                 data = data,
                 method = "lda",
                 trControl = ctrl)
print(paste("Akurasi CV:", mean(cv_model$resample$Accuracy)))
## [1] "Akurasi CV: 0.343055555555556"

Visualisasi

  1. Decision Boundaries : Plot ini menunjukkan garis pemisah antara kategori efisiensi mesin (Rendah/Sedang/Tinggi) berdasarkan konsumsi BBM (sumbu X) dan emisi CO₂ (sumbu Y).

  2. Plot Diskriminan : pada Sumbu LD1/LD2 mengkombinasi linear dari variabel asli (BBM & CO₂) yang paling membedakan kategori.

# 1. Decision Boundaries
library(klaR)
partimat(engine_eff_cat ~ fuel_consumption + CO2_emissions,
        data = train_data,
        method = "lda",
        main = "Decision Boundaries LDA")

# 2. Plot Diskriminan
plot(model_lda, dimen = 2, main = "Proyeksi LDA")

Analisis Regresi Logistik Ordinal

Tujuannya adalah memprediksi kategori efisiensi mesin (Rendah-Sedang-Tinggi) berdasarkan konsumsi BBM dan emisi CO₂, dengan memperhatikan urutan kategori (ordinal). Hasil Signifikansi Variabel : 1. Uji Wald (p-value < 0.05) menunjukkan apakah BBM/CO₂ berpengaruh signifikan. 2. Uji Brant memverifikasi asumsi proportional odds (jika p > 0.05, asumsi terpenuhi).

Hasil Odds Ratio (OR): 1. OR > 1: Peningkatan variabel prediktor (misal CO₂) meningkatkan kecenderungan efisiensi ke kategori lebih tinggi. 2. OR < 1: Efek sebaliknya.

##########################
### REGRESI LOGISTIK ORDINAL ###
##########################

library(MASS)   # Untuk fungsi polr()
library(brant)  # Untuk uji asumsi proportional odds
library(caret)  # Untuk evaluasi model

# 1. PEMBENTUKAN MODEL (ESTIMASI PARAMETER)
model_ordinal <- polr(engine_eff_cat ~ fuel_consumption + CO2_emissions,
                     data = train_data,
                     Hess = TRUE)  # Hessian untuk perhitungan standar error

# Ringkasan model
summary_model <- summary(model_ordinal)
print(summary_model)
## Call:
## polr(formula = engine_eff_cat ~ fuel_consumption + CO2_emissions, 
##     data = train_data, Hess = TRUE)
## 
## Coefficients:
##                   Value Std. Error t value
## fuel_consumption -2.579      4.021 -0.6414
## CO2_emissions     2.832      4.217  0.6717
## 
## Intercepts:
##               Value   Std. Error t value
## Rendah|Sedang -0.6874  0.0861    -7.9829
## Sedang|Tinggi  0.6849  0.0861     7.9560
## 
## Residual Deviance: 2216.203 
## AIC: 2224.203
# 2. UJI SIGNIFIKANSI VARIABEL

# a. Uji Serentak (Likelihood Ratio Test)
null_model <- polr(engine_eff_cat ~ 1, data = train_data, Hess = TRUE)
lr_test <- anova(null_model, model_ordinal)
print("Uji Likelihood Ratio (Serentak):")
## [1] "Uji Likelihood Ratio (Serentak):"
print(lr_test)
## Likelihood ratio tests of ordinal regression models
## 
## Response: engine_eff_cat
##                              Model Resid. df Resid. Dev   Test    Df  LR stat.
## 1                                1      1007   2216.802                       
## 2 fuel_consumption + CO2_emissions      1005   2216.203 1 vs 2     2 0.5992202
##     Pr(Chi)
## 1          
## 2 0.7411071
# b. Uji Parsial (Wald Test)
coef_table <- coef(summary(model_ordinal))
p_values <- pnorm(abs(coef_table[, "t value"]), lower.tail = FALSE) * 2
coef_table <- cbind(coef_table, "p value" = round(p_values, 4))
print("Uji Wald (Parsial):")
## [1] "Uji Wald (Parsial):"
print(coef_table)
##                       Value Std. Error    t value p value
## fuel_consumption -2.5790506 4.02099379 -0.6413963  0.5213
## CO2_emissions     2.8323947 4.21666823  0.6717139  0.5018
## Rendah|Sedang    -0.6873873 0.08610727 -7.9829183  0.0000
## Sedang|Tinggi     0.6848849 0.08608459  7.9559524  0.0000
# c. Uji Asumsi Proportional Odds
print("Uji Brant (Proportional Odds):")
## [1] "Uji Brant (Proportional Odds):"
brant_test <- brant(model_ordinal)
## ---------------------------------------------------- 
## Test for     X2  df  probability 
## ---------------------------------------------------- 
## Omnibus          0.81    2   0.67
## fuel_consumption 0.01    1   0.92
## CO2_emissions        0   1   0.97
## ---------------------------------------------------- 
## 
## H0: Parallel Regression Assumption holds
print(brant_test)
##                           X2 df probability
## Omnibus          0.805155632  2   0.6685943
## fuel_consumption 0.011304943  1   0.9153247
## CO2_emissions    0.001647024  1   0.9676279
# 3. AKURASI MODEL

# Prediksi pada data test
pred_ordinal <- predict(model_ordinal, newdata = test_data)

# Confusion Matrix
conf_matrix_ordinal <- confusionMatrix(pred_ordinal, test_data$engine_eff_cat)
print("Confusion Matrix (Regresi Logistik Ordinal):")
## [1] "Confusion Matrix (Regresi Logistik Ordinal):"
print(conf_matrix_ordinal)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction Rendah Sedang Tinggi
##     Rendah     37     48     36
##     Sedang      0      0      0
##     Tinggi    105     94    111
## 
## Overall Statistics
##                                           
##                Accuracy : 0.3434          
##                  95% CI : (0.2986, 0.3903)
##     No Information Rate : 0.3411          
##     P-Value [Acc > NIR] : 0.4776          
##                                           
##                   Kappa : 0.0084          
##                                           
##  Mcnemar's Test P-Value : <2e-16          
## 
## Statistics by Class:
## 
##                      Class: Rendah Class: Sedang Class: Tinggi
## Sensitivity                0.26056        0.0000        0.7551
## Specificity                0.70934        1.0000        0.2993
## Pos Pred Value             0.30579           NaN        0.3581
## Neg Pred Value             0.66129        0.6705        0.7025
## Prevalence                 0.32947        0.3295        0.3411
## Detection Rate             0.08585        0.0000        0.2575
## Detection Prevalence       0.28074        0.0000        0.7193
## Balanced Accuracy          0.48495        0.5000        0.5272
# 4. INTERPRETASI (ODDS RATIO)

# Hitung odds ratio
odds_ratio <- exp(coef(model_ordinal))
ci <- exp(confint(model_ordinal))
## Waiting for profiling to be done...
# Tabel interpretasi
interpretasi <- data.frame(
  Variabel = names(odds_ratio),
  Odds_Ratio = round(odds_ratio, 3),
  CI_95_bawah = round(ci[,1], 3),
  CI_95_atas = round(ci[,2], 3),
  Keterangan = ifelse(odds_ratio > 1, 
                     "Meningkatkan kecenderungan ke kategori lebih tinggi",
                     "Meningkatkan kecenderungan ke kategori lebih rendah")
)

print("Interpretasi Odds Ratio:")
## [1] "Interpretasi Odds Ratio:"
print(interpretasi)
##                          Variabel Odds_Ratio CI_95_bawah CI_95_atas
## fuel_consumption fuel_consumption      0.076       0.000    201.758
## CO2_emissions       CO2_emissions     16.986       0.004  67609.760
##                                                           Keterangan
## fuel_consumption Meningkatkan kecenderungan ke kategori lebih rendah
## CO2_emissions    Meningkatkan kecenderungan ke kategori lebih tinggi
# Visualisasi Odds Ratio
library(ggplot2)
ggplot(interpretasi, aes(x = Variabel, y = Odds_Ratio)) +
  geom_point(size = 3) +
  geom_errorbar(aes(ymin = CI_95_bawah, ymax = CI_95_atas), width = 0.2) +
  geom_hline(yintercept = 1, linetype = "dashed", color = "red") +
  labs(title = "Odds Ratio dengan 95% Confidence Interval",
       y = "Odds Ratio",
       x = "Variabel Prediktor") +
  theme_minimal()

Evaluasi Model

Pada tahapan akhir kita melakukan evaluasi model yang mengevaluasi LDA, mengevaluasi regresi ordinal, perbandingan model, visualisasi metriknya, analisis per kelas, dan keterbatasan model

##########################
### EVALUASI MODEL ###
##########################
install.packages("MLmetrics")
## Installing package into '/cloud/lib/x86_64-pc-linux-gnu-library/4.4'
## (as 'lib' is unspecified)
library(MLmetrics)  # Untuk metrik tambahan
## 
## Attaching package: 'MLmetrics'
## The following objects are masked from 'package:caret':
## 
##     MAE, RMSE
## The following object is masked from 'package:base':
## 
##     Recall
### 1. EVALUASI LDA ###
lda_pred <- predict(model_lda, newdata = test_data)
lda_cm <- confusionMatrix(lda_pred$class, test_data$engine_eff_cat)

# Ekstrak metrik LDA
lda_metrics <- data.frame(
  Model = "LDA",
  Accuracy = lda_cm$overall["Accuracy"],
  Kappa = lda_cm$overall["Kappa"],
  Precision = lda_cm$byClass[, "Pos Pred Value"] %>% mean(),
  Recall = lda_cm$byClass[, "Sensitivity"] %>% mean(),
  F1 = lda_cm$byClass[, "F1"] %>% mean()
)

### 2. EVALUASI REGRESI LOGISTIK ORDINAL ###
ordinal_pred <- predict(model_ordinal, newdata = test_data)
ordinal_cm <- confusionMatrix(ordinal_pred, test_data$engine_eff_cat)

# Ekstrak metrik Ordinal
ordinal_metrics <- data.frame(
  Model = "Regresi Logistik Ordinal",
  Accuracy = ordinal_cm$overall["Accuracy"],
  Kappa = ordinal_cm$overall["Kappa"],
  Precision = ordinal_cm$byClass[, "Pos Pred Value"] %>% mean(),
  Recall = ordinal_cm$byClass[, "Sensitivity"] %>% mean(),
  F1 = ordinal_cm$byClass[, "F1"] %>% mean()
)

### 3. PERBANDINGAN MODEL ###
combined_metrics <- rbind(lda_metrics, ordinal_metrics)
print("Perbandingan Performa Model:")
## [1] "Perbandingan Performa Model:"
print(combined_metrics)
##                              Model  Accuracy        Kappa Precision    Recall
## Accuracy                       LDA 0.2761021 -0.083979557 0.2491419 0.2772668
## Accuracy1 Regresi Logistik Ordinal 0.3433875  0.008422148       NaN 0.3385551
##                F1
## Accuracy  0.22437
## Accuracy1      NA
### 4. VISUALISASI METRIK ###
library(tidyr)
metrics_long <- combined_metrics %>%
  pivot_longer(cols = -Model, names_to = "Metric", values_to = "Value")

ggplot(metrics_long, aes(x = Metric, y = Value, fill = Model)) +
  geom_bar(stat = "identity", position = "dodge") +
  geom_text(aes(label = round(Value, 3)), position = position_dodge(width = 0.9), vjust = -0.5) +
  labs(title = "Perbandingan Metrik Evaluasi Model",
       x = "Metrik",
       y = "Nilai") +
  theme_minimal() +
  scale_fill_brewer(palette = "Set1")
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_text()`).

### 5. ANALISIS PER KLAS ###
cat("\n=== DETAIL PER KLAS (LDA) ===\n")
## 
## === DETAIL PER KLAS (LDA) ===
print(lda_cm$byClass)
##               Sensitivity Specificity Pos Pred Value Neg Pred Value Precision
## Class: Rendah  0.04225352   0.9169550      0.2000000      0.6608479 0.2000000
## Class: Sedang  0.61267606   0.2629758      0.2900000      0.5801527 0.2900000
## Class: Tinggi  0.17687075   0.7359155      0.2574257      0.6333333 0.2574257
##                   Recall         F1 Prevalence Detection Rate
## Class: Rendah 0.04225352 0.06976744  0.3294664     0.01392111
## Class: Sedang 0.61267606 0.39366516  0.3294664     0.20185615
## Class: Tinggi 0.17687075 0.20967742  0.3410673     0.06032483
##               Detection Prevalence Balanced Accuracy
## Class: Rendah           0.06960557         0.4796043
## Class: Sedang           0.69605568         0.4378259
## Class: Tinggi           0.23433875         0.4563931
cat("\n=== DETAIL PER KLAS (ORDINAL) ===\n")
## 
## === DETAIL PER KLAS (ORDINAL) ===
print(ordinal_cm$byClass)
##               Sensitivity Specificity Pos Pred Value Neg Pred Value Precision
## Class: Rendah   0.2605634   0.7093426      0.3057851      0.6612903 0.3057851
## Class: Sedang   0.0000000   1.0000000            NaN      0.6705336        NA
## Class: Tinggi   0.7551020   0.2992958      0.3580645      0.7024793 0.3580645
##                  Recall        F1 Prevalence Detection Rate
## Class: Rendah 0.2605634 0.2813688  0.3294664     0.08584687
## Class: Sedang 0.0000000        NA  0.3294664     0.00000000
## Class: Tinggi 0.7551020 0.4857768  0.3410673     0.25754060
##               Detection Prevalence Balanced Accuracy
## Class: Rendah            0.2807425         0.4849530
## Class: Sedang            0.0000000         0.5000000
## Class: Tinggi            0.7192575         0.5271989
### 6. KETERBATASAN MODEL ###
cat("\nKeterbatasan:
1. Akurasi kedua model relatif rendah (~34%) menunjukkan prediktor yang ada kurang informatif
2. Model kesulitan memprediksi kelas 'Sedang' (Recall = 0%)
3. Confidence Interval Odds Ratio sangat lebar menandakan estimasi tidak stabil
4. Uji signifikansi (p-value > 0.05) menunjukkan prediktor tidak signifikan\n")
## 
## Keterbatasan:
## 1. Akurasi kedua model relatif rendah (~34%) menunjukkan prediktor yang ada kurang informatif
## 2. Model kesulitan memprediksi kelas 'Sedang' (Recall = 0%)
## 3. Confidence Interval Odds Ratio sangat lebar menandakan estimasi tidak stabil
## 4. Uji signifikansi (p-value > 0.05) menunjukkan prediktor tidak signifikan