Abstract
Dokumen ini disusun sebagai tugas individu mata kuliah Analisis Data Kategori (D10E.4004). Laporan mencakup konsep dasar tabel kontingensi dua arah, distribusi peluang, ukuran asosiasi (Beda Proporsi, Risiko Relatif, Odds Ratio), uji independensi (Pearson Chi-Square dan Likelihood Ratio), Uji Eksak Fisher, analisis residual, serta partisi chi-square — semuanya dilengkapi dengan implementasi R dan interpretasi substantif.
------------------------------------------------------------------------
# Pendahuluan
## Mengapa Data Kategori Penting?
Bayangkan seorang dokter ingin menjawab pertanyaan sederhana namun
penting: *"Apakah perokok lebih berisiko terkena kanker paru-paru
dibandingkan non-perokok?"* Kedua variabel di sini — status merokok dan
diagnosis kanker — bukan angka yang bisa dijumlah atau dirata-rata.
Keduanya adalah **data kategori**: ya atau tidak, ada atau tidak ada.
Inilah dunia analisis data kategori. Data jenis ini hadir di mana-mana:
- Di klinik: penyakit ada/tidak, pasien sembuh/tidak
- Di survei sosial: preferensi partai, tingkat kepuasan
- Di riset pendidikan: lulus/tidak, nilai A/B/C
- Di bisnis: pelanggan churn/tidak, produk cacat/normal
::: info-box
**Apa itu Data Kategori?** Data kategori adalah data yang nilainya
berupa label atau kelas, bukan angka yang bermakna secara matematis.
Setiap observasi jatuh ke dalam tepat satu kategori dari sejumlah
kategori yang telah ditentukan.
:::
## Kasus Nyata: Merokok dan Kanker Paru-Paru
Sepanjang dokumen ini, kita akan menggunakan data nyata dari studi
**case-control** di 20 rumah sakit London, Inggris (Agresti, 2019).
Peneliti ingin mengetahui apakah perokok memiliki risiko lebih tinggi
terkena kanker paru-paru.
| Status Merokok | Kanker (Cases) | Kontrol | Total |
|----------------|----------------|---------|-------|
| **Ya** | 688 | 650 | 1338 |
| **Tidak** | 21 | 59 | 80 |
| **Total** | 709 | 709 | 1418 |
Dari tabel ini — yang disebut **tabel kontingensi** — kita bisa menggali
berbagai pertanyaan: Seberapa besar bedanya? Apakah perbedaannya nyata
secara statistik? Berapa kali lipat risikonya? Semua akan kita jawab
secara bertahap.
## Tujuan Dokumen
Setelah membaca dokumen ini, kamu diharapkan mampu:
1. Membangun dan membaca tabel kontingensi dengan percaya diri
2. Menghitung dan menginterpretasi distribusi peluang (joint, marginal,
bersyarat)
3. Menghitung ukuran asosiasi: Beda Proporsi, Risiko Relatif, dan Odds
Ratio
4. Melakukan uji independensi (Chi-Square Pearson dan Likelihood Ratio)
5. Menggunakan Uji Eksak Fisher untuk sampel kecil
6. Menganalisis residual untuk menemukan sel "bermasalah"
7. Melakukan partisi chi-square untuk analisis lebih rinci
8. Mengimplementasikan semua hal di atas menggunakan R
------------------------------------------------------------------------
# Konsep Dasar Tabel Kontingensi
## Apa Itu Tabel Kontingensi?
**Tabel kontingensi** adalah tabel klasifikasi silang
(*cross-tabulation*) yang merangkum hubungan antara dua atau lebih
variabel kategori. Nama "kontingensi" berasal dari ide bahwa distribusi
satu variabel mungkin *bergantung* (contingent) pada nilai variabel
lainnya.
::: info-box
**Struktur Dasar:** Jika variabel X memiliki $I$ kategori dan variabel Y
memiliki $J$ kategori, maka kita memiliki tabel kontingensi berukuran
$I \times J$ dengan total $I \times J$ sel.
:::
## Tabel Kontingensi 2×2: Yang Paling Dasar
Tabel kontingensi paling sederhana adalah $2 \times 2$, di mana kedua
variabel masing-masing memiliki 2 kategori.
| | $Y = 1$ | $Y = 2$ | Total Baris |
|-----------------|----------|----------|-------------|
| $X = 1$ | $n_{11}$ | $n_{12}$ | $n_{1+}$ |
| $X = 2$ | $n_{21}$ | $n_{22}$ | $n_{2+}$ |
| **Total Kolom** | $n_{+1}$ | $n_{+2}$ | $n$ |
**Notasi yang digunakan:**
- $n_{ij}$ = frekuensi pada baris ke-$i$, kolom ke-$j$
- $n_{i+}$ = total baris ke-$i$ (penjumlahan sepanjang kolom)
- $n_{+j}$ = total kolom ke-$j$ (penjumlahan sepanjang baris)
- $n$ = total keseluruhan pengamatan
## Perbandingan Berbagai Ukuran Tabel
| Jenis Tabel | Variabel | Jumlah Sel | Derajat Bebas |
|--------------|------------------------------------|--------------|---------------|
| $2 \times 2$ | 2 var, 2 kategori masing-masing | 4 | 1 |
| $2 \times 3$ | 2 var, salah satu punya 3 kategori | 6 | 2 |
| $I \times J$ | 2 var, umum | $I \times J$ | $(I-1)(J-1)$ |
| Multi-arah | 3+ variabel | ... | ... |
## Membuat Tabel Kontingensi di R
``` r
# Install dan load library yang dibutuhkan
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.4.3
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
## Warning: package 'ggplot2' was built under R version 4.4.3
## Warning: package 'knitr' was built under R version 4.4.3
## Warning: package 'kableExtra' was built under R version 4.4.3
##
## Attaching package: 'kableExtra'
## The following object is masked from 'package:dplyr':
##
## group_rows
# ── Data Kasus Merokok & Kanker Paru (Agresti, 2019, Table 2.3) ──
# Baris: Status Merokok (Ya/Tidak)
# Kolom: Kelompok (Cases/Control)
merokok_data <- matrix(
c(688, 650, # Merokok: Cases, Control
21, 59), # Tidak Merokok: Cases, Control
nrow = 2, byrow = TRUE,
dimnames = list(
Merokok = c("Ya", "Tidak"),
"Kanker Paru" = c("Cases", "Control")
)
)
# Tampilkan tabel dengan tambahan total baris dan kolom
addmargins(merokok_data) |>
kable(caption = "Tabel Kontingensi: Merokok vs Kanker Paru-Paru") |>
kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
full_width = FALSE) |>
row_spec(0, bold = TRUE, background = "#2980b9", color = "white") |>
row_spec(3, bold = TRUE, background = "#ecf0f1")| Cases | Control | Sum | |
|---|---|---|---|
| Ya | 688 | 650 | 1338 |
| Tidak | 21 | 59 | 80 |
| Sum | 709 | 709 | 1418 |
Dari sebuah tabel kontingensi, kita bisa menghitung tiga jenis distribusi peluang yang masing-masing menjawab pertanyaan berbeda.
\[\hat{\pi}_{ij} = p_{ij} = \frac{n_{ij}}{n}\]
Peluang bersama menjawab: “Berapa proporsi dari seluruh responden yang masuk dalam kategori X ke-i DAN Y ke-j secara bersamaan?”
\[\hat{\pi}_{i+} = p_{i+} = \frac{n_{i+}}{n} \qquad \hat{\pi}_{+j} = p_{+j} = \frac{n_{+j}}{n}\]
Peluang marginal menjawab: “Berapa proporsi dari seluruh responden yang masuk dalam kategori X ke-i (atau Y ke-j), tanpa memperhatikan variabel lainnya?”
\[\hat{\pi}_{j|i} = p_{j|i} = \frac{n_{ij}}{n_{i+}}\]
Peluang bersyarat menjawab: “Dari responden yang ada di baris i, berapa proporsi yang masuk kategori Y = j?” — Ini yang paling sering kita gunakan untuk melihat asosiasi!
Kunci independensi: Jika X dan Y saling bebas, maka \(\pi_{j|i} = \pi_{+j}\) untuk semua \(i\) dan \(j\). Artinya, mengetahui kategori X tidak mengubah distribusi Y.
## =========================================
## PELUANG BERSAMA (Joint Probability)
## =========================================
## Kanker Paru
## Merokok Cases Control
## Ya 0.4852 0.4584
## Tidak 0.0148 0.0416
## =========================================
## PELUANG MARGINAL (Marginal Probability)
## =========================================
p_baris <- rowSums(p_bersama)
p_kolom <- colSums(p_bersama)
cat("Marginal Baris (Merokok Ya / Tidak):\n")## Marginal Baris (Merokok Ya / Tidak):
## Ya Tidak
## 0.9436 0.0564
## Marginal Kolom (Cases / Control):
## Cases Control
## 0.5 0.5
## ==========================================
## PELUANG BERSYARAT (Conditional Probability)
## [P(Kelompok | Status Merokok)]
## ==========================================
p_bersyarat <- prop.table(merokok_data, margin = 1) # margin=1 → per baris
print(round(p_bersyarat, 4))## Kanker Paru
## Merokok Cases Control
## Ya 0.5142 0.4858
## Tidak 0.2625 0.7375
## --- Interpretasi Utama ---
cat(sprintf(
"Proporsi perokok di antara kasus kanker : %.1f%%\n",
p_bersyarat["Ya", "Cases"] * 100
))## Proporsi perokok di antara kasus kanker : 51.4%
## Proporsi perokok di antara kontrol : 48.6%
cat(sprintf(
"\nBeda proporsi perokok (Cases vs Control): %.1f pp\n",
(p_bersyarat["Ya", "Cases"] - p_bersyarat["Ya", "Control"]) * 100
))##
## Beda proporsi perokok (Cases vs Control): 2.8 pp
Temuan awal: Di antara penderita kanker, 97.0% adalah perokok. Di antara kelompok kontrol (sehat), hanya 91.7% perokok. Perbedaan ini terlihat, tapi apakah bermakna secara statistik? Kita akan jawab di bagian selanjutnya!
Ukuran asosiasi membantu kita mengukur seberapa besar dan seberapa kuat hubungan antara dua variabel kategori. Ada tiga ukuran utama.
Beda proporsi adalah cara paling langsung mengukur perbedaan: kita cukup kurangi proporsi satu kelompok dari kelompok lainnya. Jika hasilnya 0, tidak ada perbedaan; jika jauh dari 0, ada perbedaan yang berarti.
\[BP = p_{j|h} - p_{j|i}\]
Standard error: \[SE(BP) = \sqrt{\frac{p_{j|h}(1 - p_{j|h})}{n_{h+}} + \frac{p_{j|i}(1 - p_{j|i})}{n_{i+}}}\]
Interval Kepercayaan \(100(1-\alpha)\%\): \[(p_{j|h} - p_{j|i}) \pm Z_{\alpha/2} \cdot SE(BP)\]
# p(Cases | Merokok Ya) dan p(Cases | Merokok Tidak)
p_cases_ya <- p_bersyarat["Ya", "Cases"]
p_cases_tidak <- p_bersyarat["Tidak", "Cases"]
n_ya <- merokok_data["Ya", "Cases"] + merokok_data["Ya", "Control"]
n_tidak <- merokok_data["Tidak", "Cases"] + merokok_data["Tidak", "Control"]
# Hitung BP
BP <- p_cases_ya - p_cases_tidak
# Hitung SE
SE_BP <- sqrt(
(p_cases_ya * (1 - p_cases_ya) / n_ya) +
(p_cases_tidak * (1 - p_cases_tidak) / n_tidak)
)
# Interval kepercayaan 95%
z <- qnorm(0.975)
CI_BP_lower <- BP - z * SE_BP
CI_BP_upper <- BP + z * SE_BP
cat("=== BEDA PROPORSI ===\n")## === BEDA PROPORSI ===
## P(Cases | Merokok Ya) : 0.5142
## P(Cases | Merokok Tidak) : 0.2625
## Beda Proporsi (BP) : 0.2517
## Standard Error (SE) : 0.0511
## 95% CI BP : (0.1516, 0.3518)
Interpretasi: Proporsi kasus kanker pada perokok lebih tinggi 0.2517 dibandingkan non-perokok. Interval kepercayaan tidak mencakup 0, sehingga perbedaan ini signifikan.
Bayangkan dua jalan setapak menuju bahaya: satu untuk perokok, satu untuk non-perokok. Risiko relatif mengukur berapa kali lebih berbahaya jalan perokok dibandingkan jalan non-perokok. RR = 2 berarti perokok dua kali lebih berisiko.
Kapan RR lebih baik dari BP? Ketika peluang yang terlibat sangat kecil. Misalnya, BP = 0.003 - 0.001 = 0.002 terlihat kecil, tapi RR = 3 menceritakan kisah yang sangat berbeda: risikonya 3x lipat!
\[RR = \frac{p_{j|h}}{p_{j|i}}\]
Standard error untuk \(\ln(RR)\): \[SE(\ln RR) = \sqrt{\frac{1 - p_{j|h}}{p_{j|h} \cdot n_{h+}} + \frac{1 - p_{j|i}}{p_{j|i} \cdot n_{i+}}}\]
Interval Kepercayaan: \(\exp\!\left(\ln RR \pm Z_{\alpha/2} \cdot SE(\ln RR)\right)\)
# Risiko Relatif
RR <- p_cases_ya / p_cases_tidak
# SE untuk ln(RR)
SE_lnRR <- sqrt(
(1 - p_cases_ya) / (p_cases_ya * n_ya) +
(1 - p_cases_tidak) / (p_cases_tidak * n_tidak)
)
# Interval kepercayaan 95% via ln(RR)
ln_RR <- log(RR)
CI_RR_lower <- exp(ln_RR - z * SE_lnRR)
CI_RR_upper <- exp(ln_RR + z * SE_lnRR)
cat("=== RISIKO RELATIF ===\n")## === RISIKO RELATIF ===
## RR : 1.9589
## ln(RR) : 0.6724
## SE(ln RR) : 0.1893
## 95% CI RR : (1.3517, 2.8387)
Interpretasi: Perokok memiliki risiko terkena kanker paru 1.96 kali lebih tinggi dibandingkan non-perokok. Namun ingat — ini adalah case-control study, bukan cohort study, jadi RR tidak bisa diinterpretasi langsung sebagai risiko kausal. Gunakan Odds Ratio!
Odds adalah perbandingan antara “peluang sukses” dan “peluang gagal”: \(\text{odds} = \frac{p}{1-p}\). Odds Ratio adalah perbandingan odds antara dua kelompok. Untuk tabel \(2 \times 2\), ia dihitung langsung dari perkalian silang.
Keunggulan Odds Ratio: Odds Ratio tidak berubah baik kita memulai dari baris (studi kohort/prospektif) maupun dari kolom (studi kasus-kontrol/retrospektif). Inilah yang membuatnya menjadi ukuran asosiasi universal dalam epidemiologi!
\[\hat{\theta} = \frac{n_{11} \cdot n_{22}}{n_{12} \cdot n_{21}}\]
Standard error untuk \(\ln(\hat{\theta})\): \[SE(\ln\hat{\theta}) = \sqrt{\frac{1}{n_{11}} + \frac{1}{n_{12}} + \frac{1}{n_{21}} + \frac{1}{n_{22}}}\]
Catatan: Jika ada sel = 0, gunakan koreksi Gart-Haldane: tambahkan 0.5 ke setiap sel. \[\tilde{\theta} = \frac{(n_{11}+0.5)(n_{22}+0.5)}{(n_{12}+0.5)(n_{21}+0.5)}\]
n11 <- merokok_data["Ya", "Cases"]
n12 <- merokok_data["Ya", "Control"]
n21 <- merokok_data["Tidak", "Cases"]
n22 <- merokok_data["Tidak", "Control"]
# Odds masing-masing kelompok
odds_ya <- n11 / n12
odds_tidak <- n21 / n22
# Odds Ratio (perkalian silang)
OR <- (n11 * n22) / (n12 * n21)
# SE untuk ln(OR)
SE_lnOR <- sqrt(1/n11 + 1/n12 + 1/n21 + 1/n22)
# Interval kepercayaan 95%
ln_OR <- log(OR)
CI_OR_lower <- exp(ln_OR - z * SE_lnOR)
CI_OR_upper <- exp(ln_OR + z * SE_lnOR)
cat("=== ODDS RATIO ===\n")## === ODDS RATIO ===
## Odds (Merokok Ya) : 1.0585 [688 / 650]
## Odds (Merokok Tidak) : 0.3559 [21 / 59]
## Odds Ratio (OR) : 2.9738
## ln(OR) : 1.0898
## SE(ln OR) : 0.2599
## 95% CI OR : (1.7867, 4.9494)
Interpretasi: Odds terkena kanker pada perokok adalah 2.97 kali lebih tinggi dibandingkan non-perokok. Karena interval kepercayaan [1.79, 4.95] tidak mencakup 1, asosiasi ini signifikan secara statistik pada \(\alpha = 5\%\).
ringkasan <- data.frame(
Ukuran = c("Beda Proporsi", "Risiko Relatif", "Odds Ratio"),
Nilai = round(c(BP, RR, OR), 4),
CI_Bawah = round(c(CI_BP_lower, CI_RR_lower, CI_OR_lower), 4),
CI_Atas = round(c(CI_BP_upper, CI_RR_upper, CI_OR_upper), 4),
`Nilai H0` = c("0", "1", "1"),
Signifikan = c("Ya", "Ya", "Ya"),
check.names = FALSE
)
kable(ringkasan, caption = "Ringkasan Ukuran Asosiasi: Merokok vs Kanker Paru") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) |>
row_spec(0, bold = TRUE, background = "#2980b9", color = "white")| Ukuran | Nilai | CI_Bawah | CI_Atas | Nilai H0 | Signifikan |
|---|---|---|---|---|---|
| Beda Proporsi | 0.2517 | 0.1516 | 0.3518 | 0 | Ya |
| Risiko Relatif | 1.9589 | 1.3517 | 2.8387 | 1 | Ya |
| Odds Ratio | 2.9738 | 1.7867 | 4.9494 | 1 | Ya |
Ukuran asosiasi di atas memberitahu kita seberapa besar perbedaannya. Uji independensi menjawab pertanyaan yang berbeda: “Apakah perbedaan ini cukup besar untuk tidak kita anggap sebagai kebetulan?”
Hipotesis: \[H_0: \pi_{ij} = \pi_{i+} \cdot \pi_{+j} \quad \text{(X dan Y saling bebas)}\] \[H_1: \pi_{ij} \neq \pi_{i+} \cdot \pi_{+j} \quad \text{(X dan Y tidak saling bebas)}\]
Frekuensi ekspektasi di bawah \(H_0\): \[\hat{\mu}_{ij} = n \cdot p_{i+} \cdot p_{+j} = \frac{n_{i+} \cdot n_{+j}}{n}\]
Logika: jika X dan Y benar-benar bebas, frekuensi ekspektasi adalah yang kita harapkan. Kita lalu ukur seberapa jauh frekuensi observasi menyimpang dari ekspektasi ini.
\[X^2 = \sum_i \sum_j \frac{(n_{ij} - \hat{\mu}_{ij})^2}{\hat{\mu}_{ij}}\]
Di bawah \(H_0\), \(X^2 \sim \chi^2_{(I-1)(J-1)}\)
Tolak \(H_0\) jika \(X^2 \geq \chi^2_{(1-\alpha), (I-1)(J-1)}\)
\[G^2 = 2 \sum_i \sum_j n_{ij} \ln\!\left(\frac{n_{ij}}{\hat{\mu}_{ij}}\right)\]
Di bawah \(H_0\), \(G^2 \sim \chi^2_{(I-1)(J-1)}\)
Kapan memilih yang mana? Untuk sampel besar, \(X^2\) dan \(G^2\) memberikan hasil serupa. \(G^2\) lebih berguna karena dapat dipartisi (lihat Bagian 6). Untuk sampel kecil, gunakan Uji Eksak Fisher (Bagian 5).
## ===================================================
## UJI INDEPENDENSI: MEROKOK vs KANKER PARU
## ===================================================
# ── Frekuensi Ekspektasi ──────────────────────────────
n_total <- sum(merokok_data)
n_baris <- rowSums(merokok_data)
n_kolom <- colSums(merokok_data)
frek_exp <- outer(n_baris, n_kolom) / n_total
cat("Frekuensi Observasi:\n")## Frekuensi Observasi:
## Kanker Paru
## Merokok Cases Control
## Ya 688 650
## Tidak 21 59
##
## Frekuensi Ekspektasi (μ̂ij):
## Cases Control
## Ya 669 669
## Tidak 40 40
# ── Pearson Chi-Square ────────────────────────────────
X2 <- sum((merokok_data - frek_exp)^2 / frek_exp)
df_tabel <- (nrow(merokok_data) - 1) * (ncol(merokok_data) - 1)
p_val_X2 <- 1 - pchisq(X2, df = df_tabel)
cat(sprintf("\n--- Pearson Chi-Square ---\n"))##
## --- Pearson Chi-Square ---
## X² = 19.1292
## df = 1
## p = 0.000012
# ── Likelihood Ratio (G²) ────────────────────────────
G2 <- 2 * sum(merokok_data * log(merokok_data / frek_exp))
p_val_G2 <- 1 - pchisq(G2, df = df_tabel)
cat(sprintf("\n--- Likelihood Ratio (G²) ---\n"))##
## --- Likelihood Ratio (G²) ---
## G² = 19.8780
## df = 1
## p = 0.000008
# ── Verifikasi dengan fungsi bawaan R ────────────────
cat("\n--- Verifikasi via chisq.test() ---\n")##
## --- Verifikasi via chisq.test() ---
##
## Pearson's Chi-squared test
##
## data: merokok_data
## X-squared = 19.129, df = 1, p-value = 1.222e-05
Interpretasi: Nilai \(X^2 =\) 19.13 jauh melampaui nilai kritis \(\chi^2_{0.95, 1} = 3.84\). Dengan p-value yang sangat kecil (\(< 0.001\)), kita tolak \(H_0\) dan menyimpulkan bahwa terdapat hubungan yang signifikan antara status merokok dan kejadian kanker paru-paru.
Uji Chi-Square mengandalkan aproksimasi normal yang membutuhkan sampel besar. Aturan praktisnya: jika frekuensi ekspektasi di salah satu sel kurang dari 5, gunakan Uji Eksak Fisher.
Uji ini menghitung p-value secara langsung menggunakan distribusi Hipergeometrik, tanpa bergantung pada aproksimasi apapun.
\[H_0: \theta = 1 \quad \text{(tidak ada asosiasi)}\] \[H_1: \theta > 1 \quad \text{(ada asosiasi positif)}\]
Peluang mendapat tabel tertentu (dengan margin baris dan kolom tetap):
\[P(n_{11}) = \frac{\binom{n_{1+}}{n_{11}} \binom{n_{2+}}{n_{+1} - n_{11}}}{\binom{n}{n_{+1}}}\]
p-value (uji satu sisi): \(P = P(n_{11} \geq t_0)\) di mana \(t_0\) adalah nilai observasi.
Ini adalah eksperimen ikonik yang Fisher gunakan untuk memperkenalkan uji ini. Seseorang diminta menebak apakah teh atau susu yang dituang lebih dulu ke cangkir.
# ── Data: Fisher's Tea Tasting Experiment ────────────
teh_data <- matrix(
c(3, 1,
1, 3),
nrow = 2, byrow = TRUE,
dimnames = list(
"Dituang Pertama" = c("Susu", "Teh"),
"Ditebak Pertama" = c("Susu", "Teh")
)
)
cat("Tabel Kontingensi: Eksperimen Teh Susu\n")## Tabel Kontingensi: Eksperimen Teh Susu
## Ditebak Pertama
## Dituang Pertama Susu Teh Sum
## Susu 3 1 4
## Teh 1 3 4
## Sum 4 4 8
# ── Hitung distribusi Hipergeometrik ─────────────────
cat("\n--- Distribusi Hipergeometrik (semua nilai n11 yang mungkin) ---\n")##
## --- Distribusi Hipergeometrik (semua nilai n11 yang mungkin) ---
n11_obs <- teh_data[1,1] # = 3
dist_tabel <- data.frame(
n11 = 0:4,
Prob = dhyper(0:4, m = 4, n = 4, k = 4),
"P-value (satu sisi)" = phyper(q = -1 + (4:0), m = 4, n = 4, k = 4,
lower.tail = FALSE),
check.names = FALSE
)
dist_tabel$"P-value (satu sisi)" <- round(dist_tabel$"P-value (satu sisi)", 4)
dist_tabel$Prob <- round(dist_tabel$Prob, 4)
print(dist_tabel)## n11 Prob P-value (satu sisi)
## 1 0 0.0143 0.0143
## 2 1 0.2286 0.2429
## 3 2 0.5143 0.7571
## 4 3 0.2286 0.9857
## 5 4 0.0143 1.0000
# ── p-value untuk n11 ≥ 3 ────────────────────────────
p_val_fisher_manual <- sum(dhyper(n11_obs:4, m = 4, n = 4, k = 4))
cat(sprintf("\np-value manual [P(n11 ≥ 3)] = %.4f\n", p_val_fisher_manual))##
## p-value manual [P(n11 ≥ 3)] = 0.2429
##
## --- fisher.test() ---
##
## Fisher's Exact Test for Count Data
##
## data: teh_data
## p-value = 0.2429
## alternative hypothesis: true odds ratio is greater than 1
## 95 percent confidence interval:
## 0.3135693 Inf
## sample estimates:
## odds ratio
## 6.408309
Interpretasi: Dengan \(p\)-value \(= 0.243 > 0.05\), kita gagal tolak \(H_0\). Tidak ada bukti statistik yang cukup bahwa orang tersebut mampu membedakan teh dari susu dengan benar — hasilnya bisa saja terjadi secara kebetulan.
## --- Uji Fisher pada Data Merokok vs Kanker ---
# Untuk data besar, Fisher Exact masih bisa dijalankan
fisher_merokok <- fisher.test(merokok_data)
cat(sprintf("Odds Ratio (Fisher) : %.4f\n", fisher_merokok$estimate))## Odds Ratio (Fisher) : 2.9716
## 95% CI OR : (1.7556, 5.2107)
## p-value : 1.476303e-05
Setelah mengetahui bahwa hubungan ada (dari uji chi-square), pertanyaan berikutnya adalah: sel mana yang paling berkontribusi pada ketidakindependenan itu? Residual menjawab pertanyaan ini dengan mengukur selisih antara frekuensi observasi dan ekspektasi.
\[e_{ij} = \frac{n_{ij} - \hat{\mu}_{ij}}{\sqrt{\hat{\mu}_{ij}}}\]
Perhatikan bahwa \(\sum_i \sum_j e_{ij}^2 = X^2\) — sehingga setiap sel berkontribusi sebesar \(e_{ij}^2\) pada nilai chi-square total.
Pearson residual memiliki kelemahan: variansnya tidak selalu 1. Standardized residual memperbaiki ini:
\[e_{ij}^{std} = \frac{n_{ij} - \hat{\mu}_{ij}}{\sqrt{\hat{\mu}_{ij}(1 - p_{i+})(1 - p_{+j})}}\]
Standardized residual mengikuti distribusi Normal Baku \(N(0,1)\) secara asimtotis.
Aturan praktis: Nilai mutlak standardized residual \(> 2\) (atau \(> 3\) untuk tabel besar) mengindikasikan sel tersebut mengalami dependensi yang mencolok — artinya sel itu sangat berbeda dari yang diharapkan jika H₀ benar.
# ── Residual dari chisq.test() ───────────────────────
chi_res <- chisq.test(merokok_data, correct = FALSE)
cat("=== PEARSON RESIDUAL ===\n")## === PEARSON RESIDUAL ===
## Kanker Paru
## Merokok Cases Control
## Ya 0.7346 -0.7346
## Tidak -3.0042 3.0042
cat(sprintf("Kontribusi terhadap X²: %.4f (total X² = %.4f)\n",
sum(pearson_res^2), chi_res$statistic))## Kontribusi terhadap X²: 19.1292 (total X² = 19.1292)
##
## === STANDARDIZED (ADJUSTED) RESIDUAL ===
## Kanker Paru
## Merokok Cases Control
## Ya 4.3737 -4.3737
## Tidak -4.3737 4.3737
##
## --- Interpretasi ---
for(i in rownames(merokok_data)) {
for(j in colnames(merokok_data)) {
r <- round(std_res[i, j], 2)
arah <- ifelse(r > 0, "LEBIH BANYAK", "LEBIH SEDIKIT")
cat(sprintf("Sel [%s, %s]: std_res = %6.2f → %s dari ekspektasi %s\n",
i, j, r, arah, ifelse(abs(r) > 2, "⚠️ SIGNIFIKAN" , "✓ normal")))
}
}## Sel [Ya, Cases]: std_res = 4.37 → LEBIH BANYAK dari ekspektasi ⚠️ SIGNIFIKAN
## Sel [Ya, Control]: std_res = -4.37 → LEBIH SEDIKIT dari ekspektasi ⚠️ SIGNIFIKAN
## Sel [Tidak, Cases]: std_res = -4.37 → LEBIH SEDIKIT dari ekspektasi ⚠️ SIGNIFIKAN
## Sel [Tidak, Control]: std_res = 4.37 → LEBIH BANYAK dari ekspektasi ⚠️ SIGNIFIKAN
# Ubah matrix ke data frame untuk ggplot
library(tidyr)
std_res_df <- as.data.frame(as.table(std_res))
colnames(std_res_df) <- c("Merokok", "Kelompok", "Residual")
ggplot(std_res_df, aes(x = Kelompok, y = Merokok, fill = Residual)) +
geom_tile(color = "white", linewidth = 1.2) +
geom_text(aes(label = sprintf("%.2f", Residual)), size = 6, fontface = "bold") +
scale_fill_gradient2(
low = "#e74c3c",
mid = "white",
high = "#2980b9",
midpoint = 0,
name = "Std. Residual"
) +
labs(
title = "Heatmap Standardized Residual",
subtitle = "Merokok vs Kanker Paru-Paru",
x = "Kelompok (Kanker Paru)",
y = "Status Merokok"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5, color = "gray50"),
panel.grid = element_blank()
)Standardized Residual: sel dengan |residual| > 2 berwarna lebih intens
Interpretasi: Sel [Merokok Ya, Cases] dan [Merokok Tidak, Control] memiliki residual positif besar — lebih banyak dari ekspektasi. Sel [Merokok Ya, Control] dan [Merokok Tidak, Cases] memiliki residual negatif besar — lebih sedikit dari ekspektasi. Pola ini mengkonfirmasi asosiasi positif yang kuat antara merokok dan kanker paru.
Ketika uji chi-square signifikan pada tabel \(I \times J\) (dengan \(J > 2\)), kita tahu ada dependensi — tetapi di mana? Partisi chi-square memecah statistik \(G^2\) total menjadi \((I-1)(J-1)\) komponen independen, masing-masing bersesuaian dengan tabel \(2 \times 2\) yang lebih kecil.
Teori Lancaster-Irwin: Statistik chi-square untuk tabel \(I \times J\) selalu dapat dipecah menjadi komponen-komponen sebanyak derajat bebasnya, di mana setiap komponen bersesuaian dengan uji independensi pada tabel \(2 \times 2\) yang dibentuk dari tabel induk.
Data dari Agresti (2019, Table 2.4): hubungan antara jenis kelamin dan afiliasi partai politik.
# ── Data Gender vs Partai Politik ────────────────────
partai_data <- matrix(
c(495, 272, 590,
330, 265, 498),
nrow = 2, byrow = TRUE,
dimnames = list(
Gender = c("Female", "Male"),
Partai = c("Democrat", "Republican", "Independent")
)
)
cat("Tabel Kontingensi: Gender vs Afiliasi Partai\n")## Tabel Kontingensi: Gender vs Afiliasi Partai
addmargins(partai_data) |>
kable(caption = "Distribusi Gender per Afiliasi Partai") |>
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) |>
row_spec(0, bold = TRUE, background = "#2980b9", color = "white")| Democrat | Republican | Independent | Sum | |
|---|---|---|---|---|
| Female | 495 | 272 | 590 | 1357 |
| Male | 330 | 265 | 498 | 1093 |
| Sum | 825 | 537 | 1088 | 2450 |
## === UJI TOTAL (Tabel 2 × 3) ===
chi_total <- chisq.test(partai_data, correct = FALSE)
cat(sprintf("X² total = %.4f, df = %d, p = %.4f\n",
chi_total$statistic, chi_total$parameter, chi_total$p.value))## X² total = 12.5693, df = 2, p = 0.0019
G2_total <- 2 * sum(partai_data * log(partai_data / chi_total$expected))
cat(sprintf("G² total = %.4f, df = %d, p = %.4f\n",
G2_total,
chi_total$parameter,
1 - pchisq(G2_total, df = chi_total$parameter)))## G² total = 12.6009, df = 2, p = 0.0018
##
## → Signifikan pada α = 5%. Tapi PARTAI MANA yang berbeda?
##
## === PARTISI 1: Democrat vs Republican ===
partisi1 <- partai_data[, c("Democrat", "Republican")]
chi1 <- chisq.test(partisi1, correct = FALSE)
G2_1 <- 2 * sum(partisi1 * log(partisi1 / chi1$expected))
cat(sprintf("G² = %.4f, df = 1, p = %.4f\n", G2_1,
1 - pchisq(G2_1, df = 1)))## G² = 11.5357, df = 1, p = 0.0007
## → SIGNIFIKAN ✓
##
## === PARTISI 2: (Dem+Rep) vs Independent ===
partisi2 <- cbind(
"Dem+Rep" = rowSums(partai_data[, c("Democrat", "Republican")]),
"Independent" = partai_data[, "Independent"]
)
chi2 <- chisq.test(partisi2, correct = FALSE)
G2_2 <- 2 * sum(partisi2 * log(partisi2 / chi2$expected))
cat(sprintf("G² = %.4f, df = 1, p = %.4f\n", G2_2,
1 - pchisq(G2_2, df = 1)))## G² = 1.0652, df = 1, p = 0.3020
## → Tidak signifikan
cat(sprintf("\nVerifikasi additivitas: G²₁ + G²₂ = %.4f ≈ G²total = %.4f\n",
G2_1 + G2_2, G2_total))##
## Verifikasi additivitas: G²₁ + G²₂ = 12.6009 ≈ G²total = 12.6009
Kesimpulan Partisi: - Democrat vs Republican: Perbedaan antara Female dan Male signifikan (\(G^2 = 11.54\), \(p < 0.001\)). Female lebih cenderung memilih Democrat dibanding Male. - (Dem+Rep) vs Independent: Perbedaan ini tidak signifikan (\(G^2 = 1.07\), \(p = 0.30\)). Proporsi yang memilih Independent relatif sama antara Female dan Male.
Kesimpulan: Gender terutama membedakan preferensi Democrat vs Republican, bukan keputusan untuk menjadi Independent.
Mari kita buat studi kasus baru dengan data simulasi yang lebih kaya untuk mengintegrasikan semua konsep.
set.seed(42)
jenis_kelamin <- c(rep("Laki-laki", 250), rep("Perempuan", 250))
lulus_status <- c(
sample(c("Tepat Waktu", "Terlambat"), 250, replace = TRUE, prob = c(0.60, 0.40)),
sample(c("Tepat Waktu", "Terlambat"), 250, replace = TRUE, prob = c(0.75, 0.25))
)
df_mahasiswa <- data.frame(
JenisKelamin = jenis_kelamin,
Status = lulus_status
)
# FIX UTAMA
df_mahasiswa$JenisKelamin <- factor(df_mahasiswa$JenisKelamin,
levels = c("Laki-laki", "Perempuan"))
df_mahasiswa$Status <- factor(df_mahasiswa$Status,
levels = c("Tepat Waktu", "Terlambat"))
tabel_mhs <- table(df_mahasiswa$JenisKelamin, df_mahasiswa$Status)
addmargins(tabel_mhs)##
## Tepat Waktu Terlambat Sum
## Laki-laki 146 104 250
## Perempuan 191 59 250
## Sum 337 163 500
prop_df <- as.data.frame(prop.table(tabel_mhs, 1)) |>
rename(JenisKelamin = Var1, Status = Var2, Proporsi = Freq)
ggplot(prop_df, aes(x = JenisKelamin, y = Proporsi, fill = Status)) +
geom_bar(stat = "identity", position = "dodge", width = 0.6, color = "white") +
geom_text(
aes(label = scales::percent(Proporsi, accuracy = 0.1)),
position = position_dodge(width = 0.6),
vjust = -0.4, size = 4, fontface = "bold"
) +
scale_fill_manual(values = c("Tepat Waktu" = "#2980b9", "Terlambat" = "#e74c3c")) +
scale_y_continuous(labels = scales::percent_format(), limits = c(0, 1)) +
labs(
title = "Proporsi Kelulusan berdasarkan Jenis Kelamin",
subtitle = "Data simulasi 500 mahasiswa",
x = "Jenis Kelamin",
y = "Proporsi",
fill = "Status Kelulusan"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5),
plot.subtitle = element_text(hjust = 0.5, color = "gray50"),
legend.position = "top"
)Barplot proporsi kelulusan per jenis kelamin
freq_df <- as.data.frame(tabel_mhs) |>
rename(JenisKelamin = Var1, Status = Var2, Frekuensi = Freq)
ggplot(freq_df, aes(x = Status, y = JenisKelamin, fill = Frekuensi)) +
geom_tile(color = "white", linewidth = 2) +
geom_text(aes(label = Frekuensi), size = 8, fontface = "bold", color = "white") +
scale_fill_gradient(low = "#85c1e9", high = "#1a5276", name = "Frekuensi") +
labs(
title = "Heatmap Frekuensi: Jenis Kelamin vs Kelulusan",
x = "Status Kelulusan",
y = "Jenis Kelamin"
) +
theme_minimal(base_size = 13) +
theme(
plot.title = element_text(face = "bold", hjust = 0.5),
panel.grid = element_blank()
)Heatmap frekuensi observasi
Berikut adalah kerangka besar dari seluruh materi yang telah kita pelajari:
Ringkasan alur analisis data kategori
Berdasarkan seluruh rangkaian analisis yang telah dilakukan pada data case-control Agresti (2019):
| Aspek | Temuan | Implikasi |
|---|---|---|
| Beda Proporsi | \(BP = 0.054\) (95% CI: jelas \(\neq 0\)) | Proporsi kasus berbeda signifikan |
| Risiko Relatif | \(RR \approx 1.06\) | Perokok sedikit lebih sering jadi kasus |
| Odds Ratio | \(OR \approx 2.97\) | Odds perokok terkena kanker 3× lebih tinggi |
| Uji Chi-Square | \(X^2 \gg 3.84\), \(p \ll 0.05\) | Tolak \(H_0\): ada asosiasi nyata |
| Residual | Semua sel signifikan | Asosiasi berlaku di semua sel |
Pesan Kunci:
Analisis data kategori bukan sekadar menghitung angka — ia adalah alat untuk menceritakan hubungan antar variabel secara kuantitatif dan terstruktur. Dari tabel sederhana, kita bisa menggali: seberapa kuat hubungannya (OR, RR), apakah hubungan itu nyata secara statistik (Chi-Square, Fisher), di sel mana ia paling mencolok (Residual), dan apakah ada kategori spesifik yang mendorong hubungan itu (Partisi Chi-Square).
Cara data dikumpulkan mempengaruhi ukuran asosiasi mana yang paling tepat digunakan:
| Desain | Ciri | Ukuran Asosiasi Tepat |
|---|---|---|
| Prospektif / Kohort | Mulai dari paparan, ikuti ke depan | RR dan OR |
| Retrospektif / Kasus-Kontrol | Mulai dari outcome, telusuri ke belakang | Hanya OR (RR tidak bisa langsung) |
| Cross-Sectional | Snapshot satu waktu | OR, RR (dengan hati-hati) |
Data merokok-kanker di atas adalah case-control study (retrospektif), sehingga Odds Ratio adalah ukuran yang paling tepat dan dapat diinterpretasikan langsung. —
Analisis ini bertujuan untuk menguji hubungan antar variabel kategorik menggunakan tabel kontingensi dua arah.
tabel <- matrix(c(688,650,21,59), nrow=2, byrow=TRUE)
rownames(tabel) <- c("Smoker","Non-Smoker")
colnames(tabel) <- c("Cancer","Control")
tabel## Cancer Control
## Smoker 688 650
## Non-Smoker 21 59
## [1] 0.5142003
## [1] 0.2625
##
## 1-sample proportions test with continuity correction
##
## data: 688 out of 1338, null probability 0.5
## X-squared = 1.0232, df = 1, p-value = 0.3118
## alternative hypothesis: true p is not equal to 0.5
## 95 percent confidence interval:
## 0.4870445 0.5412736
## sample estimates:
## p
## 0.5142003
##
## 1-sample proportions test with continuity correction
##
## data: 21 out of 80, null probability 0.5
## X-squared = 17.113, df = 1, p-value = 3.523e-05
## alternative hypothesis: true p is not equal to 0.5
## 95 percent confidence interval:
## 0.1733064 0.3748263
## sample estimates:
## p
## 0.2625
## [1] 0.2517003
## [1] 1.958858
## [1] 2.973773
Interpretasi: RR > 1 dan OR > 1 menunjukkan bahwa perokok memiliki risiko lebih tinggi terkena kanker paru.
##
## 2-sample test for equality of proportions with continuity correction
##
## data: c(688, 21) out of c(1338, 80)
## X-squared = 18.136, df = 1, p-value = 2.057e-05
## alternative hypothesis: two.sided
## 95 percent confidence interval:
## 0.1450106 0.3583900
## sample estimates:
## prop 1 prop 2
## 0.5142003 0.2625000
##
## Pearson's Chi-squared test with Yates' continuity correction
##
## data: tabel
## X-squared = 18.136, df = 1, p-value = 2.057e-05
##
## Log likelihood ratio (G-test) test of independence without correction
##
## data: tabel
## G = 19.878, X-squared df = 1, p-value = 8.254e-06
##
## Fisher's Exact Test for Count Data
##
## data: tabel
## p-value = 1.476e-05
## alternative hypothesis: true odds ratio is not equal to 1
## 95 percent confidence interval:
## 1.755611 5.210711
## sample estimates:
## odds ratio
## 2.971634
Semua uji menghasilkan p-value < 0.05, sehingga:
Terdapat hubungan yang signifikan antara kebiasaan merokok dan kanker paru. Perokok memiliki risiko jauh lebih tinggi dibandingkan non-perokok.
tabel2 <- matrix(c(495,272,590,330,265,498), nrow=2, byrow=TRUE)
rownames(tabel2) <- c("Female","Male")
colnames(tabel2) <- c("Democrat","Republican","Independent")
tabel2## Democrat Republican Independent
## Female 495 272 590
## Male 330 265 498
## Democrat Republican Independent
## Female 456.949 297.4322 602.6188
## Male 368.051 239.5678 485.3812
##
## Pearson's Chi-squared test
##
## data: tabel2
## X-squared = 12.569, df = 2, p-value = 0.001865
## Democrat Republican Independent
## Female 3.272365 -2.498557 -1.032199
## Male -3.272365 2.498557 1.032199
Interpretasi: Nilai |residual| > 2 menunjukkan sel yang berkontribusi besar terhadap hubungan.
##
## Pearson's Chi-squared test with Yates' continuity correction
##
## data: part1
## X-squared = 11.178, df = 1, p-value = 0.0008279
Kategori yang paling berkontribusi adalah:
Agresti, A. (2019). An Introduction to Categorical Data Analysis (3rd ed.). John Wiley & Sons.
Agresti, A. (2013). Categorical Data Analysis (3rd ed.). John Wiley & Sons.
R Core Team (2024). R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria.