Abstrak:
Penyakit alergi merupakan salah satu masalah kesehatan global dengan prevalensi yang terus meningkat, khususnya pada kelompok usia muda termasuk mahasiswa. Penelitian ini bertujuan untuk mengidentifikasi faktor-faktor yang berhubungan dengan kejadian alergi pada mahasiswa menggunakan regresi logistik biner. Data yang digunakan merupakan data sekunder dari University Student Allergy Dataset dengan 854 observasi mahasiswa, di mana 46,49% responden teridentifikasi mengalami alergi. Variabel respons berupa status alergi, sedangkan variabel prediktor meliputi karakteristik demografis, faktor lingkungan, paparan asap rokok, dan riwayat alergi keluarga. Pemeriksaan asumsi meliputi uji linearitas Box–Tidwell dan uji multikolinearitas GVIF. Seleksi variabel dilakukan menggunakan metode stepwise bidirectional berbasis AIC, yang menghasilkan penurunan nilai AIC dari 885,35 menjadi 881,02. Hasil menunjukkan bahwa faktor risiko dominan adalah riwayat dermatitis atopik keluarga (OR = 3,777; p < 0,001), riwayat rinitis alergi keluarga (OR = 3,395; p < 0,001), tempat tinggal di kawasan industri (OR = 2,196; p = 0,001), dan merokok aktif (OR = 2,013; p = 0,011). Jenis kelamin laki-laki menunjukkan efek protektif yang signifikan (OR = 0,605; p = 0,015). Model memiliki kalibrasi yang baik (Hosmer–Lemeshow p = 0,936) dan kemampuan diskriminasi memadai (AUC testing = 0,728) tanpa indikasi overfitting.
Kata Kunci: alergi; regresi logistik biner; faktor risiko; mahasiswa; paparan asap rokok; odds ratio
Latar belakang:
Penyakit alergi merupakan masalah kesehatan yang prevalensinya terus meningkat termasuk pada kelompok mahasiswa. Perubahan lingkungan tempat tinggal, pola hidup, serta faktor genetik dan lingkungan dapat meningkatkan risiko terjadinya alergi. Karena kejadian alergi dipengaruhi oleh banyak faktor secara bersamaan, diperlukan metode analisis yang mampu mengevaluasi hubungan tersebut. Regresi logistik biner digunakan dalam penelitian ini karena dapat menganalisis pengaruh beberapa faktor risiko terhadap status alergi sekaligus mengukur besar asosiasi melalui odds ratio (OR). Penelitian ini menggunakan University Student Allergy Dataset dari Mendeley Data.
Tujuan Penelitian :
Data sekunder dari University Student Allergy Dataset (Reza & Paul, 2026) 854 observasi mahasiswa Daffodil International University, Bangladesh. Dari 17 variabel tersedia pada tahap pra-pemodelan, dilakukan seleksi variabel untuk memastikan kestabilan model dan kesesuaian konseptual sehingga yang digunakan hanya 9 variabel prediktor. Beberapa variabel dikeluarkan dari analisis dengan alasan sebagai berikut:
required_packages <- c("dplyr", "ggplot2", "broom", "knitr",
"car", "readr", "scales", "ResourceSelection")
missing_packages <- required_packages[
!vapply(required_packages, requireNamespace, logical(1), quietly = TRUE)
]
if (length(missing_packages) > 0) install.packages(missing_packages)
invisible(lapply(required_packages, library, character.only = TRUE))# ---- Ganti path sesuai lokasi file di komputer Anda ----
allergy <- read_delim(
file.choose(),
delim = ";",
show_col_types = FALSE
)
knitr::kable(head(allergy, 5), caption = "Tabel Data Alergi (5 baris pertama)") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
scroll_box(width = "100%", box_css = "border: none; padding: 0; margin: 0;")| gender | age | weight_kg | pet_home | residence | mold_env | family_allergy | smoke_now | secondhand_smoke | target_class |
|---|---|---|---|---|---|---|---|---|---|
| Male | 24 | 62 | Yes | Suburban | No | Asthma | No | Yes | positive |
| Male | 24 | 57 | Yes | Rural village | Yes | No Allergy | No | No | negative |
| Male | 22 | 86 | No | Suburban | Yes | Food Allergy | Yes | No | positive |
| Male | 25 | 63 | Yes | Rural village | Yes | Allergic Rhinitis | Yes | Yes | positive |
| Male | 23 | 63 | No | Urban | No | No Allergy | No | No | positive |
alergi <- allergy %>%
mutate(
# Outcome biner
status_alergi_num = ifelse(target_class == "positive", 1L, 0L),
status_alergi = ifelse(target_class == "positive", "Alergi", "Tidak Alergi"),
# Variabel numerik
usia = age,
berat_badan = weight_kg,
# Variabel kategorik → factor berlabel Indonesia
jenis_kelamin = factor(gender,
levels = c("Female", "Male"),
labels = c("Perempuan", "Laki-laki")),
kepemilikan_hewan = factor(pet_home,
levels = c("No", "Yes"),
labels = c("Tidak", "Ya")),
tempat_tinggal = factor(residence,
levels = c("Urban", "Suburban",
"Rural village", "Industrial area"),
labels = c("Perkotaan", "Pinggiran Kota",
"Perdesaan", "Kawasan Industri")),
lingkungan_jamur = factor(mold_env,
levels = c("No", "Yes"),
labels = c("Tidak", "Ya")),
riwayat_keluarga = factor(family_allergy,
levels = c("No Allergy", "Allergic Rhinitis", "Asthma",
"Atopic Dermatitis", "Drug Allergy", "Food Allergy"),
labels = c("Tidak Ada Alergi", "Rinitis Alergi", "Asma",
"Dermatitis Atopik", "Alergi Obat", "Alergi Makanan")),
perokok_aktif = factor(smoke_now,
levels = c("No", "Yes"),
labels = c("Tidak", "Ya")),
paparan_asap = factor(secondhand_smoke,
levels = c("No", "Yes"),
labels = c("Tidak", "Ya"))
) %>%
mutate(
jenis_kelamin = relevel(jenis_kelamin, ref = "Perempuan"),
kepemilikan_hewan = relevel(kepemilikan_hewan, ref = "Tidak"),
tempat_tinggal = relevel(tempat_tinggal, ref = "Perkotaan"),
lingkungan_jamur = relevel(lingkungan_jamur, ref = "Tidak"),
riwayat_keluarga = relevel(riwayat_keluarga, ref = "Tidak Ada Alergi"),
perokok_aktif = relevel(perokok_aktif, ref = "Tidak"),
paparan_asap = relevel(paparan_asap, ref = "Tidak")
)alergi %>%
transmute(
Jenis_Kelamin = jenis_kelamin,
Usia = usia,
Berat_Badan = berat_badan,
Kepemilikan_Hewan = kepemilikan_hewan,
Tempat_Tinggal = tempat_tinggal,
Lingkungan_Jamur = lingkungan_jamur,
Riwayat_Keluarga = riwayat_keluarga,
Perokok_Aktif = perokok_aktif,
Paparan_Asap_Pasif = paparan_asap,
Status_Alergi = status_alergi
) %>%
head(5) %>%
kable(caption = "Tabel Contoh Data Setelah Transformasi (5 Observasi Pertama)") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
scroll_box(width = "100%", box_css = "border: none; padding: 0; margin: 0;")| Jenis_Kelamin | Usia | Berat_Badan | Kepemilikan_Hewan | Tempat_Tinggal | Lingkungan_Jamur | Riwayat_Keluarga | Perokok_Aktif | Paparan_Asap_Pasif | Status_Alergi |
|---|---|---|---|---|---|---|---|---|---|
| Laki-laki | 24 | 62 | Ya | Pinggiran Kota | Tidak | Asma | Tidak | Ya | Alergi |
| Laki-laki | 24 | 57 | Ya | Perdesaan | Ya | Tidak Ada Alergi | Tidak | Tidak | Tidak Alergi |
| Laki-laki | 22 | 86 | Tidak | Pinggiran Kota | Ya | Alergi Makanan | Ya | Tidak | Alergi |
| Laki-laki | 25 | 63 | Ya | Perdesaan | Ya | Rinitis Alergi | Ya | Ya | Alergi |
| Laki-laki | 23 | 63 | Tidak | Perkotaan | Tidak | Tidak Ada Alergi | Tidak | Tidak | Alergi |
Sembilan variabel prediktor yang digunakan disajikan pada tabel berikut.
tabel_variabel <- data.frame(
Variabel = c("Usia", "Berat Badan", "Jenis Kelamin",
"Kepemilikan Hewan Peliharaan", "Tempat Tinggal",
"Lingkungan Berjamur", "Riwayat Alergi Keluarga",
"Status Perokok Aktif", "Paparan Asap Rokok Pasif"),
Tipe_Data = c("Numerik", "Numerik", rep("Kategorikal", 7)),
Deskripsi = c("Usia mahasiswa dalam tahun.",
"Berat badan mahasiswa dalam kilogram.",
"Jenis kelamin mahasiswa.",
"Kepemilikan hewan peliharaan di rumah.",
"Lingkungan tempat tinggal mahasiswa.",
"Kondisi lingkungan berjamur atau lembap.",
"Riwayat alergi dalam keluarga.",
"Status mahasiswa sebagai perokok aktif.",
"Paparan asap rokok dari lingkungan sekitar."),
Keterangan = c("Data numerik", "Data numerik",
"Laki-laki atau Perempuan",
"Ya atau Tidak",
"Perkotaan, Pinggiran Kota, Perdesaan, Kawasan Industri",
"Ya atau Tidak",
"Tidak Ada Alergi, Rinitis Alergi, Asma, Dermatitis Atopik, Alergi Obat, Alergi Makanan",
"Ya atau Tidak",
"Ya atau Tidak")
)
kable(tabel_variabel,
col.names = c("Variabel", "Tipe Data", "Deskripsi", "Keterangan"),
caption = "Tabel Variabel prediktor yang digunakan dalam analisis.") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
scroll_box(width = "100%", box_css = "border: none; padding: 0; margin: 0;")| Variabel | Tipe Data | Deskripsi | Keterangan |
|---|---|---|---|
| Usia | Numerik | Usia mahasiswa dalam tahun. | Data numerik |
| Berat Badan | Numerik | Berat badan mahasiswa dalam kilogram. | Data numerik |
| Jenis Kelamin | Kategorikal | Jenis kelamin mahasiswa. | Laki-laki atau Perempuan |
| Kepemilikan Hewan Peliharaan | Kategorikal | Kepemilikan hewan peliharaan di rumah. | Ya atau Tidak |
| Tempat Tinggal | Kategorikal | Lingkungan tempat tinggal mahasiswa. | Perkotaan, Pinggiran Kota, Perdesaan, Kawasan Industri |
| Lingkungan Berjamur | Kategorikal | Kondisi lingkungan berjamur atau lembap. | Ya atau Tidak |
| Riwayat Alergi Keluarga | Kategorikal | Riwayat alergi dalam keluarga. | Tidak Ada Alergi, Rinitis Alergi, Asma, Dermatitis Atopik, Alergi Obat, Alergi Makanan |
| Status Perokok Aktif | Kategorikal | Status mahasiswa sebagai perokok aktif. | Ya atau Tidak |
| Paparan Asap Rokok Pasif | Kategorikal | Paparan asap rokok dari lingkungan sekitar. | Ya atau Tidak |
ggplot(alergi, aes(x = status_alergi, fill = status_alergi)) +
geom_bar(width = 0.62,color = "#7a1f0d",linewidth = 0.5) +
geom_text(stat = "count",aes(label = paste0(
after_stat(count),"\n(",
round(after_stat(count) / nrow(alergi) * 100, 1),
"%)") ),
vjust = -0.35,lineheight = 1,size = 4.5,family = "Inter",fontface = "bold",color = "#3a0e05") +
scale_fill_manual(values = c("Alergi" = "#9e2a2b", "Tidak Alergi" = "#b83a1b" )) +
scale_y_continuous(expand = expansion(mult = c(0, 0.15))) +
labs(title = "Distribusi Status Alergi Responden",
x = "Status Alergi",y = "Jumlah Mahasiswa") +
theme_minimal(base_family = "Inter") +
theme(
plot.background =element_rect(fill = "#fdfbf7",color = NA),
panel.background =element_rect(fill = "#fdfbf7",color = NA),
plot.title =element_text(size = 18,face = "bold",hjust = 0.5,color = "#3a0e05",margin = margin(b = 18)),
axis.title =element_text(size = 13,face = "bold",color = "#7a1f0d"),
axis.text =element_text(size = 12,color = "#3a0e05"),
panel.grid.major.x =element_blank(),panel.grid.minor =element_blank(),panel.grid.major.y =element_line(color = "#f5ebd6",linewidth = 0.6),
legend.position = "none"
)Figure 3.1: Gambar 1. Distribusi Status Alergi Responden
Dari 854 responden, 397 (46,49%) berstatus alergi positif dan 457 (53,51%) berstatus alergi negatif. Proporsi yang relatif seimbang mengindikasikan tidak adanya ketidakseimbangan kelas ekstrem, sehingga analisis regresi logistik dapat dilakukan tanpa prosedur penyeimbangan tambahan.
tabel_desc <- data.frame(
Variabel = c("Usia (tahun)", "Berat Badan (kg)"),
Minimum = c(min(alergi$usia), min(alergi$berat_badan)),
Q1 = c(quantile(alergi$usia, .25), quantile(alergi$berat_badan, .25)),
Median = c(median(alergi$usia), median(alergi$berat_badan)),
Mean = c(round(mean(alergi$usia), 2), round(mean(alergi$berat_badan), 2)),
Q3 = c(quantile(alergi$usia, .75), quantile(alergi$berat_badan, .75)),
Maksimum = c(max(alergi$usia), max(alergi$berat_badan)),
SD = c(round(sd(alergi$usia), 2), round(sd(alergi$berat_badan), 2))
)
kable(tabel_desc, align = "c",
caption = " Statistik Deskriptif Variabel Numerik (n = 854)")| Variabel | Minimum | Q1 | Median | Mean | Q3 | Maksimum | SD |
|---|---|---|---|---|---|---|---|
| Usia (tahun) | 18 | 20 | 22 | 21.67 | 23 | 50 | 1.88 |
| Berat Badan (kg) | 37 | 57 | 65 | 66.36 | 75 | 130 | 13.44 |
Interpretasi:
Variabel usia memiliki rata-rata sekitar 21–22 tahun dengan sebaran yang relatif homogen menunjukkan bahwa mayoritas responden berada pada rentang usia mahasiswa aktif. Variabel berat badan memiliki variasi yang lebih besar dibandingkan usia, yang ditunjukkan oleh nilai simpangan baku yang lebih tinggi. Hal ini mengindikasikan adanya heterogenitas karakteristik fisik antarresponden.
desc_cat <- bind_rows(
alergi %>% count(jenis_kelamin) %>% mutate(Variabel = "Jenis Kelamin", Kategori = as.character(jenis_kelamin)) %>% select(Variabel, Kategori, Frekuensi = n),
alergi %>% count(tempat_tinggal) %>% mutate(Variabel = "Tempat Tinggal", Kategori = as.character(tempat_tinggal)) %>% select(Variabel, Kategori, Frekuensi = n),
alergi %>% count(kepemilikan_hewan) %>% mutate(Variabel = "Kepemilikan Hewan", Kategori = as.character(kepemilikan_hewan)) %>% select(Variabel, Kategori, Frekuensi = n),
alergi %>% count(lingkungan_jamur) %>% mutate(Variabel = "Lingkungan Berjamur", Kategori = as.character(lingkungan_jamur)) %>% select(Variabel, Kategori, Frekuensi = n),
alergi %>% count(riwayat_keluarga) %>% mutate(Variabel = "Riwayat Alergi Keluarga", Kategori = as.character(riwayat_keluarga)) %>% select(Variabel, Kategori, Frekuensi = n),
alergi %>% count(perokok_aktif) %>% mutate(Variabel = "Perokok Aktif", Kategori = as.character(perokok_aktif)) %>% select(Variabel, Kategori, Frekuensi = n),
alergi %>% count(paparan_asap) %>% mutate(Variabel = "Paparan Asap Rokok Pasif", Kategori = as.character(paparan_asap)) %>% select(Variabel, Kategori, Frekuensi = n)
) %>%
mutate(Persentase = paste0(round(Frekuensi / nrow(alergi) * 100, 2), "%"))
kable(desc_cat, caption = "Distribusi Variabel Prediktor Kategorik (n = 854)")| Variabel | Kategori | Frekuensi | Persentase |
|---|---|---|---|
| Jenis Kelamin | Perempuan | 185 | 21.66% |
| Jenis Kelamin | Laki-laki | 669 | 78.34% |
| Tempat Tinggal | Perkotaan | 358 | 41.92% |
| Tempat Tinggal | Pinggiran Kota | 156 | 18.27% |
| Tempat Tinggal | Perdesaan | 207 | 24.24% |
| Tempat Tinggal | Kawasan Industri | 133 | 15.57% |
| Kepemilikan Hewan | Tidak | 673 | 78.81% |
| Kepemilikan Hewan | Ya | 181 | 21.19% |
| Lingkungan Berjamur | Tidak | 548 | 64.17% |
| Lingkungan Berjamur | Ya | 306 | 35.83% |
| Riwayat Alergi Keluarga | Tidak Ada Alergi | 352 | 41.22% |
| Riwayat Alergi Keluarga | Rinitis Alergi | 167 | 19.56% |
| Riwayat Alergi Keluarga | Asma | 127 | 14.87% |
| Riwayat Alergi Keluarga | Dermatitis Atopik | 40 | 4.68% |
| Riwayat Alergi Keluarga | Alergi Obat | 18 | 2.11% |
| Riwayat Alergi Keluarga | Alergi Makanan | 150 | 17.56% |
| Perokok Aktif | Tidak | 752 | 88.06% |
| Perokok Aktif | Ya | 102 | 11.94% |
| Paparan Asap Rokok Pasif | Tidak | 679 | 79.51% |
| Paparan Asap Rokok Pasif | Ya | 175 | 20.49% |
Interpretasi:
Mayoritas responden berjenis kelamin laki-laki (78,34%; n = 669) dengan proporsi perempuan sebesar 21,66% (n = 185). Sebagian besar responden tidak memiliki hewan peliharaan (78,81%) dan tidak terpapar lingkungan berjamur (64,17%). Berdasarkan jenis tempat tinggal, perkotaan mendominasi (41,92%), diikuti perdesaan (24,24%), pinggiran kota (18,27%), dan kawasan industri (15,57%). Pada variabel riwayat alergi keluarga, 41,22% responden tidak memiliki riwayat alergi artinya hampir 59% responden memiliki setidaknya satu anggota keluarga dengan kondisi atopik tertentu, yang mengindikasikan besarnya potensi kontribusi faktor genetik/herediter terhadap kejadian alergi mahasiswa.
Uji Box–Tidwell memeriksa apakah hubungan antara variabel kontinu (usia dan berat badan) dengan log-odds bersifat linear, dengan menyertakan suku interaksi \(X \cdot \ln(X)\) ke dalam model. Asumsi terpenuhi jika koefisien suku interaksi tidak signifikan (\(p-value > 0{,}05\)).
\[\text{logit}[\pi(x)] = \beta_0 + \beta_1 X + \beta_2 (X \cdot \ln X)\]
alergi_bt <- alergi %>%
mutate(
usia_log = usia * log(usia + 1),
berat_badan_log = berat_badan * log(berat_badan + 1)
)
fit_bt <- glm(
status_alergi_num ~
jenis_kelamin + usia + usia_log +
berat_badan + berat_badan_log +
kepemilikan_hewan + lingkungan_jamur + tempat_tinggal +
riwayat_keluarga + perokok_aktif + paparan_asap,
data = alergi_bt, family = binomial
)
bt_hasil <- broom::tidy(fit_bt) %>%
filter(grepl("log", term)) %>%
select(Prediktor = term, Estimasi = estimate,
Galat_Baku = std.error, z_value = statistic, p_value = p.value) %>%
mutate(
across(where(is.numeric), ~ round(.x, 4)),
Kesimpulan = ifelse(p_value > 0.05, "✅ Terpenuhi", "❌ Tidak Terpenuhi")
)
kable(bt_hasil,
caption = "Tabel Hasil Uji Linearitas Box–Tidwell untuk Variabel Kontinu")| Prediktor | Estimasi | Galat_Baku | z_value | p_value | Kesimpulan |
|---|---|---|---|---|---|
| usia_log | 0.1898 | 0.2556 | 0.7424 | 0.4578 | ✅ Terpenuhi |
| berat_badan_log | -0.0075 | 0.0399 | -0.1888 | 0.8503 | ✅ Terpenuhi |
Kesimpulan: Koefisien suku interaksi untuk usia (\(p-value = 0{,}458\)) dan berat badan (\(p-value = 0{,}850\)) tidak signifikan secara statistik. Asumsi linearitas pada skala logit terpenuhi untuk kedua variabel kontinu tidak diperlukan transformasi tambahan.
Multikolinearitas diperiksa menggunakan Generalized Variance Inflation Factor (GVIF), perluasan VIF untuk variabel kategorik politomus. Kriteria: \(\text{GVIF}^{1/(2 \cdot \text{df})} < 2\).
fit_vif_penuh <- glm(
status_alergi_num ~
jenis_kelamin + usia + berat_badan +
kepemilikan_hewan + tempat_tinggal + lingkungan_jamur +
riwayat_keluarga + perokok_aktif + paparan_asap,
data = alergi, family = binomial(link = "logit")
)
vif_raw <- vif(fit_vif_penuh)
vif_df <- as.data.frame(vif_raw)
vif_tabel <- data.frame(
Prediktor = rownames(vif_df),
GVIF = round(vif_df[, 1], 4),
df = vif_df[, 2],
GVIF_adj = round(vif_df[, 3], 4)
) %>%
mutate(Kesimpulan = ifelse(GVIF_adj < 2, "✅ Aman", "⚠️ Bermasalah"))
kable(vif_tabel,
col.names = c("Prediktor", "GVIF", "df",
"GVIF¹⁄⁽²·ᵈᶠ⁾", "Kesimpulan"),
caption = "Tabel Hasil Uji Multikolinearitas GVIF Model Penuh")| Prediktor | GVIF | df | GVIF¹⁄⁽²·ᵈᶠ⁾ | Kesimpulan |
|---|---|---|---|---|
| jenis_kelamin | 1.2657 | 1 | 1.1250 | ✅ Aman |
| usia | 1.0265 | 1 | 1.0132 | ✅ Aman |
| berat_badan | 1.2014 | 1 | 1.0961 | ✅ Aman |
| kepemilikan_hewan | 1.0194 | 1 | 1.0097 | ✅ Aman |
| tempat_tinggal | 1.0782 | 3 | 1.0126 | ✅ Aman |
| lingkungan_jamur | 1.0557 | 1 | 1.0275 | ✅ Aman |
| riwayat_keluarga | 1.0997 | 5 | 1.0095 | ✅ Aman |
| perokok_aktif | 1.1040 | 1 | 1.0507 | ✅ Aman |
| paparan_asap | 1.0676 | 1 | 1.0333 | ✅ Aman |
Kesimpulan: Seluruh nilai \(\text{GVIF}^{1/(2 \cdot \text{df})}\) jauh di bawah ambang batas 2,0 (nilai tertinggi 1,1250 pada jenis kelamin). Tidak terdapat masalah multikolinearitas estimasi koefisien dapat dianggap stabil.
Data dibagi menjadi 80% data training dan 20% data testing menggunakan stratified random sampling berdasarkan variabel respons, agar proporsi kelas tetap seimbang pada kedua subset.
set.seed(123)
stratified_split <- function(y, prop = 0.8) {
idx_by_class <- split(seq_along(y), y)
train_idx <- lapply(
idx_by_class,
function(idx) sample(idx, size = floor(length(idx) * prop))
)
unlist(train_idx, use.names = FALSE)
}
train_id <- stratified_split(alergi$status_alergi_num, prop = 0.8)
data_training <- alergi[train_id, ]
data_testing <- alergi[-train_id, ]
split_info <- data.frame(
Subset = c("Training", "Testing"),
n = c(
nrow(data_training),
nrow(data_testing)
),
Alergi_Positif = c(
sum(data_training$status_alergi_num == 1),
sum(data_testing$status_alergi_num == 1)
),
Proporsi_Positif = c(
paste0(round(mean(data_training$status_alergi_num == 1) * 100, 2), "%"),
paste0(round(mean(data_testing$status_alergi_num == 1) * 100, 2), "%")
),
Alergi_Negatif = c(
sum(data_training$status_alergi_num == 0),
sum(data_testing$status_alergi_num == 0)
),
Proporsi_Negatif = c(
paste0(round(mean(data_training$status_alergi_num == 0) * 100, 2), "%"),
paste0(round(mean(data_testing$status_alergi_num == 0) * 100, 2), "%")
)
)
kable(
split_info,
col.names = c(
"Subset",
"n",
"Alergi Positif",
"Proporsi Positif",
"Alergi Negatif",
"Proporsi Negatif"
),
caption = "Tabel Ringkasan Pembagian Data Training dan Testing"
)| Subset | n | Alergi Positif | Proporsi Positif | Alergi Negatif | Proporsi Negatif |
|---|---|---|---|---|---|
| Training | 682 | 317 | 46.48% | 365 | 53.52% |
| Testing | 172 | 80 | 46.51% | 92 | 53.49% |
Model penuh dibangun dengan memasukkan seluruh 9 variabel kandidat ke dalam model regresi logistik biner menggunakan data training.
\[\text{logit}[\pi(\mathbf{x})] = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + \cdots + \beta_p x_p\]
model_penuh <- glm(
status_alergi_num ~
jenis_kelamin + usia + berat_badan +
kepemilikan_hewan + tempat_tinggal + lingkungan_jamur +
riwayat_keluarga + perokok_aktif + paparan_asap,
data = data_training,
family = binomial(link = "logit")
)
penuh_tidy <- broom::tidy(model_penuh) %>%
mutate(
across(c(estimate, std.error, statistic), ~ round(.x, 4)),
p.value = signif(p.value, 3),
Keterangan = ifelse(p.value < 0.05, "* Signifikan", "Tidak Signifikan")
)
kable(penuh_tidy,
col.names = c("Variabel", "β", "Galat Baku", "z", "p-value", "Keterangan"),
caption = "Tabel Estimasi Parameter Model Penuh (n training = 682)")| Variabel | β | Galat Baku | z | p-value | Keterangan |
|---|---|---|---|---|---|
| (Intercept) | -0.6139 | 1.0251 | -0.5989 | 5.49e-01 | Tidak Signifikan |
| jenis_kelaminLaki-laki | -0.4158 | 0.2252 | -1.8464 | 6.48e-02 | Tidak Signifikan |
| usia | 0.0024 | 0.0427 | 0.0557 | 9.56e-01 | Tidak Signifikan |
| berat_badan | -0.0070 | 0.0067 | -1.0434 | 2.97e-01 | Tidak Signifikan |
| kepemilikan_hewanYa | 0.3095 | 0.2056 | 1.5058 | 1.32e-01 | Tidak Signifikan |
| tempat_tinggalPinggiran Kota | 0.2206 | 0.2334 | 0.9453 | 3.45e-01 | Tidak Signifikan |
| tempat_tinggalPerdesaan | 0.4999 | 0.2114 | 2.3651 | 1.80e-02 | * Signifikan |
| tempat_tinggalKawasan Industri | 0.7637 | 0.2491 | 3.0662 | 2.17e-03 | * Signifikan |
| lingkungan_jamurYa | 0.1402 | 0.1754 | 0.7996 | 4.24e-01 | Tidak Signifikan |
| riwayat_keluargaRinitis Alergi | 1.1863 | 0.2298 | 5.1631 | 2.00e-07 | * Signifikan |
| riwayat_keluargaAsma | 1.0571 | 0.2504 | 4.2223 | 2.42e-05 | * Signifikan |
| riwayat_keluargaDermatitis Atopik | 1.3036 | 0.3977 | 3.2781 | 1.05e-03 | * Signifikan |
| riwayat_keluargaAlergi Obat | 0.2584 | 0.6114 | 0.4226 | 6.73e-01 | Tidak Signifikan |
| riwayat_keluargaAlergi Makanan | 1.0910 | 0.2324 | 4.6948 | 2.70e-06 | * Signifikan |
| perokok_aktifYa | 0.6918 | 0.2771 | 2.4969 | 1.25e-02 | * Signifikan |
| paparan_asapYa | 0.3126 | 0.2140 | 1.4606 | 1.44e-01 | Tidak Signifikan |
Interpretasi:
Pada model penuh, terdapat tujuh koefisien yang signifikan secara parsial tempat tinggal di kawasan industri, tempat tinggal di perdesaan, riwayat rinitis alergi , riwayat asma, riwayat dermatitis atopik, riwayat alergi makanan, dan merokok aktif. Variabel usia, berat badan, paparan jamur lingkungan, dan paparan asap rokok pasif tidak signifikan secara parsial. Perlu diperhatikan bahwa variabel jenis kelamin yang belum signifikan pada model penuh (p = 0,065) berpotensi menjadi signifikan setelah proses seleksi variabel karena kemungkinan adanya efek confunding dari variabel usia dan berat badan dalam model penuh.
Seleksi variabel dilakukan dengan metode stepwise bidirectional berbasis AIC. Algoritma mempertimbangkan penambahan (forward) dan penghapusan (backward) variabel secara bergantian hingga tidak ada lagi penurunan AIC.
\[\text{AIC} = -2\ln(L) + 2k\]
stepwise_tab <- data.frame(
Langkah = c("Awal", "1", "2", "3", "Final"),
`Variabel Dieliminasi` = c("Model Penuh (9 variabel)",
"Eliminasi: Usia",
"Eliminasi: Paparan Jamur Lingkungan",
"Eliminasi: Berat Badan",
"Tidak ada eliminasi lebih lanjut"),
AIC = c(885.35, 883.35, 881.99, 881.02, 881.02),
ΔAIC = c("—", "↓ 2,00", "↓ 1,36", "↓ 0,97", "✓")
)
kable(stepwise_tab,
col.names = c("Langkah", "Variabel Dieliminasi/Ditambah", "AIC", "ΔAIC"),
caption = "Tabel Ringkasan Proses Seleksi Stepwise Bidirectional AIC")| Langkah | Variabel Dieliminasi/Ditambah | AIC | ΔAIC |
|---|---|---|---|
| Awal | Model Penuh (9 variabel) | 885.35 | — |
| 1 | Eliminasi: Usia | 883.35 | ↓ 2,00 |
| 2 | Eliminasi: Paparan Jamur Lingkungan | 881.99 | ↓ 1,36 |
| 3 | Eliminasi: Berat Badan | 881.02 | ↓ 0,97 |
| Final | Tidak ada eliminasi lebih lanjut | 881.02 | ✓ |
Interpretasi:
Proses seleksi stepwise menghasilkan tiga tahap eliminasi berurutan: variabel usia (ΔAIC = −2,00), paparan jamur lingkungan (ΔAIC = −1,36), dan berat badan (ΔAIC = −0,97). Tidak ada variabel yang ditambahkan kembali pada tahap forward. AIC model final (881,02) lebih rendah 4,33 poin dibandingkan model penuh (885,35) dengan pengurangan tiga variabel, mengindikasikan peningkatan efisiensi model yang bermakna. Model final dipilih karena menyeimbangkan kesesuaian model dengan data dan kompleksitas parameter secara optimal.
Model final memuat 6 variabel prediktor: jenis kelamin, kepemilikan hewan peliharaan, jenis tempat tinggal, riwayat alergi keluarga, merokok aktif, dan paparan asap rokok pasif.
final_tidy <- broom::tidy(model_final) %>%
mutate(
across(c(estimate, std.error, statistic), ~ round(.x, 4)),
p.value = signif(p.value, 3),
Keterangan = ifelse(p.value < 0.05, "* Signifikan", "Tidak Signifikan")
)
kable(final_tidy,
col.names = c("Variabel", "β", "Galat Baku", "z", "p-value", "Keterangan"),
caption = "Tabel Estimasi Parameter Model Final (n training = 682)")| Variabel | β | Galat Baku | z | p-value | Keterangan |
|---|---|---|---|---|---|
| (Intercept) | -0.9431 | 0.2333 | -4.0429 | 5.28e-05 | * Signifikan |
| jenis_kelaminLaki-laki | -0.5034 | 0.2078 | -2.4222 | 1.54e-02 | * Signifikan |
| kepemilikan_hewanYa | 0.3247 | 0.2043 | 1.5890 | 1.12e-01 | Tidak Signifikan |
| tempat_tinggalPinggiran Kota | 0.2385 | 0.2327 | 1.0250 | 3.05e-01 | Tidak Signifikan |
| tempat_tinggalPerdesaan | 0.5375 | 0.2090 | 2.5710 | 1.01e-02 | * Signifikan |
| tempat_tinggalKawasan Industri | 0.7868 | 0.2478 | 3.1745 | 1.50e-03 | * Signifikan |
| riwayat_keluargaRinitis Alergi | 1.2224 | 0.2262 | 5.4033 | 1.00e-07 | * Signifikan |
| riwayat_keluargaAsma | 1.0683 | 0.2482 | 4.3046 | 1.67e-05 | * Signifikan |
| riwayat_keluargaDermatitis Atopik | 1.3288 | 0.3961 | 3.3544 | 7.95e-04 | * Signifikan |
| riwayat_keluargaAlergi Obat | 0.2454 | 0.6084 | 0.4033 | 6.87e-01 | Tidak Signifikan |
| riwayat_keluargaAlergi Makanan | 1.1148 | 0.2309 | 4.8283 | 1.40e-06 | * Signifikan |
| perokok_aktifYa | 0.6995 | 0.2740 | 2.5529 | 1.07e-02 | * Signifikan |
| paparan_asapYa | 0.3173 | 0.2132 | 1.4882 | 1.37e-01 | Tidak Signifikan |
Fungsi logit model final:
\[\hat{g}(\mathbf{x}) = -0{,}9431 - 0{,}5034 \cdot I_{\text{laki-laki}} + 0{,}3247 \cdot I_{\text{hewan}} \] \[+ 0{,}7868 \cdot I_{\text{industri}}+ 0{,}5375 \cdot I_{\text{perdesaan}}+ 0{,}2385 \cdot I_{\text{pinggiran}} \] \[+ 1{,}2224 \cdot I_{\text{rinitis}} + 1{,}0683\cdot I_{\text{asma}} + 1{,}3288 \cdot I_{\text{dermatitis}} + 0{,}2454 \cdot I_{\text{alergi obat}}\] \[+ 1{,}1148 \cdot I_{\text{alergi makanan}} + 0{,}6995 \cdot I_{\text{merokok}} + 0{,}3173 \cdot I_{\text{asap pasif}}\]Interpretasi:
Model final memuat 6 prediktor. Koefisien terbesar pada riwayat dermatitis atopik (\(\beta = 1{,}3288\)), rinitis alergi (\(\beta = 1{,}2224\)), dan alergi makanan (\(\beta = 1{,}1148\)). Satu-satunya efek protektif: jenis kelamin laki-laki (\(\beta = -0{,}5034\)). Riwayat dermatitis atopik keluarga memiliki pengaruh paling dominan dalam model.
Hipotesis:
\(H_0: \beta_1 = \beta_2 = \cdots = \beta_{12} = 0\) vs \(H_1\): minimal satu \(\beta_i \neq 0\)
\[G^2 = -2\ln\left(\frac{L_0}{L_1}\right) = -2[\ln(L_0) - \ln(L_1)]\]
ll_null <- logLik(glm(status_alergi_num ~ 1,
data = data_training, family = binomial))
ll_final <- logLik(model_final)
G2_val <- -2 * (as.numeric(ll_null) - as.numeric(ll_final))
df_G2 <- length(coef(model_final)) - 1
chi2_kritis <- qchisq(0.95, df = df_G2)
pval_G2 <- pchisq(G2_val, df = df_G2, lower.tail = FALSE)
uji_G2 <- data.frame(
`G^2` = round(G2_val, 3),
df = df_G2,
`χ^2 Tabel` = round(chi2_kritis, 3),
`p-value` = signif(pval_G2, 3),
Keputusan = "Tolak H₀"
)
kable(uji_G2, caption = "Tabel Hasil Uji G² (Likelihood Ratio Test) Model Final")| G.2 | df | χ.2.Tabel | p.value | Keputusan |
|---|---|---|---|---|
| 87.057 | 12 | 21.026 | 0 | Tolak H₀ |
Kesimpulan: \(G^2 = 87{,}057\) dengan \(\text{df} = 12\) jauh melampaui nilai kritis \(\chi^2_{(12;\,0{,}05)} = 21{,}026\) (\(p-value< 0{,}001\)). \(H_0\) ditolak model secara keseluruhan signifikan dan mampu menjelaskan variasi status alergi mahasiswa.
Uji Wald mengevaluasi signifikansi setiap koefisien secara parsial.
\[W^2 = \left(\frac{\hat{\beta}_j}{SE(\hat{\beta}_j)}\right)^2 \sim \chi^2_{(1)}\]
\(H_0\) ditolak jika \(W^2 > \chi^2_{(1;\,0{,}05)} = 3{,}84\) atau \(p < 0{,}05\).
wald_tabel <- broom::tidy(model_final) %>%
filter(term != "(Intercept)") %>%
mutate(
df = 1,
W2 = round((estimate / std.error)^2, 3),
p.value = signif(p.value, 3),
Keterangan = ifelse(W2 > qchisq(0.95, 1), "✅ Signifikan", "Tidak Signifikan")
) %>%
transmute(
Variabel = term,
β = round(estimate, 4),
`Galat Baku` = round(std.error, 4),
df,
`W²` = W2,
`p-value` = p.value,
Keterangan
)
kable(wald_tabel, caption = "Tabel Hasil Uji Wald (Parsial) Model Final")| Variabel | β | Galat Baku | df | W² | p-value | Keterangan |
|---|---|---|---|---|---|---|
| jenis_kelaminLaki-laki | -0.5034 | 0.2078 | 1 | 5.867 | 1.54e-02 | ✅ Signifikan |
| kepemilikan_hewanYa | 0.3247 | 0.2043 | 1 | 2.525 | 1.12e-01 | Tidak Signifikan |
| tempat_tinggalPinggiran Kota | 0.2385 | 0.2327 | 1 | 1.051 | 3.05e-01 | Tidak Signifikan |
| tempat_tinggalPerdesaan | 0.5375 | 0.2090 | 1 | 6.610 | 1.01e-02 | ✅ Signifikan |
| tempat_tinggalKawasan Industri | 0.7868 | 0.2478 | 1 | 10.078 | 1.50e-03 | ✅ Signifikan |
| riwayat_keluargaRinitis Alergi | 1.2224 | 0.2262 | 1 | 29.196 | 1.00e-07 | ✅ Signifikan |
| riwayat_keluargaAsma | 1.0683 | 0.2482 | 1 | 18.529 | 1.67e-05 | ✅ Signifikan |
| riwayat_keluargaDermatitis Atopik | 1.3288 | 0.3961 | 1 | 11.252 | 7.95e-04 | ✅ Signifikan |
| riwayat_keluargaAlergi Obat | 0.2454 | 0.6084 | 1 | 0.163 | 6.87e-01 | Tidak Signifikan |
| riwayat_keluargaAlergi Makanan | 1.1148 | 0.2309 | 1 | 23.312 | 1.40e-06 | ✅ Signifikan |
| perokok_aktifYa | 0.6995 | 0.2740 | 1 | 6.517 | 1.07e-02 | ✅ Signifikan |
| paparan_asapYa | 0.3173 | 0.2132 | 1 | 2.215 | 1.37e-01 | Tidak Signifikan |
Interpretasi:
Hasil uji Wald menunjukkan bahwa 8 dari 12 koefisien signifikan parsial (\(p-value < 0{,}05\)): jenis kelamin, kawasan industri, perdesaan, rinitis alergi, asma, dermatitis atopik, alergi makanan, dan merokok aktif. Jenis kelamin yang sebelumnya tidak signifikan (\(p-value = 0{,}065\)) menjadi signifikan (\(p-value = 0{,}015\)) setelah usia dan berat badan dieliminasi menunjukkan efek confounding yang sebelumnya menyamarkan pengaruh sesungguhnya
Uji Hosmer–Lemeshow mengevaluasi kalibrasi model dengan membandingkan frekuensi yang teramati dan diprediksi pada 10 kelompok.
\(H_0\): Model fit dengan data (tidak ada perbedaan signifikan antara nilai teramati dan diprediksi).
p_training_final <- predict(model_final, newdata = data_training, type = "response")
p_testing_final <- predict(model_final, newdata = data_testing, type = "response")
hl <- hoslem.test(data_training$status_alergi_num, p_training_final, g = 10)
hl_tabel <- data.frame(
`χ^2` = round(hl$statistic, 4),
df = hl$parameter,
`χ^2 Tabel` = round(qchisq(0.95, hl$parameter), 3),
`p-value` = round(hl$p.value, 3),
Keputusan = "Gagal Tolak H₀ — Model Fit"
)
kable(hl_tabel, caption = "Tabel Hasil Uji Hosmer–Lemeshow Model Final")| χ.2 | df | χ.2.Tabel | p.value | Keputusan | |
|---|---|---|---|---|---|
| X-squared | 2.9821 | 8 | 15.507 | 0.935 | Gagal Tolak H₀ — Model Fit |
Kesimpulan: \(\chi^2 = 2{,}982\), \(p-valu = 0{,}936 > 0{,}05\). \(H_0\) gagal ditolak model memiliki kalibrasi yang baik tidak terdapat perbedaan signifikan antara nilai yang teramati dan diprediksi.
Odds ratio (OR) dihitung sebagai \(\text{OR} = e^{\hat{\beta}_j}\) dengan interval kepercayaan 95% Wald: \(\exp(\hat{\beta}_j \pm 1{,}96 \cdot SE(\hat{\beta}_j))\).
or_tabel <- broom::tidy(model_final) %>%
filter(term != "(Intercept)") %>%
mutate(
OR = round(exp(estimate), 4),
CI_Bawah = round(exp(estimate - 1.96 * std.error), 4),
CI_Atas = round(exp(estimate + 1.96 * std.error), 4),
`p-value` = signif(p.value, 3),
Keterangan = ifelse(p.value < 0.05, "✅ Signifikan", "Tidak Signifikan")
) %>%
transmute(
Variabel = term,
OR,
`95% CI` = paste0(CI_Bawah, " – ", CI_Atas),
`p-value`,
Keterangan
)
kable(or_tabel, caption = "Tabel Odds Ratio dan Interval Kepercayaan 95% Model Final")| Variabel | OR | 95% CI | p-value | Keterangan |
|---|---|---|---|---|
| jenis_kelaminLaki-laki | 0.6045 | 0.4022 – 0.9084 | 1.54e-02 | ✅ Signifikan |
| kepemilikan_hewanYa | 1.3836 | 0.927 – 2.0651 | 1.12e-01 | Tidak Signifikan |
| tempat_tinggalPinggiran Kota | 1.2693 | 0.8045 – 2.0027 | 3.05e-01 | Tidak Signifikan |
| tempat_tinggalPerdesaan | 1.7117 | 1.1363 – 2.5785 | 1.01e-02 | ✅ Signifikan |
| tempat_tinggalKawasan Industri | 2.1963 | 1.3512 – 3.57 | 1.50e-03 | ✅ Signifikan |
| riwayat_keluargaRinitis Alergi | 3.3954 | 2.1793 – 5.2902 | 1.00e-07 | ✅ Signifikan |
| riwayat_keluargaAsma | 2.9104 | 1.7894 – 4.7338 | 1.67e-05 | ✅ Signifikan |
| riwayat_keluargaDermatitis Atopik | 3.7765 | 1.7374 – 8.209 | 7.95e-04 | ✅ Signifikan |
| riwayat_keluargaAlergi Obat | 1.2781 | 0.3879 – 4.2113 | 6.87e-01 | Tidak Signifikan |
| riwayat_keluargaAlergi Makanan | 3.0490 | 1.9392 – 4.7939 | 1.40e-06 | ✅ Signifikan |
| perokok_aktifYa | 2.0128 | 1.1764 – 3.444 | 1.07e-02 | ✅ Signifikan |
| paparan_asapYa | 1.3734 | 0.9043 – 2.0857 | 1.37e-01 | Tidak Signifikan |
Interpretasi:
Tabel di atas menyajikan odds ratio (OR) beserta interval kepercayaan 95% untuk seluruh variabel dalam model final. Variabel dengan OR > 1 meningkatkan peluang kejadian alergi, sedangkan OR < 1 menunjukkan efek protektif. Faktor risiko dominan secara berurutan: riwayat dermatitis atopik keluarga (OR = 3,777), riwayat rinitis alergi keluarga (OR = 3,395), riwayat alergi makanan keluarga (OR = 3,049), riwayat asma keluarga (OR = 2,910), tinggal di kawasan industri (OR = 2,196), dan merokok aktif (OR = 2,013). Jenis kelamin laki-laki merupakan satu-satunya variabel dengan efek protektif signifikan (OR = 0,605; 95% CI: 0,402–0,908).
or_plot <- broom::tidy(model_final) %>%
filter(term != "(Intercept)") %>%
mutate(
OR = exp(estimate),
CI_bawah = exp(estimate - 1.96 * std.error),
CI_atas = exp(estimate + 1.96 * std.error),
Warna = ifelse(
p.value < 0.05,
"Signifikan (p < 0,05)",
"Tidak Signifikan"))
ggplot(or_plot,aes(x = OR,y = reorder(term, OR),color = Warna)
) +
geom_errorbarh(aes(xmin = CI_bawah,xmax = CI_atas),height = 0.22,linewidth = 1
) +
geom_point(size = 4) +
geom_vline(
xintercept = 1,
linetype = "longdash",
linewidth = 0.9,
color = "#9e2a2b") +
scale_x_log10(
breaks = c(0.5, 1, 2, 3, 5)) +
scale_color_manual(
values = c(
"Signifikan (p < 0,05)" = "#2b5c3f",
"Tidak Signifikan" = "#8e8e8e" )) +
labs(title = "Odds Ratio dan Interval Kepercayaan 95%",
subtitle = "Model Final Seleksi Stepwise AIC",
x = "Odds Ratio (Skala Logaritmik)",
y = NULL,
color = NULL) +
theme_minimal(base_family = "Inter") +
theme(
plot.background =
element_rect(
fill = "#fdfbf7",
color = NA
),
panel.background =element_rect(fill ="#fdfbf7",color = NA),
plot.title =element_text(size = 18,
face = "bold",color = "#3a0e05",hjust =0.5 ),
plot.subtitle =element_text(
size = 12,color = "#7a1f0d",hjust = 0.5 ),
axis.title =element_text(size = 13,face = "bold",color = "#7a1f0d"),
axis.text =element_text(size = 11,
color = "#3a0e05"),
panel.grid.major.y =element_blank(),
panel.grid.minor =element_blank(),
panel.grid.major.x =element_line(color = "#f5ebd6",linewidth = 0.6),
legend.position = "bottom",
legend.title =element_blank(),
legend.text =element_text(size = 11,color ="#3a0e05")
)Figure 7.1: Gambar 2. Forest Plot Odds Ratio Model Final (Skala Logaritmik)
Semakin jauh titik dari garis 1,0 ke kanan semakin besar risiko alergi. Faktor risiko terkuat adalah riwayat dermatitis atopik, rinitis alergi, alergi makanan, dan asma dalam keluarga semuanya meningkatkan risiko 3–4 kali lipat. Tinggal di kawasan industri dan merokok aktif juga meningkatkan risiko sekitar 2 kali lipat. Sebaliknya, laki-laki justru lebih terlindungi dari alergi dibanding perempuan. Empat variabel abu-abu (kepemilikan hewan, paparan asap pasif, alergi obat, pinggiran kota) garisnya memotong angka 1,0 belum terbukti signifikan.
Mahasiswa laki-laki memiliki risiko alergi 39,55% lebih rendah dibanding perempuan (OR = 0,605; p = 0,015). Perempuan lebih rentan karena hormon estrogen memperkuat respons imun tipe alergi (Th2) dan meningkatkan produksi IgE sehingga tubuh perempuan lebih mudah bereaksi terhadap alergen.
| Tempat Tinggal | OR | 95% CI | p-value | Interpretasi |
|---|---|---|---|---|
| Kawasan Industri | 2,196 | 1,351–3,570 | 0,001 | ↑ risiko signifikan |
| Perdesaan | 1,712 | 1,136–2,579 | 0,010 | ↑ risiko signifikan |
| Pinggiran Kota | 1,269 | 0,805–2,003 | 0,305 | Tidak signifikan |
Dibanding mahasiswa yang tinggal di perkotaan, mahasiswa di kawasan industri risikonya 2,196 kali lebih tinggi paparan polutan seperti PM2.5, SO₂, dan VOC merusak lapisan pelindung saluran napas sehingga alergen lebih mudah masuk. Mahasiswa di perdesaan juga lebih berisiko (1,712 kali) bukan berarti desa lebih buruk, tapi kemungkinan karena paparan spora jamur, debu pertanian, dan bulu hewan ternak yang lebih tinggi. Pinggiran kota tidak menunjukkan perbedaan signifikan dibanding perkotaan.
Perokok aktif berisiko alergi dua kali lebih tinggi dibanding yang tidak merokok (OR = 2,013; p = 0,011). Asap rokok mengandung ribuan zat iritan yang merusak lapisan epitel saluran napas dan memicu peradangan kronis kondisi ini mempermudah tubuh tersensitisasi terhadap alergen. Paparan asap rokok pasif menunjukkan arah yang sama (OR = 1,373) namun belum signifikan secara statistik.
| Kondisi.Atopik.Keluarga | OR | X95..CI | p.value | Status |
|---|---|---|---|---|
| Dermatitis Atopik | 3.777 | 1,737–8,209 | < 0,001 | ✅ Signifikan |
| Rinitis Alergi | 3.395 | 2,179–5,290 | < 0,001 | ✅ Signifikan |
| Alergi Makanan | 3.049 | 1,939–4,794 | < 0,001 | ✅ Signifikan |
| Asma | 2.910 | 1,789–4,734 | < 0,001 | ✅ Signifikan |
| Alergi Obat | 1.278 | 0,388–4,211 | 0,687 | Tidak Signifikan |
Ini faktor risiko terkuat dalam model. Jika ada anggota keluarga dengan dermatitis atopik, risiko alergi hampir 4 kali lipat (OR = 3,777). Rinitis alergi keluarga meningkatkan risiko 3,4 kali, alergi makanan 3 kali, dan asma 2,9 kali. Artinya gen yang diturunkan orang tua sangat menentukan kecenderungan sistem imun anak untuk bereaksi berlebihan terhadap alergen. Satu-satunya yang tidak signifikan adalah riwayat alergi obat (OR = 1,278; p = 0,687), kemungkinan karena jumlah kasusnya sangat sedikit dalam data (hanya 18 orang; 2,11%).
ll_n <- logLik(glm(status_alergi_num ~ 1, data = data_training, family = binomial))
ll_m <- logLik(model_final)
n <- nrow(data_training)
eval_tabel <- data.frame(
Metrik = c("Jumlah Variabel Prediktor", "AIC", "Deviasi (G²)",
"McFadden Pseudo-R²", "Cox & Snell R²", "Nagelkerke R²"),
`Model Final` = c(
6,
round(AIC(model_final), 3),
round(-2 * (as.numeric(ll_n) - as.numeric(ll_m)), 3),
round(1 - as.numeric(ll_m) / as.numeric(ll_n), 3),
round(1 - exp((2/n) * (as.numeric(ll_n) - as.numeric(ll_m))), 3),
round((1 - exp((2/n) * (as.numeric(ll_n) - as.numeric(ll_m)))) /
(1 - exp((2/n) * as.numeric(ll_n))), 3)
)
)
kable(eval_tabel, caption = "Tabel Evaluasi Kinerja Model Final")| Metrik | Model.Final |
|---|---|
| Jumlah Variabel Prediktor | 6.000 |
| AIC | 881.015 |
| Deviasi (G²) | 87.057 |
| McFadden Pseudo-R² | 0.092 |
| Cox & Snell R² | 0.120 |
| Nagelkerke R² | 0.160 |
Interpretasi:
Nilai McFadden’s R² = 0,092, Cox & Snell R² = 0,120, dan Nagelkerke R² = 0,160 menunjukkan kemampuan penjelasan variasi status alergi yang relatif terbatas. Pada penelitian epidemiologi observasional berbasis survei cross-sectional, kondisi ini umum dijumpai karena masih terdapat faktor lain yang belum tercakup dalam model, seperti riwayat klinis lebih rinci, pola konsumsi, tingkat stres, dan paparan alergen spesifik. Oleh karena itu, model ini lebih tepat diarahkan sebagai alat analitik untuk mengidentifikasi asosiasi antarvariabel bukan sebagai model prediksi klinis. Interpretasi utama difokuskan pada nilai OR dan signifikansi statistik masing-masing faktor risiko.
roc_train <- titik_roc(data_training$status_alergi_num, p_training_final) %>% mutate(Data = "Training")
roc_test <- titik_roc(data_testing$status_alergi_num, p_testing_final) %>% mutate(Data = "Testing")
auc_train <- nilai_auc(roc_train)
auc_test <- nilai_auc(roc_test)
ggplot(bind_rows(roc_train, roc_test),
aes(x = fpr, y = sensitivitas, color = Data)) +
geom_path(linewidth = 1.2) +
geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "gray50") +
annotate("text", x = 0.75, y = 0.30,
label = paste0("AUC Training = ", round(auc_train, 3),
"\nAUC Testing = ", round(auc_test, 3)),
hjust = 0, size = 4, color = "#2c3e50", fontface = "italic") +
coord_equal() +
scale_color_manual(values = c("Training" = "#e74c3c", "Testing" = "#2980b9")) +
labs(
title = "Kurva ROC — Model Final",
subtitle = paste0("AUC Training = ", round(auc_train, 3),
" | AUC Testing = ", round(auc_test, 3)),
x = "False Positive Rate (1 − Spesifisitas)",
y = "Sensitivitas (True Positive Rate)",
color = "Data"
) +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(face = "bold"))Figure 8.1: Gambar 3. Kurva ROC Model Final — Data Training dan Testing
AUC Testing (0,728) > AUC Training (0,702) selisih kecil mengindikasikan model tidak mengalami overfitting dan memiliki kemampuan generalisasi yang baik. Nilai AUC 0,7–0,8 dianggap memadai untuk studi eksplorasi faktor risiko berbasis survei.
Threshold optimal ditentukan menggunakan Youden Index yang memaksimalkan jumlah sensitivitas dan spesifisitas secara simultan.
# Threshold optimal via Youden Index
optimal <- roc_train %>%
filter(is.finite(ambang)) %>%
arrange(desc(youden), desc(sensitivitas)) %>%
slice(1)
threshold <- optimal$ambang[1]
cat("Threshold Optimal (Youden Index):", round(threshold, 3), "\n")Threshold Optimal (Youden Index): 0.485
# Confusion matrix — Training
pred_train <- as.integer(p_training_final >= threshold)
cm_train <- table(
`Aktual` = ifelse(data_training$status_alergi_num == 1, "Alergi", "Tidak Alergi"),
`Prediksi` = ifelse(pred_train == 1, "Alergi", "Tidak Alergi")
)
kable(addmargins(cm_train), caption = "Tabel Confusion Matrix — Data Training")| Alergi | Tidak Alergi | Sum | |
|---|---|---|---|
| Alergi | 205 | 112 | 317 |
| Tidak Alergi | 122 | 243 | 365 |
| Sum | 327 | 355 | 682 |
# Confusion matrix — Testing
pred_test <- as.integer(p_testing_final >= threshold)
cm_test <- table(
`Aktual` = ifelse(data_testing$status_alergi_num == 1, "Alergi", "Tidak Alergi"),
`Prediksi` = ifelse(pred_test == 1, "Alergi", "Tidak Alergi")
)
kable(addmargins(cm_test), caption = "Tabel Confusion Matrix — Data Testing")| Alergi | Tidak Alergi | Sum | |
|---|---|---|---|
| Alergi | 52 | 28 | 80 |
| Tidak Alergi | 29 | 63 | 92 |
| Sum | 81 | 91 | 172 |
m_train <- metrik_klasifikasi(data_training$status_alergi_num, p_training_final, threshold)
m_test <- metrik_klasifikasi(data_testing$status_alergi_num, p_testing_final, threshold)
perbandingan <- data.frame(
Metrik = c("Akurasi", "Tingkat Kesalahan", "Sensitivitas",
"Spesifisitas", "Presisi", "Skor F1", "Akurasi Seimbang"),
Training = round(c(m_train$Akurasi, m_train$Tingkat_Kesalahan,
m_train$Sensitivitas, m_train$Spesifisitas,
m_train$Presisi, m_train$Skor_F1,
m_train$Akurasi_Seimbang), 4),
Testing = round(c(m_test$Akurasi, m_test$Tingkat_Kesalahan,
m_test$Sensitivitas, m_test$Spesifisitas,
m_test$Presisi, m_test$Skor_F1,
m_test$Akurasi_Seimbang), 4)
)
kable(perbandingan,
col.names = c("Metrik", "Data Training", "Data Testing"),
caption = "Tabel Perbandingan Metrik Klasifikasi — Training vs Testing")| Metrik | Data Training | Data Testing |
|---|---|---|
| Akurasi | 0.6569 | 0.6686 |
| Tingkat Kesalahan | 0.3431 | 0.3314 |
| Sensitivitas | 0.6467 | 0.6500 |
| Spesifisitas | 0.6658 | 0.6848 |
| Presisi | 0.6269 | 0.6420 |
| Skor F1 | 0.6366 | 0.6460 |
| Akurasi Seimbang | 0.6562 | 0.6674 |
Seluruh metrik pada data testing konsisten sedikit lebih baik dari data training (Akurasi: 0,669 vs 0,657; F1-Score: 0,646 vs 0,637) mengonfirmasi tidak adanya overfitting.
Penelitian ini menerapkan regresi logistik biner dengan prosedur pemodelan komprehensif menggunakan University Student Allergy Dataset (\(n = 854\)). Model final yang terbentuk melalui seleksi stepwise bidirectional berbasis AIC memuat 6 variabel prediktor: jenis kelamin, kepemilikan hewan peliharaan, jenis tempat tinggal, riwayat alergi keluarga, merokok aktif, dan paparan asap rokok pasif.
Temuan utama:
Temuan ini menggarisbawahi bahwa kejadian alergi pada mahasiswa berkaitan dengan interaksi predisposisi genetik, paparan lingkungan, dan faktor gaya hidup yang dapat dimodifikasi. Faktor yang dapat diintervensi seperti pengendalian paparan asap rokok dan peningkatan kualitas lingkungan tempat tinggal berpotensi mendukung upaya pencegahan alergi pada populasi mahasiswa. Penelitian selanjutnya disarankan menggunakan desain longitudinal, konfirmasi diagnostik klinis, serta populasi yang lebih representatif.