01ภาพรวมของกราฟนี้
Kernel heat map เกลี่ยจุดแต่ละจุดเป็นเนินแล้วรวมกันเป็นพื้นผิวความหนาแน่น ทำให้จุดร้อนอ่านง่ายกว่าแผนที่จุดดิบมาก แต่รัศมีการเกลี่ยเป็นค่าที่คนตั้งและเปลี่ยนภาพได้ทั้งหมด จึงต้องระบุเสมอ และพื้นผิวที่ได้ก็ไม่ใช่ข้อมูลจริงแต่เป็นการประมาณ
เปรียบเทียบให้เห็นภาพ
เหมือนภาพถ่ายความร้อนที่บริเวณอุ่นกลืนเข้าหากันเป็นพื้นผิวเรียบ แทนที่จะเห็นเป็นจุดแยก ๆ
ใช้ที่ไหนบ้าง — จุดร้อนของอาชญากรรมในเมือง ความหนาแน่นของอุบัติเหตุบนถนน หรือบริเวณที่ผู้ใช้แอปกระจุกตัว
สร้างอย่างไร
- ขั้นที่ 1เลือกรัศมีการเกลี่ยตามสเกลของปรากฏการณ์
- ขั้นที่ 2คำนวณพื้นผิวความหนาแน่นจากทุกจุด
- ขั้นที่ 3ใช้สเกลสีลำดับเดียวและระบุหน่วย
- ขั้นที่ 4แสดงจุดดิบทับด้วยเพื่อให้เห็นว่าข้อมูลจริงมีแค่ไหน
ตัวอย่างคลาสสิก · แผนที่จุดร้อนอาชญากรรมในงานตำรวจเชิงพื้นที่
หน่วยงานตำรวจหลายแห่งใช้แผนที่ความร้อนกำหนดจุดตรวจตรา ซึ่งได้ผลในการลดเหตุในบริเวณนั้น แต่ก็เกิดข้อวิจารณ์สำคัญว่าจุดร้อนสะท้อนที่ที่ตำรวจไปตรวจบ่อย มากกว่าที่ที่เกิดเหตุจริง ซึ่งเป็นตัวอย่างชัดเจนว่าภาพที่ดูน่าเชื่อถืออาจสะท้อนวิธีเก็บข้อมูลมากกว่าปรากฏการณ์
02อธิบายภาษาคนเข้าใจง่าย ๆ
แผนที่จุดดิบมีปัญหาเมื่อจุดเยอะ เพราะจุดทับกันจนบอกไม่ได้ว่าบริเวณไหนหนาแน่นกว่ากันจริง ๆ
Kernel heat map แก้ด้วยการเกลี่ย แทนที่จะวาดจุดเป็นจุด มันวาดแต่ละจุดเป็นเนินเตี้ย ๆ ที่สูงตรงกลางและค่อย ๆ ลดลงตามระยะ แล้วบวกเนินทั้งหมดเข้าด้วยกัน
ผลคือพื้นผิวเรียบที่บริเวณจุดกระจุกกันจะสูงและเข้ม ซึ่งเป็นภาพที่สายตาอ่านได้ทันทีโดยไม่ต้องเพ่งนับจุด
ราคาที่จ่ายอย่างแรกและสำคัญที่สุดคือรัศมีการเกลี่ยเป็นค่าที่คนตั้ง รัศมีเล็กให้จุดร้อนหลายจุดแยกกัน รัศมีใหญ่ให้จุดร้อนใหญ่จุดเดียว จากข้อมูลชุดเดียวกันทุกประการ
อย่างที่สองคือพื้นผิวที่เห็นไม่ใช่ข้อมูล แต่เป็นการประมาณ ค่าที่บริเวณหนึ่งอาจสูงเพราะมีจุดข้างเคียงหลายจุด ทั้งที่ตรงนั้นไม่มีข้อมูลเลยสักจุดเดียว
ผลที่ตามมาคือกราฟนี้ตอบได้แค่ว่าจุดร้อนอยู่บริเวณไหนโดยประมาณ ไม่ได้ตอบว่าที่ตำแหน่งนี้มีค่าเท่าไร และห้ามใช้ตัดสินใจระดับรายตำแหน่ง
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
lat | ละติจูด | เชิงพื้นที่ | y | ต่อเนื่อง | พิกัดของเหตุการณ์ |
lon | ลองจิจูด | เชิงพื้นที่ | x | ต่อเนื่อง | ระบบพิกัดเดียวกับแผนที่ฐาน |
valueไม่บังคับ | น้ำหนัก | ตัวเลขต่อเนื่อง | color | บวกเท่านั้น | ไม่ใส่ก็ได้ ถือว่าทุกจุดหนักเท่ากัน |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- ★ เลือกรัศมีการเกลี่ยตามสเกลของปรากฏการณ์และบันทึกไว้
- ตัดจุดที่พิกัดผิดปกติออก
- ตัดสินใจว่าจะแสดงจุดดิบทับหรือไม่
ค่าที่หายไป — บริเวณที่ไม่มีการเก็บข้อมูลจะดูเหมือนบริเวณที่ไม่มีเหตุการณ์ ต้องตีกรอบขอบเขตการเก็บข้อมูลให้ชัด
สเกลของแกน — ใช้สเกลสีลำดับเดียวไล่อ่อนไปเข้ม และระบุว่าค่าที่แสดงคือความหนาแน่นประมาณ ไม่ใช่จำนวนนับ
กราฟจะเริ่มพังเมื่อไร
จุดน้อยกว่า 30 จุด พื้นผิวที่ได้เป็นภาพลวงมากกว่าข้อมูล
หน้าตาไฟล์ที่ต้องเตรียม
lat,lon,value
13.8013,100.1331,184
18.2209,98.5482,50
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- ตำแหน่งของจุดร้อนหลัก
- จำนวนจุดร้อนที่แยกจากกัน
- ทิศทางการแผ่ของความหนาแน่น
สิ่งที่กราฟนี้ตอบไม่ได้
- ★ ค่าที่ตำแหน่งใดตำแหน่งหนึ่ง เพราะพื้นผิวเป็นการประมาณไม่ใช่ข้อมูลจริง
- ★ ขอบเขตของจุดร้อนที่แน่นอน เพราะขึ้นกับรัศมีที่คนตั้ง
- จำนวนเหตุการณ์ในบริเวณหนึ่ง
อ่านตามลำดับนี้
- อ่านคำอธิบายว่ารัศมีการเกลี่ยเท่าไร
- มองหาบริเวณที่เข้มที่สุด
- ดูจุดดิบที่ทับอยู่เพื่อตรวจว่าจุดร้อนนั้นมาจากข้อมูลจริงหรือจากการเกลี่ย
คำถามที่ตอบได้
- จุดร้อนอยู่บริเวณไหน
- มีจุดร้อนกี่จุด
- จุดร้อนกระจุกหรือกระจาย
สัญญาณว่ามีอะไรผิด
- ไม่ระบุรัศมีการเกลี่ย
- จุดร้อนในบริเวณที่แทบไม่มีจุดดิบเลย
- ใช้กับข้อมูลน้อยกว่า 30 จุด
- ใช้สเกลสีรุ้ง
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ★ ระบุรัศมีการเกลี่ยไว้ในคำอธิบายเสมอ
- แสดงจุดดิบทับเพื่อให้ตรวจสอบได้
- ตีกรอบขอบเขตที่เก็บข้อมูลให้ชัด
รูปแบบชื่อกราฟที่ควรใช้
จุดร้อนของ <เหตุการณ์> โดยเกลี่ยด้วยรัศมี <ค่า>
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ kernel-heat-map-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด kernel-heat-map-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from scipy.stats import gaussian_kde
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("kernel-heat-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")
RADIUS = "46" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
kde = gaussian_kde(np.vstack([df["lon"], df["lat"]]),
weights=df["value"], bw_method=0.3) # bw_method = รัศมีการเกลี่ย
gx, gy = np.mgrid[df["lon"].min():df["lon"].max():160j,
df["lat"].min():df["lat"].max():160j]
z = kde(np.vstack([gx.ravel(), gy.ravel()])).reshape(gx.shape)
im = ax.pcolormesh(gx, gy, z, cmap="Blues", shading="gouraud")
ax.scatter(df["lon"], df["lat"], s=6, color="#22201c", alpha=0.5) # จุดดิบทับให้ตรวจได้
fig.colorbar(im, ax=ax, label="ความหนาแน่นประมาณ", shrink=0.8)
ax.set_title("bw = 0.3", loc="left")
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("kernel-heat-map.png", dpi=160)
print("เขียน kernel-heat-map.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
รัศมีที่เลือกเปลี่ยนตำแหน่งของจุดร้อนได้ — รัศมีกว้างทำให้จุดร้อนหลายจุดรวมเป็นจุดเดียว
ดูข้อมูลเป็นตาราง (24 แถว)
| ละติจูด | ลองจิจูด | ค่า |
|---|---|---|
| 13.801 | 100.133 | 184 |
| 18.221 | 98.548 | 50.0 |
| 7.663 | 99.011 | 80.0 |
| 14.373 | 101.583 | 162 |
| 18.167 | 100.079 | 178 |
| 8.684 | 98.466 | 162 |
| 14.958 | 102.031 | 138 |
| 19.180 | 98.561 | 71.0 |
| 6.854 | 98.738 | 199 |
| 13.264 | 100.423 | 182 |
| 19.617 | 98.267 | 90.0 |
| 7.085 | 98.117 | 141 |
| 12.458 | 99.895 | 84.0 |
| 18.895 | 98.480 | 148 |
| 6.866 | 96.877 | 120 |
| 13.118 | 100.550 | 65.0 |
| 18.923 | 100.168 | 76.0 |
| 7.398 | 98.207 | 150 |
| 13.792 | 100.146 | 126 |
| 18.545 | 98.655 | 134 |
| 7.153 | 98.406 | 96.0 |
| 14.906 | 99.642 | 31.0 |
| 18.063 | 97.913 | 160 |
| 7.676 | 99.105 | 90.0 |
ลองแล้วจะเห็นอะไร
- รัศมีการเกลี่ย: ลองเลื่อนจากแคบไปกว้างแล้วดูว่าจุดร้อนสามจุดรวมเป็นจุดเดียวได้อย่างไร — ข้อมูลไม่เปลี่ยนเลย แต่ข้อสรุปเปลี่ยนไปคนละเรื่อง
ข้อควรระวัง
- ★ ไม่ระบุรัศมีการเกลี่ย ทั้งที่ค่านี้เปลี่ยนจำนวนจุดร้อนที่เห็นได้ทั้งหมด
- ตีความพื้นผิวว่าเป็นข้อมูลจริง ทั้งที่เป็นการประมาณ
- ใช้กับข้อมูลน้อยเกินไปจนพื้นผิวเป็นภาพลวง
- ไม่แสดงจุดดิบ ทำให้ผู้อ่านตรวจสอบไม่ได้ว่าจุดร้อนมาจากไหน
เมื่อไหร่ไม่ควรใช้กราฟนี้
- ต้องอ่านค่าของแต่ละบริเวณ→ใช้แทน: hexbin-map ที่นับจริงในแต่ละช่อง
- มีจุดข้อมูลน้อยกว่า 30 จุด→ใช้แทน: dot-density-map หรือ scatter-plot
- ข้อมูลผูกกับเขตการปกครอง→ใช้แทน: choropleth-map