01ภาพรวมของกราฟนี้
Seasonal decomposition plot แยกอนุกรมเวลาออกเป็นสามส่วนประกอบแล้ววางเรียงกันสี่แผงบนแกนเวลาเดียวกัน แผงบนคือข้อมูลจริง ตามด้วยแนวโน้ม ฤดูกาล และส่วนที่เหลือ ส่วนที่เหลือคือชั้นที่มีค่าที่สุด เพราะมันคือสิ่งที่โมเดลอธิบายไม่ได้
เปรียบเทียบให้เห็นภาพ
เหมือนแยกเสียงเพลงออกเป็นเสียงเบส เสียงกลอง และเสียงร้อง เมื่อฟังแยกกันเราจะได้ยินรายละเอียดที่ฟังรวมแล้วไม่เคยสังเกต
ใช้ที่ไหนบ้าง — วิเคราะห์ยอดขายก่อนทำพยากรณ์ ตรวจว่าการเปลี่ยนแปลงเป็นเรื่องฤดูกาลหรือเรื่องจริง หรือหาเหตุการณ์ผิดปกติที่ซ่อนอยู่ใต้ฤดูกาล
สร้างอย่างไร
- ขั้นที่ 1เลือกความยาวของรอบฤดูกาล
- ขั้นที่ 2สกัดแนวโน้มด้วยค่าเฉลี่ยเคลื่อนที่
- ขั้นที่ 3สกัดฤดูกาลจากค่าเฉลี่ยของแต่ละตำแหน่งในรอบ
- ขั้นที่ 4ส่วนที่เหลือคือสิ่งที่อธิบายไม่ได้
ตัวอย่างคลาสสิก · วิธี STL ของ Cleveland และคณะ ปี 1990
STL ย่อมาจาก Seasonal-Trend decomposition using Loess เป็นวิธีที่ยืดหยุ่นกว่าการแยกแบบคลาสสิกเพราะยอมให้รูปร่างของฤดูกาลค่อย ๆ เปลี่ยนไปตามเวลาได้ และทนต่อค่าผิดปกติ จึงกลายเป็นมาตรฐานของการแยกส่วนประกอบอนุกรมเวลาจนถึงวันนี้
02อธิบายภาษาคนเข้าใจง่าย ๆ
อนุกรมเวลาส่วนใหญ่เป็นผลรวมของสามอย่าง คือทิศทางระยะยาว รูปแบบที่ซ้ำตามฤดูกาล และความผันผวนที่อธิบายไม่ได้ เมื่อดูรวมกันเราแยกไม่ออกว่าอันไหนเป็นอันไหน
การแยกส่วนประกอบคำนวณแต่ละชั้นออกมาแล้ววาดเรียงกัน แผงบนสุดคือข้อมูลดิบ แผงถัดมาคือแนวโน้มที่ได้จากการทำให้เรียบ แผงที่สามคือรูปแบบที่ซ้ำทุกรอบ และแผงล่างคือส่วนที่เหลือ
ชั้นที่คนมักมองข้ามแต่มีค่าที่สุดคือแผงล่าง เพราะมันคือสิ่งที่แนวโน้มและฤดูกาลอธิบายไม่ได้ ถ้ามีจุดที่พุ่งสูงในแผงนี้ นั่นคือเหตุการณ์จริงที่ไม่ใช่ฤดูกาลและไม่ใช่แนวโน้ม
สิ่งที่ต้องตัดสินใจคือความยาวของรอบ ถ้าข้อมูลรายเดือนก็มักเป็น 12 แต่ถ้าเป็นรายวันอาจเป็น 7 สำหรับรอบสัปดาห์หรือ 365 สำหรับรอบปี การเลือกผิดทำให้ผลการแยกไม่มีความหมาย
ข้อควรระวังคือการแยกส่วนประกอบเป็นแบบจำลอง ไม่ใช่ความจริง มันสมมติว่าสามชั้นบวกกันได้และฤดูกาลมีรูปร่างคงที่ ซึ่งอาจไม่จริงกับข้อมูลของเรา
03ข้อมูลที่ต้องใช้
คอลัมน์ที่ต้องเตรียม
| คอลัมน์ | ทำหน้าที่ | ชนิด | เข้ารหัสด้วย | จำนวนค่าที่เหมาะ | หมายเหตุ |
|---|---|---|---|---|---|
date | เวลา | เวลา | x | อย่างน้อย 2 รอบเต็ม | ต้องมีข้อมูลอย่างน้อยสองรอบจึงจะสกัดฤดูกาลได้ |
value | ค่าที่วัดได้ | ตัวเลขต่อเนื่อง | y | ต่อเนื่อง | ช่วงห่างต้องสม่ำเสมอ ข้อมูลที่เก็บไม่สม่ำเสมอต้องรวมเป็นช่วงก่อน |
ต้องเตรียมข้อมูลอย่างไรก่อนวาด
- ทำให้ช่วงห่างของเวลาสม่ำเสมอ
- เลือกความยาวของรอบและเขียนเหตุผล
- ตัดสินใจว่าจะใช้แบบบวกหรือแบบคูณ
ค่าที่หายไป — ต้องเติมค่าที่หายก่อนแยกส่วนประกอบ เพราะค่าเฉลี่ยเคลื่อนที่คำนวณข้ามช่องว่างไม่ได้ และต้องระบุวิธีเติมที่ใช้
สเกลของแกน — แต่ละแผงใช้สเกลของตัวเองเพราะขนาดของส่วนประกอบต่างกันมาก แต่ต้องใช้แกนเวลาร่วมกันเสมอ
กราฟจะเริ่มพังเมื่อไร
น้อยกว่า 2 รอบเต็ม สกัดฤดูกาลไม่ได้เลย
หน้าตาไฟล์ที่ต้องเตรียม
date,month,value
…04อ่านแล้วเห็นอะไร
สิ่งที่เห็นได้จากภาพ
- ทิศทางระยะยาวที่แท้จริงหลังตัดฤดูกาลออก
- รูปร่างและความแรงของฤดูกาล
- จุดผิดปกติที่โผล่ในแผงส่วนที่เหลือ
สิ่งที่กราฟนี้ตอบไม่ได้
- ไม่บอกสาเหตุของส่วนที่เหลือ บอกได้แค่ว่ามีอยู่
- ผลการแยกเปลี่ยนไปตามความยาวรอบที่เลือก จึงไม่ใช่ความจริงที่แน่นอน
อ่านตามลำดับนี้
- ดูแผงแนวโน้มเพื่ออ่านทิศทางที่แท้จริง
- ดูแผงฤดูกาลเพื่ออ่านรูปแบบที่ซ้ำ
- ดูแผงส่วนที่เหลือเพื่อหาเหตุการณ์ผิดปกติ
คำถามที่ตอบได้
- ยอดที่โตขึ้นเป็นเรื่องจริงหรือแค่ฤดูกาล
- ฤดูกาลแรงแค่ไหนเทียบกับแนวโน้ม
- มีเหตุการณ์ผิดปกติเกิดขึ้นเมื่อไร
สัญญาณว่ามีอะไรผิด
- ส่วนที่เหลือมีรูปแบบชัดเจน ซึ่งแปลว่าแบบจำลองยังจับอะไรไม่หมด
- ขนาดของส่วนที่เหลือใหญ่พอ ๆ กับฤดูกาล
05เหมาะกับการนำเสนอแบบไหน
ทำอย่างไรให้คนอ่านได้ข้อสรุปโดยไม่ต้องบรรยาย
- เขียนความยาวรอบที่ใช้และวิธีแยกไว้ใต้กราฟ
- ทำเครื่องหมายจุดที่ส่วนที่เหลือพุ่งสูงผิดปกติ
- ระบุว่าใช้แบบบวกหรือแบบคูณ
รูปแบบชื่อกราฟที่ควรใช้
หลังตัดฤดูกาลออก แนวโน้มที่แท้จริง<เพิ่ม/ลด> <ปริมาณ> และมีเหตุการณ์ผิดปกติที่ <เวลา>
06เอาไปใช้ต่อ
โค้ดทุกชิ้นอ่านไฟล์ seasonal-decomposition-sample.csv ชุดเดียวกับที่ Playground ใช้ และตัวเลขในโค้ดเปลี่ยนตามค่าที่คุณปรับด้านล่าง
# ติดตั้ง: pip install "matplotlib==3.10.*" "pandas==2.*"
# ดาวน์โหลด seasonal-decomposition-sample.csv จากหน้านี้ แล้ววางไว้ข้างสคริปต์
import matplotlib
matplotlib.use("Agg") # รันแบบไม่มีหน้าจอ ถ้ารันในเครื่องตัวเองลบบรรทัดนี้ได้
import matplotlib.pyplot as plt
import pandas as pd
from statsmodels.tsa.seasonal import seasonal_decompose # pip install statsmodels
C1, C2, C3 = "#2a78d6", "#eb6834", "#1baf7a" # palette ของคลัง เปลี่ยนเป็นสีองค์กรได้
INK, SURFACE = "#0b0b0b", "#fcfcfb"
df = pd.read_csv("seasonal-decomposition-sample.csv")
df["value"] = pd.to_numeric(df["value"], errors="coerce")
NOTE = "residual" # ← ค่าจาก Playground
fig, ax = plt.subplots(figsize=(7.6, 4.2), facecolor=SURFACE)
ax.set_facecolor(SURFACE)
df["date"] = pd.to_datetime(df["date"])
s = df.set_index("date")["value"].asfreq("MS").interpolate() # ต้องเติมค่าที่หายก่อน
result = seasonal_decompose(s, model="additive", period=12) # period = ความยาวรอบ
fig.clf(); fig.set_size_inches(7.6, 6)
for i, (name, comp) in enumerate([("ข้อมูลจริง", s), ("แนวโน้ม", result.trend),
("ฤดูกาล", result.seasonal), ("ส่วนที่เหลือ", result.resid)]):
a = fig.add_subplot(4, 1, i + 1)
a.plot(comp.index, comp, color=[INK, C1, C3, C2][i], linewidth=1.6)
a.set_ylabel(name, fontsize=9); a.grid(alpha=0.2)
ax.spines[["top", "right"]].set_visible(False) # ลดเส้นที่ไม่ได้ให้ข้อมูล
fig.tight_layout()
fig.savefig("seasonal-decomposition.png", dpi=160)
print("เขียน seasonal-decomposition.png แล้ว ·", len(df), "แถว")07ลองเล่นเพื่อเข้าใจ
ดูข้อมูลเป็นตาราง (48 แถว)
| วันที่ | จริง | แนวโน้ม | ฤดูกาล | เหลือ |
|---|---|---|---|---|
| 2023-01-15 | 113 | 113 | 8.60 | -8.60 |
| 2023-02-14 | 133 | 123 | 15.8 | -5.67 |
| 2023-03-16 | 137 | 128 | 23.4 | -13.8 |
| 2023-04-15 | 146 | 132 | 25.1 | -11.2 |
| 2023-05-15 | 140 | 134 | 20.6 | -14.5 |
| 2023-06-14 | 136 | 134 | 15.6 | -14.1 |
| 2023-07-14 | 125 | 133 | 4.73 | -12.6 |
| 2023-08-13 | 118 | 131 | -6.09 | -7.12 |
| 2023-09-12 | 113 | 129 | -13.0 | -2.70 |
| 2023-10-12 | 111 | 127 | -16.1 | -0.39 |
| 2023-11-11 | 113 | 126 | -10.2 | -3.08 |
| 2023-12-11 | 122 | 125 | -3.05 | -0.79 |
| 2024-01-10 | 136 | 127 | 8.60 | -0.26 |
| 2024-02-09 | 143 | 128 | 15.8 | -1.01 |
| 2024-03-10 | 153 | 129 | 23.4 | -0.10 |
| 2024-04-09 | 160 | 131 | 25.1 | 3.89 |
| 2024-05-09 | 156 | 132 | 20.6 | 3.26 |
| 2024-06-08 | 160 | 134 | 15.6 | 10.1 |
| 2024-07-08 | 142 | 135 | 4.73 | 1.83 |
| 2024-08-07 | 134 | 137 | -6.09 | 3.67 |
| 2024-09-06 | 126 | 138 | -13.0 | 0.90 |
| 2024-10-06 | 128 | 139 | -16.1 | 4.56 |
| 2024-11-05 | 135 | 141 | -10.2 | 4.25 |
| 2024-12-05 | 141 | 143 | -3.05 | 0.91 |
| 2025-01-04 | 158 | 144 | 8.60 | 4.54 |
| 2025-02-03 | 171 | 147 | 15.8 | 8.02 |
| 2025-03-05 | 175 | 149 | 23.4 | 2.61 |
| 2025-04-04 | 180 | 150 | 25.1 | 4.61 |
| 2025-05-04 | 180 | 152 | 20.6 | 6.72 |
| 2025-06-03 | 171 | 153 | 15.6 | 2.01 |
| 2025-07-03 | 160 | 155 | 4.73 | 0.48 |
| 2025-08-02 | 150 | 156 | -6.09 | 0.37 |
| 2025-09-01 | 143 | 157 | -13.0 | -1.33 |
| 2025-10-01 | 139 | 158 | -16.1 | -3.67 |
| 2025-10-31 | 149 | 160 | -10.2 | -0.35 |
| 2025-11-30 | 155 | 161 | -3.05 | -2.35 |
| 2025-12-30 | 175 | 162 | 8.60 | 4.33 |
| 2026-01-29 | 177 | 163 | 15.8 | -1.34 |
| 2026-02-28 | 200 | 165 | 23.4 | 11.3 |
| 2026-03-30 | 194 | 166 | 25.1 | 2.72 |
| 2026-04-29 | 192 | 167 | 20.6 | 4.56 |
| 2026-05-29 | 186 | 168 | 15.6 | 2.01 |
| 2026-06-28 | 186 | 171 | 4.73 | 10.3 |
| 2026-07-28 | 169 | 172 | -6.09 | 3.09 |
| 2026-08-27 | 164 | 174 | -13.0 | 3.12 |
| 2026-09-26 | 159 | 176 | -16.1 | -0.49 |
| 2026-10-26 | 166 | 177 | -10.2 | -0.82 |
| 2026-11-25 | 178 | 179 | -3.05 | 2.23 |
ลองแล้วจะเห็นอะไร
- สังเกตอะไร: ดูแผงล่างสุดแล้วหาจุดที่พุ่งสูงผิดปกติ นั่นคือเหตุการณ์จริงที่ไม่ใช่ฤดูกาลและไม่ใช่แนวโน้ม ซึ่งกราฟเส้นรวมกลบไว้มิด
ข้อควรระวัง
- เลือกความยาวรอบผิด ทำให้ผลการแยกไม่มีความหมายเลย
- มองข้ามแผงส่วนที่เหลือ ซึ่งเป็นแผงที่มีข้อมูลใหม่มากที่สุด
- ตีความผลการแยกว่าเป็นความจริง ทั้งที่เป็นเพียงแบบจำลองที่ตั้งสมมติฐานไว้
- ใช้แบบบวกกับข้อมูลที่ฤดูกาลโตตามระดับ ซึ่งควรใช้แบบคูณ
เมื่อไหร่ไม่ควรใช้กราฟนี้
- ข้อมูลมีน้อยกว่า 2 รอบเต็ม→ใช้แทน: line-chart
- ผู้อ่านไม่ใช่นักวิเคราะห์→ใช้แทน: cycle-plot หรือ line-chart พร้อมคำอธิบาย
- ต้องการเห็นแนวโน้มภายในแต่ละเดือน→ใช้แทน: cycle-plot