Atlas
statminds
Time Series (Predictive Causal Model)The underlying model family class (e.g. GLM, linear model, categorical matrix, log-linear).Parametric ReferenceStatistical methods that assume a specific probability distribution family (typically normal).12-stage workflow

Granger Causality

The engine for Predictive Precedence. This model audits whether the past values of one time series significantly improve the forecast of another, revealing the 'Information Flow' between temporal signals.

Model familyTime Series (Predictive Causal Model)
Hypothesispredictive_causality
AliasesPredictive Causality Test · Temporal Precedence Audit · G-Causality
G1
Predictive Precedence Audit
Determine if Variable X consistently 'leads' Variable Y in time.
G2
Information Flow Mapping
Identify the direction of influence between two related temporal processes.
G3
Temporal Synergy Discovery
Quantify the unique predictive power added by a secondary series while neutralizing the primary's own history.
Visual Overview Dashboard
1

What is it?

Granger Causality analyzes sequences of data points ordered chronologically over time to extract patterns, model trends, and make forecasts.

The engine for Predictive Precedence. This model audits whether the past values of one time series significantly improve the forecast of another, revealing the 'Information Flow' between temporal signals.

2

Goals & Indications

  • Predictive Precedence Audit: Determine if Variable X consistently 'leads' Variable Y in time.
  • Information Flow Mapping: Identify the direction of influence between two related temporal processes.
  • Temporal Synergy Discovery: Quantify the unique predictive power added by a secondary series while neutralizing the primary's own history.
3

Core Idea Diagram

XYGranger Causes
4

Claims tested

H₀: H₀: X does not Granger-cause Y (past values of X provide no additional predictive information for Y beyond Y's own past values)
Hₐ: Hₐ: X Granger-causes Y (past values of X significantly improve prediction of Y beyond what Y's own past values provide)
5

How it works

  1. Select appropriate lag order p using information criteria.
  2. Estimate restricted regression of Y on its own past values only.
  3. Estimate unrestricted regression including past values of X and Y.
  4. Run F-test on X coefficients; low p-value indicates Granger causality.
6

Assumptions

Both X and Y are stationary: Time series must be stationary to avoid spurious regression
Appropriate lag order selection: Correct number of lags p captures temporal relationship without over-fitting
Linear relationships between variables: Granger causality based on linear VAR; nonlinear relationships not detected
7

Important Note

CRITICAL: Granger causality is NOT true causation - it tests predictive precedence only. X Granger-causes Y means X's past helps predict Y, but this does NOT prove X causes Y. Alternative explanations: (1) Y causes X with a lag, (2) Both X and Y are driven by third variable Z, (3) Spurious correlation from non-stationarity. Granger causality is a statistical relationship about prediction, not a statement about underlying causal mechanisms. The test asks: 'Does knowing X's history improve forecasts of Y?' NOT 'Does X cause Y?' Think of it as 'predictive usefulness' rather than causation. Always test bi-directionally (X→Y and Y→X) and consider omitted variables. Key assumption: relevant information contained only in X and Y time series; if other variables matter, results may be spurious.

8

Worked Example

Null HypothesisF-Statdfp-valueCauses?
X does not cause Y8.45(2, 94)0.0004Yes
Y does not cause X1.12(2, 94)0.3312No
Interactive Sandbox

Granger Causality Simulation Laboratory

Granger Causality checks if past values of X significantly improve prediction of Y. Slide the causality coefficients to observe temporal lead/lag relationships.

Causality Strength: X → Y0.35
Causality Strength: Y → X0.00
F-Test for X Granger Causes Y
F-statistic: 11.4669
p-value: 0.00071Significant Granger Cause
Overlapping Time Series paths
2.8-2.4
Series X
Series Y
The 12-Stage Precision Workflow
01No Precedence Null
Hypotheses
We test the null that 'X does not Granger-cause Y' against the discovery of a non-zero predictive bond from the past.
02Global Stationarity
Assumptions
The ultimate prerequisite: both time series MUST be stationary. Unit roots in either series lead to 'Spurious Causality' artifacts.
03Lag-Order Audit
Diagnostics
Utilizing AIC/BIC to find the optimal 'History Window'—too few lags miss the signal; too many overfit the noise.
04focus
Does weekly Practice Volume lead to subsequent drops in clinic No-Show rates? Auditing the 'Practice-Compliance' loop.
05Toda-Yamamoto Pivot
Alternatives
Knowing when to switch to the Toda-Yamamoto approach if the series are integrated or cointegrated (non-stationary).
06F-Test Strike
Significance
Executing the joint F-test on all lagged coefficients of the predictor series—the definitive strike on predictive precedence.
07Incremental R²
Effect Size
Quantifying the proportion of future variance captured by the predictor above and beyond the outcome's own history.
08Temporal Depth
Sample Size
Ensuring a minimum of 100+ timepoints to provide enough 'Degree of Freedom' buffer for the multi-lagged F-math.
09Bidirectional Flow
Reporting
Always testing and reporting BOTH directions (X → Y and Y → X) to rule out simple co-movement or find feedback loops.
10grangertest Logic
Software
Executing 'grangertest()' commands, ensuring the 'order' parameter correctly reflects your lag-selection diagnostics.
11focus
Avoiding the error of claiming 'Physical Causality'—Granger only proves 'Predictive Precedence' (X precedes Y).
12focus
Tracing the model back to Clive Granger (1969) and the Nobel-winning breakthrough in temporal information forensics.
01Hypothesis test logic

Hypotheses

Pragmatic null and alternative hypotheses defined in mathematical notation.

A hypothesis is a question sharpened to a point. Ambiguity is the enemy of inference.
Logic Core
Null · H₀

H₀: X does not Granger-cause Y (past values of X provide no additional predictive information for Y beyond Y's own past values)

Alternative · Hₐ

Hₐ: X Granger-causes Y (past values of X significantly improve prediction of Y beyond what Y's own past values provide)

Why it matters predictive_causality

CRITICAL: Granger causality is NOT true causation - it tests predictive precedence only. X Granger-causes Y means X's past helps predict Y, but this does NOT prove X causes Y. Alternative explanations: (1) Y causes X with a lag, (2) Both X and Y are driven by third variable Z, (3) Spurious correlation from non-stationarity. Granger causality is a statistical relationship about prediction, not a statement about underlying causal mechanisms. The test asks: 'Does knowing X's history improve forecasts of Y?' NOT 'Does X cause Y?' Think of it as 'predictive usefulness' rather than causation. Always test bi-directionally (X→Y and Y→X) and consider omitted variables. Key assumption: relevant information contained only in X and Y time series; if other variables matter, results may be spurious.

02Model diagnostics

Assumptions

The core mathematical criteria needed to ensure that statistical testing remains unbiased and valid.

Stationarity is the prerequisite of prediction. You cannot forecast a system that is fundamentally changing its rules.
Integrity Shield
7
Assumptions
6
Critical / High Severity
How to check
Quick
Plot both time series: stationary shows constant mean (no trend), constant variance. Plot ACF: stationary series has ACF that decays quickly. Non-stationary: wandering mean/trend, ACF decays very slowly. Visual inspection often sufficient for obvious non-stationarity
Rigorous
Augmented Dickey-Fuller (ADF) test on both X and Y: H₀=unit root (non-stationary), p<0.05 indicates stationarity. KPSS test: H₀=stationarity, p<0.05 indicates non-stationarity. Use both tests: ADF rejects + KPSS doesn't reject = stationary. If both non-stationary: test for cointegration using Engle-Granger or Johansen test. Cointegrated non-stationary series can still have valid Granger causality in VECM framework. PP test (Phillips-Perron) as robustness check
If violated
If non-stationary: (1) Difference both series until stationary (first difference usually sufficient): test Granger causality on differenced series. CAUTION: differencing changes interpretation to 'changes in X cause changes in Y'. (2) If cointegrated: use Vector Error Correction Model (VECM) which preserves long-run relationship. (3) Detrend both series via regression, test on residuals. (4) Transform (log, Box-Cox) if variance non-stationary, then difference. NEVER test Granger causality on non-stationary series without differencing or cointegration - leads to spurious results
How to check
Quick
Start with small lags (p=1 to 4 for quarterly data, 1 to 12 for monthly). Plot Granger test p-values across multiple lags: if significant at multiple consecutive lags, relationship likely robust. If significant at only one arbitrary lag, may be spurious. Cross-correlation function (CCF) shows at which lags X predicts Y
Rigorous
Use information criteria to select VAR lag order: fit VAR(p) for p=1,2,3,...,pmax, compare AIC/BIC/HQ. AIC: tends to select larger p (liberal). BIC: penalizes complexity more (conservative). Likelihood ratio test: compare VAR(p) vs VAR(p-1). Lag selection critical: too few lags → omitted variable bias (Type I error); too many lags → loss of power, overfitting (Type II error). Some suggest testing at multiple reasonable lags as robustness check. Schwert criterion: pmax = int[12×(T/100)^(1/4)] for monthly data
If violated
Under-specification (p too small): residuals show autocorrelation (Ljung-Box test fails), Granger test may falsely reject (X appears to cause Y due to omitted dynamics). Fix: increase p until residuals white noise, use information criteria. Over-specification (p too large): loss of statistical power (fails to detect true causality), estimates imprecise, multicollinearity among lagged variables. Fix: use BIC (penalizes complexity), cross-validation, or expert knowledge of temporal dynamics. Best practice: report results at multiple reasonable lags to assess robustness
How to check
Quick
Scatter plots of Y_t vs X_{t-k} for k=1,2,3,... Should show linear patterns if linear Granger causality present. Obvious curvature suggests nonlinearity. Plot residuals from VAR: should appear random; patterns suggest nonlinearity
Rigorous
Linearity tests: Ramsey RESET test on VAR equations, BDS test for nonlinear dependence in residuals, Tsay test for nonlinearity. Check if adding squared/interaction terms of lagged X significantly improves fit (F-test). Plot CCF: linear relationships show clear peaks; complex patterns may indicate nonlinearity. Neural network tests for nonlinearity (White test). If linear tests fail but strong theoretical reason for relationship: likely nonlinear
If violated
If nonlinear relationship: (1) Transform variables (log, Box-Cox) to linearize relationship. (2) Use nonlinear Granger causality tests: Baek-Brock test, Hiemstra-Jones test, transfer entropy. (3) Threshold VAR or regime-switching models if relationship changes by regime. (4) Neural network Granger causality (uses neural nets to model nonlinear f). (5) Accept that linear test has limited power for nonlinear relationships; report as limitation. Standard Granger test may fail to detect nonlinear causality (Type II error)
How to check
Quick
Theoretical reasoning: is there a variable Z that plausibly drives both X and Y? Example: ice cream sales (X) and drowning (Y) both caused by temperature (Z). Economic domain knowledge critical. If X and Y both have common trends/cycles, suspect common driver
Rigorous
Multivariate Granger causality: test X→Y controlling for other variables Z₁, Z₂,... in expanded VAR(X,Y,Z₁,Z₂,...). If X→Y relationship disappears when Z added, Z is confounder. Instantaneous causality test: check if contemporaneous X_t (not just lagged) predicts Y_t after controlling for lags - suggests omitted Z. Theory-guided: include all theoretically relevant variables. Data-driven: use LASSO VAR for variable selection. Cannot fully rule out omitted variables; rely on theory
If violated
If confounders suspected: (1) Include them in multivariate VAR: test if X→Y in VAR(X,Y,Z₁,Z₂,...). (2) Use instrumental variables if confounders not observed. (3) Acknowledge as limitation: 'X Granger-causes Y conditional on information in {X,Y} only; cannot rule out omitted variable Z'. (4) Use domain knowledge to argue no major confounders. Omitted variable bias is fundamental limitation of Granger causality - can never be 100% certain all relevant variables included. Best defense: strong theory, robustness checks with additional variables
How to check
Quick
Plot ACF of residuals for each VAR equation: should have no spikes outside confidence bands (±1.96/√n). Residuals over time should appear random, no patterns. If ACF shows spikes, lag order insufficient
Rigorous
Portmanteau tests on VAR residuals: Ljung-Box Q-test, multivariate Portmanteau test. H₀=no autocorrelation up to lag h, p>0.05 indicates white noise. Test each equation separately and jointly. Breusch-Godfrey LM test for serial correlation. If residuals autocorrelated: lag order too small, model misspecified, or nonlinearity present. Autocorrelated residuals → biased standard errors → invalid Granger test p-values
If violated
If residuals autocorrelated (Portmanteau p<0.05): (1) Increase VAR lag order p until residuals white noise. (2) Check for structural breaks or outliers distorting fit. (3) Consider nonlinear models if linear VAR inadequate. (4) Add seasonal dummies if seasonal patterns remain. (5) Check stationarity - non-stationary variables cause autocorrelated residuals. Never accept Granger test results if residuals autocorrelated; inference invalid (standard errors biased, p-values unreliable)
How to check
Quick
Plot both time series: look for sudden level shifts, trend changes at specific time points. Common causes: policy changes, financial crises, technological shifts. Rolling-window Granger tests: estimate test statistic in moving windows; if varies dramatically, suggests instability
Rigorous
Structural break tests on VAR: Chow test at known break date, sup-Wald test (Andrews) for unknown break, Bai-Perron multiple break test. Test parameter constancy: recursive estimates of VAR coefficients, CUSUM of squared residuals. If break detected: compare Granger causality before vs after break (may differ). QLR (Quandt Likelihood Ratio) test. Nyblom stability test for parameter constancy
If violated
If structural break at time τ: (1) Include dummy variables in VAR (level shift, slope shift). (2) Fit separate VARs before and after break; test Granger causality in each regime separately. (3) Use time-varying parameter VAR (state-space framework). (4) Markov-switching VAR if regime changes recurrent. (5) Use only post-break data if break is recent. Structural breaks invalidate full-sample Granger causality (relationship may hold in one regime, not another)
How to check
Quick
Count observations (T): minimum T≥100 for simple Granger test, preferably T≥200. Rule of thumb: T ≥ 10×p×k where p=lags, k=number of variables. For p=4 lags, 2 variables: need T≥80 minimum, prefer T≥160. Short samples: tests have low power (fail to detect true causality)
Rigorous
Calculate degrees of freedom: df = T - p×k - 1 where T=sample size, p=lags, k=variables. Need sufficient df for F-test reliability (generally df≥30). Power analysis: given effect size, what sample size needed to detect with 80% power? Bootstrap p-values more reliable for small samples (T<100). Cross-validation: ensure enough data for train/validation split. Information criteria penalized for small samples (AICc, BICc)
If violated
If T too small relative to p: (1) Reduce lag order (use BIC which penalizes complexity; prefer small p for small T). (2) Use penalty-likelihood ratio test (more conservative for small samples). (3) Bootstrap p-values instead of asymptotic F-distribution. (4) Acknowledge low power: 'May fail to detect causality due to limited sample size'. (5) Collect more data if possible. (6) Use Bayesian VAR with informative priors. NEVER use many lags (large p) with small samples (T<100); estimates unstable, test has no power
03Residual Forensics

Diagnostics

Checking residual plots and indices to examine model deviations and ensure standard error integrity.

White noise is the goal. If your residuals have a pattern, you have left information on the table.
System Health
Essential checks
  1. Stationarity audit (ADF) for both the predictor and outcome series.
  2. Lag-length consistency check using AIC / BIC / HQIC.
  3. Bidirectional strike: Testing X → Y AND Y → X to rule out feedback loops.
  4. Omnibus F-test for the joint significance of all lagged predictor coefficients.
  5. Residual serial correlation audit (Ljung-Box) for the augmented regression.
Recommended checks
  1. Forecast Error Variance Decomposition (FEVD) to quantify 'Causal Weight'.
  2. Toda-Yamamoto robustness strike if series are integrated (non-stationary).
  3. Residual cross-correlation audit to detect unmodeled common-shocks.
  4. Stability check of the regression coefficients over time.
  5. Out-of-sample predictive gain audit (MSE with vs. without predictor).
04Live Instances

Applied Minds

Review concrete study examples, data layout guidelines, and copy executable syntax scripts.

Theory is the map. Practice is the terrain. Simulation bridges the gap.
Applied Wisdom
Example 01

Does Advertising Granger-Cause Sales?

Test if advertising expenditure (X) Granger-causes sales revenue (Y) using weekly data over 3 years (n=156 weeks). Demonstrates complete Granger causality workflow: data generation with known causal lag, stationarity testing (ADF), lag order selection via information criteria, Granger causality test using F-test and chi-square test, bi-directional testing (X→Y and Y→X), visualizations of lagged relationships and cross-correlation function (CCF), VAR model estimation to show coefficient patterns, impulse response functions, forecast error variance decomposition, robustness checks across multiple lags, and careful interpretation distinguishing statistical Granger causality from true causation. This example emphasizes the critical distinction: Granger causality shows predictive precedence, not proof of causal mechanism.

# ============================================================================
# GRANGER CAUSALITY TEST: Does Advertising Granger-Cause Sales?
# ============================================================================
# Question: Does advertising expenditure (X) Granger-cause sales revenue (Y)?
# Data: 156 weeks (3 years) of weekly advertising and sales data
# Method: Granger causality test via F-test on VAR model
# ============================================================================
# CRITICAL: Granger causality ≠ true causation
#           It tests if X's past helps PREDICT Y, not if X CAUSES Y
# ============================================================================

# Load required packages
library(lmtest)        # grangertest()
library(vars)          # VAR(), causality()
library(tseries)       # adf.test()
library(ggplot2)       # visualization
library(gridExtra)     # multiple plots
library(forecast)      # auto.arima for lag selection

set.seed(123)

# ============================================================================
# 1. DATA GENERATION: Advertising and Sales with Known Causal Relationship
# ============================================================================

cat("============================================================================\n")
cat("GRANGER CAUSALITY TEST: Advertising → Sales\n")
cat("============================================================================\n\n")

# Generate 156 weeks of data (3 years)
n <- 156
time_index <- 1:n

# Generate advertising expenditure (X) - AR(1) process with trend
adv_innovation <- rnorm(n, 0, 5)
advertising <- numeric(n)
advertising[1] <- 50
for (t in 2:n) {
  advertising[t] <- 40 + 0.1*t + 0.5*advertising[t-1] + adv_innovation[t]
}

# Generate sales (Y) - depends on LAGGED advertising (2-week lag) + own dynamics
sales_innovation <- rnorm(n, 0, 10)
sales <- numeric(n)
sales[1] <- 100
sales[2] <- 100
for (t in 3:n) {
  # Sales depends on: own lag, advertising from 2 weeks ago, and noise
  sales[t] <- 80 + 0.3*sales[t-1] + 0.8*advertising[t-2] + sales_innovation[t]
}

# Create data frame
data <- data.frame(
  week = time_index,
  advertising = advertising,
  sales = sales
)

cat("Data generated: n =", n, "weeks\n")
cat("TRUE relationship: Sales[t] = f(Sales[t-1], Advertising[t-2]) + noise\n")
cat("  → Advertising DOES Granger-cause Sales(2-week lag)\n\n")

cat("Summary statistics:\n")
cat("Advertising: Mean=", round(mean(data$advertising), 2), 
    ", SD=", round(sd(data$advertising), 2), "\n")
cat("Sales: Mean=", round(mean(data$sales), 2), 
    ", SD=", round(sd(data$sales), 2), "\n\n")

# ============================================================================
# 2. EXPLORATORY VISUALIZATION
# ============================================================================

cat("=== EXPLORATORY ANALYSIS ===\n\n")

# Time series plots
p1 <- ggplot(data, aes(x=week, y=advertising)) +
  geom_line(color="steelblue", size=1) +
  labs(title="Advertising Expenditure over Time", x="Week", y="Advertising($1000s)") +
  theme_minimal()

p2 <- ggplot(data, aes(x=week, y=sales)) +
  geom_line(color="darkgreen", size=1) +
  labs(title="Sales Revenue over Time", x="Week", y="Sales($1000s)") +
  theme_minimal()

# Scatter plot: Sales vs Lagged Advertising
data$adv_lag2 <- c(NA, NA, data$advertising[1:(n-2)])

p3 <- ggplot(data, aes(x=adv_lag2, y=sales)) +
  geom_point(alpha=0.5, color="darkred") +
  geom_smooth(method="lm", se=TRUE, color="blue") +
  labs(title="Sales vs Advertising(2-week lag)", 
       x="Advertising[t-2]", y="Sales[t]") +
  theme_minimal()

# Cross-correlation function (CCF)
par(mfrow=c(1,1))
ccf_result <- ccf(data$advertising, data$sales, lag.max=20, 
                  main="Cross-Correlation: Advertising vs Sales",
                  ylab="CCF", xlab="Lag(negative=advertising leads sales)")

cat("Cross-correlation function (CCF) computed\n")
cat("  Negative lags: advertising LEADS sales\n")
cat("  Positive lags: sales LEADS advertising\n")
cat("  Peak at negative lag suggests advertising predicts future sales\n\n")

# Find peak CCF
peak_lag <- which.max(abs(ccf_result$acf)) - 21  # Adjust for 0-lag at position 21
cat("Peak CCF at lag:", peak_lag, "\n")
if (peak_lag < 0) {
  cat("  → Advertising leads sales by", abs(peak_lag), "weeks(consistent with Granger causality)\n\n")
}

# ============================================================================
# 3. STATIONARITY TESTING
# ============================================================================

cat("=== STATIONARITY TESTS ===\n\n")

# Function to test stationarity
test_stationarity <- function(series, name) {
  cat(name, ":\n")
  
  # ADF test
  adf <- adf.test(series)
  cat("  ADF test: statistic=", round(adf$statistic, 4), 
      ", p-value=", round(adf$p.value, 4), "\n")
  if (adf$p.value < 0.05) {
    cat("    → STATIONARY(p < 0.05)\n")
  } else {
    cat("    → NON-STATIONARY(p ≥ 0.05)\n")
  }
  
  # KPSS test
  kpss <- kpss.test(series, null="Trend")
  cat("  KPSS test: statistic=", round(kpss$statistic, 4), 
      ", p-value=", round(kpss$p.value, 4), "\n")
  if (kpss$p.value >= 0.05) {
    cat("    → STATIONARY(p ≥ 0.05)\n")
  } else {
    cat("    → NON-STATIONARY(p < 0.05)\n")
  }
  cat("\n")
  
  return(adf$p.value < 0.05 & kpss$p.value >= 0.05)
}

# Test both series
adv_stationary <- test_stationarity(data$advertising, "Advertising")
sales_stationary <- test_stationarity(data$sales, "Sales")

if (!adv_stationary | !sales_stationary) {
  cat("WARNING: At least one series non-stationary\n")
  cat("Action: Apply first differencing\n\n")
  
  # Difference both series
  data$adv_diff <- c(NA, diff(data$advertising))
  data$sales_diff <- c(NA, diff(data$sales))
  
  cat("Testing differenced series:\n")
  test_stationarity(na.omit(data$adv_diff), "Advertising(differenced)")
  test_stationarity(na.omit(data$sales_diff), "Sales(differenced)")
  
  # Use differenced series
  use_diff <- TRUE
  cat("Proceeding with DIFFERENCED series for Granger test\n")
  cat("NOTE: Interpretation changes to 'changes in X cause changes in Y'\n\n")
} else {
  use_diff <- FALSE
  cat("Both series stationary → proceed with LEVELS\n\n")
}

# ============================================================================
# 4. LAG ORDER SELECTION
# ============================================================================

cat("=== LAG ORDER SELECTION ===\n\n")

if (use_diff) {
  # VAR on differenced data (remove NA from differencing)
  var_data <- data.frame(
    advertising = data$adv_diff,
    sales = data$sales_diff
  )
  var_data <- na.omit(var_data)
} else {
  var_data <- data.frame(
    advertising = data$advertising,
    sales = data$sales
  )
}

# Select lag order using information criteria
lag_select <- VARselect(var_data, lag.max=12, type="const")

cat("Information criteria for VAR lag selection:\n")
print(lag_select$selection)
cat("\n")

aic_lag <- lag_select$selection["AIC(n)"]
bic_lag <- lag_select$selection["SC(n)"]  # SC = Schwarz = BIC

cat("Selected lags:\n")
cat("  AIC recommends p =", aic_lag, "\n")
cat("  BIC recommends p =", bic_lag, "(more conservative)\n\n")

# Use BIC lag (more conservative, avoids overfitting)
optimal_lag <- bic_lag
cat("Using p =", optimal_lag, "lags(BIC criterion)\n\n")

# ============================================================================
# 5. GRANGER CAUSALITY TEST: Does Advertising → Sales?
# ============================================================================

cat("=== GRANGER CAUSALITY TEST: Advertising → Sales ===\n\n")

# Method 1: Using grangertest() from lmtest
cat("Method 1: grangertest() from lmtest package\n")
cat("---------------------------------------------\n")

granger_adv_sales <- grangertest(sales ~ advertising, 
                                  order=optimal_lag, data=var_data)
print(granger_adv_sales)
cat("\n")

if (granger_adv_sales$`Pr(>F)`[2] < 0.05) {
  cat("RESULT: REJECT H₀ (p < 0.05)\n")
  cat("  → Advertising DOES Granger-cause Sales\n")
  cat("  → Past advertising helps PREDICT future sales\n")
} else {
  cat("RESULT: FAIL TO REJECT H₀ (p ≥ 0.05)\n")
  cat("  → Advertising does NOT Granger-cause Sales\n")
  cat("  → Past advertising does not help predict sales\n")
}
cat("\n")

# Method 2: Using VAR() and causality() from vars package
cat("Method 2: VAR model with causality() test\n")
cat("------------------------------------------\n")

var_model <- VAR(var_data, p=optimal_lag, type="const")

# Granger causality test via vars::causality()
causality_test <- causality(var_model, cause="advertising")

cat("\nGranger causality(F-test): Advertising → Sales\n")
print(causality_test$Granger)

cat("\nInterpretation:\n")
if (causality_test$Granger$p.value < 0.05) {
  cat("  p-value =", round(causality_test$Granger$p.value, 4), "< 0.05\n")
  cat("  → SIGNIFICANT: Advertising Granger-causes Sales\n\n")
} else {
  cat("  p-value =", round(causality_test$Granger$p.value, 4), "≥ 0.05\n")
  cat("  → NOT SIGNIFICANT: No Granger causality detected\n\n")
}

# ============================================================================
# 6. BI-DIRECTIONAL TESTING: Does Sales → Advertising?
# ============================================================================

cat("=== BI-DIRECTIONAL TEST: Sales → Advertising ===\n\n")

granger_sales_adv <- grangertest(advertising ~ sales, 
                                  order=optimal_lag, data=var_data)
print(granger_sales_adv)
cat("\n")

if (granger_sales_adv$`Pr(>F)`[2] < 0.05) {
  cat("RESULT: Sales DOES Granger-cause Advertising\n")
  cat("  → Past sales help predict future advertising\n")
  cat("  → BIDIRECTIONAL causality(feedback loop)\n")
} else {
  cat("RESULT: Sales does NOT Granger-cause Advertising\n")
  cat("  → UNIDIRECTIONAL causality(Advertising → Sales only)\n")
}
cat("\n")

# Summary of causality direction
cat("CAUSALITY SUMMARY:\n")
cat("------------------\n")
adv_to_sales <- granger_adv_sales$`Pr(>F)`[2] < 0.05
sales_to_adv <- granger_sales_adv$`Pr(>F)`[2] < 0.05

if (adv_to_sales & sales_to_adv) {
  cat("  Direction: BIDIRECTIONAL(X ↔ Y)\n")
  cat("  Advertising ↔ Sales: Feedback loop\n")
} else if (adv_to_sales & !sales_to_adv) {
  cat("  Direction: UNIDIRECTIONAL(X → Y)\n")
  cat("  Advertising → Sales only\n")
} else if (!adv_to_sales & sales_to_adv) {
  cat("  Direction: REVERSE(Y → X)\n")
  cat("  Sales → Advertising only\n")
} else {
  cat("  Direction: NONE\n")
  cat("  No Granger causality detected in either direction\n")
}
cat("\n")

# ============================================================================
# 7. VAR MODEL COEFFICIENTS - Show Which Lags Matter
# ============================================================================

cat("=== VAR MODEL COEFFICIENTS ===\n\n")

cat("Sales equation(showing which lags of Advertising predict Sales):\n")
cat("---------------------------------------------------------------\n")
summary(var_model)$varresult$sales

cat("\nKey coefficients to examine:\n")
sales_coefs <- coef(var_model$varresult$sales)
adv_coefs <- sales_coefs[grep("advertising", names(sales_coefs))]

cat("\nLagged Advertising coefficients in Sales equation:\n")
for (i in 1:length(adv_coefs)) {
  coef_val <- adv_coefs[i]
  coef_name <- names(adv_coefs)[i]
  sig <- abs(coef_val / sqrt(vcov(var_model$varresult$sales)[i,i])) > 1.96
  
  cat(" ", coef_name, ": ", round(coef_val, 4))
  if (sig) cat(" ***")
  cat("\n")
}
cat("*** = significant at 5% level(|t| > 1.96)\n\n")

# ============================================================================
# 8. IMPULSE RESPONSE FUNCTIONS (IRF)
# ============================================================================

cat("=== IMPULSE RESPONSE FUNCTIONS ===\n\n")

# IRF: How does Sales respond to shock in Advertising?
irf_result <- irf(var_model, impulse="advertising", response="sales", 
                   n.ahead=20, boot=TRUE, runs=1000)

cat("Computing IRF: Effect of Advertising shock on Sales over 20 periods\n")
cat("Using bootstrap(1000 runs) for confidence intervals\n\n")

plot(irf_result, main="Impulse Response: Sales response to Advertising shock")

cat("\nInterpretation:\n")
cat("  - Shows dynamic effect of one-time advertising shock on sales\n")
cat("  - Y-axis: change in sales\n")
cat("  - X-axis: periods after shock\n")
cat("  - If response is positive and significant: advertising shock increases sales\n\n")

# ============================================================================
# 9. FORECAST ERROR VARIANCE DECOMPOSITION (FEVD)
# ============================================================================

cat("=== FORECAST ERROR VARIANCE DECOMPOSITION ===\n\n")

# FEVD: What % of Sales forecast error is due to Advertising shocks?
fevd_result <- fevd(var_model, n.ahead=20)

cat("FEVD for Sales(% of forecast error variance explained by each variable):\n")
cat("-------------------------------------------------------------------------\n")
print(round(fevd_result$sales * 100, 2))
cat("\n")

cat("Interpretation:\n")
adv_contribution <- fevd_result$sales[10, "advertising"]  # 10 periods ahead
cat("  At 10-period horizon, Advertising explains", 
    round(adv_contribution*100, 1), "% of Sales forecast error variance\n")
if (adv_contribution > 0.1) {
  cat("  → Advertising is IMPORTANT for Sales prediction\n")
} else {
  cat("  → Advertising has WEAK explanatory power(other factors dominate)\n")
}
cat("\n")

# ============================================================================
# 10. RESIDUAL DIAGNOSTICS
# ============================================================================

cat("=== RESIDUAL DIAGNOSTICS ===\n\n")

# Portmanteau test for residual autocorrelation
serial_test <- serial.test(var_model, lags.pt=16, type="PT.asymptotic")

cat("Portmanteau test for residual autocorrelation:\n")
print(serial_test)
cat("\n")

if (serial_test$serial$p.value > 0.05) {
  cat("GOOD: Residuals are white noise(no autocorrelation)\n")
  cat("  → VAR model adequately captures dynamics\n")
} else {
  cat("WARNING: Residuals show autocorrelation\n")
  cat("  → May need to increase lag order\n")
}
cat("\n")

# Plot residuals
par(mfrow=c(2,2))
plot(var_model$varresult$sales$residuals, type="l", 
     main="Sales Equation Residuals", ylab="Residuals")
abline(h=0, col="red", lty=2)

acf(var_model$varresult$sales$residuals, main="ACF: Sales Residuals")

plot(var_model$varresult$advertising$residuals, type="l", 
     main="Advertising Equation Residuals", ylab="Residuals")
abline(h=0, col="red", lty=2)

acf(var_model$varresult$advertising$residuals, main="ACF: Advertising Residuals")
par(mfrow=c(1,1))

# ============================================================================
# 11. ROBUSTNESS: Test at Multiple Lags
# ============================================================================

cat("=== ROBUSTNESS CHECK: Multiple Lag Orders ===\n\n")

lag_range <- 1:8
p_values <- numeric(length(lag_range))

for (i in seq_along(lag_range)) {
  p <- lag_range[i]
  gt <- grangertest(sales ~ advertising, order=p, data=var_data)
  p_values[i] <- gt$`Pr(>F)`[2]
}

robustness_df <- data.frame(
  Lag = lag_range,
  P_value = round(p_values, 4),
  Significant = ifelse(p_values < 0.05, "Yes", "No")
)

cat("Granger test p-values across different lag orders:\n")
print(robustness_df, row.names=FALSE)
cat("\n")

# Plot p-values across lags
par(mfrow=c(1,1))
plot(lag_range, p_values, type="b", pch=19, col="steelblue",
     xlab="Lag order(p)", ylab="p-value",
     main="Granger Causality p-values across Lag Orders",
     ylim=c(0, max(p_values, 0.1)))
abline(h=0.05, col="red", lty=2, lwd=2)
text(max(lag_range)*0.7, 0.06, "α = 0.05", col="red")

cat("Interpretation:\n")
sig_lags <- sum(p_values < 0.05)
cat(" ", sig_lags, "out of", length(lag_range), "lag orders show significant causality\n")
if (sig_lags >= length(lag_range)/2) {
  cat("  → ROBUST: Granger causality holds across multiple lag specifications\n")
} else {
  cat("  → WEAK: Causality sensitive to lag choice(may be spurious)\n")
}
cat("\n")

# ============================================================================
# 12. CRITICAL INTERPRETATION & WARNINGS
# ============================================================================

cat("============================================================================\n")
cat("CRITICAL INTERPRETATION\n")
cat("============================================================================\n\n")

cat("WHAT WE FOUND:\n")
if (granger_adv_sales$`Pr(>F)`[2] < 0.05) {
  cat("  ✓ Advertising DOES Granger-cause Sales(p < 0.05)\n")
  cat("  ✓ Past advertising expenditure helps PREDICT future sales\n")
  cat("  ✓ Including advertising lags improves sales forecasts\n\n")
} else {
  cat("  ✗ No Granger causality detected(p ≥ 0.05)\n")
  cat("  ✗ Past advertising does not improve sales predictions\n\n")
}

cat("WHAT THIS DOES NOT MEAN:\n")
cat("  ✗ Does NOT prove advertising CAUSES sales\n")
cat("  ✗ Does NOT rule out reverse causation(sales → advertising)\n")
cat("  ✗ Does NOT account for confounders(economy, competition, etc.)\n")
cat("  ✗ Does NOT imply increasing advertising will increase sales\n\n")

cat("ALTERNATIVE EXPLANATIONS:\n")
cat("  1. Reverse causation: High sales → increased advertising budget\n")
cat("  2. Common cause: Economic growth drives both advertising and sales\n")
cat("  3. Spurious correlation: Non-stationarity creates false relationship\n")
cat("  4. Omitted variables: Competitor actions, seasonality, etc.\n\n")

cat("GRANGER CAUSALITY = PREDICTIVE PRECEDENCE, NOT CAUSATION\n\n")

cat("============================================================================\n")
cat("GRANGER CAUSALITY TEST COMPLETE\n")
cat("============================================================================\n")

# ============================================================================
# END OF GRANGER CAUSALITY EXAMPLE
# ============================================================================
05Tactical Pivots

Alternatives

Structured fallback pathways for choosing alternative tests when normality or slopes requirements fail.

When the path is blocked, pivot. Rigor is not rigidity; it is the intelligent adaptation to reality.
Adaptive Strategy
Measurement Precision Ladder Ideal · Continuous Bivariate Stream
Ratio
Maintain Granger logic. Optimal for auditing the 'Lead-Lag' pulse between high-fidelity continuous series.
Peak Signal
Interval
Ideal for Information Flow. Ensure both series are stationary before committing to the causality strike.
Standard Profile
Nominal / Ordinal
Abandon Granger. Use Cross-Lagged Panel Models or Markov-Chain audits to model categorical precedence.
Identity Only
Temporal Trajectory Audit Dynamic Precedence Flow
Temporal Lead
Information flow.
Stay with Granger Causality. Identify if X contains unique blueprints for the future of Y.
Multi-Variable Lead
Systemic precedence.
Pivot to VAR Models to audit the web of causality across more than two series.
Adaptive Technical Safeguards · adaptive safeguards
non stationary series
  • Toda-Yamamoto Strike — The required elite alternative when series are integrated (unit roots) but not cointegrated.
  • Differencing Audit — Neutralize trends before testing precedence to avoid 'Spurious Causality'.
non linear causality
  • Non-Parametric Granger (Hiemstra-Jones) — Audit precedence without linear constraints using information theory.
  • Kernel Granger — Use high-D mapping to capture curved 'Lead-Lag' relationships.
too many lags
  • AIC / BIC Selection — Prune the 'History Window' to maximize power and prevent overfitting of random ghosts.
06Adjusted Comparisons

Post-hoc

Group mean comparisons and correction controls (e.g. Tukey HSD, Bonferroni) to protect against Family-Wise Error Rates.

The omnibus test opens the door; post-hoc analysis explores the room.
Forensic Detail
Adjusted Comparisons
  • Vary lag length and check consistency of results
  • Test bidirectional causality (X→Y and Y→X)
  • Compare with Toda-Yamamoto approach (robust to integration order)
  • Test in VAR framework for multivariate setting
  • Check for instantaneous causality
  • Examine stability over subsamples (rolling window)
Interpretation Guidelines

Granger causality tests predictive relationship between time series. Post-hoc involves robustness checks.

07Standardized scale impact

Effect Size

Understanding effect sizes (e.g., Cohen's d, Partial Eta-Squared) and clinical impact benchmarks.

Significance is noise. Magnitude is the signal. Measure the impact, not just the probability.
Impact Magnitude
N/A
Recommended Measure
08Statistical Power

Sample Size

Guidelines for minimum sample requirements and power analysis parameters.

An underpowered study is an ethical failure. Respect the data by collecting enough of it.
Power Protocol
Floor Requirements

The 'Predictive Depth' Minimum: A minimum of 100 timepoints is essential. Multi-lagged F-tests (Granger) mathematically collapse if the temporal window is too narrow to estimate the LEAD-LAG interaction.

Effect SizeParametersRequired n
Small Effectf²=.02 (Small)n ≈ 600 timepoints
Medium Effectf²=.15 (Medium)n ≈ 120 timepoints
Large Effectf²=.35 (Large)n ≈ 60 timepoints
Key considerations

The 'Lag Balance': More lags increase the 'History' but decrease the 'Degrees of Freedom'. Only include lags that survive the AIC/BIC audit to maximize your N-efficiency.

G*Power StrategyBenchmark: F-tests → Granger Precedence. Parameters: Number of lags (p), Incremental R², α = .05, Power = .80. Note: Power is dictated by the 'Information Gain' added by the second variable.
09APA narrative blueprint

Reporting

How to compile statistical results into publication prose matching APA and journal style guides.

Data does not speak for itself. It requires a translator. Be clear, be precise, be honest.
Narrative Arc
10Exhibit Builder

Manuscript Lab

Copy standard summary tables and forensic reporting grids to outline analysis details.

Table 1: Granger Causality Audit for Leading Indicators
DirectionF-Statisticdfp-valueConclusion
Oil Prices → Inflation12.422< .001Granger Causality Found
Inflation → Oil Prices1.122.345No Evidence of Lead
Note. Null Hypothesis: Variable X does NOT Granger-cause Variable Y. Lag = 2.
Oil → Inflation (p < .001)Confirms 'Leading Indicator' Status. Oil prices provide an advance signal for future inflation, suggesting a 2-period lead time.
Header glossary

Predictive Priority. Means that past values of X contain information that helps predict Y better than just using past values of Y alone.

The Information Gain. Measures how much forecasting error is reduced by adding the leading indicator.

11Algorithmic Logic

Command Center

Syntax libraries and function parameters for executing calculations in stats packages.

Code is the modern laboratory. Clean execution ensures reproducible discovery.
Execution Engine
# 1. Execute Granger Causality Test
lmtest::grangertest(y ~ x, order = 2, data = df)

# 2. Causality in VAR framework
model <- vars::VAR(df_combined, p = 2)
vars::causality(model, cause = 'x')
Library stack
R
lmtestvars
Python
statsmodels.tsa.stattools
Elite Forensic Strike

Granger causality is NOT philosophical causality. It is 'Temporal Priority'. It only proves that X happens BEFORE Y, not that X creates Y.

# Execute Impulse Response Functions (IRF)
# Visualize how a shock in X ripples through Y over time.
plot(vars::irf(model))
12The Over-adjustment Trap

Common Mistakes

Analytical caveats and corrections to maintain modeling integrity.

Wisdom is learning from the failures of others. Anticipate the error before it occurs.
Defensive Logic
Why it's wrong
Granger causality only shows that X's past helps PREDICT Y; it does NOT prove X CAUSES Y. It's a statistical relationship about prediction, not a statement about underlying causal mechanisms. Even strong Granger causality can exist without any true causal relationship (e.g., due to common third variable, reverse causation with lags, or spurious correlation)
The correction
ALWAYS clarify: 'Granger causality' means 'predictive precedence' or 'temporal ordering in prediction', NOT causation. Report as 'X Granger-causes Y' (technical term) or 'X's past helps predict Y', never 'X causes Y'. Consider alternative explanations: (1) reverse causality (Y→X with lag), (2) omitted variable Z drives both, (3) spurious correlation. Test bi-directionally. Use domain knowledge to assess plausibility of causal interpretation. Granger causality is necessary but not sufficient for causation
Why it's wrong
Testing Granger causality on non-stationary variables leads to spurious results - finding causality that doesn't exist. Non-stationary series often trend together by chance, creating false Granger causality. Standard F-test distribution invalid with non-stationary data. Results are meaningless and misleading
The correction
ALWAYS test stationarity FIRST using ADF and KPSS tests on both X and Y. If non-stationary: (1) Difference both series until stationary, then test (interpretation changes to 'changes in X cause changes in Y'). (2) If cointegrated, use VECM framework (preserves long-run relationship). (3) Detrend if deterministic trend. Never proceed with Granger test on non-stationary levels unless cointegrated. Plot time series and ACF to visually check stationarity
Why it's wrong
Too few lags: omitted variable bias, fails to capture full dynamic relationship, residuals autocorrelated, test may falsely reject H₀ (Type I error). Too many lags: loss of power (fails to detect true causality - Type II error), overfitting, multicollinearity, imprecise estimates, wastes degrees of freedom. Wrong lag order invalidates test results
The correction
Use information criteria (AIC/BIC/HQIC) to select lag order p from VAR model; BIC more conservative (penalizes complexity). Check residual diagnostics: residuals must be white noise (Ljung-Box p>0.05) - if autocorrelated, increase p. Test robustness: run Granger test at multiple reasonable lags (e.g., p=1 to 12 for monthly); if significant at consecutive lags, relationship more credible. Domain knowledge: consider plausible lag length (e.g., advertising may affect sales after 2-4 weeks). Report lag sensitivity
Why it's wrong
Causality may be bidirectional (feedback loop: X→Y AND Y→X) or reverse (only Y→X). Testing only X→Y misses this. Finding X→Y doesn't rule out Y→X. May misinterpret as unidirectional when it's actually feedback. Incomplete analysis leads to wrong conclusions about direction of relationship
The correction
ALWAYS test both directions: X→Y and Y→X. Four possible outcomes: (1) X→Y only (unidirectional), (2) Y→X only (reverse causality), (3) X↔Y (bidirectional feedback), (4) No causality either direction. Report both test results. If bidirectional: indicates feedback loop (each variable helps predict the other). Use VAR framework which naturally models bidirectional relationships. Check instantaneous correlation of residuals (suggests omitted variable affecting both)
Why it's wrong
If third variable Z affects both X and Y, X may appear to Granger-cause Y when actually Z drives both (spurious causality). Classic example: ice cream sales (X) and drowning deaths (Y) - both caused by temperature (Z), not causal relationship between X and Y. Omitted variables are fundamental threat to validity; cannot be ruled out from data alone
The correction
Include theoretically relevant variables: test X→Y in multivariate VAR(X,Y,Z₁,Z₂,...). If X→Y relationship disappears when Z added, Z is confounder. Use domain knowledge to identify potential confounders. Test for instantaneous causality: high contemporaneous correlation of VAR residuals suggests omitted variable. Acknowledge limitation: 'X Granger-causes Y conditional on {X,Y} only; cannot rule out omitted Z'. Sensitivity analysis: how robust is X→Y to including additional variables? Theory crucial for identifying confounders
Why it's wrong
If VAR residuals show autocorrelation, model hasn't captured all dynamics - lag order insufficient or model misspecified. Autocorrelated residuals → biased standard errors → invalid F-test → unreliable p-values for Granger test. May falsely conclude causality exists (or doesn't) when conclusion is invalid due to model inadequacy
The correction
ALWAYS check VAR residual diagnostics: (1) Portmanteau test (Ljung-Box) on residuals: p>0.05 indicates white noise. (2) Plot ACF/PACF of residuals: no spikes outside confidence bands. (3) Visual inspection: residuals should appear random, no patterns. If autocorrelation detected: increase lag order p until residuals white noise, or consider nonlinear models. Never trust Granger test results if residuals autocorrelated - inference completely invalid
Why it's wrong
If causal relationship changes over time (structural break), full-sample Granger test misleading: may find causality that holds only in one regime, or fail to detect causality that exists in sub-periods. Policy changes, financial crises, technological shifts often cause parameter instability. Pooling unstable regimes gives meaningless average relationship
The correction
Test for structural breaks: Chow test at known break, sup-Wald (Andrews) for unknown break, rolling-window Granger tests. If break detected: (1) Include dummies in VAR for level/slope shifts. (2) Fit separate VARs before/after break; test causality in each regime (may differ). (3) Use time-varying parameter VAR or Markov-switching VAR. (4) Use only post-break data if break recent. Plot recursive Granger test statistics to assess stability. Report regime-specific results if breaks present
Why it's wrong
Granger test requires sufficient observations: estimating VAR(p) with k variables uses k×p parameters per equation. Small T relative to parameters: imprecise estimates, low power (fails to detect true causality - Type II error), unreliable information criteria, F-test distribution approximation poor. Severe problem when testing many lags with short series
The correction
Check sample size: need T ≥ 10×p×k as rough guide (T=obs, p=lags, k=variables). For reliable test: T≥100 minimum, prefer T≥200. If T small: (1) Reduce lag order (use BIC which penalizes complexity). (2) Bootstrap p-values instead of asymptotic F-distribution. (3) Penalty-likelihood ratio test (more conservative). (4) Acknowledge low power: 'Limited sample may fail to detect causality'. (5) Collect more data if possible. Never test many lags (large p) with T<100
Why it's wrong
Standard Granger test based on linear VAR - only detects linear predictive relationships. If X→Y relationship is nonlinear (e.g., threshold effects, state-dependent), linear test has no power to detect it (Type II error). May falsely conclude no causality when strong nonlinear causality exists. Misses important dynamics
The correction
Check linearity: scatter plots Y vs X_{t-k}, Ramsey RESET test, BDS test for nonlinear dependence in residuals. If nonlinearity suspected: (1) Transform variables (log, Box-Cox) to linearize. (2) Use nonlinear Granger causality tests: Baek-Brock, Hiemstra-Jones, transfer entropy. (3) Threshold VAR (relationship varies by regime). (4) Neural network Granger causality. (5) Report as limitation: 'Linear Granger test; nonlinear causality not ruled out'. Consider theoretical reasons for nonlinearity
Why it's wrong
Statistically significant Granger causality (p<0.05) doesn't mean X has large or economically meaningful impact on Y. With large sample size, tiny predictive effects can be statistically significant but practically irrelevant. Test only answers 'Is there any predictive relationship?' not 'Is it important?'. Effect size matters for decision-making
The correction
Report both statistical significance AND effect size: (1) R² increase: how much variance in Y is explained by X's lags? (2) Forecast error variance decomposition (FEVD): what % of Y's forecast error is due to X shocks? (3) Impulse response functions: magnitude of Y's response to X shock. (4) Out-of-sample forecast improvement: does adding X meaningfully improve Y forecasts (RMSE reduction)? Distinguish statistical from practical significance. X may Granger-cause Y but explain only 2% variance - causality exists but weak, may not matter for decisions
13Academic Lineage

References

Scholarly lineage and citation keys grounding the statistical framework.

We stand on the shoulders of giants. Honor the source of the method.
Academic Lineage
[1]
[2]
[3]
[4]
[5]
[6]
To lead is not always to cause. Granger tells you which variable holds the blueprints for the future of the other. Respect the precedence, but audit the causality with caution.
The Interpretive Rigor Directive
statminds · GrangerMind reference · v2.2 · updated 2026-01-1715 of 15 sections