時間序列的季節性:3種模式及8種建模方法
分析和處理季節性是時間序列分析中的一個關鍵工作,在本文中我們將描述三種類型的季節性以及常見的8種建模方法。
什么是季節性?
季節性是構成時間序列的關鍵因素之一,是指在一段時間內以相似強度重復的系統運動。
季節變化可以由各種因素引起,例如天氣、日歷或經濟條件。各種應用程序中都有這樣的例子。由于假期和旅游的緣故,夏天的機票更貴。另一個例子是消費者支出,由于因為12月的假期而增加。
季節性是指某些時期的平均值與其他時期的平均值不同。這個問題導致該系列是非平穩的。這就是為什么在建立模型時分析季節性是很重要的。
3種模式
在時間序列中可以出現三種類型的季節模式。季節性可以是確定性的,也可以是隨機的。在隨機方面,季節模式可能是平穩的,也可能不是。
這些季節性并不是相互排斥的。時間序列可以同時具有確定性和隨機季節性成分。
1、確定的季節性
具有確定性季節性的時間序列具有恒定的季節模式。它總是以一種可預測的方式出現,無論是在強度上還是在周期性上:
相似強度:在同一季節期間,季節+模式的水平保持不變;
不變周期性:波峰和波谷的位置不改變。也就是說季節模式每次重復之間的時間是恒定的。
比如說下面這個就是一個具有確定性季節性的合成月時間序列:
import numpy as np
period = 12
size = 120
beta1 = 0.3
beta2 = 0.6
sin1 = np.asarray([np.sin(2 * np.pi * i / 12) for i in np.arange(1, size + 1)])
cos1 = np.asarray([np.cos(2 * np.pi * i / 12) for i in np.arange(1, size + 1)])
xt = np.cumsum(np.random.normal(scale=0.1, size=size))
series_det = xt + beta1*sin1 + beta2*cos1 + np.random.normal(scale=0.1, size=size)
我們也可以用傅里葉級數來模擬季節性。傅里葉級數是不同周期的正弦和余弦波。如果季節性是確定性的,那么用傅里葉級數來描述是非常準確的。
2、隨機平穩的季節性
beta1 = np.linspace(-.6, .3, num=size)
beta2 = np.linspace(.6, -.3, num=size)
sin1 = np.asarray([np.sin(2 * np.pi * i / 12) for i in np.arange(1, size + 1)])
cos1 = np.asarray([np.cos(2 * np.pi * i / 12) for i in np.arange(1, size + 1)])
xt = np.cumsum(np.random.normal(scale=0.1, size=size))
# synthetic series with stochastic seasonality
series_stoc = xt + beta1*sin1 + beta2*cos1 + np.random.normal(scale=0.1, size=size)
在連續的季節周期(如一年)中隨機平穩的季節性演變。雖然強度難以預測,但周期性大致保持不變。
有了確定性的季節性,給定月份的預測不會隨年份而改變。對于隨機平穩季節性,最佳猜測取決于前一年同月的值。
3、隨機非平穩季節性
季節模式會在幾個季節期間發生顯著變化,這種季節性的周期性也隨著時間的推移而變化。這意味著波峰和波谷的位置不同。
這種季節性模式的例子出現在不同的領域。這些數據包括消費系列或工業生產數據。當時間序列具有綜合季節性時,變化很難預測。
季節性時間序列的測試
可視化時間序列是一種檢查季節模式的簡單方法。但是可視化并不能系統的說明季節性的模式,所以就需要更系統的方法來描述時間序列的而季節性。
1、測量季節強度
我們可以根據以下方法量化季節模式的強度:
import pandas as pd
from statsmodels.tsa.api import STL
def seasonal_strength(series: pd.Series) -> float:
# time series decomposition
series_decomp = STL(series, period=period).fit()
# variance of residuals + seasonality
resid_seas_var = (series_decomp.resid + series_decomp.seasonal).var()
# variance of residuals
resid_var = series_decomp.resid.var()
# seasonal strength
result = 1 - (resid_var / resid_seas_var)
return result
這個函數估計季節性的強度,不管它是確定性的還是隨機的。
# strong seasonality in the deterministic series
seasonal_strength(series_det)
# 0.93
# strong seasonality in the stochastic series
seasonal_strength(series_stoc)
# 0.91
如果該值高于0.64[2],則需要應用季節性差異過濾器。另一種檢測季節性的方法是QS測試,它在季節性滯后時檢查自相關性。
2、檢測非平穩季節性
有一些統計檢驗是用來檢驗季節模式是否是非平穩的。
一個常見的例子是Canova-Hansen (CH)測試。其假設如下:
- H0(零假設):季節模式平穩(無季節單位根);
- H1:該系列包含一個季節性單位根
OCSB測試和HEGY測試是CH的兩種替代方法。這些方法都可以在Python的pmdarima 庫中找到。
from pmdarima.arima import nsdiffs
period = 12 # monthly data
nsdiffs(x=series_det, m=period, test='ch')
nsdiffs(x=series_det, m=period, test='ocsb')
nsdiffs(x=series_stoc, m=period, test='ch')
nsdiffs(x=series_stoc, m=period, test='ocsb')
函數nsdiffs返回使序列平穩所需的季節差步數。
3、相關性檢測
還有其他專為季節數據設計的檢測。例如,季節性肯德爾檢驗是一種非參數檢驗,用于檢查季節性時間序列的單調趨勢。
檢測季節性模式
季節性指的是在一段時間內重復出現的模式。這是一個重要的變化來源,對建模很重要。
有很多種種處理季節性的方法,其中一些方法在建模之前去掉了季節成分。經季節調整的數據(時間序列減去季節成分)強調長期影響,如趨勢或商業周期。而另外一些方法增加了額外的變量來捕捉季節性的周期性。
在討論不同的方法之前,先創建一個時間序列并描述它的季節模式,我們還繼續使用上面的代碼
period = 12 # monthly series
size = 120
beta1 = np.linspace(-.6, .3, num=size)
beta2 = np.linspace(.6, -.3, num=size)
sin1 = np.asarray([np.sin(2 * np.pi * i / 12) for i in np.arange(1, size + 1)])
cos1 = np.asarray([np.cos(2 * np.pi * i / 12) for i in np.arange(1, size + 1)])
xt = np.cumsum(np.random.normal(scale=0.1, size=size))
yt = xt + beta1 * sin1 + beta2 * cos1 + np.random.normal(scale=0.1, size=size)
yt = pd.Series(yt)
然后通過強度來描述季節模式:
seasonal_strength(yt, period=12)
# 0.90
結果為0.90,表明季節性確實很強。該時間序列的自相關圖如下圖所示:
再使用我們上面介紹的Canova-Hansen檢驗來查看季節性單位根:
from pmdarima.arima import nsdiffs
nsdiffs(x=yt, m=period, test='ch')
# 0
結果為0,表示不存在季節單位根。也就是說季節模式是平穩的。
那么,我們該如何應對像這樣的季節性模式呢?
季節性建模
1、虛擬變量
季節性虛擬變量是一組二元變量。它們表示一個觀測值是否屬于一個給定的時期(例如一月)。
下面是一個如何創建這些變量的例子:
from sktime.transformations.series.date import DateTimeFeatures
from sklearn.preprocessing import OneHotEncoder
monthly_feats = DateTimeFeatures(ts_freq='M',
keep_original_columns=False,
feature_scope='efficient')
datetime_feats = monthly_feats.fit_transform(yt)
datetime_feats = datetime_feats.drop('year', axis=1)
encoder = OneHotEncoder(drop='first', sparse=False)
encoded_feats = encoder.fit_transform(datetime_feats)
encoded_feats_df = pd.DataFrame(encoded_feats,
columns=encoder.get_feature_names_out(),
dtype=int)
這段代碼產生如下數據。
在每個觀察中獲得有關季度和月份的信息(左側表)。該信息存儲在datetime_feats對象中。然后使用one-hot編碼來創建虛擬變量(右側表)。
如果季節性是確定的,那么季節虛擬變量是非常有效。因為確定的季節性種季節模式是固定的,也就是強度和周期性基本不變。并且我們還可以通過檢驗季節虛擬變量的系數來分析季節效應及其變化,這有利于模型的可解釋性。
但是季節性虛擬變量的缺點也很明顯,它假設不同的時期是獨立的。比如1月份的觀測結果與12月份的觀測結果相關。虛擬變量對這種相關性視而不見。所以如果季節模式發生變化,虛擬變量就會產生很多問題。
2、傅里葉級數
傅里葉級數是基于正弦和余弦波的周期性和確定性的變量。與季節性虛擬變量相反,這些三角函數將季節性建模為周期性模式,并且這種結構更能反映現實。
sktime中包含了很好的方法:
from sktime.transformations.series.fourier import FourierFeatures
fourier = FourierFeatures(sp_list=[12],
fourier_terms_list=[4],
keep_original_columns=False)
fourier_feats = fourier.fit_transform(yt)
這里需要指定兩個主要參數:
- sp_list:將季節期間作為一個列表(例如,12個月的數據)
- fourier_terms_list:項的個數,指要包含的正弦和余弦級數的個數。這些都會影響到表示的平滑度。
傅里葉級數是可以添加到模型中的解釋變量。并且可以將這些特性與滯后特性結合起來。
3、徑向基函數
徑向基函數(RBF)是傅里葉級數的替代方法。它他用過創建重復的鐘形曲線來模擬重復的圖案。
在scikit-lego包中有一個RepeatingBasisFunction方法:
from sklego.preprocessing import RepeatingBasisFunction
rbf_encoder = RepeatingBasisFunction(n_periods=4,
column='month_of_year',
input_range=(1, 12),
remainder='drop',
width=0.25)
rbf_features = rbf_encoder.fit_transform(datetime_feats)
rbf_features_df = pd.DataFrame(rbf_features,
columns=[f'RBF{i}'
for i in range(rbf_features.shape[1])])
該方法最重要的三個參數如下:
- n_periods:要包含的基函數的個數
- input_range:列的輸入范圍。例如,在上面的例子中,我們使用(1,12),這是月份的范圍;
- width:徑向基函數的寬度,主要的作用是控制其平滑度
與傅里葉級數一樣,RBF變量可以用作模型中的解釋變量。
4、季節性自回歸
自回歸是大多數預測模型的基礎。這個想法是使用最近的過去觀察(滯后)來預測未來的值。這個概念可以擴展到季節性模型。季節性自回歸模型包括同一季節的過去值作為預測因子。
SARIMA是一種流行的方法,它應用了這個想法:
import pmdarima as pm
model = pm.auto_arima(yt, m=12, trace=True)
model.summary()
# Best model: ARIMA(0,1,0)(1,0,0)[12]
利用季節滯后作為解釋變量是模擬季節性的有效方法。但是在使用這種方法時,應該處理季節性單位根。因為非平穩的數據會產生很多問題。
5、添加額外變量
季節性虛擬變量或傅立葉級數等方法都可以捕捉到周期性模式。但是這些方法都是替代性的方法。
我們也可以通過添加額外變量的方式對季節性進行建模,例如溫度或每個月的工作日數等外生變量來模擬季節性。
6、季節性差分
通過在建模之前從數據中刪除季節性來處理季節性。這種方法叫做季節差分。
季節差異是取同一季節連續觀測值之間的差異的過程。這種操作對于去除季節性單位根部特別有用。
可以使用diff方法進行季節差異:
from sklearn.model_selection import train_test_split
from sktime.forecasting.compose import make_reduction
from sklearn.linear_model import RidgeCV
train, test = train_test_split(yt, test_size=12, shuffle=False)
train_sdiff = train.diff(periods=12)[12:]
forecaster = make_reduction(estimator=RidgeCV(),
strategy='recursive',
window_length=3)
forecaster.fit(train_sdiff)
diff_pred = forecaster.predict(fh=list(range(1, 13)))
我們在差分序列上建立了Ridge回歸模型。通過還原差值運算,可以得到原始尺度上的預報。
7、時間序列分解
還可以使用時間序列分解方法(如STL)去除季節性。
差分和分解的區別是什么?
差分和分解都用于從時間序列中去除季節性。但是轉換后的數據的建模方式不同。
當應用差分時,模型使用差分數據。所以需要還原差分操作以獲得原始尺度上的預測。
而使用基于分解的方法,需要兩組預測。一個是季節性部分,另一個是季節性調整后的數據。最后的預測是各部分預測的總和。
下面是一個基于分解的方法如何工作的例子:
from statsmodels.tsa.api import STL
from sktime.forecasting.naive import NaiveForecaster
# fitting the seasonal decomposition method
series_decomp = STL(yt, period=period).fit()
# adjusting the data
seas_adj = yt - series_decomp.seasonal
# forecasting the non-seasonal part
forecaster = make_reduction(estimator=RidgeCV(),
strategy='recursive',
window_length=3)
forecaster.fit(seas_adj)
seas_adj_pred = forecaster.predict(fh=list(range(1, 13)))
# forecasting the seasonal part
seas_forecaster = NaiveForecaster(strategy='last', sp=12)
seas_forecaster.fit(series_decomp.seasonal)
seas_preds = seas_forecaster.predict(fh=list(range(1, 13)))
# combining the forecasts
preds = seas_adj_pred + seas_preds
在這個例子中,我們建立了一個Ridge 回歸模型來預測經季節調整后的數據。然后將兩個預測加在一起。
8、動態線性模型(DLM)
回歸模型的參數通常是靜態的。它們不隨時間變化,或者是時不變的。DLM是線性回歸的一種特殊情況。其主要特點是參數隨時間而變化,而不是靜態的。
dlm假定季節性時間序列的結構隨季節而變化。因此合理的方法是建立具有時變參數的模型。隨季節變化的參數。
參考文獻[4]中的書的第15章提供了這種方法的一個簡潔的R示例。他們使用時變的MARSS(多元自回歸狀態空間)方法來模擬季節性變化。
總結
時間序列建模并不是一項簡單的任務,它需要考慮多個因素和技術。季節性的存在可以對時間序列數據的分析和預測產生重要影響。識別和理解季節性模式有助于揭示數據的周期性變化、制定季節性調整策略以及進行更準確的預測。時間序列建模往往需要結合經驗和領域知識,同時靈活運用不同的技術和方法,以獲得準確、可靠的模型和預測結果。