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).
Penelitian ini hadir untuk mengisi kesenjangan tersebut dengan menerapkan regresi logistik biner secara komprehensif, mencakup uji asumsi, seleksi variabel berbasis AIC, serta perhitungan odds ratio dengan interval kepercayaan 95% sehingga menghasilkan model yang valid secara statistik dan dapat diinterpretasikan secara klinis. Penelitian ini menduga bahwa gejala klinis klasik diabetes (polyuria, polydipsia, polyphagia) akan meningkatkan risiko diabetes sesuai trias klasik pada literatur, sementara usia dan jenis kelamin diduga berpengaruh signifikan dengan arah hubungan yang masih perlu dikonfirmasi secara empiris. 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).
Tujuan penelitian ini adalah:
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
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)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"
)| Informasi | Nilai |
|---|---|
| Jumlah Baris | 520 |
| Jumlah Kolom | 17 |
Interpretasi: Dataset berhasil dibaca dengan 520 observasi dan 17 variabel (termasuk variabel respon).
| No | Variabel | Tipe | Keterangan |
|---|---|---|---|
| 1 | Age (Usia) | Numerik | Usia pasien dalam tahun |
| 2 | Gender (Jenis Kelamin) | Kategorik | Laki-laki/ Perempuan |
| 3 | Polyuria | Kategorik | Sering buang air kecil berlebih (Ya/Tidak) |
| 4 | Polydipsia | Kategorik | Rasa haus berlebihan (Ya/Tidak) |
| 5 | Sudden Weight Loss | Kategorik | Penurunan berat badan mendadak (Ya/Tidak) |
| 6 | Weakness | Kategorik | Kelemahan tubuh (Ya/Tidak) |
| 7 | Polyphagia | Kategorik | Nafsu makan berlebihan (Ya/Tidak) |
| 8 | Genital Thrush | Kategorik | Infeksi jamur genital (Ya/Tidak) |
| 9 | Visual Blurring | Kategorik | Pandangan kabur (Ya/Tidak) |
| 10 | Itching | Kategorik | Gatal-gatal (Ya/Tidak) |
| 11 | Irritability | Kategorik | Mudah tersinggung/marah (Ya/Tidak) |
| 12 | Delayed Healing | Kategorik | Penyembuhan luka lambat (Ya/Tidak) |
| 13 | Partial Paresis | Kategorik | Kelemahan otot sebagian (Ya/Tidak) |
| 14 | Muscle Stiffness | Kategorik | Kekakuan otot (Ya/Tidak) |
| 15 | Alopecia | Kategorik | Kerontokan rambut (Ya/Tidak) |
| 16 | Obesity | Kategorik | Obesitas (Ya/Tidak) |
| 17 | Class | Respon | Status diabetes (Positif/Negatif) |
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.
## '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"
)| 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 |
Interpretasi: Tidak ditemukan missing values pada dataset ini. Seluruh 520 observasi dapat digunakan dalam analisis selanjutnya tanpa penghapusan baris.
Materi: Rekoding variabel dilakukan agar fungsi
glm() dapat mengidentifikasi kategori referensi secara
eksplisit. Skema rekoding mengacu pada Hosmer & Lemeshow (2013) dan
Agresti (2018):
class):
Negative → 0 (referensi), Positive → 1Female → 0 (referensi),
Male → 1No → 0 (referensi),
Yes → 1Variabel 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.
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.
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)")| 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.
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")| 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")| 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).
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
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
Interpretasi: Dari kedua grafik tersebut, terlihat bahwa distribusi usia responden pada kedua kelompok status diabetes memiliki bentuk yang relatif simetris dan tumpang tindih cukup besar, menandakan bahwa usia bukan pemisah yang tajam antara kelompok positif dan negatif. Namun, kelompok positif diabetes cenderung memiliki distribusi yang lebih lebar dan bergeser sedikit ke kanan, dengan puncak densitas berada di sekitar usia 50 tahun, sementara kelompok negatif memuncak lebih awal di sekitar usia 45 tahun. Hal ini konsisten dengan rata-rata usia kelompok positif (49,07 tahun) yang lebih tinggi dibanding kelompok negatif (46,36 tahun). Selain itu, kelompok positif juga memiliki ekor distribusi yang lebih panjang ke arah usia tua (hingga >80 tahun), mengindikasikan bahwa proporsi penderita diabetes pada kelompok usia lanjut lebih besar dibanding yang tidak menderita diabetes.
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")| 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.
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")| 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
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.
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)")| 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 |
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")
}| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 19 | 173 | 9.5 | 54.1 |
| 1 | 1 | 181 | 147 | 90.5 | 45.9 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 185 | 77 | 92.5 | 24.1 |
| 1 | 1 | 15 | 243 | 7.5 | 75.9 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 192 | 95 | 96 | 29.7 |
| 1 | 1 | 8 | 225 | 4 | 70.3 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 171 | 132 | 85.5 | 41.2 |
| 1 | 1 | 29 | 188 | 14.5 | 58.8 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 113 | 102 | 56.5 | 31.9 |
| 1 | 1 | 87 | 218 | 43.5 | 68.1 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 152 | 131 | 76 | 40.9 |
| 1 | 1 | 48 | 189 | 24 | 59.1 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 167 | 237 | 83.5 | 74.1 |
| 1 | 1 | 33 | 83 | 16.5 | 25.9 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 142 | 145 | 71 | 45.3 |
| 1 | 1 | 58 | 175 | 29 | 54.7 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 101 | 166 | 50.5 | 51.9 |
| 1 | 1 | 99 | 154 | 49.5 | 48.1 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 184 | 210 | 92 | 65.6 |
| 1 | 1 | 16 | 110 | 8 | 34.4 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 114 | 167 | 57 | 52.2 |
| 1 | 1 | 86 | 153 | 43 | 47.8 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 168 | 128 | 84 | 40 |
| 1 | 1 | 32 | 192 | 16 | 60 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 140 | 185 | 70 | 57.8 |
| 1 | 1 | 60 | 135 | 30 | 42.2 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 99 | 242 | 49.5 | 75.6 |
| 1 | 1 | 101 | 78 | 50.5 | 24.4 |
| Kode | Negatif | Positif | % Negatif | % Positif | |
|---|---|---|---|---|---|
| 0 | 0 | 173 | 259 | 86.5 | 80.9 |
| 1 | 1 | 27 | 61 | 13.5 | 19.1 |
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.
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.
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.
Pada kelompok positif, 58,8% mengalami penurunan berat badan mendadak dibandingkan hanya 14,5% pada kelompok negatif. Gejala ini cukup diskriminatif terhadap status diabetes.
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.
Sebesar 59,1% pasien positif mengalami polyphagia dibandingkan 24% pada kelompok negatif. Gejala ini cukup membedakan kedua kelompok meskipun tidak setajam polyuria dan polydipsia.
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.
Sebesar 54,7% pasien positif mengalami penglihatan kabur dibandingkan 29% pada kelompok negatif. Gejala ini cukup relevan namun tidak sangat spesifik.
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.
Proporsi yang mengalami iritabilitas pada kelompok positif (34,4%) jauh lebih tinggi dibandingkan negatif (8%), menunjukkan daya pembeda yang cukup baik.
Distribusinya hampir seimbang antara kedua kelompok (positif: 47,8%, negatif: 43%). Gejala ini memiliki daya pembeda yang rendah.
Sebesar 60% pasien positif mengalami partial paresis dibandingkan hanya 16% pada kelompok negatif. Gejala ini cukup diskriminatif terhadap status diabetes.
Proporsi yang mengalami kekakuan otot: 42,2% pada positif dan 30% pada negatif. Daya pembedanya tergolong sedang.
Alopecia lebih banyak ditemukan pada kelompok Negatif (50,5%) dibandingkan Positif (24,4%), mengindikasikan bahwa alopecia berkorelasi negatif dengan status diabetes dalam dataset ini.
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.
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:
Kriteria: Variabel dinyatakan signifikan apabila \(p < 0{,}05\).
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")| 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.
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")
)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:
age dan logit bersifat linearKeputusan: Tolak \(H_0\) jika \(p < 0{,}05\)
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")| 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.
✦ MATERI
Materi: Multikolinearitas terjadi ketika prediktor saling berkorelasi tinggi, sehingga estimasi koefisien menjadi tidak stabil dan standar error membesar. Karena model mengandung variabel prediktor kategorik, digunakan Generalized Variance Inflation Factor (GVIF) sebagai perluasan dari VIF klasik:
\[\text{GVIF}(X_2|X_1) = \frac{\det(\hat{R}_{11})\det(\hat{R}_{22})}{\det(\hat{R})}\]
di mana \(X_1\) adalah matriks variabel prediktor yang menjadi perhatian, \(X_2\) adalah matriks variabel prediktor lainnya, dan \(\hat{R}\) adalah matriks korelasi gabungan. Karena nilai GVIF membesar seiring bertambahnya derajat bebas (df) variabel, digunakan bentuk terstandardisasi \(\text{GVIF}^{1/(2 \times df)}\) untuk perbandingan antar variabel.
Threshold:
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),
GVIF = round(vif_raw, 4),
Df = 1,
`GVIF^(1/(2*Df))` = round(sqrt(vif_raw), 4),
Keterangan = ifelse(sqrt(vif_raw) > 2.236, "Masalah!", "OK"),
check.names = FALSE
)
ada_masalah <- any(sqrt(vif_raw) > 2.236)
}
knitr::kable(vif_df, caption = "Hasil Uji Multikolinearitas (GVIF) — Model Penuh")| Variabel | GVIF | Df | GVIF^(1/(2*Df)) | Keterangan | |
|---|---|---|---|---|---|
| age | age | 2.2125 | 1 | 1.4875 | OK |
| gender | gender | 1.9705 | 1 | 1.4037 | OK |
| polyuria | polyuria | 2.6225 | 1 | 1.6194 | OK |
| polydipsia | polydipsia | 1.7235 | 1 | 1.3128 | OK |
| sudden_wt_loss | sudden_wt_loss | 1.6217 | 1 | 1.2735 | OK |
| weakness | weakness | 1.7699 | 1 | 1.3304 | OK |
| polyphagia | polyphagia | 1.5921 | 1 | 1.2618 | OK |
| genital_thrush | genital_thrush | 1.7476 | 1 | 1.3220 | OK |
| visual_blurring | visual_blurring | 2.7065 | 1 | 1.6452 | OK |
| itching | itching | 3.1867 | 1 | 1.7851 | OK |
| irritability | irritability | 1.3773 | 1 | 1.1736 | OK |
| delayed_healing | delayed_healing | 2.1922 | 1 | 1.4806 | OK |
| partial_paresis | partial_paresis | 1.3957 | 1 | 1.1814 | OK |
| muscle_stiffness | muscle_stiffness | 1.6886 | 1 | 1.2995 | OK |
| alopecia | alopecia | 2.4811 | 1 | 1.5752 | OK |
| obesity | obesity | 1.2828 | 1 | 1.1326 | OK |
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.
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]\]
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"
)| 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)")| 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.
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:
## 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
##
## 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")| 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.
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)")| 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 |
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"
)
koef <- coef(model_stepwise)
terms <- sapply(seq_along(koef)[-1], function(i) {
tanda <- ifelse(koef[i] >= 0, " + ", " - ")
paste0(tanda, abs(round(koef[i], 4)),
" \\cdot \\text{", names(koef)[i], "}")
})
# Bagi menjadi 3 baris
baris1 <- paste(terms[1:4], collapse = "")
baris2 <- paste(terms[5:8], collapse = "")
baris3 <- paste(terms[9:length(terms)], collapse = "")
persamaan <- paste0(
"$$
\\begin{aligned}
\\text{logit}(P) &= ", round(koef[1],4), baris1, " \\\\
&", baris2, " \\\\
&", baris3, "
\\end{aligned}
$$"
)
cat(persamaan)\[ \begin{aligned} \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} \end{aligned} \]
\[P(\text{diabetes positif}) = \frac{1}{1 + e^{-\text{logit}(P)}}\]
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:
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")| 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)"
)| Statistik | Nilai |
|---|---|
| G² | 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. Hal ini menunjukkan bahwa model regresi logistik yang memuat variabel-variabel prediktor memberikan peningkatan kecocokan yang signifikan dibandingkan model null (model yang hanya memuat intersep). Dengan demikian, secara simultan terdapat setidaknya satu variabel prediktor yang berpengaruh signifikan terhadap status diabetes, sehingga model final secara keseluruhan signifikan dalam menjelaskan variasi status diabetes.
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:
Hipotesis:
Keputusan: Gagal tolak \(H_0\) jika \(p > 0{,}05\) → Model layak digunakan.
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")| 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.
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}\]
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.
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)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"
)| 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): Polydipsia merupakan faktor yang berhubungan signifikan dengan kejadian diabetes mellitus. Hasil analisis menunjukkan bahwa responden yang mengalami polydipsia memiliki odds terdiagnosis diabetes yang lebih tinggi dibandingkan responden yang tidak mengalami polydipsia setelah mengontrol variabel lain dalam model (OR = 142,82; CI 95%: 24,06–847,84). Temuan ini sejalan dengan literatur yang menyebutkan bahwa polydipsia merupakan salah satu gejala klasik diabetes akibat peningkatan kadar glukosa darah yang memicu rasa haus berlebihan. Meskipun nilai odds ratio yang diperoleh sangat besar dan menunjukkan hubungan yang kuat, interval kepercayaan yang sangat lebar mengindikasikan bahwa estimasi besarnya efek masih memiliki ketidakpastian yang cukup tinggi. Oleh karena itu, hasil ini lebih tepat diinterpretasikan sebagai bukti adanya hubungan yang sangat kuat dan signifikan antara polydipsia dan diabetes mellitus, sementara besaran pengaruhnya perlu ditafsirkan secara hati-hati.
Polyuria (OR ≈ 110): Polyuria merupakan faktor yang berhubungan signifikan dengan kejadian diabetes mellitus. Berdasarkan hasil analisis, responden yang mengalami polyuria memiliki odds terdiagnosis diabetes yang lebih tinggi dibandingkan responden yang tidak mengalami polyuria setelah mengontrol variabel lain dalam model (OR = 110,08; CI 95%: 25,41–476,81). Secara fisiologis, kondisi ini terjadi akibat tingginya kadar glukosa dalam darah yang menyebabkan glukosa ikut diekskresikan melalui urin dan menarik lebih banyak air sehingga frekuensi buang air kecil meningkat. Nilai odds ratio yang sangat besar menunjukkan adanya hubungan yang kuat antara polyuria dan diabetes mellitus. Namun, interval kepercayaan yang relatif lebar menunjukkan bahwa estimasi besarnya efek masih kurang presisi sehingga nilai odds ratio tidak dapat dianggap sebagai ukuran pasti dari besarnya pengaruh. Dengan demikian, hasil penelitian ini lebih menegaskan adanya hubungan yang kuat dan signifikan dibandingkan menekankan besarnya nilai odds ratio secara eksak.
Irritabilitas (OR ≈ 11): Seseorang yang mengalami iritabilitas memiliki odds 11,35 kali lebih tinggi untuk diabetes positif. Meskipun iritabilitas bukan gejala klasik diabetes dalam literatur umum, hipoglikemia atau fluktuasi gula darah yang tidak stabil dapat mempengaruhi fungsi otak dan menyebabkan perubahan mood termasuk iritabilitas.
Genital Thrush (OR ≈ 9): Seseorang dengan genital thrush memiliki odds 8,65 kali lebih tinggi untuk diabetes positif. Hubungan ini memiliki dasar biologis yang kuat: kadar glukosa tinggi di urin (glikosuria) menciptakan lingkungan ideal untuk pertumbuhan jamur Candida albicans di area genital.
Polyphagia (OR ≈ 5):Seseorang yang mengalami polyphagia memiliki odds 5,25 kali lebih tinggi untuk diabetes positif. Polyphagia terjadi karena meskipun kadar glukosa darah tinggi, sel-sel tubuh tidak dapat menggunakannya akibat defisiensi insulin sehingga tubuh terus mengirim sinyal lapar. Bersama polyuria dan polydipsia, ketiga gejala ini membentuk trias klasik diabetes.
Age (OR ≈ 0,9440) : Setiap penambahan satu tahun usia, odds seseorang didiagnosis diabetes positif menjadi 0,944 kali odds sebelumnya, setelah mengontrol variabel lainnya. Dengan kata lain setiap penambahan satu tahun usia dikaitkan dengan penurunan odds diabetes positif sebesar 5,6%, setelah mengontrol variabel lainnya. Hasil ini tampak kontraintuitif karena secara umum diabetes tipe 2 lebih umum pada usia lanjut. Namun dalam konteks analisis multivariat, efek usia dapat termediasi oleh gejala-gejala klinis yang sudah dikontrol dalam model, sehingga responden yang lebih muda dengan banyak gejala klinis cenderung lebih konsisten didiagnosis positif dalam dataset ini.
Gender laki-laki(OR ≈ 0,012): Laki-laki memiliki odds 0,012 kali untuk didiagnosis diabetes positif dibandingkan perempuan, setelah mengontrol variabel lainnya, yang berarti perempuan secara statistik memiliki risiko diabetes positif yang jauh lebih tinggi dalam dataset ini. Temuan ini tampak kontraintuitif mengingat secara epidemiologis prevalensi diabetes tidak berbeda jauh antar jenis kelamin. Dalam konteks dataset ini, setelah dikontrol oleh seluruh gejala klinis, perempuan dengan profil gejala yang sama dengan laki-laki cenderung lebih sering didiagnosis positif, yang kemungkinan mencerminkan bias seleksi populasi di rumah sakit atau perbedaan pola pelaporan gejala.
Itching (OR ≈ 0,04): Seseorang yang mengalami itching memiliki odds menderita diabetes sebesar 0,040 kali dibandingkan responden yang tidak mengalami itching, dengan asumsi variabel prediktor lainnya tetap konstan. Hasil ini menunjukkan adanya hubungan negatif antara itching dan status diabetes dalam model yang diperoleh. Namun, temuan tersebut tidak dapat diinterpretasikan sebagai bukti bahwa itching bersifat protektif terhadap diabetes, melainkan hanya mencerminkan pola asosiasi statistik pada data penelitian. Oleh karena itu, hasil ini perlu ditafsirkan secara hati-hati dan memerlukan konfirmasi lebih lanjut melalui penelitian pada populasi yang lebih luas dan representatif.
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 | 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 | *** |
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
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.
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 |
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)")| 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. Nilai ini jauh melampaui batas 0,20 yang umumnya dianggap menunjukkan model dengan daya diskriminasi yang sangat baik.
Cox-Snell R² = 0.6389: Sekitar 63.9% mengindikasikan bahwa model memiliki kemampuan yang kuat dalam menjelaskan variasi status diabetes dibandingkan model tanpa prediktor, meskipun ukuran ini secara teoritis tidak dapat mencapai nilai maksimum 1.
Nagelkerke R² = 0.8658: Model mampu menjelaskan sekitar 86.6% variabilitas kejadian diabetes — kategori sangat baik. Ketiga ukuran pseudo R² tersebut secara konsisten menunjukkan nilai yang tinggi, sehingga dapat disimpulkan bahwa model final memiliki kecocokan yang sangat baik dan kemampuan yang kuat dalam menjelaskan status diabetes.
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)")| 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 | 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:
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.
# 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), ")")) | 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)")| 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%). Dalam konteks skrining diabetes, sensitivity lebih diprioritaskan untuk meminimalkan false negative (kasus positif yang tidak terdeteksi), sehingga threshold optimal lebih disarankan untuk aplikasi klinis.
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.
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")| 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.
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)
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
koef <- coef(model_stepwise)
persamaan2 <- paste0(
"$$\\begin{aligned}\n\\text{logit}(P) = & ",
round(koef[1], 4), " - ", abs(round(koef[2], 4)), " \\cdot X_{\\text{age}}",
" - ", abs(round(koef[3], 4)), " \\cdot X_{\\text{gender}}",
" + ", abs(round(koef[4], 4)), " \\cdot X_{\\text{polyuria}}",
" + ", abs(round(koef[5], 4)), " \\cdot X_{\\text{polydipsia}} \\\\",
"\n& + ", abs(round(koef[6], 4)), " \\cdot X_{\\text{weakness}}",
" + ", abs(round(koef[7], 4)), " \\cdot X_{\\text{polyphagia}}",
" + ", abs(round(koef[8], 4)), " \\cdot X_{\\text{genital thrush}}",
" + ", abs(round(koef[9], 4)), " \\cdot X_{\\text{visual blurring}} \\\\",
"\n& - ", abs(round(koef[10], 4)), " \\cdot X_{\\text{itching}}",
" + ", abs(round(koef[11], 4)), " \\cdot X_{\\text{irritability}}",
" + ", abs(round(koef[12], 4)), " \\cdot X_{\\text{partial paresis}}",
"\n\\end{aligned}$$"
)
cat(persamaan2)\[\begin{aligned} \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}} \end{aligned}\]
\[P(\text{diabetes positif}) = \frac{1}{1 + e^{-\text{logit}(P)}}\]
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")| 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% |
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.