第 5 篇:初試牛刀 - 簡單的預測方法
經過前面四篇的學習,我們已經具備了處理時間序列數據的基本功:加載、可視化、分解以及處理平穩性。現在,激動人心的時刻到來了——我們要開始嘗試預測 (Forecasting) 未來!
預測是時間序列分析最核心的應用之一。雖然存在很多復雜的預測模型,但萬丈高樓平地起,一些看似簡單的預測方法不僅容易理解和實現,有時效果還出奇地好,并且它們是理解更高級模型的重要基礎。
本篇,我們將學習幾種“入門級”的時間序列預測方法:
- 預測的基本概念: 區分訓練與預測,劃分數據集。
- 樸素預測 (Naive Forecast): 最簡單的方法。
- 簡單平均法 (Simple Average): 用歷史平均值預測。
- 移動平均法 (Moving Average): 用近期歷史平均值預測。
- (可選) 季節性樸素預測 (Seasonal Naive Forecast): 考慮季節性的樸素方法。
我們將用 Python 實現這些方法,并看看它們的預測效果如何。
預測的基本概念
在進行預測之前,我們需要明確兩個基本概念:
-
擬合 (In-sample Fit) vs. 預測 (Out-of-sample Forecast):
- 擬合: 使用模型去“解釋”或“匹配”我們已經擁有的歷史數據。
- 預測: 使用模型去推斷我們尚未觀測到的未來數據點。這才是我們通常意義上的“預測”。
-
訓練集 (Training Set) vs. 測試集 (Test Set):
-
為了評估模型的真實預測能力,我們不能用全部歷史數據來構建模型,然后又用這些數據來評估。這就像考試前知道了所有答案。
-
標準的做法是:將歷史數據劃分為兩部分:
- 訓練集: 用于構建(或“訓練”)我們的預測模型。模型只能看到這部分數據。
- 測試集: 用于評估模型的預測效果。模型在訓練階段看不到這部分數據。我們將模型的預測結果與測試集的真實值進行比較。
-
對于時間序列,通常是按時間順序劃分,較早的數據作為訓練集,較晚的數據作為測試集。例如,用前 80% 的數據訓練,后 20% 的數據測試。
-
最簡單的預測模型
現在,讓我們來認識幾位簡單但重要的“預測選手”。我們將繼續使用之前的月度 CO2 數據(或你可以替換成自己的數據)。
import pandas as pd
import numpy as np
import statsmodels.api as sm
import matplotlib.pyplot as plt
import seaborn as sns# --- 數據準備 ---
# 1. 加載數據
data = sm.datasets.co2.load_pandas().data
data['co2'].interpolate(inplace=True)
monthly_data = data.resample('M').mean()# 2. 劃分訓練集和測試集 (例如,最后 2 年作為測試集)
train_data = monthly_data[:-24] # 除去最后 24 個月
test_data = monthly_data[-24:] # 最后 24 個月print(f"訓練集范圍: {train_data.index.min()} to {train_data.index.max()}")
print(f"測試集范圍: {test_data.index.min()} to {test_data.index.max()}")# --- 可視化劃分結果 (可選) ---
plt.figure(figsize=(12, 6))
plt.plot(train_data.index, train_data['co2'], label='Train Data')
plt.plot(test_data.index, test_data['co2'], label='Test Data (Actual)')
plt.title('CO2 Data: Train/Test Split')
plt.xlabel('Date')
plt.ylabel('CO2 Concentration')
plt.legend()
plt.show()
1. 樸素預測 (Naive Forecast)
- 邏輯: 假設未來跟現在(最近的已知值)一樣。預測下一期的值就等于訓練集中最后一期的實際值。
?(t+1) = Y(t_last_train)
- 優點: 極其簡單,無需參數,是一個重要的基準 (Baseline) 模型(任何更復雜的模型都應該比它做得更好才有意義)。
- 缺點: 無法捕捉趨勢和季節性,對波動敏感。
- 實現:
# 獲取訓練集最后一個值
last_train_value = train_data['co2'].iloc[-1]# 創建測試集長度的預測值,所有值都等于 last_train_value
naive_forecast = pd.Series([last_train_value] * len(test_data), index=test_data.index)print("\n樸素預測 (Naive Forecast):")
print(naive_forecast.head())
2. 簡單平均法 (Simple Average)
- 邏輯: 假設未來會和歷史的平均水平一樣。預測未來所有期的值都等于訓練集中所有數據的平均值。
?(t+k) = mean(Y_train)
for all k > 0
- 優點: 簡單,考慮了所有歷史信息。
- 缺點: 忽略了時間序列的演變(趨勢、季節性),對早期數據和近期數據給予同等權重。如果序列有明顯趨勢,效果通常很差。
- 實現:
# 計算訓練集平均值
train_mean = train_data['co2'].mean()# 創建測試集長度的預測值,所有值都等于 train_mean
simple_avg_forecast = pd.Series([train_mean] * len(test_data), index=test_data.index)print("\n簡單平均法預測 (Simple Average Forecast):")
print(simple_avg_forecast.head())
3. 移動平均法 (Moving Average)
- 邏輯: 只考慮最近的一段歷史。預測下一期的值等于訓練集中最近 N 個數據點的平均值。N 是需要我們指定的窗口大小 (Window Size)。
?(t+1) = mean(Y(t), Y(t-1), ..., Y(t-N+1))
- 優點: 比簡單平均法更關注近期變化,能一定程度平滑短期波動。
- 缺點: 無法很好地處理趨勢和季節性。預測值對窗口大小 N 很敏感。它本質上是對近期水平的估計,不是趨勢預測。嚴格來說,移動平均更多用于平滑數據或作為更復雜模型的組件,直接用于多步預測效果有限(通常只預測一步,或者假設未來多步都等于這個平均值)。
- 實現 (預測未來所有期都等于最后窗口的平均值):
# 設置移動平均窗口大小 (例如,最近 12 個月)
window_size = 12# 計算訓練集最后 N 個點的平均值
moving_avg = train_data['co2'].iloc[-window_size:].mean()# 創建測試集長度的預測值
moving_avg_forecast = pd.Series([moving_avg] * len(test_data), index=test_data.index)print(f"\n移動平均法預測 (Moving Average Forecast, N={window_size}):")
print(moving_avg_forecast.head())
4. (可選) 季節性樸素預測 (Seasonal Naive Forecast)
- 邏輯: 假設下個季節/周期的同一時間點會和上個季節/周期一樣。例如,預測明年 1 月的值等于今年 1 月的值。
?(t+k) = Y(t+k-s)
,其中s
是季節周期長度 (e.g., 12 for monthly data with annual seasonality)。
- 優點: 考慮了季節性,對于有強季節性模式的數據可能效果不錯。也是一個重要的基準。
- 缺點: 忽略了趨勢和其他變化。
- 實現 (需要訪問訓練集中更早的數據):
# 季節周期
seasonality = 12seasonal_naive_forecast_list = []
for i in range(len(test_data)):if i >= seasonality:# 使用測試集前一個季節周期的預測值seasonal_value = test_data['co2'].iloc[i - seasonality]else:# 不足一個周期,用訓練集最后一個完整周期前的值seasonal_value = train_data['co2'].iloc[-seasonality + i]seasonal_naive_forecast_list.append(seasonal_value)seasonal_naive_forecast = pd.Series(seasonal_naive_forecast_list, index=test_data.index)print("\n季節性樸素預測 (Seasonal Naive Forecast):")
print(seasonal_naive_forecast.head())
可視化預測結果
光看數字不夠直觀,讓我們把預測結果和測試集的真實值畫在一起比較一下。
plt.figure(figsize=(14, 8))# 繪制訓練數據
plt.plot(train_data.index, train_data['co2'], label='Train Data')# 繪制測試數據 (真實值)
plt.plot(test_data.index, test_data['co2'], label='Test Data (Actual)', color='black', linewidth=2)# 繪制各種預測結果
plt.plot(test_data.index, naive_forecast, label='Naive Forecast', linestyle='--')
plt.plot(test_data.index, simple_avg_forecast, label='Simple Average Forecast', linestyle='--')
plt.plot(test_data.index, moving_avg_forecast, label=f'Moving Average (N={window_size}) Forecast', linestyle='--')
plt.plot(test_data.index, seasonal_naive_forecast, label='Seasonal Naive Forecast', linestyle='--')# 添加標題和標簽
plt.title('Comparison of Simple Forecast Methods')
plt.xlabel('Date')
plt.ylabel('CO2 Concentration')
plt.legend()
plt.tight_layout()
plt.show()
解讀圖形:
- 觀察每種預測方法(虛線)與測試集真實值(黑色實線)的接近程度。
- 對于 CO2 數據(有明顯趨勢和季節性):
- 樸素預測 和 簡單平均 顯然跟不上趨勢,預測線是平的。
- 移動平均 (N=12) 也基本是平的,因為它只是最后12個月的平均,沒有預測趨勢。
- 季節性樸素預測 捕捉到了季節波動,但沒有捕捉到整體上升的趨勢。
- 這表明,對于具有明顯趨勢和/或季節性的數據,這些簡單方法可能不足以做出準確預測。它們更多是作為后續更復雜模型的比較基準。
小結
今天我們邁出了預測的第一步:
- 理解了預測的目標是推斷未來值,以及訓練集/測試集劃分的重要性。
- 學習并實現了四種簡單的預測方法:
- 樸素預測 (Naive):
?(t+1) = Y(t)
- 簡單平均法 (Simple Average):
?(t+k) = mean(Y_train)
- 移動平均法 (Moving Average):
?(t+1) = mean(Y(t), ..., Y(t-N+1))
- 季節性樸素預測 (Seasonal Naive):
?(t+k) = Y(t+k-s)
- 樸素預測 (Naive):
- 通過可視化比較了這些方法在測試集上的表現,并認識到它們作為基準模型的價值。
下一篇預告
我們已經做出了幾種預測,但是怎么量化地評價哪個預測“更好”呢?光靠看圖是不夠嚴謹的。下一篇,我們將學習常用的預測評估指標 (Evaluation Metrics),如 MAE, MSE, RMSE 等,它們將幫助我們用數字來衡量預測的準確性。
準備好給你的預測打分了嗎?敬請期待!
(嘗試用你自己的時間序列數據跑一遍這些簡單預測方法,看看哪個效果相對最好?窗口大小 N 對移動平均預測影響大嗎?歡迎分享!)