NARDL Model
The Nonlinear Autoregressive Distributed Lag (NARDL) model extends the linear ARDL framework to capture asymmetric effects in cointegrating relationships. Based on the seminal work of Shin, Yu & Greenwood-Nimmo (2014).
Overview
Traditional cointegration analysis assumes symmetric adjustments to equilibrium. However, many economic relationships exhibit asymmetry—variables may respond differently to positive versus negative changes. The NARDL model addresses this by decomposing regressors into partial sums of positive and negative changes.
NARDL allows testing for both short-run and long-run asymmetric effects while maintaining the advantages of the ARDL bounds testing approach for variables with mixed integration orders I(0)/I(1).
Theoretical Framework
The NARDL approach rests on the concept of asymmetric cointegration. Consider a long-run relationship between \(y_t\) and \(x_t\):
where \(x_t^+\) and \(x_t^-\) are partial sum processes of positive and negative changes in \(x_t\):
Model Specification
The NARDL(p,q) error correction model is specified as:
Where:
- \(\rho\) is the error correction coefficient (expected to be negative)
- \(\theta^+, \theta^-\) capture the level (long-run) effects
- \(\pi_j^+, \pi_j^-\) capture the short-run dynamic effects
- Long-run multipliers: \(L^+ = -\theta^+ / \rho\) and \(L^- = -\theta^- / \rho\)
Basic Usage
Pythonfrom nardl_fourier import NARDL import pandas as pd # Load your data df = pd.read_csv('data.csv', index_col=0, parse_dates=True) # Fit NARDL model model = NARDL( data=df, depvar='coal', # Dependent variable exog_vars=['gdp', 'gdp2'], # Control variables (linear) decomp_vars=['oil_price'], # Variables to decompose (asymmetric) maxlag=4, # Maximum lag order ic='AIC', # Information criterion case=3 # Case for bounds test ) # View summary print(model.summary())
Parameters Explained
| Parameter | Type | Description |
|---|---|---|
data |
DataFrame | Pandas DataFrame with time series data |
depvar |
str | Name of the dependent variable column |
exog_vars |
list | List of exogenous/control variable names (enter linearly) |
decomp_vars |
list | Variables to decompose into positive/negative components |
maxlag |
int | Maximum lag order to consider (default: 4) |
ic |
str | Information criterion: 'AIC', 'BIC', or 'HQIC' |
case |
int | Bounds test case (1-5, default: 3) |
Partial Sum Decomposition
The core of NARDL is the partial sum decomposition. For any variable \(x\), we compute:
Pythonfrom nardl_fourier.utils import partial_sum_decomposition # Decompose a variable x_positive, x_negative = partial_sum_decomposition(df['oil_price']) # x_positive: cumulative sum of positive changes # x_negative: cumulative sum of negative changes print(f"Final positive sum: {x_positive[-1]:.2f}") print(f"Final negative sum: {x_negative[-1]:.2f}")
Long-Run Multipliers
Long-run multipliers measure the cumulative impact of a unit change in \(x\) on \(y\):
Python# Access long-run multipliers lr = model.long_run['oil_price'] print("Long-Run Multipliers:") print(f" L⁺ (positive): {lr['positive']['coefficient']:.4f}") print(f" Std.Error: {lr['positive']['std_error']:.4f}") print(f" p-value: {lr['positive']['p_value']:.4f}") print(f" L⁻ (negative): {lr['negative']['coefficient']:.4f}") print(f" Std.Error: {lr['negative']['std_error']:.4f}") print(f" p-value: {lr['negative']['p_value']:.4f}")
Short-Run Dynamics
Short-run coefficients capture the immediate dynamic adjustments:
Python# Error Correction Term ect = model.ect print(f"ECT (ρ): {ect['coefficient']:.4f}") print(f"Half-life: {ect['half_life']:.2f} periods") # Short-run coefficients table from nardl_fourier import ResultsTable table = ResultsTable(model) sr_df = table.short_run_table() print(sr_df)
The error correction term (ρ) should be negative and significant. If ρ ≥ 0 or |ρ| > 2, the model may be unstable.
Bounds Test for Cointegration
The PSS bounds test examines whether a long-run relationship exists:
Python# Access bounds test results bt = model.bounds_test print("PSS Bounds Test:") print(f" F-statistic: {bt['f_statistic']:.4f}") print(f" Critical Values (5%):") print(f" I(0): {bt['critical_values']['F']['5%']['I(0)']:.4f}") print(f" I(1): {bt['critical_values']['F']['5%']['I(1)']:.4f}") print(f" Decision: {bt['decision']['F_5%']}")
Interpretation
- F > I(1) bound: Reject H₀ → Evidence of cointegration
- F < I(0) bound: Fail to reject H₀ → No cointegration
- I(0) < F < I(1): Inconclusive (consider Bootstrap NARDL)
Testing for Asymmetry
Wald tests formally examine whether positive and negative effects are statistically different:
Python# Access Wald test results wald = model.wald['oil_price'] # Short-run asymmetry print("Short-Run Asymmetry Test:") print(f" H₀: π⁺ = π⁻ at all lags") print(f" F-statistic: {wald['short_run']['f_statistic']:.4f}") print(f" p-value: {wald['short_run']['p_value']:.4f}") print(f" Asymmetric: {wald['short_run']['asymmetric']}") # Long-run asymmetry print("\\nLong-Run Asymmetry Test:") print(f" H₀: L⁺ = L⁻") print(f" F-statistic: {wald['long_run']['f_statistic']:.4f}") print(f" p-value: {wald['long_run']['p_value']:.4f}") print(f" Asymmetric: {wald['long_run']['asymmetric']}")
Complete Example
Pythonimport pandas as pd import numpy as np from nardl_fourier import NARDL, ResultsTable, NARDLPlots # Create sample data np.random.seed(42) n = 200 df = pd.DataFrame({ 'coal': np.cumsum(np.random.randn(n)), 'oil_price': np.cumsum(np.random.randn(n)), 'gdp': np.cumsum(np.random.randn(n)) }) # Fit NARDL model model = NARDL( data=df, depvar='coal', exog_vars=['gdp'], decomp_vars=['oil_price'], maxlag=4, ic='AIC' ) # Print full summary print(model.summary()) # Generate publication tables table = ResultsTable(model) print("\\nLong-Run Multipliers:") print(table.long_run_table()) print("\\nDiagnostics:") print(table.diagnostics_table()) # Export to LaTeX table.to_latex('nardl_results.tex') # Create plots plots = NARDLPlots(model) plots.dynamic_multipliers('oil_price', horizon=20) plots.cusum()
For handling structural breaks, see Fourier NARDL. For eliminating inconclusive bounds test results, see Bootstrap NARDL.