01ภาพรวมของกราฟนี้
Violin plot รวมจุดแข็งของ box plot กับ density plot เข้าด้วยกัน ความกว้างของรูปบอกความหนาแน่นที่ระดับค่านั้น และมักมีกล่องเล็ก ๆ อยู่ข้างในเพื่อให้อ่านมัธยฐานได้ ผลคือเห็นทั้งรูปร่างและค่าสรุปในสัญลักษณ์เดียว
เปรียบเทียบให้เห็นภาพ
เหมือนมองเงาของฝูงคนที่ยืนเรียงตามส่วนสูง ตรงไหนที่เงากว้างแปลว่าคนช่วงนั้นเยอะ ซึ่งเป็นสิ่งที่การบอกแค่ค่ากลางมองไม่เห็น
ใช้ที่ไหนบ้าง — เทียบผลการทดลองหลายกลุ่มในงานวิจัย ตรวจว่ากลุ่มไหนมีสองยอดซ่อนอยู่ หรือแทน box plot เมื่อรูปร่างสำคัญ
สร้างอย่างไร
- ขั้นที่ 1คำนวณความหนาแน่นของแต่ละกลุ่ม
- ขั้นที่ 2สะท้อนเส้นความหนาแน่นสองข้างของแกนกลาง
- ขั้นที่ 3ใส่กล่องหรือเส้นมัธยฐานไว้ข้างใน
- ขั้นที่ 4ระบุ bandwidth ที่ใช้
ตัวอย่างคลาสสิก · Hintze และ Nelson เสนอ violin plot ในปี 1998
ทั้งสองชี้ว่า box plot ให้ข้อมูลสรุปที่ดีแต่ปิดบังโครงสร้างของข้อมูล และเสนอให้รวมการประมาณความหนาแน่นเข้ากับสัญลักษณ์กล่องเดิม จุดขายหลักคือการเผยการกระจายแบบสองยอดที่กล่องไม่มีทางแสดงได้
Hintze & Nelson · Violin Plots (The American Statistician) ↗
02อธิบายภาษาคนเข้าใจง่าย ๆ
Box plot กระชับมากแต่ซ่อนรูปร่างไว้หมด ปัญหานี้ไม่ใช่เรื่องทฤษฎี เพราะกลุ่มที่มีสองยอด เช่น ลูกค้าสองแบบปนกัน จะให้กล่องหน้าตาปกติที่ไม่มีอะไรเตือนเราเลย
Violin plot แก้ด้วยการเอาเส้นความหนาแน่นมาวาดแทนกล่อง แล้วสะท้อนให้สมมาตรสองข้าง ความกว้างของรูปตรงระดับค่าใดบอกว่ามีข้อมูลหนาแน่นแค่ไหนที่ระดับนั้น
เพราะเรายังอยากได้ตัวเลขสรุปด้วย จึงนิยมวาดกล่องเล็ก ๆ หรือเส้นมัธยฐานไว้ข้างในรูป ทำให้ได้ทั้งสองอย่างในสัญลักษณ์เดียว
ข้อควรระวังที่สืบทอดมาจาก density plot คือมันมีการตัดสินใจเรื่อง bandwidth ซ่อนอยู่ ความอ้วนผอมของไวโอลินเปลี่ยนได้ตามค่าที่เลือก จึงควรระบุไว้
อีกข้อคือการสะท้อนสองข้างไม่ได้เพิ่มข้อมูลอะไร มันแค่ทำให้ดูสมดุล บางคนจึงใช้แบบครึ่งเดียวแล้วเอาอีกครึ่งไปวางจุดข้อมูลจริงแทน ซึ่งกลายเป็น raincloud plot
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
group | กลุ่มที่เทียบ | หมวดหมู่ | x | 2–8 กลุ่ม | เกิน 8 กลุ่มรูปจะแคบจนอ่านรูปร่างไม่ได้ |
value | ค่าที่วัดได้ | ตัวเลขต่อเนื่อง | y | ต่อเนื่อง | ต้องเป็นข้อมูลดิบและมีอย่างน้อย 30 จุดต่อกลุ่ม |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- ตรวจว่าทุกกลุ่มมีข้อมูลพอ
- เลือกและบันทึก bandwidth
- ตัดสินใจว่าจะใส่กล่องข้างในหรือไม่
ค่าที่หายไป — ตัดค่าว่างและแสดงจำนวนจุดของแต่ละกลุ่ม เพราะความกว้างของไวโอลินไม่ได้บอกขนาดกลุ่ม
สเกลของแกน — linear เป็นหลัก และทุกกลุ่มต้องใช้สเกลเดียวกัน
กราฟจะเริ่มพังเมื่อไร
กลุ่มที่มีน้อยกว่า 30 จุด รูปร่างจะสะท้อน bandwidth มากกว่าสะท้อนข้อมูล
หน้าตาไฟล์ที่ต้องเตรียม
group,value
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- รูปร่างการกระจายของแต่ละกลุ่ม
- กลุ่มที่มีสองยอด ซึ่ง box plot จะซ่อนไว้
- ความเบ้จากความไม่สมมาตรของรูปตามแนวตั้ง
- มัธยฐานจากเส้นข้างใน
สิ่งที่กราฟนี้ตอบไม่ได้
- ความกว้างไม่ได้บอกจำนวนข้อมูลของกลุ่ม เว้นแต่จะตั้งค่าให้ scale ตามจำนวน
- รูปร่างขึ้นกับ bandwidth ที่เลือก จึงไม่ใช่ข้อเท็จจริงที่แน่นอน
อ่านตามลำดับนี้
- ดูว่ารูปอ้วนที่ระดับค่าไหน นั่นคือช่วงที่ข้อมูลหนาแน่น
- นับจำนวนส่วนที่อ้วนเพื่อหาจำนวนยอด
- ดูเส้นมัธยฐานข้างในเพื่ออ่านค่ากลาง
คำถามที่ตอบได้
- กลุ่มไหนมีข้อมูลสองกลุ่มปนกันอยู่
- การกระจายของกลุ่มนี้เบ้ไปทางไหน
- กลุ่มไหนที่ค่าเกาะกลุ่มแน่นที่สุด
สัญญาณว่ามีอะไรผิด
- ไวโอลินอ้วนมากในกลุ่มที่มีข้อมูลน้อย ซึ่งเป็นผลของ bandwidth ไม่ใช่ของข้อมูล
- ไม่ระบุจำนวนข้อมูลต่อกลุ่ม
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- เขียนจำนวนข้อมูลของแต่ละกลุ่มไว้ใต้ชื่อ
- ระบุ bandwidth ที่ใช้
- ใส่กล่องหรือเส้นมัธยฐานไว้ข้างในเสมอเพื่อให้อ่านค่าสรุปได้
รูปแบบชื่อกราฟที่ควรใช้
<กลุ่ม> มีการกระจายสองยอด ต่างจากกลุ่มอื่นที่มียอดเดียว
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ violin-plot-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด violin-plot-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns # pip install seaborn
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("violin-plot-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "width" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
sns.violinplot(data=df, x="group", y="value", ax=ax, inner="box", # inner="box" ให้อ่านค่าสรุปได้
color="#9ec5f4", linewidth=1.4, cut=0) # cut=0 ไม่ยื่นเลยข้อมูลจริง
counts = df.groupby("group").size()
ax.set_xticklabels([f"{k}\n(n={v})" for k, v in counts.items()])
ax.set_ylabel("ค่าที่วัดได้"); ax.grid(axis="y", alpha=0.25); ax.set_axisbelow(True)
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("violin-plot.png", dpi=160)
print("เขียน violin-plot.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (4 แถว)
| กลุ่ม | n | ต่ำสุด | Q1 | มัธยฐาน | Q3 | สูงสุด |
|---|---|---|---|---|---|---|
| สาขา A | 80 | 50.0 | 54.8 | 57.6 | 59.7 | 67.1 |
| สาขา B | 80 | 55.9 | 63.4 | 66.3 | 70.2 | 76.6 |
| สาขา C | 80 | 55.6 | 60.1 | 61.7 | 63.7 | 68.1 |
| สาขา D | 80 | 62.6 | 69.0 | 73.7 | 77.0 | 84.9 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: เทียบกับหน้า box-plot ที่ใช้ข้อมูลชุดเดียวกัน จะเห็นว่าไวโอลินเผยรูปร่างที่กล่องซ่อนไว้ทั้งหมด
ข้อควรระวัง
- ปล่อยให้รูปยื่นเลยค่าต่ำสุดและสูงสุดจริงของข้อมูล ซึ่งสื่อว่ามีค่าที่ไม่เคยวัด
- ใช้กับกลุ่มที่มีข้อมูลน้อย ซึ่งรูปร่างมาจาก bandwidth ไม่ใช่จากข้อมูล
- ไม่ใส่กล่องหรือเส้นมัธยฐานข้างใน ทำให้อ่านค่าสรุปไม่ได้เลย
- ตีความความกว้างว่าเป็นจำนวนข้อมูลของกลุ่ม ทั้งที่ปกติทุกไวโอลินถูกปรับให้กว้างเท่ากัน
เมื่อไหร่ไม่ควรใช้กราฟนี้
- ข้อมูลน้อยกว่า 30 จุดต่อกลุ่ม→ใช้แทน: strip-plot หรือ beeswarm-plot ที่แสดงทุกจุด
- ต้องการความกระชับสูงสุดและรูปร่างไม่สำคัญ→ใช้แทน: box-plot
- ต้องการเห็นทั้งรูปร่าง สถิติ และจุดดิบ→ใช้แทน: raincloud-plot