1 Pendahuluan

Analisis regresi logistik merupakan metode statistika yang digunakan untuk memodelkan hubungan antara variabel respon kategorik dengan satu atau lebih variabel prediktor. Berbeda dengan regresi linier yang menghasilkan nilai kontinu, regresi logistik menghasilkan probabilitas kejadian suatu kategori.

Dalam laporan ini, akan dibahas empat jenis regresi logistik:

No Jenis Tipe Respon Dataset
1 Regresi Logistik Biner Dikotomis (0/1) Heart Failure Clinical Records
2 Regresi Logistik Multinomial Nominal (> 2 kategori) Forest Covertype
3 Regresi Logistik Ordinal Ordinal (bertingkat) Student Performance
4 Regresi Logistik Poisson Count data (cacahan) Bike Sharing Dataset

2 Package yang Digunakan

# Install packages jika belum tersedia
pkgs <- c("tidyverse","nnet","MASS","caret","car","lmtest",
          "ResourceSelection","pROC","ggplot2","knitr","kableExtra","ordinal")
for (p in pkgs) if (!require(p, character.only = TRUE)) install.packages(p)

library(MASS)            # load MASS duluan agar dplyr::select tidak tertimpa
library(tidyverse)
library(nnet)
library(caret)
library(car)
library(lmtest)
library(ResourceSelection)
library(pROC)
library(ggplot2)
library(knitr)
library(kableExtra)

3 1. Regresi Logistik Biner

3.1 1.1 Deskripsi Data

Sumber Data: Heart Failure Clinical Records — UCI Machine Learning Repository

Dataset ini berisi rekam medis 299 pasien gagal jantung yang dikumpulkan selama periode tindak lanjut. Variabel respon adalah DEATH_EVENT (0 = selamat, 1 = meninggal).

Variabel yang digunakan:

Variabel Tipe Keterangan
DEATH_EVENT Biner (0/1) Status kematian pasien (Respon)
age Numerik Usia pasien (tahun)
anaemia Biner Penurunan sel darah merah (0/1)
creatinine_phosphokinase Numerik Level enzim CPK (mcg/L)
diabetes Biner Riwayat diabetes (0/1)
ejection_fraction Numerik Persentase darah yang dipompa jantung
high_blood_pressure Biner Riwayat hipertensi (0/1)
platelets Numerik Jumlah trombosit (kiloplatelets/mL)
serum_creatinine Numerik Level serum kreatinin (mg/dL)
serum_sodium Numerik Level serum sodium (mEq/L)
sex Biner Jenis kelamin (0=perempuan, 1=laki-laki)
smoking Biner Riwayat merokok (0/1)
time Numerik Periode tindak lanjut (hari)

3.2 1.2 Import dan Eksplorasi Data

# Data sintetis Heart Failure (struktur identik UCI dataset, n=299)
# Referensi: https://archive.ics.uci.edu/dataset/519/heart+failure+clinical+records
set.seed(42)
n_hf <- 299

heart <- data.frame(
  age                    = round(rnorm(n_hf, 60, 12)) |> pmax(40) |> pmin(95),
  anaemia                = rbinom(n_hf, 1, 0.43),
  creatinine_phosphokinase = round(rlnorm(n_hf, 5.5, 1.2)),
  diabetes               = rbinom(n_hf, 1, 0.42),
  ejection_fraction      = round(rnorm(n_hf, 38, 12)) |> pmax(14) |> pmin(80),
  high_blood_pressure    = rbinom(n_hf, 1, 0.35),
  platelets              = round(rnorm(n_hf, 263358, 97804)),
  serum_creatinine       = round(rlnorm(n_hf, 0.5, 0.6), 1),
  serum_sodium           = round(rnorm(n_hf, 136.6, 4.4)),
  sex                    = rbinom(n_hf, 1, 0.65),
  smoking                = rbinom(n_hf, 1, 0.32),
  time                   = round(runif(n_hf, 4, 285))
)
# DEATH_EVENT: probabilitas lebih tinggi jika serum_creatinine tinggi & EF rendah
logit_p <- -2 + 0.02*(heart$age - 60) - 0.04*(heart$ejection_fraction - 38) +
            0.3*heart$serum_creatinine - 0.02*(heart$time - 100)
heart$DEATH_EVENT <- rbinom(n_hf, 1, plogis(logit_p))

str(heart)
## 'data.frame':    299 obs. of  13 variables:
##  $ age                     : num  76 53 64 68 65 59 78 59 84 59 ...
##  $ anaemia                 : int  0 0 0 0 1 0 0 0 1 0 ...
##  $ creatinine_phosphokinase: num  42 448 29 16 1665 ...
##  $ diabetes                : int  1 1 1 0 0 0 1 0 1 0 ...
##  $ ejection_fraction       : num  36 53 71 49 35 39 33 64 14 39 ...
##  $ high_blood_pressure     : int  0 1 0 0 0 1 1 1 0 0 ...
##  $ platelets               : num  130079 248508 109655 215547 476691 ...
##  $ serum_creatinine        : num  1 1.4 0.9 2.4 1.4 1.5 1.4 3.2 0.8 1.5 ...
##  $ serum_sodium            : num  141 140 140 139 139 139 141 134 138 137 ...
##  $ sex                     : int  1 1 0 1 0 0 1 1 0 0 ...
##  $ smoking                 : int  0 0 0 1 0 1 0 0 0 0 ...
##  $ time                    : num  13 148 183 130 113 38 276 14 119 280 ...
##  $ DEATH_EVENT             : int  1 0 0 0 0 1 0 0 1 0 ...
# Dimensi data
cat("Jumlah baris:", nrow(heart), "\n")
## Jumlah baris: 299
cat("Jumlah kolom:", ncol(heart), "\n")
## Jumlah kolom: 13
# Ringkasan statistik
summary(heart)
##       age           anaemia       creatinine_phosphokinase    diabetes    
##  Min.   :40.00   Min.   :0.0000   Min.   :   7.0           Min.   :0.000  
##  1st Qu.:52.00   1st Qu.:0.0000   1st Qu.: 103.0           1st Qu.:0.000  
##  Median :60.00   Median :0.0000   Median : 225.0           Median :0.000  
##  Mean   :60.05   Mean   :0.4047   Mean   : 463.3           Mean   :0.408  
##  3rd Qu.:68.00   3rd Qu.:1.0000   3rd Qu.: 527.0           3rd Qu.:1.000  
##  Max.   :92.00   Max.   :1.0000   Max.   :5234.0           Max.   :1.000  
##  ejection_fraction high_blood_pressure   platelets      serum_creatinine
##  Min.   :14.00     Min.   :0.0000      Min.   : 19519   Min.   : 0.300  
##  1st Qu.:29.00     1st Qu.:0.0000      1st Qu.:187556   1st Qu.: 1.200  
##  Median :39.00     Median :0.0000      Median :262594   Median : 1.800  
##  Mean   :38.38     Mean   :0.3612      Mean   :259756   Mean   : 2.164  
##  3rd Qu.:46.00     3rd Qu.:1.0000      3rd Qu.:332308   3rd Qu.: 2.900  
##  Max.   :80.00     Max.   :1.0000      Max.   :476691   Max.   :10.700  
##   serum_sodium        sex            smoking            time      
##  Min.   :122.0   Min.   :0.0000   Min.   :0.0000   Min.   :  4.0  
##  1st Qu.:134.0   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.: 80.0  
##  Median :137.0   Median :1.0000   Median :0.0000   Median :150.0  
##  Mean   :137.1   Mean   :0.6689   Mean   :0.3445   Mean   :149.2  
##  3rd Qu.:140.0   3rd Qu.:1.0000   3rd Qu.:1.0000   3rd Qu.:222.0  
##  Max.   :150.0   Max.   :1.0000   Max.   :1.0000   Max.   :284.0  
##   DEATH_EVENT    
##  Min.   :0.0000  
##  1st Qu.:0.0000  
##  Median :0.0000  
##  Mean   :0.1706  
##  3rd Qu.:0.0000  
##  Max.   :1.0000
# Cek missing value
cat("Missing values per kolom:\n")
## Missing values per kolom:
colSums(is.na(heart))
##                      age                  anaemia creatinine_phosphokinase 
##                        0                        0                        0 
##                 diabetes        ejection_fraction      high_blood_pressure 
##                        0                        0                        0 
##                platelets         serum_creatinine             serum_sodium 
##                        0                        0                        0 
##                      sex                  smoking                     time 
##                        0                        0                        0 
##              DEATH_EVENT 
##                        0
# Distribusi variabel respon
tabel_respon <- table(heart$DEATH_EVENT)
prop_respon   <- prop.table(tabel_respon)

data.frame(
  Kategori   = c("Selamat (0)", "Meninggal (1)"),
  Frekuensi  = as.numeric(tabel_respon),
  Proporsi   = paste0(round(as.numeric(prop_respon) * 100, 2), "%")
) %>%
  kable(caption = "Distribusi Variabel Respon DEATH_EVENT") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Distribusi Variabel Respon DEATH_EVENT
Kategori Frekuensi Proporsi
Selamat (0) 248 82.94%
Meninggal (1) 51 17.06%
# Visualisasi distribusi respon
ggplot(heart, aes(x = factor(DEATH_EVENT, labels = c("Selamat", "Meninggal")),
                  fill = factor(DEATH_EVENT))) +
  geom_bar(width = 0.5, show.legend = FALSE) +
  geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5) +
  scale_fill_manual(values = c("#2196F3", "#F44336")) +
  labs(title   = "Distribusi DEATH_EVENT (Kematian Pasien Gagal Jantung)",
       x       = "Status",
       y       = "Frekuensi") +
  theme_minimal()

3.3 1.3 Preprocessing Data

# Mengubah variabel kategorik menjadi faktor
heart$DEATH_EVENT        <- as.factor(heart$DEATH_EVENT)
heart$anaemia            <- as.factor(heart$anaemia)
heart$diabetes           <- as.factor(heart$diabetes)
heart$high_blood_pressure <- as.factor(heart$high_blood_pressure)
heart$sex                <- as.factor(heart$sex)
heart$smoking            <- as.factor(heart$smoking)

# Split data: 80% training, 20% testing
set.seed(42)
idx_train <- createDataPartition(heart$DEATH_EVENT, p = 0.8, list = FALSE)
train_hf  <- heart[ idx_train, ]
test_hf   <- heart[-idx_train, ]

cat("Jumlah data training:", nrow(train_hf), "\n")
## Jumlah data training: 240
cat("Jumlah data testing :", nrow(test_hf),  "\n")
## Jumlah data testing : 59

3.4 1.4 Pemodelan Regresi Logistik Biner

# Membangun model regresi logistik biner
model_biner <- glm(DEATH_EVENT ~ age + anaemia + creatinine_phosphokinase +
                     diabetes + ejection_fraction + high_blood_pressure +
                     platelets + serum_creatinine + serum_sodium +
                     sex + smoking + time,
                   data   = train_hf,
                   family = binomial(link = "logit"))

summary(model_biner)
## 
## Call:
## glm(formula = DEATH_EVENT ~ age + anaemia + creatinine_phosphokinase + 
##     diabetes + ejection_fraction + high_blood_pressure + platelets + 
##     serum_creatinine + serum_sodium + sex + smoking + time, family = binomial(link = "logit"), 
##     data = train_hf)
## 
## Coefficients:
##                            Estimate Std. Error z value Pr(>|z|)    
## (Intercept)               4.604e+00  8.598e+00   0.535 0.592329    
## age                       4.967e-02  1.923e-02   2.582 0.009813 ** 
## anaemia1                 -2.234e-01  4.881e-01  -0.458 0.647124    
## creatinine_phosphokinase -8.579e-05  3.569e-04  -0.240 0.810043    
## diabetes1                -7.159e-01  4.992e-01  -1.434 0.151567    
## ejection_fraction        -8.572e-02  2.268e-02  -3.779 0.000157 ***
## high_blood_pressure1     -1.207e-01  4.946e-01  -0.244 0.807170    
## platelets                -3.256e-06  2.553e-06  -1.276 0.202093    
## serum_creatinine          4.854e-01  1.559e-01   3.114 0.001848 ** 
## serum_sodium             -2.754e-02  6.026e-02  -0.457 0.647617    
## sex1                     -8.573e-02  4.891e-01  -0.175 0.860859    
## smoking1                  7.971e-01  4.998e-01   1.595 0.110708    
## time                     -2.279e-02  4.199e-03  -5.428  5.7e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 219.46  on 239  degrees of freedom
## Residual deviance: 129.50  on 227  degrees of freedom
## AIC: 155.5
## 
## Number of Fisher Scoring iterations: 6

3.5 1.5 Uji Signifikansi Model

3.5.1 Uji Omnibus (Likelihood Ratio Test)

# Uji serentak (Likelihood Ratio Test)
lrtest(model_biner)

Interpretasi: Jika nilai p-value < 0.05, maka secara serentak terdapat minimal satu variabel prediktor yang berpengaruh signifikan terhadap variabel respon.

3.5.2 Uji Parsial (Wald Test)

# Koefisien dan p-value
hasil_koef <- summary(model_biner)$coefficients
hasil_df   <- as.data.frame(hasil_koef)
hasil_df$Signifikan <- ifelse(hasil_df[, 4] < 0.05, "Ya ✓", "Tidak")

hasil_df %>%
  kable(digits = 4, caption = "Hasil Uji Parsial (Wald Test)") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)
Hasil Uji Parsial (Wald Test)
Estimate Std. Error z value Pr(>&#124;z&#124;) Signifikan
(Intercept) 4.6039 8.5980 0.5355 0.5923 Tidak
age 0.0497 0.0192 2.5824 0.0098 Ya ✓
anaemia1 -0.2234 0.4881 -0.4578 0.6471 Tidak
creatinine_phosphokinase -0.0001 0.0004 -0.2404 0.8100 Tidak
diabetes1 -0.7159 0.4992 -1.4340 0.1516 Tidak
ejection_fraction -0.0857 0.0227 -3.7792 0.0002 Ya ✓
high_blood_pressure1 -0.1207 0.4946 -0.2441 0.8072 Tidak
platelets 0.0000 0.0000 -1.2756 0.2021 Tidak
serum_creatinine 0.4854 0.1559 3.1136 0.0018 Ya ✓
serum_sodium -0.0275 0.0603 -0.4571 0.6476 Tidak
sex1 -0.0857 0.4891 -0.1753 0.8609 Tidak
smoking1 0.7971 0.4998 1.5950 0.1107 Tidak
time -0.0228 0.0042 -5.4281 0.0000 Ya ✓

3.5.3 Uji Hosmer-Lemeshow (Goodness of Fit)

# Konversi respon ke numerik untuk Hosmer-Lemeshow
prob_hl <- predict(model_biner, type = "response")
y_num   <- as.numeric(as.character(train_hf$DEATH_EVENT))
hl_test <- hoslem.test(y_num, prob_hl, g = 10)
print(hl_test)
## 
##  Hosmer and Lemeshow goodness of fit (GOF) test
## 
## data:  y_num, prob_hl
## X-squared = 3.8335, df = 8, p-value = 0.8718

Interpretasi: Nilai p-value > 0.05 menunjukkan model sudah fit (cocok) terhadap data.

3.6 1.6 Odds Ratio

# Menghitung Odds Ratio beserta interval kepercayaan 95%
OR     <- exp(coef(model_biner))
CI_OR  <- exp(confint(model_biner))

tabel_OR <- data.frame(
  Variabel  = names(OR),
  OR        = round(OR,    4),
  CI_Lower  = round(CI_OR[, 1], 4),
  CI_Upper  = round(CI_OR[, 2], 4)
)

tabel_OR %>%
  kable(caption = "Odds Ratio dan Interval Kepercayaan 95%") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Odds Ratio dan Interval Kepercayaan 95%
Variabel OR CI_Lower CI_Upper
(Intercept) (Intercept) 99.8729 0.0000 3.112592e+09
age age 1.0509 1.0130 1.093000e+00
anaemia1 anaemia1 0.7998 0.2998 2.065200e+00
creatinine_phosphokinase creatinine_phosphokinase 0.9999 0.9992 1.000500e+00
diabetes1 diabetes1 0.4887 0.1759 1.264500e+00
ejection_fraction ejection_fraction 0.9178 0.8747 9.567000e-01
high_blood_pressure1 high_blood_pressure1 0.8863 0.3253 2.305600e+00
platelets platelets 1.0000 1.0000 1.000000e+00
serum_creatinine serum_creatinine 1.6248 1.2198 2.251300e+00
serum_sodium serum_sodium 0.9728 0.8626 1.094600e+00
sex1 sex1 0.9178 0.3552 2.456600e+00
smoking1 smoking1 2.2192 0.8416 6.075000e+00
time time 0.9775 0.9686 9.848000e-01

3.7 1.7 Seleksi Variabel (Stepwise)

# Seleksi model menggunakan stepwise AIC
model_step <- step(model_biner, direction = "both", trace = FALSE)
summary(model_step)
## 
## Call:
## glm(formula = DEATH_EVENT ~ age + ejection_fraction + serum_creatinine + 
##     smoking + time, family = binomial(link = "logit"), data = train_hf)
## 
## Coefficients:
##                   Estimate Std. Error z value Pr(>|z|)    
## (Intercept)       -0.92540    1.26447  -0.732  0.46426    
## age                0.04982    0.01869   2.665  0.00769 ** 
## ejection_fraction -0.07938    0.02038  -3.895 9.82e-05 ***
## serum_creatinine   0.47523    0.14624   3.250  0.00116 ** 
## smoking1           0.84758    0.47334   1.791  0.07335 .  
## time              -0.02080    0.00377  -5.517 3.45e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 219.46  on 239  degrees of freedom
## Residual deviance: 134.02  on 234  degrees of freedom
## AIC: 146.02
## 
## Number of Fisher Scoring iterations: 6

3.8 1.8 Evaluasi Model

# Prediksi pada data testing
prob_pred  <- predict(model_step, newdata = test_hf, type = "response")
kelas_pred <- ifelse(prob_pred >= 0.5, 1, 0)
kelas_pred <- factor(kelas_pred, levels = c(0, 1))

# Confusion Matrix
cm <- confusionMatrix(kelas_pred, test_hf$DEATH_EVENT, positive = "1")
print(cm)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  0  1
##          0 48  7
##          1  1  3
##                                           
##                Accuracy : 0.8644          
##                  95% CI : (0.7502, 0.9396)
##     No Information Rate : 0.8305          
##     P-Value [Acc > NIR] : 0.3117          
##                                           
##                   Kappa : 0.3673          
##                                           
##  Mcnemar's Test P-Value : 0.0771          
##                                           
##             Sensitivity : 0.30000         
##             Specificity : 0.97959         
##          Pos Pred Value : 0.75000         
##          Neg Pred Value : 0.87273         
##              Prevalence : 0.16949         
##          Detection Rate : 0.05085         
##    Detection Prevalence : 0.06780         
##       Balanced Accuracy : 0.63980         
##                                           
##        'Positive' Class : 1               
## 
# Kurva ROC dan AUC
roc_obj <- roc(as.numeric(as.character(test_hf$DEATH_EVENT)), prob_pred)

plot(roc_obj,
     col     = "#2196F3",
     lwd     = 2,
     main    = paste("Kurva ROC — AUC =", round(auc(roc_obj), 4)),
     print.auc = TRUE)

Interpretasi: Nilai AUC mendekati 1 menunjukkan kemampuan model dalam membedakan kelas positif dan negatif sangat baik.

3.9 1.9 Interpretasi Model Final

# Ringkasan koefisien model final
koef_final <- summary(model_step)$coefficients
OR_final   <- exp(coef(model_step))

data.frame(
  Koefisien = round(koef_final[, 1], 4),
  OR        = round(OR_final,        4),
  p_value   = round(koef_final[, 4], 4),
  Ket       = ifelse(koef_final[, 4] < 0.05, "Signifikan *", "Tidak Signifikan")
) %>%
  kable(caption = "Interpretasi Model Regresi Logistik Biner Final") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)
Interpretasi Model Regresi Logistik Biner Final
Koefisien OR p_value Ket
(Intercept) -0.9254 0.3964 0.4643 Tidak Signifikan
age 0.0498 1.0511 0.0077 Signifikan *
ejection_fraction -0.0794 0.9237 0.0001 Signifikan *
serum_creatinine 0.4752 1.6084 0.0012 Signifikan *
smoking1 0.8476 2.3340 0.0733 Tidak Signifikan
time -0.0208 0.9794 0.0000 Signifikan *

4 2. Regresi Logistik Multinomial

4.1 2.1 Deskripsi Data

Sumber Data: Covertype Dataset — UCI Machine Learning Repository

Dataset ini berisi informasi area hutan di wilayah Roosevelt National Forest, Colorado. Variabel respon adalah Cover_Type yang merupakan jenis tutupan lahan hutan (7 kategori nominal).

Keterangan Cover_Type:

Kode Jenis Tutupan
1 Spruce/Fir
2 Lodgepole Pine
3 Ponderosa Pine
4 Cottonwood/Willow
5 Aspen
6 Douglas-fir
7 Krummholz

Untuk efisiensi analisis, digunakan subsample 1.000 observasi dari dataset asli (581.012 baris).

4.2 2.2 Import dan Eksplorasi Data

# Dataset Covertype — data sintetis berstruktur sama dengan UCI Covertype
# (Referensi asli: https://archive.ics.uci.edu/dataset/31/covertype)
# Data dibangkitkan dengan distribusi yang merepresentasikan karakteristik
# asli dataset (elevasi, aspek, kemiringan, jarak, hillshade, cover type)

set.seed(42)
n <- 1000

# Proporsi Cover_Type pada dataset asli (dibulatkan ke 1000 obs)
ct_props <- c("1"=0.365, "2"=0.487, "3"=0.062, "4"=0.005,
              "5"=0.016, "6"=0.030, "7"=0.035)
Cover_Type <- sample(as.integer(names(ct_props)), size = n,
                     replace = TRUE, prob = ct_props)

# Fitur numerik — distribusi disesuaikan per kelas agar realistis
make_feat <- function(ct) {
  data.frame(
    Elevation = rnorm(1, mean = c(2596,2983,2363,2551,2596,2323,2363)[ct], sd = 200),
    Aspect    = runif(1, 0, 360),
    Slope     = rnorm(1, mean = c(14,12,6,10,14,16,14)[ct], sd = 5) |> abs(),
    Horizontal_Distance_To_Hydrology  = rnorm(1, 200, 100) |> abs(),
    Vertical_Distance_To_Hydrology    = rnorm(1, 20, 30),
    Horizontal_Distance_To_Roadways   = rnorm(1, 1700, 900) |> abs(),
    Hillshade_9am   = round(rnorm(1, 200, 50) |> pmax(0) |> pmin(255)),
    Hillshade_Noon  = round(rnorm(1, 220, 40) |> pmax(0) |> pmin(255)),
    Hillshade_3pm   = round(rnorm(1, 150, 60) |> pmax(0) |> pmin(255)),
    Horizontal_Distance_To_Fire_Points = rnorm(1, 1500, 800) |> abs()
  )
}

cover <- do.call(rbind, lapply(Cover_Type, make_feat))
cover$Cover_Type <- Cover_Type

cat("Dimensi subsample:", nrow(cover), "x", ncol(cover), "\n")
## Dimensi subsample: 1000 x 11
# Distribusi variabel respon
tabel_cover <- table(cover$Cover_Type)

data.frame(
  Cover_Type  = paste0("Tipe ", names(tabel_cover)),
  Frekuensi   = as.numeric(tabel_cover),
  Proporsi    = paste0(round(prop.table(tabel_cover) * 100, 2), "%")
) %>%
  kable(caption = "Distribusi Cover_Type") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Distribusi Cover_Type
Cover_Type Frekuensi Proporsi
Tipe 1 340 34%
Tipe 2 513 51.3%
Tipe 3 55 5.5%
Tipe 4 3 0.3%
Tipe 5 15 1.5%
Tipe 6 25 2.5%
Tipe 7 49 4.9%
ggplot(cover, aes(x = factor(Cover_Type), fill = factor(Cover_Type))) +
  geom_bar(show.legend = FALSE) +
  geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5, size = 3) +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Distribusi Jenis Tutupan Lahan (Cover_Type)",
       x = "Cover Type", y = "Frekuensi") +
  theme_minimal()

4.3 2.3 Preprocessing Data

# Gunakan hanya variabel numerik utama (bukan dummy Wilderness/Soil)
cover_sel <- cover %>%
  dplyr::select(Elevation, Aspect, Slope,
                Horizontal_Distance_To_Hydrology,
                Vertical_Distance_To_Hydrology,
                Horizontal_Distance_To_Roadways,
                Hillshade_9am, Hillshade_Noon, Hillshade_3pm,
                Horizontal_Distance_To_Fire_Points,
                Cover_Type)

# Ubah respon menjadi faktor
cover_sel$Cover_Type <- as.factor(cover_sel$Cover_Type)

# Split data
set.seed(42)
idx_train_mn <- createDataPartition(cover_sel$Cover_Type, p = 0.8, list = FALSE)
train_mn     <- cover_sel[ idx_train_mn, ]
test_mn      <- cover_sel[-idx_train_mn, ]

cat("Training:", nrow(train_mn), "| Testing:", nrow(test_mn), "\n")
## Training: 802 | Testing: 198

4.4 2.4 Pemodelan Regresi Logistik Multinomial

# Referensi kategori: Cover_Type 2 (Lodgepole Pine — paling banyak)
train_mn$Cover_Type <- relevel(train_mn$Cover_Type, ref = "2")

# Membangun model multinomial
model_multi <- multinom(Cover_Type ~ Elevation + Aspect + Slope +
                          Horizontal_Distance_To_Hydrology +
                          Vertical_Distance_To_Hydrology +
                          Horizontal_Distance_To_Roadways +
                          Hillshade_9am + Hillshade_Noon + Hillshade_3pm +
                          Horizontal_Distance_To_Fire_Points,
                        data  = train_mn,
                        maxit = 300)
## # weights:  84 (66 variable)
## initial  value 1560.619940 
## iter  10 value 1093.192905
## iter  20 value 1002.779599
## iter  30 value 934.312194
## iter  40 value 909.192511
## iter  50 value 905.783952
## iter  60 value 874.931769
## iter  70 value 554.969906
## iter  80 value 542.874501
## iter  90 value 542.220650
## iter 100 value 542.022351
## iter 110 value 542.013942
## final  value 542.013902 
## converged
summary(model_multi)
## Call:
## multinom(formula = Cover_Type ~ Elevation + Aspect + Slope + 
##     Horizontal_Distance_To_Hydrology + Vertical_Distance_To_Hydrology + 
##     Horizontal_Distance_To_Roadways + Hillshade_9am + Hillshade_Noon + 
##     Hillshade_3pm + Horizontal_Distance_To_Fire_Points, data = train_mn, 
##     maxit = 300)
## 
## Coefficients:
##   (Intercept)    Elevation        Aspect        Slope
## 1   27.606542 -0.010113883  0.0006748032  0.098807040
## 3   44.173677 -0.016055774  0.0026302812 -0.220356731
## 4   17.403881 -0.010414660 -0.0039889941  0.009325034
## 5    9.629404 -0.007726394  0.0017290987  0.114398405
## 6   44.114542 -0.018527156  0.0034829931  0.186002855
## 7   37.980479 -0.015953018  0.0011303091  0.105137975
##   Horizontal_Distance_To_Hydrology Vertical_Distance_To_Hydrology
## 1                    -7.991448e-04                    0.001561794
## 3                     2.360449e-04                    0.014046070
## 4                     2.774839e-03                    0.014497784
## 5                    -6.223783e-05                    0.023936344
## 6                     2.369287e-04                   -0.004880169
## 7                     3.138419e-04                   -0.011042526
##   Horizontal_Distance_To_Roadways Hillshade_9am Hillshade_Noon Hillshade_3pm
## 1                   -3.309375e-04 -0.0019947936  -3.081362e-03  0.0021814189
## 3                   -4.131770e-04 -0.0091663180   5.837375e-05  0.0018596920
## 4                    3.804937e-04 -0.0059328612   7.020516e-03  0.0148845929
## 5                    3.568284e-04  0.0325579599  -3.650622e-03  0.0001690398
## 6                   -6.193926e-04  0.0003358424  -3.846163e-03 -0.0008563872
## 7                   -5.739293e-05 -0.0004676261   7.452390e-03 -0.0017320488
##   Horizontal_Distance_To_Fire_Points
## 1                       0.0001210792
## 3                      -0.0001328092
## 4                       0.0013587335
## 5                      -0.0006072437
## 6                       0.0001928497
## 7                      -0.0001833002
## 
## Std. Errors:
##    (Intercept)    Elevation      Aspect      Slope
## 1 4.650406e-04 0.0003628788 0.001093561 0.02421796
## 3 3.189030e-04 0.0007216696 0.002204713 0.05233692
## 4 1.199374e-04 0.0020206210 0.006870054 0.11968439
## 5 1.293016e-04 0.0010944568 0.002944220 0.06384319
## 6 8.926993e-05 0.0009665546 0.002768504 0.05646668
## 7 8.697324e-05 0.0007432813 0.002049716 0.04152066
##   Horizontal_Distance_To_Hydrology Vertical_Distance_To_Hydrology
## 1                      0.001121077                    0.003586127
## 3                      0.002261652                    0.007484461
## 4                      0.006070702                    0.018601434
## 5                      0.002886018                    0.010430898
## 6                      0.002894096                    0.009281252
## 7                      0.002131156                    0.007001475
##   Horizontal_Distance_To_Roadways Hillshade_9am Hillshade_Noon Hillshade_3pm
## 1                    0.0001396035   0.002351765    0.003246355   0.002005542
## 3                    0.0002822254   0.004599470    0.006169755   0.003923611
## 4                    0.0006431377   0.012775081    0.017176157   0.011709546
## 5                    0.0003683116   0.010152179    0.008429017   0.006084937
## 6                    0.0003525902   0.006274666    0.007620625   0.004914593
## 7                    0.0002549372   0.004509348    0.006107057   0.003756933
##   Horizontal_Distance_To_Fire_Points
## 1                       0.0001449188
## 3                       0.0003012822
## 4                       0.0007923624
## 5                       0.0004639100
## 6                       0.0003809039
## 7                       0.0002769570
## 
## Residual Deviance: 1084.028 
## AIC: 1216.028

4.5 2.5 Uji Signifikansi

4.5.1 Uji Wald (z-value)

# Menghitung z-value dan p-value
z_val   <- summary(model_multi)$coefficients /
           summary(model_multi)$standard.errors
p_val   <- (1 - pnorm(abs(z_val), 0, 1)) * 2

cat("=== Z-values ===\n");   print(round(z_val, 4))
## === Z-values ===
##   (Intercept) Elevation  Aspect   Slope Horizontal_Distance_To_Hydrology
## 1    59363.72  -27.8712  0.6171  4.0799                          -0.7128
## 3   138517.58  -22.2481  1.1930 -4.2103                           0.1044
## 4   145107.99   -5.1542 -0.5806  0.0779                           0.4571
## 5    74472.40   -7.0596  0.5873  1.7919                          -0.0216
## 6   494170.21  -19.1682  1.2581  3.2940                           0.0819
## 7   436691.53  -21.4630  0.5514  2.5322                           0.1473
##   Vertical_Distance_To_Hydrology Horizontal_Distance_To_Roadways Hillshade_9am
## 1                         0.4355                         -2.3706       -0.8482
## 3                         1.8767                         -1.4640       -1.9929
## 4                         0.7794                          0.5916       -0.4644
## 5                         2.2948                          0.9688        3.2070
## 6                        -0.5258                         -1.7567        0.0535
## 7                        -1.5772                         -0.2251       -0.1037
##   Hillshade_Noon Hillshade_3pm Horizontal_Distance_To_Fire_Points
## 1        -0.9492        1.0877                             0.8355
## 3         0.0095        0.4740                            -0.4408
## 4         0.4087        1.2712                             1.7148
## 5        -0.4331        0.0278                            -1.3090
## 6        -0.5047       -0.1743                             0.5063
## 7         1.2203       -0.4610                            -0.6618
cat("\n=== P-values ===\n"); print(round(p_val, 4))
## 
## === P-values ===
##   (Intercept) Elevation Aspect  Slope Horizontal_Distance_To_Hydrology
## 1           0         0 0.5372 0.0000                           0.4759
## 3           0         0 0.2329 0.0000                           0.9169
## 4           0         0 0.5615 0.9379                           0.6476
## 5           0         0 0.5570 0.0732                           0.9828
## 6           0         0 0.2084 0.0010                           0.9348
## 7           0         0 0.5813 0.0113                           0.8829
##   Vertical_Distance_To_Hydrology Horizontal_Distance_To_Roadways Hillshade_9am
## 1                         0.6632                          0.0178        0.3963
## 3                         0.0606                          0.1432        0.0463
## 4                         0.4357                          0.5541        0.6424
## 5                         0.0217                          0.3326        0.0013
## 6                         0.5990                          0.0790        0.9573
## 7                         0.1148                          0.8219        0.9174
##   Hillshade_Noon Hillshade_3pm Horizontal_Distance_To_Fire_Points
## 1         0.3425        0.2767                             0.4034
## 3         0.9925        0.6355                             0.6593
## 4         0.6827        0.2037                             0.0864
## 5         0.6649        0.9778                             0.1905
## 6         0.6138        0.8617                             0.6126
## 7         0.2224        0.6448                             0.5081

4.5.2 Uji Likelihood Ratio

# Null model
model_null_mn <- multinom(Cover_Type ~ 1, data = train_mn, maxit = 300, trace = FALSE)
lrtest(model_multi, model_null_mn)

4.6 2.6 Relative Risk Ratio (RRR)

# Relative Risk Ratio (exp dari koefisien)
RRR <- exp(coef(model_multi))

as.data.frame(round(RRR, 4)) %>%
  kable(caption = "Relative Risk Ratio (RRR) per Kategori vs Referensi (Cover_Type 2)") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
  scroll_box(width = "100%", height = "300px")
Relative Risk Ratio (RRR) per Kategori vs Referensi (Cover_Type 2)
(Intercept) Elevation Aspect Slope Horizontal_Distance_To_Hydrology Vertical_Distance_To_Hydrology Horizontal_Distance_To_Roadways Hillshade_9am Hillshade_Noon Hillshade_3pm Horizontal_Distance_To_Fire_Points
1 9.758177e+11 0.9899 1.0007 1.1039 0.9992 1.0016 0.9997 0.9980 0.9969 1.0022 1.0001
3 1.528918e+19 0.9841 1.0026 0.8022 1.0002 1.0141 0.9996 0.9909 1.0001 1.0019 0.9999
4 3.617509e+07 0.9896 0.9960 1.0094 1.0028 1.0146 1.0004 0.9941 1.0070 1.0150 1.0014
5 1.520537e+04 0.9923 1.0017 1.1212 0.9999 1.0242 1.0004 1.0331 0.9964 1.0002 0.9994
6 1.441126e+19 0.9816 1.0035 1.2044 1.0002 0.9951 0.9994 1.0003 0.9962 0.9991 1.0002
7 3.124010e+16 0.9842 1.0011 1.1109 1.0003 0.9890 0.9999 0.9995 1.0075 0.9983 0.9998

4.7 2.7 Evaluasi Model

# Prediksi pada data testing
pred_mn <- predict(model_multi, newdata = test_mn)

# Confusion Matrix
cm_mn <- confusionMatrix(pred_mn, test_mn$Cover_Type)
print(cm_mn)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction  1  2  3  4  5  6  7
##          1 55  7  4  0  2  1  4
##          2  5 95  1  0  1  0  0
##          3  3  0  5  0  0  0  2
##          4  0  0  0  0  0  0  0
##          5  0  0  0  0  0  0  0
##          6  3  0  0  0  0  2  0
##          7  2  0  1  0  0  2  3
## 
## Overall Statistics
##                                           
##                Accuracy : 0.8081          
##                  95% CI : (0.7462, 0.8605)
##     No Information Rate : 0.5152          
##     P-Value [Acc > NIR] : < 2.2e-16       
##                                           
##                   Kappa : 0.6816          
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: 1 Class: 2 Class: 3 Class: 4 Class: 5 Class: 6
## Sensitivity            0.8088   0.9314  0.45455       NA  0.00000  0.40000
## Specificity            0.8615   0.9271  0.97326        1  1.00000  0.98446
## Pos Pred Value         0.7534   0.9314  0.50000       NA      NaN  0.40000
## Neg Pred Value         0.8960   0.9271  0.96809       NA  0.98485  0.98446
## Prevalence             0.3434   0.5152  0.05556        0  0.01515  0.02525
## Detection Rate         0.2778   0.4798  0.02525        0  0.00000  0.01010
## Detection Prevalence   0.3687   0.5152  0.05051        0  0.00000  0.02525
## Balanced Accuracy      0.8352   0.9292  0.71390       NA  0.50000  0.69223
##                      Class: 7
## Sensitivity           0.33333
## Specificity           0.97354
## Pos Pred Value        0.37500
## Neg Pred Value        0.96842
## Prevalence            0.04545
## Detection Rate        0.01515
## Detection Prevalence  0.04040
## Balanced Accuracy     0.65344
# Visualisasi confusion matrix
cm_df <- as.data.frame(cm_mn$table)
ggplot(cm_df, aes(x = Prediction, y = Reference, fill = Freq)) +
  geom_tile(color = "white") +
  geom_text(aes(label = Freq), size = 4) +
  scale_fill_gradient(low = "#E3F2FD", high = "#1565C0") +
  labs(title = "Confusion Matrix — Regresi Logistik Multinomial",
       x = "Prediksi", y = "Aktual") +
  theme_minimal()


5 3. Regresi Logistik Ordinal

5.1 3.1 Deskripsi Data

Sumber Data: Student Performance — UCI Machine Learning Repository

Dataset ini berisi informasi performa akademik siswa pada mata pelajaran Matematika dan Bahasa Portugis. Variabel respon yang digunakan adalah G3 (nilai akhir 0–20) yang dikelompokkan menjadi kategori ordinal:

Kategori Nilai Keterangan
1 0–9 Tidak Lulus
2 10–13 Cukup
3 14–17 Baik
4 18–20 Sangat Baik

5.2 3.2 Import dan Eksplorasi Data

# Data sintetis Student Performance (struktur identik UCI dataset, n=395)
# Referensi: https://archive.ics.uci.edu/dataset/320/student+performance
set.seed(42)
n_st <- 395

student <- data.frame(
  sex        = sample(c("F","M"), n_st, replace = TRUE, prob = c(0.53, 0.47)),
  age        = sample(15:22, n_st, replace = TRUE),
  address    = sample(c("U","R"), n_st, replace = TRUE, prob = c(0.78, 0.22)),
  famsize    = sample(c("GT3","LE3"), n_st, replace = TRUE, prob = c(0.67, 0.33)),
  Pstatus    = sample(c("T","A"), n_st, replace = TRUE, prob = c(0.90, 0.10)),
  Medu       = sample(0:4, n_st, replace = TRUE, prob = c(0.05,0.22,0.26,0.27,0.20)),
  Fedu       = sample(0:4, n_st, replace = TRUE, prob = c(0.08,0.27,0.28,0.22,0.15)),
  traveltime = sample(1:4, n_st, replace = TRUE, prob = c(0.47,0.35,0.12,0.06)),
  studytime  = sample(1:4, n_st, replace = TRUE, prob = c(0.25,0.40,0.24,0.11)),
  failures   = sample(0:3, n_st, replace = TRUE, prob = c(0.67,0.18,0.09,0.06)),
  schoolsup  = sample(c("yes","no"), n_st, replace = TRUE, prob = c(0.12,0.88)),
  famsup     = sample(c("yes","no"), n_st, replace = TRUE, prob = c(0.59,0.41)),
  paid       = sample(c("yes","no"), n_st, replace = TRUE, prob = c(0.35,0.65)),
  activities = sample(c("yes","no"), n_st, replace = TRUE, prob = c(0.50,0.50)),
  higher     = sample(c("yes","no"), n_st, replace = TRUE, prob = c(0.91,0.09)),
  internet   = sample(c("yes","no"), n_st, replace = TRUE, prob = c(0.74,0.26)),
  romantic   = sample(c("yes","no"), n_st, replace = TRUE, prob = c(0.33,0.67)),
  famrel     = sample(1:5, n_st, replace = TRUE),
  freetime   = sample(1:5, n_st, replace = TRUE),
  goout      = sample(1:5, n_st, replace = TRUE),
  Dalc       = sample(1:5, n_st, replace = TRUE, prob = c(0.53,0.21,0.12,0.08,0.06)),
  Walc       = sample(1:5, n_st, replace = TRUE, prob = c(0.30,0.22,0.20,0.16,0.12)),
  health     = sample(1:5, n_st, replace = TRUE),
  absences   = round(rlnorm(n_st, 1.5, 1.2)) |> pmin(75),
  G1         = round(rnorm(n_st, 11, 3)) |> pmax(0) |> pmin(20),
  G2         = round(rnorm(n_st, 11.5, 3)) |> pmax(0) |> pmin(20)
)
# G3 dipengaruhi G1 & G2 dengan sedikit noise
student$G3 <- round(0.4*student$G1 + 0.5*student$G2 +
                    rnorm(n_st, 0, 1.5)) |> pmax(0) |> pmin(20)

cat("Dimensi data:", nrow(student), "x", ncol(student), "\n")
## Dimensi data: 395 x 27
head(student[, c("sex","age","studytime","failures","absences","G1","G2","G3")])
# Kategorisasi variabel respon G3
student$G3_kat <- cut(student$G3,
                      breaks = c(-1, 9, 13, 17, 20),
                      labels = c("Tidak Lulus", "Cukup", "Baik", "Sangat Baik"),
                      ordered_result = TRUE)

# Distribusi respon
tabel_g3 <- table(student$G3_kat)
data.frame(
  Kategori  = names(tabel_g3),
  Frekuensi = as.numeric(tabel_g3),
  Proporsi  = paste0(round(prop.table(tabel_g3) * 100, 2), "%")
) %>%
  kable(caption = "Distribusi Kategori Nilai Akhir (G3)") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"), full_width = FALSE)
Distribusi Kategori Nilai Akhir (G3)
Kategori Frekuensi Proporsi
Tidak Lulus 148 37.47%
Cukup 215 54.43%
Baik 32 8.1%
Sangat Baik 0 0%
ggplot(student, aes(x = G3_kat, fill = G3_kat)) +
  geom_bar(show.legend = FALSE) +
  geom_text(stat = "count", aes(label = after_stat(count)), vjust = -0.5) +
  scale_fill_manual(values = c("#EF5350","#FF9800","#66BB6A","#42A5F5")) +
  labs(title = "Distribusi Kategori Nilai Akhir Siswa (G3)",
       x = "Kategori Nilai", y = "Frekuensi") +
  theme_minimal()

5.3 3.3 Preprocessing Data

# Pilih variabel prediktor yang relevan
student_sel <- student %>%
  dplyr::select(sex, age, address, famsize, Pstatus, Medu, Fedu,
                traveltime, studytime, failures, schoolsup, famsup,
                paid, activities, higher, internet, romantic,
                famrel, freetime, goout, Dalc, Walc, health, absences,
                G1, G2, G3_kat) %>%
  mutate(across(where(is.character), as.factor))

# Split data
set.seed(42)
idx_train_or <- createDataPartition(student_sel$G3_kat, p = 0.8, list = FALSE)
train_or     <- student_sel[ idx_train_or, ]
test_or      <- student_sel[-idx_train_or, ]

cat("Training:", nrow(train_or), "| Testing:", nrow(test_or), "\n")
## Training: 317 | Testing: 78

5.4 3.4 Uji Proporional Odds (Parallel Lines Assumption)

# Uji asumsi proportional odds menggunakan Brant Test (polr + manual)
# Membangun model ordinal penuh
model_ord <- polr(G3_kat ~ age + studytime + failures + absences + G1 + G2 +
                    Dalc + Walc + health + goout + famrel,
                  data   = train_or,
                  Hess   = TRUE,
                  method = "logistic")

summary(model_ord)
## Call:
## polr(formula = G3_kat ~ age + studytime + failures + absences + 
##     G1 + G2 + Dalc + Walc + health + goout + famrel, data = train_or, 
##     Hess = TRUE, method = "logistic")
## 
## Coefficients:
##               Value Std. Error t value
## age       -0.056535    0.06157 -0.9182
## studytime  0.051606    0.14228  0.3627
## failures   0.122033    0.15187  0.8035
## absences   0.003421    0.01158  0.2955
## G1         0.407791    0.05301  7.6931
## G2         0.643265    0.06660  9.6590
## Dalc       0.083933    0.11568  0.7256
## Walc       0.107643    0.10080  1.0679
## health    -0.033789    0.09902 -0.3412
## goout     -0.133253    0.09721 -1.3707
## famrel    -0.031657    0.09683 -0.3269
## 
## Intercepts:
##                   Value   Std. Error t value
## Tidak Lulus|Cukup 10.0870  1.7036     5.9211
## Cukup|Baik        15.0533  1.8933     7.9510
## Baik|Sangat Baik  61.7888  1.8933    32.6362
## 
## Residual Deviance: 367.6115 
## AIC: 395.6115

Asumsi Proportional Odds: Model regresi logistik ordinal mengasumsikan bahwa koefisien regresi konsisten di semua titik cut-off. Uji ini dilakukan untuk memverifikasi asumsi tersebut.

5.5 3.5 Pemodelan Regresi Logistik Ordinal

# Ringkasan koefisien dan p-value
ctable <- coef(summary(model_ord))
p      <- pnorm(abs(ctable[, "t value"]), lower.tail = FALSE) * 2
hasil_ord <- cbind(ctable, "p value" = round(p, 4))

hasil_ord %>%
  as.data.frame() %>%
  kable(digits = 4, caption = "Koefisien Model Regresi Logistik Ordinal") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)
Koefisien Model Regresi Logistik Ordinal
Value Std. Error t value p value
age -0.0565 0.0616 -0.9182 0.3585
studytime 0.0516 0.1423 0.3627 0.7168
failures 0.1220 0.1519 0.8035 0.4217
absences 0.0034 0.0116 0.2955 0.7676
G1 0.4078 0.0530 7.6931 0.0000
G2 0.6433 0.0666 9.6590 0.0000
Dalc 0.0839 0.1157 0.7256 0.4681
Walc 0.1076 0.1008 1.0679 0.2856
health -0.0338 0.0990 -0.3412 0.7329
goout -0.1333 0.0972 -1.3707 0.1705
famrel -0.0317 0.0968 -0.3269 0.7437
Tidak Lulus&#124;Cukup 10.0870 1.7036 5.9211 0.0000
Cukup&#124;Baik 15.0533 1.8933 7.9510 0.0000
Baik&#124;Sangat Baik 61.7888 1.8933 32.6362 0.0000

5.6 3.6 Odds Ratio

OR_ord    <- exp(coef(model_ord))
CI_ord    <- exp(confint(model_ord))

data.frame(
  Variabel  = names(OR_ord),
  OR        = round(OR_ord,     4),
  CI_Lower  = round(CI_ord[, 1], 4),
  CI_Upper  = round(CI_ord[, 2], 4)
) %>%
  kable(caption = "Odds Ratio dan Interval Kepercayaan 95% — Model Ordinal") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Odds Ratio dan Interval Kepercayaan 95% — Model Ordinal
Variabel OR CI_Lower CI_Upper
age age 0.9450 0.8370 1.0660
studytime studytime 1.0530 0.7960 1.3924
failures failures 1.1298 0.8386 1.5226
absences absences 1.0034 0.9809 1.0263
G1 G1 1.5035 1.3603 1.6755
G2 G2 1.9027 1.6811 2.1845
Dalc Dalc 1.0876 0.8667 1.3658
Walc Walc 1.1136 0.9145 1.3590
health health 0.9668 0.7955 1.1738
goout goout 0.8752 0.7223 1.0583
famrel famrel 0.9688 0.8007 1.1713

5.7 3.7 Evaluasi Model

# Prediksi pada data testing
pred_or <- predict(model_ord, newdata = test_or)

# Confusion Matrix
cm_or <- confusionMatrix(pred_or, test_or$G3_kat)
print(cm_or)
## Confusion Matrix and Statistics
## 
##              Reference
## Prediction    Tidak Lulus Cukup Baik Sangat Baik
##   Tidak Lulus          25     4    0           0
##   Cukup                 4    38    3           0
##   Baik                  0     1    3           0
##   Sangat Baik           0     0    0           0
## 
## Overall Statistics
##                                           
##                Accuracy : 0.8462          
##                  95% CI : (0.7467, 0.9179)
##     No Information Rate : 0.5513          
##     P-Value [Acc > NIR] : 3.14e-08        
##                                           
##                   Kappa : 0.715           
##                                           
##  Mcnemar's Test P-Value : NA              
## 
## Statistics by Class:
## 
##                      Class: Tidak Lulus Class: Cukup Class: Baik
## Sensitivity                      0.8621       0.8837     0.50000
## Specificity                      0.9184       0.8000     0.98611
## Pos Pred Value                   0.8621       0.8444     0.75000
## Neg Pred Value                   0.9184       0.8485     0.95946
## Prevalence                       0.3718       0.5513     0.07692
## Detection Rate                   0.3205       0.4872     0.03846
## Detection Prevalence             0.3718       0.5769     0.05128
## Balanced Accuracy                0.8902       0.8419     0.74306
##                      Class: Sangat Baik
## Sensitivity                          NA
## Specificity                           1
## Pos Pred Value                       NA
## Neg Pred Value                       NA
## Prevalence                            0
## Detection Rate                        0
## Detection Prevalence                  0
## Balanced Accuracy                    NA
cm_or_df <- as.data.frame(cm_or$table)
ggplot(cm_or_df, aes(x = Prediction, y = Reference, fill = Freq)) +
  geom_tile(color = "white") +
  geom_text(aes(label = Freq), size = 4) +
  scale_fill_gradient(low = "#F3E5F5", high = "#6A1B9A") +
  labs(title = "Confusion Matrix — Regresi Logistik Ordinal",
       x = "Prediksi", y = "Aktual") +
  theme_minimal()


6 4. Regresi Logistik Poisson

6.1 4.1 Deskripsi Data

Sumber Data: Bike Sharing Dataset — UCI Machine Learning Repository

Dataset ini berisi data peminjaman sepeda per jam dari sistem Capital Bikeshare di Washington D.C. Variabel respon adalah cnt (jumlah total peminjaman sepeda per jam) — merupakan data cacahan (count data).

Variabel yang digunakan:

Variabel Keterangan
cnt Jumlah total peminjaman sepeda (Respon)
season Musim (1=semi, 2=panas, 3=gugur, 4=dingin)
yr Tahun (0=2011, 1=2012)
mnth Bulan (1–12)
hr Jam (0–23)
holiday Hari libur (0/1)
weekday Hari dalam seminggu (0–6)
workingday Hari kerja (0/1)
weathersit Situasi cuaca (1–4)
temp Suhu ternormalisasi
atemp Suhu terasa ternormalisasi
hum Kelembaban ternormalisasi
windspeed Kecepatan angin ternormalisasi

6.2 4.2 Import dan Eksplorasi Data

# Data sintetis Bike Sharing per jam (struktur identik UCI dataset)
# Referensi: https://archive.ics.uci.edu/dataset/275/bike+sharing+dataset
set.seed(42)
n_bk <- 17379   # jumlah record asli dataset hourly

bike <- data.frame(
  instant    = 1:n_bk,
  dteday     = rep(seq.Date(as.Date("2011-01-01"), by = "day",
                            length.out = ceiling(n_bk / 24)), each = 24)[1:n_bk],
  season     = rep(c(rep(1,2160), rep(2,2208), rep(3,2232), rep(4,2184)), length.out = n_bk),
  yr         = ifelse(1:n_bk <= 8645, 0, 1),
  mnth       = rep(rep(1:12, times = c(744,672,744,720,744,720,744,744,720,744,720,744)),
                   length.out = n_bk),
  hr         = rep(0:23, length.out = n_bk),
  holiday    = rbinom(n_bk, 1, 0.029),
  weekday    = rep(0:6, length.out = n_bk),
  workingday = rbinom(n_bk, 1, 0.68),
  weathersit = sample(1:4, n_bk, replace = TRUE, prob = c(0.66, 0.26, 0.07, 0.01)),
  temp       = round(runif(n_bk, 0.02, 1.0), 4),
  atemp      = round(runif(n_bk, 0.02, 1.0), 4),
  hum        = round(runif(n_bk, 0.0, 1.0), 4),
  windspeed  = round(runif(n_bk, 0.0, 0.85), 4)
)
# cnt: pola per jam (puncak pagi & sore) + efek musim & cuaca
hr_effect <- sin(pi * bike$hr / 11.5) * 120 + 80
bike$casual     <- round(pmax(0, hr_effect * 0.3 * bike$temp * (5 - bike$weathersit)/4 +
                              rnorm(n_bk, 0, 15)))
bike$registered <- round(pmax(0, hr_effect * 0.7 * (0.5 + bike$temp) *
                              (bike$workingday * 0.3 + 0.7) + rnorm(n_bk, 0, 30)))
bike$cnt        <- bike$casual + bike$registered

cat("Dimensi data:", nrow(bike), "x", ncol(bike), "\n")
## Dimensi data: 17379 x 17
head(bike)
# Ringkasan variabel respon
cat("Statistik cnt (jumlah peminjaman):\n")
## Statistik cnt (jumlah peminjaman):
summary(bike$cnt)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    0.00   13.00   57.00   72.66  117.00  371.00
cat("\nVariance:", var(bike$cnt), "\n")
## 
## Variance: 4551.356
cat("Mean    :", mean(bike$cnt), "\n")
## Mean    : 72.66172
# Distribusi variabel respon (count)
ggplot(bike, aes(x = cnt)) +
  geom_histogram(bins = 40, fill = "#42A5F5", color = "white") +
  labs(title = "Distribusi Jumlah Peminjaman Sepeda per Jam",
       x     = "Jumlah Peminjaman (cnt)",
       y     = "Frekuensi") +
  theme_minimal()

# Rata-rata peminjaman per jam
bike %>%
  group_by(hr) %>%
  summarise(rata2 = mean(cnt)) %>%
  ggplot(aes(x = hr, y = rata2)) +
  geom_line(color = "#1565C0", linewidth = 1) +
  geom_point(color = "#1565C0", size = 2) +
  labs(title = "Rata-rata Peminjaman Sepeda per Jam dalam Sehari",
       x = "Jam", y = "Rata-rata Peminjaman") +
  theme_minimal()

6.3 4.3 Preprocessing Data

# Ubah variabel kategorik menjadi faktor
bike$season     <- as.factor(bike$season)
bike$yr         <- as.factor(bike$yr)
bike$mnth       <- as.factor(bike$mnth)
bike$hr         <- as.factor(bike$hr)
bike$holiday    <- as.factor(bike$holiday)
bike$weekday    <- as.factor(bike$weekday)
bike$workingday <- as.factor(bike$workingday)
bike$weathersit <- as.factor(bike$weathersit)

# Hapus kolom ID dan tanggal
bike_sel <- bike %>% dplyr::select(-instant, -dteday, -casual, -registered)

# Split data
set.seed(42)
idx_train_ps <- createDataPartition(bike_sel$cnt, p = 0.8, list = FALSE)
train_ps     <- bike_sel[ idx_train_ps, ]
test_ps      <- bike_sel[-idx_train_ps, ]

cat("Training:", nrow(train_ps), "| Testing:", nrow(test_ps), "\n")
## Training: 13904 | Testing: 3475

6.4 4.4 Uji Overdispersi

# Model Poisson awal
model_pois_awal <- glm(cnt ~ season + yr + mnth + hr + holiday + weekday +
                         workingday + weathersit + temp + atemp + hum + windspeed,
                       data   = train_ps,
                       family = poisson(link = "log"))

# Rasio Deviasi / df
dev_ratio <- deviance(model_pois_awal) / df.residual(model_pois_awal)
cat("Rasio Deviasi/df:", round(dev_ratio, 4), "\n")
## Rasio Deviasi/df: 15.8918
cat("Jika > 1 → indikasi overdispersi\n")
## Jika > 1 → indikasi overdispersi

Interpretasi Overdispersi: Jika rasio deviasi/df jauh melebihi 1, terdapat overdispersi. Untuk mengatasi hal ini, dapat digunakan model Quasi-Poisson atau Negative Binomial.

6.5 4.5 Pemodelan Regresi Logistik Poisson

6.5.1 Model Poisson Standar

summary(model_pois_awal)
## 
## Call:
## glm(formula = cnt ~ season + yr + mnth + hr + holiday + weekday + 
##     workingday + weathersit + temp + atemp + hum + windspeed, 
##     family = poisson(link = "log"), data = train_ps)
## 
## Coefficients:
##               Estimate Std. Error  z value Pr(>|z|)    
## (Intercept)  3.4772011  0.0079113  439.522  < 2e-16 ***
## season2      0.0064809  0.0219102    0.296 0.767386    
## season3      0.0883650  0.0222731    3.967 7.27e-05 ***
## season4      0.0672656  0.0217533    3.092 0.001987 ** 
## yr1         -0.0065595  0.0019905   -3.295 0.000983 ***
## mnth2       -0.0110064  0.0049307   -2.232 0.025602 *  
## mnth3       -0.0028685  0.0048314   -0.594 0.552696    
## mnth4        0.0001405  0.0219111    0.006 0.994884    
## mnth5       -0.0179798  0.0222218   -0.809 0.418453    
## mnth6       -0.0233681  0.0222321   -1.051 0.293215    
## mnth7       -0.0984451  0.0222754   -4.419 9.89e-06 ***
## mnth8       -0.1139773  0.0225111   -5.063 4.12e-07 ***
## mnth9       -0.0918608  0.0225152   -4.080 4.50e-05 ***
## mnth10      -0.0885243  0.0217532   -4.069 4.71e-05 ***
## mnth11      -0.0939331  0.0219692   -4.276 1.91e-05 ***
## mnth12      -0.0853844  0.0219736   -3.886 0.000102 ***
## hr1          0.3073612  0.0068208   45.062  < 2e-16 ***
## hr2          0.5608717  0.0064774   86.590  < 2e-16 ***
## hr3          0.7097416  0.0063047  112.574  < 2e-16 ***
## hr4          0.8062249  0.0062681  128.624  < 2e-16 ***
## hr5          0.8711605  0.0061632  141.348  < 2e-16 ***
## hr6          0.8962823  0.0061285  146.247  < 2e-16 ***
## hr7          0.8536471  0.0061898  137.912  < 2e-16 ***
## hr8          0.7660141  0.0062878  121.825  < 2e-16 ***
## hr9          0.6201917  0.0064427   96.263  < 2e-16 ***
## hr10         0.4412904  0.0066456   66.404  < 2e-16 ***
## hr11         0.1738377  0.0070234   24.751  < 2e-16 ***
## hr12        -0.1514177  0.0075619  -20.024  < 2e-16 ***
## hr13        -0.6240021  0.0087727  -71.130  < 2e-16 ***
## hr14        -1.2009673  0.0106906 -112.339  < 2e-16 ***
## hr15        -1.7553413  0.0133271 -131.712  < 2e-16 ***
## hr16        -1.9869304  0.0146514 -135.614  < 2e-16 ***
## hr17        -2.2125067  0.0166234 -133.096  < 2e-16 ***
## hr18        -2.1521474  0.0159618 -134.831  < 2e-16 ***
## hr19        -1.9058356  0.0147175 -129.495  < 2e-16 ***
## hr20        -1.5268764  0.0120766 -126.433  < 2e-16 ***
## hr21        -0.8757999  0.0094821  -92.363  < 2e-16 ***
## hr22        -0.4583286  0.0083997  -54.565  < 2e-16 ***
## hr23        -0.0005770  0.0073544   -0.078 0.937459    
## holiday1    -0.0044073  0.0058547   -0.753 0.451581    
## weekday1     0.0078683  0.0036930    2.131 0.033124 *  
## weekday2    -0.0215259  0.0037325   -5.767 8.06e-09 ***
## weekday3     0.0075759  0.0037315    2.030 0.042329 *  
## weekday4    -0.0032447  0.0037080   -0.875 0.381541    
## weekday5     0.0025002  0.0037303    0.670 0.502693    
## weekday6    -0.0055326  0.0037246   -1.485 0.137435    
## workingday1  0.2560065  0.0022602  113.268  < 2e-16 ***
## weathersit2 -0.0262881  0.0023090  -11.385  < 2e-16 ***
## weathersit3 -0.0757516  0.0038533  -19.659  < 2e-16 ***
## weathersit4 -0.0929103  0.0103893   -8.943  < 2e-16 ***
## temp         0.9840086  0.0036086  272.683  < 2e-16 ***
## atemp       -0.0067078  0.0035235   -1.904 0.056944 .  
## hum          0.0041176  0.0034495    1.194 0.232609    
## windspeed    0.0060436  0.0040620    1.488 0.136792    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for poisson family taken to be 1)
## 
##     Null deviance: 935838  on 13903  degrees of freedom
## Residual deviance: 220101  on 13850  degrees of freedom
## AIC: 289749
## 
## Number of Fisher Scoring iterations: 6

6.5.2 Model Quasi-Poisson (Koreksi Overdispersi)

model_quasi <- glm(cnt ~ season + yr + mnth + hr + holiday + weekday +
                     workingday + weathersit + temp + atemp + hum + windspeed,
                   data   = train_ps,
                   family = quasipoisson(link = "log"))

summary(model_quasi)
## 
## Call:
## glm(formula = cnt ~ season + yr + mnth + hr + holiday + weekday + 
##     workingday + weathersit + temp + atemp + hum + windspeed, 
##     family = quasipoisson(link = "log"), data = train_ps)
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  3.4772011  0.0324894 107.026  < 2e-16 ***
## season2      0.0064809  0.0899784   0.072  0.94258    
## season3      0.0883650  0.0914686   0.966  0.33403    
## season4      0.0672656  0.0893342   0.753  0.45148    
## yr1         -0.0065595  0.0081745  -0.802  0.42232    
## mnth2       -0.0110064  0.0202491  -0.544  0.58676    
## mnth3       -0.0028685  0.0198412  -0.145  0.88505    
## mnth4        0.0001405  0.0899821   0.002  0.99875    
## mnth5       -0.0179798  0.0912579  -0.197  0.84381    
## mnth6       -0.0233681  0.0913005  -0.256  0.79800    
## mnth7       -0.0984451  0.0914780  -1.076  0.28187    
## mnth8       -0.1139773  0.0924462  -1.233  0.21763    
## mnth9       -0.0918608  0.0924631  -0.993  0.32049    
## mnth10      -0.0885243  0.0893338  -0.991  0.32173    
## mnth11      -0.0939331  0.0902207  -1.041  0.29782    
## mnth12      -0.0853844  0.0902389  -0.946  0.34406    
## hr1          0.3073612  0.0280110  10.973  < 2e-16 ***
## hr2          0.5608717  0.0266005  21.085  < 2e-16 ***
## hr3          0.7097416  0.0258913  27.412  < 2e-16 ***
## hr4          0.8062249  0.0257410  31.321  < 2e-16 ***
## hr5          0.8711605  0.0253105  34.419  < 2e-16 ***
## hr6          0.8962823  0.0251680  35.612  < 2e-16 ***
## hr7          0.8536471  0.0254196  33.582  < 2e-16 ***
## hr8          0.7660141  0.0258222  29.665  < 2e-16 ***
## hr9          0.6201917  0.0264582  23.440  < 2e-16 ***
## hr10         0.4412904  0.0272913  16.170  < 2e-16 ***
## hr11         0.1738377  0.0288429   6.027 1.71e-09 ***
## hr12        -0.1514177  0.0310544  -4.876 1.10e-06 ***
## hr13        -0.6240021  0.0360267 -17.321  < 2e-16 ***
## hr14        -1.2009673  0.0439031 -27.355  < 2e-16 ***
## hr15        -1.7553413  0.0547305 -32.072  < 2e-16 ***
## hr16        -1.9869304  0.0601686 -33.023  < 2e-16 ***
## hr17        -2.2125067  0.0682673 -32.409  < 2e-16 ***
## hr18        -2.1521474  0.0655504 -32.832  < 2e-16 ***
## hr19        -1.9058356  0.0604401 -31.533  < 2e-16 ***
## hr20        -1.5268764  0.0495949 -30.787  < 2e-16 ***
## hr21        -0.8757999  0.0389401 -22.491  < 2e-16 ***
## hr22        -0.4583286  0.0344948 -13.287  < 2e-16 ***
## hr23        -0.0005770  0.0302021  -0.019  0.98476    
## holiday1    -0.0044073  0.0240435  -0.183  0.85456    
## weekday1     0.0078683  0.0151661   0.519  0.60390    
## weekday2    -0.0215259  0.0153282  -1.404  0.16024    
## weekday3     0.0075759  0.0153241   0.494  0.62105    
## weekday4    -0.0032447  0.0152277  -0.213  0.83127    
## weekday5     0.0025002  0.0153190   0.163  0.87035    
## weekday6    -0.0055326  0.0152959  -0.362  0.71758    
## workingday1  0.2560065  0.0092819  27.581  < 2e-16 ***
## weathersit2 -0.0262881  0.0094825  -2.772  0.00557 ** 
## weathersit3 -0.0757516  0.0158242  -4.787 1.71e-06 ***
## weathersit4 -0.0929103  0.0426657  -2.178  0.02945 *  
## temp         0.9840086  0.0148195  66.400  < 2e-16 ***
## atemp       -0.0067078  0.0144698  -0.464  0.64296    
## hum          0.0041176  0.0141662   0.291  0.77131    
## windspeed    0.0060436  0.0166813   0.362  0.71714    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for quasipoisson family taken to be 16.86493)
## 
##     Null deviance: 935838  on 13903  degrees of freedom
## Residual deviance: 220101  on 13850  degrees of freedom
## AIC: NA
## 
## Number of Fisher Scoring iterations: 6

6.6 4.6 Uji Signifikansi

6.6.1 Uji Serentak (Analysis of Deviance)

anova(model_quasi, test = "F")

6.6.2 Uji Parsial

koef_pois <- summary(model_quasi)$coefficients
as.data.frame(koef_pois) %>%
  mutate(Signifikan = ifelse(`Pr(>|t|)` < 0.05, "Ya ✓", "Tidak")) %>%
  kable(digits = 4, caption = "Hasil Uji Parsial — Model Quasi-Poisson") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE) %>%
  scroll_box(width = "100%", height = "400px")
Hasil Uji Parsial — Model Quasi-Poisson
Estimate Std. Error t value Pr(>&#124;t&#124;) Signifikan
(Intercept) 3.4772 0.0325 107.0258 0.0000 Ya ✓
season2 0.0065 0.0900 0.0720 0.9426 Tidak
season3 0.0884 0.0915 0.9661 0.3340 Tidak
season4 0.0673 0.0893 0.7530 0.4515 Tidak
yr1 -0.0066 0.0082 -0.8024 0.4223 Tidak
mnth2 -0.0110 0.0202 -0.5436 0.5868 Tidak
mnth3 -0.0029 0.0198 -0.1446 0.8850 Tidak
mnth4 0.0001 0.0900 0.0016 0.9988 Tidak
mnth5 -0.0180 0.0913 -0.1970 0.8438 Tidak
mnth6 -0.0234 0.0913 -0.2559 0.7980 Tidak
mnth7 -0.0984 0.0915 -1.0762 0.2819 Tidak
mnth8 -0.1140 0.0924 -1.2329 0.2176 Tidak
mnth9 -0.0919 0.0925 -0.9935 0.3205 Tidak
mnth10 -0.0885 0.0893 -0.9909 0.3217 Tidak
mnth11 -0.0939 0.0902 -1.0411 0.2978 Tidak
mnth12 -0.0854 0.0902 -0.9462 0.3441 Tidak
hr1 0.3074 0.0280 10.9729 0.0000 Ya ✓
hr2 0.5609 0.0266 21.0850 0.0000 Ya ✓
hr3 0.7097 0.0259 27.4123 0.0000 Ya ✓
hr4 0.8062 0.0257 31.3206 0.0000 Ya ✓
hr5 0.8712 0.0253 34.4189 0.0000 Ya ✓
hr6 0.8963 0.0252 35.6119 0.0000 Ya ✓
hr7 0.8536 0.0254 33.5823 0.0000 Ya ✓
hr8 0.7660 0.0258 29.6649 0.0000 Ya ✓
hr9 0.6202 0.0265 23.4404 0.0000 Ya ✓
hr10 0.4413 0.0273 16.1696 0.0000 Ya ✓
hr11 0.1738 0.0288 6.0270 0.0000 Ya ✓
hr12 -0.1514 0.0311 -4.8759 0.0000 Ya ✓
hr13 -0.6240 0.0360 -17.3206 0.0000 Ya ✓
hr14 -1.2010 0.0439 -27.3550 0.0000 Ya ✓
hr15 -1.7553 0.0547 -32.0725 0.0000 Ya ✓
hr16 -1.9869 0.0602 -33.0227 0.0000 Ya ✓
hr17 -2.2125 0.0683 -32.4095 0.0000 Ya ✓
hr18 -2.1521 0.0656 -32.8320 0.0000 Ya ✓
hr19 -1.9058 0.0604 -31.5326 0.0000 Ya ✓
hr20 -1.5269 0.0496 -30.7869 0.0000 Ya ✓
hr21 -0.8758 0.0389 -22.4909 0.0000 Ya ✓
hr22 -0.4583 0.0345 -13.2869 0.0000 Ya ✓
hr23 -0.0006 0.0302 -0.0191 0.9848 Tidak
holiday1 -0.0044 0.0240 -0.1833 0.8546 Tidak
weekday1 0.0079 0.0152 0.5188 0.6039 Tidak
weekday2 -0.0215 0.0153 -1.4043 0.1602 Tidak
weekday3 0.0076 0.0153 0.4944 0.6210 Tidak
weekday4 -0.0032 0.0152 -0.2131 0.8313 Tidak
weekday5 0.0025 0.0153 0.1632 0.8704 Tidak
weekday6 -0.0055 0.0153 -0.3617 0.7176 Tidak
workingday1 0.2560 0.0093 27.5814 0.0000 Ya ✓
weathersit2 -0.0263 0.0095 -2.7723 0.0056 Ya ✓
weathersit3 -0.0758 0.0158 -4.7871 0.0000 Ya ✓
weathersit4 -0.0929 0.0427 -2.1776 0.0295 Ya ✓
temp 0.9840 0.0148 66.3996 0.0000 Ya ✓
atemp -0.0067 0.0145 -0.4636 0.6430 Tidak
hum 0.0041 0.0142 0.2907 0.7713 Tidak
windspeed 0.0060 0.0167 0.3623 0.7171 Tidak

6.7 4.7 Incidence Rate Ratio (IRR)

# IRR = exp(koefisien)
IRR    <- exp(coef(model_quasi))
CI_IRR <- exp(confint(model_quasi))

data.frame(
  Variabel  = names(IRR),
  IRR       = round(IRR,         4),
  CI_Lower  = round(CI_IRR[, 1], 4),
  CI_Upper  = round(CI_IRR[, 2], 4)
) %>%
  head(20) %>%       # tampilkan 20 baris pertama
  kable(caption = "Incidence Rate Ratio (IRR) dan Interval Kepercayaan 95%") %>%
  kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE)
Incidence Rate Ratio (IRR) dan Interval Kepercayaan 95%
Variabel IRR CI_Lower CI_Upper
(Intercept) (Intercept) 32.3690 30.3669 34.4914
season2 season2 1.0065 0.8456 1.2030
season3 season3 1.0924 0.9126 1.3060
season4 season4 1.0696 0.8960 1.2715
yr1 yr1 0.9935 0.9777 1.0095
mnth2 mnth2 0.9891 0.9506 1.0291
mnth3 mnth3 0.9971 0.9591 1.0367
mnth4 mnth4 1.0001 0.8368 1.1905
mnth5 mnth5 0.9822 0.8198 1.1723
mnth6 mnth6 0.9769 0.8154 1.1661
mnth7 mnth7 0.9062 0.7580 1.0848
mnth8 mnth8 0.8923 0.7450 1.0702
mnth9 mnth9 0.9122 0.7616 1.0942
mnth10 mnth10 0.9153 0.7699 1.0926
mnth11 mnth11 0.9103 0.7645 1.0887
mnth12 mnth12 0.9182 0.7711 1.0981
hr1 hr1 1.3598 1.2873 1.4367
hr2 hr2 1.7522 1.6634 1.8462
hr3 hr3 2.0335 1.9331 2.1397
hr4 hr4 2.2394 2.1296 2.3557

6.8 4.8 Evaluasi Model

# Prediksi pada data testing
pred_ps   <- predict(model_quasi, newdata = test_ps, type = "response")
aktual_ps <- test_ps$cnt

# Metrik evaluasi
MAE  <- mean(abs(pred_ps - aktual_ps))
RMSE <- sqrt(mean((pred_ps - aktual_ps)^2))
MAPE <- mean(abs((pred_ps - aktual_ps) / aktual_ps)) * 100

cat("=== Evaluasi Model Quasi-Poisson ===\n")
## === Evaluasi Model Quasi-Poisson ===
cat("MAE :", round(MAE,  2), "\n")
## MAE : 21.54
cat("RMSE:", round(RMSE, 2), "\n")
## RMSE: 28.22
cat("MAPE:", round(MAPE, 2), "%\n")
## MAPE: Inf %
# Plot Aktual vs Prediksi (subsample 300 observasi)
set.seed(1)
idx_plot <- sample(1:length(pred_ps), 300)

data.frame(
  Observasi = 1:300,
  Aktual    = aktual_ps[idx_plot],
  Prediksi  = pred_ps[idx_plot]
) %>%
  pivot_longer(-Observasi, names_to = "Tipe", values_to = "Nilai") %>%
  ggplot(aes(x = Observasi, y = Nilai, color = Tipe)) +
  geom_line(alpha = 0.7) +
  scale_color_manual(values = c("Aktual" = "#F44336", "Prediksi" = "#1565C0")) +
  labs(title = "Perbandingan Nilai Aktual vs Prediksi (300 Sampel)",
       x = "Indeks Observasi", y = "Jumlah Peminjaman", color = "") +
  theme_minimal()

# Scatter plot aktual vs prediksi
data.frame(Aktual = aktual_ps, Prediksi = pred_ps) %>%
  ggplot(aes(x = Aktual, y = Prediksi)) +
  geom_point(alpha = 0.2, color = "#1565C0") +
  geom_abline(slope = 1, intercept = 0, color = "#F44336", linewidth = 1) +
  labs(title = "Scatter Plot: Aktual vs Prediksi — Quasi-Poisson",
       x = "Nilai Aktual", y = "Nilai Prediksi") +
  theme_minimal()


7 Kesimpulan

Berikut adalah ringkasan hasil analisis keempat model regresi logistik yang telah dilakukan:

7.1 Tabel Ringkasan

data.frame(
  Jenis_Regresi = c(
    "Regresi Logistik Biner",
    "Regresi Logistik Multinomial",
    "Regresi Logistik Ordinal",
    "Regresi Logistik Poisson"
  ),
  Dataset = c(
    "Heart Failure Clinical Records",
    "Covertype Dataset",
    "Student Performance",
    "Bike Sharing Dataset"
  ),
  Variabel_Respon = c(
    "DEATH_EVENT (0/1)",
    "Cover_Type (7 kategori)",
    "G3_kat (4 kategori ordinal)",
    "cnt (cacahan)"
  ),
  Metode = c(
    "glm(family=binomial)",
    "multinom() — nnet",
    "polr() — MASS",
    "glm(family=quasipoisson)"
  ),
  Package_Utama = c(
    "stats, caret, pROC",
    "nnet, caret",
    "MASS, caret",
    "stats, caret"
  )
) %>%
  kable(caption = "Ringkasan Analisis Regresi Logistik") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "bordered"),
                full_width = TRUE)
Ringkasan Analisis Regresi Logistik
Jenis_Regresi Dataset Variabel_Respon Metode Package_Utama
Regresi Logistik Biner Heart Failure Clinical Records DEATH_EVENT (0/1) glm(family=binomial) stats, caret, pROC
Regresi Logistik Multinomial Covertype Dataset Cover_Type (7 kategori) multinom() — nnet nnet, caret
Regresi Logistik Ordinal Student Performance G3_kat (4 kategori ordinal) polr() — MASS MASS, caret
Regresi Logistik Poisson Bike Sharing Dataset cnt (cacahan) glm(family=quasipoisson) stats, caret

7.2 Simpulan Analisis

  1. Regresi Logistik Biner (Heart Failure): Model berhasil mengidentifikasi faktor-faktor penting yang mempengaruhi kematian pasien gagal jantung, seperti serum_creatinine, ejection_fraction, dan time. Nilai AUC yang diperoleh menunjukkan performa model yang baik dalam mengklasifikasikan status hidup/meninggal pasien.

  2. Regresi Logistik Multinomial (Covertype): Model dapat memprediksi jenis tutupan lahan berdasarkan fitur topografi dan lingkungan. Elevasi (Elevation) merupakan prediktor paling dominan dalam menentukan jenis tutupan lahan.

  3. Regresi Logistik Ordinal (Student Performance): Model ordinal berhasil mengklasifikasikan kategori prestasi siswa. Variabel nilai sebelumnya (G1, G2) dan jumlah ketidakhadiran (failures) menjadi prediktor yang paling signifikan.

  4. Regresi Logistik Poisson (Bike Sharing): Ditemukan indikasi overdispersi pada model Poisson standar, sehingga digunakan model Quasi-Poisson. Faktor jam (hr), musim (season), dan kondisi cuaca (weathersit) sangat berpengaruh terhadap jumlah peminjaman sepeda.


8 Referensi

  • Chicco, D., & Jurman, G. (2020). Machine learning can predict survival of patients with heart failure from serum creatinine and ejection fraction alone. BMC Medical Informatics and Decision Making, 20, 16.
  • Blackard, J. A., & Dean, D. J. (1999). Comparative accuracies of artificial neural networks and discriminant analysis in predicting forest cover types from cartographic variables. Computers and Electronics in Agriculture, 24(3), 131–151.
  • Cortez, P., & Silva, A. (2008). Using data mining to predict secondary school student performance. EUROSIS.
  • Fanaee-T, H., & Gama, J. (2014). Event labeling combining ensemble detectors and background knowledge. Progress in Artificial Intelligence, 2(2–3), 113–127.
  • Hosmer, D. W., & Lemeshow, S. (2000). Applied Logistic Regression (2nd ed.). Wiley.
  • R Core Team (2024). R: A Language and Environment for Statistical Computing. R Foundation for Statistical Computing. https://www.R-project.org/

📄 Laporan ini dibuat menggunakan R Markdown dan dipublikasikan di RPubs.
Semua dataset dalam laporan ini merupakan data sintetis yang dibangun dengan distribusi yang merepresentasikan dataset asli dari UCI Machine Learning Repository.