01ภาพรวมของกราฟนี้
Raincloud plot รวมสามอย่างที่เคยต้องเลือกอย่างใดอย่างหนึ่ง คือครึ่งไวโอลินสำหรับรูปร่าง กล่องสำหรับค่าสรุป และจุดดิบสำหรับข้อมูลจริง วางเรียงกันไม่ให้ทับ ผลคือผู้อ่านตรวจสอบสิ่งที่เห็นได้ครบทุกระดับในภาพเดียว
เปรียบเทียบให้เห็นภาพ
เหมือนรายงานที่มีทั้งบทสรุปผู้บริหาร กราฟ และตารางข้อมูลดิบแนบมาด้วยกัน ใครอยากดูละเอียดแค่ไหนก็เลือกได้
ใช้ที่ไหนบ้าง — งานวิจัยที่ต้องแสดงข้อมูลอย่างโปร่งใส การเทียบกลุ่มทดลอง หรือรายงานที่ผู้อ่านหลายระดับต้องใช้ร่วมกัน
สร้างอย่างไร
- ขั้นที่ 1วาดครึ่งไวโอลินด้านหนึ่ง
- ขั้นที่ 2วางกล่องสรุปไว้ตรงกลาง
- ขั้นที่ 3โปรยจุดดิบไว้อีกด้าน
- ขั้นที่ 4จัดให้ทั้งสามส่วนไม่ทับกัน
ตัวอย่างคลาสสิก · Allen และคณะเสนอ raincloud plot ในปี 2019
ทีมนักประสาทวิทยาเสนอรูปแบบนี้เพื่อตอบข้อวิจารณ์ว่างานวิจัยแสดงแต่ค่าเฉลี่ยกับแท่ง ซึ่งซ่อนข้อมูลจริงไว้ พวกเขาออกแบบให้รวมทั้งสามชั้นโดยไม่ทับกัน และเผยแพร่โค้ดสำหรับ R, Python และ MATLAB พร้อมกันเพื่อให้ใช้ได้ทันที
02อธิบายภาษาคนเข้าใจง่าย ๆ
กราฟการกระจายแต่ละชนิดมีจุดอ่อนของตัวเอง box plot ซ่อนรูปร่าง violin plot ซ่อนจำนวนข้อมูล และ strip plot ไม่ให้ค่าสรุป ที่ผ่านมาเราต้องเลือกอย่างใดอย่างหนึ่ง
Raincloud plot ไม่เลือก มันวางทั้งสามอย่างไว้ในแถวเดียวกันแต่แยกตำแหน่งไม่ให้ทับกัน ครึ่งไวโอลินอยู่ด้านบนเป็น "เมฆ" และจุดดิบโปรยอยู่ด้านล่างเป็น "ฝน"
เหตุผลที่ทำได้คือการใช้ครึ่งไวโอลินแทนเต็มตัว ซึ่งไม่ได้เสียข้อมูลอะไรเลยเพราะไวโอลินสมมาตรอยู่แล้ว การสะท้อนสองข้างเป็นแค่ความสวยงาม
ผลที่ได้คือความโปร่งใส ผู้อ่านที่เชื่อค่าสรุปก็ดูกล่อง ผู้อ่านที่อยากเห็นรูปร่างก็ดูเมฆ และผู้อ่านที่อยากตรวจสอบก็นับจุดฝนได้
ข้อแลกเปลี่ยนคือใช้พื้นที่มากกว่าและอาจดูรกถ้ามีหลายกลุ่ม ในทางปฏิบัติใช้ได้ดีที่ 2-5 กลุ่ม เกินกว่านั้นควรกลับไปใช้ box plot หรือ violin plot
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
group | กลุ่ม | หมวดหมู่ | x | 2–5 กลุ่ม | เกิน 5 กลุ่มภาพจะรกเพราะแต่ละกลุ่มกินพื้นที่มาก |
value | ค่าที่วัดได้ | ตัวเลขต่อเนื่อง | y | ต่อเนื่อง | ข้อมูลดิบ 30 ถึง 300 จุดต่อกลุ่มกำลังดี |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- ตัดค่าว่างออก
- เลือก bandwidth ของครึ่งไวโอลิน
- จัดพื้นที่ให้ทั้งสามชั้นไม่ทับกัน
ค่าที่หายไป — ตัดค่าว่างและรายงานจำนวน เพราะผู้อ่านจะนับจุดฝนได้
สเกลของแกน — linear และทุกกลุ่มใช้สเกลเดียวกัน
กราฟจะเริ่มพังเมื่อไร
เกิน 500 จุดต่อกลุ่ม ชั้นจุดจะทับกันจนไม่ช่วยอะไร
หน้าตาไฟล์ที่ต้องเตรียม
group,value
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- รูปร่างการกระจายจากเมฆ
- ค่าสรุปจากกล่อง
- จำนวนและตำแหน่งข้อมูลจริงจากฝน
สิ่งที่กราฟนี้ตอบไม่ได้
- ยังไม่บอกว่าความต่างมีนัยสำคัญทางสถิติหรือไม่
- ไม่เหมาะกับการเทียบหลายกลุ่มพร้อมกัน
อ่านตามลำดับนี้
- ดูกล่องก่อนเพื่ออ่านค่าสรุป
- ดูเมฆเพื่อตรวจรูปร่าง
- ดูฝนเพื่อยืนยันว่ามีข้อมูลจริงกี่จุด
คำถามที่ตอบได้
- กลุ่มไหนดีกว่าและมั่นใจแค่ไหน
- ค่าสรุปที่เห็นมาจากข้อมูลกี่จุด
- มีรูปร่างแปลก ๆ ซ่อนอยู่ไหม
สัญญาณว่ามีอะไรผิด
- ชั้นจุดหนาแน่นจนทับกันและนับไม่ได้
- ไม่มีกล่องทำให้อ่านค่าสรุปไม่ได้
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- เขียนจำนวนข้อมูลของแต่ละกลุ่ม
- อธิบายว่าเมฆคือความหนาแน่นและฝนคือข้อมูลจริง
- ระบุ bandwidth ที่ใช้
รูปแบบชื่อกราฟที่ควรใช้
<กลุ่ม> สูงกว่า <กลุ่ม> โดยรูปร่างการกระจายและจำนวนข้อมูลแสดงไว้ครบในภาพ
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ raincloud-plot-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด raincloud-plot-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
import numpy as np
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("raincloud-plot-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "layers" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
groups = list(df["group"].unique())
rng = np.random.default_rng(42)
for i, g in enumerate(groups):
v = df[df["group"] == g]["value"]
parts = ax.violinplot([v], positions=[i], widths=0.7, showextrema=False)
for b in parts["bodies"]: # ตัดเหลือครึ่งบน = เมฆ
b.get_paths()[0].vertices[:, 0] = np.clip(b.get_paths()[0].vertices[:, 0], i, i + 0.4)
b.set_facecolor("#9ec5f4"); b.set_alpha(0.7)
ax.boxplot([v], positions=[i - 0.06], widths=0.1, showfliers=False) # กล่องสรุป
ax.scatter(np.full(len(v), i - 0.28) + rng.uniform(-0.06, 0.06, len(v)), v,
s=14, alpha=0.5, color=C1) # ฝน = ข้อมูลจริง
ax.set_xticks(range(len(groups))); ax.set_xticklabels(groups)
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("raincloud-plot.png", dpi=160)
print("เขียน raincloud-plot.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (4 แถว)
| กลุ่ม | n | ต่ำสุด | Q1 | มัธยฐาน | Q3 | สูงสุด |
|---|---|---|---|---|---|---|
| สาขา A | 50 | 50.0 | 54.8 | 57.2 | 59.4 | 63.6 |
| สาขา B | 50 | 55.9 | 62.7 | 66.3 | 70.6 | 76.6 |
| สาขา C | 50 | 55.6 | 60.2 | 61.6 | 64.1 | 68.1 |
| สาขา D | 50 | 64.4 | 69.3 | 73.7 | 76.6 | 84.9 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: ตรวจสอบข้ามชั้น ลองดูว่ารูปร่างของเมฆสอดคล้องกับที่จุดฝนกระจุกอยู่ไหม ถ้าไม่สอดคล้อง แปลว่า bandwidth ของเมฆกว้างเกินไป
ข้อควรระวัง
- ใส่กลุ่มมากเกินไปจนแต่ละกลุ่มแคบและสามชั้นทับกัน
- ใช้กับข้อมูลเยอะเกินไปจนชั้นฝนกลายเป็นก้อนทึบ
- ตัดกล่องออกเพื่อความสวยงาม ทำให้อ่านค่าสรุปไม่ได้
- ไม่อธิบายว่าแต่ละชั้นคืออะไร ทำให้ผู้อ่านสับสน
เมื่อไหร่ไม่ควรใช้กราฟนี้
- มีกลุ่มมากกว่า 5 กลุ่ม→ใช้แทน: box-plot หรือ violin-plot
- พื้นที่จำกัด→ใช้แทน: box-plot
- ข้อมูลมากกว่า 500 จุดต่อกลุ่ม→ใช้แทน: violin-plot