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

กราฟวงรอบCycle Plot

แยกคำถามฤดูกาลออกจากคำถามแนวโน้ม

5
เฉพาะทางควรรู้ก่อน · อ่านกราฟเส้นเป็น และเข้าใจแนวคิดเรื่องฤดูกาลกับแนวโน้ม
ลองเล่น

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

Cycle plot จัดข้อมูลใหม่โดยรวมเดือนเดียวกันของทุกปีไว้ด้วยกัน ภายในแต่ละเดือนวาดแนวโน้มข้ามปี และมีเส้นค่าเฉลี่ยของเดือนนั้นกำกับ ผลคือแยก "รูปแบบฤดูกาล" ออกจาก "แนวโน้มระยะยาว" ได้ชัดเจน ซึ่งกราฟเส้นธรรมดาผสมสองอย่างนี้ไว้ด้วยกัน

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

เหมือนแยกรูปถ่ายวันเกิดของเด็กคนหนึ่งตามอายุ เราเห็นทั้งว่าเด็กโตขึ้นทุกปี และเห็นว่าแต่ละช่วงวัยมีลักษณะเฉพาะอย่างไร

ใช้ที่ไหนบ้าง — ยอดขายรายเดือนหลายปี ปริมาณการใช้ไฟฟ้าตามฤดู จำนวนนักท่องเที่ยวรายเดือน หรือข้อมูลใดที่มีทั้งฤดูกาลและแนวโน้ม

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

  1. ขั้นที่ 1จัดกลุ่มข้อมูลตามตำแหน่งในรอบ เช่น เดือน
  2. ขั้นที่ 2ภายในแต่ละกลุ่มเรียงตามปี
  3. ขั้นที่ 3วาดเส้นแนวโน้มภายในกลุ่ม
  4. ขั้นที่ 4ลากเส้นค่าเฉลี่ยของแต่ละกลุ่ม

ตัวอย่างคลาสสิก · Cycle plot ของ Cleveland ที่ใช้กับข้อมูลการจ้างงานรายเดือน

William Cleveland เสนอกราฟนี้เพื่อแก้ปัญหาว่ากราฟเส้นรายเดือนทำให้แยกไม่ออกว่าการเพิ่มขึ้นมาจากฤดูกาลหรือจากแนวโน้ม เขาแสดงให้เห็นว่าข้อมูลการจ้างงานชุดเดียวกันเล่าคนละเรื่องเมื่อจัดใหม่แบบนี้

Perceptual Edge · Cycle plots

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

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

Cycle plot จัดข้อมูลใหม่ทั้งหมด แทนที่จะเรียงตามเวลาจริง มันเอาเดือนมกราคมของทุกปีมาไว้ด้วยกัน แล้วกุมภาพันธ์ของทุกปีไว้ด้วยกัน เรียงไปเรื่อย ๆ

ภายในแต่ละกลุ่มจึงมีเส้นสั้น ๆ ที่แสดงว่าเดือนมกราคมของแต่ละปีเปลี่ยนไปอย่างไร ถ้าเส้นนี้ชันขึ้น แปลว่าธุรกิจโตจริงในเดือนนั้น ไม่ใช่แค่ผลของฤดูกาล

เส้นแนวนอนที่พาดในแต่ละกลุ่มคือค่าเฉลี่ยของเดือนนั้น เมื่อดูเส้นเฉลี่ยทั้ง 12 กลุ่มพร้อมกัน เราจะเห็นรูปร่างของฤดูกาลชัดเจนโดยไม่มีแนวโน้มมารบกวน

สิ่งที่กราฟนี้ตอบได้และกราฟอื่นตอบยากคือ "เดือนที่ยอดสูงสุดยังโตอยู่ไหม" ซึ่งเป็นคำถามที่สำคัญมากในธุรกิจที่มีฤดูกาลแรง

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

รูปทรงของข้อมูลอนุกรมเวลา
จำนวนมิติที่เข้ารหัสได้3 มิติ
จำนวนแถวที่เหมาะ3–8 รอบ × 12 กลุ่มขั้นต่ำ 36 แถว
เส้นฐานศูนย์เลือกได้อ่านจากความชันภายในกลุ่มและตำแหน่งของเส้นเฉลี่ย ไม่ใช่ความยาวจากศูนย์

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

คอลัมน์ทำหน้าที่ชนิดเข้ารหัสด้วยจำนวนค่าที่เหมาะหมายเหตุ
dateเวลาเวลาxอย่างน้อย 3 รอบเต็มต้องมีข้อมูลหลายรอบถึงจะเห็นแนวโน้มภายในกลุ่ม
monthตำแหน่งในรอบมีลำดับcolumn12 เดือนหรือ 7 วันกลุ่มที่จะจัดข้อมูลเข้า
valueค่าที่วัดได้ตัวเลขต่อเนื่องyต่อเนื่องทุกกลุ่มใช้สเกลร่วมกันเสมอ

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

  1. สกัดตำแหน่งในรอบจากวันที่
  2. ตรวจว่ามีข้อมูลครบทุกกลุ่มในทุกรอบ
  3. คำนวณค่าเฉลี่ยของแต่ละกลุ่มไว้ลากเส้น

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

สเกลของแกน — linear และต้องใช้สเกลร่วมกันทุกกลุ่ม ไม่อย่างนั้นการเทียบข้ามกลุ่มจะไม่มีความหมาย

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

น้อยกว่า 3 รอบ เส้นภายในกลุ่มจะสั้นเกินกว่าจะบอกแนวโน้มได้

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

date,month,value

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

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

  • รูปร่างของฤดูกาลจากตำแหน่งของเส้นเฉลี่ยทั้ง 12 กลุ่ม
  • แนวโน้มภายในแต่ละเดือนจากความชันของเส้นสั้น
  • เดือนที่โตเร็วกว่าเดือนอื่น

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

  • ไม่แสดงลำดับเวลาจริง จึงหาเหตุการณ์เฉพาะกิจไม่ได้
  • ไม่บอกว่ารูปแบบฤดูกาลเปลี่ยนรูปร่างไปหรือไม่

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

  1. ดูเส้นเฉลี่ยของทั้ง 12 กลุ่มก่อนเพื่ออ่านรูปร่างฤดูกาล
  2. ดูความชันของเส้นสั้นในแต่ละกลุ่มเพื่ออ่านแนวโน้ม
  3. หากลุ่มที่ชันต่างจากกลุ่มอื่น

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

  • เดือนไหนสูงที่สุดตามฤดูกาล
  • เดือนที่เคยดีที่สุดยังโตอยู่ไหม
  • ฤดูกาลแรงแค่ไหนเทียบกับแนวโน้มรวม

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

  • แต่ละกลุ่มใช้สเกลของตัวเอง
  • มีข้อมูลน้อยกว่า 3 รอบแต่ยังวาดเส้นแนวโน้ม

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

ผู้ชมที่เหมาะนักวิเคราะห์ที่ต้องแยกฤดูกาลออกจากแนวโน้ม
เวลาที่ผู้ชมต้องใช้ต้องเรียนรู้ก่อน
ขนาดเล็กสุดที่ยังอ่านได้480×280 px
รายงานเชิงวิเคราะห์
สำรวจข้อมูลด้วยตัวเอง
สิ่งพิมพ์
สไลด์ผู้บริหารต้องอธิบายวิธีจัดข้อมูลใหม่ก่อน ซึ่งกินเวลา
สื่อสารกับคนทั่วไปการจัดกลุ่มใหม่ขัดกับความคาดหวังว่าแกนนอนคือเวลาที่ไหลไปข้างหน้า

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

  1. เขียนว่าแกนนอนคือกลุ่มตามฤดูกาล ไม่ใช่เวลาที่ไหลต่อเนื่อง
  2. ทำให้เส้นค่าเฉลี่ยเด่นกว่าเส้นรายปี
  3. เขียนจำนวนรอบที่ใช้คำนวณ

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

<เดือน> เป็นเดือนที่สูงที่สุดตามฤดูกาล และยัง<โต/หด>ต่อเนื่องทุกปี

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), "แถว")

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

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

ข้อมูลตัวอย่าง · 48 แถว · seed 53● คำนวณใหม่ในเบราว์เซอร์ทันทีที่ปรับค่า
เส้นสั้นในแต่ละกลุ่มคือแนวโน้มข้ามปีของเดือนนั้น
ค่าแต่ละปีในเดือนนั้นค่าเฉลี่ยของเดือนค่าเฉลี่ยรวมทุกเดือน
100120140160180200ม.ค.ก.พ.มี.ค.เม.ย.พ.ค.มิ.ย.ก.ค.ส.ค.ก.ย.ต.ค.พ.ย.ธ.ค.ม.ค. · เฉลี่ย 145ก.พ. · เฉลี่ย 156มี.ค. · เฉลี่ย 166เม.ย. · เฉลี่ย 170พ.ค. · เฉลี่ย 167มิ.ย. · เฉลี่ย 163ก.ค. · เฉลี่ย 153ส.ค. · เฉลี่ย 143ก.ย. · เฉลี่ย 137ต.ค. · เฉลี่ย 134พ.ย. · เฉลี่ย 141ธ.ค. · เฉลี่ย 149
เดือนที่สูงสุดเม.ย.
ช่วงฤดูกาล36.0
จำนวนรอบ4
ดูข้อมูลเป็นตาราง (12 แถว)
cycle plot 12 เดือน
เดือนค่าเฉลี่ยต่างจากค่าเฉลี่ยรวม
ม.ค.145-6.62
ก.พ.1564.05
มี.ค.16614.2
เม.ย.17018.0
พ.ค.16715.0
มิ.ย.16311.1
ก.ค.1531.15
ส.ค.143-9.07
ก.ย.137-15.4
ต.ค.134-18.0
พ.ย.141-11.3
ธ.ค.149-3.05

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

  • สังเกตอะไร: สังเกตว่าเส้นสั้นในแต่ละกลุ่มชันขึ้นเกือบทุกกลุ่ม นั่นคือแนวโน้มระยะยาว ส่วนความสูงที่ต่างกันระหว่างกลุ่มคือฤดูกาล สองเรื่องนี้แยกกันได้แล้ว

ข้อควรระวัง

  1. ใช้สเกลต่างกันในแต่ละกลุ่ม ทำให้เทียบข้ามกลุ่มไม่ได้
  2. ไม่อธิบายว่าแกนนอนไม่ใช่เวลาที่ไหลต่อเนื่อง ผู้อ่านจะตีความผิดทั้งกราฟ
  3. ใช้กับข้อมูลที่มีน้อยกว่า 3 รอบ ซึ่งเส้นภายในกลุ่มไม่มีความหมาย
  4. ทำให้เส้นรายปีเด่นกว่าเส้นค่าเฉลี่ย จนรูปร่างฤดูกาลมองไม่เห็น

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

  • ข้อมูลไม่มีรูปแบบฤดูกาลใช้แทน: line-chart
  • ต้องการดูเหตุการณ์เฉพาะกิจตามลำดับเวลาจริงใช้แทน: line-chart
  • ต้องการแยกส่วนประกอบเชิงสถิติอย่างเป็นทางการใช้แทน: seasonal-decomposition

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

เทคนิคถัดไป · กราฟแยกส่วนประกอบอนุกรมเวลา