今回はKaggle公式チュートリアル4つ目のTime Series as Featuresを扱います。
(合計6つあるので、折り返し)
前回はフーリエ級数などを用いてcos, sin関数で表し、周期性のあるデータへのアプローチをしてきました。
個人的にも前回までの内容が手に馴染んでないので、Kaggleのデータセットでもちょこちょこ練習しようかなと思います。
今回は特徴量としての時系列データを扱っていく方法です。
例えば移動平均で見れば年々単調増加しているとか、fourierを使ってみると周期性が見つかった、という以外にも
「不定期であるが山がある」ようなデータももちろんあります。
こういう時には移動平均や規則性のある関数を使うことはなかなか難しいので、自分で特徴量を作ったりする必要があります。
今回はそれをみていきます。
先に書いておきますがコーディングの部分に関してはやや複雑であり、1から実装するというのは少し厳しいよね〜というコードですが、順にみていきます。
Cycle(循環?)
時系列データにおける「Cycle」の的確な和訳が不明なので、一旦cycleとします(まぁ、循環とか繰り返しとかで大丈夫とは思いますが、、)
今までの季節性のような規則正しい周期と、何年かに一度起こるようなこと(cycle)の違いは時間依存性があるかどうか、ということに繋がります。
季節性のデータは時系列データによって回帰モデルが作れたりしていましたが、Cycleのような「不規則でありつつ周期がある」という場合は、時系列データのもつ特徴はさほど関係ないとみることができます。
Cycleデータへのアプローチ
一番基礎的なアプローチはLagデータを使ってみていくことになります。
Lagデータは時系列データの一番初めに扱ったshift関数により、任意の日数だけ前(後)のデータを参照したデータをここでは意味します
この時間差のLagデータにより現在データを任意の前(後)のデータで予測することができるようになるため、不規則な時系列データを時間依存性のあるモデリングに落とし込むことができます。
(同じ行に現在と過去のデータがあるため)
Lag Plot
時間差プロット?(和訳不明のため、ここもlag plotで行きます。。)
時系列データの初期でもseabornのregplotで現在データとlagの相関関係と線形性を見ましたが、それです
指定した区間の各1日ごととかで相関関係がどう推移しているのか?を見たりします。
(ちなみに、これを自己相関(autocorrelation)と表記されてたりします。)
イメージの補助のため少しコーディングしました。
rng = np.random.default_rng()
x = np.linspace(100, 200, 100)
y = np.round(x + rng.integers(-80, 70, size=100) - rng.integers(0, 150, size=100), 0)
time_series = pd.date_range(start='2001-4-1', periods=100, freq='MS')
data = pd.DataFrame({'Sales': y}, index=time_series, dtype='int')
lags = 5
_, axes = plt.subplots(1, lags, figsize=(20, 6))
for i in range(1, lags+1):
data[f'Lag_{i}'] = data['Sales'].shift(i)
corr = data[[f'Lag_{i}', 'Sales']].corr()
sns.regplot(x=f'Lag_{i}', y='Sales', data=data, ci=None, label=f'Corr is {corr}', ax=axes[i-1])
axes[i-1].set_title(f'Lag_{i} - Now')
axes[i-1].legend()
plt.show()
(import とかは割愛しました。)
乱数であちこち飛ばしているデータなのでいまいちピンとこないかもなんで、公式のチュートリアルから引用すると以下。
引用元:https://www.kaggle.com/ryanholbrook/time-series-as-features#Lagged-Series-and-Lag-Plots
適切な区間を選ぶ(コレログラム)
(おそらく)ハイバーパラメータの部類に入るLagの指定ですが、クラスタリングのelbowメソッドのように可視化して決める方法(関数)があります。
(当然全部の区間を特徴量として扱うわけにもいかないので)
先に図を見ておきましょう。
import statsmodels.api as sm
# 意図的に周期生のある乱数を生成
rng = np.random.default_rng()
random_lst1 = rng.integers(-80, 70, size=20)
lst1 = [var for i in range(5) for var in random_lst1]
random_lst2 = rng.integers(-80, 70, size=50)
lst2 = [var for i in range(2) for var in random_lst2]
x = np.linspace(100, 200, 100)
y = np.round(x + lst1 - lst2, 0)
time_series = pd.date_range(start='2001-4-1', periods=100, freq='MS')
data = pd.DataFrame({'Sales': y}, index=time_series, dtype='int')
res = sm.graphics.tsa.plot_acf(data['Sales'], lags=10)
statsmodelsにはコレログラムを生成する関数が用意されています。
lagsにみたい区間だけ入れて1つずつずらしながらどのくらい自己相関があるのかを見ています。
図の青(?)で覆われているような部分は自動で計算してくれて「相間があるとは言えない(つまり、特徴量として有効ではない)」ことがわかります。
今回の例で言えば、lag=5,6くらいかな〜という感じです(もっと区間を広げてみるのもありです。)
自作データで軽く演習
ほんとはちゃんとしたデータを使うのが一番ですが、せっかくなのでいつもながら自作してコードを書いてみます。
(ちゃんとしたデータを使いたい方は公式のチュートリアルで学びましょう)
特にコードの説明はせずにサラッとみるだけにとどめようと思います
データ準備とコレログラム
plt.rc("figure", autolayout=True, figsize=(11, 4))
plt.rc(
"axes",
labelweight="bold",
labelsize="large",
titleweight="bold",
titlesize=16,
titlepad=10,
)
plot_params = dict(
color="0.75",
style=".-",
markeredgecolor="0.25",
markerfacecolor="0.25",
)
rng = np.random.default_rng()
random_lst1 = rng.integers(-50, 50, size=25)
lst1 = [var for i in range(4) for var in random_lst1]
random_lst2 = rng.integers(-50, 50, size=25)
lst2 = [var for i in range(4) for var in random_lst2]
x = np.linspace(100, 200, 100)
y = np.round([l1 - l2 for l1, l2 in zip(lst1, lst2)], 0)
time_series = pd.date_range(start='2001-4-1', periods=100, freq='MS')
data = pd.DataFrame({'Sales': y}, index=time_series, dtype='int')
ax = data['Sales'].plot(title="Trend", **plot_params)
res = sm.graphics.tsa.plot_acf(data['Sales'], lags=15)
予測
def make_lags(ts, lags):
return pd.concat(
{
f'y_lag_{i}': ts.shift(i)
for i in range(1, lags + 1)
},
axis=1)
# Lagest_splitのを作成し、上部にできる欠損は0補完
X = make_lags(data['Sales'], lags=3)
X = X.fillna(0.0)
# 予測
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
# Create target series and data splits
y = data['Sales'].copy()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=60, shuffle=False)
# Fit and predict
model = LinearRegression() # `fit_intercept=True` since we didn't use DeterministicProcess
model.fit(X_train, y_train)
y_pred = pd.Series(model.predict(X_train), index=y_train.index)
y_fore = pd.Series(model.predict(X_test), index=y_test.index)
ax = y_train.plot(**plot_params)
ax = y_test.plot(**plot_params)
ax = y_pred.plot(ax=ax)
_ = y_fore.plot(ax=ax, color='C3')
そこまで予測はできてないにせよ、山を追従している感じはあるかな〜という感じでしょうか。
乱数データなので綺麗には行きませんが、どのようなことをしてどのようなアプローチをしているか理解できればあとは実践かなと思います!
今回参考にさせていただいた資料
いつもいろんなサイトにお世話になっております。
些細ながらも皆様の学習・業務の一助となれば良いなと願っております。