01ภาพรวมของกราฟนี้
Marginal scatter วางฮิสโทแกรมของแต่ละตัวแปรไว้ที่ขอบของแผนภาพกระจาย ผลคือตอบคำถามสองชั้นในภาพเดียว คือตัวแปรแต่ละตัวกระจายอย่างไร และทั้งสองสัมพันธ์กันอย่างไร ซึ่งเป็นคำถามที่มักตามกันมาเสมอ
เปรียบเทียบให้เห็นภาพ
เหมือนแผนที่ที่มีกราฟความสูงของเส้นทางอยู่ด้านล่างและด้านข้าง เราเห็นทั้งภาพรวมและภาพตัดขวางพร้อมกัน
ใช้ที่ไหนบ้าง — สำรวจข้อมูลสองตัวแปรอย่างละเอียด ตรวจว่าความสัมพันธ์ที่เห็นมาจากการกระจายที่เบ้หรือไม่ หรือใช้ในรายงานที่มีพื้นที่พอ
สร้างอย่างไร
- ขั้นที่ 1วาดแผนภาพกระจายตรงกลาง
- ขั้นที่ 2วางฮิสโทแกรมของ x ไว้ด้านบน
- ขั้นที่ 3วางฮิสโทแกรมของ y ไว้ด้านขวา
- ขั้นที่ 4ใช้สเกลแกนร่วมกันระหว่างส่วนกลางกับขอบ
ตัวอย่างคลาสสิก · Joint plot ที่กลายเป็นมาตรฐานในไลบรารีวิเคราะห์ข้อมูล
seaborn ตั้งชื่อฟังก์ชันนี้ว่า jointplot เพราะมันแสดง joint distribution ตรงกลางและ marginal distribution ที่ขอบ ซึ่งเป็นแนวคิดพื้นฐานทางสถิติที่แปลเป็นภาพได้ตรงไปตรงมา จนกลายเป็นรูปแบบที่นักวิเคราะห์ใช้เป็นภาพแรกเมื่อสำรวจสองตัวแปร
02อธิบายภาษาคนเข้าใจง่าย ๆ
เวลาดูความสัมพันธ์ระหว่างสองตัวแปร คำถามที่ตามมาเสมอคือแต่ละตัวแปรกระจายอย่างไร เพราะการกระจายที่เบ้แรงอาจทำให้ความสัมพันธ์ที่เห็นเป็นภาพลวง
ปกติเราต้องวาดสามกราฟแยกกัน คือแผนภาพกระจายหนึ่งอันและฮิสโทแกรมสองอัน แล้วสลับสายตาไปมา
Marginal scatter รวมทั้งสามไว้ในภาพเดียวโดยวางฮิสโทแกรมไว้ที่ขอบ เงื่อนไขสำคัญคือแกนต้องตรงกัน ฮิสโทแกรมด้านบนต้องใช้สเกล x เดียวกับส่วนกลาง
สิ่งที่มองเห็นได้ทันทีคือความสอดคล้อง ถ้าฮิสโทแกรมของ x มีสองยอดแต่จุดตรงกลางดูเป็นเมฆเดียว แปลว่ามีกลุ่มย่อยซ่อนอยู่ที่แผนภาพกระจายเพียงอย่างเดียวมองไม่เห็น
ข้อแลกเปลี่ยนคือพื้นที่ ส่วนกลางที่เป็นข้อมูลหลักจะเล็กลงเพราะต้องแบ่งที่ให้ขอบ ในทางปฏิบัติจึงเหมาะกับรายงานที่มีพื้นที่พอ ไม่ใช่ dashboard ที่แน่น
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
x | ตัวแปรแกนนอน | ตัวเลขต่อเนื่อง | x | ต่อเนื่อง | ฮิสโทแกรมด้านบนใช้สเกลเดียวกัน |
y | ตัวแปรแกนตั้ง | ตัวเลขต่อเนื่อง | y | ต่อเนื่อง | ฮิสโทแกรมด้านขวาใช้สเกลเดียวกัน |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- ตัดแถวที่ไม่ครบ
- เลือกจำนวน bin ของฮิสโทแกรมทั้งสองด้าน
- จัดพื้นที่ให้ส่วนกลางใหญ่ที่สุด
ค่าที่หายไป — ตัดแถวที่ขาดค่าใดค่าหนึ่งออกจากทั้งสามส่วนพร้อมกัน ไม่อย่างนั้นฮิสโทแกรมขอบจะนับแถวมากกว่าจุดตรงกลาง
สเกลของแกน — สเกลของฮิสโทแกรมขอบต้องตรงกับสเกลของส่วนกลางอย่างเคร่งครัด
กราฟจะเริ่มพังเมื่อไร
เกิน 3,000 จุด ส่วนกลางจะทับกัน ควรเปลี่ยนส่วนกลางเป็น hexbin แต่คงฮิสโทแกรมขอบไว้
หน้าตาไฟล์ที่ต้องเตรียม
x,y
23.61,65.65
58.57,67.07
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- ความสัมพันธ์ระหว่างสองตัวแปรจากส่วนกลาง
- รูปร่างการกระจายของแต่ละตัวแปรจากขอบ
- ความสอดคล้องหรือขัดแย้งระหว่างสองส่วน เช่น ขอบมีสองยอดแต่กลางเป็นเมฆเดียว
สิ่งที่กราฟนี้ตอบไม่ได้
- ฮิสโทแกรมขอบไม่บอกว่าจุดในกลุ่มย่อยนั้นอยู่ตรงไหนของส่วนกลาง
- พื้นที่ของส่วนกลางเล็กลง จึงเห็นรายละเอียดน้อยกว่าแผนภาพกระจายเต็มขนาด
อ่านตามลำดับนี้
- ดูส่วนกลางก่อนเพื่ออ่านความสัมพันธ์
- ดูขอบทั้งสองเพื่ออ่านการกระจายของแต่ละตัว
- ตรวจว่าจำนวนยอดที่ขอบสอดคล้องกับกลุ่มก้อนที่เห็นตรงกลางไหม
คำถามที่ตอบได้
- สองตัวแปรสัมพันธ์กันไหม และแต่ละตัวกระจายอย่างไร
- ความสัมพันธ์ที่เห็นเกิดจากการกระจายที่เบ้หรือไม่
สัญญาณว่ามีอะไรผิด
- สเกลของขอบไม่ตรงกับส่วนกลาง
- ฮิสโทแกรมขอบใหญ่กว่าส่วนกลางจนกลายเป็นตัวเอก
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ทำให้ส่วนกลางใหญ่ที่สุดเสมอ ขอบเป็นส่วนเสริม
- ใช้สีจางกว่าสำหรับฮิสโทแกรมขอบ
- ระบุจำนวน bin ของขอบ
รูปแบบชื่อกราฟที่ควรใช้
<x> กับ <y> สัมพันธ์กันแบบ <ทิศทาง> โดย <ตัวแปร> มีการกระจายแบบ <รูปร่าง>
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ marginal-scatter-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด marginal-scatter-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("marginal-scatter-sample.csv")
df["x"] = pd.to_numeric(df["x"], errors="coerce")
df["y"] = pd.to_numeric(df["y"], errors="coerce")
NOTE = "match" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
fig.clf()
d = df.dropna(subset=["x", "y"])
g = sns.jointplot(data=d, x="x", y="y", height=6, color=C1,
marginal_kws=dict(bins=24, color="#9ec5f4"), # ขอบใช้สีจางกว่าส่วนกลาง
joint_kws=dict(s=22, alpha=0.6))
g.figure.savefig("marginal-scatter.png", dpi=160)
print("เขียน marginal-scatter.png แล้ว")
raise SystemExit # seaborn สร้าง figure ของตัวเอง
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("marginal-scatter.png", dpi=160)
print("เขียน marginal-scatter.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (30 แถว)
| x | y |
|---|---|
| 23.6 | 65.7 |
| 58.6 | 67.1 |
| 43.2 | 61.5 |
| 31.6 | 57.4 |
| 26.4 | 71.5 |
| 38.5 | 46.3 |
| 38.3 | 48.6 |
| 56.2 | 53.5 |
| 30.6 | 68.0 |
| 52.2 | 64.3 |
| 52.0 | 60.4 |
| 45.2 | 63.8 |
| 36.1 | 72.4 |
| 44.3 | 66.1 |
| 37.2 | 53.5 |
| 44.5 | 44.4 |
| 35.4 | 67.2 |
| 37.2 | 65.3 |
| 48.4 | 46.1 |
| 34.8 | 45.7 |
| 37.1 | 70.3 |
| 59.8 | 41.3 |
| 54.2 | 60.4 |
| 39.0 | 57.1 |
| 19.7 | 67.8 |
| 35.7 | 58.7 |
| 42.8 | 62.6 |
| 38.9 | 35.2 |
| 31.1 | 78.5 |
| 57.9 | 54.7 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: นับยอดของฮิสโทแกรมด้านบน แล้วดูว่าตรงกับจำนวนกลุ่มก้อนที่เห็นตรงกลางไหม ถ้าไม่ตรงแปลว่ามีโครงสร้างที่มองเห็นได้จากมุมเดียวเท่านั้น
ข้อควรระวัง
- ใช้สเกลของขอบไม่ตรงกับส่วนกลาง ทำให้เชื่อมโยงสองภาพผิด
- ทำให้ฮิสโทแกรมขอบใหญ่จนแย่งความสนใจจากส่วนกลาง
- ตัดค่าว่างไม่พร้อมกันทั้งสามส่วน ทำให้จำนวนที่นับไม่ตรงกัน
- ใช้กับข้อมูลเยอะเกินไปจนส่วนกลางกลายเป็นก้อนทึบ
เมื่อไหร่ไม่ควรใช้กราฟนี้
- พื้นที่จำกัด→ใช้แทน: scatter-plot อย่างเดียว
- สนใจแค่การกระจายของตัวแปรเดียว→ใช้แทน: histogram
- จุดเยอะจนทับกัน→ใช้แทน: hexbin-plot