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

กราฟดัมเบลDumbbell Chart

เห็นทั้งจุดเริ่ม จุดจบ และระยะที่ขยับ

3
ผสานหลายแนวคิดควรรู้ก่อน · อ่านกราฟจุดและกราฟแท่งเป็น
ลองเล่น

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

Dumbbell chart วางค่าของสองช่วงเวลา (หรือสองกลุ่ม) ไว้บนแถวเดียวกันแล้วเชื่อมด้วยเส้น ผลคือคนอ่านได้ข้อมูลสามอย่างในภาพเดียว คือค่าเริ่ม ค่าจบ และความยาวของเส้นซึ่งคือขนาดของการเปลี่ยนแปลง

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

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

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

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

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

ตัวอย่างคลาสสิก · ช่องว่างค่าจ้างระหว่างเพศในแต่ละอาชีพ

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

Financial Times · Visual Vocabulary

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

เวลาอยากบอกว่า "ปีนี้ดีขึ้นกว่าปีที่แล้วไหม" แยกตามสาขา คนมักวาดกราฟแท่งกลุ่มที่มีสองแท่งต่อสาขา ซึ่งอ่านค่าได้ แต่ขนาดของการเปลี่ยนแปลงต้องคำนวณในหัวเอง

Dumbbell แก้ด้วยการวางค่าทั้งสองไว้บนเส้นเดียวกันแล้วเชื่อมด้วยเส้นตรง ทีนี้ "ความยาวของเส้น" กลายเป็นตัวแทนของการเปลี่ยนแปลงโดยตรง ยาวแปลว่าเปลี่ยนมาก สั้นแปลว่าเกือบเท่าเดิม

ทิศทางก็อ่านได้ฟรีเช่นกัน ถ้าจุดสีเข้มอยู่ขวาของจุดสีอ่อนแปลว่าเพิ่มขึ้น ถ้าอยู่ซ้ายแปลว่าลดลง คนอ่านจึงได้ข้อมูลสามอย่างโดยแทบไม่ต้องคิด

การเรียงลำดับเป็นเครื่องมือสำคัญ ถ้าเรียงตามขนาดการเปลี่ยนแปลง เราจะเห็นทันทีว่าใครขยับมากที่สุด แต่ถ้าเรียงตามค่าปัจจุบัน เราจะเห็นอันดับล่าสุดแทน สองแบบนี้ตอบคนละคำถาม

ข้อจำกัดคือรองรับได้แค่สองจุดเวลา ถ้ามีสามช่วงขึ้นไปเส้นจะเริ่มสับสน ให้เปลี่ยนไปใช้ slope chart หรือ multi-line chart ภาพจำคือ สองจุดใช้ดัมเบล สามจุดขึ้นไปใช้เส้น

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

รูปทรงของข้อมูลตารางแบบกว้าง (หนึ่งแถวหนึ่ง entity)
จำนวนมิติที่เข้ารหัสได้3 มิติ
จำนวนแถวที่เหมาะ6–20 รายการขั้นต่ำ 4 แถว
เส้นฐานศูนย์เลือกได้อ่านจากตำแหน่งของจุดและระยะระหว่างจุด ไม่ได้อ่านจากระยะถึงศูนย์ จึงย่อแกนได้ถ้าระบุให้ชัด

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

คอลัมน์ทำหน้าที่ชนิดเข้ารหัสด้วยจำนวนค่าที่เหมาะหมายเหตุ
categoryรายการหมวดหมู่y5–25 รายการหนึ่งแถวหนึ่งรายการ
beforeค่าก่อนตัวเลขต่อเนื่องxต่อเนื่องต้องเป็นหน่วยเดียวกับ after
afterค่าหลังตัวเลขต่อเนื่องxต่อเนื่องถ้าหน่วยต่างกันจะเทียบไม่ได้ ต้องแปลงให้เหมือนกันก่อน

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

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

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

สเกลของแกน — linear และใช้สเกลเดียวกันทั้งสองจุดเสมอ ถ้าค่ากินหลายเท่าให้ใช้ log แต่ต้องรู้ว่าความยาวเส้นจะไม่แปรตามส่วนต่างอีกต่อไป

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

เกิน 30 รายการ เส้นจะชิดจนอ่านทิศทางไม่ออก

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

category,before,after

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

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

  • รายการที่เปลี่ยนแปลงมากที่สุด จากความยาวของเส้น
  • ทิศทางของการเปลี่ยนแปลงรายรายการ
  • รายการที่เกือบไม่เปลี่ยนเลย
  • การสลับอันดับ — รายการที่แซงกันระหว่างสองช่วง

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

  • ไม่บอกว่าเกิดอะไรขึ้นระหว่างสองจุดเวลา — อาจขึ้นแล้วลงหรือขึ้นตรง ๆ ก็ได้
  • ไม่บอกว่าการเปลี่ยนแปลงมีนัยสำคัญทางสถิติหรือไม่
  • ไม่รองรับมากกว่าสองจุดเวลา

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

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

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

  • ใครดีขึ้นมากที่สุด
  • มีกี่รายการที่แย่ลง
  • ช่องว่างระหว่างสองกลุ่มแคบลงหรือกว้างขึ้น

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

  • ไม่มี legend บอกว่าจุดสีไหนคือก่อนหรือหลัง
  • เส้นทับกันจนแยกไม่ออกว่าเส้นไหนของใคร
  • ใช้สองสีที่ดูเท่ากันจนไม่รู้ว่าอันไหนมาก่อน

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

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

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

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

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

<รายการ> เปลี่ยนมากที่สุดที่ <ส่วนต่าง> ขณะที่ <จำนวน> รายการแทบไม่ขยับ

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

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

# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด dumbbell-chart-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("dumbbell-chart-sample.csv")
df["before"] = pd.to_numeric(df["before"], errors="coerce")
df["after"] = pd.to_numeric(df["after"], errors="coerce")
SORT = "delta"   # ← ค่าจาก Playground

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

df["delta"] = df["after"] - df["before"]
d = df.sort_values("delta")
ax.hlines(d["category"], d["before"], d["after"], color="#9ec5f4", linewidth=3)
ax.plot(d["before"], d["category"], "o", markersize=9, color="#9ec5f4", label="ก่อน")
ax.plot(d["after"], d["category"], "o", markersize=9, color="#1c5cab", label="หลัง")
ax.legend(frameon=False, ncols=2)   # ต้องมี legend เพราะสีเป็นตัวบอกลำดับเวลา
ax.set_xlabel("คะแนน")

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

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

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

ข้อมูลตัวอย่าง · 12 แถว · seed 23● คำนวณใหม่ในเบราว์เซอร์ทันทีที่ปรับค่า
ลำดับของแถว
ก่อนหลัง
ชลบุรีกรุงเทพฯเชียงใหม่สงขลานครปฐมสุราษฎร์ธานีระยองนครราชสีมาขอนแก่นอุดรธานีพิษณุโลกภูเก็ต40.050.060.070.080.090.0ชลบุรี · ก่อน 47.0 → หลัง 63.0 (+16.0)กรุงเทพฯ · ก่อน 42.0 → หลัง 56.0 (+14.0)เชียงใหม่ · ก่อน 45.0 → หลัง 53.0 (+8.00)สงขลา · ก่อน 59.0 → หลัง 66.0 (+7.00)นครปฐม · ก่อน 74.0 → หลัง 81.0 (+7.00)สุราษฎร์ธานี · ก่อน 68.0 → หลัง 73.0 (+5.00)ระยอง · ก่อน 69.0 → หลัง 73.0 (+4.00)นครราชสีมา · ก่อน 64.0 → หลัง 67.0 (+3.00)ขอนแก่น · ก่อน 48.0 → หลัง 49.0 (+1.00)อุดรธานี · ก่อน 71.0 → หลัง 68.0 (-3.00)พิษณุโลก · ก่อน 67.0 → หลัง 64.0 (-3.00)ภูเก็ต · ก่อน 61.0 → หลัง 55.0 (-6.00)+16.0
เปลี่ยนมากที่สุดชลบุรี · +16.0
เพิ่มขึ้น9 จาก 12
ส่วนต่างเฉลี่ย4.42
ดูข้อมูลเป็นตาราง (12 แถว)
กราฟดัมเบล 12 หมวด เปลี่ยนแปลงมากที่สุดคือ ชลบุรี
หมวดก่อนหลังส่วนต่าง
ชลบุรี47.063.016.0
กรุงเทพฯ42.056.014.0
เชียงใหม่45.053.08.00
สงขลา59.066.07.00
นครปฐม74.081.07.00
สุราษฎร์ธานี68.073.05.00
ระยอง69.073.04.00
นครราชสีมา64.067.03.00
ขอนแก่น48.049.01.00
อุดรธานี71.068.0-3.00
พิษณุโลก67.064.0-3.00
ภูเก็ต61.055.0-6.00

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

  • เรียงตาม: สลับสองแบบแล้วสังเกตว่าภาพเล่าคนละเรื่อง — เรียงตามส่วนต่างตอบว่า "ใครขยับมากสุด" ส่วนเรียงตามค่าล่าสุดตอบว่า "ตอนนี้ใครนำ"

ข้อควรระวัง

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

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

  • มีมากกว่าสองจุดเวลาใช้แทน: slope-chart หรือ multi-line-chart
  • สนใจแค่ขนาดการเปลี่ยนแปลง ไม่สนใจค่าเริ่มต้นใช้แทน: diverging-bar ของส่วนต่าง
  • รายการเกิน 30 รายการใช้แทน: scatter-plot ที่มีแกนก่อนและแกนหลัง

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

เทคนิคถัดไป · กราฟกระสุน