01ภาพรวมของกราฟนี้
Contour plot ประมาณความหนาแน่นของข้อมูลบนระนาบสองมิติแล้ววาดเส้นรอบบริเวณที่มีความหนาแน่นเท่ากัน คล้ายเส้นระดับความสูงบนแผนที่ ผลคือเห็นว่ามีกี่กระจุกและแต่ละกระจุกรูปร่างอย่างไร ซึ่งเป็นโครงสร้างที่จุดทับกันซ่อนไว้
เปรียบเทียบให้เห็นภาพ
เหมือนเส้นระดับความสูงบนแผนที่ภูเขา เส้นที่ชิดกันแปลว่าชัน เส้นที่วนเป็นวงปิดแปลว่าเป็นยอดเขา
ใช้ที่ไหนบ้าง — หากลุ่มย่อยในข้อมูลขนาดใหญ่ แสดงบริเวณที่ค่าปกติอยู่ หรือวางทับ scatter plot เพื่อเสริมโครงสร้าง
สร้างอย่างไร
- ขั้นที่ 1ประมาณความหนาแน่นสองมิติ
- ขั้นที่ 2เลือกจำนวนชั้นที่จะวาด
- ขั้นที่ 3วาดเส้นหรือระบายสีตามชั้น
- ขั้นที่ 4วางจุดข้อมูลจริงทับบาง ๆ
ตัวอย่างคลาสสิก · เส้นระดับความสูงบนแผนที่ภูมิประเทศ
แนวคิดเส้นชั้นมาจากแผนที่ภูมิประเทศที่ใช้กันมาตั้งแต่ศตวรรษที่ 18 หลักการเดียวกันคือวาดเส้นเชื่อมจุดที่มีค่าเท่ากัน การยืมภาพนี้มาใช้กับความหนาแน่นของข้อมูลได้ผลดีเพราะคนคุ้นเคยกับการอ่านแผนที่อยู่แล้ว
02อธิบายภาษาคนเข้าใจง่าย ๆ
เมื่อข้อมูลสองมิติมีเป็นหมื่นจุด เราอยากรู้ว่ามันกระจุกเป็นกี่ก้อนและแต่ละก้อนรูปร่างอย่างไร ซึ่งเป็นคำถามเรื่องโครงสร้างไม่ใช่เรื่องจุดรายตัว
Contour plot ตอบด้วยการประมาณความหนาแน่นทั่วทั้งระนาบ แล้ววาดเส้นรอบบริเวณที่ความหนาแน่นเท่ากัน วิธีคิดเหมือนเส้นระดับบนแผนที่ทุกประการ
สิ่งที่อ่านได้คือจำนวนยอด ถ้ามีวงปิดสองกลุ่มแยกกัน แปลว่าข้อมูลมีสองกลุ่มย่อยที่ scatter plot อาจแสดงเป็นเมฆก้อนเดียว
ระยะห่างระหว่างเส้นก็มีความหมาย เส้นที่ชิดกันแปลว่าความหนาแน่นเปลี่ยนเร็ว ซึ่งมักเป็นขอบของกลุ่ม ส่วนเส้นที่ห่างกันแปลว่าบริเวณนั้นค่อย ๆ จางออกไป
ข้อควรระวังที่สำคัญคือเส้นเหล่านี้เป็นการประมาณ ไม่ใช่ขอบเขตจริง และรูปร่างเปลี่ยนตามพารามิเตอร์ที่เลือก จึงควรวางจุดข้อมูลจริงทับไว้เสมอเพื่อให้ผู้อ่านตรวจสอบได้
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
x | ตัวแปรแกนนอน | ตัวเลขต่อเนื่อง | x | ต่อเนื่อง | ข้อมูลดิบ |
y | ตัวแปรแกนตั้ง | ตัวเลขต่อเนื่อง | y | ต่อเนื่อง | ข้อมูลดิบ |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- ตัดแถวที่ไม่ครบ
- เลือกจำนวนชั้นและ bandwidth แล้วบันทึกไว้
- ตัดสินใจว่าจะวางจุดจริงทับหรือไม่
ค่าที่หายไป — ตัดแถวที่ไม่ครบและรายงานจำนวน เพราะการประมาณความหนาแน่นใช้ทุกจุดที่มี
สเกลของแกน — linear ทั้งสองแกน การใช้ log จะทำให้รูปร่างของชั้นเปลี่ยนไปมากและต้องอธิบาย
กราฟจะเริ่มพังเมื่อไร
ต่ำกว่า 200 จุด เส้นชั้นจะสะท้อนพารามิเตอร์มากกว่าสะท้อนข้อมูล
หน้าตาไฟล์ที่ต้องเตรียม
x,y
23.61,65.65
58.57,67.07
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- จำนวนกระจุกของข้อมูลจากจำนวนวงปิด
- รูปร่างของแต่ละกระจุกว่ากลมหรือยาว
- ความชันของขอบจากระยะห่างระหว่างเส้น
สิ่งที่กราฟนี้ตอบไม่ได้
- ไม่บอกจำนวนจุดที่แน่นอนในแต่ละบริเวณ
- เส้นเป็นการประมาณ ไม่ใช่ขอบเขตจริงของข้อมูล
อ่านตามลำดับนี้
- นับวงปิดเพื่อหาจำนวนกระจุก
- ดูรูปร่างของวงในสุดของแต่ละกระจุก
- ดูว่าเส้นชิดหรือห่างเพื่อประเมินความชัน
คำถามที่ตอบได้
- ข้อมูลมีกี่กลุ่มย่อย
- บริเวณไหนที่ข้อมูลกระจุกที่สุด
- กลุ่มข้อมูลเอียงไปทางไหน
สัญญาณว่ามีอะไรผิด
- เส้นยื่นเข้าไปในบริเวณที่ไม่มีจุดข้อมูลเลย
- ไม่ระบุจำนวนชั้นหรือพารามิเตอร์ที่ใช้
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- วางจุดข้อมูลจริงทับบาง ๆ เพื่อให้ตรวจสอบได้
- ระบุจำนวนชั้นและวิธีประมาณ
- ติดป้ายบอกว่าแต่ละชั้นครอบคลุมข้อมูลประมาณกี่เปอร์เซ็นต์
รูปแบบชื่อกราฟที่ควรใช้
ข้อมูลกระจุกเป็น <จำนวน> กลุ่ม โดยกลุ่มหลักอยู่ที่ <ตำแหน่ง>
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ contour-plot-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด contour-plot-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
from scipy.stats import gaussian_kde
import numpy as np
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("contour-plot-sample.csv")
df["x"] = pd.to_numeric(df["x"], errors="coerce")
df["y"] = pd.to_numeric(df["y"], errors="coerce")
LEVELS = 5 # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
d = df.dropna(subset=["x", "y"])
kde = gaussian_kde(np.vstack([d["x"], d["y"]]))
gx, gy = np.mgrid[d["x"].min():d["x"].max():80j, d["y"].min():d["y"].max():80j]
z = kde(np.vstack([gx.ravel(), gy.ravel()])).reshape(gx.shape)
cf = ax.contourf(gx, gy, z, levels=LEVELS, cmap="Blues", alpha=0.85)
ax.scatter(d["x"], d["y"], s=5, color=INK, alpha=0.2) # จุดจริงทับไว้ให้ตรวจสอบได้
fig.colorbar(cf, ax=ax, label="ความหนาแน่น", shrink=0.7)
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("contour-plot.png", dpi=160)
print("เขียน contour-plot.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
เส้นชั้นเป็นค่าประมาณจากข้อมูล ไม่ใช่ขอบเขตจริง — จำนวนชั้นที่เลือกเปลี่ยนภาพที่เห็น
ดูข้อมูลเป็นตาราง (5 แถว)
| ชั้น | รัศมี (เท่าของ SD) | ครอบคลุมประมาณ |
|---|---|---|
| ชั้นที่ 1 | 3.10 | 99% |
| ชั้นที่ 2 | 2.48 | 95% |
| ชั้นที่ 3 | 1.86 | 82% |
| ชั้นที่ 4 | 1.24 | 54% |
| ชั้นที่ 5 | 0.62 | 17% |
ลองแล้วจะเห็นอะไร
- จำนวนชั้น: เพิ่มจำนวนชั้นแล้วจะเห็นรายละเอียดมากขึ้น แต่ก็เห็นชั้นที่เกิดจากการประมาณมากกว่าจากข้อมูลจริงด้วย
ข้อควรระวัง
- ไม่วางจุดข้อมูลจริงทับ ทำให้ผู้อ่านตรวจสอบไม่ได้ว่าเส้นมาจากข้อมูลจริงแค่ไหน
- ใช้กับข้อมูลน้อย ซึ่งเส้นจะสะท้อนพารามิเตอร์มากกว่าข้อมูล
- ไม่ระบุจำนวนชั้นหรือ bandwidth ที่ใช้
- ตีความเส้นว่าเป็นขอบเขตจริงของกลุ่มข้อมูล
เมื่อไหร่ไม่ควรใช้กราฟนี้
- ข้อมูลน้อยกว่า 200 จุด→ใช้แทน: scatter-plot
- ต้องการทราบจำนวนจุดที่แน่นอน→ใช้แทน: hexbin-plot ที่นับจริง
- ผู้อ่านไม่คุ้นกับเส้นระดับ→ใช้แทน: hexbin-plot ที่อ่านง่ายกว่า