Sedikit angka di dunia riset dikutip selaras p-value — dan sedikit juga yang disalahartikan seluas itu. Bab berita menuliskannya sebagai “terbukti signifikan”, laporan internal menumpukinya sebagai stempel kepastian, padahal makna aslinya jauh lebih sempit. Pada chapter ini kita definisikan p-value dengan presisi, menghitungnya langsung dari statistik uji, lalu membongkar dua kesalahpahaman terbesarnya: bahwa p-value mengukur peluang hipotesis nol benar, dan bahwa p-value kecil berarti efeknya besar. Setelah chapter ini, membaca kalimat “p < 0,05” tidak akan sama lagi.

Definisi yang Tepat #

p-value adalah peluang — dengan syarat H0 benar — memperoleh data yang sama ekstremnya atau lebih ekstrem lagi daripada yang kita lihat. Perhatikan arah syaratnya:

text
p-value = P(data se-ekstrem ini atau lebih | H0 benar)

Yang diberi peluang adalah data, bukan hipotesis. p-value tidak pernah menyatakan “peluang H0 benar”, karena pengondisinya justru menganggap H0 benar sejak awal. Ia juga bukan ukuran besar efek: dua penelitian dengan efek yang sama besar bisa menghasilkan p-value yang jauh berbeda, seperti akan kita buktikan sebentar lagi. Karena itu tabel berikut layak dihafal:

PernyataanStatus
“Peluang mendapat data seekstrem ini bila H0 benar adalah p”Benar
“Peluang H0 benar adalah p”Keliru — membalik arah syarat
“p kecil berarti efeknya besar”Keliru — p sensitif terhadap n
“p ≥ 0,05 berarti tidak ada efek”Keliru — hanya berarti bukti kurang

Menghitung p-value dari Statistik Uji #

Bab sebelumnya menghasilkan t hitung −4,0452 untuk uji porsi kopi. p-value-nya adalah luas kedua ekor kurva-t (df = 15) di luar ±4,0452. Fungsi t.sf (survival function) menghitung satu ekor kanan; untuk uji dua sisi kita menggandakannya:

python
import numpy as np
from scipy.stats import t, ttest_1samp

porsi = np.array([
    296, 302, 288, 291, 299, 285, 293, 306,
    297, 290, 288, 301, 294, 287, 295, 292,
])
hasil = ttest_1samp(porsi, popmean=300.0)
p_manual = 2 * t.sf(abs(hasil.statistic), df=porsi.size - 1)

print(f"t hitung (scipy) : {hasil.statistic:.4f}")
print(f"p-value (scipy)  : {hasil.pvalue:.6f}")
print(f"p-value (manual) : {p_manual:.6f}")

Output yang Dihasilkan:

text
t hitung (scipy) : -4.0452
p-value (scipy)  : 0.001058
p-value (manual) : 0.001058

Bacaannya begini: bila rata-rata porsi memang benar 300 ml, peluang mengukur 16 gelas dan mendapat rata-rata sampel sejauh 294 ml atau lebih jauh lagi hanya 0,1%. Data kita termasuk sangat jarang di dunia tempat H0 benar — kelangkaan itulah yang mendorong kita menolak H0. Perhitungan manual dengan 2 * t.sf(...) menghasilkan angka yang sama persis dengan scipy, karena memang itulah yang scipy hitung di balik layar.

p-value Tidak Mengukur Besarnya Efek #

Sekarang eksperimen yang membongkar anggapan keliru kedua. Dua peneliti mengukur porsi kopi dari proses yang sama-sama “menyimpang 2,5 ml dari klaim” — satu dengan 12 gelas, satu dengan 160 gelas:

python
import numpy as np
from scipy.stats import ttest_1samp

rng = np.random.default_rng(42)
mu0 = 300.0
kecil = rng.normal(297.5, 8.0, size=12)
besar = rng.normal(297.5, 8.0, size=160)

for nama, data in [("n = 12 ", kecil), ("n = 160", besar)]:
    hasil = ttest_1samp(data, popmean=mu0)
    print(f"{nama}: xbar = {data.mean():.2f}, "
          f"selisih = {data.mean() - mu0:+.2f} ml, p-value = {hasil.pvalue:.4f}")

Output yang Dihasilkan:

text
n = 12 : xbar = 296.37, selisih = -3.63 ml, p-value = 0.1291
n = 160: xbar = 297.12, selisih = -2.88 ml, p-value = 0.0000

Mari kita telaah hasil ini:

  1. n = 12 → p = 0,1291: selisih 3,63 ml terlalu kecil dibanding derau pengukuran; sampel sekecil ini tidak cukup bukti untuk melawan klaim.
  2. n = 160 → p = 0,0000: selisihnya bahkan lebih kecil (2,88 ml — sekitar 1% isi gelas), tetapi dengan 160 gelas, selisih sekecil itu pun konsisten terlihat dan p-value jatuh di bawah 0,0001.
  3. Pelajarannya: p-value mencampur dua bahan — jarak rata-rata dari klaim dan ketelitian pengukuran (yang naik dengan n). p-value kecil hanya berkata “kita cukup yakin ada selisih”; besar selisih itu harus dibaca dari data, dan kepentingannya dinilai dengan ukuran efek pada chapter Ukuran Efek.

Ambang 0,05: Konvensi, Bukan Hukum Alam #

Dari mana angka 0,05? Bukan dari alam — ia warisan konvensi yang dipopulerkan R.A. Fisher pada era tabel statistik tercetak, lalu mengeras menjadi kebiasaan publikasi. Simulasi berikut memperlihatkan sisi hikmahnya: ketika H0 selalu benar dan kita menguji berulang kali, berapa banyak “penemuan” palsu yang muncul?

python
import numpy as np
from scipy.stats import ttest_1samp

rng = np.random.default_rng(42)
alpha = 0.05
signifikan = 0
percobaan = 2000
for _ in range(percobaan):
    sampel = rng.normal(300.0, 8.0, size=16)
    if ttest_1samp(sampel, popmean=300.0).pvalue < alpha:
        signifikan += 1

print(f"H0 selalu benar: {signifikan}/{percobaan} uji signifikan "
      f"({signifikan / percobaan:.1%})")

Output yang Dihasilkan:

text
H0 selalu benar: 111/2000 uji signifikan (5.5%)

Dari 2.000 uji pada populasi yang benar-benar sesuai H0, 111 uji (5,5%) tetap menghasilkan p < 0,05. Ini bukan galat simulasi — p-value memang berdistribusi seragam di bawah H0, sehingga sekitar 5% uji pasti “signifikan” tanpa ada efek apa pun. Angka 5% inilah laju positif palsu yang kita sepakati menanggungnya sebagai harga pengambilan keputusan dari sampel.

Rangkuman #

  • p-value = P(data se-ekstrem ini atau lebih | H0 benar): peluang menempel pada data, bukan pada hipotesis.
  • Untuk uji dua sisi, p = 2 × t.sf(|t|, df); hasilnya identik dengan keluaran ttest_1samp.
  • p-value mencampur besar selisih dan ketelitian pengukuran: selisih 2,88 ml bisa “sangat signifikan” hanya karena n besar — p kecil tidak berarti efek besar.
  • Ambang 0,05 adalah konvensi dengan arti pasti: bila H0 selalu benar, sekitar 5% uji akan tampak signifikan (simulasi kita: 5,5%).

Menolak H0 yang sebenarnya benar adalah satu jenis kesalahan; gagal menolak H0 yang sebenarnya salah adalah jenis lainnya. Dua-duanya punya nama, laju, dan harga keputusannya sendiri — pokok bahasan chapter Kesalahan Tipe I dan Tipe II.

Referensi #

  1. SciPy — scipy.stats: Statistical Functions
  2. statsmodels — Documentation