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

กราฟฝูงผึ้งBeeswarm Plot

เห็นทุกจุดและเห็นความหนาแน่นพร้อมกัน

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

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

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

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

เหมือนคนต่อแถวรอลิฟต์ที่ยืนกระจายออกด้านข้างเมื่อคนเยอะ ความกว้างของกลุ่มคนจึงบอกว่าตรงนั้นมีคนมากแค่ไหน

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

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

  1. ขั้นที่ 1เรียงจุดตามค่า
  2. ขั้นที่ 2จัดตำแหน่งในแกนตั้งฉากให้ไม่ทับกัน
  3. ขั้นที่ 3เพิ่มเส้นมัธยฐาน
  4. ขั้นที่ 4ระบุจำนวนจุดของแต่ละกลุ่ม

ตัวอย่างคลาสสิก · Beeswarm ในงานข่าวข้อมูลของ The Economist และ FT

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

Financial Times · Visual Vocabulary

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

Strip plot แก้ปัญหาจุดทับกันด้วยการเบียดแบบสุ่ม ซึ่งได้ผลแต่มีข้อเสียคือความกว้างของกลุ่มจุดไม่ได้บอกอะไร มันเป็นแค่ความสุ่ม

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

ทันทีที่ตำแหน่งมีความหมาย ความกว้างของฝูงที่ระดับค่าหนึ่งก็กลายเป็นตัวบอกความหนาแน่นที่ระดับนั้น เราจึงได้รูปร่างการกระจายแบบเดียวกับ violin plot มาฟรี

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

ข้อจำกัดคือเมื่อข้อมูลเยอะเกินไป ฝูงจะกว้างจนล้นพื้นที่ที่มี ในทางปฏิบัติใช้ได้ดีถึงราว 500 จุดต่อกลุ่ม เกินกว่านั้นต้องกลับไปใช้ violin หรือ box plot

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

รูปทรงของข้อมูลข้อมูลดิบรายแถว (ยังไม่สรุป)
จำนวนมิติที่เข้ารหัสได้2 มิติ
จำนวนแถวที่เหมาะ30–300 จุดต่อกลุ่มขั้นต่ำ 20 แถว
เส้นฐานศูนย์เลือกได้อ่านจากตำแหน่งของจุดบนแกนค่าร่วม

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

คอลัมน์ทำหน้าที่ชนิดเข้ารหัสด้วยจำนวนค่าที่เหมาะหมายเหตุ
groupกลุ่มหมวดหมู่x1–8 กลุ่มแต่ละกลุ่มต้องมีพื้นที่กว้างพอให้ฝูงกระจาย
valueค่าที่วัดได้ตัวเลขต่อเนื่องyต่อเนื่องข้อมูลดิบทุกจุด

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

  1. ตัดค่าว่างออก
  2. กำหนดขนาดจุดให้เหมาะกับจำนวนข้อมูล
  3. เตรียมค่ามัธยฐานไว้ลากเส้น

ค่าที่หายไป — ตัดค่าว่างและรายงานจำนวน เพราะจุดที่เห็นคือข้อมูลจริงที่ผู้อ่านนับได้

สเกลของแกน — linear เป็นหลัก และทุกกลุ่มใช้สเกลเดียวกัน

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

เกิน 500 จุดต่อกลุ่ม ฝูงจะกว้างเกินพื้นที่ — ใช้ violin-plot แทน

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

group,value

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

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

  • ความหนาแน่นจากความกว้างของฝูงที่แต่ละระดับ
  • จำนวนข้อมูลจริงซึ่งนับได้
  • จุดที่แยกตัวออกไปจากฝูง

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

  • ตำแหน่งในแกนตั้งฉากไม่ได้แทนตัวแปรใด มันเป็นผลของการจัดวางเท่านั้น
  • เมื่อข้อมูลเยอะมาก ฝูงจะล้นและบิดรูปร่าง

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

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

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

  • ข้อมูลเกาะกลุ่มที่ค่าไหน
  • กลุ่มนี้มีข้อมูลกี่จุด
  • มีค่าผิดปกติกี่จุด

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

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

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

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

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

  1. เพิ่มเส้นมัธยฐานพาดฝูง
  2. เขียนจำนวนจุดของแต่ละกลุ่ม
  3. ระบายสีจุดที่เป็นกรณีพิเศษให้ต่างออกไป

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

ข้อมูลทั้ง <จำนวน> จุดของ <กลุ่ม> หนาแน่นที่สุดที่ <ค่า>

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

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

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

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

df = pd.read_csv("beeswarm-plot-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "density"   # ← ค่าจาก Playground

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

sns.swarmplot(data=df, x="group", y="value", ax=ax, color=C1, size=4.5, alpha=0.8)
for i, (g, v) in enumerate(df.groupby("group")["value"]):
    ax.plot([i - 0.28, i + 0.28], [v.median()] * 2, color=INK, linewidth=2)   # เส้นมัธยฐาน
counts = df.groupby("group").size()
ax.set_xticklabels([f"{k}\n(n={v})" for k, v in counts.items()])

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

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

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

ข้อมูลตัวอย่าง · 200 แถว · seed 101● คำนวณใหม่ในเบราว์เซอร์ทันทีที่ปรับค่า
ฝูงกว้างตรงไหนแปลว่าข้อมูลหนาแน่นตรงนั้น
สาขา Aสาขา Bสาขา Cสาขา D
50.060.070.080.090.0สาขา Aสาขา Bสาขา Cสาขา Dสาขา A · มัธยฐาน 57.2 · IQR 54.8–59.4 · n=50สาขา B · มัธยฐาน 66.3 · IQR 62.7–70.6 · n=50สาขา C · มัธยฐาน 61.6 · IQR 60.2–64.1 · n=50สาขา D · มัธยฐาน 73.7 · IQR 69.3–76.6 · n=50
กลุ่มที่มัธยฐานสูงสุดสาขา D
กลุ่มที่กระจายมากสุดสาขา B
จำนวนกลุ่ม4
ดูข้อมูลเป็นตาราง (4 แถว)
beeswarm plot 4 กลุ่ม
กลุ่มnต่ำสุดQ1มัธยฐานQ3สูงสุด
สาขา A5050.054.857.259.463.6
สาขา B5055.962.766.370.676.6
สาขา C5055.660.261.664.168.1
สาขา D5064.469.373.776.684.9

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

  • สังเกตอะไร: เทียบกับหน้า strip-plot ที่ใช้ข้อมูลเดียวกัน จะเห็นว่าความกว้างของ beeswarm มีความหมาย ส่วนของ strip plot เป็นแค่ความสุ่ม

ข้อควรระวัง

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

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

  • มีมากกว่า 500 จุดต่อกลุ่มใช้แทน: violin-plot หรือ box-plot
  • ต้องการความกระชับสูงสุดใช้แทน: box-plot
  • มีข้อมูลน้อยมากและไม่ต้องกังวลเรื่องจุดทับใช้แทน: strip-plot

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

เทคนิคถัดไป · กราฟการแจกแจงสะสม