Data Analyst › สถิติสำหรับ DA

การทดสอบสมมติฐานและ p-value

ประมาณ 30 นาที

เป้าหมายของบท

  • เข้าใจ null hypothesis (H0) กับ alternative hypothesis (H1) และตรรกะของการ "ทดสอบเพื่อหักล้าง H0"
  • ทำ one-sample t-test เพื่อเทียบค่าเฉลี่ยตัวอย่างกับค่าที่มีคนอ้าง
  • ทำ two-sample t-test (Welch's) เพื่อเทียบค่าเฉลี่ยระหว่างสองกลุ่ม
  • อ่านและตีความ p-value ให้ถูกต้อง (ไม่ใช่ "ความน่าจะเป็นที่ H0 เป็นจริง")

ทำไมต้องมี “การทดสอบสมมติฐาน”

บทที่แล้วเราเรียนรู้ว่าค่าเฉลี่ยจากตัวอย่างมีความไม่แน่นอน จึงต้องรายงานเป็นช่วง (confidence interval) แทนที่จะเป็นตัวเลขเดียว คำถามต่อไปที่พบบ่อยในงานวิเคราะห์คือ: “มีคนอ้างตัวเลขบางอย่างมา (เช่น ยอดขายเฉลี่ยคือ X บาท) ข้อมูลตัวอย่างที่เรามีขัดแย้ง กับคำกล่าวนั้นไหม” หรือ “สองกลุ่มนี้ค่าเฉลี่ยต่างกันจริงหรือแค่ต่างกันเพราะสุ่มตัวอย่างมาไม่เหมือนกัน” — การทดสอบสมมติฐาน (hypothesis testing) คือกรอบการคิดที่ตอบคำถามแบบนี้อย่างเป็นระบบ

null hypothesis (H0) กับ alternative hypothesis (H1)

ทุกการทดสอบสมมติฐานเริ่มจากตั้งสมมติฐานคู่หนึ่ง:

  • H0 (null hypothesis) — สมมติฐานที่บอกว่า “ไม่มีความแตกต่าง / ไม่มีผล” เช่น “ยอดขายเฉลี่ย = 5000 บาท” หรือ “ค่าเฉลี่ย ของสองกลุ่มเท่ากัน” นี่คือสิ่งที่เราพยายามหาหลักฐานมาหักล้าง ไม่ใช่สิ่งที่เราอยากพิสูจน์
  • H1 (alternative hypothesis) — สมมติฐานตรงข้าม เช่น “ยอดขายเฉลี่ย ≠ 5000 บาท” หรือ “ค่าเฉลี่ยของสองกลุ่มไม่เท่ากัน”

ตรรกะสำคัญคือ: เราไม่มีทาง “พิสูจน์” ว่า H0 เป็นจริงได้ 100% ทำได้แค่ดูว่าข้อมูลที่มีมีหลักฐานเพียงพอที่จะปฏิเสธ H0 หรือไม่ ถ้าไม่มีหลักฐานเพียงพอ เราจะพูดว่า “fail to reject H0” (ไม่ปฏิเสธ H0) ไม่ใช่ “H0 เป็นจริงแน่นอน”

One-sample t-test — เทียบค่าเฉลี่ยตัวอย่างกับค่าที่มีคนอ้าง

สมมติผู้บริหารอ้างว่า ยอดออเดอร์เฉลี่ยของร้านคือ 5000 บาท ทีมอยากตรวจสอบด้วยตัวอย่าง 200 ออเดอร์ — ใช้ตัวอย่างชุด เดียวกับบทที่แล้วเป๊ะๆ (seed=42, size=200, replace=False จากยอดขายต่อออเดอร์ที่เสร็จสมบูรณ์) เพื่อให้ผลลัพธ์ตรงกันทุกครั้ง:

import pandas as pd
import numpy as np
from scipy import stats

orders = pd.read_csv("data/orders.csv")
order_items = pd.read_csv("data/order_items.csv")

merged = order_items.merge(orders, on='order_id')
completed = merged[merged['status'] == 'completed'].copy()
completed['line'] = completed['quantity'] * completed['unit_price']
order_rev = completed.groupby('order_id')['line'].sum()

rng = np.random.default_rng(42)
sample = rng.choice(order_rev.values, size=200, replace=False)

t_stat, p_value = stats.ttest_1samp(sample, 5000)

ในที่นี้ H0 คือ “ค่าเฉลี่ยจริง = 5000” ส่วน H1 คือ “ค่าเฉลี่ยจริง ≠ 5000” — ผลที่ได้: t_stat ≈ 1.908, p_value ≈ 0.0578

ที่ระดับนัยสำคัญ (significance level) มาตรฐาน α = 0.05: เพราะ p_value (0.0578) > α (0.05) เราจึง fail to reject H0 — ข้อมูลตัวอย่างนี้ไม่ได้ขัดแย้งกับคำกล่าวที่ว่ายอดเฉลี่ยคือ 5000 บาทอย่างมีนัยสำคัญ

p-value คืออะไรกันแน่ (และไม่ใช่อะไร)

p-value คือ: ถ้า H0 เป็นจริงจริงๆ โอกาสที่จะสุ่มตัวอย่างมาแล้วได้ผลลัพธ์สุดขั้วเท่านี้หรือมากกว่านี้ คือเท่าไหร่

ในตัวอย่างข้างบน p_value ≈ 0.0578 แปลว่า “ถ้ายอดเฉลี่ยจริงเท่ากับ 5000 บาทจริงๆ โอกาสที่จะสุ่มตัวอย่าง 200 ออเดอร์มาแล้ว ได้ค่าเฉลี่ยที่ต่างจาก 5000 มากเท่ากับที่เราเห็น (หรือมากกว่า) คือ 5.78%”

ข้อควรระวังที่พบบ่อยที่สุด: อย่าอ่าน p_value = 0.0578 ว่า “มีโอกาส 94.2% ที่คำกล่าวของผู้บริหารเป็นจริง” — p-value ไม่ใช่ ความน่าจะเป็นที่ H0 เป็นจริง มันคือความน่าจะเป็นของข้อมูล ภายใต้สมมติฐานว่า H0 เป็นจริง ต่างหาก คนละคำถามกัน

สรุปการอ่านผลให้ถูก:

ผลลัพธ์ อ่านว่าอะไร อ่านผิดที่พบบ่อย
p < 0.05 (ปฏิเสธ H0) มีหลักฐานเพียงพอที่จะบอกว่าข้อมูลขัดแย้งกับ H0 อย่างมีนัยสำคัญ “พิสูจน์แล้วว่า H1 เป็นจริง 100%”
p ≥ 0.05 (fail to reject) ข้อมูลที่มีไม่พอจะหักล้าง H0 ได้ “พิสูจน์แล้วว่า H0 เป็นจริง 100%”

Two-sample t-test — เทียบค่าเฉลี่ยระหว่างสองกลุ่ม

อีกคำถามที่พบบ่อยกว่าคือเทียบค่าเฉลี่ยของ สองกลุ่ม เช่น “ลูกค้าที่มาจากช่องทาง organic กับ ads ใครสั่งซื้อเฉลี่ยต่อ ออเดอร์สูงกว่ากัน” ที่นี่ H0 คือ “ค่าเฉลี่ยของสองกลุ่มเท่ากัน”

import pandas as pd
from scipy import stats

orders = pd.read_csv("data/orders.csv")
order_items = pd.read_csv("data/order_items.csv")
customers = pd.read_csv("data/customers.csv")

completed = orders[orders['status'] == 'completed']
oc = completed.merge(customers[['customer_id', 'channel']], on='customer_id')
items = order_items.merge(oc[['order_id', 'channel']], on='order_id')
items['line'] = items['quantity'] * items['unit_price']
rev_by_order = items.groupby(['order_id', 'channel'])['line'].sum().reset_index()

organic = rev_by_order[rev_by_order['channel'] == 'organic']['line']   # n=3010, mean≈5405.2
ads = rev_by_order[rev_by_order['channel'] == 'ads']['line']           # n=2034, mean≈5015.77

t2, p2 = stats.ttest_ind(organic, ads, equal_var=False)

ผลที่ได้: t2 ≈ 1.8537, p2 ≈ 0.0639 — เพราะ p2 > 0.05 จึง fail to reject H0 เช่นกัน: ไม่มีหลักฐานเพียงพอที่จะบอกว่า สองช่องทางนี้มียอดสั่งซื้อเฉลี่ยต่างกันอย่างมีนัยสำคัญ แม้ตัวเลขดิบ (5405.2 vs 5015.77) จะดูต่างกันพอสมควรก็ตาม — นี่คือ เหตุผลที่ต้องทดสอบทางสถิติ ไม่ใช่ดูตัวเลขดิบแล้วสรุปเอง เพราะความต่างขนาดนี้อาจเกิดจากการสุ่มตัวอย่างมาไม่เท่ากันล้วนๆ ก็ได้

ทำไมต้องใช้ equal_var=False (Welch’s t-test)

scipy.stats.ttest_ind มีสองโหมด:

  • equal_var=True (ค่าเริ่มต้น) — Student’s t-test ดั้งเดิม สมมติว่าความแปรปรวนของสองกลุ่มเท่ากัน
  • equal_var=False — Welch’s t-test ไม่สมมติว่าความแปรปรวนเท่ากัน

ในทางปฏิบัติ เรามักไม่รู้ล่วงหน้าว่าความแปรปรวนของสองกลุ่มเท่ากันจริงไหม (และข้อมูลยอดขาย/รายได้มักมีความแปรปรวนต่างกัน ระหว่างกลุ่มด้วย) จึงแนะนำให้ใช้ equal_var=False เป็นค่าเริ่มต้นเสมอเมื่อไม่แน่ใจ — ปลอดภัยกว่าและแทบไม่มีข้อเสีย

ทำไม α ที่เลือกไว้ล่วงหน้าถึงสำคัญ

α = 0.05 เป็นธรรมเนียมที่ใช้กันแพร่หลาย แต่ไม่ใช่กฎเหล็กทางคณิตศาสตร์ — ถ้าทีมเลือกใช้ α = 0.10 แทน ผลการทดสอบ two-sample t-test ข้างบน (p2 ≈ 0.0639) จะกลายเป็น “มีนัยสำคัญ” ทันที (0.0639 < 0.10) ทั้งที่ข้อมูลไม่ได้เปลี่ยนเลย สักตัวเลขเดียว — นี่คือเหตุผลว่าทำไมควรกำหนด α ไว้ล่วงหน้าก่อนดูผล ไม่ใช่เลือก α ทีหลังเพื่อให้ได้ข้อสรุปที่อยากได้ (รูปแบบหนึ่งของ p-hacking) และทำไมผลลัพธ์ที่ p-value อยู่ใกล้เส้นแบ่ง (เช่น 0.049 vs 0.051) ไม่ควรถูกตีความว่าต่างกัน อย่างมีนัยสำคัญในทางปฏิบัติ

ตัวอย่างแก้โจทย์

กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้

ผู้บริหารอ้างว่ายอดออเดอร์เฉลี่ยคือ 5000 บาท — จริงไหม

ผู้บริหารอ้างว่ายอดออเดอร์เฉลี่ยของร้านอยู่ที่ 5000 บาท ทีมอยากตรวจสอบคำกล่าวนี้ด้วยตัวอย่าง 200 ออเดอร์ (สุ่มแบบเดียวกับบทที่แล้ว) ว่าข้อมูลตัวอย่างขัดแย้งกับคำกล่าวนี้อย่างมีนัยสำคัญหรือไม่

  1. ตั้งสมมติฐาน: H0 คือ "ค่าเฉลี่ยจริงของยอดออเดอร์ = 5000 บาท" (สมมติฐานว่าง ที่เราพยายามหาหลักฐานมาหักล้าง) ส่วน H1 คือ "ค่าเฉลี่ยจริง ≠ 5000 บาท" จากนั้นสร้างตัวอย่างเดียวกับบทที่แล้วเป๊ะๆ (seed 42, ขนาด 200, ไม่คืนที่)

    Python

ลูกค้าช่องทาง organic กับ ads ใครสั่งของแพงกว่ากันจริงไหม

ทีมการตลาดสงสัยว่าลูกค้าที่มาจากช่องทาง organic กับ ads มียอดสั่งซื้อเฉลี่ยต่อออเดอร์ต่างกันหรือไม่ เทียบด้วย two-sample t-test ระหว่างสองกลุ่ม

  1. เตรียมยอดขายต่อออเดอร์ (เฉพาะออเดอร์ที่เสร็จสมบูรณ์) พร้อมช่องทางของลูกค้าแต่ละออเดอร์ แล้วแยกเป็นสองกลุ่มตาม channel

    Python

เติมช่องว่าง

แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ

ยังไม่ได้ทำ

ทำ one-sample t-test ทดสอบว่ายอดออเดอร์เฉลี่ยของตัวอย่าง (สุ่มแบบเดียวกับบทที่แล้ว seed 42 ขนาด 200 ไม่คืนที่) ต่างจากคำกล่าวอ้างที่ว่า "ยอดเฉลี่ยคือ 5000 บาท" อย่างมีนัยสำคัญหรือไม่ เก็บค่าสถิติ t ไว้ในตัวแปร t_stat และ p-value ไว้ในตัวแปร p_value เติม ___ ให้ครบ

แบบฝึกท้ายบท

บทนี้จะนับว่าเสร็จเมื่อผ่านข้อพื้นฐานและประยุกต์ครบ ข้อท้าทายได้ดาวเพิ่ม

🟢 พื้นฐาน

🟢 พื้นฐานยังไม่ได้ทำ

คำนวณค่าสถิติ t ของ one-sample t-test ที่เทียบค่าเฉลี่ยของตัวอย่าง (สุ่มแบบเดียวกับบทที่แล้ว: seed 42, ขนาด 200, ไม่คืนที่ จากยอดขายต่อออเดอร์ที่เสร็จสมบูรณ์) กับค่าที่อ้างว่า μ = 5000 เก็บไว้ในตัวแปร t_stat

🟢 พื้นฐานยังไม่ได้ทำ

จาก one-sample t-test เดียวกับข้อที่แล้ว (ตัวอย่างเดิม เทียบกับ μ = 5000) เก็บ p-value ไว้ในตัวแปร p_value

🟢 พื้นฐานยังไม่ได้ทำ

จาก p-value ของข้อที่แล้ว ที่ระดับนัยสำคัญ α=0.05 เราปฏิเสธ H0 (ที่ว่ายอดเฉลี่ย = 5000) หรือไม่ เก็บผลเป็น boolean ไว้ในตัวแปร reject_h0

🟢 พื้นฐานยังไม่ได้ทำ

คำนวณยอดขายเฉลี่ยต่อออเดอร์ (เฉพาะออเดอร์ที่เสร็จสมบูรณ์) ของลูกค้าที่มาจากช่องทาง organic เก็บไว้ในตัวแปร mean_organic

🟡 ประยุกต์

🟡 ประยุกต์ยังไม่ได้ทำ

เหมือนข้อที่แล้ว แต่คำนวณยอดขายเฉลี่ยต่อออเดอร์ของลูกค้าที่มาจากช่องทาง ads แทน เก็บไว้ในตัวแปร mean_ads

🟡 ประยุกต์ยังไม่ได้ทำ

ทำ two-sample t-test แบบ Welch's (equal_var=False) เทียบยอดขายเฉลี่ยต่อออเดอร์ระหว่างกลุ่ม organic กับ ads เก็บค่าสถิติ t ไว้ในตัวแปร t2

🟡 ประยุกต์ยังไม่ได้ทำ

จาก two-sample t-test เดียวกับข้อที่แล้ว เก็บ p-value ไว้ในตัวแปร p2

🔴 ท้าทาย

🔴 ท้าทายยังไม่ได้ทำ

จาก p-value ของ two-sample t-test (ข้อที่แล้ว) ที่ระดับนัยสำคัญ α=0.05 มีหลักฐานเพียงพอไหมว่าลูกค้า organic สั่งซื้อเฉลี่ยต่อออเดอร์สูงกว่าลูกค้า ads อย่างมีนัยสำคัญ เก็บผลเป็น boolean ไว้ในตัวแปร is_significant

🔴 ท้าทายยังไม่ได้ทำ

ถ้าทีมเลือกใช้ระดับนัยสำคัญที่หลวมกว่าเดิมคือ α=0.10 (แทนที่จะเป็น 0.05) ผลการทดสอบ two-sample t-test ข้อ e7 จะกลายเป็น "มีนัยสำคัญ" หรือไม่ (p2 < 0.10) เก็บผลเป็น boolean ไว้ในตัวแปร sig_at_10pct — ตัวอย่างที่เป็นรูปธรรมว่าทำไมค่า α ที่เลือกไว้ล่วงหน้าถึงสำคัญ

Quiz

1. null hypothesis (H0) คืออะไรโดยหลักการ
2. p-value คือความน่าจะเป็นของอะไร
3. p < 0.05 แปลว่าอะไร
4. two-sample t-test แบบ equal_var=False (Welch's) ต่างจาก equal_var=True ยังไง
5. การไม่ปฏิเสธ H0 (fail to reject) แปลว่า H0 เป็นจริงแน่นอนไหม

สรุป

  • null hypothesis (H0) คือสมมติฐาน "ไม่มีผล/ไม่มีความแตกต่าง" ที่เราพยายามหาหลักฐานมาหักล้าง ไม่ใช่สิ่งที่อยากพิสูจน์
  • one-sample t-test (ttest_1samp) เทียบค่าเฉลี่ยตัวอย่างกับค่าที่อ้าง ส่วน two-sample t-test (ttest_ind, แนะนำ equal_var=False) เทียบค่าเฉลี่ยระหว่างสองกลุ่ม
  • p-value คือความน่าจะเป็นที่จะเห็นผลสุดขั้วเท่านี้หรือมากกว่า ถ้า H0 เป็นจริง ไม่ใช่ความน่าจะเป็นที่ H0 เป็นจริง และ fail to reject H0 ไม่ได้แปลว่า H0 ถูกพิสูจน์แล้ว

คำถามสัมภาษณ์

อธิบาย p-value ให้ผู้บริหารที่ไม่ได้เรียนสถิติฟัง โดยไม่ใช้คำว่า "null hypothesis"

อาจอธิบายแบบนี้: "สมมติว่าตัวเลขที่เราอยากตรวจสอบนั้นถูกต้องจริงๆ (เช่น ยอดเฉลี่ยคือ 5000 บาทจริง) ถ้าเราสุ่มตัวอย่างมาดูซ้ำๆ หลายๆ ครั้ง มีโอกาสกี่เปอร์เซ็นต์ที่จะได้ตัวอย่างที่ผลต่างจากตัวเลขนั้นมากเท่ากับหรือมากกว่าที่เราเห็นในครั้งนี้" ถ้าตัวเลขนี้ (p-value) ต่ำมาก แปลว่าสิ่งที่เราสังเกตเห็นไม่น่าจะเกิดจากความบังเอิญเฉยๆ ถ้าตัวเลขไม่ถูกต้องจริง — จุดสำคัญคือ p-value ไม่ได้บอกว่า "ตัวเลขที่อ้างมีโอกาสถูกกี่เปอร์เซ็นต์" มันตอบคำถามกลับด้านกัน

ทำไมการทดสอบทางสถิติที่ไม่ปฏิเสธ H0 ไม่ได้แปลว่าไม่มีความแตกต่างจริง

เพราะ "fail to reject H0" แปลว่าข้อมูลที่มีอยู่ไม่มากพอจะให้หลักฐานที่แข็งแรงพอจะหักล้าง H0 ได้ ไม่ได้แปลว่า H0 ถูกพิสูจน์ ว่าเป็นจริง — สาเหตุที่พบบ่อยคือขนาดตัวอย่างเล็กเกินไป (statistical power ไม่พอ) หรือผลต่างที่แท้จริงมีขนาดเล็กจนตรวจจับ ไม่ได้ด้วยตัวอย่างขนาดนี้ ความแตกต่างจริงอาจยังมีอยู่ แค่ข้อมูลที่เก็บมายังไม่พอจะเห็นมันชัดเจน

ถ้า p-value = 0.049 กับ 0.051 ควรตีความต่างกันมากไหม

ไม่ควรตีความต่างกันมาก — เส้นแบ่ง α=0.05 เป็นธรรมเนียมที่ตกลงกันไว้ล่วงหน้า ไม่ใช่กำแพงที่มีความหมายพิเศษทางสถิติจริงๆ ผลลัพธ์ทั้งสองแทบไม่ต่างกันเลยในแง่ความแข็งแรงของหลักฐาน การตัดสินใจทางธุรกิจที่เปลี่ยนขั้วสุดโต่ง (ทำ/ไม่ทำ) แค่เพราะ p-value ต่างกัน 0.002 จึงเป็นความเสี่ยงที่ควรระวัง ควรดูขนาดของผลต่าง (effect size) และบริบทประกอบด้วยเสมอ ไม่ใช่ยึด เกณฑ์ 0.05 แบบทื่อๆ