最近接触到格兰杰因果关系(Granger Causality)这种研究时序变量之间的因果关系的统计工具,顺手找到了一段可以用的代码,即标题,特记录之。
稍微调研一下Granger Causality就知道,GC不是真正的因果关系,但是不妨碍各领域的研究人员使用它来探究变量之间的影响关系。
GC的定义大致是这样:假设有变量 X t X_t Xt和 Y t Y_t Yt,都是时序变量,若利用 X t X_t Xt的历史数据(当然也利用 Y t Y_t Yt的历史数据)去预测 Y t Y_t Yt的未来,比单独使用 Y t Y_t Yt的历史数据要效果好,就可以说 X t X_t Xt是 Y t Y_t Yt的Granger原因, X t X_t Xt granger causes Y t Y_t Yt。
因此,假定我们已经收集了一些变量的序列数据,要想探索他们之间的Granger Causality,通常的做法是,构建一个VAR(Vector Auto-Regressor)模型,利用最小二乘法求出系数矩阵(一般不止一个),若所有矩阵的 ( i , j ) (i,j) (i,j)位置都是0,则说明变量 i i i和 j j j没有Granger因果关系,反之则有。
下图是wiki对Multivariate Granger Causality的描述,以及VAR的数学描述,比较简单不再赘述。
直接看代码吧。需要用到Python的一个库,statsmodels,可能需要提前安装。
from statsmodels.tsa.api import VAR
import statsmodels.stats.multitest as multitest
import numpy as np
class LinVAR:
def __init__(self, X: np.ndarray, K=1):
"""
Linear VAR model.
@param X: numpy array with data of shape T x p.
@param K: order of the VAR model (maximum lag).
"""
# X.shape: T x p
super(LinVAR, self).__init__()
self.model = VAR(X)
self.p = X.shape[1]
self.K = K
# Fit the model
self.model_results = self.model.fit(maxlags=self.K)
def infer_causal_structure(self, kind="f", adjust=True, signed=True):
"""
Infer GC based on the fitted VAR model.
@param kind: type of the statistical test for GC (as implemented within statsmodels). Default: F-test. Option: 'wald'
@param adjust: whether to adjust p-values? If True, p-values are adjusted using the Benjamini-Hochberg procedure
for controlling the FDR.
@param signed: whether to return coeffcient signs?
@return: p x p array with p-values, p x p array with hypothesis test results, and, if signed == True,
p x p array with coefficient signs.
"""
pvals = np.zeros((self.p, self.p))
reject = None
for i in range(self.p):
for j in range(self.p):
pvals[i, j] = self.model_results.test_causality(caused=i, causing=j, kind=kind).pvalue
reject = pvals <= 0.05
if adjust:
reject, pvals, alpha_Sidak, alpha_Bonf = multitest.multipletests(pvals.ravel(), method="fdr_bh")
pvals = np.reshape(pvals, (self.p, self.p))
reject = np.reshape(reject, (self.p, self.p))
if signed:
return pvals, reject, np.sign(self.model_results.params[1:, :].T * reject)
else:
return pvals, reject
if __name__ == '__main__':
X = np.zeros((100,3), dtype=np.float32)
X[:,0] = np.sin(np.arange(100))
X[:,1] = np.sin(np.arange(100)-10)
X[:,2] = np.random.rand(100)
var = LinVAR(X)
print(var.infer_causal_structure())
输出:
array([[0. , 0. , 0.80416488],
[0. , 0. , 0.95490601],
[0.93203971, 0.34845459, 0.30533194]]),
array([[ True, True, False],
[ True, True, False],
[False, False, False]]),
array([[ 1., 1., 0.],
[-1., -1., 0.],
[ 0., 0., 0.]]))
很显然,对于这个小例子,变量1和变量2是有关系的,2是1的滞后,而变量3则是随机噪声。