01ภาพรวมของกราฟนี้
ECDF วาดสัดส่วนของข้อมูลที่มีค่าน้อยกว่าหรือเท่ากับค่าที่กำหนด เป็นกราฟการกระจายชนิดเดียวที่ไม่ต้องเลือกพารามิเตอร์ใด ๆ เลย ไม่มี bin ไม่มี bandwidth และอ่านเปอร์เซ็นไทล์ได้โดยตรงจากแกน
เปรียบเทียบให้เห็นภาพ
เหมือนยืนที่เส้นชัยแล้วนับว่ามีคนเข้าเส้นไปแล้วกี่เปอร์เซ็นต์ ณ เวลาหนึ่ง ๆ ไม่ต้องแบ่งช่วงเวลาเป็นถัง
ใช้ที่ไหนบ้าง — ตอบคำถามเชิงข้อตกลงบริการ เช่น 95% ของคำขอเสร็จภายในกี่วินาที เทียบการกระจายของสองกลุ่มอย่างแม่นยำ หรือใช้แทนฮิสโทแกรมเมื่อไม่อยากเลือก bin
สร้างอย่างไร
- ขั้นที่ 1เรียงข้อมูลจากน้อยไปมาก
- ขั้นที่ 2คำนวณสัดส่วนสะสมของแต่ละจุด
- ขั้นที่ 3วาดเป็นเส้นขั้นบันได
- ขั้นที่ 4ทำเครื่องหมายเปอร์เซ็นไทล์ที่สนใจ
ตัวอย่างคลาสสิก · กราฟเปอร์เซ็นไทล์ของเวลาตอบสนองในงานวิศวกรรมระบบ
ทีมที่ดูแลระบบออนไลน์ใช้ ECDF เป็นมาตรฐาน เพราะคำถามที่ต้องตอบคือ "99% ของคำขอเสร็จภายในกี่มิลลิวินาที" ซึ่งอ่านจาก ECDF ได้ตรง ๆ ขณะที่ค่าเฉลี่ยกลบปัญหาที่หางขวาไว้มิด
02อธิบายภาษาคนเข้าใจง่าย ๆ
ฮิสโทแกรมกับกราฟความหนาแน่นมีจุดอ่อนร่วมกันคือทั้งคู่ต้องเลือกพารามิเตอร์ และการเลือกต่างกันให้ภาพต่างกัน ซึ่งหมายความว่าภาพที่เห็นสะท้อนการตัดสินใจของคนทำไปด้วย
ECDF ไม่มีปัญหานี้เลย มันแค่เรียงข้อมูลแล้วถามว่า ณ ค่าใดค่าหนึ่ง มีข้อมูลกี่เปอร์เซ็นต์ที่ต่ำกว่าหรือเท่ากับค่านั้น คำตอบมีแค่แบบเดียว ไม่มีอะไรให้เลือก
ผลพลอยได้ที่มีค่ามากคือการอ่านเปอร์เซ็นไทล์ ถ้าอยากรู้ว่า 90% ของคำขอเสร็จภายในกี่วินาที ก็ลากเส้นแนวนอนที่ 0.9 แล้วอ่านค่าบนแกนนอน ซึ่งเป็นสิ่งที่ฮิสโทแกรมทำไม่ได้
การเทียบสองกลุ่มก็ทำได้ดี เพราะเส้น ECDF สองเส้นซ้อนกันได้โดยไม่บังกันเหมือนฮิสโทแกรม และระยะห่างในแนวตั้งระหว่างสองเส้นก็มีความหมายทางสถิติจริง
ข้อเสียคือคนอ่านต้องเรียนรู้ก่อน เพราะรูปร่างของ ECDF ไม่ได้บอกยอดของการกระจายอย่างตรงไปตรงมา ช่วงที่เส้นชันคือช่วงที่ข้อมูลหนาแน่น ซึ่งต้องคิดอีกขั้นหนึ่ง
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
value | ค่าที่วัดได้ | ตัวเลขต่อเนื่อง | x | ต่อเนื่อง | ข้อมูลดิบ ไม่ต้องสรุปมาก่อน และไม่ต้องเลือกพารามิเตอร์ใด ๆ |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- เรียงข้อมูลจากน้อยไปมาก
- ตัดค่าว่างออก
- ตัดสินใจว่าจะเทียบกับการแจกแจงอ้างอิงหรือไม่
ค่าที่หายไป — ตัดค่าว่างและรายงานจำนวน เพราะสัดส่วนสะสมคำนวณจากจำนวนที่เหลือ
สเกลของแกน — แกนนอนตามค่าจริง ใช้ log ได้ถ้าข้อมูลเบ้ แกนตั้งเป็น 0 ถึง 1 เสมอ
กราฟจะเริ่มพังเมื่อไร
ต่ำกว่า 20 แถว ขั้นบันไดจะหยาบจนอ่านเปอร์เซ็นไทล์ได้ไม่ละเอียด
หน้าตาไฟล์ที่ต้องเตรียม
value
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- เปอร์เซ็นไทล์ใด ๆ อ่านได้ตรงจากกราฟ
- ช่วงที่เส้นชันคือช่วงที่ข้อมูลหนาแน่น
- ช่วงที่เส้นแบนคือช่วงที่แทบไม่มีข้อมูล
สิ่งที่กราฟนี้ตอบไม่ได้
- ไม่แสดงยอดของการกระจายอย่างตรงไปตรงมา ต้องอ่านจากความชัน
- ผู้อ่านทั่วไปมักตีความรูปร่างผิดถ้าไม่เคยเห็นมาก่อน
อ่านตามลำดับนี้
- ลากเส้นแนวนอนที่สัดส่วนที่สนใจแล้วอ่านค่าบนแกนนอน
- ดูช่วงที่เส้นชันเพื่อหาช่วงที่ข้อมูลหนาแน่น
- ดูช่วงที่เส้นแบนเพื่อหาช่องว่างของข้อมูล
คำถามที่ตอบได้
- กี่เปอร์เซ็นต์ที่ต่ำกว่าค่านี้
- ค่าที่เปอร์เซ็นไทล์ที่ 95 คือเท่าไร
- สองกลุ่มนี้มีการกระจายต่างกันตรงไหน
สัญญาณว่ามีอะไรผิด
- เส้นไม่ได้เริ่มที่ 0 หรือจบที่ 1
- ใช้กับข้อมูลน้อยจนขั้นบันไดหยาบมาก
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ลากเส้นประที่เปอร์เซ็นไทล์สำคัญ เช่น 50, 90, 95 พร้อมป้ายค่า
- เขียนจำนวนข้อมูลไว้
- ถ้าเทียบหลายกลุ่ม ให้ติดป้ายไว้ปลายเส้น
รูปแบบชื่อกราฟที่ควรใช้
90% ของ <สิ่งที่วัด> อยู่ต่ำกว่า <ค่า> ขณะที่ค่ากลางอยู่ที่ <ค่า>
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ ecdf-plot-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด ecdf-plot-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("ecdf-plot-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "percentile" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
v = np.sort(df["value"].dropna().to_numpy())
p = np.arange(1, len(v) + 1) / len(v) # สัดส่วนสะสม ไม่มีพารามิเตอร์ให้เลือก
ax.step(v, p, where="post", color=C1, linewidth=2)
for q in [0.25, 0.5, 0.9]:
val = np.quantile(v, q)
ax.plot([v.min(), val], [q, q], "--", color="#c9c6bd", linewidth=1)
ax.plot([val, val], [0, q], "--", color="#c9c6bd", linewidth=1)
ax.annotate(f"{int(q*100)}% ≤ {val:,.1f}", (val, q), xytext=(6, -12), textcoords="offset points", fontsize=9)
ax.set_ylim(0, 1); ax.set_xlabel("ค่าที่วัดได้"); ax.set_ylabel("สัดส่วนสะสม")
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("ecdf-plot.png", dpi=160)
print("เขียน ecdf-plot.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (5 แถว)
| ควอนไทล์ | ค่า |
|---|---|
| 5% | 52.4 |
| 25% | 58.3 |
| 50% | 62.6 |
| 75% | 66.2 |
| 95% | 70.0 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: ดูจุดที่ทำเครื่องหมายไว้ที่ 25, 50 และ 75 เปอร์เซ็นต์ จะเห็นว่าอ่านเปอร์เซ็นไทล์ได้ตรง ๆ ซึ่งฮิสโทแกรมทำไม่ได้เลย
ข้อควรระวัง
- ใช้กับผู้อ่านที่ไม่เคยเห็นกราฟสะสม ทำให้ตีความรูปร่างผิด
- ตีความช่วงที่เส้นแบนว่าข้อมูลกระจุกตัว ทั้งที่ความจริงคือแทบไม่มีข้อมูลในช่วงนั้น
- ใช้กับข้อมูลน้อยจนขั้นบันไดหยาบแต่ยังอ่านเปอร์เซ็นไทล์ละเอียด
- ซ้อนเกิน 3 เส้นจนแยกไม่ออก
เมื่อไหร่ไม่ควรใช้กราฟนี้
- ผู้อ่านทั่วไปที่ไม่คุ้นกับกราฟสะสม→ใช้แทน: histogram หรือ density-plot
- ต้องการเห็นยอดของการกระจายชัด ๆ→ใช้แทน: histogram หรือ density-plot
- ต้องเทียบหลายกลุ่มพร้อมกันเกิน 3 กลุ่ม→ใช้แทน: box-plot หรือ ridgeline-plot