01ภาพรวมของกราฟนี้
Cycle plot จัดข้อมูลใหม่โดยรวมเดือนเดียวกันของทุกปีไว้ด้วยกัน ภายในแต่ละเดือนวาดแนวโน้มข้ามปี และมีเส้นค่าเฉลี่ยของเดือนนั้นกำกับ ผลคือแยก "รูปแบบฤดูกาล" ออกจาก "แนวโน้มระยะยาว" ได้ชัดเจน ซึ่งกราฟเส้นธรรมดาผสมสองอย่างนี้ไว้ด้วยกัน
เปรียบเทียบให้เห็นภาพ
เหมือนแยกรูปถ่ายวันเกิดของเด็กคนหนึ่งตามอายุ เราเห็นทั้งว่าเด็กโตขึ้นทุกปี และเห็นว่าแต่ละช่วงวัยมีลักษณะเฉพาะอย่างไร
ใช้ที่ไหนบ้าง — ยอดขายรายเดือนหลายปี ปริมาณการใช้ไฟฟ้าตามฤดู จำนวนนักท่องเที่ยวรายเดือน หรือข้อมูลใดที่มีทั้งฤดูกาลและแนวโน้ม
สร้างอย่างไร
- ขั้นที่ 1จัดกลุ่มข้อมูลตามตำแหน่งในรอบ เช่น เดือน
- ขั้นที่ 2ภายในแต่ละกลุ่มเรียงตามปี
- ขั้นที่ 3วาดเส้นแนวโน้มภายในกลุ่ม
- ขั้นที่ 4ลากเส้นค่าเฉลี่ยของแต่ละกลุ่ม
ตัวอย่างคลาสสิก · Cycle plot ของ Cleveland ที่ใช้กับข้อมูลการจ้างงานรายเดือน
William Cleveland เสนอกราฟนี้เพื่อแก้ปัญหาว่ากราฟเส้นรายเดือนทำให้แยกไม่ออกว่าการเพิ่มขึ้นมาจากฤดูกาลหรือจากแนวโน้ม เขาแสดงให้เห็นว่าข้อมูลการจ้างงานชุดเดียวกันเล่าคนละเรื่องเมื่อจัดใหม่แบบนี้
02อธิบายภาษาคนเข้าใจง่าย ๆ
ข้อมูลรายเดือนหลายปีมีสองเรื่องปนกันอยู่ คือแนวโน้มระยะยาวที่ค่อย ๆ ขึ้นหรือลง กับรูปแบบฤดูกาลที่ซ้ำทุกปี กราฟเส้นธรรมดาแสดงทั้งสองรวมกันจนแยกไม่ออกว่าการเพิ่มขึ้นครั้งนี้เป็นเพราะฤดูกาลหรือเพราะธุรกิจโตจริง
Cycle plot จัดข้อมูลใหม่ทั้งหมด แทนที่จะเรียงตามเวลาจริง มันเอาเดือนมกราคมของทุกปีมาไว้ด้วยกัน แล้วกุมภาพันธ์ของทุกปีไว้ด้วยกัน เรียงไปเรื่อย ๆ
ภายในแต่ละกลุ่มจึงมีเส้นสั้น ๆ ที่แสดงว่าเดือนมกราคมของแต่ละปีเปลี่ยนไปอย่างไร ถ้าเส้นนี้ชันขึ้น แปลว่าธุรกิจโตจริงในเดือนนั้น ไม่ใช่แค่ผลของฤดูกาล
เส้นแนวนอนที่พาดในแต่ละกลุ่มคือค่าเฉลี่ยของเดือนนั้น เมื่อดูเส้นเฉลี่ยทั้ง 12 กลุ่มพร้อมกัน เราจะเห็นรูปร่างของฤดูกาลชัดเจนโดยไม่มีแนวโน้มมารบกวน
สิ่งที่กราฟนี้ตอบได้และกราฟอื่นตอบยากคือ "เดือนที่ยอดสูงสุดยังโตอยู่ไหม" ซึ่งเป็นคำถามที่สำคัญมากในธุรกิจที่มีฤดูกาลแรง
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
date | เวลา | เวลา | x | อย่างน้อย 3 รอบเต็ม | ต้องมีข้อมูลหลายรอบถึงจะเห็นแนวโน้มภายในกลุ่ม |
month | ตำแหน่งในรอบ | มีลำดับ | column | 12 เดือนหรือ 7 วัน | กลุ่มที่จะจัดข้อมูลเข้า |
value | ค่าที่วัดได้ | ตัวเลขต่อเนื่อง | y | ต่อเนื่อง | ทุกกลุ่มใช้สเกลร่วมกันเสมอ |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- สกัดตำแหน่งในรอบจากวันที่
- ตรวจว่ามีข้อมูลครบทุกกลุ่มในทุกรอบ
- คำนวณค่าเฉลี่ยของแต่ละกลุ่มไว้ลากเส้น
ค่าที่หายไป — รอบที่ขาดข้อมูลบางกลุ่มจะทำให้ค่าเฉลี่ยของกลุ่มนั้นบิด ต้องระบุจำนวนรอบที่ใช้คำนวณในแต่ละกลุ่ม
สเกลของแกน — linear และต้องใช้สเกลร่วมกันทุกกลุ่ม ไม่อย่างนั้นการเทียบข้ามกลุ่มจะไม่มีความหมาย
กราฟจะเริ่มพังเมื่อไร
น้อยกว่า 3 รอบ เส้นภายในกลุ่มจะสั้นเกินกว่าจะบอกแนวโน้มได้
หน้าตาไฟล์ที่ต้องเตรียม
date,month,value
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- รูปร่างของฤดูกาลจากตำแหน่งของเส้นเฉลี่ยทั้ง 12 กลุ่ม
- แนวโน้มภายในแต่ละเดือนจากความชันของเส้นสั้น
- เดือนที่โตเร็วกว่าเดือนอื่น
สิ่งที่กราฟนี้ตอบไม่ได้
- ไม่แสดงลำดับเวลาจริง จึงหาเหตุการณ์เฉพาะกิจไม่ได้
- ไม่บอกว่ารูปแบบฤดูกาลเปลี่ยนรูปร่างไปหรือไม่
อ่านตามลำดับนี้
- ดูเส้นเฉลี่ยของทั้ง 12 กลุ่มก่อนเพื่ออ่านรูปร่างฤดูกาล
- ดูความชันของเส้นสั้นในแต่ละกลุ่มเพื่ออ่านแนวโน้ม
- หากลุ่มที่ชันต่างจากกลุ่มอื่น
คำถามที่ตอบได้
- เดือนไหนสูงที่สุดตามฤดูกาล
- เดือนที่เคยดีที่สุดยังโตอยู่ไหม
- ฤดูกาลแรงแค่ไหนเทียบกับแนวโน้มรวม
สัญญาณว่ามีอะไรผิด
- แต่ละกลุ่มใช้สเกลของตัวเอง
- มีข้อมูลน้อยกว่า 3 รอบแต่ยังวาดเส้นแนวโน้ม
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- เขียนว่าแกนนอนคือกลุ่มตามฤดูกาล ไม่ใช่เวลาที่ไหลต่อเนื่อง
- ทำให้เส้นค่าเฉลี่ยเด่นกว่าเส้นรายปี
- เขียนจำนวนรอบที่ใช้คำนวณ
รูปแบบชื่อกราฟที่ควรใช้
<เดือน> เป็นเดือนที่สูงที่สุดตามฤดูกาล และยัง<โต/หด>ต่อเนื่องทุกปี
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ cycle-plot-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด cycle-plot-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("cycle-plot-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "within" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
df["date"] = pd.to_datetime(df["date"])
months = list(dict.fromkeys(df["month"]))
overall = df["value"].mean()
for i, m in enumerate(months):
g = df[df["month"] == m].sort_values("date")
xs = np.linspace(i - 0.38, i + 0.38, len(g))
ax.plot(xs, g["value"], color="#5598e7", linewidth=1.6) # แนวโน้มข้ามปีภายในเดือน
ax.plot([i - 0.42, i + 0.42], [g["value"].mean()] * 2, color=C1, linewidth=2.6) # ค่าเฉลี่ยของเดือน
ax.axhline(overall, color=INK, linestyle="--", linewidth=1)
ax.set_xticks(range(len(months))); ax.set_xticklabels(months, fontsize=8)
ax.set_ylabel("ค่า")
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("cycle-plot.png", dpi=160)
print("เขียน cycle-plot.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (12 แถว)
| เดือน | ค่าเฉลี่ย | ต่างจากค่าเฉลี่ยรวม |
|---|---|---|
| ม.ค. | 145 | -6.62 |
| ก.พ. | 156 | 4.05 |
| มี.ค. | 166 | 14.2 |
| เม.ย. | 170 | 18.0 |
| พ.ค. | 167 | 15.0 |
| มิ.ย. | 163 | 11.1 |
| ก.ค. | 153 | 1.15 |
| ส.ค. | 143 | -9.07 |
| ก.ย. | 137 | -15.4 |
| ต.ค. | 134 | -18.0 |
| พ.ย. | 141 | -11.3 |
| ธ.ค. | 149 | -3.05 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: สังเกตว่าเส้นสั้นในแต่ละกลุ่มชันขึ้นเกือบทุกกลุ่ม นั่นคือแนวโน้มระยะยาว ส่วนความสูงที่ต่างกันระหว่างกลุ่มคือฤดูกาล สองเรื่องนี้แยกกันได้แล้ว
ข้อควรระวัง
- ใช้สเกลต่างกันในแต่ละกลุ่ม ทำให้เทียบข้ามกลุ่มไม่ได้
- ไม่อธิบายว่าแกนนอนไม่ใช่เวลาที่ไหลต่อเนื่อง ผู้อ่านจะตีความผิดทั้งกราฟ
- ใช้กับข้อมูลที่มีน้อยกว่า 3 รอบ ซึ่งเส้นภายในกลุ่มไม่มีความหมาย
- ทำให้เส้นรายปีเด่นกว่าเส้นค่าเฉลี่ย จนรูปร่างฤดูกาลมองไม่เห็น
เมื่อไหร่ไม่ควรใช้กราฟนี้
- ข้อมูลไม่มีรูปแบบฤดูกาล→ใช้แทน: line-chart
- ต้องการดูเหตุการณ์เฉพาะกิจตามลำดับเวลาจริง→ใช้แทน: line-chart
- ต้องการแยกส่วนประกอบเชิงสถิติอย่างเป็นทางการ→ใช้แทน: seasonal-decomposition