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

แผนที่จุดความหนาแน่นDot Density Map

เห็นความหนาแน่นจริงโดยไม่ถูกขอบเขตการปกครองบิด

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

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

Dot density map วางจุดหนึ่งจุดต่อทุก ๆ จำนวนหน่วย ทำให้เห็นความหนาแน่นที่แท้จริงโดยไม่ถูกขอบเขตการปกครองบิดเบือนแบบ choropleth แลกกับการที่อ่านค่าที่แน่นอนไม่ได้เลยและจุดที่เกินราวสองหมื่นจะอิ่มตัวจนบอกอะไรไม่ได้

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

เหมือนภาพถ่ายดาวเทียมตอนกลางคืนที่แสงไฟบอกว่าคนอยู่ตรงไหนจริง ๆ โดยไม่สนใจเส้นแบ่งจังหวัด

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

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

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

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

โครงการ Racial Dot Map ของมหาวิทยาลัยเวอร์จิเนียวางจุดหนึ่งจุดต่อประชากรหนึ่งคนจากสำมะโนปี 2010 ภาพที่ได้เผยให้เห็นเส้นแบ่งที่ชัดเจนระหว่างย่านต่าง ๆ ซึ่งตัวเลขระดับเขตกลบไว้หมด และกลายเป็นหลักฐานเชิงภาพที่ทรงพลังในการถกเถียงเรื่องนโยบายที่อยู่อาศัย

Wikipedia · Dot distribution map

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

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

Dot density map เลี่ยงปัญหานี้ด้วยการไม่ใช้เขตเลย แต่วางจุดลงไปตรง ๆ โดยหนึ่งจุดแทนจำนวนหน่วยที่กำหนดไว้

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

ราคาที่จ่ายอย่างแรกคืออ่านค่าไม่ได้เลย ไม่มีใครนับจุดได้ และไม่ควรพยายาม กราฟนี้ตอบคำถามว่า "หนาแน่นตรงไหน" ไม่ใช่ "มีเท่าไร"

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

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

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

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

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

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

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

  1. ★ เลือกอัตราส่วนจุดต่อหน่วยให้จุดรวมอยู่ในช่วงที่อ่านได้
  2. สุ่มตำแหน่งภายในพื้นที่ที่อยู่อาศัยจริงเท่านั้น
  3. ล็อกค่าเริ่มต้นของการสุ่มเพื่อให้ภาพซ้ำได้

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

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

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

เกิน 20,000 จุด บริเวณหนาแน่นอิ่มตัวเป็นก้อนทึบ

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

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

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

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

  • บริเวณที่หนาแน่นจริงโดยไม่ถูกเส้นเขตบิด
  • รูปแบบการกระจายตามภูมิประเทศ เช่น ตามแนวถนนหรือแม่น้ำ
  • พื้นที่ว่างที่ไม่มีใครอยู่

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

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

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

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

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

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

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

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

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

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

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

  1. ★ ระบุอัตราส่วนจุดต่อหน่วยไว้ในคำอธิบายให้เด่น
  2. ตีกรอบพื้นที่ที่พูดถึงในเรื่อง
  3. ใส่แผนที่ฐานสีอ่อนเพื่อให้รู้ว่าที่นี่คือที่ไหน

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

การกระจายของ <สิ่งที่วัด> โดยหนึ่งจุดแทน <จำนวน> หน่วย

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

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

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

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

df = pd.read_csv("dot-density-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")
PER = "20"   # ← ค่าจาก Playground

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

PER = 20                                            # หนึ่งจุดแทน 20 หน่วย
rng = np.random.default_rng(42)                     # ล็อกการสุ่มเพื่อให้ภาพซ้ำได้
xs, ys = [], []
for _, r in df.iterrows():
    n = int(r["value"] / PER)
    xs += list(r["lon"] + rng.normal(0, 0.08, n))   # สุ่มรอบพิกัดกลาง
    ys += list(r["lat"] + rng.normal(0, 0.08, n))
ax.scatter(xs, ys, s=3.5, color=C1, alpha=0.45, linewidths=0)
ax.set_xlabel("ลองจิจูด"); ax.set_ylabel("ละติจูด")
ax.set_title(f"หนึ่งจุด = {PER} หน่วย", loc="left")

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

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

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

ข้อมูลตัวอย่าง · 40 แถว · seed 347● คำนวณใหม่ในเบราว์เซอร์ทันทีที่ปรับค่า
อัตราส่วนจุดต่อหน่วย
184 หน่วย50.0 หน่วย80.0 หน่วย162 หน่วย178 หน่วย162 หน่วย138 หน่วย71.0 หน่วย199 หน่วย182 หน่วย90.0 หน่วย141 หน่วย84.0 หน่วย148 หน่วย120 หน่วย65.0 หน่วย76.0 หน่วย150 หน่วย126 หน่วย134 หน่วย96.0 หน่วย31.0 หน่วย160 หน่วย90.0 หน่วย152 หน่วย104 หน่วย86.0 หน่วย26.0 หน่วย184 หน่วย102 หน่วย182 หน่วย145 หน่วย165 หน่วย128 หน่วย125 หน่วย169 หน่วย98.0 หน่วย23.0 หน่วย153 หน่วย174 หน่วยหนึ่งจุด = 20 หน่วย · จุดที่วาดเป็นตำแหน่งโดยประมาณ ไม่ใช่ที่อยู่จริง

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

หนึ่งจุดเท่ากับ20
จุดทั้งหมดที่วาด247
จำนวนตำแหน่ง40
ดูข้อมูลเป็นตาราง (24 แถว)
dot density map 40 ตำแหน่ง
ละติจูดลองจิจูดค่า
13.801100.133184
18.22198.54850.0
7.66399.01180.0
14.373101.583162
18.167100.079178
8.68498.466162
14.958102.031138
19.18098.56171.0
6.85498.738199
13.264100.423182
19.61798.26790.0
7.08598.117141
12.45899.89584.0
18.89598.480148
6.86696.877120
13.118100.55065.0
18.923100.16876.0
7.39898.207150
13.792100.146126
18.54598.655134
7.15398.40696.0
14.90699.64231.0
18.06397.913160
7.67699.10590.0

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

  • หนึ่งจุดแทนกี่หน่วย: ลดจำนวนหน่วยต่อจุดแล้วจุดจะเยอะขึ้นจนบริเวณหนาแน่นกลายเป็นก้อนทึบ — ตัวเลขนี้เปลี่ยนข้อสรุปที่ผู้อ่านได้

ข้อควรระวัง

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

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

  • ต้องอ่านค่าของแต่ละพื้นที่ใช้แทน: choropleth-map พร้อมตารางประกอบ
  • จุดหนาแน่นจนอิ่มตัวใช้แทน: hexbin-map หรือ kernel-heat-map
  • ข้อมูลมีแค่ระดับเขต ไม่มีพิกัดจริงใช้แทน: choropleth-map

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

เทคนิคถัดไป · แผนที่ความร้อน