01ภาพรวมของกราฟนี้
Chord diagram จัดกลุ่มไว้รอบวงกลมแล้วลากริบบิ้นเชื่อมระหว่างกลุ่มที่มีการไหลถึงกัน จุดแข็งคือรองรับการไหลสองทางและวงจร ซึ่ง sankey ทำไม่ได้เพราะมันบังคับให้ไหลไปข้างหน้าทางเดียว
เปรียบเทียบให้เห็นภาพ
เหมือนแผนที่เส้นทางบินที่วาดสนามบินไว้รอบวงแล้วลากเส้นเชื่อมคู่ที่มีเที่ยวบินถึงกัน โดยเส้นหนาคือเส้นทางที่มีผู้โดยสารมาก
ใช้ที่ไหนบ้าง — การย้ายถิ่นระหว่างภูมิภาค การค้าระหว่างประเทศ การส่งต่อผู้ป่วยระหว่างโรงพยาบาล หรือการโอนเงินระหว่างบัญชี
สร้างอย่างไร
- ขั้นที่ 1จัดกลุ่มไว้รอบวงโดยให้ความยาวส่วนโค้งแปรตามปริมาณรวม
- ขั้นที่ 2ลากริบบิ้นเชื่อมทุกคู่ที่มีการไหล
- ขั้นที่ 3ให้ความหนาของริบบิ้นแปรตามปริมาณ
- ขั้นที่ 4ระบายสีตามต้นทางเพื่อบอกทิศทาง
ตัวอย่างคลาสสิก · Circos plot ในงานพันธุศาสตร์
นักพันธุศาสตร์ใช้แผนภาพคอร์ดแสดงการเชื่อมโยงระหว่างตำแหน่งบนโครโมโซม โดยจัดโครโมโซมไว้รอบวงแล้วลากเส้นเชื่อมคู่ที่เกี่ยวข้องกัน รูปแบบนี้เหมาะกับข้อมูลพันธุกรรมพอดีเพราะโครโมโซมไม่มีลำดับก่อนหลังตามธรรมชาติ การวางเป็นวงจึงไม่บังคับลำดับที่ไม่มีอยู่จริง
02อธิบายภาษาคนเข้าใจง่าย ๆ
Sankey ใช้ได้ดีเมื่อของไหลไปข้างหน้าทางเดียว แต่ข้อมูลหลายอย่างไม่เป็นแบบนั้น เช่น คนย้ายถิ่นระหว่างภาคที่ไหลไปกลับทั้งสองทาง
Chord diagram แก้ด้วยการเลิกใช้แกนซ้ายขวา แล้วจัดกลุ่มไว้รอบวงกลมแทน ทีนี้การไหลจากใครไปใครก็แค่ลากริบบิ้นข้ามวง ไม่มีข้อจำกัดเรื่องทิศทาง
ความยาวของส่วนโค้งที่แต่ละกลุ่มครองบอกปริมาณรวมที่ไหลผ่านกลุ่มนั้น ส่วนความหนาของริบบิ้นบอกปริมาณของคู่นั้น ๆ
ทิศทางบอกได้ด้วยสีหรือด้วยการทำให้ปลายริบบิ้นสองข้างหนาไม่เท่ากัน ซึ่งเป็นเทคนิคที่ใช้กันเมื่อการไหลสองทางไม่เท่ากัน
ข้อจำกัดที่หนักคือจำนวนเส้น เมื่อเกินประมาณ 20 ริบบิ้น เส้นที่พาดกลางวงจะทับกันจนอ่านไม่ได้ กรณีนั้นต้องเปลี่ยนไปใช้ adjacency matrix ที่ไม่มีปัญหาเส้นทับ
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
source | ต้นทาง | โหนด | angle | 4–12 กลุ่ม | ชุดเดียวกับปลายทาง |
target | ปลายทาง | โหนด | angle | 4–12 กลุ่ม | อาจไหลกลับไปยังต้นทางได้ |
value | ปริมาณ | ตัวเลขต่อเนื่อง | size | บวกเท่านั้น | ความหนาของริบบิ้นแปรตามค่า |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- รวมปริมาณของคู่เดียวกันให้เป็นแถวเดียว
- ตัดสินใจว่าจะรวมสองทิศทางเป็นเส้นเดียวหรือแยก
- รวมกลุ่มเล็กเป็น "อื่น ๆ"
ค่าที่หายไป — คู่ที่ไม่มีการไหลไม่ต้องวาดริบบิ้น แต่ต้องแยกให้ชัดจากคู่ที่ไม่มีข้อมูล ซึ่งควรระบุในคำอธิบาย
สเกลของแกน — ความหนาแปรตรงกับปริมาณ และความยาวส่วนโค้งแปรตามปริมาณรวมของกลุ่ม
กราฟจะเริ่มพังเมื่อไร
เกิน 20 ริบบิ้น เส้นที่พาดกลางวงทับกันจนอ่านไม่ได้ — ใช้ adjacency-matrix
หน้าตาไฟล์ที่ต้องเตรียม
source,target,value
เข้าชมเว็บ,ดูสินค้า,7975
เข้าชมเว็บ,ออกทันที,4078
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- กลุ่มที่มีปริมาณไหลผ่านมากที่สุดจากความยาวส่วนโค้ง
- คู่ที่มีการไหลมากที่สุดจากความหนาของริบบิ้น
- กลุ่มที่เชื่อมกับทุกกลุ่มกับกลุ่มที่เชื่อมกับไม่กี่กลุ่ม
สิ่งที่กราฟนี้ตอบไม่ได้
- อ่านค่าที่แม่นยำจากความหนาไม่ได้
- เมื่อริบบิ้นเยอะจะทับกันจนตามเส้นเดียวไม่ได้
- ทิศทางไม่ชัดถ้าไม่ใช้สีหรือความหนาที่ต่างกันสองปลาย
อ่านตามลำดับนี้
- ดูความยาวส่วนโค้งของแต่ละกลุ่มก่อน
- หาริบบิ้นที่หนาที่สุด
- ตามริบบิ้นจากปลายหนึ่งไปอีกปลาย
คำถามที่ตอบได้
- คู่ไหนมีการไหลมากที่สุด
- กลุ่มไหนเป็นศูนย์กลาง
- การไหลสองทางระหว่างคู่นี้สมดุลไหม
สัญญาณว่ามีอะไรผิด
- ริบบิ้นเกิน 20 เส้นจนทับกัน
- ไม่บอกทิศทางของการไหล
- ไม่มีตัวเลขกำกับเลย
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ระบายสีริบบิ้นตามต้นทางเพื่อบอกทิศทาง
- ติดชื่อกลุ่มไว้นอกวงพร้อมปริมาณรวม
- เน้นริบบิ้นที่เป็นประเด็นและทำเส้นอื่นจาง
รูปแบบชื่อกราฟที่ควรใช้
การไหลระหว่าง <กลุ่ม> และ <กลุ่ม> มากที่สุดที่ <ปริมาณ>
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ chord-diagram-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด chord-diagram-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("chord-diagram-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "arc" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
# matplotlib ไม่มี chord ในตัว — แสดงเป็นเมทริกซ์การไหลแทน (อ่านแม่นกว่าเมื่อเส้นเยอะ)
nodes = sorted(set(df["source"]) | set(df["target"]))
m = np.zeros((len(nodes), len(nodes)))
for _, r in df.iterrows():
m[nodes.index(r["source"]), nodes.index(r["target"])] = r["value"]
im = ax.imshow(m, cmap="Blues", aspect="auto")
ax.set_xticks(range(len(nodes))); ax.set_xticklabels(nodes, rotation=40, ha="right", fontsize=8)
ax.set_yticks(range(len(nodes))); ax.set_yticklabels(nodes, fontsize=8)
ax.set_ylabel("ต้นทาง"); ax.set_xlabel("ปลายทาง")
fig.colorbar(im, ax=ax, label="ปริมาณ", shrink=0.7)
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("chord-diagram.png", dpi=160)
print("เขียน chord-diagram.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
เส้นที่พาดผ่านกลางวงทับกันมาก — ถ้าเส้นเกิน 20 เส้นให้ใช้ adjacency matrix แทน
ดูข้อมูลเป็นตาราง (8 แถว)
| จาก | ไป | ปริมาณ |
|---|---|---|
| เข้าชมเว็บ | ดูสินค้า | 7,975 |
| เข้าชมเว็บ | ออกทันที | 4,078 |
| ดูสินค้า | ใส่ตะกร้า | 3,083 |
| ดูสินค้า | ออกกลางทาง | 5,112 |
| ใส่ตะกร้า | กรอกที่อยู่ | 1,952 |
| ใส่ตะกร้า | ทิ้งตะกร้า | 1,166 |
| กรอกที่อยู่ | ชำระเงินสำเร็จ | 1,418 |
| กรอกที่อยู่ | ชำระไม่สำเร็จ | 457 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: เทียบความยาวส่วนโค้งของแต่ละกลุ่มก่อนดูริบบิ้น จะเห็นว่ากลุ่มที่ส่วนโค้งยาวไม่จำเป็นต้องมีริบบิ้นหนาที่สุดเสมอไป
ข้อควรระวัง
- ใส่ริบบิ้นเกิน 20 เส้นจนทับกันจนอ่านไม่ได้
- ไม่บอกทิศทางของการไหล ทำให้ไม่รู้ว่าใครส่งให้ใคร
- ใช้กับข้อมูลที่ไหลทางเดียว ซึ่ง sankey อ่านง่ายกว่ามาก
- ไม่ใส่ตัวเลขกำกับ ทำให้ต้องเดาจากความหนาที่อ่านได้หยาบ
เมื่อไหร่ไม่ควรใช้กราฟนี้
- การไหลเป็นทางเดียวและมีขั้นชัดเจน→ใช้แทน: sankey-diagram
- มีเส้นเชื่อมเกิน 20 เส้น→ใช้แทน: adjacency-matrix
- ต้องอ่านค่าให้แม่น→ใช้แทน: heatmap-matrix ของการไหล