01ภาพรวมของกราฟนี้
Lollipop chart คือกราฟแท่งที่ผอมลงจนเหลือเส้นบาง ๆ แล้วเติมจุดไว้ปลาย ค่าถูกอ่านจากตำแหน่งของจุด ไม่ใช่จากพื้นที่ของแท่ง จึงลดหมึกที่ไม่จำเป็นลงมากเมื่อมีรายการเยอะ และทำให้ภาพไม่ดูหนักจนเกินไป
เปรียบเทียบให้เห็นภาพ
เหมือนเปลี่ยนจากการวัดด้วยแท่งไม้หนา ๆ มาเป็นการปักหมุดบนไม้บรรทัด สิ่งที่เราอ่านคือตำแหน่งของหัวหมุด ส่วนก้านมีไว้แค่ช่วยลากสายตากลับไปหาชื่อ
ใช้ที่ไหนบ้าง — จัดอันดับรายการจำนวนมาก แสดงคะแนนรายตัวชี้วัด หรือใช้แทนกราฟแท่งเมื่อภาพเริ่มดูหนักเกินไป
สร้างอย่างไร
- ขั้นที่ 1เรียงจากมากไปน้อย
- ขั้นที่ 2ลากก้านจากศูนย์ถึงค่า
- ขั้นที่ 3วาดหัวกลมที่ตำแหน่งค่า
- ขั้นที่ 4ติดตัวเลขข้างหัว
ตัวอย่างคลาสสิก · กราฟจัดอันดับที่ต้องการ data-ink ratio สูง
Edward Tufte เสนอแนวคิด data-ink ratio ว่าหมึกทุกหยดบนภาพควรสื่อข้อมูล lollipop chart คือผลโดยตรงของแนวคิดนี้ที่ใช้กับกราฟแท่ง เพราะพื้นที่สีทึบของแท่งไม่ได้เพิ่มข้อมูลเกินกว่าที่ตำแหน่งปลายแท่งบอกอยู่แล้ว
Edward Tufte · The Visual Display of Quantitative Information ↗
02อธิบายภาษาคนเข้าใจง่าย ๆ
เวลาจัดอันดับ 20-30 รายการด้วยกราฟแท่ง ภาพจะเต็มไปด้วยพื้นที่สีจนดูหนักและทึบ ทั้งที่ข้อมูลจริงที่เราต้องการคือแค่ "ค่าของแต่ละรายการอยู่ตรงไหน"
Lollipop แก้ด้วยการตัดความหนาของแท่งออก เหลือแค่เส้นบาง ๆ แล้วเติมจุดไว้ปลายเพื่อบอกตำแหน่งค่า หมึกในภาพลดลงมาก แต่ข้อมูลที่อ่านได้เท่าเดิมทุกประการ
ข้อควรระวังคือ เพราะเราอ่านค่าจากตำแหน่งของจุด ไม่ใช่จากความยาวของแท่ง หลายคนจึงคิดว่าตัดแกนไม่ให้เริ่มที่ศูนย์ก็ได้ ซึ่งจริงบางส่วน แต่ถ้ายังลากก้านจากศูนย์อยู่ ก้านก็ยังสื่อความยาวและยังต้องเริ่มที่ศูนย์
ถ้าตั้งใจจะตัดแกนจริง ๆ ให้ตัดก้านทิ้งไปเลย เหลือแค่จุด ซึ่งก็คือ dot plot นั่นเอง นี่คือความต่างเดียวที่สำคัญระหว่างสองกราฟนี้
ข้อดีที่ตามมาคือ เมื่อจุดสองจุดมีค่าใกล้กันมาก เราจะเห็นได้ชัดกว่าเมื่อเป็นแท่งหนา เพราะไม่มีพื้นที่สีมาบดบังความต่างเล็ก ๆ
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
category | รายการ | หมวดหมู่ | y | 5–40 รายการ | รองรับรายการได้มากกว่ากราฟแท่งเพราะก้านบาง |
value | ค่า | ตัวเลขต่อเนื่อง | x | ต่อเนื่อง | ถ้าก้านยังลากจากศูนย์อยู่ ค่าต้องมีศูนย์ที่มีความหมาย |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- เรียงตามค่า
- ตัดสินใจว่าจะลากก้านจากศูนย์หรือจากค่าอ้างอิงอื่น
- ระบุค่าอ้างอิงนั้นใน caption
ค่าที่หายไป — รายการที่ไม่มีค่าให้แสดงชื่อพร้อมเครื่องหมาย "—" ที่ตำแหน่งของมัน เพื่อให้เห็นว่ามีอยู่แต่ไม่มีข้อมูล
สเกลของแกน — linear เมื่อก้านลากจากศูนย์ ถ้าอยากใช้ log ต้องตัดก้านออกและเปลี่ยนเป็น dot-plot
กราฟจะเริ่มพังเมื่อไร
เกิน 40 รายการ ก้านจะชิดกันจนดูเป็นแรเงา — ใช้ ranking-table หรือ data-table-viz แทน
หน้าตาไฟล์ที่ต้องเตรียม
category,value
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- อันดับของทุกรายการ
- ระยะห่างระหว่างอันดับที่ติดกัน ซึ่งเห็นชัดกว่าเมื่อเป็นแท่งหนา
- กลุ่มของรายการที่ค่าใกล้กันจนเกาะเป็นก้อน
- รายการที่โดดออกไปจากกลุ่ม
สิ่งที่กราฟนี้ตอบไม่ได้
- ไม่บอกการเปลี่ยนแปลงตามเวลา
- ไม่บอกว่าความต่างเล็ก ๆ ที่เห็นมีนัยสำคัญหรือไม่
อ่านตามลำดับนี้
- ดูจุดบนสุดก่อน
- กวาดตาลงตามตำแหน่งของจุด ไม่ใช่ตามความยาวก้าน
- หาช่องว่างกว้าง ๆ ระหว่างจุด ซึ่งคือเส้นแบ่งกลุ่มธรรมชาติ
คำถามที่ตอบได้
- อันดับหนึ่งคือใคร
- รายการเหล่านี้แบ่งเป็นกี่กลุ่มโดยธรรมชาติ
- รายการที่เราสนใจห่างจากอันดับหนึ่งเท่าไร
สัญญาณว่ามีอะไรผิด
- ตัดแกนไม่ให้เริ่มที่ศูนย์ทั้งที่ยังมีก้าน
- ไม่เรียงลำดับ
- หัวจุดใหญ่จนบังความต่างระหว่างรายการที่ใกล้กัน
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- ระบายสีหัวจุดของรายการที่สนใจให้ต่างจากที่เหลือ
- ติดตัวเลขข้างหัวจุดได้ทุกรายการเพราะมีที่ว่าง
- ถ้ามีค่าอ้างอิง ให้ลากเส้นแนวตั้งของค่านั้นพาดผ่าน
รูปแบบชื่อกราฟที่ควรใช้
<รายการ> นำอันดับหนึ่งที่ <ค่า> โดยกลุ่มนำมี <จำนวน> รายการที่ค่าใกล้เคียงกัน
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ lollipop-chart-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด lollipop-chart-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("lollipop-chart-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
SORT = "value-desc" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
d = df.sort_values("value")
ax.hlines(d["category"], 0, d["value"], color="#9ec5f4", linewidth=2) # ก้านบาง
ax.plot(d["value"], d["category"], "o", markersize=9, color=C1) # หัวคือสิ่งที่อ่านค่า
ax.set_xlim(0, None)
ax.set_xlabel("ยอดขาย (ล้านบาท)")
ax.grid(axis="x", alpha=0.25); ax.set_axisbelow(True)
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("lollipop-chart.png", dpi=160)
print("เขียน lollipop-chart.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (12 แถว)
| หมวด | ค่า |
|---|---|
| กรุงเทพฯ | 502 |
| ชลบุรี | 464 |
| ขอนแก่น | 457 |
| เชียงใหม่ | 455 |
| ภูเก็ต | 419 |
| สงขลา | 363 |
| นครราชสีมา | 344 |
| สุราษฎร์ธานี | 275 |
| อุดรธานี | 267 |
| พิษณุโลก | 216 |
| ระยอง | 183 |
| นครปฐม | 172 |
ลองแล้วจะเห็นอะไร
- การเรียงลำดับ: เรียงตามค่าแล้วจะเห็นช่องว่างที่แบ่งรายการเป็นกลุ่ม ๆ ซึ่งเป็นสิ่งที่เรียงตามตัวอักษรซ่อนไว้
ข้อควรระวัง
- ตัดแกนไม่ให้เริ่มที่ศูนย์ทั้งที่ยังลากก้านจากศูนย์ ทำให้ก้านสื่อความยาวที่ผิด
- ใช้หัวจุดใหญ่เกินไปจนบังความต่างระหว่างรายการที่ค่าใกล้กัน
- ใช้กับรายการน้อยกว่า 5 รายการ ซึ่งกราฟแท่งธรรมดาอ่านง่ายกว่า
- ไม่เรียงลำดับทั้งที่ใช้จัดอันดับ
เมื่อไหร่ไม่ควรใช้กราฟนี้
- มีน้อยกว่า 5 รายการ→ใช้แทน: bar-chart ที่คุ้นเคยกว่า
- ต้องการตัดแกนเพราะค่าเกาะกลุ่มกันสูง→ใช้แทน: dot-plot ที่ไม่มีก้านจึงตัดแกนได้
- ต้องเทียบสองช่วงเวลา→ใช้แทน: dumbbell-chart