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

แผนที่ช่องหกเหลี่ยมHexbin Map

ได้ความหนาแน่นที่นับจริง ไม่ใช่ประมาณ

3
ผสานหลายแนวคิดควรรู้ก่อน · เข้าใจการรวมข้อมูลเป็นช่อง
ลองเล่น

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

Hexbin map แบ่งแผนที่เป็นช่องหกเหลี่ยมขนาดเท่ากันแล้วนับจุดในแต่ละช่อง ต่างจาก kernel heat map ตรงที่ค่าที่เห็นคือจำนวนนับจริงไม่ใช่การประมาณ และต่างจาก choropleth ตรงที่ทุกช่องมีพื้นที่เท่ากันจึงเทียบกันได้ยุติธรรม แลกกับการที่ขนาดช่องยังเป็นค่าที่คนตั้ง

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

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

ใช้ที่ไหนบ้าง — ความหนาแน่นของจุดบริการ การกระจายของการใช้งานแอปตามพื้นที่ หรือความหนาแน่นของเหตุการณ์ที่ต้องเทียบเชิงปริมาณ

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

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

ตัวอย่างคลาสสิก · Hexbin ในการแสดงข้อมูลจุดจำนวนมากบนแผนที่

เมื่อ Uber และบริการเรียกรถอื่นต้องแสดงความต้องการเรียกรถหลายล้านครั้งบนแผนที่ การวาดทุกจุดเป็นไปไม่ได้ ระบบช่องหกเหลี่ยมกลายเป็นมาตรฐานเพราะให้ค่าที่นับได้จริงและเทียบข้ามพื้นที่ได้ จนกลายเป็นระบบดัชนีเชิงพื้นที่ที่เผยแพร่เป็นโอเพนซอร์ส

Wikipedia · Hexagonal binning

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

Kernel heat map สวยแต่มีจุดอ่อนที่ไม่หายไป คือค่าที่เห็นเป็นการประมาณ ไม่มีใครบอกได้ว่าบริเวณนี้มีเหตุการณ์กี่ครั้งจริง ๆ

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

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

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

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

และเหมือนทุกวิธีที่รวมข้อมูลเป็นช่อง ตำแหน่งของเส้นแบ่งช่องมีผลต่อภาพ การเลื่อนตารางไปนิดเดียวก็ทำให้ช่องที่เข้มที่สุดย้ายที่ได้

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

รูปทรงของข้อมูลข้อมูลดิบรายแถว (ยังไม่สรุป)
จำนวนมิติที่เข้ารหัสได้3 มิติ
จำนวนแถวที่เหมาะ300–50,000 จุดขั้นต่ำ 30 แถว
เส้นฐานศูนย์ไม่เกี่ยวข้องค่าเข้ารหัสด้วยสี ไม่มีแกนค่า

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

คอลัมน์ทำหน้าที่ชนิดเข้ารหัสด้วยจำนวนค่าที่เหมาะหมายเหตุ
latละติจูดเชิงพื้นที่yต่อเนื่องพิกัดของจุด
lonลองจิจูดเชิงพื้นที่xต่อเนื่องระบบพิกัดเดียวกับแผนที่ฐาน
value
ไม่บังคับ
ค่าที่รวมตัวเลขต่อเนื่องcolorบวกเท่านั้นไม่ใส่ก็ได้ จะนับจำนวนจุดแทน

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

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

ค่าที่หายไป — ช่องที่ไม่มีจุดเลยควรเว้นว่าง ไม่ใช่ระบายเป็นสีอ่อนสุดของสเกล เพราะสองอย่างนี้ต่างกัน

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

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

จุดน้อยกว่า 50 จุด ช่องส่วนใหญ่ว่างจนอ่านไม่ได้

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

lat,lon,value
13.8013,100.1331,184
18.2209,98.5482,50

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

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

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

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

  • ★ ขนาดช่องเป็นค่าที่คนตั้ง และเปลี่ยนแล้วภาพก็เปลี่ยน
  • ตำแหน่งที่แน่นอนของจุดภายในช่อง
  • รูปแบบที่ละเอียดกว่าขนาดช่อง

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

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

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

  • บริเวณไหนมีค่ามากที่สุด
  • ค่ากระจายตัวอย่างไรในพื้นที่
  • มีกี่บริเวณที่มีค่าสูง

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

  • ไม่ระบุขนาดช่องเป็นหน่วยจริง
  • ช่องที่ว่างถูกระบายเหมือนช่องที่มีค่าต่ำ
  • ช่องเล็กเกินจนภาพเป็นจุดกระจัดกระจาย

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

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

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

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

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

<ตัวชี้วัด> ต่อพื้นที่ <ขนาดช่อง> โดยบริเวณ <ที่ไหน> สูงที่สุด

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

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

# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด hexbin-map-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("hexbin-map-sample.csv")
df["lat"] = pd.to_numeric(df["lat"], errors="coerce")
df["lon"] = pd.to_numeric(df["lon"], errors="coerce")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "equal"   # ← ค่าจาก Playground

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

hb = ax.hexbin(df["lon"], df["lat"], C=df["value"], reduce_C_function=np.sum,
                gridsize=14,                            # จำนวนช่องในแนวนอน
                cmap="Blues", mincnt=1,                 # mincnt=1 → ช่องว่างไม่ถูกระบาย
                edgecolors="#fcfcfb", linewidths=0.4)
fig.colorbar(hb, ax=ax, label="ผลรวมในช่อง", shrink=0.8)
ax.set_xlabel("ลองจิจูด"); ax.set_ylabel("ละติจูด")
ax.set_title("หนึ่งช่อง ≈ 12 กม.", loc="left")

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

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

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

ข้อมูลตัวอย่าง · 40 แถว · seed 347● คำนวณใหม่ในเบราว์เซอร์ทันทีที่ปรับค่า
ทุกช่องพื้นที่เท่ากัน
3 ตำแหน่ง · 4924 ตำแหน่ง · 4573 ตำแหน่ง · 3691 ตำแหน่ง · 1622 ตำแหน่ง · 2541 ตำแหน่ง · 1621 ตำแหน่ง · 1384 ตำแหน่ง · 4103 ตำแหน่ง · 3754 ตำแหน่ง · 5561 ตำแหน่ง · 84.01 ตำแหน่ง · 1201 ตำแหน่ง · 31.01 ตำแหน่ง · 1601 ตำแหน่ง · 1521 ตำแหน่ง · 86.03 ตำแหน่ง · 2981 ตำแหน่ง · 1841 ตำแหน่ง · 1022 ตำแหน่ง · 3181 ตำแหน่ง · 23.00.00556
จำนวนช่อง21
ช่องที่มากที่สุด556
ทุกช่องขนาดเท่ากันใช่
ดูข้อมูลเป็นตาราง (14 แถว)
hexbin map 21 ช่อง
ช่องจำนวนตำแหน่งค่ารวม
อันดับ 14556
อันดับ 23492
อันดับ 34457
อันดับ 44410
อันดับ 53375
อันดับ 63369
อันดับ 72318
อันดับ 83298
อันดับ 92254
อันดับ 101184
อันดับ 111162
อันดับ 121162
อันดับ 131160
อันดับ 141152

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

  • สังเกตอะไร: เทียบกับหน้า choropleth-map จะเห็นว่าที่นี่ไม่มีเขตไหนครองสายตาเพราะพื้นที่ใหญ่ ทุกช่องเท่ากันหมด

ข้อควรระวัง

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

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

  • ต้องการภาพที่ดูเรียบและเล่าเรื่องง่ายใช้แทน: kernel-heat-map
  • ข้อมูลผูกกับเขตการปกครองอยู่แล้วใช้แทน: choropleth-map
  • จุดน้อยกว่า 50 จุดใช้แทน: dot-density-map

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

เทคนิคถัดไป · แผนที่บิดตามค่า