3 回答

TA貢獻1860條經驗 獲得超8個贊
概述
通過使用Scikit-Learn
庫,可以考慮使用不同的決策樹來預測數據。在此示例中,我們將使用AdaBoostRegressor
,但也可以切換到RandomForestRegressor
或任何其他可用的樹。因此,通過選擇樹,我們應該意識到去除數據的趨勢,通過這種方式,我們說明了通過分別對數據進行差分和對數變換來控制時間序列的均值和方差的示例。
準備數據
時間序列有兩個基本組成部分,即均值和方差。理想情況下,我們希望控制這些組件,對于可變性,我們可以簡單地對數據應用對數變換,對于趨勢我們可以區分它,我們稍后會看到。
此外,對于這種方法,我們考慮實際值 y_t 可以用兩個先驗值 y_t-1 和 y_t-2 來解釋。您可以通過修改函數的輸入來處理這些滯后值range
。
# Load data
tsdf = pd.read_csv('tsdf.csv', sep="\t")
# For simplicity I just take the target variable and the date
tsdf = tsdf[['sales_index', 'dates']]
# Log value
tsdf['log_sales_index'] = np.log(tsdf.sales_index)
# Add previous n-values
for i in range(3):
? ??
? ? tsdf[f'sales_index_lag_{i+1}'] = tsdf.sales_index.shift(i+1)
? ??
? ? # For simplicity we drop the null values?
? ? tsdf.dropna(inplace=True)
? ??
? ? tsdf[f'log_sales_index_lag_{i+1}'] = np.log(tsdf[f'sales_index_lag_{i+1}'])
? ??
? ? tsdf[f'log_difference_{i+1}'] = tsdf.log_sales_index - tsdf[f'log_sales_index_lag_{i+1}']
一旦我們的數據準備就緒,我們就會得到類似于下圖的結果。
數據是固定的嗎?
為了控制時間序列的平均分量,我們應該對數據進行一些差分。為了確定是否需要執行此步驟,我們可以執行單位根測試。為簡單起見,我們將考慮 KPSS 檢驗,我們假設數據是平穩的零假設,特別是,它假設圍繞均值或線性趨勢平穩。
from arch.unitroot import KPSS
# Test for stationary
kpss_test = KPSS(tsdf.sales_index)
# Test summary?
print(kpss_test.summary().as_text())
我們看到P-value = .280
大于通常的約定0.05
。因此,我們需要對數據應用一階差分。作為旁注,可以迭代地執行此測試以了解應將多少差異應用于數據。
在下圖中,我們看到了原始數據與對數一階差分的比較,注意時間序列的這些最后值突然發生變化,這似乎是結構性變化,但我們不會深入潛入其中。如果您想深入了解這個主題,布魯斯·漢森 (Bruce Hansen) 提供的這些幻燈片很有用。
plt.figure(figsize=(12,?6)) plt.subplot(1,2,1) plt.plot(tsdf.sales_index) plt.title('Original?Time?Series') plt.subplot(1,2,2) plt.plot(tsdf.log_difference_1) plt.title('Log?first?difference?Time?Series')
決策樹模型
正如我們之前所說,我們正在考慮決策樹模型,在使用它們時應該注意從時間序列中刪除趨勢。例如,如果你有上升趨勢,tress 不擅長預測下降趨勢。在下面的代碼示例中,我選擇了AdaBoostRegressor
,但您可以自由選擇其他樹模型。另外,注意 被?log_difference_1
認為是由log_difference_2
和解釋的log_difference_3
。
注意。您的數據集還有其他協變量 或
aus_avg_rain
,slg_adt_ctl
因此考慮將它們用于預測,您也可以對它們應用滯后值。
from sklearn.model_selection import train_test_split
from sklearn.ensemble import AdaBoostRegressor
# Forecast difference of log values
X, Y = tsdf[['log_difference_2', 'log_difference_3']], tsdf['log_difference_1']
# Split in train-test
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, shuffle=False, random_state=0)
# Initialize the estimator
mdl_adaboost = AdaBoostRegressor(n_estimators=500, learning_rate=0.05)
# Fit the data
mdl_adaboost.fit(X_train, Y_train)
# Make predictions
pred = mdl_adaboost.predict(X_test)
test_size = X_test.shape[0]
評估預測
test_size = X_test.shape[0]
plt.plot(list(range(test_size)), np.exp(tsdf.tail(test_size).log_sales_index_lag_1? + pred), label='predicted', color='red')
plt.plot(list(range(test_size)), tsdf.tail(test_size).sales_index, label='real', color='blue')
plt.legend(loc='best')
plt.title('Predicted vs Real with log difference values')
決策樹模型似乎準確地預測了真實值。我將使用TimeSeriesSplit
from 函數scikit-learn
通過平均絕對誤差來評估模型的誤差。
from sklearn.model_selection import TimeSeriesSplit
from sklearn.metrics import mean_absolute_error
X, Y = np.array(tsdf[['log_difference_2', 'log_difference_3']]), np.array(tsdf['log_difference_1'])
# Initialize a TimeSeriesSplitter
tscv = TimeSeriesSplit(n_splits=5)
# Retrieve log_sales_index and sales_index to unstransform data
tsdf_log_sales_index = np.array(tsdf.copy().reset_index().log_sales_index_lag_1)
tsdf_sales_index = np.array(tsdf.copy().reset_index().sales_index_lag_1)
# Dict to store metric value at every iteration
metric_iter = {}
for idx, val in enumerate(tscv.split(X)):
? ??
? ? ? ? train_i, test_i = val
? ??
? ? ? ? X_train, X_test = X[train_i], X[test_i]
? ? ? ? Y_train, Y_test = Y[train_i], Y[test_i]
? ? ? ? # Initialize the estimator
? ? ? ? mdl_adaboost = AdaBoostRegressor(n_estimators=500, learning_rate=0.05)
? ? ? ? # Fit the data
? ? ? ? mdl_adaboost.fit(X_train, Y_train)
? ? ? ? # Make predictions
? ? ? ? pred = mdl_adaboost.predict(X_test)
? ? ? ??
? ? ? ? # Unstransform predictions
? ? ? ? pred_untransform = [np.exp(val_test + val_pred) for val_test, val_pred in zip(tsdf_log_sales_index[test_i], pred)]
? ? ? ??
? ? ? ? # Real value
? ? ? ? real = tsdf_sales_index[test_i]
? ? ? ??
? ? ? ? # Store metric
? ? ? ? metric_iter[f'iter_{idx + 1}'] = mean_absolute_error(real, pred_untransform)? ? ? ? ? ? ?
現在我們看到平均MAE誤差非常低。
print(f'Average MAE error: {np.mean(list(metric_iter.values()))}')
>>> Average MAE error: 17.631090959806535

TA貢獻1877條經驗 獲得超1個贊
這有兩個部分:
一個有效的預測代碼解決方案。
幾個話題的簡單討論
解決方案
我將建議一種略有不同且更抽象的方法:使用構建在 scikit-learn 之上的Darts 。它包括您期望的“列表”庫(例如,pandas 和 NumPy),但也包括一些您需要對該領域有相當深入的了解才能考慮包含的庫(例如,Torch、Prophet 和 Holidays)。此外,它已經包含了許多模型。重要說明:特定庫是 u8darts,而不是飛鏢。它們很相似——并且具有相似的依賴關系——但它們并不相同。
使用它,您可以輕松地開發出具有相當不錯結果的預測模型。例如,整個代碼,包括合并重命名的列標題(以及刪除未重命名的列和重復的行)只是幾行。
import pandas as pd
import matplotlib.pyplot as plt
from darts import TimeSeries
from darts.models import ExponentialSmoothing
df = pd.read_csv("../data/tsdf.csv", sep="\t")
df.drop(['Unnamed: 0', 'aus_slg_fmCatl'], axis=1, inplace = True)
df.columns = ['Date', 'adult_cattle(head)','Bulls Bullocks & Steers(head)', 'Cows&Heifers(head)',
? ? ? ? ? ? ? 'Calves(head)', 'Beef(tonnes)', 'Veal(tonnes)','Adult cattle(kg/head)', 'Calves(kg/head)',
? ? ? ? ? ? ? 'aust-avg_rain','US/AUS_ExchangeRate', 'sales_index', 'retail_sales_index']
df.drop_duplicates(subset=['Date'], inplace=True)
series = TimeSeries.from_dataframe(df, 'Date', 'Beef(tonnes)').values
train, val = series.split_before(pd.Timestamp('2019-01-01'))
model = ExponentialSmoothing()
model.fit(train)
prediction = model.predict(len(val))
series.plot(label='actual')
prediction.plot(label='forecast', lw=2)
plt.legend()
此外,他們的 repo 有一個帶有額外模型和示例的筆記本
結果
討論
針對我們收到的評論中的討論,我認為有幾點需要說明。
去除季節性
如果您正在為預測模型拍攝,這是您很少想做的事情。如果你知道每年冬天家庭/辦公室供暖的燃料價格都會上漲,但決定將其排除在外——你會被交易員活活吃掉,他們很樂意拿走你的錢。
虛擬數據
這里的訣竅是您不知道您的虛擬數據是否與真實數據看起來足夠相似。然而,解決方案是一個不同的問題。
“在 Python 中使用 R 庫……”
ARMA 和 ARIMA
...不太可能產生豐碩的結果,部分原因是誤差項假設誤差是IID,但市場存在偏差。指數平滑效果很好,因為它替代了
“但是當我針對 sales_index 運行它時……”
sales_index 將由幾個自變量組成。正如您所期望的那樣,肉類生產(奇怪的是與降雨量呈負相關)和匯率。但是,您的模型中未包含其他國家/地區的生產數據(100% 純有機、草料、“AAA”艾伯塔牛肉)、稅收、國內牛肉生產、運輸等。這就是您可能不想要的原因對抗既有寬客又有專業領域專業知識的專業交易員。最后,我要指出的是,時間上沒有任何跡象表明這是正常的還是不正常的。
從 2015 年到 2017 年,似乎出現了 1.5 年的上升,隨后一直下降到 2019 年。這似乎在重復,但 2020 年的突然變化讓人相信這是一種失常。但數據太小,很難判斷任何有效性。在這種情況下,趨勢線或通道會更好。

TA貢獻1868條經驗 獲得超4個贊
在初始化模型和進行預測時,您可以ARMA使用可選參數向模型添加其他功能。exog
例如,要添加一些您的功能:
# initialize the model
model = ARMA(train['sales_index'],
exog=train[['slg_adt_ctl', 'slg_bbs', 'retail_sales_index']],
order=(2,0))
model_fit = model.fit() # fit the model
# make predictions
predictions = model_fit.predict(start=len(train),
end=len(train)+len(test)-1,
exog=test[['slg_adt_ctl', 'slg_bbs', 'retail_sales_index']],
dynamic=False)
當我們制作預測圖時,我們現在獲得了一些額外的預測能力。
添加回答
舉報