🎓 レベル:標準 | 重要度:A(必須)
📎 前提:カルマンフィルタ・ローカルレベルとローカルトレンドモデル | 関連:時系列データと分解(成分の抽出)
要点(BLUF)
- フィルタは各時点で過去と現在の観測だけ()を使うオンライン推定。平滑化(スムーザー)は全データ()を使い、過去の状態を未来の観測で遡って改善するオフライン推定。
- スムーザーは未来の情報も使う分、分散が小さく真の状態に近い。同じ系列でフィルタ RMSE → スムーザー 、状態分散も と縮みます。
- 状態空間なら欠測(NaN)も自然に扱える:欠測時は更新を飛ばし予測だけで状態を運ぶ=不確実性つきの補間。
statsmodelsは NaN を自動処理します。予測は「未来が全部欠測」の特殊ケースとして同じ枠で説明できます。
1. フィルタ vs 平滑化
カルマンフィルタ のフィルタは、時点 の状態を (その時までの観測)で推定しました——リアルタイム向きのオンライン推定です。一方、解析が終わってデータが全部揃っているなら、時点 の状態を推定するのに未来の観測 も使える。これが平滑化です。
- フィルタ :過去と現在のみ。各時点で確定(後から変わらない)。
- スムーザー :全データ。未来も使うのでより正確・分散が小さい。
固定区間スムーザー(RTS スムーザー)は、フィルタを最後まで回したあと後ろ向きに1回走らせて補正します。
「未来から見て状態がこちらにズレていたら、その分を遡って今の推定にも反映する」という補正です。情報が増えるのでスムーザーの分散 はフィルタの 以下になります。
直観的には、フィルタは系列の端(最新)で最も働き、過去ほど未来の観測を取りこぼす。スムーザーは全点で両側の観測を使えるので、系列の真ん中で特に有利です。
コード①:フィルタとスムーザーを真の隠れ状態と比べる
真の隠れ水準を仕込んだローカルレベル系列で、filtered_state(フィルタ)と smoothed_state(スムーザー)を取り出し、どちらが真の水準に近いかを RMSE と状態分散で比べます。
import warnings
warnings.simplefilter("ignore")
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from statsmodels.tsa.statespace.structural import UnobservedComponents
# 真のローカルレベル(隠れ水準を保存しておく)
np.random.seed(7)
n = 120
Q_true, H_true = 0.4, 6.0
alpha = np.zeros(n); alpha[0] = 5.0
for t in range(1, n):
alpha[t] = alpha[t-1] + np.random.normal(0, np.sqrt(Q_true))
y = alpha + np.random.normal(0, np.sqrt(H_true), n)
# 真の分散を与えてフィルタ&スムーザーを実行
res = UnobservedComponents(y, level="local level").smooth([H_true, Q_true])
filt = res.filtered_state[0] # 過去だけを使った推定
smooth = res.smoothed_state[0] # 全データを使った推定
filt_var = res.filtered_state_cov[0, 0]
smooth_var = res.smoothed_state_cov[0, 0]
rmse_filt = np.sqrt(np.mean((filt - alpha)**2))
rmse_smooth = np.sqrt(np.mean((smooth - alpha)**2))
print(f"RMSE フィルタ(過去のみ) = {rmse_filt:.3f}")
print(f"RMSE スムーザー(全データ)= {rmse_smooth:.3f}(こちらが小さい)")
print(f"平均の状態分散 フィルタ={filt_var.mean():.3f} スムーザー={smooth_var.mean():.3f}")
print(f"中央 t=60 の状態分散 フィルタ={filt_var[60]:.3f} スムーザー={smooth_var[60]:.3f}")
fig, ax = plt.subplots(figsize=(9, 4.5))
ax.plot(y, color="0.75", lw=0.8, marker="o", ms=2.5, label="観測 y_t")
ax.plot(alpha, color="k", ls="--", lw=1.5, label="真の隠れ水準")
ax.plot(filt, color="C0", lw=1.5, label="フィルタ(過去のみ)")
ax.plot(smooth, color="C3", lw=1.8, label="スムーザー(全データ)")
ax.set_xlabel("時点 t"); ax.set_ylabel("値"); ax.legend()
ax.set_title("フィルタ vs 平滑化:スムーザーは未来も使い真の水準に近い")
plt.tight_layout(); plt.show()
出力:
RMSE フィルタ(過去のみ) = 1.125
RMSE スムーザー(全データ)= 0.893(こちらが小さい)
平均の状態分散 フィルタ=1.431 スムーザー=0.793
中央 t=60 の状態分散 フィルタ=1.362 スムーザー=0.768
出力の意味:スムーザーの RMSE は で、フィルタの より真の隠れ水準に近い。状態分散も平均 、系列中央()で とスムーザーで縮みます——未来の観測ぶん情報が増えたから。図ではフィルタ(青)が観測のギザギザに少し遅れて追従するのに対し、スムーザー(赤)は両側の観測で均され、黒破線の真の水準に滑らかに張り付きます。過去を振り返って分析するなら、フィルタよりスムーザーを使うべき。ただしスムーザーは全データが要るので、リアルタイム予測には使えません(その時点で未来は無い)。
2. 欠測補間
状態空間のもう一つの強みは欠測への耐性です。観測 が無い時点では、カルマンの更新ステップを飛ばし、予測ステップだけで状態を運びます(ゲイン と同じ)。
観測で補正されないので、欠測が続くほど状態分散 が増え続けます=「埋めた値の自信が下がる」。観測が戻れば更新が再開し、分散が縮む。こうして欠測区間を不確実性つきで補間できます。スムーザーを併用すれば、欠測の両側の観測から内挿するので、さらに自然に埋まります。
コード②:欠測区間をスムーザーで不確実性つきに補間
系列の一部を np.nan にして欠測を作り、UnobservedComponents がそれを自動処理して補間する様子を見ます。欠測点の補間値が真の水準に近いか、95%区間が真値を捕らえるか、欠測点の標準誤差(SE)が観測点より大きいかを確かめます。
import warnings
warnings.simplefilter("ignore")
import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from statsmodels.tsa.statespace.structural import UnobservedComponents
# 真のローカルレベル
np.random.seed(9)
n = 100
Q_true, H_true = 0.5, 3.0
alpha = np.zeros(n); alpha[0] = 8.0
for t in range(1, n):
alpha[t] = alpha[t-1] + np.random.normal(0, np.sqrt(Q_true))
y_full = alpha + np.random.normal(0, np.sqrt(H_true), n)
# 一部を欠測(NaN)にする:40-49 と 70-74 を欠測
y_obs = y_full.copy()
missing_idx = list(range(40, 50)) + list(range(70, 75))
y_obs[missing_idx] = np.nan
print(f"欠測区間 = {missing_idx[0]}-{missing_idx[9]} と {missing_idx[10]}-{missing_idx[-1]}(計{len(missing_idx)}点)")
# statsmodels は NaN を自動処理(欠測時は更新をスキップ=予測のみで状態を運ぶ)
res = UnobservedComponents(y_obs, level="local level").smooth([H_true, Q_true])
interp = res.smoothed_state[0] # 欠測も含む平滑化水準
interp_se = np.sqrt(res.smoothed_state_cov[0, 0])
# 欠測点での補間 vs 真値
mi = np.array(missing_idx)
rmse_interp = np.sqrt(np.mean((interp[mi] - alpha[mi])**2))
covered = ((alpha[mi] >= interp[mi]-1.96*interp_se[mi]) &
(alpha[mi] <= interp[mi]+1.96*interp_se[mi])).sum()
obs_mask = ~np.isnan(y_obs)
print(f"欠測点 補間値 RMSE vs 真の水準 = {rmse_interp:.3f}")
print(f"欠測点 95%区間が真値を捕捉 = {covered}/{len(mi)}")
print(f"平滑化SE 観測あり点の平均={interp_se[obs_mask].mean():.3f} "
f"欠測点の平均={interp_se[mi].mean():.3f}(欠測点は不確実で大きい)")
fig, ax = plt.subplots(figsize=(9, 4.5))
ax.plot(np.arange(n), alpha, color="k", ls="--", lw=1.4, label="真の隠れ水準")
ax.plot(np.arange(n), y_obs, color="0.6", lw=0, marker="o", ms=3, label="観測 y_t(欠測あり)")
ax.plot(np.arange(n), interp, color="C3", lw=1.8, label="スムーザー補間")
ax.fill_between(np.arange(n), interp-1.96*interp_se, interp+1.96*interp_se,
color="C3", alpha=0.2, label="95%区間")
for a, b in [(40, 49), (70, 74)]:
ax.axvspan(a, b, color="C1", alpha=0.12)
ax.set_xlabel("時点 t"); ax.set_ylabel("値"); ax.legend()
ax.set_title("欠測区間の補間:区間は欠測中に広がる(不確実性つき補間)")
plt.tight_layout(); plt.show()
出力:
欠測区間 = 40-49 と 70-74(計15点)
欠測点 補間値 RMSE vs 真の水準 = 1.222
欠測点 95%区間が真値を捕捉 = 14/15
平滑化SE 観測あり点の平均=0.797 欠測点の平均=1.198(欠測点は不確実で大きい)
出力の意味:欠測 15 点を一切手当てせず np.nan のまま渡すだけで、スムーザーが両側の観測から内挿して埋めました。補間値の RMSE は 、95%区間は で真の水準を捕捉。重要なのは標準誤差で、観測がある点の平均 SE に対し欠測点は と大きい——「埋めた値ほど自信がない」と区間が正直に語ります。図のオレンジ帯は欠測区間(40-49・70-74)で膨らみ、観測が戻ると締まります。平均値や線形内挿と違い、状態空間の補間は不確実性を定量化できるのが決定的な差です。欠測の中央が最も不確実(両端から最も遠い)という挙動も自然に出ます。
3. 予測は「未来が全部欠測」
ここまでで予測・フィルタ・平滑化・欠測補間が一つの枠に収まります。すべて「観測がある所では更新し、無い所では予測ステップだけで状態を運ぶ」同じカルマンの操作だからです。
flowchart LR
F["フィルタ:今までの観測 y_(1..t) で現在を推定"] --> S["スムーザー:全観測 y_(1..n) で過去を改善"]
S --> M["欠測補間:途中の欠測を両側から内挿"]
M --> P["予測:未来 y_(t+1..) が全部欠測の特殊ケース"]
- 欠測補間=系列の途中に観測が無い区間(両側に観測あり)。
- 予測=系列の末尾から先が全部欠測(片側=過去にしか観測が無い)。
だから将来予測は「未来をすべて NaN にした欠測補間」と数学的に同じ。片側からしか情報が来ないぶん不確実性が大きく、予測区間はホライズンとともに広がります(ローカルレベルとローカルトレンドモデル・ARMA・ARIMAモデル)。逆に言えば、過去にも未来にも観測がある欠測の方が、末尾の予測より埋めやすい——両側から挟めるからです。
4. 数式の直観
- 情報が増えれば分散は減る:スムーザーは未来の観測という追加情報を使うので、。「振り返るほど過去はよく見える」を分散の不等式が表します。
- 欠測=ゲイン0:観測が無い時点はカルマンゲイン と同じで、状態は予測だけで運ばれ分散が増える。情報が来ない区間で自信が下がる、という自然な振る舞い。
- 一つのアルゴリズムで全部:予測・補間・平滑化を別々の手法でやる必要はなく、状態空間+カルマンなら観測の有無を切り替えるだけ。これが状態空間フレームの大きな実務的利点です。
⚠️ よくある誤解
- 「予測にスムーザーを使う」は不可:スムーザーは全データ(未来含む)が前提。リアルタイムの予測では未来が無いので、使えるのはフィルタだけ。バックテストでうっかりスムーザー状態を特徴量に使うと未来情報の漏洩になります(予測の評価指標と時系列CV)。
- 「補間値は真値と同じ」ではない:補間はあくまで推定。SE・予測区間とセットで扱い、点だけ取り出して確定値のように使わないこと。欠測中央ほど不確実です。
- 「欠測は事前に平均で埋めるべき」ではない:平均代入は不確実性を消し、後段の推定を過信させます。状態空間は NaN のまま渡せばよく、欠測の不確実性を保ったまま推定できます。
- 「スムーザーは常にフィルタより良いから常用」ではない:精度はスムーザーが上ですが、未来確定後にしか計算できない。用途(リアルタイム監視か事後分析か)で使い分けます。
- 「長い欠測でも安心」ではない:欠測が長く続くと区間が大きく広がり、補間は事実上モデルの外挿になります。長期欠測の補間値は過信禁物——区間の広がりがその警告です。
関連ノート
- カルマンフィルタ(フィルタ=オンライン推定の漸化式)
- ローカルレベルとローカルトレンドモデル(対象モデル・予測区間が広がる理由)
- 状態空間モデルの枠組み(状態空間の一般形)
- 時系列データと分解(成分の抽出という発想の源流)
- 予測の評価指標と時系列CV(情報漏洩・時間順検証)
- ARMA・ARIMAモデル(予測区間がホライズンで広がる)
- 第4章 状態空間とカルマン 目次
- 時系列分析・予測テキスト 全体目次