動的因子と一致指数

因子モデルは、観測された多数の変数の変動の大部分に影響を与える少数の未観測の「因子」を見つけることを目指すもので、主成分分析のような次元削減手法に関連しています。動的因子モデルは、未観測因子の推移ダイナミクスを明示的にモデル化するため、時系列データに適用されることが多いです。

マクロ経済の一致指数は、「景気循環」の共通成分を捉えることを目的としています。この共通成分は、多くのマクロ経済変数に同時に影響を与えると仮定されます。一致指数(例えば、一致経済指標指数)の推定と利用は、動的因子モデルの登場より以前から行われていましたが、StockとWatson(1989年、1991年)はいくつかの影響力のある論文で動的因子モデルを用いてこれらに理論的基盤を提供しました。

以下では、KimとNelson(1999年)が提示したStockとWatson(1991年)のモデルを参考に、動的因子モデルを定式化し、最尤法を用いてそのパラメータを推定し、一致指数を作成します。

マクロ経済データ

一致指数は、以下の4つのマクロ経済変数の動きを考慮して作成されます(これらの変数のバージョンはFREDで利用可能です。以下に使用されたシリーズのIDを示します)。

  • 工業生産指数 (IPMAN)

  • 実質総所得(移転支払いを除く)(W875RX1)

  • 製造業および貿易販売額 (CMRMTSPL)

  • 非農業部門雇用者数 (PAYEMS)

すべての場合において、データは月次で提供され、季節調整が行われています。対象となる期間は1972年から2005年です。

[1]:
%matplotlib inline

import numpy as np
import pandas as pd
import statsmodels.api as sm
import matplotlib.pyplot as plt

np.set_printoptions(precision=4, suppress=True, linewidth=120)
[2]:
from pandas_datareader.data import DataReader

# Get the datasets from FRED
start = '1979-01-01'
end = '2014-12-01'
indprod = DataReader('IPMAN', 'fred', start=start, end=end)
income = DataReader('W875RX1', 'fred', start=start, end=end)
sales = DataReader('CMRMTSPL', 'fred', start=start, end=end)
emp = DataReader('PAYEMS', 'fred', start=start, end=end)
# dta = pd.concat((indprod, income, sales, emp), axis=1)
# dta.columns = ['indprod', 'income', 'sales', 'emp']

注意: 最近のFREDの更新(2015年8月12日)において、時系列データCMRMTSPLが1997年からのデータに短縮されました。これは、CMRMTSPLが接合されたシリーズであるため、前半の期間はHMRMTから構成され、後半の期間がCMRMTで定義されていることが原因である可能性があります。

この問題はその後(2016年2月11日)に修正されましたが、以下に示すように、HMRMTとCMRMTを手動で組み合わせてこのシリーズを構築することも可能です(Alfredのxlsファイルの注記から引用したプロセス)。

[3]:
# HMRMT = DataReader('HMRMT', 'fred', start='1967-01-01', end=end)
# CMRMT = DataReader('CMRMT', 'fred', start='1997-01-01', end=end)
[4]:
# HMRMT_growth = HMRMT.diff() / HMRMT.shift()
# sales = pd.Series(np.zeros(emp.shape[0]), index=emp.index)

# # Fill in the recent entries (1997 onwards)
# sales[CMRMT.index] = CMRMT

# # Backfill the previous entries (pre 1997)
# idx = sales.loc[:'1997-01-01'].index
# for t in range(len(idx)-1, 0, -1):
#     month = idx[t]
#     prev_month = idx[t-1]
#     sales.loc[prev_month] = sales.loc[month] / (1 + HMRMT_growth.loc[prev_month].values)
[5]:
dta = pd.concat((indprod, income, sales, emp), axis=1)
dta.columns = ['indprod', 'income', 'sales', 'emp']
dta.index.freq = dta.index.inferred_freq
[6]:
dta.loc[:, 'indprod':'emp'].plot(subplots=True, layout=(2, 2), figsize=(15, 6));
../../../_images/examples_notebooks_generated_statespace_dfm_coincident_8_0.png

StockとWatson(1991)は、自身のデータセットにおいて、各系列に単位根が存在するという帰無仮説を棄却できなかった(つまり、系列は和分過程である)と報告していますが、系列が共和分しているという強い証拠は見つからなかったと述べています。

その結果、変数の対数の一階差分を用い、それを平均差し引き後に標準化してモデルを推定することを提案しています。

[7]:
# Create log-differenced series
dta['dln_indprod'] = (np.log(dta.indprod)).diff() * 100
dta['dln_income'] = (np.log(dta.income)).diff() * 100
dta['dln_sales'] = (np.log(dta.sales)).diff() * 100
dta['dln_emp'] = (np.log(dta.emp)).diff() * 100

# De-mean and standardize
dta['std_indprod'] = (dta['dln_indprod'] - dta['dln_indprod'].mean()) / dta['dln_indprod'].std()
dta['std_income'] = (dta['dln_income'] - dta['dln_income'].mean()) / dta['dln_income'].std()
dta['std_sales'] = (dta['dln_sales'] - dta['dln_sales'].mean()) / dta['dln_sales'].std()
dta['std_emp'] = (dta['dln_emp'] - dta['dln_emp'].mean()) / dta['dln_emp'].std()

動的因子

一般的な動的因子モデルは次のように表されます:

\[\begin{split}\begin{align} y_t & = \Lambda f_t + B x_t + u_t \\ f_t & = A_1 f_{t-1} + \dots + A_p f_{t-p} + \eta_t \qquad \eta_t \sim N(0, I)\\ u_t & = C_1 u_{t-1} + \dots + C_q u_{t-q} + \varepsilon_t \qquad \varepsilon_t \sim N(0, \Sigma) \end{align}\end{split}\]

ここで、\(y_t\) は観測データ、\(f_t\) は観測されない因子(ベクトル自己回帰として進化する)、\(x_t\) は(オプションの)外生変数、\(u_t\) は誤差、または「固有の」プロセス(\(u_t\) には自己相関が許容される場合もある)を表します。\(\Lambda\) 行列は「因子負荷行列」と呼ばれることが多いです。因子の誤差項の分散は観測されない因子の識別を確保するために単位行列に設定されます。

このモデルは状態空間形式に変換可能であり、観測されない因子はカルマンフィルターを用いて推定されます。フィルタリングの再帰計算の副産物として尤度を評価でき、最大尤度推定法を用いてパラメータを推定することが可能です。

モデル仕様

このアプリケーションで使用する具体的な動的因子モデルは、1つの観測されない因子を含み、その因子はAR(2)過程に従うと仮定されます。イノベーション\(\varepsilon_t\)は独立であると仮定され(したがって\(\Sigma\)は対角行列になります)、各方程式に関連する誤差項\(u_{i,t}\)も独立したAR(2)過程に従うと仮定されます。

したがって、ここで考慮する仕様は以下の通りです:

\[\begin{split}\begin{align} y_{i,t} & = \lambda_i f_t + u_{i,t} \\ u_{i,t} & = c_{i,1} u_{1,t-1} + c_{i,2} u_{i,t-2} + \varepsilon_{i,t} \qquad & \varepsilon_{i,t} \sim N(0, \sigma_i^2) \\ f_t & = a_1 f_{t-1} + a_2 f_{t-2} + \eta_t \qquad & \eta_t \sim N(0, I)\\ \end{align}\end{split}\]

ここで、\(i\)[indprod, income, sales, emp] のいずれかです。

このモデルは、statsmodels に組み込まれている DynamicFactor モデルを使用して定式化できます。特に、以下の仕様が適用されます:

  • k_factors = 1 - (観測されない因子は1つ)

  • factor_order = 2 - (因子はAR(2)過程に従う)

  • error_var = False - (誤差項は独立したAR過程として進化し、VARとして進化しない。この設定はデフォルトのため、以下では明示されていません)

  • error_order = 2 - (誤差項は2次の自己相関を持つ:すなわちAR(2)過程)

  • error_cov_type = 'diagonal' - (イノベーションは相関がない:これもデフォルト設定)

モデルが作成されると、パラメータは最尤法(maximum likelihood)を使用して推定できます。これは、fit() メソッドを使用して行われます。

注意:データは平均を引いた後に標準化されています。これは、後に示す結果を解釈する際に重要です。

補足:Kim and Nelson (1999) の実証例では、雇用変数(employment variable)が因子のラグ値にも依存するやや異なるモデルを考慮しています。このモデルは組み込みの DynamicFactor クラスには適合しませんが、サブクラスを使用して新しいパラメータや制約を実装することで対応できます。詳細は以下の付録Aを参照してください。

パラメータ推定

多変量モデルでは比較的多くのパラメータを持つことがあり、局所的な最小値から抜け出して最尤値を見つけるのが難しい場合があります。この問題を軽減するために、モデルで定義された初期パラメータを使用し、Scipyで利用可能な修正版Powell法を用いて最初の最大化ステップを実行します(詳細はminimizeのドキュメントを参照してください)。この最大化ステップで得られたパラメータは、その後、標準的なLBFGS最適化法における初期パラメータとして使用されます。

[8]:
# Get the endogenous data
endog = dta.loc['1979-02-01':, 'std_indprod':'std_emp']

# Create the model
mod = sm.tsa.DynamicFactor(endog, k_factors=1, factor_order=2, error_order=2)
initial_res = mod.fit(method='powell', disp=False)
res = mod.fit(initial_res.params, disp=False)

推定結果

モデルが推定された後、分析や推論に使用できる2つの要素があります:

  • 推定されたパラメータ

  • 推定された因子

パラメータ

推定されたパラメータはモデルの影響を理解するのに役立ちますが、観測された変数が多く、または観測されない要因が多いモデルでは解釈が難しいことがあります。

この難しさの一因は、因子負荷量と観測されない要因との識別問題によるものです。識別の問題として簡単に見ることができるのは、負荷量と要因の符号です:以下に示すモデルと同等のモデルは、すべての因子負荷量と観測されない要因の符号を反転させることによって得られます。

ここで、このモデルにおける解釈しやすい影響の一つは、観測されない要因の持続性です:この要因は実質的な持続性を示すことがわかります。

[9]:
print(res.summary(separate_params=False))
                                             Statespace Model Results
=================================================================================================================
Dep. Variable:     ['std_indprod', 'std_income', 'std_sales', 'std_emp']   No. Observations:                  431
Model:                                 DynamicFactor(factors=1, order=2)   Log Likelihood               -1586.566
                                                          + AR(2) errors   AIC                           3209.133
Date:                                                   Wed, 28 Aug 2024   BIC                           3282.323
Time:                                                           14:38:52   HQIC                          3238.031
Sample:                                                       02-01-1979
                                                            - 12-01-2014
Covariance Type:                                                     opg
====================================================================================================
                                       coef    std err          z      P>|z|      [0.025      0.975]
----------------------------------------------------------------------------------------------------
loading.f1.std_indprod              -0.6463      0.011    -58.537      0.000      -0.668      -0.625
loading.f1.std_income               -0.2314      0.028     -8.314      0.000      -0.286      -0.177
loading.f1.std_sales                -0.2707      0.029     -9.311      0.000      -0.328      -0.214
loading.f1.std_emp                  -0.6354      0.013    -49.971      0.000      -0.660      -0.610
sigma2.std_indprod                   0.0010    2.7e-05     37.750      0.000       0.001       0.001
sigma2.std_income                    0.8423      0.024     35.811      0.000       0.796       0.888
sigma2.std_sales                     0.7673      0.055     13.843      0.000       0.659       0.876
sigma2.std_emp                       0.0553      0.001    107.611      0.000       0.054       0.056
L1.f1.f1                             0.3455      0.027     12.870      0.000       0.293       0.398
L2.f1.f1                             0.4472      0.030     14.961      0.000       0.389       0.506
L1.e(std_indprod).e(std_indprod)    -1.9636      0.001  -1505.271      0.000      -1.966      -1.961
L2.e(std_indprod).e(std_indprod)    -1.0000   7.62e-07  -1.31e+06      0.000      -1.000      -1.000
L1.e(std_income).e(std_income)      -0.2412      0.019    -12.481      0.000      -0.279      -0.203
L2.e(std_income).e(std_income)      -0.1401      0.037     -3.827      0.000      -0.212      -0.068
L1.e(std_sales).e(std_sales)        -0.3118      0.055     -5.707      0.000      -0.419      -0.205
L2.e(std_sales).e(std_sales)        -0.0287      0.061     -0.469      0.639      -0.149       0.091
L1.e(std_emp).e(std_emp)             0.3580      0.008     42.128      0.000       0.341       0.375
L2.e(std_emp).e(std_emp)             0.4871      0.010     50.836      0.000       0.468       0.506
================================================================================================
Ljung-Box (L1) (Q):     nan, 0.15, 0.62, 6.97   Jarque-Bera (JB):   nan, 10067.56, 9.30, 4015.26
Prob(Q):                nan, 0.70, 0.43, 0.01   Prob(JB):                  nan, 0.00, 0.01, 0.00
Heteroskedasticity (H): nan, 4.25, 0.50, 0.37   Skew:                     nan, -0.91, 0.02, 1.12
Prob(H) (two-sided):    nan, 0.00, 0.00, 0.00   Kurtosis:                nan, 26.61, 3.72, 17.79
================================================================================================

Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
C:\Users\user\projects\statsmodels\venv\lib\site-packages\statsmodels\tsa\stattools.py:1420: RuntimeWarning: invalid value encountered in divide
  test_statistic = numer_squared_sum / denom_squared_sum
C:\Users\user\projects\statsmodels\venv\lib\site-packages\statsmodels\tsa\stattools.py:693: RuntimeWarning: invalid value encountered in divide
  acf = avf[: nlags + 1] / avf[0]

推定された因子

未観測の因子をプロットすることは有用である場合がありますが、ここでは以下の2つの理由から、それほど有用ではありません:

  1. 上記で述べた符号に関連する識別の問題。

  2. データが差分を取られているため、推定された因子は元のデータではなく、差分データの変動を説明している。

これらの理由から、コインシデント指数が作成されます(下記参照)。

これらの留意点を踏まえ、未観測の因子が下記にプロットされており、米国の景気後退に関するNBERの指標も併せて示されています。この因子は、ある程度の景気循環活動を捉えるのに成功しているようです。

[10]:
fig, ax = plt.subplots(figsize=(13,3))

# Plot the factor
dates = endog.index._mpl_repr()
ax.plot(dates, res.factors.filtered[0], label='Factor')
ax.legend()

# Retrieve and also plot the NBER recession indicators
rec = DataReader('USREC', 'fred', start=start, end=end)
ylim = ax.get_ylim()
ax.fill_between(dates[:-3], ylim[0], ylim[1], rec.values[:-4,0], facecolor='k', alpha=0.1);
../../../_images/examples_notebooks_generated_statespace_dfm_coincident_19_0.png

推定後の解析

ここでは、同時指数を構築することによってモデルの結果を解釈できますが、推定された因子が何を捉えているかを把握するための有用で一般的なアプローチがあります。それは、推定された因子を与えられたものとして、それらを(定数と共に)各観測変数に対して回帰し、その決定係数(\(R^2\) 値)を記録する方法です。これにより、各因子がどの変数の分散の大部分を説明しているのか、または説明していないのかを把握できます。

変数と因子が多いモデルでは、これにより因子の解釈が容易になることがあります(例えば、ある因子は主に実体的な変数に対して負荷がかかり、別の因子は名目変数に対して負荷がかかることがあります)。

このモデルでは、内生変数が4つと因子が1つだけであるため、\(R^2\) 値の簡単な表を消化することは容易ですが、より大きなモデルではそうではありません。そのため、棒グラフがよく使用されます。グラフからは、因子が産業生産指数の変動のほとんどを説明し、売上や雇用の変動の大部分を説明していることが簡単にわかりますが、所得の説明にはあまり役立っていないこともわかります。

[11]:
res.plot_coefficients_of_determination(figsize=(8,2));
../../../_images/examples_notebooks_generated_statespace_dfm_coincident_21_0.png

一致指数

上記のように、このモデルの目的は、マクロ経済の現状を理解するために使用できる解釈可能な時系列を作成することでした。これが一致指数の目的です。以下にその構成を示します。構成についての説明に興味がある読者は、Kim and Nelson (1999) または Stock and Watson (1991) を参照してください。

本質的には、行っていることは(差分を取った)因子の平均を再構成することです。これを、フィラデルフィア連邦準備銀行(FRED上のUSPHCI)が発表した一致指数と比較します。

[12]:
usphci = DataReader('USPHCI', 'fred', start='1979-01-01', end='2014-12-01')['USPHCI']
usphci.plot(figsize=(13,3));
../../../_images/examples_notebooks_generated_statespace_dfm_coincident_23_0.png
[13]:
dusphci = usphci.diff()[1:].values
def compute_coincident_index(mod, res):
    # Estimate W(1)
    spec = res.specification
    design = mod.ssm['design']
    transition = mod.ssm['transition']
    ss_kalman_gain = res.filter_results.kalman_gain[:,:,-1]
    k_states = ss_kalman_gain.shape[0]

    W1 = np.linalg.inv(np.eye(k_states) - np.dot(
        np.eye(k_states) - np.dot(ss_kalman_gain, design),
        transition
    )).dot(ss_kalman_gain)[0]

    # Compute the factor mean vector
    factor_mean = np.dot(W1, dta.loc['1972-02-01':, 'dln_indprod':'dln_emp'].mean())

    # Normalize the factors
    factor = res.factors.filtered[0]
    factor *= np.std(usphci.diff()[1:]) / np.std(factor)

    # Compute the coincident index
    coincident_index = np.zeros(mod.nobs+1)
    # The initial value is arbitrary; here it is set to
    # facilitate comparison
    coincident_index[0] = usphci.iloc[0] * factor_mean / dusphci.mean()
    for t in range(0, mod.nobs):
        coincident_index[t+1] = coincident_index[t] + factor[t] + factor_mean

    # Attach dates
    coincident_index = pd.Series(coincident_index, index=dta.index).iloc[1:]

    # Normalize to use the same base year as USPHCI
    coincident_index *= (usphci.loc['1992-07-01'] / coincident_index.loc['1992-07-01'])

    return coincident_index

以下に、計算した一致指数を米国の景気後退と比較して、USPHCI(米国一致指数)とともにプロットします。

[14]:
fig, ax = plt.subplots(figsize=(13,3))

# Compute the index
coincident_index = compute_coincident_index(mod, res)

# Plot the factor
dates = endog.index._mpl_repr()
ax.plot(dates, coincident_index, label='Coincident index')
ax.plot(usphci.index._mpl_repr(), usphci, label='USPHCI')
ax.legend(loc='lower right')

# Retrieve and also plot the NBER recession indicators
ylim = ax.get_ylim()
ax.fill_between(dates[:-3], ylim[0], ylim[1], rec.values[:-4,0], facecolor='k', alpha=0.1);
../../../_images/examples_notebooks_generated_statespace_dfm_coincident_26_0.png

付録 1: 動的因子モデルの拡張

前回の仕様は次のように記述されていました:

\[\begin{split}\begin{align} y_{i,t} & = \lambda_i f_t + u_{i,t} \\ u_{i,t} & = c_{i,1} u_{i,t-1} + c_{i,2} u_{i,t-2} + \varepsilon_{i,t} \qquad & \varepsilon_{i,t} \sim N(0, \sigma_i^2) \\ f_t & = a_1 f_{t-1} + a_2 f_{t-2} + \eta_t \qquad & \eta_t \sim N(0, I)\\ \end{align}\end{split}\]

状態空間形式で書かれた前回のモデルの仕様は次の観測方程式を持っていました:

\[\begin{split}\begin{bmatrix} y_{\text{indprod}, t} \\ y_{\text{income}, t} \\ y_{\text{sales}, t} \\ y_{\text{emp}, t} \\ \end{bmatrix} = \begin{bmatrix} \lambda_\text{indprod} & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \lambda_\text{income} & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ \lambda_\text{sales} & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ \lambda_\text{emp} & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ \end{bmatrix} \begin{bmatrix} f_t \\ f_{t-1} \\ u_{\text{indprod}, t} \\ u_{\text{income}, t} \\ u_{\text{sales}, t} \\ u_{\text{emp}, t} \\ u_{\text{indprod}, t-1} \\ u_{\text{income}, t-1} \\ u_{\text{sales}, t-1} \\ u_{\text{emp}, t-1} \\ \end{bmatrix}\end{split}\]

および遷移方程式:

\[\begin{split}\begin{bmatrix} f_t \\ f_{t-1} \\ u_{\text{indprod}, t} \\ u_{\text{income}, t} \\ u_{\text{sales}, t} \\ u_{\text{emp}, t} \\ u_{\text{indprod}, t-1} \\ u_{\text{income}, t-1} \\ u_{\text{sales}, t-1} \\ u_{\text{emp}, t-1} \\ \end{bmatrix} = \begin{bmatrix} a_1 & a_2 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & c_{\text{indprod}, 1} & 0 & 0 & 0 & c_{\text{indprod}, 2} & 0 & 0 & 0 \\ 0 & 0 & 0 & c_{\text{income}, 1} & 0 & 0 & 0 & c_{\text{income}, 2} & 0 & 0 \\ 0 & 0 & 0 & 0 & c_{\text{sales}, 1} & 0 & 0 & 0 & c_{\text{sales}, 2} & 0 \\ 0 & 0 & 0 & 0 & 0 & c_{\text{emp}, 1} & 0 & 0 & 0 & c_{\text{emp}, 2} \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ \end{bmatrix} \begin{bmatrix} f_{t-1} \\ f_{t-2} \\ u_{\text{indprod}, t-1} \\ u_{\text{income}, t-1} \\ u_{\text{sales}, t-1} \\ u_{\text{emp}, t-1} \\ u_{\text{indprod}, t-2} \\ u_{\text{income}, t-2} \\ u_{\text{sales}, t-2} \\ u_{\text{emp}, t-2} \\ \end{bmatrix} + R \begin{bmatrix} \eta_t \\ \varepsilon_{t} \end{bmatrix}\end{split}\]

DynamicFactor モデルは、状態空間の表現を設定し、DynamicFactor.update メソッドで適切な位置に適合したパラメータ値を埋め込みます。

拡張された仕様は、前の例と同様ですが、雇用がファクターの遅延値にも依存することを許可したいという点が異なります。これにより、\(y_{\text{emp},t}\) の方程式に変更が加わります。現在、次のようになります:

\[\begin{split}\begin{align} y_{i,t} & = \lambda_i f_t + u_{i,t} \qquad & i \in \{\text{indprod}, \text{income}, \text{sales} \}\\ y_{i,t} & = \lambda_{i,0} f_t + \lambda_{i,1} f_{t-1} + \lambda_{i,2} f_{t-2} + \lambda_{i,3} f_{t-3} + u_{i,t} \qquad & i = \text{emp} \\ u_{i,t} & = c_{i,1} u_{i,t-1} + c_{i,2} u_{i,t-2} + \varepsilon_{i,t} \qquad & \varepsilon_{i,t} \sim N(0, \sigma_i^2) \\ f_t & = a_1 f_{t-1} + a_2 f_{t-2} + \eta_t \qquad & \eta_t \sim N(0, I)\\ \end{align}\end{split}\]

次に、対応する観測方程式は次のようになります:

\[\begin{split}\begin{bmatrix} y_{\text{indprod}, t} \\ y_{\text{income}, t} \\ y_{\text{sales}, t} \\ y_{\text{emp}, t} \\ \end{bmatrix} = \begin{bmatrix} \lambda_\text{indprod} & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \lambda_\text{income} & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ \lambda_\text{sales} & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ \lambda_\text{emp,1} & \lambda_\text{emp,2} & \lambda_\text{emp,3} & \lambda_\text{emp,4} & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ \end{bmatrix} \begin{bmatrix} f_t \\ f_{t-1} \\ f_{t-2} \\ f_{t-3} \\ u_{\text{indprod}, t} \\ u_{\text{income}, t} \\ u_{\text{sales}, t} \\ u_{\text{emp}, t} \\ u_{\text{indprod}, t-1} \\ u_{\text{income}, t-1} \\ u_{\text{sales}, t-1} \\ u_{\text{emp}, t-1} \\ \end{bmatrix}\end{split}\]

ここで、新たに2つの状態変数 \(f_{t-2}\)\(f_{t-3}\) を導入しており、そのため遷移方程式を次のように更新する必要があります:

\[\begin{split}\begin{bmatrix} f_t \\ f_{t-1} \\ f_{t-2} \\ f_{t-3} \\ u_{\text{indprod}, t} \\ u_{\text{income}, t} \\ u_{\text{sales}, t} \\ u_{\text{emp}, t} \\ u_{\text{indprod}, t-1} \\ u_{\text{income}, t-1} \\ u_{\text{sales}, t-1} \\ u_{\text{emp}, t-1} \\ \end{bmatrix} = \begin{bmatrix} a_1 & a_2 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & c_{\text{indprod}, 1} & 0 & 0 & 0 & c_{\text{indprod}, 2} & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & c_{\text{income}, 1} & 0 & 0 & 0 & c_{\text{income}, 2} & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & c_{\text{sales}, 1} & 0 & 0 & 0 & c_{\text{sales}, 2} & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & c_{\text{emp}, 1} & 0 & 0 & 0 & c_{\text{emp}, 2} \\ 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ \end{bmatrix} \begin{bmatrix} f_{t-1} \\ f_{t-2} \\ f_{t-3} \\ f_{t-4} \\ u_{\text{indprod}, t-1} \\ u_{\text{income}, t-1} \\ u_{\text{sales}, t-1} \\ u_{\text{emp}, t-1} \\ u_{\text{indprod}, t-2} \\ u_{\text{income}, t-2} \\ u_{\text{sales}, t-2} \\ u_{\text{emp}, t-2} \\ \end{bmatrix} + R \begin{bmatrix} \eta_t \\ \varepsilon_{t} \end{bmatrix}\end{split}\]

このモデルは DynamicFactor クラスではそのまま扱うことはできませんが、状態空間の表現を適切に変更することでサブクラスを作成し、処理することができます。

まず、factor_order = 4を設定した場合、ほぼ求めているものが得られることに気づくでしょう。この場合、観測方程式の最後の行は次のようになります:

\[\begin{split}\begin{bmatrix} \vdots \\ y_{\text{emp}, t} \\ \end{bmatrix} = \begin{bmatrix} \vdots & & & & & & & & & & & \vdots \\ \lambda_\text{emp,1} & 0 & 0 & 0 & 0 & 0 & 0 & 1 & 0 & 0 & 0 & 0 \\ \end{bmatrix} \begin{bmatrix} f_t \\ f_{t-1} \\ f_{t-2} \\ f_{t-3} \\ \vdots \end{bmatrix}\end{split}\]

そして、遷移方程式の最初の行は次のようになります:

\[\begin{split}\begin{bmatrix} f_t \\ \vdots \end{bmatrix} = \begin{bmatrix} a_1 & a_2 & a_3 & a_4 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \\ \vdots & & & & & & & & & & & \vdots \\ \end{bmatrix} \begin{bmatrix} f_{t-1} \\ f_{t-2} \\ f_{t-3} \\ f_{t-4} \\ \vdots \end{bmatrix} + R \begin{bmatrix} \eta_t \\ \varepsilon_{t} \end{bmatrix}\end{split}\]

私たちが求めるものと比較すると、次の違いがあります:

  1. 上記の状況では、\(\lambda_{\text{emp}, j}\)\(j > 0\)のときゼロに強制されており、これをパラメータとして推定したいのです。

  2. 私たちはファクターがAR(2)に従って遷移することを望んでいますが、上記の状況ではAR(4)になっています。

私たちの戦略は、DynamicFactorをサブクラス化し、それがほとんどの作業(状態空間表現の設定など)を行うようにします。factor_order = 4を仮定しているためです。サブクラスで実際に行うことは、この2つの問題を解決することだけです。

まず、サブクラスの完全なコードを示します。それについては後で説明します。最初に重要なことは、以下で定義されているメソッドのどれも省略できなかったという点です。実際、__init__start_paramsparam_namestransform_paramsuntransform_params、およびupdateメソッドは、DynamicFactorクラスだけでなく、statsmodelsのすべての状態空間モデルの核心を形成しています。

[15]:
from statsmodels.tsa.statespace import tools
class ExtendedDFM(sm.tsa.DynamicFactor):
    def __init__(self, endog, **kwargs):
            # Setup the model as if we had a factor order of 4
            super(ExtendedDFM, self).__init__(
                endog, k_factors=1, factor_order=4, error_order=2,
                **kwargs)

            # Note: `self.parameters` is an ordered dict with the
            # keys corresponding to parameter types, and the values
            # the number of parameters of that type.
            # Add the new parameters
            self.parameters['new_loadings'] = 3

            # Cache a slice for the location of the 4 factor AR
            # parameters (a_1, ..., a_4) in the full parameter vector
            offset = (self.parameters['factor_loadings'] +
                      self.parameters['exog'] +
                      self.parameters['error_cov'])
            self._params_factor_ar = np.s_[offset:offset+2]
            self._params_factor_zero = np.s_[offset+2:offset+4]

    @property
    def start_params(self):
        # Add three new loading parameters to the end of the parameter
        # vector, initialized to zeros (for simplicity; they could
        # be initialized any way you like)
        return np.r_[super(ExtendedDFM, self).start_params, 0, 0, 0]

    @property
    def param_names(self):
        # Add the corresponding names for the new loading parameters
        #  (the name can be anything you like)
        return super(ExtendedDFM, self).param_names + [
            'loading.L%d.f1.%s' % (i, self.endog_names[3]) for i in range(1,4)]

    def transform_params(self, unconstrained):
            # Perform the typical DFM transformation (w/o the new parameters)
            constrained = super(ExtendedDFM, self).transform_params(
            unconstrained[:-3])

            # Redo the factor AR constraint, since we only want an AR(2),
            # and the previous constraint was for an AR(4)
            ar_params = unconstrained[self._params_factor_ar]
            constrained[self._params_factor_ar] = (
                tools.constrain_stationary_univariate(ar_params))

            # Return all the parameters
            return np.r_[constrained, unconstrained[-3:]]

    def untransform_params(self, constrained):
            # Perform the typical DFM untransformation (w/o the new parameters)
            unconstrained = super(ExtendedDFM, self).untransform_params(
                constrained[:-3])

            # Redo the factor AR unconstrained, since we only want an AR(2),
            # and the previous unconstrained was for an AR(4)
            ar_params = constrained[self._params_factor_ar]
            unconstrained[self._params_factor_ar] = (
                tools.unconstrain_stationary_univariate(ar_params))

            # Return all the parameters
            return np.r_[unconstrained, constrained[-3:]]

    def update(self, params, transformed=True, **kwargs):
        # Peform the transformation, if required
        if not transformed:
            params = self.transform_params(params)
        params[self._params_factor_zero] = 0

        # Now perform the usual DFM update, but exclude our new parameters
        super(ExtendedDFM, self).update(params[:-3], transformed=True, **kwargs)

        # Finally, set our new parameters in the design matrix
        self.ssm['design', 3, 1:4] = params[-3:]

では、今何をしたのでしょうか?

``__init__``

ここで重要なステップは、操作している基本的な動的因子モデルを指定することでした。特に、上記で説明したように、factor_order=4で初期化していますが、最終的には因子のAR(2)モデルが得られます。また、一般的なセットアップ関連のタスクも実行しました。

``start_params``

start_paramsは、最適化アルゴリズムで初期値として使用されます。新しいパラメータを3つ追加しているため、それらを渡す必要があります。これを行わなかった場合、最適化アルゴリズムはデフォルトの開始値を使用しますが、その場合、3つの要素が不足します。

``param_names``

param_namesはさまざまな場所で使用されますが、特に結果クラスで使用されます。下記で結果の完全なサマリーを取得しますが、これはすべてのパラメータに名前が関連付けられている場合のみ可能です。

``transform_params````untransform_params``

最適化アルゴリズムは、制約なしでパラメータ値を選択する可能性があります。通常、これは望ましくなく(例えば、分散は負であってはならないため)、transform_paramsは最適化アルゴリズムが使用する制約なしの値を、モデルに適した制約付きの値に変換するために使用されます。分散項は通常、正の値を強制するために二乗され、ARのラグ係数は通常、定常モデルを導くように制約されます。untransform_paramsはその逆の操作に使用されます(これは、開始パラメータが通常、モデルに適した値で指定されるため重要です。最適化アルゴリズムに適したパラメータに変換してから最適化ルーチンを開始する必要があります)。

新しいパラメータ(ロード)は理論的には任意の値を取ることができるため、変換や逆変換を行う必要はありませんが、次の2つの理由からこの関数を修正する必要があります:

  1. DynamicFactorクラスのバージョンでは、現在のように3つ少ないパラメータを期待しています。少なくとも、3つの新しいパラメータを処理する必要があります。

  2. DynamicFactorクラスのバージョンでは、因子のラグ係数をAR(4)モデルのように定常に制約しています。実際にはAR(2)モデルを使用しているため、この制約を再度設定する必要があります。ここでは、最後の2つの自己回帰係数をゼロに設定しています。

``update``

新しいupdateメソッドを指定する必要がある最も重要な理由は、状態空間の定式化に3つの新しいパラメータを組み込む必要があるからです。具体的には、親クラスDynamicFactor.updateに、3つの新しいパラメータを除くすべてのパラメータを状態空間表現に配置させ、最後の3つのパラメータを手動で追加します。

[16]:
# Create the model
extended_mod = ExtendedDFM(endog)
initial_extended_res = extended_mod.fit(maxiter=1000, disp=False)
extended_res = extended_mod.fit(initial_extended_res.params, method='nm', maxiter=1000)
print(extended_res.summary(separate_params=False))
Optimization terminated successfully.
         Current function value: 4.686378
         Iterations: 285
         Function evaluations: 478
                                             Statespace Model Results
=================================================================================================================
Dep. Variable:     ['std_indprod', 'std_income', 'std_sales', 'std_emp']   No. Observations:                  431
Model:                                 DynamicFactor(factors=1, order=4)   Log Likelihood               -2019.829
                                                          + AR(2) errors   AIC                           4085.658
Date:                                                   Wed, 28 Aug 2024   BIC                           4179.178
Time:                                                           14:39:03   HQIC                          4122.583
Sample:                                                       02-01-1979
                                                            - 12-01-2014
Covariance Type:                                                     opg
====================================================================================================
                                       coef    std err          z      P>|z|      [0.025      0.975]
----------------------------------------------------------------------------------------------------
loading.f1.std_indprod              -0.7065      0.036    -19.661      0.000      -0.777      -0.636
loading.f1.std_income               -0.2505      0.039     -6.460      0.000      -0.327      -0.175
loading.f1.std_sales                -0.4504      0.023    -19.252      0.000      -0.496      -0.405
loading.f1.std_emp                  -0.4117      0.038    -10.806      0.000      -0.486      -0.337
sigma2.std_indprod                   0.2338      0.045      5.155      0.000       0.145       0.323
sigma2.std_income                    0.8735      0.030     29.572      0.000       0.816       0.931
sigma2.std_sales                     0.5226      0.035     15.117      0.000       0.455       0.590
sigma2.std_emp                       0.2599      0.023     11.352      0.000       0.215       0.305
L1.f1.f1                             0.2845      0.054      5.300      0.000       0.179       0.390
L2.f1.f1                             0.3891      0.058      6.686      0.000       0.275       0.503
L3.f1.f1                                  0   1.87e-10          0      1.000   -3.66e-10    3.66e-10
L4.f1.f1                                  0   1.87e-10          0      1.000   -3.66e-10    3.66e-10
L1.e(std_indprod).e(std_indprod)    -0.2225      0.119     -1.864      0.062      -0.456       0.012
L2.e(std_indprod).e(std_indprod)    -0.1977      0.094     -2.098      0.036      -0.382      -0.013
L1.e(std_income).e(std_income)      -0.2010      0.023     -8.916      0.000      -0.245      -0.157
L2.e(std_income).e(std_income)      -0.0981      0.047     -2.099      0.036      -0.190      -0.007
L1.e(std_sales).e(std_sales)        -0.4842      0.047    -10.269      0.000      -0.577      -0.392
L2.e(std_sales).e(std_sales)        -0.2280      0.050     -4.543      0.000      -0.326      -0.130
L1.e(std_emp).e(std_emp)             0.2325      0.041      5.707      0.000       0.153       0.312
L2.e(std_emp).e(std_emp)             0.4893      0.049     10.044      0.000       0.394       0.585
loading.L1.f1.std_emp               -0.0824      0.037     -2.258      0.024      -0.154      -0.011
loading.L2.f1.std_emp               -0.0065      0.035     -0.184      0.854      -0.076       0.063
loading.L3.f1.std_emp               -0.1731      0.028     -6.094      0.000      -0.229      -0.117
=====================================================================================================
Ljung-Box (L1) (Q):     0.13, 0.01, 1.06, 4.34   Jarque-Bera (JB):   272.57, 10232.62, 25.49, 3815.65
Prob(Q):                0.72, 0.92, 0.30, 0.04   Prob(JB):                     0.00, 0.00, 0.00, 0.00
Heteroskedasticity (H): 0.75, 4.84, 0.44, 0.44   Skew:                        0.26, -0.98, 0.26, 0.78
Prob(H) (two-sided):    0.08, 0.00, 0.00, 0.00   Kurtosis:                   6.86, 26.79, 4.07, 17.49
=====================================================================================================

Warnings:
[1] Covariance matrix calculated using the outer product of gradients (complex-step).
[2] Covariance matrix is singular or near-singular, with condition number 2.17e+19. Standard errors may be unstable.

このモデルは尤度を高めますが、追加の3つのパラメータにペナルティを課すAICおよびBICの基準では好ましくありません。

さらに、定性的な結果は変更されていません。更新された\(R^2\)チャートと新しい一致指数の両方が、前回の結果と実質的に同一であることからもそれが確認できます。

[17]:
extended_res.plot_coefficients_of_determination(figsize=(8,2));
../../../_images/examples_notebooks_generated_statespace_dfm_coincident_34_0.png
[18]:
fig, ax = plt.subplots(figsize=(13,3))

# Compute the index
extended_coincident_index = compute_coincident_index(extended_mod, extended_res)

# Plot the factor
dates = endog.index._mpl_repr()
ax.plot(dates, coincident_index, '-', linewidth=1, label='Basic model')
ax.plot(dates, extended_coincident_index, '--', linewidth=3, label='Extended model')
ax.plot(usphci.index._mpl_repr(), usphci, label='USPHCI')
ax.legend(loc='lower right')
ax.set(title='Coincident indices, comparison')

# Retrieve and also plot the NBER recession indicators
ylim = ax.get_ylim()
ax.fill_between(dates[:-3], ylim[0], ylim[1], rec.values[:-4,0], facecolor='k', alpha=0.1);
../../../_images/examples_notebooks_generated_statespace_dfm_coincident_35_0.png

最終更新日: 2025年01月28日