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

กราฟแท่งแข่งขันBar Chart Race

ดึงดูดสายตาได้ดี แต่เทียบข้ามเวลาได้แย่

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

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

Bar chart race แสดงกราฟแท่งที่เรียงอันดับใหม่ทีละช่วงเวลาแบบเคลื่อนไหว จุดแข็งคือดึงความสนใจได้ดีมากในสื่อสังคม จุดอ่อนที่ร้ายแรงคือผู้ชมจำสถานะก่อนหน้าไม่ได้ จึงเปรียบเทียบข้ามเวลาได้แย่กว่ากราฟนิ่งอย่าง bump chart มาก

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

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

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

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

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

ตัวอย่างคลาสสิก · กระแสกราฟแท่งแข่งขันบนสื่อสังคมปี 2019

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

Financial Times · Visual Vocabulary

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

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

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

ผลคือคำถามอย่าง "รายการนี้ไต่ขึ้นมาตั้งแต่เมื่อไร" หรือ "ใครแซงใครกี่ครั้ง" ตอบไม่ได้เลยจากภาพเคลื่อนไหว ทั้งที่ bump chart ตอบได้ในภาพเดียว

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

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

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

รูปทรงของข้อมูลตารางแบบยาว (หนึ่งแถวหนึ่งสังเกต)
จำนวนมิติที่เข้ารหัสได้3 มิติ
จำนวนแถวที่เหมาะ8–12 รายการ × 20–80 เฟรมขั้นต่ำ 30 แถว
เส้นฐานศูนย์ต้องเริ่มที่ศูนย์เป็นกราฟแท่ง ความยาวจึงต้องแปรตามค่าจากศูนย์

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

คอลัมน์ทำหน้าที่ชนิดเข้ารหัสด้วยจำนวนค่าที่เหมาะหมายเหตุ
brandรายการที่แข่งกันหมวดหมู่color5–15 รายการ★ สีต้องผูกกับตัวตนของรายการ ไม่ใช่กับอันดับ
periodช่วงเวลามีลำดับorder10–100 เฟรมยิ่งเฟรมถี่ภาพยิ่งลื่น
scoreค่าที่ใช้จัดอันดับตัวเลขต่อเนื่องlengthต่อเนื่องความยาวแท่งแปรตามค่านี้

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

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

ค่าที่หายไป — รายการที่ยังไม่มีข้อมูลในช่วงต้นควรไม่ปรากฏเลย ไม่ใช่แสดงเป็นแท่งศูนย์ เพราะแท่งศูนย์แปลว่ามีอยู่แต่ค่าเป็นศูนย์

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

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

เกิน 15 รายการ ผู้ชมตามไม่ทันเพราะแท่งสลับที่เร็วเกินไป

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

period,brand,score,rank
ม.ค.,แบรนด์ ก,99.1,1
ม.ค.,แบรนด์ ข,89.9,2

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

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

  • อันดับ ณ เฟรมที่กำลังดู
  • ความรู้สึกถึงการเปลี่ยนแปลงโดยรวม
  • ช่วงเวลาที่มีการสลับอันดับกันวุ่นวาย

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

  • ★ เทียบข้ามเวลาไม่ได้เลย เพราะเห็นได้ทีละเฟรมและความจำระยะสั้นจำไม่ได้
  • ไม่บอกว่าใครไต่ขึ้นมาตั้งแต่เมื่อไร
  • นับจำนวนครั้งที่แซงกันไม่ได้

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

  1. หยุดที่เฟรมที่สนใจแล้วอ่านเหมือนกราฟแท่งธรรมดา
  2. ใช้ตัวเลื่อนเวลาเพื่อเทียบสองเฟรม
  3. ถ้าต้องการวิเคราะห์จริง ให้ดู bump chart ควบคู่

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

  • ตอนนี้ใครนำ
  • มีการแซงกันเกิดขึ้นไหม

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

  • สีเปลี่ยนตามอันดับแทนที่จะผูกกับตัวตนของรายการ
  • ไม่มีปุ่มหยุดหรือตัวเลื่อนเวลา
  • แกนค่าปรับตามเฟรมโดยไม่บอก

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

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

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

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

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

<รายการ> ขึ้นเป็นอันดับหนึ่งใน <ช่วงเวลา>

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

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

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

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

df = pd.read_csv("bar-race-sample.csv")
df["score"] = pd.to_numeric(df["score"], errors="coerce")
df["rank"] = pd.to_numeric(df["rank"], errors="coerce")
FRAME = 7   # ← ค่าจาก Playground

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

periods = list(dict.fromkeys(df["period"]))
brands = list(dict.fromkeys(df["brand"]))
colors = {b: [C1, C2, C3, "#eda100", "#e87ba4"][i % 5] for i, b in enumerate(brands)}
# ★ สีผูกกับตัวตนของแบรนด์ ไม่ใช่กับอันดับ

def draw(k):
    ax.clear()
    snap = df[df["period"] == periods[k]].sort_values("score")
    ax.barh(snap["brand"], snap["score"], color=[colors[b] for b in snap["brand"]], height=0.66)
    ax.set_xlim(0, df["score"].max() * 1.05)          # ตรึงแกนไว้เพื่อให้เทียบข้ามเฟรมได้
    ax.text(0.98, 0.06, periods[k], transform=ax.transAxes, ha="right", fontsize=26, color="#b5b2aa")

anim = FuncAnimation(fig, draw, frames=len(periods), interval=700)
anim.save("bar-race.gif", writer="pillow", dpi=110)
print("เขียน bar-race.gif แล้ว")
raise SystemExit

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

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

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

ข้อมูลตัวอย่าง · 8 แถว · seed 263● คำนวณใหม่ในเบราว์เซอร์ทันทีที่ปรับค่า
เลื่อนเพื่อดูแต่ละเฟรม
0.0020.040.060.080.0100120ส.ค.แบรนด์ ข106แบรนด์ ข · 106 · อันดับ 1แบรนด์ ก104แบรนด์ ก · 104 · อันดับ 2แบรนด์ ง103แบรนด์ ง · 103 · อันดับ 3แบรนด์ ค75.0แบรนด์ ค · 75.0 · อันดับ 4แบรนด์ จ63.8แบรนด์ จ · 63.8 · อันดับ 5

ภาพเคลื่อนไหวทำให้จำอันดับก่อนหน้าไม่ได้ ถ้าต้องการเทียบข้ามเวลาจริง ๆ ใช้ bump chart หรือ small multiples

ช่วงเวลาที่แสดงส.ค.
ผู้นำในเฟรมนี้แบรนด์ ข
เฟรมทั้งหมด8
ดูข้อมูลเป็นตาราง (5 แถว)
bar chart race เฟรม ส.ค. · ผู้นำคือ แบรนด์ ข
อันดับแบรนด์คะแนน
1แบรนด์ ข106
2แบรนด์ ก104
3แบรนด์ ง103
4แบรนด์ ค75.0
5แบรนด์ จ63.8

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

  • ช่วงเวลา: เลื่อนกลับไปกลับมาแล้วลองตอบว่ารายการหนึ่งไต่ขึ้นหรือตกลงตั้งแต่เฟรมแรก จะพบว่าตอบยากมาก — นี่คือจุดอ่อนหลักของภาพเคลื่อนไหว ลองเทียบกับหน้า bump-chart ที่ตอบได้ทันที

ข้อควรระวัง

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

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

  • ต้องการวิเคราะห์การเปลี่ยนอันดับใช้แทน: bump-chart ที่แสดงทุกช่วงเวลาในภาพเดียว
  • เป็นเอกสารที่พิมพ์หรืออ่านเงียบ ๆใช้แทน: small-multiples หรือ bump-chart
  • มีเพียงสองช่วงเวลาใช้แทน: slope-chart หรือ dumbbell-chart

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

เทคนิคถัดไป · แผนภาพต้นไม้