Rの関数から dlmModARMA {dlm} を確認します。
本ポストはこちらの続きです。

dlmModARMA 関数
dlmModARMA
は、ARMA(p,q)モデルを動的線形モデル(DLM)、すなわち状態空間モデルの形式で構築するためのヘルパー関数です。
ARMA(p,q)モデルは、時系列データ y_t
の現在の値を、過去の値(AR: 自己回帰部分)と過去のノイズ(MA: 移動平均部分)の線形和で表現する時系列モデルです。
y_t = φ_1 * y_{t-1} + ... + φ_p * y_{t-p} + ε_t + θ_1 * ε_{t-1} + ... + θ_q * ε_{t-q}
ここで ε_t
は平均0、分散 σ^2
のホワイトノイズです。
このARMAモデルは、適切な状態ベクトル α_t
を定義することで、以下の状態空間表現に変換できます。
- 観測方程式:
y_t = F_t * α_t + v_t
- 状態方程式:
α_t = G_t * α_{t-1} + w_t
dlmModARMA
関数は、ユーザーがAR係数 φ
、MA係数 θ
、ノイズの分散 σ^2
を指定するだけで、この変換を自動的に行い、対応する遷移行列 G
やシステムノイズの共分散行列 W
などを設定した dlm
オブジェクトを生成します。
この関数を使うことで、ARMAモデルを dlm
パッケージの統一的な枠組みで扱えるようになり、カルマンフィルタ(dlmFilter
)や平滑化(dlmSmooth
)、最尤法によるパラメータ推定(dlmMLE
)といった機能を利用できるようになります。
dlmModARMA 関数の引数
dlmModARMA
関数の引数について確認します。
library(dlm)
args(dlmModARMA)
function (ar = NULL, ma = NULL, sigma2 = 1, dV, m0, C0)
NULL
ar = NULL
: ARMAモデルのAR(自己回帰)部分の係数を指定する数値ベクトルです。p
次のARモデルの場合、c(φ_1, φ_2, ..., φ_p)
のように指定します。デフォルトはNULL
で、AR項がないことを意味します。ma = NULL
: ARMAモデルのMA(移動平均)部分の係数を指定する数値ベクトルです。q
次のMAモデルの場合、c(θ_1, θ_2, ..., θ_q)
のように指定します。デフォルトはNULL
で、MA項がないことを意味します。sigma2 = 1
: ARMAモデルを駆動するホワイトノイズε_t
の分散σ^2
を指定するスカラー値です。このノイズは「innovation」とも呼ばれます。デフォルトは1です。dV
: 観測ノイズv_t
の分散V
を指定します。純粋なARMAモデルでは、観測値y_t
はノイズなしで状態ベクトルから生成されると考えるため、観測ノイズは存在しません(V=0
)。dlmModARMA
は、この引数が指定されない場合、自動的にdV=0
を設定します。もしdV
に0より大きい値を設定すると、ARMA過程そのものに加えて、測定誤差などの観測ノイズが上乗せされた「ARMA + noise」モデルを表現することになります。m0
: 状態ベクトルα
の初期値α_0
に関する事前分布の平均ベクトルを指定します。この引数が指定されない場合、モデルが定常であると仮定して、期待値が0のベクトルが自動的に設定されます。C0
: 状態ベクトルα
の初期値α_0
に関する事前分布の共分散行列を指定します。この引数が指定されない場合、モデルが定常であると仮定した上での状態ベクトルの無条件共分散行列が自動的に計算され、設定されます。
シミュレーション
ここからは、dlmModARMA
を使ってARMAモデルのシミュレーションを行います。
サンプルデータの作成
まず、シミュレーションのためのサンプルデータを作成します。ここでは、AR(2)モデルに従う時系列データを生成します。
- モデル:
y_t = 0.7 * y_{t-1} - 0.2 * y_{t-2} + ε_t
- ノイズ
ε_t
の分散σ^2
: 1.5 - データ点数: 1000
# 必要なパッケージをロードします
library(ggplot2)
# 再現性のために乱数シードを設定します
<- 20251002
seed set.seed(seed)
# シミュレーションの設定
<- 1000
time_len <- c(0.7, -0.2) # AR(2)の真の係数
ar_true # ma_true <- NULL # MA項はなし
<- 1.5 # ノイズの真の分散
sigma2_true
# arima.sim を使ってARMA過程に従う時系列データを生成します
<- arima.sim(
y_t n = time_len,
model = list(ar = ar_true),
sd = sqrt(sigma2_true)
)
# 生成したデータをデータフレームにまとめます
<- data.frame(
sim_data time = 1:time_len,
y = as.numeric(y_t)
)
# 生成したデータを確認します
cat("--- サンプルデータの一部を確認 ---\n")
print(head(sim_data))
# データをプロットして確認します
<- ggplot(sim_data, aes(x = time, y = y)) +
p_data geom_line(color = "royalblue") +
labs(
title = "生成したAR(2)のサンプルデータ",
x = "時間",
y = "値"
+
) theme_bw()
print(p_data)
--- サンプルデータの一部を確認 ---
time y
1 1 -0.3033776
2 2 -1.3874422
3 3 -0.3174086
4 4 -1.1692636
5 5 1.3950522
6 6 2.0683807
dlmModARMAを用いたモデルの推定
次に、作成したサンプルデータ(y_t
)がAR(2)モデルに従うと仮定し、dlmModARMA
とdlmMLE
を使って未知のパラメータ(2つのAR係数とノイズ分散)を最尤法で推定します。その後、推定されたモデルを使ってフィルタリングを行い、観測値をどの程度うまく予測できるかを確認します。
# ヘルパー関数:AR係数を部分自己相関係数(PACF)に変換
# Durbin-Levinsonの再帰アルゴリズムを実装します。
<- function(ar) {
ar_to_pacf <- length(ar)
p if (p == 0) {
return(numeric(0))
}
# AR係数を行列として初期化
<- matrix(0, nrow = p, ncol = p)
phi 1, 1] <- ar[1]
phi[
if (p > 1) {
for (k in 2:p) {
# より次数の低い係数を計算
for (j in 1:(k - 1)) {
- 1, j] <- phi[k, j] + phi[k, k] * phi[k, k - j]
phi[k
}
}
}
# 対角要素がPACFに対応します
return(diag(phi))
}
# 1. モデルの構造を定義する関数 (build function) を作成します
<- function(par) {
build_ar2 <- ARtransPars(par[1:2])
ar_coeffs <- exp(par[3])
sigma2_val dlmModARMA(ar = ar_coeffs, sigma2 = sigma2_val)
}
# 2. 最尤推定 (MLE) でパラメータを推定します
# === 初期値の設定 ===
<- ar.mle(y_t, order.max = 2, demean = FALSE)
ar_fit_init <- ar_fit_init$ar
ar_coeffs_init <- ar_fit_init$var.pred
sigma2_init
# 上記で定義したヘルパー関数を使い、AR係数をPACFに変換します
<- ar_to_pacf(ar_coeffs_init)
pacf_init # PACFをatanh()で制約のないパラメータに変換します
<- atanh(pacf_init)
par_ar_init
<- log(sigma2_init)
par_sigma2_init <- c(par_ar_init, par_sigma2_init)
par_init
cat("計算された初期値:", par_init, "\n")
<- dlmMLE(
mle_fit y = y_t,
parm = par_init, # 計算した初期値を設定
build = build_ar2,
method = "BFGS"
)
# 推定結果を確認します
cat("推定の収束状況 (0は正常終了):", mle_fit$conv, "\n")
cat("推定された対数尤度:", -mle_fit$value, "\n\n")
# 推定されたパラメータを元のスケールに戻します
<- ARtransPars(mle_fit$par[1:2])
ar_est <- exp(mle_fit$par[3])
sigma2_est
cat("推定されたAR(1)係数:", ar_est[1], "(真の値:", ar_true[1], ")\n")
cat("推定されたAR(2)係数:", ar_est[2], "(真の値:", ar_true[2], ")\n")
cat("推定されたノイズの分散 (sigma2):", sigma2_est, "(真の値:", sigma2_true, ")\n\n")
# 3. 推定されたパラメータを使って最終的なモデルを構築します
<- build_ar2(mle_fit$par)
final_model
# 4. カルマンフィルタを実行します
<- dlmFilter(y = y_t, mod = final_model)
filtered_res
# 5. 結果を整形し、可視化します
<- filtered_res$f
one_step_ahead_forecasts <- data.frame(
results_df time = sim_data$time,
observed = sim_data$y,
forecast = as.numeric(one_step_ahead_forecasts)
)
<- ggplot(results_df, aes(x = time)) +
p_forecast geom_line(aes(y = observed, color = "観測値"), alpha = 0.8) +
geom_line(aes(y = forecast, color = "1期先予測値"), linetype = "dotted", linewidth = 1) +
scale_color_manual(values = c("観測値" = "black", "1期先予測値" = "red")) +
labs(
title = "観測値とフィルタリングによる1期先予測値の比較",
x = "時間",
y = "値",
color = "系列"
+
) theme_bw() +
theme(legend.position = "bottom")
print(p_forecast)
計算された初期値: 0 0 0.4571158
推定の収束状況 (0は正常終了): 0
推定された対数尤度: -742.264
推定されたAR(1)係数: 0.76747 (真の値: 0.7 )
推定されたAR(2)係数: -0.2188414 (真の値: -0.2 )
推定されたノイズの分散 (sigma2): 1.579307 (真の値: 1.5 )
シミュレーション結果の解釈
- パラメータ推定:
dlmMLE
によって推定されたAR係数(0.78, -0.22)およびノイズ分散(1.58)は、シミュレーションで設定した真の値(0.7, -0.2, 1.5)に近い値となっています。 - 観測値と1期先予測値の比較:
- プロットでは、黒い実線が実際の観測値、赤い鎖線がモデルによる1期先予測値です。
- 赤い鎖線が黒い実線の動きにほぼ追従しています。
「フィルタリングによる1期先予測値」の意味
状態空間モデルの計算エンジンであるカルマンフィルタは、時点t=1, 2, 3, ...
と時間を進めながら、以下の2つのステップを繰り返します。
- 予測 (Prediction):
- 時点
t-1
までに得られた全ての情報を使って、時点t
のシステムの状態(例えばARMAモデルの状態ベクトル)を予測します。 - 次に、その予測された状態を使って、時点
t
の観測値y_t
を予測します。 - この瞬間に計算された
y_t
の予測値こそが、「フィルタリングによる1期先予測値」です。
- 時点
- 更新 (Update):
- 実際に観測された
y_t
の値と、ステップ1で予測したy_t
の予測値を比較し、予測誤差を計算します。 - この予測誤差を使って、ステップ1で行った「状態の予測」を補正し、より確からしい「時点
t
の状態の推定値」を求めます。この「更新された状態の推定値」をフィルタリングされた状態と呼びます。
- 実際に観測された
そして、この更新された状態を使って、また次の時点 t+1
の「予測」ステップに進みます。
つまり、「フィルタリングによる1期先予測値」とは、カルマンフィルタの逐次的な計算サイクルの中で、常に一歩先(1期先)の観測値を予測し続ける過程で生まれる値のことです。
以上です。