マルコフスイッチング自己回帰モデル¶
このノートブックでは、Kim and Nelson (1999)で示されたいくつかの結果を再現するために、statsmodelsのマルコフスイッチングモデルの使用例を提供します。Hamilton (1989) フィルターとKim (1994) スムーザーを適用しています。
これは、E-Views 8のマルコフスイッチングモデル(http://www.eviews.com/EViews8/ev8ecswitch_n.html#MarkovAR)や、Stata 14のマルコフスイッチングモデル(http://www.stata.com/manuals14/tsmswitch.pdf)と比較してテストされています。
[1]:
%matplotlib inline
from datetime import datetime
from io import BytesIO
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import requests
import statsmodels.api as sm
# NBER recessions
from pandas_datareader.data import DataReader
usrec = DataReader(
"USREC", "fred", start=datetime(1947, 1, 1), end=datetime(2013, 4, 1)
)
ハミルトン(1989)のGNPスイッチングモデル¶
これは、マルコフスイッチングモデルを導入したハミルトン(1989)の画期的な論文を再現したものです。このモデルは、プロセスの平均が2つのレジーム間でスイッチする4次の自己回帰モデルです。次のように書くことができます。
各期間において、レジームは以下の遷移確率行列に従って遷移します:
ここで、\(p_{ij}\)はレジーム\(i\)からレジーム\(j\)への遷移確率を示します。
このモデルのクラスは、statsmodelsの時系列部分にあるMarkovAutoregressionです。モデルを作成するには、k_regimes=2でレジームの数を指定し、order=4で自己回帰の次数を指定する必要があります。デフォルトのモデルにはスイッチング自己回帰係数が含まれていますが、ここではswitching_ar=Falseを指定してそれを避けます。
作成後、モデルは最尤推定法でfitされます。内部では、期待値最大化(EM)アルゴリズムのいくつかのステップを使用して良い初期パラメータが見つけられ、クワジ・ニュートン(BFGS)アルゴリズムが適用されて最大値を素早く見つけます。
[2]:
# Get the RGNP data to replicate Hamilton
dta = pd.read_stata("rgnp.dta").iloc[1:]
dta.index = pd.DatetimeIndex(dta.date, freq="QS")
dta_hamilton = dta.rgnp
# Plot the data
dta_hamilton.plot(title="Growth rate of Real GNP", figsize=(12, 3))
# Fit the model
mod_hamilton = sm.tsa.MarkovAutoregression(
dta_hamilton, k_regimes=2, order=4, switching_ar=False
)
res_hamilton = mod_hamilton.fit()
[3]:
res_hamilton.summary()
[3]:
| Dep. Variable: | rgnp | No. Observations: | 131 |
|---|---|---|---|
| Model: | MarkovAutoregression | Log Likelihood | -181.263 |
| Date: | Thu, 28 Nov 2024 | AIC | 380.527 |
| Time: | 23:10:50 | BIC | 406.404 |
| Sample: | 04-01-1951 | HQIC | 391.042 |
| - 10-01-1984 | |||
| Covariance Type: | approx |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| const | -0.3588 | 0.265 | -1.356 | 0.175 | -0.877 | 0.160 |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| const | 1.1635 | 0.075 | 15.614 | 0.000 | 1.017 | 1.310 |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| sigma2 | 0.5914 | 0.103 | 5.761 | 0.000 | 0.390 | 0.793 |
| ar.L1 | 0.0135 | 0.120 | 0.112 | 0.911 | -0.222 | 0.249 |
| ar.L2 | -0.0575 | 0.138 | -0.418 | 0.676 | -0.327 | 0.212 |
| ar.L3 | -0.2470 | 0.107 | -2.310 | 0.021 | -0.457 | -0.037 |
| ar.L4 | -0.2129 | 0.111 | -1.926 | 0.054 | -0.430 | 0.004 |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| p[0->0] | 0.7547 | 0.097 | 7.819 | 0.000 | 0.565 | 0.944 |
| p[1->0] | 0.0959 | 0.038 | 2.542 | 0.011 | 0.022 | 0.170 |
Warnings:
[1] Covariance matrix calculated using numerical (complex-step) differentiation.
私たちは、景気後退の確率のフィルタリングおよびスムージングされた推定値をプロットします。フィルタリングとは、時刻 \(t\) における確率の推定値で、時刻 \(t\) までのデータに基づいています(ただし、時刻 \(t+1, ..., T\) のデータは除外されます)。スムージングとは、標本内のすべてのデータを使用して、時刻 \(t\) における確率の推定値を算出することを指します。
参考までに、灰色の期間はNBERの景気後退期間を示しています。
[4]:
fig, axes = plt.subplots(2, figsize=(7, 7))
ax = axes[0]
ax.plot(res_hamilton.filtered_marginal_probabilities[0])
ax.fill_between(usrec.index, 0, 1, where=usrec["USREC"].values, color="k", alpha=0.1)
ax.set_xlim(dta_hamilton.index[4], dta_hamilton.index[-1])
ax.set(title="Filtered probability of recession")
ax = axes[1]
ax.plot(res_hamilton.smoothed_marginal_probabilities[0])
ax.fill_between(usrec.index, 0, 1, where=usrec["USREC"].values, color="k", alpha=0.1)
ax.set_xlim(dta_hamilton.index[4], dta_hamilton.index[-1])
ax.set(title="Smoothed probability of recession")
fig.tight_layout()
推定された遷移行列から、不況と拡張の予想期間を計算することができます。
[5]:
print(res_hamilton.expected_durations)
[ 4.07604745 10.42589385]
この場合、景気後退は約1年間(4四半期)、景気拡大は約2年半続くと予想されています。
Kim, Nelson, and Startz (1998) 三状態分散スイッチングモデル¶
このモデルは、レジームの異方分散(分散の切り替え)を持ち、平均効果がない場合の推定を示しています。データセットは以下のリンクからアクセスできます: http://econ.korea.ac.kr/~cjkim/MARKOV/data/ew_excs.prn
対象となるモデルは次の通りです:
自己回帰成分がないため、このモデルは MarkovRegression クラスを使用して適合できます。平均効果がないため、trend='n' を指定します。分散が切り替わるレジームは3つであると仮定されているため、k_regimes=3 と switching_variance=True を指定します(デフォルトでは、レジーム間で分散は同じであると仮定されます)。
[6]:
# Get the dataset
ew_excs = requests.get("http://econ.korea.ac.kr/~cjkim/MARKOV/data/ew_excs.prn").content
raw = pd.read_table(BytesIO(ew_excs), header=None, skipfooter=1, engine="python")
raw.index = pd.date_range("1926-01-01", "1995-12-01", freq="MS")
dta_kns = raw.loc[:"1986"] - raw.loc[:"1986"].mean()
# Plot the dataset
dta_kns[0].plot(title="Excess returns", figsize=(12, 3))
# Fit the model
mod_kns = sm.tsa.MarkovRegression(
dta_kns, k_regimes=3, trend="n", switching_variance=True
)
res_kns = mod_kns.fit()
[7]:
res_kns.summary()
[7]:
| Dep. Variable: | 0 | No. Observations: | 732 |
|---|---|---|---|
| Model: | MarkovRegression | Log Likelihood | 1001.895 |
| Date: | Thu, 28 Nov 2024 | AIC | -1985.790 |
| Time: | 23:10:57 | BIC | -1944.428 |
| Sample: | 01-01-1926 | HQIC | -1969.834 |
| - 12-01-1986 | |||
| Covariance Type: | approx |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| sigma2 | 0.0012 | 0.000 | 7.136 | 0.000 | 0.001 | 0.002 |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| sigma2 | 0.0040 | 0.000 | 8.489 | 0.000 | 0.003 | 0.005 |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| sigma2 | 0.0311 | 0.006 | 5.461 | 0.000 | 0.020 | 0.042 |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| p[0->0] | 0.9747 | 0.000 | 7857.416 | 0.000 | 0.974 | 0.975 |
| p[1->0] | 0.0195 | 0.010 | 1.949 | 0.051 | -0.000 | 0.039 |
| p[2->0] | 2.354e-08 | nan | nan | nan | nan | nan |
| p[0->1] | 0.0253 | 3.97e-05 | 637.835 | 0.000 | 0.025 | 0.025 |
| p[1->1] | 0.9688 | 0.013 | 75.528 | 0.000 | 0.944 | 0.994 |
| p[2->1] | 0.0493 | 0.032 | 1.551 | 0.121 | -0.013 | 0.112 |
Warnings:
[1] Covariance matrix calculated using numerical (complex-step) differentiation.
以下に、各レジームにいる確率をプロットします。高分散レジームが確率的に高いのは、いくつかの期間に限られます。
[8]:
fig, axes = plt.subplots(3, figsize=(10, 7))
ax = axes[0]
ax.plot(res_kns.smoothed_marginal_probabilities[0])
ax.set(title="Smoothed probability of a low-variance regime for stock returns")
ax = axes[1]
ax.plot(res_kns.smoothed_marginal_probabilities[1])
ax.set(title="Smoothed probability of a medium-variance regime for stock returns")
ax = axes[2]
ax.plot(res_kns.smoothed_marginal_probabilities[2])
ax.set(title="Smoothed probability of a high-variance regime for stock returns")
fig.tight_layout()
フィラルド(1994年)の時間変動遷移確率¶
このモデルは、時間変動する遷移確率による推定を示しています。データセットは以下のリンクから取得できます: http://econ.korea.ac.kr/~cjkim/MARKOV/data/filardo.prn
上記のモデルでは、遷移確率が時間を通じて一定であると仮定していました。ここでは、遷移確率が経済の状態に応じて変化することを許容します。それ以外は、ハミルトン(1989年)のマルコフ自己回帰モデルと同様です。
各期間において、レジームは次の時間変動遷移確率の行列に従って遷移します:
ここで、\(p_{ij,t}\) は、期間\(t\)においてレジーム\(i\)からレジーム\(j\)への遷移確率であり、次のように定義されます:
遷移確率を最尤推定の一部として推定する代わりに、回帰係数\(\beta_{ij}\)を推定します。これらの回帰係数は、遷移確率と、予め決定されたまたは外生的な回帰変数\(x_{t-1}\)との関係を示します。
[9]:
# Get the dataset
filardo = requests.get("http://econ.korea.ac.kr/~cjkim/MARKOV/data/filardo.prn").content
dta_filardo = pd.read_table(
BytesIO(filardo), sep=" +", header=None, skipfooter=1, engine="python"
)
dta_filardo.columns = ["month", "ip", "leading"]
dta_filardo.index = pd.date_range("1948-01-01", "1991-04-01", freq="MS")
dta_filardo["dlip"] = np.log(dta_filardo["ip"]).diff() * 100
# Deflated pre-1960 observations by ratio of std. devs.
# See hmt_tvp.opt or Filardo (1994) p. 302
std_ratio = (
dta_filardo["dlip"]["1960-01-01":].std() / dta_filardo["dlip"][:"1959-12-01"].std()
)
dta_filardo["dlip"][:"1959-12-01"] = dta_filardo["dlip"][:"1959-12-01"] * std_ratio
dta_filardo["dlleading"] = np.log(dta_filardo["leading"]).diff() * 100
dta_filardo["dmdlleading"] = dta_filardo["dlleading"] - dta_filardo["dlleading"].mean()
# Plot the data
dta_filardo["dlip"].plot(
title="Standardized growth rate of industrial production", figsize=(13, 3)
)
plt.figure()
dta_filardo["dmdlleading"].plot(title="Leading indicator", figsize=(13, 3))
C:\Users\user\AppData\Local\Temp\ipykernel_13456\2507337649.py:15: FutureWarning: ChainedAssignmentError: behaviour will change in pandas 3.0!
You are setting values through chained assignment. Currently this works in certain cases, but when using Copy-on-Write (which will become the default behaviour in pandas 3.0) this will never work to update the original DataFrame or Series, because the intermediate object on which we are setting values will behave as a copy.
A typical example is when you are setting values in a column of a DataFrame, like:
df["col"][row_indexer] = value
Use `df.loc[row_indexer, "col"] = values` instead, to perform the assignment in a single step and ensure this keeps updating the original `df`.
See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
dta_filardo["dlip"][:"1959-12-01"] = dta_filardo["dlip"][:"1959-12-01"] * std_ratio
[9]:
<Axes: title={'center': 'Leading indicator'}>
時間変動遷移確率は exog_tvtp パラメータによって指定されます。
ここでは、モデル適合の別の特徴を示します - MLEの初期パラメータを求めるためのランダムサーチの使用です。マルコフスイッチングモデルは、尤度関数の局所的な最大値が多く特徴であることがよくあるため、最適化の初期ステップを実行することが最良のパラメータを見つけるのに役立ちます。
以下では、初期パラメータベクトルから20個のランダムな摂動を調べ、最良のものを実際の初期パラメータとして使用することを指定します。検索がランダムであるため、結果の再現性を確保するために、事前に乱数生成器をシードします。
[10]:
mod_filardo = sm.tsa.MarkovAutoregression(
dta_filardo.iloc[2:]["dlip"],
k_regimes=2,
order=4,
switching_ar=False,
exog_tvtp=sm.add_constant(dta_filardo.iloc[1:-1]["dmdlleading"]),
)
np.random.seed(12345)
res_filardo = mod_filardo.fit(search_reps=20)
[11]:
res_filardo.summary()
[11]:
| Dep. Variable: | dlip | No. Observations: | 514 |
|---|---|---|---|
| Model: | MarkovAutoregression | Log Likelihood | -586.572 |
| Date: | Thu, 28 Nov 2024 | AIC | 1195.144 |
| Time: | 23:11:23 | BIC | 1241.808 |
| Sample: | 03-01-1948 | HQIC | 1213.433 |
| - 04-01-1991 | |||
| Covariance Type: | approx |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| const | -0.8659 | 0.153 | -5.658 | 0.000 | -1.166 | -0.566 |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| const | 0.5173 | 0.077 | 6.706 | 0.000 | 0.366 | 0.668 |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| sigma2 | 0.4844 | 0.037 | 13.172 | 0.000 | 0.412 | 0.556 |
| ar.L1 | 0.1895 | 0.050 | 3.761 | 0.000 | 0.091 | 0.288 |
| ar.L2 | 0.0793 | 0.051 | 1.552 | 0.121 | -0.021 | 0.180 |
| ar.L3 | 0.1109 | 0.052 | 2.136 | 0.033 | 0.009 | 0.213 |
| ar.L4 | 0.1223 | 0.051 | 2.418 | 0.016 | 0.023 | 0.221 |
| coef | std err | z | P>|z| | [0.025 | 0.975] | |
|---|---|---|---|---|---|---|
| p[0->0].tvtp0 | 1.6494 | 0.446 | 3.702 | 0.000 | 0.776 | 2.523 |
| p[1->0].tvtp0 | -4.3595 | 0.747 | -5.833 | 0.000 | -5.824 | -2.895 |
| p[0->0].tvtp1 | -0.9945 | 0.566 | -1.758 | 0.079 | -2.103 | 0.114 |
| p[1->0].tvtp1 | -1.7702 | 0.508 | -3.484 | 0.000 | -2.766 | -0.775 |
Warnings:
[1] Covariance matrix calculated using numerical (complex-step) differentiation.
以下に、経済が低生産状態で運営されている確率の平滑化したプロットを示し、比較のために再度NBERの景気後退期間を含めています。
[12]:
fig, ax = plt.subplots(figsize=(12, 3))
ax.plot(res_filardo.smoothed_marginal_probabilities[0])
ax.fill_between(usrec.index, 0, 1, where=usrec["USREC"].values, color="gray", alpha=0.2)
ax.set_xlim(dta_filardo.index[6], dta_filardo.index[-1])
ax.set(title="Smoothed probability of a low-production state")
[12]:
[Text(0.5, 1.0, 'Smoothed probability of a low-production state')]
時間変動する遷移確率を使用することで、低生産状態の期待される持続期間が時間とともにどのように変化するかを確認できます。
[13]:
res_filardo.expected_durations[0].plot(
title="Expected duration of a low-production state", figsize=(12, 3)
)
[13]:
<Axes: title={'center': 'Expected duration of a low-production state'}>
景気後退時には、低生産状態が続く期間の予想は景気拡大時よりもはるかに長いです。
[ ]:
[ ]:
[ ]:
[ ]: