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


1 Pendahuluan

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 :

  1. Mengidentifikasi faktor-faktor yang berasosiasi dengan kejadian alergi pada mahasiswa.
  2. Mengestimasi besar asosiasi masing-masing faktor risiko terhadap status alergi menggunakan nilai odds ratio (OR) dalam kerangka regresi logistik biner.
  3. Menyusun model regresi logistik biner yang mampu menjelaskan hubungan antara faktor risiko dengan status alergi mahasiswa.

2 Data dan Variabel 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:

  • Diagnosis rinitis (rhinitis_diag) dan Gejala pemicu (trigger_symptom): dikeluarkan karena mengalami perfect separation, di mana seluruh responden dengan nilai “ya” semuanya memiliki status alergi sehingga menyebabkan model tidak dapat konvergen dengan baik.
  • Kondisi yang didiagnosis dokter (doctor_cond): tidak diikutsertakan karena bersifat tautologis yaitu individu yang telah didiagnosis alergi oleh dokter secara otomatis termasuk dalam kategori alergi.
  • Durasi rinitis (rhinitis_duration): merupakan turunan langsung dari variabel diagnosis rinitis (rhinitis_diag) sehingga tidak memberikan informasi tambahan dalam model.
  • Jenis hewan peliharaan (pet_type): dikeluarkan karena menggabungkan dua konstruk yang berbeda sehingga bersifat ambigu secara konseptual.
  • Tinggi badan (height_ft): tidak relevan terhadap tujuan analisis.
  • Jumlah rokok per hari (cigs_per_day): dikeluarkan karena redundan dengan variabel status merokok (smoke_now) yang sudah merepresentasikan status merokok.

2.1 Instalasi & Load Package

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))

2.2 Load data

# ---- 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;")
Table 2.1: Table 2.2: Tabel Data Alergi (5 baris pertama)
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

2.3 Transformasi data

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;")
Table 2.3: Table 2.4: Tabel Contoh Data Setelah Transformasi (5 Observasi Pertama)
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

2.4 Varibael respon yang digunakan

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;")
Table 2.5: Table 2.6: Tabel Variabel prediktor yang digunakan dalam analisis.
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

3 Statistika Deskriptif

3.1 Distribusi Variabel Respons

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"
  )
Gambar 1. Distribusi Status Alergi Responden

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.

3.2 Statistik Deskriptif Variabel Numerik

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)")
Table 3.1: 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.

3.3 Distribusi Variabel Kategorik

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)")
Table 3.2: 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.


4 Pemeriksaan Asumsi

4.1 Uji Linearitas — Box–Tidwell

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")
Table 4.1: 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.

4.2 Uji Multikolinearitas — GVIF

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")
Table 4.2: 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.


5 Pemodelan Regresi Logistik Biner

5.1 Pembagian Data Training dan Testing

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"
)
Table 5.1: 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%

5.2 Model Penuh

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)")
Table 5.2: 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.

5.3 Seleksi Model — Stepwise Bidirectional AIC

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\]

model_final <- step(model_penuh, direction = "both", trace = 1)
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")
Table 5.3: 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.

5.4 Model Final

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)")
Table 5.4: 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.


6 Pengujian Signifikansi dan Kecocokan Model

6.1 Uji G² — Likelihood Ratio Test (Uji Simultan)

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")
Table 6.1: 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.

6.2 Uji Wald (Uji Parsial)

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,
    ``       = W2,       
    `p-value`  = p.value,
    Keterangan
  )

kable(wald_tabel, caption = "Tabel Hasil Uji Wald (Parsial) Model Final")
Table 6.2: Tabel Hasil Uji Wald (Parsial) Model Final
Variabel β Galat Baku df 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

6.3 Uji Hosmer–Lemeshow (Goodness-of-Fit)

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")
Table 6.3: 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.


7 Odds Ratio dan Interpretasi Faktor Risiko

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")
Table 7.1: 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).

7.1 Forest Plot Odds Ratio

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")
)
Gambar 2. Forest Plot Odds Ratio Model Final (Skala Logaritmik)

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.

7.2 Ringkasan Interpretasi Faktor Risiko

7.2.1 Faktor Demografis

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.

7.2.2 Faktor Lingkungan

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.

7.2.3 Faktor Gaya Hidup

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.

7.2.4 Riwayat Alergi Keluarga (Faktor Risiko Dominan)

Table 7.2: Tabel Odds Ratio Riwayat Alergi Keluarga
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%).


8 Evaluasi Kinerja Model

8.1 Pseudo-R² dan AIC

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")
Table 8.1: 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.

8.2 Kurva ROC dan Nilai AUC

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"))
Gambar 3. Kurva ROC Model Final — Data Training dan Testing

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.

8.3 Confusion Matrix dan Metrik Klasifikasi

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")
Table 8.2: 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")
Table 8.2: 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")
Table 8.3: 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.


9 Kesimpulan

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:

  1. Riwayat alergi keluarga merupakan determinan terkuat status alergi mahasiswa (OR: 2,910–3,777 untuk berbagai kondisi atopik), menegaskan peran predisposisi genetik sebagai faktor risiko fundamental
  2. Tinggal di kawasan industri merupakan faktor lingkungan dengan pengaruh terkuat (OR = 2,196; \(p = 0{,}001\)), diduga terkait paparan polutan udara
  3. Merokok aktif merupakan faktor gaya hidup yang dapat dimodifikasi dengan pengaruh signifikan (OR = 2,013; \(p = 0{,}011\))
  4. Jenis kelamin laki-laki menunjukkan efek protektif signifikan (OR = 0,605; \(p = 0{,}015\)), konsisten dengan mekanisme imunologis berbasis hormonal
  5. Model memiliki kalibrasi yang baik (Hosmer–Lemeshow \(p = 0{,}936\)) dan kemampuan diskriminasi memadai (AUC testing = 0,728) tanpa indikasi overfitting

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.


Analisis Data Kategorik — Program Studi Statistika, FMIPA Universitas Padjadjaran
Dataset: University Student Allergy Dataset (Mendeley Data, 2026)(doi: 10.17632/5yrzvgjryt.1)