
在現實生活中,許多資料並非靜止的,它們會隨著時間不斷變化,形成了一種特殊的資料型態,這就是時間序列資料。時間序列數據廣泛存在於各種領域,如金融市場的股票價格波動、氣候監測的溫度變化、製造業的機器故障檢測,以及網站流量的每日訪問量等。這些資料的最大特點在於:當前的觀測值與過去的值密切相關,未來的趨勢也會受到歷史數據的驅動。
在進行時間序列分析時,簡單的靜態建模方法往往不足以捕捉數據的時間依賴性或動態特徵。例如,在股票預測中,今天的收盤價與昨天的收盤價必然具有一定相關性,僅僅依靠單次獨立的觀測結果無法準確預測未來的趨勢。因此,時間序列分析的重點在於:如何利用數據的時間特性進行建模,挖掘時間依賴模式與變化規律,並在此基礎上做出準確預測。
在這篇筆記中,我會聚焦於如何針對具有時間特性的資料訓練模型,並讓大家了解以下兩個核心問題:
- 如何建構特徵,讓模型能理解時間的動態特性?
- 如何設計模型與驗證方法,確保它既考慮因果關係,又具有預測穩定性?
⏳ 什麼是時間序列分析?
時間序列分析(Time Series Analysis)是一種針對隨時間變化的資料所採用的分析方法,主要在研究資料在時間軸上的模式和趨勢,並利用這些結構性特徵進行描述、解釋和預測。 時間序列數據的最大特點是時序性,其核心假設是:過去的行為會影響未來的趨勢。
時間序列資料的定義
時間序列數據是一組依據時間順序排列的觀測值,通常包括規律的時間間隔,例如:
- 一天中的溫度變化(小時為單位)
- 每月公司的銷售數據(月份為單位)
- 每秒的心跳頻率(秒為單位)
這些數據中的觀測值彼此之間並非獨立,而是存在一定的依賴性。這種依賴性可能反映在長期的趨勢、週期性變化,或者短期的隨機波動中。
時間序列資料的基本元素
時間序列數據可以被分解為以下幾個主要部分:
- 趨勢(Trend):
- 描述數據在長期的整體變化方向,可能是逐漸增加、減少或維持穩定。
- 示例:隨著時間推移,全球平均氣溫逐漸升高。
- 季節性(Seasonality):
- 描述數據在固定時間間隔內的重複模式。
- 示例:零售業每年聖誕期間銷售激增,或者氣溫隨季節變化有明顯周期性波動。
- 週期性(Cycle):
- 與季節性不同,週期性並不一定是固定間隔的變化模式,通常與商業或經濟循環相關。
- 示例:經濟的繁榮與衰退循環。
- 隨機性(Noise):
- 數據中不可預測的隨機波動部分,通常沒有明顯的模式。
- 示例:股票市場的短期波動,以及因外部偶然事件導致的異常變動。
時間序列分析的典型方法
針對時間序列資料的特性,分析方式主要分為統計方法和機器學習/深度學習方法。
統計方法
適用於資料具有較強的線性或穩定模式的情況:
- 移動平均法(Moving Average, MA):用於平滑數據,消除短期波動,反映長期趨勢
- 自回歸模型(AR):利用歷史數據的滯後值預測當前值
- ARIMA(自回歸整合移動平均模型):是 AR 與 MA 的結合,可用於穩定與季節資料的建模與預測
- SARIMA(季節性 ARIMA):進一步結合季節性,適合具有固定周期波動的資料
機器學習方法
適用於資料具有非線性、復雜關係,或需要處理大規模特徵的情況:
- 樹狀演算法: XGBoost, Random Forests
- 支持向量機(SVM)
深度學習方法
適用於長記憶、非線性和超大規模的時序建模,對於非常複雜的動態序列表現出色:
- LSTM / GRU(長短期記憶):對長期依賴性建模效果優異
- Conv1D(卷積時間序列):適用於局部結構模式檢測
- Transformer:優化了長距依賴和注意力機制,能更靈活和穩健地建模時間序列。
時間序列分析的應用場景
金融市場預測:
- 預測股票價格、外匯匯率和債券收益等
銷售與供應鏈:
- 預測產品的需求量,制定生產和庫存計劃
氣象預測:
- 預測氣溫、降雨量、颱風路徑等
異常檢測:
- 發現工業設備運行中的不正常信號,提前預測機器故障
交通流量分析:
- 預測道路車流量或網絡流量
健康數據監測:
- 分析心率、血壓等生命體徵,進行健康風險評估
🧾 0. 環境建議與可重現性
安裝所需套件:
pip install numpy pandas matplotlib seaborn statsmodels scikit-learn prophet
設定亂數種子:
RANDOM_STATE = 42
📦 1. 資料集說明(Dataset Card)
- 名稱:Daily Weather Data
- 來源:Kaggle - Daily Climate Time Series(或 NOAA、任意地區氣象局公開資料)
- 任務:時間序列迴歸(預測未來日均氣溫)
- 欄位:
- date:日期
- meantemp:日均氣溫(°C)
- 其他可能欄位:降雨量、濕度、風速,可作為多變量特徵
🔎 2. 載入與初探(Loading & Quick EDA)
從日均溫的圖可以看出,此份資料擁有特定的模式,搭配時間來看的話就知道是每年6、7月份開始溫度會持續往上,12、1月開始往下,形成這種起伏的圖形。 這就是一種擁有標準季節性特點的時間序列資料。
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.dates import AutoDateLocator, DateFormatter
import seaborn as sns
import kagglehub
import os
# Download latest version
path = kagglehub.dataset_download("sumanthvrao/daily-climate-time-series-data")
print("Path to dataset files:", path)
os.listdir(path)
df = pd.read_csv(os.path.join(path, 'DailyDelhiClimateTrain.csv'))
df = df.sort_values("date")
df.set_index("date", inplace=True)
# 檢查資料
print(df.info())
print(df.describe())
print("Missing values:", df.isna().sum())
# 繪製每日平均溫度,僅顯示年月於 X 軸
plt.figure(figsize=(12, 4))
plt.plot(df["meantemp"])
plt.title("Daily Average Temperature")
plt.xlabel("Date")
plt.ylabel("Temperature (°C)")
# 格式化 X 軸日期顯示為年月
plt.gca().xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter('%Y-%m'))
ax = plt.gca()
ax.xaxis.set_major_locator(AutoDateLocator(maxticks=12)) # 每年最多顯示 12 個標籤
ax.xaxis.set_major_formatter(DateFormatter('%Y-%m'))
plt.gcf().autofmt_xdate()
plt.show()
Index: 1462 entries, 2013-01-01 to 2017-01-01
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 meantemp 1462 non-null float64
1 humidity 1462 non-null float64
2 wind_speed 1462 non-null float64
3 meanpressure 1462 non-null float64
meantemp humidity wind_speed meanpressure
count 1462.000000 1462.000000 1462.000000 1462.000000
mean 25.495521 60.771702 6.802209 1011.104548
std 7.348103 16.769652 4.561602 180.231668
min 6.000000 13.428571 0.000000 -3.041667
25% 18.857143 50.375000 3.475000 1001.580357
50% 27.714286 62.625000 6.221667 1008.563492
75% 31.305804 72.218750 9.238235 1014.944901
max 38.714286 100.000000 42.220000 7679.333333
Missing values: meantemp 0
humidity 0
wind_speed 0
meanpressure 0

🧼 3. 資料前處理(Preprocessing)
- 如果有缺失值,則用線性差補法補值
- 使用盒鬚圖來檢查異常值,從圖上來看,並沒有任何資料點落在上下界線以外的地方,因此判斷沒有異常值
# 缺值處理:線性插補
df["meantemp"] = df["meantemp"].interpolate(method="linear")
# 檢查是否有異常值(用盒鬚圖觀察)
sns.boxplot(x=df["meantemp"])
plt.title("Temperature Distribution")
plt.show()

📊 4. 趨勢與季節性分析(Trend & Seasonality Analysis)
時間序列分析的任務相比其他任務,會需要在建模之前多一些檢查資料的步驟,用以確定此份資料集真的適用於時間序列分析的技術。
4.1 趨勢與季節性視覺化
以結果來看:
- 具備季節性: 固定的波動
- 具備趨勢性: 趨勢穩定向上
- 具備隨機性: 殘差均勻分布在零線兩側
後面較進階的分析留待之後的筆記中再說明。
from statsmodels.tsa.seasonal import seasonal_decompose
decomp = seasonal_decompose(df["meantemp"], model="additive", period=365)
decomp.plot()
plt.suptitle("Trend, Seasonality, and Residuals", y=1.02)
plt.show()

4.2 平穩性檢定(Stationarity Test)
from statsmodels.tsa.stattools import adfuller
adf_result = adfuller(df["meantemp"])
print("ADF Statistic:", adf_result[0])
print("p-value:", adf_result[1])
if adf_result[1] < 0.05:
print("=> 平穩,可直接建模")
else:
print("=> 不平穩,建議做差分或使用SARIMA")
ADF Statistic: -2.0210690559206714
p-value: 0.2774121372301609
=> 不平穩,建議做差分或使用SARIMA
4.3 自相關分析(ACF & PACF)
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
fig, ax = plt.subplots(1, 2, figsize=(12, 4))
plot_acf(df["meantemp"], lags=50, ax=ax[0])
plot_pacf(df["meantemp"], lags=50, ax=ax[1])
ax[0].set_title("Autocorrelation Function (ACF)")
ax[1].set_title("Partial Autocorrelation Function (PACF)")
plt.show()

📉 5. 訓練/測試資料切分(Train-Test Split)
時間序列分析的資料集在切分的時候必須依照時間的先後切分,不能像其他任務一樣隨機切分,會將之前的一段時間當做訓練資料,後面的時間當做測試資料。
df_test = pd.read_csv(os.path.join(path, 'DailyDelhiClimateTest.csv'))
df_test = df_test.sort_values("date")
df_test.set_index("date", inplace=True)
train = df
test = df_test
plt.figure(figsize=(12,4))
plt.plot(train.index, train["meantemp"], label="Train")
plt.plot(test.index, test["meantemp"], label="Test")
plt.legend()
plt.title("Train-Test Split")
plt.show()

🧠 6. 建立基準模型(Baseline)
使用naive方法 (預測今天 = 昨天的溫度)
y_pred_naive = test.shift(1)["meantemp"]
mae_naive = (test["meantemp"] - y_pred_naive).abs().mean()
print("Baseline MAE:", mae_naive)
Baseline MAE: 1.3176342284233629
🔧 7. 建立 ARIMA 模型
模型訓練過程會產些一些表格跟數字,可以暫時忽略,直接看最後的預測圖,可以發現模型預測的一點也不準。 因為只單純以時間跟日均溫來建模,沒有納入其他相關的外部特徵(如:降雨量、濕度等),也沒有進行模型的參數調整。
from statsmodels.tsa.arima.model import ARIMA
from sklearn.metrics import mean_absolute_error, root_mean_squared_error
model = ARIMA(train["meantemp"], order=(5,1,2))
model_fit = model.fit()
print(model_fit.summary())
y_pred_arima = model_fit.forecast(steps=len(test))
mae_arima = mean_absolute_error(test["meantemp"], y_pred_arima)
print("ARIMA MAE:", mae_arima)
plt.figure(figsize=(12,4))
plt.plot(train.index, train["meantemp"], label="Train")
plt.plot(test.index, test["meantemp"], label="Test")
plt.plot(test.index, y_pred_arima, label="ARIMA Forecast")
plt.legend()
plt.show()
SARIMAX Results
==============================================================================
Dep. Variable: meantemp No. Observations: 1462
Model: ARIMA(5, 1, 2) Log Likelihood -2764.775
Date: Fri, 12 Sep 2025 AIC 5545.551
Time: 15:13:35 BIC 5587.846
Sample: 01-01-2013 HQIC 5561.328
- 01-01-2017
Covariance Type: opg
==============================================================================
coef std err z P>|z| [0.025 0.975]
------------------------------------------------------------------------------
ar.L1 -0.1991 0.545 -0.365 0.715 -1.268 0.870
ar.L2 0.4022 0.331 1.214 0.225 -0.247 1.052
ar.L3 -0.0749 0.037 -2.031 0.042 -0.147 -0.003
ar.L4 -0.0208 0.056 -0.371 0.710 -0.130 0.089
ar.L5 0.0099 0.047 0.210 0.833 -0.082 0.102
ma.L1 -0.0217 0.544 -0.040 0.968 -1.088 1.045
ma.L2 -0.5639 0.445 -1.266 0.205 -1.437 0.309
sigma2 2.5772 0.074 34.839 0.000 2.432 2.722
===================================================================================
Ljung-Box (L1) (Q): 0.00 Jarque-Bera (JB): 264.82
Prob(Q): 1.00 Prob(JB): 0.00
Heteroskedasticity (H): 0.80 Skew: -0.47
Prob(H) (two-sided): 0.01 Kurtosis: 4.86
===================================================================================
ARIMA MAE: 8.775028671142111

📏 8. 模型評估
rmse = mean_squared_error(test["meantemp"], y_pred_arima, squared=False)
mae = mean_absolute_error(test["meantemp"], y_pred_arima)
print("RMSE:", rmse, "MAE:", mae)
RMSE: 10.73003505614012
MAE: 8.775028671142111
🔮 9. 模型推論與保存
import joblib
# 保存ARIMA模型
joblib.dump(model_fit, "arima_temperature_model.joblib")
# 載入模型並推論未來7天
loaded_model = joblib.load("arima_temperature_model.joblib")
future_forecast = loaded_model.forecast(steps=7)
print("Next 7 days forecast:")
print(future_forecast)
Next 7 days forecast:
2017-01-02 11.338623
2017-01-03 12.027578
2017-01-04 12.776290
2017-01-05 12.918541
2017-01-06 13.061968
2017-01-07 13.033440
2017-01-08 13.077401
Freq: D, Name: predicted_mean, dtype: float64
✅ 後續改善方向
- 嘗試不同 ARIMA 參數組合 (p,d,q),使用 AIC / BIC 選擇最佳模型
- 嘗試使用 SARIMA 模型,加入季節性參數 (P,D,Q,m)
- 比較 Prophet 與 ARIMA 的 MAE、RMSE
- 嘗試用 LSTM 或 GRU 模型做預測(進階練習)
- 用外部特徵(降雨量、濕度)建立多變量模型,觀察是否能提升預測效果