01ภาพรวมของกราฟนี้
Hexbin map แบ่งแผนที่เป็นช่องหกเหลี่ยมขนาดเท่ากันแล้วนับจุดในแต่ละช่อง ต่างจาก kernel heat map ตรงที่ค่าที่เห็นคือจำนวนนับจริงไม่ใช่การประมาณ และต่างจาก choropleth ตรงที่ทุกช่องมีพื้นที่เท่ากันจึงเทียบกันได้ยุติธรรม แลกกับการที่ขนาดช่องยังเป็นค่าที่คนตั้ง
เปรียบเทียบให้เห็นภาพ
เหมือนแบ่งสนามเป็นตารางเท่ากันแล้วนับจำนวนคนในแต่ละช่อง ซึ่งเทียบช่องต่อช่องได้ตรงไปตรงมา
ใช้ที่ไหนบ้าง — ความหนาแน่นของจุดบริการ การกระจายของการใช้งานแอปตามพื้นที่ หรือความหนาแน่นของเหตุการณ์ที่ต้องเทียบเชิงปริมาณ
สร้างอย่างไร
- ขั้นที่ 1เลือกขนาดช่องหกเหลี่ยม
- ขั้นที่ 2นับจุดหรือรวมค่าในแต่ละช่อง
- ขั้นที่ 3ใช้สเกลสีลำดับเดียว
- ขั้นที่ 4ระบุขนาดช่องเป็นหน่วยพื้นที่จริงในคำอธิบาย
ตัวอย่างคลาสสิก · Hexbin ในการแสดงข้อมูลจุดจำนวนมากบนแผนที่
เมื่อ Uber และบริการเรียกรถอื่นต้องแสดงความต้องการเรียกรถหลายล้านครั้งบนแผนที่ การวาดทุกจุดเป็นไปไม่ได้ ระบบช่องหกเหลี่ยมกลายเป็นมาตรฐานเพราะให้ค่าที่นับได้จริงและเทียบข้ามพื้นที่ได้ จนกลายเป็นระบบดัชนีเชิงพื้นที่ที่เผยแพร่เป็นโอเพนซอร์ส
02อธิบายภาษาคนเข้าใจง่าย ๆ
Kernel heat map สวยแต่มีจุดอ่อนที่ไม่หายไป คือค่าที่เห็นเป็นการประมาณ ไม่มีใครบอกได้ว่าบริเวณนี้มีเหตุการณ์กี่ครั้งจริง ๆ
Hexbin map เลือกเส้นทางตรงข้าม คือแบ่งพื้นที่เป็นช่องแล้วนับจริงในแต่ละช่อง ค่าที่เห็นจึงเป็นจำนวนนับที่ตรวจสอบได้
เหตุผลที่ใช้หกเหลี่ยมแทนสี่เหลี่ยมคือหกเหลี่ยมมีเพื่อนบ้านที่ระยะเท่ากันทั้งหกด้าน ส่วนสี่เหลี่ยมมีเพื่อนบ้านแนวทแยงที่อยู่ไกลกว่า ซึ่งทำให้เกิดลายเส้นแนวตั้งและแนวนอนปลอม ๆ
ข้อได้เปรียบเหนือ choropleth คือทุกช่องมีพื้นที่เท่ากัน จึงเทียบกันได้โดยไม่ต้องกังวลว่าเขตใหญ่จะครองสายตา
ข้อจำกัดที่เหลืออยู่คือขนาดช่องยังเป็นค่าที่คนตั้ง ช่องเล็กเกินจะได้ภาพที่กระจัดกระจายเป็นจุด ๆ ช่องใหญ่เกินจะกลบรายละเอียดจนเหลือแค่ก้อนเดียว
และเหมือนทุกวิธีที่รวมข้อมูลเป็นช่อง ตำแหน่งของเส้นแบ่งช่องมีผลต่อภาพ การเลื่อนตารางไปนิดเดียวก็ทำให้ช่องที่เข้มที่สุดย้ายที่ได้
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
lat | ละติจูด | เชิงพื้นที่ | y | ต่อเนื่อง | พิกัดของจุด |
lon | ลองจิจูด | เชิงพื้นที่ | x | ต่อเนื่อง | ระบบพิกัดเดียวกับแผนที่ฐาน |
valueไม่บังคับ | ค่าที่รวม | ตัวเลขต่อเนื่อง | color | บวกเท่านั้น | ไม่ใส่ก็ได้ จะนับจำนวนจุดแทน |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- เลือกขนาดช่องให้แต่ละช่องมีจุดเฉลี่ยพอสมควร
- ตัดจุดที่พิกัดผิดปกติออก
- ตัดสินใจว่าจะนับจำนวนหรือรวมค่า
ค่าที่หายไป — ช่องที่ไม่มีจุดเลยควรเว้นว่าง ไม่ใช่ระบายเป็นสีอ่อนสุดของสเกล เพราะสองอย่างนี้ต่างกัน
สเกลของแกน — ใช้สเกลสีลำดับเดียว และเมื่อค่ากระจายแบบหางยาวให้พิจารณาสเกลลอการิทึมพร้อมระบุไว้
กราฟจะเริ่มพังเมื่อไร
จุดน้อยกว่า 50 จุด ช่องส่วนใหญ่ว่างจนอ่านไม่ได้
หน้าตาไฟล์ที่ต้องเตรียม
lat,lon,value
13.8013,100.1331,184
18.2209,98.5482,50
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- ช่องที่มีค่าสูงสุดซึ่งเทียบกันได้ตรงไปตรงมา
- รูปแบบการกระจายเชิงพื้นที่
- ช่องว่างที่ไม่มีเหตุการณ์เลย
สิ่งที่กราฟนี้ตอบไม่ได้
- ★ ขนาดช่องเป็นค่าที่คนตั้ง และเปลี่ยนแล้วภาพก็เปลี่ยน
- ตำแหน่งที่แน่นอนของจุดภายในช่อง
- รูปแบบที่ละเอียดกว่าขนาดช่อง
อ่านตามลำดับนี้
- อ่านคำอธิบายว่าหนึ่งช่องกว้างเท่าไรและสีแทนค่าอะไร
- มองหาช่องที่เข้มที่สุด
- ดูว่าช่องเข้มอยู่ติดกันเป็นบริเวณหรือกระจัดกระจาย
คำถามที่ตอบได้
- บริเวณไหนมีค่ามากที่สุด
- ค่ากระจายตัวอย่างไรในพื้นที่
- มีกี่บริเวณที่มีค่าสูง
สัญญาณว่ามีอะไรผิด
- ไม่ระบุขนาดช่องเป็นหน่วยจริง
- ช่องที่ว่างถูกระบายเหมือนช่องที่มีค่าต่ำ
- ช่องเล็กเกินจนภาพเป็นจุดกระจัดกระจาย
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ★ ระบุขนาดช่องเป็นหน่วยพื้นที่จริง เช่น หนึ่งช่องเท่ากับห้ากิโลเมตร
- เขียนค่าในช่องที่สูงที่สุดสองสามช่อง
- ใส่แผนที่ฐานสีอ่อนเพื่อบอกบริบท
รูปแบบชื่อกราฟที่ควรใช้
<ตัวชี้วัด> ต่อพื้นที่ <ขนาดช่อง> โดยบริเวณ <ที่ไหน> สูงที่สุด
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), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (14 แถว)
| ช่อง | จำนวนตำแหน่ง | ค่ารวม |
|---|---|---|
| อันดับ 1 | 4 | 556 |
| อันดับ 2 | 3 | 492 |
| อันดับ 3 | 4 | 457 |
| อันดับ 4 | 4 | 410 |
| อันดับ 5 | 3 | 375 |
| อันดับ 6 | 3 | 369 |
| อันดับ 7 | 2 | 318 |
| อันดับ 8 | 3 | 298 |
| อันดับ 9 | 2 | 254 |
| อันดับ 10 | 1 | 184 |
| อันดับ 11 | 1 | 162 |
| อันดับ 12 | 1 | 162 |
| อันดับ 13 | 1 | 160 |
| อันดับ 14 | 1 | 152 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: เทียบกับหน้า choropleth-map จะเห็นว่าที่นี่ไม่มีเขตไหนครองสายตาเพราะพื้นที่ใหญ่ ทุกช่องเท่ากันหมด
ข้อควรระวัง
- ไม่ระบุขนาดช่องเป็นหน่วยพื้นที่จริง ทำให้เทียบกับแผนที่อื่นไม่ได้
- ระบายช่องว่างด้วยสีอ่อนสุดของสเกล ทำให้ "ไม่มีข้อมูล" ปนกับ "มีน้อย"
- เลือกขนาดช่องเล็กเกินจนภาพกระจัดกระจายเป็นจุด ๆ
- ใช้กับข้อมูลน้อยเกินไปจนช่องส่วนใหญ่ว่าง
เมื่อไหร่ไม่ควรใช้กราฟนี้
- ต้องการภาพที่ดูเรียบและเล่าเรื่องง่าย→ใช้แทน: kernel-heat-map
- ข้อมูลผูกกับเขตการปกครองอยู่แล้ว→ใช้แทน: choropleth-map
- จุดน้อยกว่า 50 จุด→ใช้แทน: dot-density-map