Rの関数:dlmModARMA {dlm}

Rの関数から dlmModARMA {dlm} を確認します。

本ポストはこちらの続きです。

Rの関数:dlmModReg {dlm}
Rの関数から dlmModReg {dlm} を確認します。本ポストはこちらの続きです。dlmModReg 関数dlmModRegは、動的線形モデル(Dynamic Linear Model, 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)

# 再現性のために乱数シードを設定します
seed <- 20251002
set.seed(seed)

# シミュレーションの設定
time_len <- 1000
ar_true <- c(0.7, -0.2) # AR(2)の真の係数
# ma_true <- NULL # MA項はなし
sigma2_true <- 1.5 # ノイズの真の分散

# arima.sim を使ってARMA過程に従う時系列データを生成します
y_t <- arima.sim(
  n = time_len,
  model = list(ar = ar_true),
  sd = sqrt(sigma2_true)
)

# 生成したデータをデータフレームにまとめます
sim_data <- data.frame(
  time = 1:time_len,
  y = as.numeric(y_t)
)

# 生成したデータを確認します
cat("--- サンプルデータの一部を確認 ---\n")
print(head(sim_data))

# データをプロットして確認します
p_data <- ggplot(sim_data, aes(x = time, y = y)) +
  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
Figure 1

dlmModARMAを用いたモデルの推定

次に、作成したサンプルデータ(y_t)がAR(2)モデルに従うと仮定し、dlmModARMAdlmMLEを使って未知のパラメータ(2つのAR係数とノイズ分散)を最尤法で推定します。その後、推定されたモデルを使ってフィルタリングを行い、観測値をどの程度うまく予測できるかを確認します。

# ヘルパー関数:AR係数を部分自己相関係数(PACF)に変換
# Durbin-Levinsonの再帰アルゴリズムを実装します。
ar_to_pacf <- function(ar) {
  p <- length(ar)
  if (p == 0) {
    return(numeric(0))
  }

  # AR係数を行列として初期化
  phi <- matrix(0, nrow = p, ncol = p)
  phi[1, 1] <- ar[1]

  if (p > 1) {
    for (k in 2:p) {
      # より次数の低い係数を計算
      for (j in 1:(k - 1)) {
        phi[k - 1, j] <- phi[k, j] + phi[k, k] * phi[k, k - j]
      }
    }
  }

  # 対角要素がPACFに対応します
  return(diag(phi))
}


# 1. モデルの構造を定義する関数 (build function) を作成します
build_ar2 <- function(par) {
  ar_coeffs <- ARtransPars(par[1:2])
  sigma2_val <- exp(par[3])
  dlmModARMA(ar = ar_coeffs, sigma2 = sigma2_val)
}

# 2. 最尤推定 (MLE) でパラメータを推定します

# === 初期値の設定 ===
ar_fit_init <- ar.mle(y_t, order.max = 2, demean = FALSE)
ar_coeffs_init <- ar_fit_init$ar
sigma2_init <- ar_fit_init$var.pred

# 上記で定義したヘルパー関数を使い、AR係数をPACFに変換します
pacf_init <- ar_to_pacf(ar_coeffs_init)
# PACFをatanh()で制約のないパラメータに変換します
par_ar_init <- atanh(pacf_init)

par_sigma2_init <- log(sigma2_init)
par_init <- c(par_ar_init, par_sigma2_init)

cat("計算された初期値:", par_init, "\n")

mle_fit <- dlmMLE(
  y = y_t,
  parm = par_init, # 計算した初期値を設定
  build = build_ar2,
  method = "BFGS"
)

# 推定結果を確認します
cat("推定の収束状況 (0は正常終了):", mle_fit$conv, "\n")
cat("推定された対数尤度:", -mle_fit$value, "\n\n")

# 推定されたパラメータを元のスケールに戻します
ar_est <- ARtransPars(mle_fit$par[1:2])
sigma2_est <- exp(mle_fit$par[3])

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. 推定されたパラメータを使って最終的なモデルを構築します
final_model <- build_ar2(mle_fit$par)

# 4. カルマンフィルタを実行します
filtered_res <- dlmFilter(y = y_t, mod = final_model)

# 5. 結果を整形し、可視化します
one_step_ahead_forecasts <- filtered_res$f
results_df <- data.frame(
  time = sim_data$time,
  observed = sim_data$y,
  forecast = as.numeric(one_step_ahead_forecasts)
)

p_forecast <- ggplot(results_df, aes(x = time)) +
  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 )
Figure 2

シミュレーション結果の解釈

  • パラメータ推定: dlmMLEによって推定されたAR係数(0.78, -0.22)およびノイズ分散(1.58)は、シミュレーションで設定した真の値(0.7, -0.2, 1.5)に近い値となっています。
  • 観測値と1期先予測値の比較:

    • プロットでは、黒い実線が実際の観測値、赤い鎖線がモデルによる1期先予測値です。
    • 赤い鎖線が黒い実線の動きにほぼ追従しています。

「フィルタリングによる1期先予測値」の意味

状態空間モデルの計算エンジンであるカルマンフィルタは、時点t=1, 2, 3, ... と時間を進めながら、以下の2つのステップを繰り返します。

  1. 予測 (Prediction):

    • 時点 t-1 までに得られた全ての情報を使って、時点 t のシステムの状態(例えばARMAモデルの状態ベクトル)を予測します。
    • 次に、その予測された状態を使って、時点 t観測値 y_tを予測します。
    • この瞬間に計算された y_t の予測値こそが、「フィルタリングによる1期先予測値」です。
  2. 更新 (Update):

    • 実際に観測された y_t の値と、ステップ1で予測した y_t の予測値を比較し、予測誤差を計算します。
    • この予測誤差を使って、ステップ1で行った「状態の予測」を補正し、より確からしい「時点 t の状態の推定値」を求めます。この「更新された状態の推定値」をフィルタリングされた状態と呼びます。

そして、この更新された状態を使って、また次の時点 t+1 の「予測」ステップに進みます。

つまり、「フィルタリングによる1期先予測値」とは、カルマンフィルタの逐次的な計算サイクルの中で、常に一歩先(1期先)の観測値を予測し続ける過程で生まれる値のことです。

以上です。