How to Test Causal Relationships with Granger Causality in Python
This article explains the concept of Granger causality, outlines the four possible test outcomes, and provides a step‑by‑step Python implementation using statsmodels to determine whether one time‑series statistically causes another.
Causal Relationship
Many variables influence each other, such as GDP and consumption. The key question is whether, when two variables exhibit a lead‑lag relationship over time, the influence is unidirectional or bidirectional: does the past behavior of one variable affect the current behavior of the other, or do both affect each other?
Granger Causality Test
The Granger causality test is a statistical method that addresses this question. For two variables X and Y, the test estimates restricted and unrestricted regression models and examines the joint significance of lagged terms.
Four possible outcomes exist:
Unidirectional influence from X to Y (first equation’s lag coefficients are jointly non‑zero, Y’s are zero).
Unidirectional influence from Y to X (second equation’s lag coefficients are jointly non‑zero, X’s are zero).
Bidirectional influence (both sets of lag coefficients are jointly non‑zero).
No influence (both sets of lag coefficients are jointly zero).
The test statistic is computed by comparing the sum of squared residuals (SSR) of the restricted and unrestricted models. If the statistic exceeds the critical value, the null hypothesis of no causality is rejected.
Python Implementation
Assume we have two time‑series X and Y, where Y is generated as the previous value of X plus random noise. The following code uses statsmodels to perform the Granger causality test.
<code>import numpy as np
import statsmodels.api as sm
# generate random data
np.random.seed(0)
nobs = 100
X = np.random.rand(nobs)
Y = np.zeros(nobs)
Y[1:] = X[:-1] + np.random.rand(nobs-1)
</code>Run the Granger causality function:
<code># Granger causality test
gc_res = sm.tsa.stattools.grangercausalitytests(np.column_stack([Y, X]), 2, verbose=False)
</code>The function signature is:
<code>sm.tsa.stattools.grangercausalitytests(X, maxlag, verbose=True, alpha=0.05)
</code>where X is a NumPy array or DataFrame with the dependent variable in the first column and the independent variable in the second, maxlag specifies the maximum lag order to test, verbose controls output printing, and alpha sets the significance level.
The result dictionary contains, for each lag, several test statistics such as ssr_ftest , ssr_chi2test , params_ftest , and lrtest . The most commonly used are ssr_ftest and params_ftest , which provide an F‑statistic and p‑value to assess whether the lagged terms of the independent variable improve prediction of the dependent variable.
<code>{1: ({'ssr_ftest': (88.58101866095492, 2.73e-15, 96.0, 1),
'ssr_chi2test': (91.34917549410976, 1.20e-21, 1),
'lrtest': (64.72028981473863, 8.63e-16, 1),
'params_ftest': (88.581018660955, 2.73e-15, 96.0, 1.0)},
[...]),
2: ({'ssr_ftest': (40.083232534718334, 2.79e-13, 93.0, 2),
'ssr_chi2test': (84.47649007316981, 4.53e-19, 2),
'lrtest': (60.92207872461543, 5.90e-14, 2),
'params_ftest': (40.08323253471834, 2.79e-13, 93.0, 2.0)},
[...])}
</code>To display the results:
<code>for lag in range(1, 3):
print(f'lag={lag}')
print(f'F-statistic: {gc_res[lag][0]["params_ftest"][0]:.4f}, p-value: {gc_res[lag][0]["params_ftest"][1]:.4f}')
print(f'Granger causality: {gc_res[lag][0]["ssr_ftest"][0]:.4f}, p-value: {gc_res[lag][0]["ssr_ftest"][1]:.4f}')
print('')
</code>Output:
<code>lag=1
F-statistic: 88.5810, p-value: 0.0000
Granger causality: 88.5810, p-value: 0.0000
lag=2
F-statistic: 40.0832, p-value: 0.0000
Granger causality: 40.0832, p-value: 0.0000
</code>Since the p‑values are far below the 1% significance level, we reject the null hypothesis and conclude that X Granger‑causes Y.
Important Considerations
Ensure a clear temporal ordering of variables; ambiguous ordering leads to incorrect inference.
Select an appropriate lag length—higher lags increase model complexity and risk over‑fitting.
Choose a significance level (e.g., 0.05 or 0.01) based on the problem context.
Granger causality only indicates predictive causality, not true causal direction, and cannot rule out omitted variables.
Diagnose the model (residual analysis, heteroskedasticity tests, serial correlation tests) to verify reliability.
References
Zheng Jianhua, Zhou Chunyan, Lu Chengmin, Luo Baocheng (2011). Application of Granger causality analysis and MATLAB implementation. Computer Applications and Software, 28(3), 125‑128.
Huang Chengfang, Huang Jinjin (2016). Financial market linkage study based on Granger causality analysis. Science & Technology Information, 15(13), 23‑24.
Model Perspective
Insights, knowledge, and enjoyment from a mathematical modeling researcher and educator. Hosted by Haihua Wang, a modeling instructor and author of "Clever Use of Chat for Mathematical Modeling", "Modeling: The Mathematics of Thinking", "Mathematical Modeling Practice: A Hands‑On Guide to Competitions", and co‑author of "Mathematical Modeling: Teaching Design and Cases".
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.