01ภาพรวมของกราฟนี้
Alluvial diagram แสดงการย้ายหมวดของสมาชิกระหว่างขั้นที่มีชุดหมวดเดียวกัน เช่น ลูกค้าที่อยู่กลุ่มพรีเมียมปีนี้ไปอยู่กลุ่มไหนในปีหน้า ต่างจาก sankey ตรงที่ sankey ไหลไปข้างหน้าสู่โหนดใหม่ ส่วน alluvial วนอยู่ในชุดหมวดเดิม
เปรียบเทียบให้เห็นภาพ
เหมือนแผนภาพการย้ายห้องเรียนของนักเรียนทุกปี ห้องมีชุดเดิมทุกปี แต่คนย้ายไปมาระหว่างห้อง
ใช้ที่ไหนบ้าง — การย้ายระดับลูกค้าระหว่างปี การเปลี่ยนพรรคที่เลือกระหว่างการเลือกตั้ง หรือการย้ายกลุ่มความเสี่ยงของผู้ป่วย
สร้างอย่างไร
- ขั้นที่ 1จัดข้อมูลเป็นหมวดของแต่ละขั้นต่อหนึ่งสมาชิก
- ขั้นที่ 2นับจำนวนที่ย้ายระหว่างทุกคู่หมวด
- ขั้นที่ 3วาดแถบของแต่ละหมวดในแต่ละขั้น
- ขั้นที่ 4ลากริบบิ้นเชื่อมตามจำนวนที่ย้าย
ตัวอย่างคลาสสิก · แผนภาพการย้ายพรรคของผู้มีสิทธิเลือกตั้ง
สื่อวิเคราะห์การเมืองใช้แผนภาพนี้แสดงว่าคนที่เลือกพรรคหนึ่งในการเลือกตั้งครั้งก่อนไปเลือกพรรคไหนในครั้งนี้ ซึ่งเป็นข้อมูลที่ตัวเลขส่วนแบ่งคะแนนอย่างเดียวบอกไม่ได้เลย เพราะพรรคที่ได้คะแนนเท่าเดิมอาจมีการย้ายเข้าออกมหาศาลที่หักล้างกัน
02อธิบายภาษาคนเข้าใจง่าย ๆ
คำถามที่ตารางตอบยากคือ "คนที่เคยอยู่กลุ่ม A ตอนนี้ไปอยู่ไหนกันบ้าง" เพราะต้องดูตารางไขว้แล้วตามทีละช่อง
Alluvial diagram ตอบด้วยภาพ แต่ละขั้นมีแถบของทุกหมวดเรียงกัน และริบบิ้นที่เชื่อมระหว่างขั้นบอกว่ามีกี่คนย้ายจากหมวดไหนไปหมวดไหน
ความต่างจาก sankey ที่ต้องเข้าใจคือ ใน sankey โหนดแต่ละขั้นเป็นคนละอย่างกัน เช่น เข้าชมเว็บแล้วไปดูสินค้า แต่ใน alluvial ทุกขั้นใช้ชุดหมวดเดียวกัน เช่น ระดับลูกค้าในปีที่ 1 2 และ 3
สิ่งที่อ่านได้ทันทีคือความมั่นคง ริบบิ้นที่วิ่งตรงจากหมวดเดิมไปหมวดเดิมคือคนที่ไม่เปลี่ยน ส่วนริบบิ้นที่ไขว้ไปมาคือการย้าย ยิ่งไขว้มากแปลว่าระบบยิ่งไม่นิ่ง
ข้อควรระวังคือเมื่อมีหลายขั้นและหลายหมวด ริบบิ้นจะไขว้กันจนตามเส้นทางของกลุ่มเดียวไม่ได้ ในทางปฏิบัติจึงจำกัดที่ประมาณ 3-4 ขั้นและ 4-5 หมวด
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
source | หมวดในขั้นก่อน | โหนด | path | 2–6 หมวด | ชุดหมวดควรเหมือนกันทุกขั้น |
target | หมวดในขั้นถัดไป | โหนด | path | 2–6 หมวด | ชุดเดียวกับ source |
value | จำนวนที่ย้าย | ตัวเลขต่อเนื่อง | size | บวกเท่านั้น | ความหนาของริบบิ้นแปรตามจำนวน |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- จัดข้อมูลเป็นคู่หมวดต่อเนื่องระหว่างขั้น
- ตรวจว่าผลรวมขาออกของแต่ละหมวดเท่ากับจำนวนสมาชิกในหมวดนั้น
- ตัดสินใจว่าคนที่หายไประหว่างขั้นจะแสดงอย่างไร
ค่าที่หายไป — สมาชิกที่หายไประหว่างขั้นต้องมีหมวด "ออกจากระบบ" ของตัวเอง ห้ามปล่อยให้หายเงียบ ๆ เพราะผลรวมจะไม่ตรง
สเกลของแกน — ความหนาแปรตรงกับจำนวน เหมือน sankey
กราฟจะเริ่มพังเมื่อไร
เกิน 5 ขั้นหรือ 6 หมวด ริบบิ้นจะไขว้กันจนตามไม่ได้
หน้าตาไฟล์ที่ต้องเตรียม
source,target,value
เข้าชมเว็บ,ดูสินค้า,7975
เข้าชมเว็บ,ออกทันที,4078
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- สัดส่วนที่อยู่หมวดเดิมจากริบบิ้นที่วิ่งตรง
- หมวดที่คนไหลเข้ามากที่สุด
- หมวดที่คนไหลออกมากที่สุด
- เส้นทางการย้ายที่พบบ่อยที่สุด
สิ่งที่กราฟนี้ตอบไม่ได้
- ไม่บอกว่าทำไมถึงย้าย
- ตามเส้นทางของกลุ่มเดียวข้ามหลายขั้นได้ยากเมื่อริบบิ้นไขว้กันมาก
อ่านตามลำดับนี้
- ดูแถบของแต่ละหมวดในขั้นแรกว่าใหญ่แค่ไหน
- ตามริบบิ้นที่ออกจากหมวดที่สนใจ
- ดูว่าริบบิ้นส่วนใหญ่วิ่งตรงหรือไขว้
คำถามที่ตอบได้
- คนที่เคยอยู่กลุ่มนี้ไปไหนกันบ้าง
- กลุ่มไหนที่คนอยู่ทนที่สุด
- มีการย้ายข้ามระดับแบบกระโดดไหม
สัญญาณว่ามีอะไรผิด
- ผลรวมของริบบิ้นที่ออกจากหมวดไม่เท่ากับขนาดของหมวดนั้น
- ไม่มีหมวดสำหรับคนที่ออกจากระบบ
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ระบายสีริบบิ้นตามหมวดต้นทางเพื่อให้ตามได้
- เขียนขนาดของแต่ละหมวดในทุกขั้น
- เน้นเส้นทางที่เป็นประเด็นและทำเส้นอื่นเป็นสีจาง
รูปแบบชื่อกราฟที่ควรใช้
<เปอร์เซ็นต์> ของกลุ่ม <หมวด> ยังอยู่หมวดเดิม ขณะที่ <เปอร์เซ็นต์> ย้ายไป <หมวด>
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ alluvial-diagram-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด alluvial-diagram-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("alluvial-diagram-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "straight" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
# matplotlib ไม่มี alluvial ในตัว — แสดงเป็นตารางการย้ายหมวดแทน
pivot = df.pivot_table(index="source", columns="target", values="value", aggfunc="sum").fillna(0)
share = pivot.div(pivot.sum(axis=1), axis=0) # สัดส่วนการย้ายจากแต่ละหมวด
im = ax.imshow(share, cmap="Blues", vmin=0, vmax=1, aspect="auto")
ax.set_xticks(range(len(pivot.columns))); ax.set_xticklabels(pivot.columns, rotation=30, ha="right", fontsize=8)
ax.set_yticks(range(len(pivot.index))); ax.set_yticklabels(pivot.index, fontsize=8)
for i in range(len(pivot.index)):
for j in range(len(pivot.columns)):
if share.iloc[i, j] > 0:
ax.text(j, i, f"{share.iloc[i, j]:.0%}", ha="center", va="center", fontsize=8)
ax.set_ylabel("หมวดเดิม"); ax.set_xlabel("หมวดใหม่")
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("alluvial-diagram.png", dpi=160)
print("เขียน alluvial-diagram.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (8 แถว)
| จาก | ไป | จำนวน |
|---|---|---|
| เข้าชมเว็บ | ดูสินค้า | 7,975 |
| เข้าชมเว็บ | ออกทันที | 4,078 |
| ดูสินค้า | ใส่ตะกร้า | 3,083 |
| ดูสินค้า | ออกกลางทาง | 5,112 |
| ใส่ตะกร้า | กรอกที่อยู่ | 1,952 |
| ใส่ตะกร้า | ทิ้งตะกร้า | 1,166 |
| กรอกที่อยู่ | ชำระเงินสำเร็จ | 1,418 |
| กรอกที่อยู่ | ชำระไม่สำเร็จ | 457 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: เทียบความหนาของริบบิ้นที่วิ่งตรงกับที่ไขว้ จะเห็นว่าระบบนี้มั่นคงแค่ไหน ซึ่งเป็นข้อมูลที่ตารางไขว้บอกได้แต่ต้องคำนวณเอง
ข้อควรระวัง
- ไม่มีหมวดสำหรับคนที่ออกจากระบบ ทำให้ผลรวมไม่ตรงและภาพโกหก
- ใช้ขั้นหรือหมวดมากเกินไปจนริบบิ้นไขว้กันจนตามไม่ได้
- สลับลำดับของหมวดระหว่างขั้น ทำให้ริบบิ้นที่ไม่เปลี่ยนหมวดกลับไขว้
- ใช้กับข้อมูลที่แต่ละขั้นเป็นคนละอย่างกัน ซึ่งควรใช้ sankey แทน
เมื่อไหร่ไม่ควรใช้กราฟนี้
- แต่ละขั้นเป็นคนละสิ่งกัน→ใช้แทน: sankey-diagram
- สนใจแค่ตารางไขว้ของสองขั้น→ใช้แทน: heatmap-matrix
- สนใจว่าคนอยู่ทนแค่ไหนตามเวลา→ใช้แทน: cohort-heatmap