01ภาพรวมของกราฟนี้
Ridgeline plot วางเส้นความหนาแน่นของแต่ละกลุ่มเรียงกันในแนวตั้งโดยซ้อนเหลื่อมเล็กน้อย การซ้อนนี้ประหยัดพื้นที่และทำให้สายตาเห็น "การเลื่อน" ของยอดจากกลุ่มหนึ่งไปอีกกลุ่มได้ทันที ใช้ได้เฉพาะเมื่อกลุ่มมีลำดับที่มีความหมาย
เปรียบเทียบให้เห็นภาพ
เหมือนมองแนวเทือกเขาจากด้านข้าง ยอดเขาที่ค่อย ๆ เลื่อนไปทางขวาบอกว่าอะไรบางอย่างกำลังเปลี่ยนไปอย่างเป็นระบบ
ใช้ที่ไหนบ้าง — การกระจายของอุณหภูมิรายเดือน คะแนนของแต่ละรุ่นการศึกษา หรือการกระจายของรายได้ตามช่วงอายุ
สร้างอย่างไร
- ขั้นที่ 1ตรวจว่ากลุ่มมีลำดับที่มีความหมาย
- ขั้นที่ 2คำนวณความหนาแน่นของแต่ละกลุ่ม
- ขั้นที่ 3วางเรียงกันโดยซ้อนเหลื่อม
- ขั้นที่ 4ใช้สีไล่เฉดตามลำดับของกลุ่ม
ตัวอย่างคลาสสิก · Joy Division กับปกอัลบั้ม Unknown Pleasures ปี 1979
ปกอัลบั้มที่เป็นเส้นสัญญาณจากพัลซาร์ซ้อนกันเป็นแนวสันเขา กลายเป็นที่มาของชื่อเล่น joyplot รูปแบบนี้ถูกนำกลับมาใช้จริงในงานข้อมูลเพราะมันเผยการเลื่อนของการกระจายได้ดีกว่าการวางกราฟแยกกัน
02อธิบายภาษาคนเข้าใจง่าย ๆ
เมื่อต้องเทียบการกระจายของ 12 เดือน การวาง violin plot 12 อันเรียงกันจะแคบจนอ่านรูปร่างไม่ได้ และการวาง density plot ซ้อนทับกันก็จะกลายเป็นเส้นพันกัน
Ridgeline วางแต่ละเส้นในแถวของตัวเองแต่ปล่อยให้ซ้อนเหลื่อมกันเล็กน้อย จึงประหยัดพื้นที่มากกว่าการวางแยกกันสนิท และยังอ่านรูปร่างของแต่ละเส้นได้
สิ่งที่กราฟนี้ทำได้ดีเป็นพิเศษคือเผย "การเลื่อน" ถ้ายอดของแต่ละแถวค่อย ๆ ขยับไปทางขวา สายตาจะจับรูปแบบนั้นได้ทันทีในฐานะแนวสันเขาที่เอียง
เงื่อนไขสำคัญคือกลุ่มต้องมีลำดับที่มีความหมาย เช่น เดือน ปี หรือช่วงอายุ ถ้าเป็นชื่อจังหวัดที่ไม่มีลำดับ การวางซ้อนกันจะไม่ได้บอกอะไรและแค่ทำให้บังกัน
ข้อควรระวังคือการซ้อนทำให้ส่วนล่างของแต่ละเส้นถูกบัง ถ้าซ้อนมากเกินไปจะเหลือแค่ยอดให้ดู ควรปรับการซ้อนให้เห็นฐานของเส้นพอสมควร
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
group | กลุ่มที่มีลำดับ | มีลำดับ | row | 4–20 กลุ่ม | ★ ต้องมีลำดับที่มีความหมาย เช่น เดือนหรือช่วงอายุ |
value | ค่าที่วัดได้ | ตัวเลขต่อเนื่อง | x | ต่อเนื่อง | ข้อมูลดิบ อย่างน้อย 30 จุดต่อกลุ่ม |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- ตรวจว่ากลุ่มมีลำดับ
- คำนวณความหนาแน่นด้วย bandwidth เดียวกันทุกกลุ่ม
- ปรับระดับการซ้อนให้เห็นฐานของเส้น
ค่าที่หายไป — กลุ่มที่ไม่มีข้อมูลต้องเว้นแถวไว้ให้เห็น เพราะตำแหน่งในลำดับคือความหมายของแถว
สเกลของแกน — ทุกกลุ่มต้องใช้สเกลแกนนอนเดียวกัน ไม่อย่างนั้นการเลื่อนที่เห็นจะไม่จริง
กราฟจะเริ่มพังเมื่อไร
เกิน 25 กลุ่มจะสูงเกินหน้าจอ และต่ำกว่า 4 กลุ่มไม่คุ้มกับความซับซ้อน
หน้าตาไฟล์ที่ต้องเตรียม
group,value
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- การเลื่อนของยอดจากกลุ่มหนึ่งไปอีกกลุ่ม
- กลุ่มที่รูปร่างต่างจากกลุ่มข้างเคียง
- กลุ่มที่มีสองยอด
สิ่งที่กราฟนี้ตอบไม่ได้
- ไม่บอกจำนวนข้อมูลของแต่ละกลุ่ม
- ส่วนล่างของเส้นถูกบังโดยเส้นถัดไป จึงอ่านหางล่างไม่ครบ
อ่านตามลำดับนี้
- มองแนวสันเขารวมว่าเอียงไปทางไหน
- ดูรูปร่างของแต่ละแถวว่ามีกี่ยอด
- หาแถวที่ผิดจากแนวโน้ม
คำถามที่ตอบได้
- การกระจายเปลี่ยนไปอย่างไรตามลำดับของกลุ่ม
- มีกลุ่มไหนที่ผิดจากแนวโน้มรวม
สัญญาณว่ามีอะไรผิด
- ใช้กับกลุ่มที่ไม่มีลำดับ
- ซ้อนมากจนเหลือแค่ยอดให้ดู
- แต่ละกลุ่มใช้สเกลแกนนอนต่างกัน
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ใช้สีไล่เฉดตามลำดับของกลุ่มเพื่อย้ำว่ามีลำดับ
- ลากเส้นแนวตั้งของค่าอ้างอิงพาดทุกแถว
- เขียนจำนวนข้อมูลของแต่ละกลุ่ม
รูปแบบชื่อกราฟที่ควรใช้
การกระจายของ <ตัวแปร> เลื่อนไปทาง <ทิศทาง> อย่างต่อเนื่องตั้งแต่ <กลุ่มแรก> ถึง <กลุ่มสุดท้าย>
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ ridgeline-plot-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด ridgeline-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("ridgeline-plot-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "shift" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
groups = list(df["group"].unique())
grid = np.linspace(df["value"].min(), df["value"].max(), 200)
for i, g in enumerate(reversed(groups)):
v = df[df["group"] == g]["value"]
dens = gaussian_kde(v)(grid); dens = dens / dens.max() * 1.6 # ปรับให้ซ้อนเหลื่อมกันได้
ax.fill_between(grid, i, i + dens, color=plt.cm.Blues(0.3 + i / len(groups) * 0.5), lw=1.2, ec=SURFACE)
ax.text(grid[0], i + 0.1, g, ha="right", fontsize=9)
ax.set_yticks([]); ax.set_xlabel("ค่าที่วัดได้")
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("ridgeline-plot.png", dpi=160)
print("เขียน ridgeline-plot.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
เส้นที่ซ้อนกันบังส่วนล่างของกลุ่มถัดไป — ใช้ได้เมื่อกลุ่มมีลำดับที่มีความหมายเท่านั้น
ดูข้อมูลเป็นตาราง (4 แถว)
| กลุ่ม | มัธยฐาน | ต่ำสุด | สูงสุด |
|---|---|---|---|
| สาขา A | 57.6 | 50.0 | 67.1 |
| สาขา B | 66.3 | 55.9 | 76.6 |
| สาขา C | 61.7 | 55.6 | 68.1 |
| สาขา D | 73.7 | 62.6 | 84.9 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: มองภาพรวมโดยไม่อ่านตัวเลข จะเห็นว่ายอดของแต่ละแถวขยับไปทางไหน ซึ่งเป็นรูปแบบที่การดูทีละกลุ่มมองไม่เห็น
ข้อควรระวัง
- ใช้กับกลุ่มที่ไม่มีลำดับ ทำให้การซ้อนไม่ได้บอกอะไรและแค่บังกัน
- ซ้อนมากเกินไปจนเห็นแค่ยอดและอ่านรูปร่างไม่ได้
- ใช้ bandwidth ต่างกันในแต่ละกลุ่ม ทำให้รูปร่างเทียบกันไม่ได้
- ไม่บอกจำนวนข้อมูลของแต่ละกลุ่ม ทำให้กลุ่มที่ข้อมูลน้อยดูน่าเชื่อถือเท่ากลุ่มใหญ่
เมื่อไหร่ไม่ควรใช้กราฟนี้
- กลุ่มไม่มีลำดับที่มีความหมาย→ใช้แทน: violin-plot หรือ box-plot
- มีน้อยกว่า 4 กลุ่ม→ใช้แทน: density-plot ซ้อนกัน
- ต้องอ่านค่าสรุปให้แม่น→ใช้แทน: box-plot