เทคนิคที่ 089 · PYTHON / JAVASCRIPT

แผนที่การไหลFlow Map

เห็นว่าอะไรไหลจากไหนไปไหน และมากแค่ไหน

3
ผสานหลายแนวคิดควรรู้ก่อน · อ่านแผนที่เป็น
ลองเล่น

01ภาพรวมของกราฟนี้

Flow map ลากเส้นจากต้นทางไปปลายทางโดยความหนาแปรตามปริมาณ เหมาะกับการย้ายถิ่น การค้า หรือการเดินทาง จุดอ่อนคือเส้นตัดกันเยอะจนอ่านไม่ได้เมื่อมีเส้นทางมากกว่าสามสิบเส้น

เปรียบเทียบให้เห็นภาพ

เหมือนแผนภาพเส้นทางบินในนิตยสารบนเครื่องบิน ที่เส้นหนาแปลว่าเส้นทางนั้นมีเที่ยวบินเยอะ

ใช้ที่ไหนบ้าง — การย้ายถิ่นระหว่างจังหวัด เส้นทางการค้าระหว่างประเทศ การเดินทางไปทำงานข้ามเขต หรือการไหลของสินค้าในโซ่อุปทาน

สร้างอย่างไร

  1. ขั้นที่ 1เตรียมข้อมูลคู่ต้นทางปลายทางพร้อมปริมาณ
  2. ขั้นที่ 2ลากเส้นโค้งเพื่อลดการทับซ้อน
  3. ขั้นที่ 3ให้ความหนาแปรตามปริมาณและใส่หัวลูกศรบอกทิศ
  4. ขั้นที่ 4แสดงเฉพาะเส้นทางที่ใหญ่ที่สุดเมื่อเส้นเยอะ

ตัวอย่างคลาสสิก · แผนที่การไหลของสินค้าโดย Charles Minard

Minard วาดแผนที่การไหลจำนวนมากในคริสต์ศตวรรษที่ 19 เช่น การส่งออกถ่านหินและการย้ายถิ่น โดยใช้ความหนาของแถบแทนปริมาณ ซึ่งวางรากฐานให้กับรูปแบบนี้ทั้งหมด และยังเป็นแรงบันดาลใจโดยตรงของ sankey diagram ที่ตัดแผนที่ออกแล้วเหลือแต่การไหล

Wikipedia · Flow map

02อธิบายภาษาคนเข้าใจง่าย ๆ

ข้อมูลการเคลื่อนย้ายมีสามส่วนพร้อมกัน คือมาจากไหน ไปไหน และมากแค่ไหน ซึ่งตารางแสดงได้แต่ทำให้เห็นรูปแบบเชิงพื้นที่ไม่ได้

Flow map รวมทั้งสามส่วนไว้ในภาพเดียว โดยปลายเส้นบอกต้นทางปลายทาง หัวลูกศรบอกทิศ และความหนาบอกปริมาณ

ผลคือรูปแบบใหญ่ ๆ เช่น ทุกเส้นทางใหญ่มุ่งเข้าเมืองหลวง หรือการค้ากระจุกอยู่ในภูมิภาคเดียวกัน ปรากฏขึ้นทันที

ราคาที่จ่ายอย่างแรกคือเส้นตัดกัน เมื่อเกินราวสามสิบเส้น ภาพจะกลายเป็นพันกันจนบอกไม่ได้ว่าเส้นไหนไปไหน

อย่างที่สองคือเส้นทางสั้นเสียเปรียบ เส้นระหว่างสองเมืองที่อยู่ใกล้กันมองเห็นยากกว่าเส้นยาวข้ามประเทศ แม้จะมีปริมาณมากกว่า ซึ่งเป็นการบิดที่ติดมากับรูปแบบนี้

ทางแก้ที่ใช้ได้จริงคือตัดให้เหลือเฉพาะเส้นทางที่ใหญ่ที่สุดสิบถึงยี่สิบเส้น แล้วระบุไว้ตรง ๆ ว่าตัดไปเท่าไรและเหลือสัดส่วนเท่าไรของทั้งหมด

03ข้อมูลที่ต้องใช้

รูปทรงของข้อมูลรายการเส้นเชื่อม source–target–value
จำนวนมิติที่เข้ารหัสได้3 มิติ
จำนวนแถวที่เหมาะ8–25 เส้นทางขั้นต่ำ 4 แถว
เส้นฐานศูนย์ไม่เกี่ยวข้องความหนาเริ่มจากศูนย์โดยธรรมชาติ

คอลัมน์ที่ต้องเตรียม

คอลัมน์ทำหน้าที่ชนิดเข้ารหัสด้วยจำนวนค่าที่เหมาะหมายเหตุ
provinceเขตต้นทางเชิงพื้นที่path5–40 จุดต้องมีพิกัด
region
ไม่บังคับ
ภูมิภาคหมวดหมู่color4–8 กลุ่มใช้สีบอกภูมิภาคต้นทาง
rateปริมาณที่ไหลตัวเลขต่อเนื่องsizeบวกเท่านั้นควบคุมความหนาของเส้น

ต้องเตรียมข้อมูลอย่างไรก่อนวาด

  1. ★ ตัดให้เหลือเฉพาะเส้นทางที่ใหญ่ที่สุดและระบุสัดส่วนที่เหลือ
  2. หาพิกัดของต้นทางและปลายทาง
  3. รวมเส้นทางไปกลับเป็นเส้นเดียวถ้าทิศทางไม่สำคัญ

ค่าที่หายไป — เส้นทางที่ไม่มีพิกัดครบต้องตัดออกและรายงานจำนวน

สเกลของแกน — ความหนาแปรตรงกับปริมาณ และเส้นหนาที่สุดไม่ควรเกินราวยี่สิบพิกเซล

กราฟจะเริ่มพังเมื่อไร

เกิน 40 เส้นทาง เส้นพันกันจนอ่านไม่ได้

หน้าตาไฟล์ที่ต้องเตรียม

province,region,rate
กรุงเทพฯ,เหนือ,26.6
เชียงใหม่,อีสาน,34.7

04อ่านแล้วเห็นอะไร

สิ่งที่เห็นได้จากภาพ

  • เส้นทางที่มีปริมาณมากที่สุด
  • ศูนย์กลางที่มีเส้นเข้าออกมากที่สุด
  • ทิศทางรวมของการเคลื่อนย้าย
  • ภูมิภาคที่เชื่อมกันเองมากกว่าเชื่อมออกนอก

สิ่งที่กราฟนี้ตอบไม่ได้

  • ★ เส้นทางระหว่างที่ใกล้กันมองเห็นยากกว่าเส้นทางไกล แม้ปริมาณจะมากกว่า
  • อ่านปริมาณที่แม่นยำจากความหนา
  • เส้นที่ถูกเส้นอื่นทับ

อ่านตามลำดับนี้

  1. มองหาเส้นที่หนาที่สุดก่อน
  2. ดูจุดที่มีเส้นเข้าออกมากที่สุด
  3. อ่านคำอธิบายว่าตัดเส้นทางเล็กออกไปเท่าไร

คำถามที่ตอบได้

  • เส้นทางไหนมีปริมาณมากที่สุด
  • ที่ไหนเป็นศูนย์กลาง
  • การเคลื่อนย้ายไหลไปทางไหนโดยรวม

สัญญาณว่ามีอะไรผิด

  • ไม่ระบุว่าตัดเส้นทางเล็กออกไปกี่เส้น
  • เส้นตัดกันจนตามไม่ได้
  • ไม่มีหัวลูกศรทั้งที่ทิศทางสำคัญ

05เหมาะกับการนำเสนอแบบไหน

ผู้ชมที่เหมาะทุกคน เพราะเส้นบนแผนที่อ่านได้โดยไม่ต้องสอน
เวลาที่ผู้ชมต้องใช้ไม่กี่วินาที
ขนาดเล็กสุดที่ยังอ่านได้420×400 px
สื่อสารกับคนทั่วไป
รายงานเชิงวิเคราะห์
สิ่งพิมพ์
สไลด์ผู้บริหาร
มือถือเส้นบางและทับกันเมื่อภาพเล็ก

ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย

  1. ★ ระบุว่าแสดงกี่เส้นทางจากทั้งหมดกี่เส้นทาง
  2. ติดค่ากำกับเส้นที่ใหญ่ที่สุดสามเส้น
  3. ใช้ความโปร่งใสเพื่อให้เห็นเส้นที่ทับกัน

รูปแบบชื่อกราฟที่ควรใช้

การไหลของ <สิ่งที่ไหล> โดย <ต้นทาง> ไป <ปลายทาง> มากที่สุด

06เอาไปใช้ต่อ

โค้ดทุกชิ้นอ่านไฟล์ flow-map-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง

# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด flow-map-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg")   # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import geopandas as gpd
import numpy as np

C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a"   # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"

df = pd.read_csv("flow-map-sample.csv")
df["rate"] = pd.to_numeric(df["rate"], errors="coerce")
NOTE = "short"   # ← ค่าจาก Playground

fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)

base = gpd.read_file("provinces.geojson")
base.plot(ax=ax, facecolor="#f2f1ed", edgecolor="#e2e0da", linewidth=0.5)
top = df.nlargest(15, "rate")                            # ตัดให้เหลือเส้นทางใหญ่
for _, r in top.sort_values("rate").iterrows():          # วาดเส้นเล็กก่อน
    x0, y0 = COORD[r["source"]]; x1, y1 = COORD[r["target"]]
    ax.annotate("", xy=(x1, y1), xytext=(x0, y0),
                arrowprops=dict(arrowstyle="-|>", color=C1, alpha=0.7,
                                lw=0.8 + r["rate"] / top["rate"].max() * 5,
                                connectionstyle="arc3,rad=0.18"))
ax.set_axis_off()
ax.set_title(f"แสดง {len(top)} จาก {len(df)} เส้นทาง", loc="left")

ax.spines[["top", "right"]].set_visible(False)   # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("flow-map.png", dpi=160)
print("เขียน flow-map.png แล้ว ·", len(df), "แถว")

ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*" · ทดสอบกับเวอร์ชัน 3.10

07ลองเล่นเพื่อเข้าใจ

ข้อมูลตัวอย่าง · 16 แถว · seed 331● คำนวณใหม่ในเบราว์เซอร์ทันทีที่ปรับค่า
เส้นสั้นเสียเปรียบ
เชียงใหม่ → อุดรธานี · 26.6พิษณุโลก → นครปฐม · 34.7อุดรธานี → ระยอง · 32.7ขอนแก่น → สงขลา · 34.4นครราชสีมา → อุดรธานี · 26.8กรุงเทพฯ → ระยอง · 10.4ชลบุรี → สงขลา · 23.1ระยอง → อุดรธานี · 20.1สุราษฎร์ธานี → นครปฐม · 17.9เชียงใหม่พิษณุโลกอุดรธานีขอนแก่นนครราชสีมานครปฐมกรุงเทพฯชลบุรีระยองสุราษฎร์ธานีภูเก็ตสงขลา

เส้นที่ยาวกว่ากินพื้นที่มากกว่า จึงดูสำคัญกว่าทั้งที่ปริมาณอาจน้อยกว่า

จำนวนเส้นทาง9
เส้นทางที่ใหญ่สุดพิษณุโลก →
ปริมาณรวม227
ดูข้อมูลเป็นตาราง (9 แถว)
flow map 9 เส้นทาง
ต้นทางปลายทางปริมาณ
เชียงใหม่อุดรธานี26.6
พิษณุโลกนครปฐม34.7
อุดรธานีระยอง32.7
ขอนแก่นสงขลา34.4
นครราชสีมาอุดรธานี26.8
กรุงเทพฯระยอง10.4
ชลบุรีสงขลา23.1
ระยองอุดรธานี20.1
สุราษฎร์ธานีนครปฐม17.9

ลองแล้วจะเห็นอะไร

  • สังเกตอะไร: สังเกตว่าเส้นสั้นที่หนาเท่ากับเส้นยาวกลับดึงสายตาน้อยกว่ามาก เพราะพื้นที่หมึกรวมน้อยกว่า

ข้อควรระวัง

  1. ไม่ตัดเส้นทางเล็กออก ทำให้เส้นพันกันจนอ่านไม่ได้
  2. ไม่ระบุว่าแสดงกี่เส้นทางจากทั้งหมดเท่าไร
  3. ลืมว่าเส้นทางสั้นเสียเปรียบเชิงสายตาโดยโครงสร้าง
  4. วาดเส้นใหญ่ก่อนเส้นเล็ก ทำให้เส้นเล็กทับเส้นใหญ่

เมื่อไหร่ไม่ควรใช้กราฟนี้

  • ตำแหน่งทางภูมิศาสตร์ไม่สำคัญใช้แทน: sankey-diagram หรือ chord-diagram
  • มีเส้นทางมากกว่า 40 เส้นใช้แทน: adjacency-matrix
  • สนใจแค่ปริมาณรวมของแต่ละที่ใช้แทน: graduated-symbol-map

กราฟที่เกี่ยวข้อง

เทคนิคถัดไป · แผนที่เส้นชั้นความสูง