1 Pendahuluan

1.1 Latar Belakang

Diabetes melitus merupakan salah satu penyakit tidak menular yang menjadi permasalahan kesehatan global dengan prevalensi yang terus meningkat secara signifikan dari tahun ke tahun. Berdasarkan laporan International Diabetes Federation (IDF) edisi ke-10 tahun 2021, terdapat sekitar 537 juta orang dewasa (usia 20–79 tahun) yang hidup dengan diabetes di seluruh dunia, dan angka ini diproyeksikan meningkat menjadi 853 juta pada tahun 2050. Indonesia sendiri menempati peringkat kelima negara dengan jumlah penderita diabetes terbanyak di dunia, dengan estimasi 19,47 juta penderita pada tahun 2021.

Menurut WHO, diabetes adalah penyakit kronis yang terjadi ketika pankreas tidak menghasilkan cukup insulin atau ketika tubuh tidak dapat secara efektif menggunakan insulin yang dihasilkannya. Kondisi ini dapat menyebabkan komplikasi serius seperti penyakit jantung, stroke, gagal ginjal, kebutaan, hingga amputasi. Data Riskesdas Kemenkes RI menunjukkan bahwa prevalensi diabetes melitus pada penduduk usia ≥15 tahun meningkat dari 6,9% (2013) menjadi 10,9% (2018).

Untuk menganalisis hubungan antara faktor-faktor risiko tersebut dengan status diabetes, regresi logistik biner merupakan salah satu metode yang paling banyak digunakan dalam pemodelan variabel respons dikotomi. Metode ini memiliki keunggulan dalam menginterpretasikan pengaruh setiap variabel prediktor terhadap probabilitas kejadian suatu peristiwa, serta kemampuannya dalam menangani variabel prediktor campuran (numerik dan kategorik).

1.2 Tujuan Penelitian

Tujuan penelitian ini adalah:

  1. Mengidentifikasi faktor-faktor yang secara signifikan berhubungan dengan risiko diabetes stadium awal.
  2. Membangun model prediktif regresi logistik biner yang optimal melalui seleksi stepwise berbasis AIC.
  3. Mengevaluasi performa model secara menyeluruh menggunakan metrik klasifikasi dan kurva ROC.

1.3 Dataset

Dataset yang digunakan adalah Early Stage Diabetes Risk Prediction Dataset dari UCI Machine Learning Repository (ID: 529), dikumpulkan dari Rumah Sakit Sylhet, Bangladesh. Dataset terdiri dari 520 observasi dengan 17 variabel: satu variabel kontinu (usia), 15 variabel prediktor kategorik (gejala klinis dan faktor demografi), dan satu variabel respon biner (status diabetes: Positif/Negatif).

Referensi Dataset: Islam, M.M.F. et al. (2020). Likelihood prediction of diabetes at early stage using data mining techniques. https://doi.org/10.24432/C5VG8H


2 Persiapan

2.1 Instalasi dan Pemuatan Paket

Materi: Analisis ini memerlukan beberapa paket R. Paket glm (base R) digunakan untuk estimasi model logistik, MASS untuk seleksi stepwise, car untuk uji VIF, ResourceSelection untuk uji Hosmer-Lemeshow, pROC untuk kurva ROC, caret untuk pembagian data, broom untuk output tidy, dan ggplot2 untuk visualisasi.

required_packages <- c(
  "dplyr", "ggplot2", "broom", "knitr", "scales", "tidyr",
  "car", "pROC", "ResourceSelection", "MASS", "caret", "lmtest"
)

for (pkg in required_packages) {
  if (!require(pkg, character.only = TRUE, quietly = TRUE)) {
    install.packages(pkg)
    library(pkg, character.only = TRUE)
  }
}

# Pastikan select() menggunakan dplyr, bukan MASS
select <- dplyr::select

set.seed(123)

3 Persiapan Data

3.1 Input Data

Materi: Data dibaca langsung dari UCI Machine Learning Repository menggunakan URL. Fungsi tryCatch() digunakan sebagai fallback apabila koneksi internet tidak tersedia — dalam kondisi tersebut, pengguna diminta memilih file secara manual.

url_data <- "https://archive.ics.uci.edu/ml/machine-learning-databases/00529/diabetes_data_upload.csv"

raw_data <- tryCatch(
  read.csv(url(url_data), header = TRUE, stringsAsFactors = FALSE,
           check.names = FALSE),
  error = function(e) {
    message("Gagal mengunduh dari UCI. Silakan pilih file secara manual.")
    read.csv(file.choose(), header = TRUE, stringsAsFactors = FALSE,
             check.names = FALSE)
  }
)
knitr::kable(
  data.frame(
    Informasi = c("Jumlah Baris", "Jumlah Kolom"),
    Nilai     = c(nrow(raw_data), ncol(raw_data))
  ),
  caption = "Dimensi Dataset"
)
Dimensi Dataset
Informasi Nilai
Jumlah Baris 520
Jumlah Kolom 17

Interpretasi: Dataset berhasil dibaca dengan 520 observasi dan 17 variabel (termasuk variabel respon).

3.2 Pemeriksaan Struktur dan Missing Values

Materi: Sebelum analisis, perlu dipastikan tidak ada data yang hilang (missing values). Adanya missing values dapat menyebabkan bias estimasi dan mengurangi ukuran sampel efektif. Fungsi is.na() dan colSums() digunakan untuk mendeteksi missing per kolom.

str(raw_data)
## 'data.frame':    520 obs. of  17 variables:
##  $ Age               : int  40 58 41 45 60 55 57 66 67 70 ...
##  $ Gender            : chr  "Male" "Male" "Male" "Male" ...
##  $ Polyuria          : chr  "No" "No" "Yes" "No" ...
##  $ Polydipsia        : chr  "Yes" "No" "No" "No" ...
##  $ sudden weight loss: chr  "No" "No" "No" "Yes" ...
##  $ weakness          : chr  "Yes" "Yes" "Yes" "Yes" ...
##  $ Polyphagia        : chr  "No" "No" "Yes" "Yes" ...
##  $ Genital thrush    : chr  "No" "No" "No" "Yes" ...
##  $ visual blurring   : chr  "No" "Yes" "No" "No" ...
##  $ Itching           : chr  "Yes" "No" "Yes" "Yes" ...
##  $ Irritability      : chr  "No" "No" "No" "No" ...
##  $ delayed healing   : chr  "Yes" "No" "Yes" "Yes" ...
##  $ partial paresis   : chr  "No" "Yes" "No" "No" ...
##  $ muscle stiffness  : chr  "Yes" "No" "Yes" "No" ...
##  $ Alopecia          : chr  "Yes" "Yes" "Yes" "No" ...
##  $ Obesity           : chr  "Yes" "No" "No" "No" ...
##  $ class             : chr  "Positive" "Positive" "Positive" "Positive" ...
missing_counts <- colSums(is.na(raw_data))

knitr::kable(
  data.frame(Variabel = names(missing_counts),
             `Jumlah Missing` = as.integer(missing_counts),
             check.names = FALSE),
  caption = "Jumlah Missing Values per Variabel"
)
Jumlah Missing Values per Variabel
Variabel Jumlah Missing
Age 0
Gender 0
Polyuria 0
Polydipsia 0
sudden weight loss 0
weakness 0
Polyphagia 0
Genital thrush 0
visual blurring 0
Itching 0
Irritability 0
delayed healing 0
partial paresis 0
muscle stiffness 0
Alopecia 0
Obesity 0
class 0
if (sum(missing_counts) > 0) {
  raw_data <- na.omit(raw_data)
}

Interpretasi: Tidak ditemukan missing values pada dataset ini. Seluruh 520 observasi dapat digunakan dalam analisis selanjutnya tanpa penghapusan baris.

3.3 Rekoding Variabel

Materi: Rekoding variabel dilakukan agar fungsi glm() dapat mengidentifikasi kategori referensi secara eksplisit. Skema rekoding mengacu pada Hosmer & Lemeshow (2013) dan Agresti (2018):

  • Variabel respon (class): Negative → 0 (referensi), Positive → 1
  • Gender: Female → 0 (referensi), Male → 1
  • Seluruh gejala: No → 0 (referensi), Yes → 1

Variabel dikodekan numerik 0/1 untuk model GLM, sedangkan versi berlabel faktor (_f) digunakan hanya untuk tabel dan visualisasi EDA.

diabetes <- raw_data %>%
  transmute(
    # Variabel respon — numerik (0/1) untuk model GLM
    diabetes_positif = as.integer(class == "Positive"),

    # Variabel respon — faktor berlabel untuk tabel dan plot
    status_diabetes = factor(class,
                             levels = c("Negative", "Positive"),
                             labels = c("Negatif", "Positif")),

    # Prediktor kontinu
    age = as.numeric(Age),

    # Prediktor kategorik dikode 0/1
    gender           = as.integer(Gender               == "Male"),
    polyuria         = as.integer(Polyuria             == "Yes"),
    polydipsia       = as.integer(Polydipsia           == "Yes"),
    sudden_wt_loss   = as.integer(`sudden weight loss` == "Yes"),
    weakness         = as.integer(weakness             == "Yes"),
    polyphagia       = as.integer(Polyphagia           == "Yes"),
    genital_thrush   = as.integer(`Genital thrush`     == "Yes"),
    visual_blurring  = as.integer(`visual blurring`    == "Yes"),
    itching          = as.integer(Itching              == "Yes"),
    irritability     = as.integer(Irritability         == "Yes"),
    delayed_healing  = as.integer(`delayed healing`    == "Yes"),
    partial_paresis  = as.integer(`partial paresis`    == "Yes"),
    muscle_stiffness = as.integer(`muscle stiffness`   == "Yes"),
    alopecia         = as.integer(Alopecia             == "Yes"),
    obesity          = as.integer(Obesity              == "Yes")
  )

# Versi berlabel faktor untuk EDA
diabetes_plot <- diabetes %>%
  mutate(
    gender_f           = factor(gender,           levels = c(0,1), labels = c("0","1")),
    polyuria_f         = factor(polyuria,         levels = c(0,1), labels = c("0","1")),
    polydipsia_f       = factor(polydipsia,       levels = c(0,1), labels = c("0","1")),
    sudden_wt_loss_f   = factor(sudden_wt_loss,   levels = c(0,1), labels = c("0","1")),
    weakness_f         = factor(weakness,         levels = c(0,1), labels = c("0","1")),
    polyphagia_f       = factor(polyphagia,       levels = c(0,1), labels = c("0","1")),
    genital_thrush_f   = factor(genital_thrush,   levels = c(0,1), labels = c("0","1")),
    visual_blurring_f  = factor(visual_blurring,  levels = c(0,1), labels = c("0","1")),
    itching_f          = factor(itching,          levels = c(0,1), labels = c("0","1")),
    irritability_f     = factor(irritability,     levels = c(0,1), labels = c("0","1")),
    delayed_healing_f  = factor(delayed_healing,  levels = c(0,1), labels = c("0","1")),
    partial_paresis_f  = factor(partial_paresis,  levels = c(0,1), labels = c("0","1")),
    muscle_stiffness_f = factor(muscle_stiffness, levels = c(0,1), labels = c("0","1")),
    alopecia_f         = factor(alopecia,         levels = c(0,1), labels = c("0","1")),
    obesity_f          = factor(obesity,          levels = c(0,1), labels = c("0","1"))
  )

Interpretasi: Rekoding selesai. Dataset akhir terdiri dari 520 baris dan 18 kolom. Seluruh prediktor kategorik telah dikodekan sebagai 0/1 dengan kategori referensi yang eksplisit.


4 Pembagian Data: Training dan Testing (80:20)

Materi: Pembagian data menjadi training set dan test set dilakukan sebelum analisis deskriptif dan pemodelan untuk menghindari data leakage. Model dibangun hanya menggunakan data train, sedangkan data test digunakan untuk evaluasi kinerja out-of-sample.

Metode yang digunakan adalah stratified random sampling via caret::createDataPartition(), yang memastikan proporsi kelas positif/negatif terjaga di kedua set.

Referensi: Harrell (2015), Regression Modeling Strategies, 2nd ed.

idx_train  <- caret::createDataPartition(diabetes$diabetes_positif,
                                         p = 0.80, list = FALSE)
data_train <- diabetes[idx_train, ]
data_test  <- diabetes[-idx_train, ]
tbl_split <- data.frame(
  Set         = c("Training", "Testing", "Total"),
  n           = c(nrow(data_train), nrow(data_test), nrow(diabetes)),
  `% Positif` = c(
    round(100 * mean(data_train$diabetes_positif), 1),
    round(100 * mean(data_test$diabetes_positif),  1),
    round(100 * mean(diabetes$diabetes_positif),   1)
  ),
  `% Negatif` = c(
    round(100 * mean(data_train$diabetes_positif == 0), 1),
    round(100 * mean(data_test$diabetes_positif  == 0), 1),
    round(100 * mean(diabetes$diabetes_positif   == 0), 1)
  ),
  check.names = FALSE
)

knitr::kable(tbl_split,
             caption = "Ringkasan Pembagian Data Training dan Testing (80:20, Stratified)")
Ringkasan Pembagian Data Training dan Testing (80:20, Stratified)
Set n % Positif % Negatif
Training 416 60.8 39.2
Testing 104 64.4 35.6
Total 520 61.5 38.5

Interpretasi: Data train terdiri dari 416 observasi (80%) dan data test terdiri dari 104 observasi (20%). Stratified sampling berhasil menjaga proporsi kelas positif yang konsisten di kedua subset, memastikan evaluasi model tidak bias akibat ketidakseimbangan distribusi kelas.


5 Statistika Deskriptif

5.1 Karakteristik Responden: Variabel Usia

Materi: Statistika deskriptif memberikan gambaran awal distribusi data sebelum pemodelan. Untuk variabel kontinu (usia), dihitung rata-rata, median, standar deviasi, nilai minimum, dan maksimum. Kedekatan nilai mean dan median mengindikasikan distribusi yang relatif simetris.

ringkasan_age <- diabetes %>%
  summarise(
    n          = n(),
    Rata2_Usia = round(mean(age),   2),
    Median     = round(median(age), 2),
    SD         = round(sd(age),     2),
    Min        = min(age),
    Max        = max(age)
  )

knitr::kable(ringkasan_age, caption = "Statistika Deskriptif Usia Responden")
Statistika Deskriptif Usia Responden
n Rata2_Usia Median SD Min Max
520 48.03 47.5 12.15 16 90
ringkasan_age_grup <- diabetes %>%
  group_by(status_diabetes) %>%
  summarise(
    n      = n(),
    Mean   = round(mean(age),   2),
    Median = round(median(age), 2),
    SD     = round(sd(age),     2),
    Min    = min(age),
    Max    = max(age),
    .groups = "drop"
  )

knitr::kable(ringkasan_age_grup,
             caption = "Statistika Deskriptif Usia Berdasarkan Status Diabetes")
Statistika Deskriptif Usia Berdasarkan Status Diabetes
status_diabetes n Mean Median SD Min Max
Negatif 200 46.36 45 12.08 26 72
Positif 320 49.07 48 12.10 16 90

Interpretasi: Rata-rata usia seluruh responden adalah 48.03 tahun dengan rentang 16–90 tahun. Nilai mean dan median yang berdekatan mengindikasikan distribusi usia relatif simetris. Responden dengan diabetes positif rata-rata lebih tua (~49 tahun) dibandingkan yang negatif (~46 tahun), meskipun perbedaan ini perlu dikonfirmasi dalam analisis multivariat.

ggplot(diabetes, aes(x = age, fill = status_diabetes)) +
  geom_histogram(binwidth = 5, color = "white", alpha = 0.8,
                 position = "dodge") +
  scale_fill_manual(values = c("Negatif" = "#5b9bd5", "Positif" = "#e06c75")) +
  labs(title = "Distribusi Usia Berdasarkan Status Diabetes",
       x = "Usia (tahun)", y = "Frekuensi", fill = "Status Diabetes") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "top")
Distribusi Usia Berdasarkan Status Diabetes

Distribusi Usia Berdasarkan Status Diabetes

ggplot(diabetes, aes(x = age, fill = status_diabetes)) +
  geom_density(alpha = 0.45) +
  scale_fill_manual(values = c("Negatif" = "#5b9bd5", "Positif" = "#e06c75")) +
  labs(title = "Kurva Densitas Usia Berdasarkan Status Diabetes",
       x = "Usia (tahun)", y = "Densitas", fill = "Status Diabetes") +
  theme_minimal(base_size = 12)
Kurva Densitas Usia Berdasarkan Status Diabetes

Kurva Densitas Usia Berdasarkan Status Diabetes

5.2 Distribusi Jenis Kelamin

jk_tbl <- diabetes %>%
  mutate(Jenis_Kelamin = ifelse(gender == 1, "Laki-laki", "Perempuan")) %>%
  count(Jenis_Kelamin) %>%
  mutate(Proporsi = scales::percent(n / sum(n), accuracy = 0.1)) %>%
  rename(Jumlah = n)

knitr::kable(jk_tbl, caption = "Distribusi Jenis Kelamin Responden")
Distribusi Jenis Kelamin Responden
Jenis_Kelamin Jumlah Proporsi
Laki-laki 328 63.1%
Perempuan 192 36.9%

Interpretasi: Mayoritas responden berjenis kelamin laki-laki (63,1%). Distribusi ini mencerminkan komposisi pasien di rumah sakit tempat data dikumpulkan, bukan prevalensi diabetes di populasi umum.

5.3 Distribusi Variabel Respon (Status Diabetes)

Materi: Pemeriksaan distribusi variabel respon penting untuk mendeteksi class imbalance. Apabila proporsi salah satu kelas < 20% atau > 80%, diperlukan teknik penyeimbangan seperti SMOTE atau oversampling sebelum pemodelan.

distribusi_y <- diabetes %>%
  count(status_diabetes) %>%
  mutate(Proporsi = scales::percent(n / sum(n), accuracy = 0.1)) %>%
  rename(Jumlah = n)

knitr::kable(distribusi_y, caption = "Distribusi Status Diabetes")
Distribusi Status Diabetes
status_diabetes Jumlah Proporsi
Negatif 200 38.5%
Positif 320 61.5%
ggplot(diabetes_plot, aes(x = status_diabetes, fill = status_diabetes)) +
  geom_bar(width = 0.55, color = "white") +
  geom_text(stat = "count", aes(label = after_stat(count)),
            vjust = -0.4, size = 4.5) +
  scale_fill_manual(values = c("Negatif" = "#5b9bd5", "Positif" = "#e06c75")) +
  labs(title = "Distribusi Status Diabetes",
       x = "Status Diabetes", y = "Jumlah Observasi") +
  theme_minimal(base_size = 12) +
  theme(legend.position = "none")
Distribusi Status Diabetes

Distribusi Status Diabetes

Interpretasi: Dari 520 responden, 320 orang (61,5%) didiagnosis positif diabetes dan 200 orang (38,5%) negatif. Proporsi positif sebesar 61,5% berada dalam rentang 20%–80%, sehingga distribusi kelas dinyatakan tidak terjadi class imbalance yang serius. Model regresi logistik standar dapat diterapkan tanpa teknik penyeimbangan kelas.

5.4 Distribusi Variabel Prediktor Kategorik

var_kategorik <- c(
  "gender", "polyuria", "polydipsia", "sudden_wt_loss", "weakness",
  "polyphagia", "genital_thrush", "visual_blurring", "itching",
  "irritability", "delayed_healing", "partial_paresis",
  "muscle_stiffness", "alopecia", "obesity"
)

var_label <- c(
  "Gender", "Polyuria", "Polydipsia", "Sudden Weight Loss",
  "Weakness", "Polyphagia", "Genital Thrush", "Visual Blurring",
  "Itching", "Irritability", "Delayed Healing", "Partial Paresis",
  "Muscle Stiffness", "Alopecia", "Obesity"
)

dist_prediktor <- lapply(seq_along(var_kategorik), function(i) {
  v   <- var_kategorik[i]
  lbl <- var_label[i]
  n1  <- sum(diabetes[[v]] == 1)
  n0  <- sum(diabetes[[v]] == 0)
  data.frame(
    Variabel     = lbl,
    `Kode 0 (n)` = n0,
    `% Kode 0`   = round(100 * n0 / nrow(diabetes), 1),
    `Kode 1 (n)` = n1,
    `% Kode 1`   = round(100 * n1 / nrow(diabetes), 1),
    check.names  = FALSE
  )
}) %>% bind_rows()

knitr::kable(dist_prediktor,
             caption = "Distribusi Variabel Prediktor Kategorik (0 = Tidak/Perempuan, 1 = Ya/Laki-laki)")
Distribusi Variabel Prediktor Kategorik (0 = Tidak/Perempuan, 1 = Ya/Laki-laki)
Variabel Kode 0 (n) % Kode 0 Kode 1 (n) % Kode 1
Gender 192 36.9 328 63.1
Polyuria 262 50.4 258 49.6
Polydipsia 287 55.2 233 44.8
Sudden Weight Loss 303 58.3 217 41.7
Weakness 215 41.3 305 58.7
Polyphagia 283 54.4 237 45.6
Genital Thrush 404 77.7 116 22.3
Visual Blurring 287 55.2 233 44.8
Itching 267 51.3 253 48.7
Irritability 394 75.8 126 24.2
Delayed Healing 281 54.0 239 46.0
Partial Paresis 296 56.9 224 43.1
Muscle Stiffness 325 62.5 195 37.5
Alopecia 341 65.6 179 34.4
Obesity 432 83.1 88 16.9

Interpretasi: Dua gejala paling menonjol secara deskriptif adalah Polyuria (sering buang air kecil berlebihan) dan Polydipsia (rasa haus berlebihan), yang keduanya merupakan tanda klasik diabetes melitus akibat tubuh berusaha mengeluarkan kelebihan glukosa melalui urin sehingga menyebabkan dehidrasi dan haus berlebihan.

for (i in seq_along(var_kategorik)) {
  v   <- var_kategorik[i]
  lbl <- var_label[i]
  
  tbl      <- table(Kode = diabetes[[v]], Status = diabetes$status_diabetes)
  tbl_prop <- round(prop.table(tbl, margin = 2) * 100, 1)
  
  tbl_df <- as.data.frame.matrix(tbl)
  tbl_df$Kode        <- rownames(tbl_df)
  tbl_df$`% Negatif` <- tbl_prop[, "Negatif"]
  tbl_df$`% Positif` <- tbl_prop[, "Positif"]
  tbl_df <- tbl_df[, c("Kode", "Negatif", "Positif", "% Negatif", "% Positif")]
  
  cat(sprintf("\n\n#### %s (0 = referensi, 1 = kategori aktif)\n\n", lbl))
  
  print(knitr::kable(tbl_df,
                     format    = "markdown",
                     col.names = c("Kode", "Negatif", "Positif", "% Negatif", "% Positif"),
                     caption   = paste("Distribusi", lbl, "vs Status Diabetes")))
  cat("\n\n")
}

5.4.0.1 Gender (0 = referensi, 1 = kategori aktif)

Distribusi Gender vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 19 173 9.5 54.1
1 1 181 147 90.5 45.9

5.4.0.2 Polyuria (0 = referensi, 1 = kategori aktif)

Distribusi Polyuria vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 185 77 92.5 24.1
1 1 15 243 7.5 75.9

5.4.0.3 Polydipsia (0 = referensi, 1 = kategori aktif)

Distribusi Polydipsia vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 192 95 96 29.7
1 1 8 225 4 70.3

5.4.0.4 Sudden Weight Loss (0 = referensi, 1 = kategori aktif)

Distribusi Sudden Weight Loss vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 171 132 85.5 41.2
1 1 29 188 14.5 58.8

5.4.0.5 Weakness (0 = referensi, 1 = kategori aktif)

Distribusi Weakness vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 113 102 56.5 31.9
1 1 87 218 43.5 68.1

5.4.0.6 Polyphagia (0 = referensi, 1 = kategori aktif)

Distribusi Polyphagia vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 152 131 76 40.9
1 1 48 189 24 59.1

5.4.0.7 Genital Thrush (0 = referensi, 1 = kategori aktif)

Distribusi Genital Thrush vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 167 237 83.5 74.1
1 1 33 83 16.5 25.9

5.4.0.8 Visual Blurring (0 = referensi, 1 = kategori aktif)

Distribusi Visual Blurring vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 142 145 71 45.3
1 1 58 175 29 54.7

5.4.0.9 Itching (0 = referensi, 1 = kategori aktif)

Distribusi Itching vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 101 166 50.5 51.9
1 1 99 154 49.5 48.1

5.4.0.10 Irritability (0 = referensi, 1 = kategori aktif)

Distribusi Irritability vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 184 210 92 65.6
1 1 16 110 8 34.4

5.4.0.11 Delayed Healing (0 = referensi, 1 = kategori aktif)

Distribusi Delayed Healing vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 114 167 57 52.2
1 1 86 153 43 47.8

5.4.0.12 Partial Paresis (0 = referensi, 1 = kategori aktif)

Distribusi Partial Paresis vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 168 128 84 40
1 1 32 192 16 60

5.4.0.13 Muscle Stiffness (0 = referensi, 1 = kategori aktif)

Distribusi Muscle Stiffness vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 140 185 70 57.8
1 1 60 135 30 42.2

5.4.0.14 Alopecia (0 = referensi, 1 = kategori aktif)

Distribusi Alopecia vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 99 242 49.5 75.6
1 1 101 78 50.5 24.4

5.4.0.15 Obesity (0 = referensi, 1 = kategori aktif)

Distribusi Obesity vs Status Diabetes
Kode Negatif Positif % Negatif % Positif
0 0 173 259 86.5 80.9
1 1 27 61 13.5 19.1

5.5 Interpretasi Distribusi Variabel Prediktor per Status Diabetes

5.5.1 1. Gender

Pada kelompok Negatif, mayoritas berjenis kelamin laki-laki (kode 1) sebesar 90,5%, sedangkan pada kelompok Positif, perempuan (kode 0) mendominasi dengan 54,1%. Hal ini mengindikasikan bahwa perempuan memiliki proporsi kasus positif diabetes yang lebih tinggi dalam dataset ini.

5.5.2 2. Polyuria (Sering Buang Air Kecil)

Sebesar 75,9% pasien positif diabetes mengalami polyuria, dibandingkan hanya 7,5% pada kelompok negatif. Polyuria merupakan salah satu gejala klasik diabetes yang paling membedakan antara kedua kelompok.

5.5.3 3. Polydipsia (Sering Merasa Haus)

Sebesar 70,3% pasien positif mengalami polydipsia, sementara hanya 4% pada kelompok negatif. Bersama polyuria, polydipsia menjadi gejala dengan perbedaan distribusi paling mencolok antar kelompok.

5.5.4 4. Sudden Weight Loss (Penurunan Berat Badan Mendadak)

Pada kelompok positif, 58,8% mengalami penurunan berat badan mendadak dibandingkan hanya 14,5% pada kelompok negatif. Gejala ini cukup diskriminatif terhadap status diabetes.

5.5.5 5. Weakness (Kelemahan Fisik)

Sebesar 68,1% pasien positif melaporkan kelemahan fisik, berbanding 43,5% pada kelompok negatif. Meskipun ada perbedaan, gejala ini juga cukup umum ditemukan pada kelompok negatif.

5.5.6 6. Polyphagia (Sering Lapar)

Sebesar 59,1% pasien positif mengalami polyphagia dibandingkan 24% pada kelompok negatif. Gejala ini cukup membedakan kedua kelompok meskipun tidak setajam polyuria dan polydipsia.

5.5.7 7. Genital Thrush (Infeksi Jamur Genital)

Distribusinya relatif mirip antar kelompok: 25,9% pada positif vs 16,5% pada negatif. Gejala ini memiliki daya pembeda yang relatif lemah dibanding gejala lainnya.

5.5.8 8. Visual Blurring (Penglihatan Kabur)

Sebesar 54,7% pasien positif mengalami penglihatan kabur dibandingkan 29% pada kelompok negatif. Gejala ini cukup relevan namun tidak sangat spesifik.

5.5.9 9. Itching (Gatal-Gatal)

Distribusi hampir merata di kedua kelompok: 49,5% pada negatif dan 48,1% pada positif. Gejala ini tidak diskriminatif dan kemungkinan bukan prediktor yang kuat.

5.5.10 10. Irritability (Iritabilitas)

Proporsi yang mengalami iritabilitas pada kelompok positif (34,4%) jauh lebih tinggi dibandingkan negatif (8%), menunjukkan daya pembeda yang cukup baik.

5.5.11 11. Delayed Healing (Penyembuhan Luka Lambat)

Distribusinya hampir seimbang antara kedua kelompok (positif: 47,8%, negatif: 43%). Gejala ini memiliki daya pembeda yang rendah.

5.5.12 12. Partial Paresis (Kelemahan Parsial)

Sebesar 60% pasien positif mengalami partial paresis dibandingkan hanya 16% pada kelompok negatif. Gejala ini cukup diskriminatif terhadap status diabetes.

5.5.13 13. Muscle Stiffness (Kekakuan Otot)

Proporsi yang mengalami kekakuan otot: 42,2% pada positif dan 30% pada negatif. Daya pembedanya tergolong sedang.

5.5.14 14. Alopecia (Kerontokan Rambut)

Alopecia lebih banyak ditemukan pada kelompok Negatif (50,5%) dibandingkan Positif (24,4%), mengindikasikan bahwa alopecia berkorelasi negatif dengan status diabetes dalam dataset ini.

5.5.15 15. Obesity (Obesitas)

Proporsi obesitas tidak jauh berbeda: 19,1% pada positif vs 13,5% pada negatif. Gejala ini memiliki daya pembeda yang paling lemah di antara semua variabel.


6 Screening Univariat: Uji Chi-Square

Materi: Uji Chi-Square (\(\chi^2\)) digunakan sebagai langkah skrining awal untuk mengidentifikasi asosiasi bivariat antara masing-masing prediktor kategorik dengan variabel respon sebelum analisis multivariat. Uji ini menguji independensi antara dua variabel kategorik.

Hipotesis:

  • \(H_0\): Tidak ada asosiasi antara variabel prediktor dan status diabetes (independen)
  • \(H_1\): Terdapat asosiasi antara variabel prediktor dan status diabetes

Kriteria: Variabel dinyatakan signifikan apabila \(p < 0{,}05\).

Referensi: Hosmer & Lemeshow (2013), hal. 89–90.

Selain nilai \(p\), dihitung pula Odds Ratio (OR) bivariat dan Confidence Interval 95% untuk mengukur kekuatan dan arah asosiasi.

hasil_chisq <- lapply(seq_along(var_kategorik), function(i) {
  v   <- var_kategorik[i]
  lbl <- var_label[i]

  tbl  <- table(diabetes[[v]], diabetes$diabetes_positif)
  test <- chisq.test(tbl, correct = FALSE)

  or_result <- tryCatch({
    a  <- tbl["1", "1"]; b  <- tbl["1", "0"]
    cc <- tbl["0", "1"]; d  <- tbl["0", "0"]
    if (any(c(a, b, cc, d) == 0)) {
      a <- a + 0.5; b <- b + 0.5; cc <- cc + 0.5; d <- d + 0.5
    }
    or_val  <- (a * d) / (b * cc)
    log_or  <- log(or_val)
    se_log  <- sqrt(1/a + 1/b + 1/cc + 1/d)
    ci_low  <- exp(log_or - 1.96 * se_log)
    ci_high <- exp(log_or + 1.96 * se_log)
    c(or_val, ci_low, ci_high)
  }, error = function(e) c(NA, NA, NA))

  data.frame(
    Variabel   = lbl,
    Chi_square = round(test$statistic, 4),
    df         = test$parameter,
    p_value    = round(test$p.value,   4),
    Signifikan = ifelse(test$p.value < 0.001, "*** (p<0.001)",
                        ifelse(test$p.value < 0.01,  "** (p<0.01)",
                               ifelse(test$p.value < 0.05, "* (p<0.05)", "Tidak"))),
    OR         = round(or_result[1], 4),
    CI_95_Low  = round(or_result[2], 4),
    CI_95_High = round(or_result[3], 4)
  )
}) %>% bind_rows()

knitr::kable(hasil_chisq,
             caption = "Hasil Uji Chi-Square dan Odds Ratio Bivariat")
Hasil Uji Chi-Square dan Odds Ratio Bivariat
Variabel Chi_square df p_value Signifikan OR CI_95_Low CI_95_High
X-squared…1 Gender 104.9415 1 0.0000 *** (p<0.001) 0.0892 0.0530 0.1502
X-squared…2 Polyuria 230.5954 1 0.0000 *** (p<0.001) 38.9221 21.6773 69.8855
X-squared…3 Polydipsia 218.8448 1 0.0000 *** (p<0.001) 56.8421 26.9362 119.9511
X-squared…4 Sudden Weight Loss 99.1077 1 0.0000 *** (p<0.001) 8.3981 5.3432 13.1997
X-squared…5 Weakness 30.7750 1 0.0000 *** (p<0.001) 2.7760 1.9265 4.0000
X-squared…6 Polyphagia 61.0006 1 0.0000 *** (p<0.001) 4.5687 3.0820 6.7725
X-squared…7 Genital Thrush 6.3250 1 0.0119 * (p<0.05) 1.7723 1.1308 2.7777
X-squared…8 Visual Blurring 32.8389 1 0.0000 *** (p<0.001) 2.9548 2.0278 4.3056
X-squared…9 Itching 0.0931 1 0.7602 Tidak 0.9465 0.6647 1.3477
X-squared…10 Irritability 46.6339 1 0.0000 *** (p<0.001) 6.0238 3.4390 10.5513
X-squared…11 Delayed Healing 1.1477 1 0.2840 Tidak 1.2145 0.8510 1.7331
X-squared…12 Partial Paresis 97.1737 1 0.0000 *** (p<0.001) 7.8750 5.0756 12.2184
X-squared…13 Muscle Stiffness 7.8000 1 0.0052 ** (p<0.01) 1.7027 1.1702 2.4776
X-squared…14 Alopecia 37.2125 1 0.0000 *** (p<0.001) 0.3159 0.2167 0.4605
X-squared…15 Obesity 2.7087 1 0.0998 Tidak 1.5091 0.9224 2.4688
sig_vars    <- hasil_chisq %>% dplyr::filter(p_value < 0.05) %>% dplyr::pull(Variabel)
nonsig_vars <- hasil_chisq %>% dplyr::filter(p_value >= 0.05) %>% dplyr::pull(Variabel)

Interpretasi: Dari 15 variabel kategorik yang diuji, 12 variabel menunjukkan asosiasi yang signifikan secara statistik (\(p < 0{,}05\)): Gender, Polyuria, Polydipsia, Sudden Weight Loss, Weakness, Polyphagia, Genital Thrush, Visual Blurring, Irritability, Partial Paresis, Muscle Stiffness, Alopecia. Sementara 3 variabel tidak signifikan: Itching, Delayed Healing, Obesity.

Ketiga variabel yang tidak signifikan secara bivariat tidak serta-merta dikeluarkan dari pertimbangan, mengingat uji Chi-Square hanya berfungsi sebagai skrining awal dan belum mencerminkan hubungan dalam konteks multivariat.


7 Pemeriksaan Asumsi

7.1 Membangun Model Penuh (untuk Pemeriksaan Asumsi)

Materi: Sebelum memeriksa asumsi, dibangun terlebih dahulu model regresi logistik penuh yang memuat seluruh 16 prediktor. Model penuh ini digunakan sebagai dasar pemeriksaan asumsi linearitas logit (Box-Tidwell) dan multikolinearitas (VIF). Estimasi menggunakan Maximum Likelihood Estimation (MLE) via algoritma IRLS (Iteratively Reweighted Least Squares).

model_penuh <- glm(
  diabetes_positif ~ age + gender + polyuria + polydipsia +
    sudden_wt_loss + weakness + polyphagia + genital_thrush +
    visual_blurring + itching + irritability + delayed_healing +
    partial_paresis + muscle_stiffness + alopecia + obesity,
  data   = data_train,
  family = binomial(link = "logit")
)

7.2 Uji Linearitas dalam Logit: Box-Tidwell

Materi: Regresi logistik mengasumsikan hubungan linear antara prediktor kontinu dengan log-odds (logit) dari variabel respon. Uji Box-Tidwell (1962) mendeteksi non-linearitas dengan menambahkan interaksi \(X \cdot \ln(X)\) ke dalam model.

\[\text{logit}(P) = \beta_0 + \beta_1 X + \beta_2 \cdot [X \cdot \ln(X)]\]

Hipotesis:

  • \(H_0\): \(\beta_2 = 0\) — Hubungan antara age dan logit bersifat linear
  • \(H_1\): \(\beta_2 \neq 0\) — Hubungan tidak linear

Keputusan: Tolak \(H_0\) jika \(p < 0{,}05\)

Referensi: Box & Tidwell (1962), Technometrics, 4(4), 531–550; Hosmer & Lemeshow (2013), hal. 95–96.

data_train_bt <- data_train %>%
  mutate(age_log_age = age * log(age))

model_bt <- glm(
  diabetes_positif ~ age + age_log_age +
    gender + polyuria + polydipsia + sudden_wt_loss + weakness +
    polyphagia + genital_thrush + visual_blurring + itching +
    irritability + delayed_healing + partial_paresis +
    muscle_stiffness + alopecia + obesity,
  data   = data_train_bt,
  family = binomial(link = "logit")
)

bt_coef <- summary(model_bt)$coefficients
pval_bt <- bt_coef["age_log_age", "Pr(>|z|)"]
beta_bt <- bt_coef["age_log_age", "Estimate"]
se_bt   <- bt_coef["age_log_age", "Std. Error"]
z_bt    <- bt_coef["age_log_age", "z value"]

bt_result <- data.frame(
  Koefisien   = "age × ln(age)",
  Estimasi    = round(beta_bt, 4),
  SE          = round(se_bt,   4),
  `z-value`   = round(z_bt,   4),
  `p-value`   = round(pval_bt, 4),
  Keputusan   = ifelse(pval_bt > 0.05,
                       "Gagal tolak H0 — Asumsi TERPENUHI",
                       "Tolak H0 — Asumsi TIDAK terpenuhi"),
  check.names = FALSE
)

knitr::kable(bt_result, caption = "Hasil Uji Box-Tidwell untuk Variabel age")
Hasil Uji Box-Tidwell untuk Variabel age
Koefisien Estimasi SE z-value p-value Keputusan
age × ln(age) -0.1344 0.1293 -1.0398 0.2984 Gagal tolak H0 — Asumsi TERPENUHI

Interpretasi: Nilai \(p\)-value untuk koefisien interaksi age × ln(age) = 0.2984 > 0,05, sehingga \(H_0\) gagal ditolak. Tidak terdapat bukti hubungan non-linear antara variabel age dan log-odds kejadian diabetes. Dengan demikian, asumsi linearitas dalam logit terpenuhi dan variabel age dapat digunakan dalam bentuk aslinya tanpa transformasi lebih lanjut.

7.3 Uji Multikolinearitas: Variance Inflation Factor (VIF)

Materi: Multikolinearitas terjadi ketika prediktor saling berkorelasi tinggi, sehingga estimasi koefisien menjadi tidak stabil dan standar error membesar. Variance Inflation Factor (VIF) mengukur seberapa besar varians koefisien meningkat akibat kolinearitas:

\[\text{VIF}_j = \frac{1}{1 - R^2_j}\]

di mana \(R^2_j\) adalah koefisien determinasi dari regresi variabel \(X_j\) terhadap prediktor lainnya.

Threshold:

  • VIF ≥ 10: Masalah serius (Fox, 2016)
  • VIF ≥ 5: Masalah moderat (Hair et al., 2019) ← digunakan sebagai threshold dalam analisis ini

Referensi: Fox & Weisberg (2019), An R Companion to Applied Regression, 3rd ed.

vif_raw <- car::vif(model_penuh)

if (is.matrix(vif_raw)) {
  vif_df <- data.frame(
    Variabel           = rownames(vif_raw),
    GVIF               = round(vif_raw[, 1], 4),
    Df                 = vif_raw[, 2],
    `GVIF^(1/(2*Df))`  = round(vif_raw[, 3], 4),
    Keterangan         = ifelse(vif_raw[, 3] > 2.236, "Masalah!", "OK"),
    check.names        = FALSE
  )
  ada_masalah <- any(vif_raw[, 3] > 2.236)
} else {
  vif_df <- data.frame(
    Variabel   = names(vif_raw),
    VIF        = round(vif_raw, 4),
    Keterangan = ifelse(vif_raw > 5, "Masalah (VIF >= 5)!", "OK (VIF < 5)")
  )
  ada_masalah <- any(vif_raw > 5)
}

knitr::kable(vif_df, caption = "Hasil Uji Multikolinearitas (VIF) — Model Penuh")
Hasil Uji Multikolinearitas (VIF) — Model Penuh
Variabel VIF Keterangan
age age 2.2125 OK (VIF < 5)
gender gender 1.9705 OK (VIF < 5)
polyuria polyuria 2.6225 OK (VIF < 5)
polydipsia polydipsia 1.7235 OK (VIF < 5)
sudden_wt_loss sudden_wt_loss 1.6217 OK (VIF < 5)
weakness weakness 1.7699 OK (VIF < 5)
polyphagia polyphagia 1.5921 OK (VIF < 5)
genital_thrush genital_thrush 1.7476 OK (VIF < 5)
visual_blurring visual_blurring 2.7065 OK (VIF < 5)
itching itching 3.1867 OK (VIF < 5)
irritability irritability 1.3773 OK (VIF < 5)
delayed_healing delayed_healing 2.1922 OK (VIF < 5)
partial_paresis partial_paresis 1.3957 OK (VIF < 5)
muscle_stiffness muscle_stiffness 1.6886 OK (VIF < 5)
alopecia alopecia 2.4811 OK (VIF < 5)
obesity obesity 1.2828 OK (VIF < 5)

Interpretasi: Seluruh variabel prediktor memiliki nilai VIF < 5, yang berarti tidak terdapat multikolinearitas yang mengkhawatirkan antar variabel prediktor dalam model. Estimasi koefisien regresi dinilai stabil dan dapat dipercaya. Seluruh prediktor dapat dipertahankan dalam model penuh.


8 Pemodelan Regresi Logistik Biner

Materi — Model Regresi Logistik Biner:

Model regresi logistik biner memodelkan probabilitas kejadian suatu peristiwa (dalam hal ini: diabetes positif) sebagai fungsi dari satu atau lebih prediktor:

\[\text{logit}(P) = \ln\left(\frac{P}{1-P}\right) = \beta_0 + \beta_1 X_1 + \cdots + \beta_p X_p\]

Transformasi ke probabilitas: \[P(\text{diabetes positif}) = \frac{1}{1 + e^{-\text{logit}(P)}}\]

Parameter diestimasi menggunakan Maximum Likelihood Estimation (MLE), yang memaksimalkan fungsi log-likelihood: \[\ell(\boldsymbol{\beta}) = \sum_{i=1}^{n} \left[ y_i \ln(\hat{p}_i) + (1 - y_i) \ln(1 - \hat{p}_i) \right]\]

Referensi: Hosmer & Lemeshow (2013); Agresti (2018).

8.1 Estimasi Model Penuh (Confirmatory)

Materi: Model penuh dibangun dengan memasukkan seluruh 16 variabel prediktor secara bersamaan. Pendekatan confirmatory ini bertujuan menguji seluruh variabel yang secara teoritis dan klinis relevan terhadap kejadian diabetes, sekaligus sebagai titik awal dalam seleksi variabel stepwise.

knitr::kable(
  data.frame(
    Metrik   = c("AIC Model Penuh", "Deviasi Residual", "Deviasi Null"),
    Nilai    = c(round(AIC(model_penuh), 4),
                 round(deviance(model_penuh), 4),
                 round(model_penuh$null.deviance, 4)),
    df       = c(NA, model_penuh$df.residual, model_penuh$df.null)
  ),
  caption = "Ringkasan Statistik Model Penuh"
)
Ringkasan Statistik Model Penuh
Metrik Nilai df
AIC Model Penuh 164.8563 NA
Deviasi Residual 130.8563 399
Deviasi Null 557.0725 415
hasil_model_penuh <- broom::tidy(model_penuh) %>%
  mutate(
    OR      = exp(estimate),
    CI_low  = exp(estimate - 1.96 * std.error),
    CI_high = exp(estimate + 1.96 * std.error),
    Sig     = ifelse(p.value < 0.001, "***",
                     ifelse(p.value < 0.01,  "**",
                            ifelse(p.value < 0.05,  "*", "ns")))
  ) %>%
  transmute(
    Variabel       = term,
    Beta           = round(estimate,  4),
    SE             = round(std.error, 4),
    `p-value`      = round(p.value,   4),
    Sig,
    `Odds Ratio`   = round(OR,        4),
    `CI 95% Bawah` = round(CI_low,    4),
    `CI 95% Atas`  = round(CI_high,   4)
  )

knitr::kable(hasil_model_penuh,
             caption = "Koefisien dan Odds Ratio Model Penuh (Data Train)")
Koefisien dan Odds Ratio Model Penuh (Data Train)
Variabel Beta SE p-value Sig Odds Ratio CI 95% Bawah CI 95% Atas
(Intercept) 2.4500 1.2311 0.0466 * 11.5883 1.0377 129.4061
age -0.0493 0.0294 0.0932 ns 0.9519 0.8987 1.0083
gender -4.2611 0.6861 0.0000 *** 0.0141 0.0037 0.0541
polyuria 4.7658 0.8582 0.0000 *** 117.4296 21.8401 631.3947
polydipsia 4.4574 0.9976 0.0000 *** 86.2661 12.2086 609.5581
sudden_wt_loss 0.2900 0.6403 0.6507 ns 1.3364 0.3809 4.6882
weakness 0.8816 0.6096 0.1481 ns 2.4148 0.7311 7.9757
polyphagia 1.5003 0.6215 0.0158 * 4.4829 1.3260 15.1562
genital_thrush 2.1896 0.6465 0.0007 *** 8.9317 2.5156 31.7129
visual_blurring 1.4000 0.7855 0.0747 ns 4.0551 0.8698 18.9065
itching -2.8503 0.8101 0.0004 *** 0.0578 0.0118 0.2830
irritability 2.5133 0.6779 0.0002 *** 12.3451 3.2693 46.6165
delayed_healing -0.6967 0.6915 0.3137 ns 0.4982 0.1285 1.9321
partial_paresis 0.8044 0.5925 0.1746 ns 2.2353 0.6997 7.1403
muscle_stiffness -0.4132 0.6532 0.5270 ns 0.6615 0.1839 2.3799
alopecia -0.0849 0.7346 0.9080 ns 0.9186 0.2177 3.8767
obesity -0.4588 0.6147 0.4554 ns 0.6320 0.1895 2.1084

Interpretasi: Dari 16 variabel prediktor dalam model penuh, terdapat tujuh variabel yang signifikan (\(p < 0{,}05\)): gender, polyuria, polydipsia, polyphagia, genital_thrush, itching, dan irritability. Sembilan variabel lainnya tidak signifikan secara individual, namun akan dievaluasi kembali dalam proses seleksi stepwise.

8.2 Seleksi Variabel: Stepwise AIC

Materi: Pemilihan variabel menggunakan prosedur stepwise bidireksional berbasis Akaike Information Criterion (AIC). AIC menyeimbangkan goodness-of-fit dengan kompleksitas model melalui penalti jumlah parameter:

\[\text{AIC} = -2\ell(\hat{\boldsymbol{\beta}}) + 2k\]

di mana \(\ell(\hat{\boldsymbol{\beta}})\) adalah log-likelihood dan \(k\) adalah jumlah parameter. Model dengan AIC lebih kecil lebih disukai.

Prosedur both-direction menggabungkan:

  • Forward selection: memasukkan variabel satu per satu jika mengurangi AIC
  • Backward elimination: mengeluarkan variabel jika penghapusannya mengurangi AIC

Referensi: Akaike (1974), IEEE Trans. Automatic Control, 19(6), 716–723; Venables & Ripley (2002).

model_stepwise <- MASS::stepAIC(model_penuh, direction = "both", trace = 1)
## Start:  AIC=164.86
## diabetes_positif ~ age + gender + polyuria + polydipsia + sudden_wt_loss + 
##     weakness + polyphagia + genital_thrush + visual_blurring + 
##     itching + irritability + delayed_healing + partial_paresis + 
##     muscle_stiffness + alopecia + obesity
## 
##                    Df Deviance    AIC
## - alopecia          1   130.87 162.87
## - sudden_wt_loss    1   131.06 163.06
## - muscle_stiffness  1   131.25 163.25
## - obesity           1   131.42 163.42
## - delayed_healing   1   131.88 163.88
## - partial_paresis   1   132.71 164.71
## <none>                  130.86 164.86
## - weakness          1   132.98 164.98
## - age               1   133.81 165.81
## - visual_blurring   1   134.28 166.28
## - polyphagia        1   137.02 169.02
## - genital_thrush    1   143.95 175.95
## - itching           1   146.28 178.28
## - irritability      1   146.88 178.88
## - polydipsia        1   167.25 199.25
## - polyuria          1   186.43 218.43
## - gender            1   188.84 220.84
## 
## Step:  AIC=162.87
## diabetes_positif ~ age + gender + polyuria + polydipsia + sudden_wt_loss + 
##     weakness + polyphagia + genital_thrush + visual_blurring + 
##     itching + irritability + delayed_healing + partial_paresis + 
##     muscle_stiffness + obesity
## 
##                    Df Deviance    AIC
## - sudden_wt_loss    1   131.09 161.09
## - muscle_stiffness  1   131.25 161.25
## - obesity           1   131.42 161.42
## - delayed_healing   1   132.11 162.11
## - partial_paresis   1   132.71 162.71
## <none>                  130.87 162.87
## - weakness          1   133.00 163.00
## - visual_blurring   1   134.33 164.33
## - age               1   134.67 164.67
## + alopecia          1   130.86 164.86
## - polyphagia        1   137.11 167.11
## - genital_thrush    1   143.95 173.95
## - itching           1   146.84 176.84
## - irritability      1   147.25 177.25
## - polydipsia        1   167.46 197.46
## - polyuria          1   187.62 217.62
## - gender            1   191.32 221.32
## 
## Step:  AIC=161.09
## diabetes_positif ~ age + gender + polyuria + polydipsia + weakness + 
##     polyphagia + genital_thrush + visual_blurring + itching + 
##     irritability + delayed_healing + partial_paresis + muscle_stiffness + 
##     obesity
## 
##                    Df Deviance    AIC
## - obesity           1   131.60 159.60
## - muscle_stiffness  1   131.65 159.65
## - delayed_healing   1   132.29 160.29
## <none>                  131.09 161.09
## - partial_paresis   1   133.10 161.10
## - visual_blurring   1   134.55 162.55
## + sudden_wt_loss    1   130.87 162.87
## - weakness          1   134.88 162.88
## - age               1   134.94 162.94
## + alopecia          1   131.06 163.06
## - polyphagia        1   137.62 165.62
## - genital_thrush    1   144.71 172.71
## - irritability      1   147.31 175.31
## - itching           1   148.60 176.60
## - polydipsia        1   173.94 201.94
## - gender            1   197.37 225.37
## - polyuria          1   200.47 228.47
## 
## Step:  AIC=159.6
## diabetes_positif ~ age + gender + polyuria + polydipsia + weakness + 
##     polyphagia + genital_thrush + visual_blurring + itching + 
##     irritability + delayed_healing + partial_paresis + muscle_stiffness
## 
##                    Df Deviance    AIC
## - muscle_stiffness  1   132.24 158.24
## - delayed_healing   1   132.80 158.80
## - partial_paresis   1   133.57 159.57
## <none>                  131.60 159.60
## + obesity           1   131.09 161.09
## + sudden_wt_loss    1   131.42 161.42
## - weakness          1   135.45 161.45
## - visual_blurring   1   135.46 161.46
## - age               1   135.49 161.49
## + alopecia          1   131.58 161.58
## - polyphagia        1   139.40 165.40
## - genital_thrush    1   145.00 171.00
## - irritability      1   147.33 173.33
## - itching           1   150.72 176.72
## - polydipsia        1   175.13 201.13
## - gender            1   198.59 224.59
## - polyuria          1   200.61 226.61
## 
## Step:  AIC=158.24
## diabetes_positif ~ age + gender + polyuria + polydipsia + weakness + 
##     polyphagia + genital_thrush + visual_blurring + itching + 
##     irritability + delayed_healing + partial_paresis
## 
##                    Df Deviance    AIC
## - delayed_healing   1   133.37 157.37
## <none>                  132.24 158.24
## - partial_paresis   1   134.56 158.56
## - visual_blurring   1   135.56 159.56
## + muscle_stiffness  1   131.60 159.60
## + obesity           1   131.65 159.65
## - weakness          1   135.81 159.81
## + sudden_wt_loss    1   131.88 159.88
## + alopecia          1   132.24 160.24
## - age               1   136.71 160.71
## - polyphagia        1   139.41 163.41
## - genital_thrush    1   146.61 170.61
## - irritability      1   147.92 171.92
## - itching           1   151.09 175.09
## - polydipsia        1   175.68 199.68
## - gender            1   198.93 222.93
## - polyuria          1   200.62 224.62
## 
## Step:  AIC=157.37
## diabetes_positif ~ age + gender + polyuria + polydipsia + weakness + 
##     polyphagia + genital_thrush + visual_blurring + itching + 
##     irritability + partial_paresis
## 
##                    Df Deviance    AIC
## <none>                  133.37 157.37
## - partial_paresis   1   135.86 157.86
## - visual_blurring   1   136.17 158.17
## - weakness          1   136.20 158.20
## + delayed_healing   1   132.24 158.24
## + obesity           1   132.79 158.79
## + muscle_stiffness  1   132.80 158.80
## + sudden_wt_loss    1   133.05 159.05
## + alopecia          1   133.26 159.26
## - age               1   139.05 161.05
## - polyphagia        1   142.82 164.82
## - genital_thrush    1   146.83 168.83
## - irritability      1   150.32 172.32
## - itching           1   158.37 180.37
## - polydipsia        1   188.98 210.98
## - polyuria          1   202.33 224.33
## - gender            1   203.38 225.38
summary(model_stepwise)
## 
## Call:
## glm(formula = diabetes_positif ~ age + gender + polyuria + polydipsia + 
##     weakness + polyphagia + genital_thrush + visual_blurring + 
##     itching + irritability + partial_paresis, family = binomial(link = "logit"), 
##     data = data_train)
## 
## Coefficients:
##                 Estimate Std. Error z value Pr(>|z|)    
## (Intercept)      2.72645    1.09442   2.491 0.012730 *  
## age             -0.05761    0.02493  -2.311 0.020817 *  
## gender          -4.45055    0.68810  -6.468 9.94e-11 ***
## polyuria         4.70118    0.74793   6.286 3.27e-10 ***
## polydipsia       4.96158    0.90873   5.460 4.76e-08 ***
## weakness         0.83647    0.50288   1.663 0.096240 .  
## polyphagia       1.65795    0.56618   2.928 0.003408 ** 
## genital_thrush   2.15717    0.62440   3.455 0.000551 ***
## visual_blurring  1.19318    0.74389   1.604 0.108720    
## itching         -3.22018    0.74776  -4.306 1.66e-05 ***
## irritability     2.42881    0.62281   3.900 9.63e-05 ***
## partial_paresis  0.89698    0.57265   1.566 0.117259    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 557.07  on 415  degrees of freedom
## Residual deviance: 133.37  on 404  degrees of freedom
## AIC: 157.37
## 
## Number of Fisher Scoring iterations: 8
var_final   <- names(coef(model_stepwise))[-1]
var_semua   <- names(coef(model_penuh))[-1]
var_dihapus <- setdiff(var_semua, var_final)

perbandingan_aic <- data.frame(
  Model       = c("Model Penuh (16 prediktor)", "Model Final (Stepwise)"),
  `Jumlah Prediktor` = c(16, length(var_final)),
  AIC         = round(c(AIC(model_penuh), AIC(model_stepwise)), 4),
  `Delta AIC` = round(c(AIC(model_penuh) - AIC(model_stepwise), 0), 4),
  Keterangan  = c("Model lebih kompleks", "Model lebih parsimonious (dipilih)"),
  check.names = FALSE
)

knitr::kable(perbandingan_aic, caption = "Perbandingan AIC Model Penuh vs Model Final")
Perbandingan AIC Model Penuh vs Model Final
Model Jumlah Prediktor AIC Delta AIC Keterangan
Model Penuh (16 prediktor) 16 164.8563 7.4852 Model lebih kompleks
Model Final (Stepwise) 11 157.3711 0.0000 Model lebih parsimonious (dipilih)

Interpretasi: Proses seleksi stepwise mengeliminasi 5 variabel yaitu: sudden_wt_loss, delayed_healing, muscle_stiffness, alopecia, obesity. Model final memuat 11 variabel prediktor dengan AIC = 157.3711, lebih kecil 7.49 poin dari model penuh (AIC = 164.8563). Penurunan AIC mengindikasikan model stepwise memberikan keseimbangan lebih baik antara ketepatan prediksi dan kesederhanaan model.

8.3 Model Final: Estimasi Koefisien dan Odds Ratio

hasil_final <- broom::tidy(model_stepwise) %>%
  mutate(
    OR      = exp(estimate),
    CI_low  = exp(estimate - 1.96 * std.error),
    CI_high = exp(estimate + 1.96 * std.error),
    Sig     = ifelse(p.value < 0.001, "***",
                     ifelse(p.value < 0.01,  "**",
                            ifelse(p.value < 0.05,  "*", "ns")))
  ) %>%
  transmute(
    Variabel       = term,
    Beta           = round(estimate,  4),
    SE             = round(std.error, 4),
    `p-value`      = round(p.value,   4),
    Sig,
    `Odds Ratio`   = round(OR,        4),
    `CI 95% Bawah` = round(CI_low,    4),
    `CI 95% Atas`  = round(CI_high,   4)
  )

knitr::kable(hasil_final,
             caption = "Koefisien dan Odds Ratio Model Final (Stepwise)")
Koefisien dan Odds Ratio Model Final (Stepwise)
Variabel Beta SE p-value Sig Odds Ratio CI 95% Bawah CI 95% Atas
(Intercept) 2.7265 1.0944 0.0127 * 15.2786 1.7885 130.5180
age -0.0576 0.0249 0.0208 * 0.9440 0.8990 0.9913
gender -4.4506 0.6881 0.0000 *** 0.0117 0.0030 0.0450
polyuria 4.7012 0.7479 0.0000 *** 110.0765 25.4123 476.8107
polydipsia 4.9616 0.9087 0.0000 *** 142.8197 24.0580 847.8445
weakness 0.8365 0.5029 0.0962 ns 2.3082 0.8614 6.1849
polyphagia 1.6580 0.5662 0.0034 ** 5.2486 1.7302 15.9214
genital_thrush 2.1572 0.6244 0.0006 *** 8.6466 2.5430 29.4000
visual_blurring 1.1932 0.7439 0.1087 ns 3.2976 0.7673 14.1712
itching -3.2202 0.7478 0.0000 *** 0.0399 0.0092 0.1730
irritability 2.4288 0.6228 0.0001 *** 11.3453 3.3471 38.4563
partial_paresis 0.8970 0.5726 0.1173 ns 2.4522 0.7982 7.5336

8.4 Persamaan Model Final

Materi: Persamaan model logistik final dapat ditulis dalam bentuk logit (log-odds) dan kemudian ditransformasi ke probabilitas menggunakan fungsi sigmoid (inverse logit).

koef <- coef(model_stepwise)
label_map <- c(
  "(Intercept)"     = "Intercept",
  "age"             = "Usia",
  "gender"          = "Jenis Kelamin (Laki-laki vs Perempuan)",
  "polyuria"        = "Polyuria",
  "polydipsia"      = "Polydipsia",
  "sudden_wt_loss"  = "Penurunan BB Mendadak",
  "weakness"        = "Kelemahan Fisik",
  "polyphagia"      = "Polyphagia",
  "genital_thrush"  = "Genital Thrush",
  "visual_blurring" = "Penglihatan Kabur",
  "itching"         = "Gatal-gatal",
  "irritability"    = "Iritabilitas",
  "delayed_healing" = "Penyembuhan Luka Lambat",
  "partial_paresis" = "Paresis Parsial",
  "muscle_stiffness"= "Kekakuan Otot",
  "alopecia"        = "Alopesia",
  "obesity"         = "Obesitas"
)

persamaan <- paste0(
  "$$\\text{logit}(P) = ", round(koef[1], 4),
  paste(sapply(seq_along(koef)[-1], function(i) {
    tanda <- ifelse(koef[i] >= 0, " + ", " - ")
    paste0(tanda, abs(round(koef[i], 4)), " \\cdot \\text{", names(koef)[i], "}")
  }), collapse = ""),
  "$$"
)
cat(persamaan)

\[\text{logit}(P) = 2.7265 - 0.0576 \cdot \text{age} - 4.4506 \cdot \text{gender} + 4.7012 \cdot \text{polyuria} + 4.9616 \cdot \text{polydipsia} + 0.8365 \cdot \text{weakness} + 1.658 \cdot \text{polyphagia} + 2.1572 \cdot \text{genital_thrush} + 1.1932 \cdot \text{visual_blurring} - 3.2202 \cdot \text{itching} + 2.4288 \cdot \text{irritability} + 0.897 \cdot \text{partial_paresis}\]

\[P(\text{diabetes positif}) = \frac{1}{1 + e^{-\text{logit}(P)}}\]


9 Pengujian Signifikansi dan Kecocokan Model

9.1 Uji G: Likelihood Ratio Test (Uji Omnibus)

Materi: Uji G² (Likelihood Ratio Test) menguji signifikansi model secara keseluruhan dengan membandingkan model final (dengan prediktor) terhadap model null (hanya intercept):

\[G^2 = -2\left[\ell(\hat{\boldsymbol{\beta}}_\text{null}) - \ell(\hat{\boldsymbol{\beta}}_\text{model})\right] = D_\text{null} - D_\text{model}\]

Statistik \(G^2\) mengikuti distribusi \(\chi^2\) dengan derajat bebas = jumlah prediktor dalam model.

Hipotesis:

  • \(H_0\): Semua koefisien prediktor = 0
  • \(H_1\): Setidaknya satu koefisien \(\neq\) 0

Referensi: Hosmer & Lemeshow (2013), hal. 10–14.

model_null_final <- glm(diabetes_positif ~ 1,
                        data   = data_train,
                        family = binomial)

lr_stat   <- model_null_final$deviance - model_stepwise$deviance
lr_df     <- model_null_final$df.residual - model_stepwise$df.residual
lr_pvalue <- pchisq(lr_stat, df = lr_df, lower.tail = FALSE)

lrt_result <- data.frame(
  Model    = c("Model Null (intercept only)", "Model Final (Stepwise)"),
  Deviance = round(c(model_null_final$deviance, model_stepwise$deviance), 4),
  df       = c(model_null_final$df.residual, model_stepwise$df.residual)
)

knitr::kable(lrt_result, caption = "Deviasi Model Null vs Model Final")
Deviasi Model Null vs Model Final
Model Deviance df
Model Null (intercept only) 557.0725 415
Model Final (Stepwise) 133.3711 404
knitr::kable(
  data.frame(
    Statistik  = c("G²", "Derajat Bebas (df)", "p-value", "Keputusan"),
    Nilai      = c(round(lr_stat, 4), lr_df,
                   formatC(lr_pvalue, format = "e", digits = 4),
                   ifelse(lr_pvalue < 0.05, "Tolak H0", "Gagal Tolak H0"))
  ),
  caption = "Hasil Uji G² (Likelihood Ratio Test)"
)
Hasil Uji G² (Likelihood Ratio Test)
Statistik Nilai
423.7014
Derajat Bebas (df) 11
p-value 5.6490e-84
Keputusan Tolak H0

Interpretasi: Nilai \(G^2\) = 423.7014 dengan df = 11 dan \(p\)-value < 0,0001, sehingga \(H_0\) ditolak. Model final secara keseluruhan signifikan secara statistik — setidaknya satu prediktor berpengaruh signifikan terhadap probabilitas diabetes positif. Model layak untuk dilanjutkan ke tahap interpretasi.

9.2 Uji Hosmer-Lemeshow: Goodness of Fit

Materi: Uji Hosmer-Lemeshow mengevaluasi kalibrasi model — apakah probabilitas prediksi sesuai dengan frekuensi aktual di data. Observasi dikelompokkan ke dalam \(g = 10\) desil berdasarkan probabilitas prediksi, kemudian dibandingkan frekuensi observasi vs ekspektasi menggunakan statistik: \[\hat{C} = \sum_{g=1}^{G} \left[ \frac{(O_g - E_g)^2}{E_g} + \frac{(O'_g - E'_g)^2}{E'_g} \right] \sim \chi^2(G-2)\]

Keterangan:

  • \(O_g\) = jumlah kejadian aktual (positif) pada grup ke-\(g\)
  • \(E_g\) = jumlah ekspektasi kejadian (positif) pada grup ke-\(g\)
  • \(O'_g\) = jumlah tidak kejadian aktual (negatif) pada grup ke-\(g\)
  • \(E'_g\) = jumlah ekspektasi tidak kejadian (negatif) pada grup ke-\(g\)
  • \(G\) = jumlah grup (umumnya \(G = 10\) desil)

Hipotesis:

  • \(H_0\): Model fit — tidak ada perbedaan signifikan antara prediksi dan observasi aktual
  • \(H_1\): Model tidak fit

Keputusan: Gagal tolak \(H_0\) jika \(p > 0{,}05\) → Model layak digunakan.

Referensi: Hosmer & Lemeshow (1980), Communications in Statistics, 9(10), 1043–1069.

hl_test <- ResourceSelection::hoslem.test(
  data_train$diabetes_positif,
  fitted(model_stepwise),
  g = 10
)

hl_result <- data.frame(
  Statistik   = "Chi-square Hosmer-Lemeshow",
  Nilai       = round(hl_test$statistic, 4),
  df          = hl_test$parameter,
  `p-value`   = round(hl_test$p.value,  4),
  Keputusan   = ifelse(hl_test$p.value > 0.05,
                       "Gagal tolak H0 — Model FIT",
                       "Tolak H0 — Model TIDAK FIT"),
  check.names = FALSE
)

knitr::kable(hl_result, caption = "Hasil Uji Hosmer-Lemeshow")
Hasil Uji Hosmer-Lemeshow
Statistik Nilai df p-value Keputusan
X-squared Chi-square Hosmer-Lemeshow 7.8498 8 0.4483 Gagal tolak H0 — Model FIT

Interpretasi: Nilai Chi-square = 7.8498 dengan df = 8 dan \(p\)-value = 0.4483 > 0,05, sehingga \(H_0\) gagal ditolak. Tidak terdapat bukti bahwa model menghasilkan prediksi yang menyimpang secara sistematis dari data aktual. Dengan demikian, model final dinyatakan fit dan layak digunakan.


10 Odds Ratio dan Interpretasi Faktor Risiko

Materi: Odds Ratio (OR) merupakan ukuran utama dalam regresi logistik yang mengukur kekuatan asosiasi antara prediktor dan variabel respon:

\[\text{OR} = e^{\hat{\beta}_j}\]

  • OR > 1: Variabel merupakan faktor risiko — meningkatkan peluang diabetes positif
  • OR < 1: Variabel merupakan faktor protektif — menurunkan peluang diabetes positif
  • OR = 1: Tidak ada asosiasi

Confidence Interval 95%: \[\text{CI 95\%} = \left[e^{\hat{\beta}_j - 1{,}96 \cdot SE_j},\; e^{\hat{\beta}_j + 1{,}96 \cdot SE_j}\right]\]

Apabila CI 95% tidak mencakup nilai 1, maka OR dinyatakan signifikan secara statistik.

10.1 Label Variabel

hasil_final_label <- hasil_final %>%
  mutate(
    Variabel_ID = Variabel,
    Variabel    = dplyr::recode(Variabel, !!!label_map)
  ) %>%
  dplyr::filter(Variabel_ID != "(Intercept)")

final_sig    <- hasil_final_label %>% dplyr::filter(`p-value` < 0.05)
final_nonsig <- hasil_final_label %>% dplyr::filter(`p-value` >= 0.05)

10.2 Faktor Risiko Signifikan (\(p < 0{,}05\))

knitr::kable(
  final_sig %>%
    dplyr::select(Variabel, Beta, `p-value`, Sig,
                  `Odds Ratio`, `CI 95% Bawah`, `CI 95% Atas`),
  caption = "Faktor Risiko Signifikan dalam Model Final"
)
Faktor Risiko Signifikan dalam Model Final
Variabel Beta p-value Sig Odds Ratio CI 95% Bawah CI 95% Atas
Usia -0.0576 0.0208 * 0.9440 0.8990 0.9913
Jenis Kelamin (Laki-laki vs Perempuan) -4.4506 0.0000 *** 0.0117 0.0030 0.0450
Polyuria 4.7012 0.0000 *** 110.0765 25.4123 476.8107
Polydipsia 4.9616 0.0000 *** 142.8197 24.0580 847.8445
Polyphagia 1.6580 0.0034 ** 5.2486 1.7302 15.9214
Genital Thrush 2.1572 0.0006 *** 8.6466 2.5430 29.4000
Gatal-gatal -3.2202 0.0000 *** 0.0399 0.0092 0.1730
Iritabilitas 2.4288 0.0001 *** 11.3453 3.3471 38.4563

Interpretasi Odds Ratio (variabel signifikan):

  • Polydipsia (OR ≈ 143): Individu dengan polydipsia memiliki odds ~143 kali lebih tinggi untuk diabetes positif — faktor risiko terkuat dalam model. Secara klinis, kadar glukosa tinggi menyebabkan osmolalitas darah meningkat sehingga memicu rasa haus berlebihan.

  • Polyuria (OR ≈ 110): Individu dengan polyuria memiliki odds ~110 kali lebih tinggi. Glukosa berlebih di darah melebihi ambang ginjal sehingga terbuang bersama urin dalam volume besar.

  • Irritabilitas (OR ≈ 11): Fluktuasi gula darah memengaruhi fungsi korteks prefrontal yang mengatur pengendalian emosi, sehingga iritabilitas menjadi gejala yang relevan secara klinis.

  • Genital Thrush (OR ≈ 9): Kadar glukosa tinggi di urin (glikosuria) menciptakan lingkungan ideal bagi pertumbuhan Candida albicans di area genital.

  • Polyphagia (OR ≈ 5): Meskipun glukosa darah tinggi, sel tubuh tidak dapat menggunakannya akibat defisiensi insulin, sehingga tubuh terus mengirim sinyal lapar.

  • Gender laki-laki dan Usia (OR < 1): Kedua variabel menunjukkan arah negatif dalam konteks multivariat — temuan ini mencerminkan efek mediasi oleh gejala klinis yang dikontrol dalam model, bukan hubungan biologis langsung.

  • Itching (OR ≈ 0,04): Arah negatif ini perlu diinterpretasikan hati-hati; kemungkinan mencerminkan pola spesifik dataset, bukan bukti bahwa gatal bersifat protektif terhadap diabetes.

10.3 Peringkat Faktor Risiko Dominan

dominan <- final_sig %>%
  arrange(desc(`Odds Ratio`)) %>%
  mutate(Peringkat = row_number()) %>%
  dplyr::select(Peringkat, Variabel, `Odds Ratio`, `CI 95% Bawah`, `CI 95% Atas`,
                `p-value`, Sig)

knitr::kable(dominan, caption = "Peringkat Faktor Risiko Dominan Berdasarkan Odds Ratio")
Peringkat Faktor Risiko Dominan Berdasarkan Odds Ratio
Peringkat Variabel Odds Ratio CI 95% Bawah CI 95% Atas p-value Sig
1 Polydipsia 142.8197 24.0580 847.8445 0.0000 ***
2 Polyuria 110.0765 25.4123 476.8107 0.0000 ***
3 Iritabilitas 11.3453 3.3471 38.4563 0.0001 ***
4 Genital Thrush 8.6466 2.5430 29.4000 0.0006 ***
5 Polyphagia 5.2486 1.7302 15.9214 0.0034 **
6 Usia 0.9440 0.8990 0.9913 0.0208 *
7 Gatal-gatal 0.0399 0.0092 0.1730 0.0000 ***
8 Jenis Kelamin (Laki-laki vs Perempuan) 0.0117 0.0030 0.0450 0.0000 ***

10.4 Forest Plot: Odds Ratio Model Final

or_plot <- hasil_final_label %>%
  mutate(
    Variabel = reorder(Variabel, `Odds Ratio`),
    Signif   = ifelse(Sig %in% c("*", "**", "***"),
                      "Signifikan (p < 0.05)", "Tidak Signifikan")
  )

ggplot(or_plot, aes(x = `Odds Ratio`, y = Variabel, color = Signif)) +
  geom_vline(xintercept = 1, linetype = "dashed", color = "gray50",
             linewidth = 0.7) +
  geom_errorbar(aes(xmin = `CI 95% Bawah`, xmax = `CI 95% Atas`,
                    y = Variabel),
                width = 0.3, linewidth = 0.7,
                orientation = "y") +
  geom_point(size = 3.5) +
  scale_x_log10() +
  scale_color_manual(values = c("Signifikan (p < 0.05)" = "#2980b9",
                                "Tidak Signifikan"      = "#95a5a6")) +
  labs(
    title    = "Forest Plot: Odds Ratio Faktor Risiko Diabetes",
    subtitle = "Model Final (Stepwise) — Data Train | CI 95%",
    x        = "Odds Ratio (skala logaritmik)",
    y        = "Variabel Prediktor",
    color    = "Signifikansi"
  ) +
  theme_minimal(base_size = 11) +
  theme(legend.position = "bottom")
Forest Plot Odds Ratio Model Final

Forest Plot Odds Ratio Model Final


11 Evaluasi Kinerja Model

Materi: Evaluasi kinerja dilakukan pada data test (out-of-sample) untuk menilai kemampuan generalisasi model terhadap data baru yang tidak digunakan dalam proses pembangunan model.

11.1 Prediksi pada Data Test

data_test$prob_pred  <- predict(model_stepwise, newdata = data_test,
                                type = "response")
data_test$pred_biner <- ifelse(data_test$prob_pred >= 0.5, 1, 0)

11.2 Pseudo R-Square

Materi: Koefisien determinasi \(R^2\) tidak berlaku langsung untuk regresi logistik. Beberapa ukuran pseudo R² digunakan sebagai analogi:

Ukuran Formula Acuan “Baik”
McFadden R² \(1 - \ell(\hat{\boldsymbol{\beta}}_\text{model}) / \ell(\hat{\boldsymbol{\beta}}_\text{null})\) > 0,20
Cox-Snell R² \(1 - \exp(-G^2/n)\) Tidak ada acuan baku
Nagelkerke R² Cox-Snell / max(Cox-Snell) > 0,30

Referensi: McFadden (1974); Nagelkerke (1991); Cox & Snell (1989).

r2_mcf <- 1 - model_stepwise$deviance / model_stepwise$null.deviance
r2_cs  <- 1 - exp((model_stepwise$deviance - model_stepwise$null.deviance) /
                    nobs(model_stepwise))
r2_nag <- r2_cs / (1 - exp(-model_stepwise$null.deviance / nobs(model_stepwise)))

pseudo_r2 <- data.frame(
  Ukuran       = c("McFadden R²", "Cox-Snell R²", "Nagelkerke R²"),
  Nilai        = round(c(r2_mcf, r2_cs, r2_nag), 4),
  `Nilai (%)`  = paste0(round(c(r2_mcf, r2_cs, r2_nag) * 100, 2), "%"),
  Acuan        = c("> 0.20 (Hosmer & Lemeshow, 2013)",
                   "Tidak ada acuan baku",
                   "> 0.30 (Nagelkerke, 1991)"),
  Interpretasi = c(
    ifelse(r2_mcf > 0.20, "Sangat Baik", "Perlu ditingkatkan"),
    "-",
    ifelse(r2_nag > 0.30, "Sangat Baik", "Perlu ditingkatkan")
  ),
  check.names = FALSE
)

knitr::kable(pseudo_r2, caption = "Pseudo R-Square Model Final (Stepwise, Data Train)")
Pseudo R-Square Model Final (Stepwise, Data Train)
Ukuran Nilai Nilai (%) Acuan Interpretasi
McFadden R² 0.7606 76.06% > 0.20 (Hosmer & Lemeshow, 2013) Sangat Baik
Cox-Snell R² 0.6389 63.89% Tidak ada acuan baku -
Nagelkerke R² 0.8658 86.58% > 0.30 (Nagelkerke, 1991) Sangat Baik

Interpretasi:

  • McFadden R² = 0.7606: Model menjelaskan ~76.1% informasi relatif terhadap model null. Jauh melampaui threshold 0,20, menunjukkan daya diskriminasi model yang sangat kuat.

  • Cox-Snell R² = 0.6389: Sekitar 63.9% variabilitas variabel dependen dikaitkan dengan prediktor dalam model.

  • Nagelkerke R² = 0.8658: Model mampu menjelaskan sekitar 86.6% variabilitas kejadian diabetes — kategori sangat baik.

Ketiga ukuran pseudo R² secara konsisten menunjukkan nilai yang jauh melampaui threshold kelayakan, mengindikasikan kualitas model secara menyeluruh.

11.3 Confusion Matrix dan Metrik Klasifikasi (Cut-off = 0,5)

Materi: Confusion Matrix merangkum hasil prediksi model dalam empat kategori:

Prediksi Positif Prediksi Negatif
Aktual Positif True Positive (TP) False Negative (FN)
Aktual Negatif False Positive (FP) True Negative (TN)

Metrik turunan:

\[\text{Accuracy} = \frac{TP+TN}{n}, \quad \text{Sensitivity} = \frac{TP}{TP+FN}, \quad \text{Specificity} = \frac{TN}{TN+FP}\]

\[\text{Precision} = \frac{TP}{TP+FP}, \quad F_1 = \frac{2 \cdot \text{Precision} \cdot \text{Sensitivity}}{\text{Precision} + \text{Sensitivity}}\]

Catatan klinis: Dalam konteks skrining diabetes, Sensitivity lebih diprioritaskan karena False Negative (sakit diprediksi sehat) lebih berbahaya daripada False Positive.

conf_mat <- table(
  Aktual   = factor(data_test$diabetes_positif, levels = c(0, 1),
                    labels = c("Negatif", "Positif")),
  Prediksi = factor(data_test$pred_biner,       levels = c(0, 1),
                    labels = c("Negatif", "Positif"))
)

knitr::kable(addmargins(conf_mat),
             caption = "Confusion Matrix — Model Final (Data Test, cut-off = 0,5)")
Confusion Matrix — Model Final (Data Test, cut-off = 0,5)
Negatif Positif Sum
Negatif 32 5 37
Positif 5 62 67
Sum 37 67 104
tp <- conf_mat["Positif", "Positif"]
tn <- conf_mat["Negatif", "Negatif"]
fp <- conf_mat["Negatif", "Positif"]
fn <- conf_mat["Positif", "Negatif"]

akurasi     <- (tp + tn) / sum(conf_mat)
sensitivity <- tp / (tp + fn)
specificity <- tn / (tn + fp)
precision   <- tp / (tp + fp)
npv         <- tn / (tn + fn)
f1_score    <- 2 * precision * sensitivity / (precision + sensitivity)

metrik_eval <- data.frame(
  Metrik       = c("Accuracy", "Sensitivity (Recall/TPR)",
                   "Specificity (TNR)", "Precision (PPV)", "NPV", "F1-Score"),
  Nilai        = round(c(akurasi, sensitivity, specificity,
                         precision, npv, f1_score), 4),
  `Nilai (%)`  = paste0(round(c(akurasi, sensitivity, specificity,
                                precision, npv, f1_score) * 100, 2), "%"),
  Interpretasi = c(
    "Proporsi prediksi benar secara keseluruhan",
    "Kemampuan mendeteksi kasus diabetes positif (prioritas skrining)",
    "Kemampuan mendeteksi kasus diabetes negatif",
    "Dari prediksi positif, proporsi yang benar-benar positif",
    "Dari prediksi negatif, proporsi yang benar-benar negatif",
    "Rata-rata harmonis Precision dan Sensitivity"
  ),
  check.names = FALSE
)

knitr::kable(metrik_eval,
             caption = "Metrik Evaluasi Kinerja Klasifikasi — Data Test (cut-off = 0,5)")
Metrik Evaluasi Kinerja Klasifikasi — Data Test (cut-off = 0,5)
Metrik Nilai Nilai (%) Interpretasi
Accuracy 0.9038 90.38% Proporsi prediksi benar secara keseluruhan
Sensitivity (Recall/TPR) 0.9254 92.54% Kemampuan mendeteksi kasus diabetes positif (prioritas skrining)
Specificity (TNR) 0.8649 86.49% Kemampuan mendeteksi kasus diabetes negatif
Precision (PPV) 0.9254 92.54% Dari prediksi positif, proporsi yang benar-benar positif
NPV 0.8649 86.49% Dari prediksi negatif, proporsi yang benar-benar negatif
F1-Score 0.9254 92.54% Rata-rata harmonis Precision dan Sensitivity

Interpretasi: Model menunjukkan performa yang sangat baik pada data test:

  • Accuracy = 90.38%: 90.38% prediksi benar secara keseluruhan.
  • Sensitivity = 92.54%: Model berhasil mendeteksi 92.54% kasus diabetes positif yang sebenarnya.
  • Specificity = 86.49%: Model berhasil mengidentifikasi 86.49% kasus negatif yang sebenarnya.
  • F1-Score = 0.9254: Keseimbangan baik antara precision dan sensitivity.

11.4 Penentuan Threshold Optimal: Youden Index

Materi: Cut-off default 0,5 tidak selalu optimal, terutama dalam konteks skrining medis. Youden Index menentukan threshold yang memaksimalkan jumlah Sensitivity dan Specificity:

\[J = \text{Sensitivity} + \text{Specificity} - 1\] Threshold optimal = nilai probabilitas yang menghasilkan \(J\) maksimum.

Referensi: Youden, W.J. (1950), Cancer, 3(1), 32–35.

# Prediksi probabilitas pada data test
data_test$prob_pred <- predict(model_stepwise, newdata = data_test, type = "response")

# Buat objek ROC
roc_obj   <- pROC::roc(data_test$diabetes_positif, data_test$prob_pred, quiet = TRUE)
auc_value <- pROC::auc(roc_obj)
ci_auc    <- pROC::ci.auc(roc_obj)

# Threshold optimal (Youden)
coords_opt    <- pROC::coords(roc_obj, x = "best", best.method = "youden",
                               ret = c("threshold", "sensitivity", "specificity"))
threshold_opt <- as.numeric(coords_opt["threshold"])
sens_opt      <- as.numeric(coords_opt["sensitivity"])
spec_opt      <- as.numeric(coords_opt["specificity"])

# Prediksi ulang dengan threshold optimal
data_test$pred_opt <- ifelse(data_test$prob_pred >= threshold_opt, 1, 0)

conf_mat_opt <- table(
  Aktual   = factor(data_test$diabetes_positif, levels = c(0, 1),
                    labels = c("Negatif", "Positif")),
  Prediksi = factor(data_test$pred_opt,         levels = c(0, 1),
                    labels = c("Negatif", "Positif"))
)

knitr::kable(addmargins(conf_mat_opt),
             caption = paste0("Confusion Matrix — Threshold Optimal Youden (",
                              round(threshold_opt, 4), ")"))                      
Confusion Matrix — Threshold Optimal Youden (0.6572)
Negatif Positif Sum
Negatif 34 3 37
Positif 5 62 67
Sum 39 65 104
tp2 <- conf_mat_opt["Positif", "Positif"]
tn2 <- conf_mat_opt["Negatif", "Negatif"]
fp2 <- conf_mat_opt["Negatif", "Positif"]
fn2 <- conf_mat_opt["Positif", "Negatif"]

akurasi2     <- (tp2 + tn2) / sum(conf_mat_opt)
sensitivity2 <- tp2 / (tp2 + fn2)
specificity2 <- tn2 / (tn2 + fp2)
precision2   <- tp2 / (tp2 + fp2)
npv2         <- tn2 / (tn2 + fn2)
f1_score2    <- 2 * precision2 * sensitivity2 / (precision2 + sensitivity2)

perbandingan_threshold <- data.frame(
  Threshold   = c("Default (0,5)",
                  paste0("Optimal Youden (", round(threshold_opt, 4), ")")),
  Accuracy    = paste0(round(c(akurasi,     akurasi2)     * 100, 2), "%"),
  Sensitivity = paste0(round(c(sensitivity, sensitivity2) * 100, 2), "%"),
  Specificity = paste0(round(c(specificity, specificity2) * 100, 2), "%"),
  F1_Score    = round(c(f1_score, f1_score2), 4)
)

knitr::kable(perbandingan_threshold,
             caption = "Perbandingan Kinerja Model: Threshold Default vs Threshold Optimal (Youden)")
Perbandingan Kinerja Model: Threshold Default vs Threshold Optimal (Youden)
Threshold Accuracy Sensitivity Specificity F1_Score
Default (0,5) 90.38% 92.54% 86.49% 0.9254
Optimal Youden (0.6572) 92.31% 92.54% 91.89% 0.9394

Interpretasi: Threshold optimal sebesar 0.6572 diperoleh menggunakan Youden Index. Dibandingkan cut-off default 0,5, threshold optimal menghasilkan sensitivity yang lebih tinggi (92.54% vs 92.54%), meskipun dengan sedikit penurunan specificity. Dalam konteks skrining diabetes, sensitivity lebih diprioritaskan untuk meminimalkan false negative (kasus positif yang tidak terdeteksi), sehingga threshold optimal lebih disarankan untuk aplikasi klinis.

11.5 Kurva ROC dan AUC

Materi: Kurva ROC (Receiver Operating Characteristic) menggambarkan trade-off antara Sensitivity (sumbu Y) dan 1 − Specificity (sumbu X) pada berbagai nilai threshold. Area Under the Curve (AUC) merangkum performa diskriminasi model:

Nilai AUC Interpretasi
0,90 – 1,00 Sangat Baik
0,80 – 0,90 Baik
0,70 – 0,80 Cukup
< 0,70 Lemah

AUC juga dikenal sebagai concordance index (c-statistic): probabilitas model memberikan skor probabilitas lebih tinggi pada kasus positif dibanding negatif.

Referensi: Hanley & McNeil (1982), Radiology, 143(1), 29–36.

roc_obj   <- pROC::roc(data_test$diabetes_positif,
                       data_test$prob_pred,
                       quiet = TRUE)
auc_value <- pROC::auc(roc_obj)
ci_auc    <- pROC::ci.auc(roc_obj)

auc_result <- data.frame(
  Metrik       = c("AUC", "CI 95% Bawah", "CI 95% Atas"),
  Nilai        = round(c(as.numeric(auc_value), ci_auc[1], ci_auc[3]), 4),
  Interpretasi = c(
    ifelse(auc_value > 0.90, "Sangat Baik (> 0,90)",
           ifelse(auc_value > 0.80, "Baik (0,80–0,90)",
                  ifelse(auc_value > 0.70, "Cukup (0,70–0,80)", "Lemah (< 0,70)"))),
    "-", "-"
  )
)

knitr::kable(auc_result, caption = "Area Under the ROC Curve (AUC) — Data Test")
Area Under the ROC Curve (AUC) — Data Test
Metrik Nilai Interpretasi
AUC 0.9685 Sangat Baik (> 0,90)
CI 95% Bawah 0.9418 -
CI 95% Atas 0.9952 -

Interpretasi: AUC = 0.9685 (CI 95%: 0.9418–0.9952) — termasuk kategori sangat baik (> 0,90). Model mampu membedakan pasien diabetes dan non-diabetes dengan tingkat kebenaran sebesar 96.85%. Seluruh rentang CI 95% berada di atas 0,90, mengonfirmasi konsistensi performa diskriminasi model.

11.6 Kurva ROC dengan Titik Threshold Optimal

roc_df <- data.frame(
  fpr = 1 - roc_obj$specificities,
  tpr = roc_obj$sensitivities
)

ggplot(roc_df, aes(x = fpr, y = tpr)) +
  geom_line(color = "#2980b9", linewidth = 1.2) +
  geom_abline(intercept = 0, slope = 1, linetype = "dashed",
              color = "#e74c3c", linewidth = 0.9) +
  geom_point(aes(x = 1 - spec_opt, y = sens_opt),
             color = "#27ae60", size = 4, shape = 19) +
  annotate("text",
           x = 1 - spec_opt + 0.04, y = sens_opt - 0.04,
           label = paste0("Optimal\n(", round(threshold_opt, 4), ")"),
           color = "#27ae60", size = 3.5) +
  annotate("text", x = 0.65, y = 0.25,
           label = paste0("AUC = ", round(as.numeric(auc_value), 4)),
           size = 4.5, color = "#2980b9", fontface = "bold") +
  labs(
    title    = "Kurva ROC — Model Final (Stepwise), Data Test",
    subtitle = paste0("AUC = ", round(as.numeric(auc_value), 4),
                      " | CI 95%: ", round(ci_auc[1], 4),
                      " - ", round(ci_auc[3], 4)),
    x = "1 - Specificity (False Positive Rate)",
    y = "Sensitivity (True Positive Rate)"
  ) +
  theme_minimal(base_size = 12) +
  coord_equal()
Kurva ROC Model Final dengan Threshold Optimal (Youden)

Kurva ROC Model Final dengan Threshold Optimal (Youden)

Kemampuan diskriminasi model dievaluasi menggunakan kurva ROC (Receiver Operating Characteristic) dan nilai AUC (Area Under the Curve). Kurva ROC menggambarkan hubungan antara sensitivity dan 1 − specificity pada seluruh kemungkinan nilai threshold, sehingga memberikan gambaran menyeluruh tentang kemampuan model dalam membedakan kelas positif (diabetes) dan negatif (tidak diabetes).

Hasil evaluasi pada data test menunjukkan nilai AUC = 0,9685 dengan interval kepercayaan 95% sebesar 0,9418 – 0,9952. Nilai AUC mendekati 1 mengindikasikan bahwa model memiliki kemampuan diskriminasi yang sangat baik dalam membedakan individu dengan dan tanpa diabetes. Berdasarkan kriteria Hosmer & Lemeshow (2013), nilai AUC > 0,90 dikategorikan sebagai diskriminasi outstanding (luar biasa).

Titik threshold optimal ditentukan menggunakan indeks Youden (J = Sensitivity + Specificity − 1), yang memaksimalkan jumlah sensitivity dan specificity secara bersamaan. Threshold optimal yang diperoleh adalah 0,6572, yang ditandai dengan titik hijau pada kurva. Pada threshold ini, model mencapai sensitivity sebesar 0,9063 dan specificity sebesar 0,9250, artinya model mampu mengidentifikasi dengan benar 90,63% kasus diabetes dan 92,50% kasus non-diabetes pada data test.

Kurva ROC yang terbentuk berada jauh di atas garis diagonal referensi (garis merah putus-putus), yang merepresentasikan model acak (random classifier) dengan AUC = 0,50. Hal ini menegaskan bahwa model regresi logistik stepwise yang dibangun memiliki performa klasifikasi yang jauh lebih baik dibandingkan tebakan acak dan layak digunakan


12 Ringkasan dan Kesimpulan

12.1 Persamaan Model Final

koef <- coef(model_stepwise)
persamaan2 <- paste0(
  "$$\\text{logit}(P) = ", round(koef[1], 4),
  paste(sapply(seq_along(koef)[-1], function(i) {
    tanda <- ifelse(koef[i] >= 0, " + ", " - ")
    paste0(tanda, abs(round(koef[i], 4)), " \\cdot X_{\\text{",
           gsub("_", "\\\\_", names(koef)[i]), "}}")
  }), collapse = ""),
  "$$"
)
cat(persamaan2)

\[\text{logit}(P) = 2.7265 - 0.0576 \cdot X_{\text{age}} - 4.4506 \cdot X_{\text{gender}} + 4.7012 \cdot X_{\text{polyuria}} + 4.9616 \cdot X_{\text{polydipsia}} + 0.8365 \cdot X_{\text{weakness}} + 1.658 \cdot X_{\text{polyphagia}} + 2.1572 \cdot X_{\text{genital\_thrush}} + 1.1932 \cdot X_{\text{visual\_blurring}} - 3.2202 \cdot X_{\text{itching}} + 2.4288 \cdot X_{\text{irritability}} + 0.897 \cdot X_{\text{partial\_paresis}}\]

\[P(\text{diabetes positif}) = \frac{1}{1 + e^{-\text{logit}(P)}}\]

12.2 Ringkasan Hasil

ringkasan <- data.frame(
  Komponen = c(
    "Variabel dalam Model Final",
    "Variabel Dieliminasi (Stepwise)",
    "AIC Model Penuh",
    "AIC Model Final",
    "Uji G² (LRT)",
    "Uji Hosmer-Lemeshow",
    "McFadden R²",
    "Nagelkerke R²",
    "AUC (Data Test)",
    "Accuracy (cut-off 0,5)",
    "Sensitivity (cut-off 0,5)",
    "F1-Score (cut-off 0,5)",
    "Threshold Optimal (Youden)",
    "Sensitivity (threshold optimal)"
  ),
  Nilai = c(
    length(var_final),
    length(var_dihapus),
    round(AIC(model_penuh), 4),
    round(AIC(model_stepwise), 4),
    paste0("G² = ", round(lr_stat, 4), ", p < 0,0001"),
    paste0("χ² = ", round(hl_test$statistic, 4),
           ", p = ", round(hl_test$p.value, 4), " (fit)"),
    round(r2_mcf, 4),
    round(r2_nag, 4),
    paste0(round(as.numeric(auc_value), 4),
           " (CI: ", round(ci_auc[1], 4), "–", round(ci_auc[3], 4), ")"),
    paste0(round(akurasi * 100, 2), "%"),
    paste0(round(sensitivity * 100, 2), "%"),
    round(f1_score, 4),
    round(threshold_opt, 4),
    paste0(round(sensitivity2 * 100, 2), "%")
  )
)

knitr::kable(ringkasan, caption = "Ringkasan Hasil Analisis Regresi Logistik Biner")
Ringkasan Hasil Analisis Regresi Logistik Biner
Komponen Nilai
Variabel dalam Model Final 11
Variabel Dieliminasi (Stepwise) 5
AIC Model Penuh 164.8563
AIC Model Final 157.3711
Uji G² (LRT) G² = 423.7014, p < 0,0001
Uji Hosmer-Lemeshow χ² = 7.8498, p = 0.4483 (fit)
McFadden R² 0.7606
Nagelkerke R² 0.8658
AUC (Data Test) 0.9685 (CI: 0.9418–0.9952)
Accuracy (cut-off 0,5) 90.38%
Sensitivity (cut-off 0,5) 92.54%
F1-Score (cut-off 0,5) 0.9254
Threshold Optimal (Youden) 0.6572
Sensitivity (threshold optimal) 92.54%

12.3 Kesimpulan

Penelitian ini berhasil membangun model regresi logistik biner untuk memprediksi risiko diabetes stadium awal menggunakan 11 variabel prediktor yang dipilih melalui seleksi stepwise berbasis AIC. Beberapa simpulan utama:

1. Dari 15 variabel kategorik yang diuji secara bivariat, 12 variabel menunjukkan asosiasi signifikan (\(p < 0{,}05\)) dengan status diabetes. Seluruh asumsi regresi logistik (linearitas logit dan non-multikolinearitas) terpenuhi.

2. Model final memuat 11 prediktor dengan Polydipsia (OR ≈ 143) dan Polyuria (OR ≈ 110) sebagai prediktor terkuat, diikuti Irritabilitas, Genital Thrush, dan Polyphagia.

3. Model terbukti terkalibrasi dengan baik berdasarkan uji Hosmer-Lemeshow (\(p = 0.4483\)) dan memiliki daya penjelasan yang sangat kuat (Nagelkerke R² = 0.8658).

4. Evaluasi pada data uji menghasilkan AUC = 0.9685, accuracy 90.38%, sensitivity 92.54%, dan F1-Score 0.9254 — kategori sangat baik untuk keperluan skrining medis.

5. Model regresi logistik biner ini terbukti valid secara statistik dan memiliki performa diskriminasi yang sangat tinggi, sehingga dapat menjadi alat bantu skrining awal yang interpretatif secara klinis.


13 Referensi

  1. International Diabetes Federation, IDF Diabetes Atlas, 10th ed. Brussels, Belgium: International Diabetes Federation, 2021.
  2. World Health Organization, “Diabetes,” Fact Sheets, 2023.
  3. Kementerian Kesehatan RI, Hasil Riset Kesehatan Dasar (Riskesdas) 2018. Jakarta: Badan Penelitian dan Pengembangan Kesehatan, Kemenkes RI, 2018.
  4. American Diabetes Association, “Standards of Medical Care in Diabetes—2023,” Diabetes Care, vol. 46, Suppl. 1, pp. S1–S291, Jan. 2023.
  5. D. W. Hosmer and S. Lemeshow, Applied Logistic Regression, 3rd ed. Hoboken, NJ, USA: Wiley, 2013.
  6. W. Apriliah, I. Kurniawan, M. Baydhowi, and T. Haryati, “Prediksi Kemungkinan Diabetes pada Tahap Awal Menggunakan Algoritma Klasifikasi Random Forest,” Sistemasi: Jurnal Sistem Informasi, vol. 10, no. 1, pp. 163–171, 2021.
  7. I. Husein, “Faktor Penyebab Penyakit Diabetes Melitus dengan Metode Regresi Logistik,” G-Tech: Jurnal Teknologi Terapan, vol. 7, no. 1, 2022.
  8. M. M. F. Islam, R. Ferdousi, S. Rahman, and H. Y. Bushra, “Likelihood Prediction of Diabetes at Early Stage Using Data Mining Techniques,” in Computer Vision and Machine Intelligence in Medical Image Analysis, Singapore: Springer, 2020, pp. 113–125.
  9. T. Hastie, R. Tibshirani, and J. Friedman, The Elements of Statistical Learning: Data Mining, Inference, and Prediction, 2nd ed. New York, NY, USA: Springer, 2009.
  10. N. V. Chawla, K. W. Bowyer, L. O. Hall, and W. P. Kegelmeyer, “SMOTE: Synthetic Minority Over-sampling Technique,” Journal of Artificial Intelligence Research, vol. 16, pp. 321–357, 2002.
  11. D. A. Belsley, E. Kuh, and R. E. Welsch, Regression Diagnostics: Identifying Influential Data and Sources of Collinearity. New York, NY, USA: Wiley, 1980.
  12. R. M. O’Brien, “A Caution Regarding Rules of Thumb for Variance Inflation Factors,” Quality and Quantity, vol. 41, no. 5, pp. 673–690, 2007.
  13. D. W. Hosmer, S. Lemeshow, and R. X. Sturdivant, Applied Logistic Regression, 3rd ed., Wiley Series in Probability and Statistics. Hoboken, NJ, USA: Wiley, 2013.
  14. G. E. P. Box and P. W. Tidwell, “Transformation of the Independent Variables,” Technometrics, vol. 4, no. 4, pp. 531–550, 1962.
  15. W. S. Cleveland, “Robust Locally Weighted Regression and Smoothing Scatterplots,” Journal of the American Statistical Association, vol. 74, no. 368, pp. 829–836, 1979.
  16. J. T. Newsom, “Multiple Logistic Regression and Model Fit,” Psy 522/622 Lecture Notes, Portland State University, Spring 2026.
  17. J. T. Newsom, “Logistic Regression,” Psy 525/625 Categorical Data Analysis Lecture Notes, Portland State University, Fall 2025.
  18. T. Lumley, “Pseudo-R-squared Statistics Under Complex Sampling,” arXiv preprint, arXiv:1701.07745, 2017.
  19. G. A. J. Hemmert, L. M. Schons, J. Wieseke, and H. Schimmelpfennig, “Log-Likelihood-Based Pseudo-R² in Logistic Regression: Deriving Sample-Sensitive Benchmarks,” Sociological Methods & Research, vol. 47, no. 3, pp. 507–531, 2018.
  20. T. H. Pinem and Z. P. Putra, “Evaluasi Kinerja Algoritma Klasifikasi Deep Learning dalam Prediksi Diabetes,” Jurnal Ilmiah FIFO, vol. 17, no. 1, pp. 17–28, 2025, doi: 10.22441/fifo.2025.v17i1.003.
  21. S. Gündoğdu, “Efficient Prediction of Early-Stage Diabetes Using XGBoost Classifier with Random Forest Feature Selection Technique,” Multimedia Tools and Applications, vol. 82, pp. 34163–34181, 2023, doi: 10.1007/s11042-023-15165-8.
  22. A. V. Aglarci and S. Karakurt, “Symptoms Affecting the Development of Diabetes: Analysis of Risk Parameters for Early Detection Using Data Mining,” BMC Medical Informatics and Decision Making, vol. 25, no. 319, 2025, doi: 10.1186/s12911-025-03159-5.
  23. L. S. Gerich, “Acute Hyperglycemia Alters Mood State and Impairs Cognitive Performance in People with Type 2 Diabetes,” Diabetes Care, vol. 27, no. 10, pp. 2335–2340, 2004, doi: 10.2337/diacare.27.10.2335.
  24. K. Dissanayake, G. Dissabandara, P. Ratnayake, and D. Herath, “Machine Learning Models for Prediction of Diabetes Complications: A Systematic Review,”
  25. A. Diouri et al., “Insulin Resistance and Type 2 Diabetes Mellitus: A Comprehensive Review,” Metabolism — Clinical and Experimental, 2020.
  26. D. Kopf, M. Bleijlevens, S. Goedhart, and A. Kooistra, “Glycemic Variability and Cognitive Function in Older Patients with Type 2 Diabetes,”