การกระจายของข้อมูล
ประมาณ 25 นาที
เป้าหมายของบท
- อ่านรูปร่างการกระจายของข้อมูล (สมมาตร vs เบ้ซ้าย/เบ้ขวา) ด้วยตา และวัดด้วย skewness
- คำนวณ z-score และใช้หา outlier ด้วยกฎ
\|z\| > 3 - เข้าใจว่ากฎ z-score ตั้งอยู่บนสมมติฐานว่าข้อมูลใกล้เคียงการแจกแจงปกติ ถ้าข้อมูลเบ้มากอาจตีความผิดได้
ทำไมต้องดู “รูปร่าง” ของข้อมูล ไม่ใช่แค่ตัวเลขสรุป
บทที่แล้วเรียนเรื่อง mean/median/std/IQR ไปแล้ว — ตัวเลขพวกนี้สรุปข้อมูลเป็น “จุดเดียว” ได้ก็จริง แต่ข้อมูลชุดเดียวกันอาจมี mean/std เท่ากันทุกประการ ทั้งที่หน้าตาการกระจาย (distribution) ต่างกันคนละเรื่อง บทนี้จะเรียนวิธีอ่าน “รูปร่าง” ของข้อมูลเพิ่มอีกชั้นหนึ่ง ผ่านสองแนวคิด: skewness (ความเบ้) และ z-score
Skewness: ข้อมูลสมมาตรหรือเบ้
ข้อมูลที่ “สมมาตร” (symmetric) จะมีหางซ้าย-ขวายาวพอๆ กัน ส่วนข้อมูลที่ “เบ้” (skewed) จะมีหางข้างใดข้างหนึ่งยาวกว่า
import pandas as pd
products = pd.read_csv("data/products.csv")
products['price'].skew()
.skew() คืนตัวเลขเดียวที่บอกทั้งทิศทางและความรุนแรงของความเบ้:
| ค่า skew | ความหมาย |
|---|---|
| ใกล้ 0 | สมมาตรพอประมาณ |
| มากกว่า 0 (เบ้ขวา / right-skewed) | หางยาวไปทางขวา มีค่าสูงผิดปกติอยู่บ้าง → มักทำให้ mean > median |
| น้อยกว่า 0 (เบ้ซ้าย / left-skewed) | หางยาวไปทางซ้าย มีค่าต่ำผิดปกติอยู่บ้าง → มักทำให้ mean < median |
ข้อมูลยอดขาย/รายได้แทบทุกธุรกิจมักเบ้ขวา เพราะลูกค้าส่วนใหญ่ซื้อของราคาปกติ แต่มีบางออเดอร์ที่ใหญ่ผิดปกติคอยดึงหางออกไปทางขวา
z-score: ค่านี้ห่างจากค่าเฉลี่ยกี่ std
z-score แปลงค่าดิบให้เป็น “จำนวน standard deviation ที่ห่างจากค่าเฉลี่ย”:
z = (x - mean) / std
z = 0แปลว่าค่านั้นเท่ากับค่าเฉลี่ยพอดีz = 2แปลว่าค่านั้นสูงกว่าค่าเฉลี่ย 2 เท่าของ stdz = -1.5แปลว่าค่านั้นต่ำกว่าค่าเฉลี่ย 1.5 เท่าของ std
กฎที่ใช้กันบ่อยคือ |z| > 3 = ผิดปกติ (outlier) เพราะถ้าข้อมูลแจกแจงแบบปกติ (normal distribution) จริงๆ
โอกาสที่จะเจอค่าที่ห่างเกิน 3 std ทั้งสองด้านมีแค่ประมาณ 0.27% เท่านั้น (scipy.stats.norm.sf(3)*2*100) — ถือว่าหายากมาก
กับดัก: กฎ \|z\| > 3 ตั้งอยู่บนสมมติฐานว่าข้อมูลใกล้เคียง normal
ปัญหาคือ ข้อมูลยอดขาย/รายได้จริงมักไม่ใกล้เคียง normal distribution เลย — มันเบ้ขวาอย่างที่เห็นด้านบน
ลองดูยอดรายได้ต่อออเดอร์ (เฉพาะออเดอร์ที่ completed) ของร้านนี้:
import pandas as pd
orders = pd.read_csv("data/orders.csv")
order_items = pd.read_csv("data/order_items.csv")
completed = orders[orders['status'] == 'completed']
merged = order_items.merge(completed, on='order_id')
merged['line'] = merged['quantity'] * merged['unit_price']
order_rev = merged.groupby('order_id')['line'].sum()
order_rev.skew()
ข้อมูลนี้เบ้ขวารุนแรง (skew สูงกว่า 3) พอเอากฎ |z| > 3 มาใช้จริง กลับเจอสัดส่วนออเดอร์ที่ “ผิดปกติ” มากกว่าที่ทฤษฎีทำนายไว้หลายเท่า
เพราะข้อมูลจริงมีหางยาวกว่าการแจกแจงปกติมาก — ไม่ได้แปลว่าใช้ z-score ไม่ได้เลย แต่ต้องตีความอย่างระมัดระวัง
และควรเทียบกับวิธีอื่น (เช่น IQR จากบทที่แล้ว) ประกอบกันเสมอ ไม่เชื่อกฎเดียวแบบไม่คิด
ตัวอย่างแก้โจทย์
กด "ขั้นต่อไป" ทีละขั้น ลองรันและแก้โค้ดในแต่ละขั้นได้
เทียบความเบ้ของราคาสินค้า กับยอดรายได้ต่อออเดอร์
อยากรู้ว่าราคาสินค้ากับยอดรายได้ต่อออเดอร์ (เฉพาะออเดอร์ที่สำเร็จ) มีรูปร่างการกระจายแบบไหน สมมาตรหรือเบ้
หา skew ของราคาสินค้าก่อน (เหมือนที่เคยดู mean/median ใน Lesson 1 ที่ต่างกันมาก)
z-score ของออเดอร์ที่ยอดรายได้สูงที่สุด
อยากรู้ว่าออเดอร์ที่มียอดรายได้สูงที่สุด "ผิดปกติ" แค่ไหนเมื่อเทียบกับค่าเฉลี่ยของออเดอร์ทั้งหมด
สร้างยอดรายได้ต่อออเดอร์เหมือนตัวอย่างที่แล้ว แล้วหาค่าเฉลี่ยกับ std
เติมช่องว่าง
แทน ___ ด้วยโค้ดที่ถูกต้อง แล้วกดตรวจคำตอบ
เติมสูตร z-score (x - mean) / std ให้ครบ เพื่อหาว่าออเดอร์ที่ยอดรายได้สูงที่สุดมี z-score เท่าไร
เก็บไว้ในตัวแปร z_max เติม ___ ให้ครบ
แบบฝึกท้ายบท
บทนี้จะนับว่าเสร็จเมื่อผ่านข้อพื้นฐานและประยุกต์ครบ ข้อท้าทายได้ดาวเพิ่ม
🟢 พื้นฐาน
skewness ของราคาสินค้า (products['price']) เก็บไว้ในตัวแปร skew_price
สร้างยอดรายได้ต่อออเดอร์ (เฉพาะออเดอร์ที่ status == 'completed', line = quantity * unit_price
แล้ว groupby ตาม order_id) แล้วหา skewness ของยอดรายได้นี้ เก็บไว้ในตัวแปร skew_order_rev
ยอดรายได้ต่อออเดอร์ (สร้างเหมือน e2) เบ้ขวาไหม (skew > 0) เก็บผลบูลีนไว้ในตัวแปร is_right_skewed
คำนวณ z-score ของยอดรายได้ ทุกออเดอร์ (ไม่ใช่แค่ออเดอร์เดียว) ด้วยสูตร (x - mean) / std
ตั้งชื่อ Series ผลลัพธ์ว่า z แล้วเก็บไว้ในตัวแปร result
🟡 ประยุกต์
นับจำนวนออเดอร์ที่ \|z\| > 3 (จากยอดรายได้ต่อออเดอร์) เก็บไว้ในตัวแปร n_outliers_z
z-score สูงสุดในบรรดาออเดอร์ทั้งหมด (ปัด 2 ตำแหน่ง) เก็บไว้ในตัวแปร max_z
เทียบวิธีหา outlier สองแบบบนยอดรายได้ต่อออเดอร์เดียวกัน: วิธี IQR (fence = Q3 + 1.5*IQR)
กับวิธี z-score (\|z\| > 3) — วิธี IQR เจอ outlier มากกว่าวิธี z-score ไหม เก็บผลบูลีนไว้ในตัวแปร iqr_flags_more
นับจำนวนออเดอร์ที่ z > 2 (ดูด้านเดียว คือหางบนเท่านั้น ไม่รวม z < -2) เก็บไว้ในตัวแปร n_above_2sigma
🔴 ท้าทาย
ยอดรายได้ (บาท) ที่จุด 3 standard deviation เหนือค่าเฉลี่ย (mean + 3*std) เก็บไว้ในตัวแปร threshold_3sigma
เทียบ % ของออเดอร์จริงที่ \|z\| > 3 (ปัด 2 ตำแหน่ง เก็บไว้ในตัวแปร actual_pct) กับ % ทางทฤษฎีที่ควรเจอ
ถ้าข้อมูลเป็น normal distribution จริง (scipy.stats.norm.sf(3)*2*100 ปัด 2 ตำแหน่ง เก็บไว้ในตัวแปร theoretical_pct)
Quiz
สรุป
- skewness (
.skew()) บอกทิศทางและขนาดของความเบ้:skew > 0เบ้ขวา (มักทำให้ mean > median),skew < 0เบ้ซ้าย - z-score = (x - mean) / std บอกว่าค่าห่างจากค่าเฉลี่ยกี่เท่าของ std กฎที่ใช้บ่อยคือ
\|z\| > 3= ผิดปกติ - กฎ z-score ตั้งอยู่บนสมมติฐานว่าข้อมูลใกล้เคียง normal distribution — ข้อมูลยอดขาย/รายได้ที่เบ้ขวารุนแรงมักมีค่า |z|>3 บ่อยกว่าที่ทฤษฎีทำนายไว้มาก ควรใช้ร่วมกับวิธีอื่น (เช่น IQR) เสมอ
คำถามสัมภาษณ์
ทำไมกฎ \|z\| > 3 อาจใช้ไม่ได้ดีกับข้อมูลยอดขาย/รายได้ที่มักเบ้ขวา
กฎนี้อ้างอิงคุณสมบัติของ normal distribution ที่บอกว่าค่าซึ่งห่างจากค่าเฉลี่ยเกิน 3 std จะเจอได้แค่ประมาณ 0.27%
แต่ข้อมูลยอดขาย/รายได้จริงมักเบ้ขวารุนแรง (ลูกค้าส่วนใหญ่ซื้อของราคาปกติ แต่มีบางออเดอร์ใหญ่ผิดปกติดึงหางออกไป)
พอเอากฎนี้ไปใช้จริง จะเจอสัดส่วนที่ \|z\|>3 สูงกว่าทฤษฎีทำนายไว้มาก (ในข้อมูลชุดนี้สูงกว่าเกือบ 8.5 เท่า)
เพราะฉะนั้นควรใช้ z-score เป็นสัญญาณเบื้องต้น ไม่ใช่กฎตายตัว และควรเทียบกับวิธีอื่นประกอบเสมอ
นอกจาก z-score แล้ว มีวิธีอื่นหา outlier อะไรอีกบ้าง
วิธีที่ใช้บ่อยที่สุดอีกแบบคือ IQR method จาก Lesson ที่แล้ว (ค่าที่อยู่นอก Q1 - 1.5*IQR ถึง Q3 + 1.5*IQR)
ซึ่งทนทานต่อความเบ้ของข้อมูลมากกว่า z-score เพราะอิงตำแหน่ง (percentile) ไม่ใช่ค่าเฉลี่ย/std ที่ถูกดึงง่ายโดยค่าสุดขั้ว
นอกจากนี้ยังมีวิธีดูจาก domain knowledge ตรงๆ (เช่น รู้ว่าราคาสินค้าติดลบเป็นไปไม่ได้อยู่แล้ว)
หรือดูกราฟ (histogram/boxplot) ประกอบ ไม่พึ่งตัวเลขสรุปตัวเดียว