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

แผนที่บิดตามค่าCartogram

ให้พื้นที่บนแผนที่แทนคน ไม่ใช่แทนพื้นดิน

4
ขั้นสูงควรรู้ก่อน · เข้าใจข้อจำกัดของ choropleth
ลองเล่น

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

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

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

เหมือนแผนที่รถไฟใต้ดินที่บิดระยะทางจริงเพื่อให้อ่านง่าย ซึ่งไม่ตรงกับภูมิศาสตร์แต่ตอบคำถามที่ผู้ใช้ถามจริงได้ดีกว่า

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

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

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

ตัวอย่างคลาสสิก · Cartogram ผลการเลือกตั้งประธานาธิบดีสหรัฐ

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

Wikipedia · Cartogram

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

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

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

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

ผลคือพื้นที่บนภาพสื่อความสำคัญที่แท้จริง ซึ่งแก้การตีความผิดที่ร้ายแรงที่สุดของแผนที่ระบายสีได้ตรงจุด

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

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

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

รูปทรงของข้อมูลGeoJSON + ตารางค่าที่ join ด้วย key
จำนวนมิติที่เข้ารหัสได้2 มิติ
จำนวนแถวที่เหมาะ15–60 เขตขั้นต่ำ 8 แถว
เส้นฐานศูนย์ไม่เกี่ยวข้องพื้นที่เริ่มจากศูนย์โดยธรรมชาติ

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

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

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

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

ค่าที่หายไป — เขตที่ไม่มีค่าจะหดจนหายไป ต้องระบุจำนวนเขตที่หายไปเสมอ

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

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

สัดส่วนค่าสูงสุดต่อต่ำสุดเกิน 100 เท่า เขตเล็กจะหายไปหมด

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

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

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

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

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

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

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

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

  1. อ่านก่อนว่าพื้นที่แทนอะไร
  2. เทียบกับแผนที่จริงข้าง ๆ เพื่อดูว่าเขตไหนโตขึ้นหรือหดลง
  3. มองหาเขตที่ใหญ่ที่สุดซึ่งคือเขตที่มีน้ำหนักมากที่สุด

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

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

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

  • ไม่บอกว่าพื้นที่แทนอะไร
  • ไม่มีแผนที่จริงไว้เทียบ
  • บิดจนเขตที่อยู่ติดกันจริงกลับไม่ติดกันบนภาพ

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

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

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

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

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

แผนที่ที่พื้นที่แทน <ตัวแปร> โดย <เขต> ใหญ่ที่สุด

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

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

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

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

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

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

# pip install cartogrampy  หรือใช้ non-contiguous cartogram แบบง่าย
gdf = gpd.read_file("provinces.geojson").merge(df, on="province")
scale = (gdf["rate"] / gdf["rate"].max()) ** 0.5        # รากที่สอง → พื้นที่แปรตามค่า
shrunk = gdf.copy()
shrunk["geometry"] = [
    gpd.GeoSeries(g).scale(s, s, origin=g.centroid).iloc[0]
    for g, s in zip(gdf.geometry, scale)]
gdf.plot(ax=ax, facecolor="none", edgecolor="#e2e0da", linewidth=0.5)   # โครงจริงไว้เทียบ
shrunk.plot(ax=ax, column="region", cmap="tab10", edgecolor="#fcfcfb", linewidth=0.8)
ax.set_axis_off()

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

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

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

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

ขนาดช่องแปรตามค่า ไม่ใช่ตามพื้นที่จริง — ผู้อ่านต้องรู้ข้อนี้ก่อน ไม่อย่างนั้นจะอ่านผิด

สูงสุดเชียงใหม่ · 34.7
ค่าเฉลี่ย22.7
จำนวนพื้นที่12
ดูข้อมูลเป็นตาราง (12 แถว)
แผนที่ 12 พื้นที่ ค่าสูงสุดที่ เชียงใหม่
จังหวัดภูมิภาคค่า (ต่อประชากรหมื่นคน)
กรุงเทพฯเหนือ26.6
เชียงใหม่อีสาน34.7
ขอนแก่นกลาง32.7
ชลบุรีตะวันออก34.4
ภูเก็ตตะวันตก26.8
นครราชสีมาใต้18.3
สงขลาเหนือ10.4
อุดรธานีอีสาน23.1
สุราษฎร์ธานีกลาง20.1
พิษณุโลกตะวันออก17.9
ระยองตะวันตก17.4
นครปฐมใต้10.5

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

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

ข้อควรระวัง

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

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

  • ผู้อ่านต้องใช้แผนที่หาตำแหน่งจริงใช้แทน: choropleth-map
  • ต้องการความเป็นระเบียบและอ่านง่ายใช้แทน: tile-grid-map
  • ต้องเทียบค่าให้แม่นใช้แทน: horizontal-bar

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

เทคนิคถัดไป · แผนที่การไหล