condaの基本的な使い方

PyCon JP 2019 に参加して、
Anaconda環境運用TIPS 〜Anacondaの環境構築について知る・質問に答えられるようになる〜
という講演を聞いてから、自分の環境がcondaとpipの混在状態で結構リスキーだったことが懸念でした。
参考: 資料のページ

(実際何度も環境壊れていますし。)
会社で使っている端末では環境を極力condaのみで作り直していたのですが、実は自宅の端末では相変わらず混在していました。
そんな時に先日、SSDが壊れてMBPを修理に出す機会があり、完全に初期化されたので今回はできる限りpipを使わないように作り直しました。

まず、Anaconda入れるところまでは昔の記事の通りです。
参考: Macにpyenvとanacondaをインストールする

これまではこの後pip install [ライブラリ名]で、どんどんライブラリを入れていましたが、これはとても危険な行為です。
condaとpipの仕組みには互換性がないからです。

condaのドキュメントにも Using pip in an environment というセクションで注意事項が書かれています。
要約すると、pipを使うなら環境を分けることと、condaでできるだけ多くのライブラリをインストールした後に残りをpipで入れるべきということです。

免責事項
さて、ここからcondaの説明など書いていきますが、僕自身がパッケージの開発者でもなく、環境構築等を専門にするエンジニアでもないただのユーザーなので、
とりあえず自分はこういう理解で使っています、というレベルの内容になります。
厳密には各自公式のドキュメントを確認の上、自己責任でご利用お願いします。
(どちらかというと、他の記事の pip install 〜と書いてるところにこそ、この免責事項が必要ですね。)

さて、 pip と conda ですが、どちらもパッケージマネージャー(パッケージを管理するツール)です。
そして管理の方法が違い、互換性がないので混ぜると二重管理になって競合し、環境の破壊等が発生しえます。

インターネット上のリポジトリで管理されているパッケージを手元の端末に追加したりアップデートしたりできるのですが、
その大元になるリポジトリが違います。
pip は PyPI 、で、 condaはrepo.anaconda.comです。

condaの最大のデメリットが、PyPIに比べてrepo.anaconda.comで配布されているパッケージの少なさだと思います。
この点を補うために、condaでは別の場所(チャンネル)からもパッケージを追加できます。
特に conda-forge は比較的充実しているのでオススメです。
ドキュメントの中でも紹介されているので安心して使えるかと思います。
参考: Conda channels

余談ですが、Python初心者の頃、-c conda-forgeの意味がわからなかったのもcondaを敬遠した理由でした。
要はconda公式リポジトリにそのパッケージはないけど、ユーザーグループが管理している conda-forge にはある時にこれを使います。

さて、チートシートに主なコマンドはまとまっているので、詳しいことはそちらを見てもらうとして、自分がいつもやっている手順を紹介します。

最初にインストールしたいパッケージの名前を確認しておきます。


# インストール済みのパッケージの中に、該当のパッケージがないことを確認する。(必要に応じてgrep)
conda list
# 公式リポジトリ内に存在するかどうか確認
conda search [パッケージ名]
# 公式リポジトリにあったらそこからインストール
conda install [パッケージ名]
# 公式リポジトリになかったら conda-forge 内を検索
conda search -c conda-forge [パッケージ名]
# conda-forgeに存在したらそこからインストール
conda install -c conda-forge [パッケージ名]
# バージョンを指定したいときは==の後ろにバージョン番号を指定
conda install [パッケージ名]==[バージョン番号]

また、インストール済みのパッケージのアップデートとアンインストールは次です。


# アップデート
conda update [パッケージ名]
# アンインストール
conda uninstall [パッケージ名]

前置きが長くなって、記事が冗長になってしまったので一旦ここまで。
次はconda-forgeでも見つからないライブラリをPyPIからcondaで入れることのできる、
conda buildや conda skeletonについて紹介したいです。

Python(statsmodels)でインパルス応答関数の計算

思いのほか理論編が長くなってしまいすでにインパルス応答で4記事目ですが、ようやく実装の話です。
グレンジャー因果性と同様に、statsmodelsで学習したVARモデルはインパルス応答関数のメソッドも持っています。

メソッド名は irf です。
statsmodels.tsa.vector_ar.var_model.VARResults.irf

早速やってみましょう。
モデルは、直交化インパルス応答関数の計算例の記事と同じです。
サンプルデータの生成と、パラメーターの推定まで完了しているとします。
今回は次の状態になりました。


# 学習したモデルのパラメーター
result = model.fit(maxlags=1)
print(result.summary())
"""
  Summary of Regression Results   
==================================
Model:                         VAR
Method:                        OLS
Date:           Sun, 23, Feb, 2020
Time:                     21:45:36
--------------------------------------------------------------------
No. of Equations:         2.00000    BIC:                   0.924001
Nobs:                     99.0000    HQIC:                  0.830357
Log likelihood:          -312.903    FPE:                    2.15278
AIC:                     0.766721    Det(Omega_mle):         2.02800
--------------------------------------------------------------------
Results for equation y1
========================================================================
           coefficient       std. error           t-stat            prob
------------------------------------------------------------------------
const         0.177695         0.918241            0.194           0.847
L1.y1         0.553623         0.112916            4.903           0.000
L1.y2         0.169688         0.169945            0.998           0.318
========================================================================

Results for equation y2
========================================================================
           coefficient       std. error           t-stat            prob
------------------------------------------------------------------------
const         1.306800         0.408504            3.199           0.001
L1.y1         0.085109         0.050234            1.694           0.090
L1.y2         0.760450         0.075605           10.058           0.000
========================================================================

Correlation matrix of residuals
            y1        y2
y1    1.000000  0.629788
y2    0.629788  1.000000
"""

サマリーの最後に、撹乱項の相関行列がありますが、分散共分散行列がみたいのでそちらも表示しておきましょう。
sigma_uに入ってます。


# 分散共分散行列
print(result.sigma_u)
"""
[[4.12100608 1.1546159 ]
 [1.1546159  0.81561201]]
"""

さて、まずは非直行化インパルス応答関数を計算して可視化してみましょう。
ちなみに、直行化/非直行化を指定するのは可視化(plot)の引数であり、計算時点で両方とも算出されています。


import matplotlib.pyplot as plt

# 10期先までの非直交化インパルス応答関数のプロット
period = 10
irf = result.irf(period)
irf.plot()
plt.show()

結果がこちらです。

想定通りの結果になっていますね。

続いて、(直行化)インパルス応答関数です。
用語としては、単にインパルス応答関数と言ったら、直行化の方をさすのですが、ライブラリでは明示的に、
orth=Trueを指定しなければなりません。(デフォルトはFalse)

計算は完了しているので、可視化のみです。


irf.plot(orth=True)
plt.show()

結果がこちら。

0期先の値が特徴的で、 y2からy1へは影響していないのに、y1からy2へは影響が発生していますね。
また、もう一点注意しないといけない点があります。
非直行化の方は、y1からy1 および y2からy2への0期先の影響の値が1であることから分かる通り、
1単位のショックを与えた時のインパルス応答関数を計算されていました。
しかし、直行化の方は、そうではなく、1標準偏差のショックを与えた時のインパルス応答関数の値になっています。
(この辺の挙動はドキュメントのどこに書いてあるのだろう?)

結果をプロットするのではなく、値を利用したい場合もあると思います。
その場合、次の2変数にプロットされた値がそれぞれ入っていますのでこれが使えます。


irf.irfs
irf.orth_irfs

直交化インパルス応答関数の計算例

今回は直交化インパルス応答関数を実際に計算してみます。

例として扱うのは、非直交化インパルス応答関数の記事と同じ次の例です。
$$
\left\{\begin{matrix}\
y_{1t}&=& -1+ 0.6 y_{1,t-1}+ 0.3 y_{2,t-1}+\varepsilon_{1t}\\\
y_{2t}&=& 1 + 0.1 y_{1,t-1}+ 0.8 y_{2,t-1}+\varepsilon_{2t}\
\end{matrix}\
\right.\
,\left(\begin{matrix}\varepsilon_{1t}\\\varepsilon_{2t}\end{matrix}\right)\sim W.N.(\boldsymbol{\Sigma})
$$
$$
\boldsymbol{\Sigma} = \left(\begin{matrix}4&1.2\\1.2&1\end{matrix}\right)
$$

さて、まずは$\boldsymbol{\Sigma}$を分解する必要があります。

$$
\begin{align}
\mathbf{A}=
\left(\begin{matrix}1&0\\0.3&1\end{matrix}\right),\\
\mathbf{D}= Var(\mathbf{u}_t)
\left(\begin{matrix}4&0\\0&0.64\end{matrix}\right)
\end{align}
$$
とすると、
$$
\boldsymbol{\Sigma} = \mathbf{ADA^{\top}}
$$
が成立します。
このため、$\boldsymbol{\varepsilon_t}$は次のように、
直交化撹乱項に分解できます。
$$
\left\{\begin{align}\
\varepsilon_{1t}&=u_{1t}\\\
\varepsilon_{2t}&=0.3u_{1t}+u_{2t}\
\end{align}\right.
$$
この撹乱項を用いることで、元のモデルがこのように変わります。

$$
\left\{\begin{matrix}\
y_{1t}&=& -1&+ &0.6 y_{1,t-1}&+ &0.3 y_{2,t-1}&+&u_{1t}\\\
y_{2t}&=& 1 &+ &0.1 y_{1,t-1}&+ &0.8 y_{2,t-1}&+&0.3u_{1t}+u_{2t}\
\end{matrix}\
\right.\
$$
$$
\mathbf{u}_t \sim W.N.(\mathbf{D})
$$
$$
\mathbf{D} = \left(\begin{matrix}4&0\\0&0.64\end{matrix}\right)
$$

この形に変形できれば、非直交化インパルス応答関数と同じ様に計算が出来ます。
例えば、$y_1$に1単位のショックを与えたときの同時点におけるインパルス応答は次になります。
$$
\begin{align}
IRF_{11}(0) = \frac{\partial y_{1t}}{\partial u_{1t}} = 1\\
IRF_{21}(0) = \frac{\partial y_{2t}}{\partial u_{1t}} = 0.3
\end{align}
$$
$IRF_{21}(0)\neq0$となるのが大きな特徴です。

ただし、逆に$y_2$に1単位のショックを与えても、$y_1$には影響がありません。
要するに次のようになります。
$$
\begin{align}
IRF_{12}(0) = \frac{\partial y_{1t}}{\partial u_{2t}} = 0\\
IRF_{22}(0) = \frac{\partial y_{2t}}{\partial u_{2t}} = 1
\end{align}
$$
これが三角分解の仮定により発生している現象であり、変数の並べ方が結果に影響してしまっている点です。
このためインパルス応答関数を使う時は、変数の並べ方に気を使う必要があります。

1期後先以降の値は、非直交化インパルス応答関数のときと同じ漸化式で逐次的に求めることが可能です。

直交化インパルス応答関数

前回の記事: 非直交化インパルス応答関数
の続きです。今回も引用元は沖本本です。

最後に書いたとおり、非直交化インパルス応答関数には、撹乱項の間の相関を無視しているという問題がありました。
これを解決するには、撹乱項を互いに無相関な撹乱項に分解して、それぞれにショックを与えたときの各変数の変化を調べることです。
しかし、一般に撹乱項を互いに無相関な撹乱項に分解することは不可能で、何かしらの仮定が必要になります。
そこで、撹乱項の分散共分散行列$\boldsymbol{\Sigma}$の三角分解を用いて、撹乱項を互いに無相関な撹乱項に分解できると仮定します。

定義 (直交化インパルス応答関数):
撹乱項の分散共分散行列の三角分解を用いて、撹乱項を互いに無相関な撹乱項に分解したとき、
互いに無相関な撹乱項は直交化撹乱項といわれる。また、$y_j$の直交化撹乱項に1単位または1標準偏差のショックを与えた時の、
$y_i$の直交化インパルス応答を時間の関数としてみたものは、$y_j$に対する$y_i$の直交化インパルス応答関数といわれる。

一般的に、インパルス応答(関数)というというと直交化インパルス応答(関数)を指すそうです。
(後日の記事でstatsmodelsによる実装紹介しますが、このモジュールでは非直交化のほうをデフォルトにしてるように見えます。
もしそうだとしたら気をつけて使わないといけないので、記事にする前にもう一回検証します。)

さて、もう少し具体的に定義します。
まず撹乱項 $\boldsymbol{\varepsilon}_t$ の分散共分散行列$\boldsymbol{\Sigma}$は正定値行列であるので、
$\mathbf{A}$を対角成分が1に等しい下三角行列、$\mathbf{D}$を対角行列として、
$$
\boldsymbol{\Sigma} = \mathbf{ADA^{\top}}
$$
と三角分解することが出来ます。
(と、本には書いてありますが、分散共分散行列が正定値なだけでなく、対称行列であることも考慮する必要あると思います。)

このとき、直交化撹乱項 $\mathbf{u}_t$は、
$$
\mathbf{u}_t = \mathbf{A}^{-1}\boldsymbol{\varepsilon}_t
$$
で定義され、各変数固有の変動を表すとみなされます。
なお、
$$
\begin{align}
Var(\mathbf{u}_t) &= Var(\mathbf{A}^{-1}\boldsymbol{\varepsilon}_t) = \mathbf{A}^{-1} Var(\boldsymbol{\varepsilon}_t)(\mathbf{A}^{-1})^{\top} \\
& =\mathbf{A}^{-1}\boldsymbol{\Sigma}(\mathbf{A}^{\top})^{-1}=\mathbf{D}
\end{align}
$$
であることから、$\mathbf{u}_t$が互いに無相関であることが分かります。
また、それぞれの分散も確認できます。

$y_j$のショックに対する$y_i$の(直交化)インパルス応答関数は、$u_{jt}$に1単位のショックを与えたときの、
$y_i$の反応を時間の関数と見たものであり、
$$
IRF_{ij}(k) = \frac{\partial y_{i,t+k}}{\partial u_{jt}}, \ \ \ k=0, 1, 2, \dots.
$$
と表されます。
1単位ではなくて、1標準偏差のショックを与えたときの変数の反応を見たい時は、
$IRF_{ij}(k)$に$\sqrt{Var(u_{jt})}$を掛けることで求まります。(非直交化のときと同じですね)

1標準偏差のショックを与えたときのインパルス応答関数を求める方法として、
三角分解の変わりにコレスキー分解を用いて撹乱項を分解して、
その分解した撹乱項に1単位ショックを与える方法も紹介されていますが、
撹乱項の絶対値が標準偏差倍違うだけの話なので省略します。

非直交化インパルス応答関数

グレンジャー因果性で複数の過程の間の関係を調べる方法を紹介してきましたが、グレンジャー因果性には
関係の強さを測れないという問題がありました。
そこで、それを補うツールとしてインパルス応答関数(IRF、 impulse response function)というものがあります。

例によって、沖本先生の本を参照しながら紹介させていただきます。

インパルス応答関数はある変数にショックを与える(要するに少し値を変動させる)と、
それが他の変数に与える影響を分析することができるものです。
VARモデルにおいては、ショックの識別の仕方によって、複数のインパルス応答関数が存在しますが、
まずは非直交化インパルス応答関数について説明します。

一般的な$n$変量のVAR(p)モデルを考えます。
$$
\mathbf{y}_t = \mathbf{c}+\boldsymbol{\Phi}_1\mathbf{y}_{t-1}+\cdots+\boldsymbol{\Phi}_p\mathbf{y}_{t-p}+\boldsymbol{\varepsilon}_t,
\ \ \ \boldsymbol{\varepsilon}_t\sim W.N.(\boldsymbol{\Sigma})
$$
本では、$\boldsymbol{\Sigma}$は対角行列ではないと仮定されていますが、対角行列の場合も同じだと思います。
(対角行列だと、その直後に登場する直行化インパルス応答関数と全く同じになってしまうで説明の都合上仮定されているのかと。)

この時、非直交化インパルス応答関数は次のように定義されます。

定義 (非直交化インパルス応答関数):
$y_{jt}$の撹乱項$\varepsilon$だけに、1単位(または、1標準偏差)のショックを与えた時の、
$y_{i,t+k}$の値の変化は、$y_j$のショックに対する$y_i$の$k$期後の非直行化インパルス応答と呼ばれる。
また、それを$k$の関数としてみたものは、$y_j$のショックに対する$y_i$の非直行化インパルス応答関数と言われる。

変化量なので、偏微分を用いて次のように表記できます。(1単位のショックを与えた場合。)
$$
IRF_{ij}(k) = \frac{\partial y_{i,t+k}}{\partial \varepsilon_{jt}}, \ \ \ k=0, 1, 2, \dots.
$$
1標準偏差のショックを与えた場合は、$IRF_{ij}(k)$に$\sqrt{Var(\varepsilon_{jt})}$を掛けることで求まります。

さて、具体的な計算方法ですが、$k=0$から逐次的に計算していくことで簡単に求まります。

次のモデルを例に、計算例が乗っているのでやってみましょう。
$$
\left\{\begin{matrix}\
y_{1t}&=& -1+ 0.6 y_{1,t-1}+ 0.3 y_{2,t-1}+\varepsilon_{1t}\\\
y_{2t}&=& 1 + 0.1 y_{1,t-1}+ 0.8 y_{2,t-1}+\varepsilon_{2t}\
\end{matrix}\
\right.\
,\left(\begin{matrix}\varepsilon_{1t}\\\varepsilon_{2t}\end{matrix}\right)\sim W.N.(\Sigma)
$$
$$
\Sigma = \left(\begin{matrix}4&1.2\\1.2&1\end{matrix}\right)
$$

$y_1$に1単位のショックを与えた時の非直交化インパルス応答関数を具体的に計算します。
k=0の場合は簡単で、そのまま微分するだけです。
$$
\begin{align}
IRF_{11}(0) = \frac{\partial y_{1t}}{\partial \varepsilon_{1t}} = 1\\
IRF_{21}(0) = \frac{\partial y_{2t}}{\partial \varepsilon_{1t}} = 0
\end{align}
$$
この後は、多変数関数の合成関数の連鎖律を使って計算していきます。$k=1$の場合、$y_1$,$y_2$それぞれについて計算すると次のようになります。
$$
\begin{align}
IRF_{11}(1) &= \frac{\partial y_{1,t+1}}{\partial \varepsilon_{1t}}
= 0.6\times \frac{\partial y_{1t}}{\partial \varepsilon_{1t}} + 0.3 \times \frac{\partial y_{2t}}{\partial \varepsilon_{1t}}\\
&= 0.6 IRF_{11}(0) + 0.3 IRF_{21}(0) =0.6
\end{align}
$$
$$
\begin{align}
IRF_{21}(1) &= \frac{\partial y_{2,t+1}}{\partial \varepsilon_{1t}}
= 0.1\times \frac{\partial y_{1t}}{\partial \varepsilon_{1t}} + 0.8 \times \frac{\partial y_{2t}}{\partial \varepsilon_{1t}}\\
&= 0.1 IRF_{11}(0) + 0.8 IRF_{21}(0) =0.1
\end{align}
$$

この後の $k=1,2,3,…$についても次の漸化式で逐次的に計算できます。
$$
\left\{\
\begin{align}\
IRF_{11}(k) &= 0.6IRF_{11}(k-1) + 0.3IRF_{21}(k-1)\\\
IRF_{21}(k) &= 0.1IRF_{11}(k-1) + 0.8IRF_{21}(k-1)\
\end{align}\
\right.\
$$
この式の中に元のモデルの式の定数項ができませんが、それはインパルス応答関数が変化量に着目しているため、
無関係(微分で消えてしまった)からです。

さて、これで非直交化インパルス応答関数が計算できましたが、
これには一つ問題点があります。
というのも、最初のモデルの式の$\boldsymbol{\Sigma}$を見るとわかるとおり、$\varepsilon_{1t}$と$\varepsilon_{2t}$には
相関があることが仮定されています。
分散共分散行列から相関係数を計算すると
$$
Corr(\varepsilon_{1t}, \varepsilon_{2t}) = \frac{1.2}{\sqrt(4\times 1)} = 0.6
$$
となり、そこそこ強い相関です。
そのため、$\varepsilon_{1t}$にだけショックを与えて、$\varepsilon_{2t}$はそのままという、
非直交化インパルス応答関数には少し不自然な点が残ります。
すでに長くなってきたので、この点を解消する方法につては次の記事で紹介させていただきます。

statsmodels でグレンジャー因果性の確認

前回の記事でグレンジャー因果性という用語を紹介したので、今回はPythonで実際に計算してみましょう。
参考: グレンジャー因果性

データは、VARモデルのパラメーター推定を行った時のように、乱数で生成します。
参考: Python(statsmodels)でVARモデルのパラメーター推定

コードの重複する部分は省略して、変数resultに、推定したモデルが格納されているとします。
(今回の記事用に実行し直したので、パラメーターは前回と違います。)

この推定結果のオブジェクトが、 test_causality というメソッドを持っており、
それを使って、グレンジャー因果性の検証ができます。
ドキュメント: statsmodels.tsa.vector_ar.var_model.VARResults.test_causality


statsmodelsにはこの他にも、二つの過程のグレンジャー因果性を確認するツールとして、
grangercausalitytests というのが用意されているようなのですが、今回はこちらは使いません。

それでは早速やってみましょう。


# 推定結果のVARモデルの確認。(以前の記事のコードで生成されている前提)
print(result.summary())
"""
  Summary of Regression Results   
==================================
Model:                         VAR
Method:                        OLS
Date:           Sun, 09, Feb, 2020
Time:                     16:37:19
--------------------------------------------------------------------
No. of Equations:         2.00000    BIC:                    1.41978
Nobs:                     99.0000    HQIC:                   1.32614
Log likelihood:          -337.444    FPE:                    3.53438
AIC:                      1.26250    Det(Omega_mle):         3.32953
--------------------------------------------------------------------
Results for equation y1
========================================================================
           coefficient       std. error           t-stat            prob
------------------------------------------------------------------------
const         1.263791         0.705621            1.791           0.073
L1.y1         0.604660         0.124431            4.859           0.000
L1.y2         0.267836         0.145104            1.846           0.065
========================================================================

Results for equation y2
========================================================================
           coefficient       std. error           t-stat            prob
------------------------------------------------------------------------
const        -3.289405         0.292191          -11.258           0.000
L1.y1         0.601292         0.051526           11.670           0.000
L1.y2         0.263057         0.060086            4.378           0.000
========================================================================

Correlation matrix of residuals
            y1        y2
y1    1.000000  0.596470
y2    0.596470  1.000000
"""

# y2 から y1 へのグレンジャー因果性の確認
test_result = result.test_causality(caused="y1", causing="y2")
print(test_result.summary())
"""
Granger causality F-test. H_0: y2 does not Granger-cause y1. Conclusion: fail to reject H_0 at 5% significance level.
==============================================
Test statistic Critical value p-value    df
----------------------------------------------
         3.407          3.890   0.066 (1, 192)
----------------------------------------------
"""

# y1 から y2 へのグレンジャー因果性の確認
test_result = result.test_causality(caused="y2", causing="y1")
print(test_result.summary())
"""
Granger causality F-test. H_0: y1 does not Granger-cause y2. Conclusion: reject H_0 at 5% significance level.
==============================================
Test statistic Critical value p-value    df
----------------------------------------------
         136.2          3.890   0.000 (1, 192)
----------------------------------------------
"""

当然データによって結果が変わる(乱数で生成しているので、モデルが同じでも実行するたびに変わります。)のですが、
y1からy2へのグレンジャー因果性がないという過程は棄却されているので、y1はy2に影響しているようです。
y2からy1の向きは、そうはならなかったですね。

さて、上のコード例のように、 test_causality というメソッドに、
caused と causing の引数で変数名を渡してあげるだけで、テスト結果を返してくれました。
例では変数名を指定しましたが、 0 や 1など、何番目の変数なのかを指定してもOKです。
これが2変数ではなくもっと多くの変数のVARモデルで、多変数から多変数へのグレンジャー因果性を調べる場合は、
causedやcausingをリストで指定すれば、同じようにテストできます。

さて、 test_causality は CausalityTestResults というオブジェクトを返してくれますが、
上の例のように、 summary() というメソッドを持っているので、人間の目で見る場合は、これをprintするのが良いでしょう。

p値などのプログラムで使いたい場合は、それぞれ変数に持っているのでそちらを使えます。


# 帰無仮説
print(test_result.h0)
# H_0: y1 does not Granger-cause y2

# 統計量
print(test_result.test_statistic)
# 136.18235785962648

# p値
print(test_result.pvalue)
# 3.9579973656016236e-24

# 結論
print(test_result.conclusion_str)
# Conclusion: reject H_0

グレンジャー因果性

ここ数回VARモデルの話を書いていますが、VARモデルの目的の一つは複数の時系列データ間の関連を調べることです。
普通は、各データにその背景があって、それらに関する理論からあの値がこうなったらどういう影響があってこっちの値がどうなる、と調べるのですが、
データだけから因果性の有無を判別することができると非常に便利です。
そのような考えを元に、 Grangerが提案したのがグレンジャー因果性です。

今回の記事も、沖本先生の経済ファイナンスデータの軽量時系列分析を元に各定義を紹介していきます。

定義(グレンジャー因果性)
現在と過去の$x$の値だけに基づいた将来の$x$の予測と、現在と過去の$x$と$y$の値に基づいた将来の$x$の予測を比較して、
後者のMSEの方が小さくなる場合、$y_t$から$x_t$へのグレンジャー因果性(Granger causality)が存在すると言われる。

これは、$x,y$の2つの過程に対する定義ですが、もっと多くの変数が存在する場合に拡張できます。
それが次の一般的なグレンジャー因果性です。

定義(一般的なグレンジャー因果性)
$\mathbf{x}_t$と$\mathbf{y}_t$をベクトル過程とする。また、$\mathbf{x}$と$\mathbf{y}$の現在と過去の値を含む、時点$t$において利用可能な情報の集合を
$\boldsymbol{\Omega}_t$とし、$\boldsymbol{\Omega}_t$から現在と過去の$\mathbf{y}$を取り除いたものを、$\tilde{\boldsymbol{\Omega}}_t$とする。
この時、$\tilde{\boldsymbol{\Omega}}_t$に基づいた将来の$\mathbf{x}$の予測と、$\boldsymbol{\Omega}_t$に基づいた将来の$\mathbf{x}$の予測を比較して、
後者のMSEのほうが小さくなる場合、$\mathbf{y}_t$から$\mathbf{x}_t$へのグレンジャー因果性が存在すると言われる。
ここで、MSEの大小は行列の意味での大小であることに注意されたい。

沖本本によると、グレンジャー因果性が頻繁に用いられるようになったたった一つの理由は、VARの枠組みでは、
グレンジャー因果性の有無が比較的簡単に明確な形で検証できるからだそうです。

2変量VAR(2)モデルを用いた説明が紹介されているのでみていきましょう。
モデルを具体的に書くと次のようになります。
$$
\left\{\begin{matrix}\
y_{1t}=c_1+\phi_{11}^{(1)}y_{1,t-1}+\phi_{12}^{(1)}y_{2,t-1}+\phi_{11}^{(2)}y_{1,t-2}+\phi_{12}^{(2)}y_{2,t-2}+\varepsilon_{1t},\\\
y_{2t}=c_2+\phi_{21}^{(1)}y_{1,t-1}+\phi_{22}^{(1)}y_{2,t-1}+\phi_{21}^{(2)}y_{1,t-2}+\phi_{22}^{(2)}y_{2,t-2}+\varepsilon_{2t}\
\end{matrix}\
\right.\
$$

ここで、$y_{2t}$から、$y_{1t}$へのグレンジャーレンジャー因果性が”存在しない”ということjは、
$\phi_{12}^{(1)}=\phi_{12}^{(2)}y_{2,t-2}=0$ということ同値です。
一般のVAR(p)において、$y_{2t}$から、$y_{1t}$へのグレンジャー因果性が存在しないとは、VARの$y_1$の式において、$y_2$に関係する係数が全部$0$ということになります。
その結果、VARの枠組みだと、F検定を用いてグレンジャー因果性を検定できます。
(複数の回帰係数をまとめて検定するので、t検定ではなくF検定だそうです。この辺り、自分も理解が浅いので別途勉強し直そうと思います。)

手順としては次のようになります。
$H_0: \phi_{12}^{(1)}=\phi_{12}^{(2)}y_{2,t-2}=0$ を検定すれば良いので、まずは、
$$
y_{1t}=c_1+\phi_{11}^{(1)}y_{1,t-1}+\phi_{12}^{(1)}y_{2,t-1}+\phi_{11}^{(2)}y_{1,t-2}+\phi_{12}^{(2)}y_{2,t-2}+\varepsilon_{1t}
$$
をOLSで推定して、残渣平方和を$SSR_1$とします。
次に制約を課したモデル、
$$
y_{1t}=c_1+\phi_{11}^{(1)}y_{1,t-1}+\phi_{11}^{(2)}y_{1,t-2}+\varepsilon_{1t}
$$
をOLSで推定して、その残差平方和を$SSR_0$とします。
この時、F統計量が次の式で定義されます。
$$
F\equiv \frac{(SSR_0-SSR_1)/2}{SSR_1/(T-5)}
$$
そうすると、$2F$は漸近的に$\chi^2(2)$に従うことが知られています。
なので、$2F$の値を$\chi^2(2)$の$95%$店と比較して、$2F$の方が大きければ、$y_2$から$y_1$へのグレンジャー因果性が存在しないという
帰無仮説を棄却して、$y_2$は$y_1$の将来を予測するのに有用であると結論づけられます。

これは1つの過程から別の1つの過程への影響を調べるものですが、もっと一般には次の手順になります。
(ここも沖本先生の本の丸写し)

n変量VAR(p)モデルにおけるグレンジャー因果性検定の手順
(1) VARモデルにおける$y_{kt}$のモデルをOLSで推定し、その残差平方和を$SSR_1$とする。
(2) VARモデルにおける$y_{kt}$のモデルに制約を課したモデルをOLSで推定し、その残差平方和を$SSR_0$とする。
(3) F統計量を
$$
F\equiv \frac{(SSR_0-SSR_1)/r}{SSR_1/(T-np-1)}
$$
で計算する。ここで、$r$はグレンジャー因果性検定に必要な制約の数である。
(4) $rF$を$\chi^2(r)$の$95%$点と比較し、$rF$のほうが大きければ、ある変数(群)から$y_{kt}$へのグレンジャー因果性は存在し、小さければグレンジャー因果性は存在しないと結論する。

あら?帰無仮説が棄却できなかった時に、存在しないって結論していいのかな?ここの記述はちょっと怪しいですね。

最後に、グレンジャー因果性の長所と短所がまとめられています。(沖本本で株式市場の例が紹介された後。)
長所は定義が明確であることt、データから容易に検定できることです。

短所は、まずはグレンジャー因果性が通常の因果性と異なっていることです。
グレンジャー因果性は因果性が存在する必要条件ではありますが、十分条件でありません。
なので、グレンジャー因果性があったからといって因果性があるとは結論づけられません。
また、グレンジャー因果性の方向と、通常の因果性の方向が同じになると限らず、
極端な例では逆向きだけ存在することもあるそうです。
そのため、何か別の理論等により因果関係の方向がはっきりしている時以外は、
定義通りに、予測に有用かどうかだけの観点で解釈することが良さそうです。

また、グレンジャー因果性は訂正的概念で、関係の強さは測れません。
(F統計量が大きいほど関係が強いみたいな考えは誤りということですね。)

Python(statsmodels)でVARモデルのパラメーター推定

今回は2変量のVARモデルにしたがうデータから、Pythonを使ってモデルの係数を推定する方法を紹介します。
答え合わせをしたいので、使うデータは特定のVARモデルから生成します。

モデルはいつもの沖本先生の本のP102ページの演習問題から拝借しました。

$$
\left\{\begin{matrix}\
y_{1t} &=& 2+ 0.5 y_{1,t-1}+ 0.4 y_{2,t-1}+\varepsilon_{1t},\\\
y_{2t} &=& -3 + 0.6 y_{1,t-1}+ 0.3 y_{2,t-1}+\varepsilon_{2t}\
\end{matrix}\
\right.\
\left(\begin{matrix}\varepsilon_{1t}\\\varepsilon_{2t}\end{matrix}\right)\sim W.N.(\mathbf{\Sigma})
$$
$$
\Sigma = \left(\begin{matrix}4&1.2\\1.2&1\end{matrix}\right)
$$

まずはデータを生成します。


import numpy as np
import matplotlib.pyplot as plt

mean = np.array([0, 0])
cov = np.array([[4, 1.2], [1.2, 1]])

# 少し長めの系列でデータを生成する。
data = np.zeros([110, 2])
epsilons = np.random.multivariate_normal(mean, cov, size=110)
print(np.cov(epsilons, rowvar=False))
"""
[[3.96508657 1.1917421 ]
 [1.1917421  1.01758275]]
"""

for i in range(1, 110):
    data[i, 0] = 2 + 0.5*data[i-1, 0] + 0.4*data[i-1, 1] + epsilons[i, 0]
    data[i, 1] = -3 + 0.6*data[i-1, 0] + 0.3*data[i-1, 1] + epsilons[i, 1]

# 初めの10項を切り捨てる
data = data[10:]
# data.shape == (100, 2)

fig = plt.figure(figsize=(12, 5), facecolor="w")
ax = fig.add_subplot(111)
ax.plot(data[:, 0], label="$y_1$")
ax.plot(data[:, 1], label="$y_2$")
ax.set_title("サンプルデータ")
plt.legend()
plt.show()

生成されたデータをプロットしたのがこちら。

さて、いよいよ statsmodelsでVARモデルのパラメーター推定をやりますが、
方法は非常に簡単で、時系列モデルのapiからVARモデルを呼び出してデータを渡し、学習するだけです。

ドキュメント:
Vector Autoregressions tsa.vector_ar


from statsmodels.tsa.api import VAR
model = VAR(data)
result = model.fit(maxlags=1)
print(result.summary())
"""
  Summary of Regression Results
==================================
Model:                         VAR
Method:                        OLS
Date:           Mon, 03, Feb, 2020
Time:                     01:10:12
--------------------------------------------------------------------
No. of Equations:         2.00000    BIC:                    1.23044
Nobs:                     99.0000    HQIC:                   1.13680
Log likelihood:          -328.071    FPE:                    2.92472
AIC:                      1.07316    Det(Omega_mle):         2.75521
--------------------------------------------------------------------
Results for equation y1
========================================================================
           coefficient       std. error           t-stat            prob
------------------------------------------------------------------------
const         1.879511         0.616917            3.047           0.002
L1.y1         0.464346         0.124135            3.741           0.000
L1.y2         0.462511         0.132200            3.499           0.000
========================================================================

Results for equation y2
========================================================================
           coefficient       std. error           t-stat            prob
------------------------------------------------------------------------
const        -2.683660         0.305923           -8.772           0.000
L1.y1         0.561468         0.061557            9.121           0.000
L1.y2         0.385671         0.065556            5.883           0.000
========================================================================

Correlation matrix of residuals
            y1        y2
y1    1.000000  0.583971
y2    0.583971  1.000000
"""

今回はモデルの形が事前にわかっているので、 maxlags=1 としましたが、 ここを少し大きめの値にしておけば自動的にlagの値を選んでくれます。
選び方は 引数”ic”で指定できます。 (未指定ならAIC)

ic : {‘aic’, ‘fpe’, ‘hqic’, ‘bic’, None}
Information criterion to use for VAR order selection.
aic : Akaike
fpe : Final prediction error
hqic : Hannan-Quinn
bic : Bayesian a.k.a. Schwarz

さて、結果の係数ですが、summaryを見ると大体次の値になっています。(四捨五入しました)
$$
\left\{\begin{matrix}\
y_{1t} &=& 1.89+ 0.46 y_{1,t-1}+ 0.46 y_{2,t-1}\\\
y_{2t} &=& -2.68 + 0.56 y_{1,t-1}+ 0.39 y_{2,t-1}\
\end{matrix}\
\right.\
$$
とても正確というわけではありませんが、概ね正しく推定できていそうです。