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

กราฟความหนาแน่นDensity Plot

รูปร่างการกระจายที่ไม่ขึ้นกับว่าแบ่ง bin ตรงไหน

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

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

Density plot ใช้การประมาณความหนาแน่นแบบเคอร์เนลเพื่อวาดเส้นเรียบแทนแท่ง ข้อดีคือรูปร่างไม่กระโดดตามตำแหน่งขอบ bin และซ้อนหลายกลุ่มได้สวยกว่า ข้อเสียคือย้ายการตัดสินใจจาก "จำนวน bin" ไปเป็น "ความกว้างของเคอร์เนล" ซึ่งซ่อนกว่าและคนมักไม่ปรับ

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

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

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

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

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

ตัวอย่างคลาสสิก · ปัญหา bandwidth ที่ Silverman วางรากฐานไว้

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

Silverman · Density Estimation for Statistics and Data Analysis

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

ปัญหาหนึ่งของฮิสโทแกรมที่คนมักไม่รู้คือ รูปร่างที่เห็นไม่ได้ขึ้นกับจำนวน bin อย่างเดียว แต่ขึ้นกับว่าขอบของ bin ตกตรงไหนด้วย ขยับขอบไปนิดเดียวยอดอาจย้ายที่

Density plot แก้ด้วยการเลิกใช้ถัง แล้วเปลี่ยนเป็นวางระฆังเล็ก ๆ ไว้บนทุกจุดข้อมูล แล้วบวกระฆังทั้งหมดเข้าด้วยกันเป็นเส้นเรียบเส้นเดียว

แต่การตัดสินใจไม่ได้หายไป มันแค่ย้ายที่ ตอนนี้เราต้องเลือกว่าระฆังแต่ละใบกว้างแค่ไหน ซึ่งเรียกว่า bandwidth ถ้าแคบเกินไปเส้นจะหยักตาม noise ถ้ากว้างเกินไปยอดหลายยอดจะถูกรีดจนเหลือยอดเดียว

อันตรายที่แท้จริงคือไลบรารีส่วนใหญ่เลือก bandwidth ให้อัตโนมัติ คนใช้จึงไม่รู้ว่ามีการตัดสินใจเกิดขึ้น ต่างจากฮิสโทแกรมที่ทุกคนรู้ว่าต้องเลือกจำนวน bin

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

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

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

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

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

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

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

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

สเกลของแกน — แกน x ตามค่าจริง แกน y เป็นความหนาแน่นซึ่งพื้นที่ใต้เส้นรวมเป็นหนึ่ง ไม่ใช่จำนวนนับ

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

ต่ำกว่า 50 แถว เส้นจะสะท้อนการเลือก bandwidth มากกว่าสะท้อนข้อมูล

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

value

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

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

  • รูปร่างการกระจายแบบเรียบ
  • จำนวนยอดที่แท้จริงของการกระจาย
  • ความเบ้และความกว้างของหาง

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

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

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

  1. ดูจำนวนและตำแหน่งของยอด
  2. ดูความกว้างของเส้นซึ่งบอกความกระจาย
  3. ดูขีดที่ฐานเพื่อรู้ว่าข้อมูลจริงอยู่ตรงไหน

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

  • การกระจายมีกี่ยอด
  • ข้อมูลเบ้ไปทางไหน
  • สองกลุ่มนี้มีรูปร่างการกระจายต่างกันไหม

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

  • เส้นยื่นเข้าไปในช่วงค่าที่เป็นไปไม่ได้ เช่น อายุติดลบ
  • ไม่บอกค่า bandwidth ที่ใช้
  • มีจุดข้อมูลน้อยแต่วาดเส้นเรียบที่ดูน่าเชื่อถือ

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

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

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

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

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

การกระจายของ <ตัวแปร> มี <จำนวน> ยอด โดยยอดหลักอยู่ที่ <ค่า>

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

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

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

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

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

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

values = df["value"].dropna().to_numpy()
kde = gaussian_kde(values, bw_method=BANDWIDTH)      # bw_method คือการตัดสินใจที่ต้องประกาศ
grid = np.linspace(values.min(), values.max(), 200)
ax.fill_between(grid, kde(grid), color="#9ec5f4", alpha=0.7)
ax.plot(grid, kde(grid), color=C1, linewidth=2)
ax.plot(values, np.zeros_like(values), "|", color=INK, alpha=0.35, markersize=8)  # ขีดข้อมูลจริง
ax.set_xlabel("ค่าที่วัดได้"); ax.set_ylabel("ความหนาแน่น (ไม่ใช่จำนวนนับ)")
ax.set_title(f"bandwidth = {BANDWIDTH} · n = {len(values)}", loc="left")

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

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

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

ข้อมูลตัวอย่าง · 400 แถว · seed 89● คำนวณใหม่ในเบราว์เซอร์ทันทีที่ปรับค่า
ความกว้างของระฆังที่วางบนแต่ละจุดข้อมูล
0.000.020.040.060.08ความหนาแน่น50.060.070.0ค่าที่วัดได้มัธยฐาน 62.6

เส้นที่ยื่นเลยค่าต่ำสุด/สูงสุดจริงเป็นผลของการประมาณ ไม่ใช่ข้อมูลที่มีอยู่

ตัวคูณ bandwidth1.00
ยอดที่นับได้1
ความเบ้-0.24
ดูข้อมูลเป็นตาราง (12 แถว)
กราฟความหนาแน่น จาก 400 ค่า
ค่า xความหนาแน่น
44.150.0001
47.140.0022
50.120.0111
53.110.0240
56.090.0369
59.080.0527
62.060.0666
65.050.0659
68.030.0491
71.020.0195
74.000.0058
76.990.0011

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

  • ตัวคูณ bandwidth: ลากไปต่ำสุดจะเห็นเส้นหยักตาม noise ลากไปสูงสุดจะเหลือเนินเดียว — ข้อมูลไม่เปลี่ยนเลย เปลี่ยนแค่การตัดสินใจของคนทำกราฟ

ข้อควรระวัง

  1. ใช้ bandwidth ค่า default โดยไม่รู้ว่ามีการตัดสินใจเกิดขึ้น ซึ่งอันตรายกว่าฮิสโทแกรมเพราะมองไม่เห็น
  2. ปล่อยให้เส้นยื่นเข้าไปในช่วงค่าที่เป็นไปไม่ได้
  3. ใช้กับข้อมูลน้อย ๆ ซึ่งได้เส้นเรียบที่ดูน่าเชื่อถือเกินความจริง
  4. ซ้อนเกิน 3 กลุ่มจนเส้นบังกัน
  5. ตีความแกนตั้งว่าเป็นจำนวนคน ทั้งที่เป็นความหนาแน่น

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

  • ข้อมูลน้อยกว่า 50 แถวใช้แทน: strip-plot หรือ histogram ที่แสดงข้อมูลจริงตรงไปตรงมา
  • ผู้อ่านต้องการจำนวนนับใช้แทน: histogram
  • ต้องเทียบมากกว่า 3 กลุ่มใช้แทน: ridgeline-plot หรือ violin-plot

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

เทคนิคถัดไป · กราฟกล่อง