Fourier NARDL Model

The Fourier NARDL extends the standard NARDL framework by incorporating Fourier trigonometric terms to capture smooth structural breaks without specifying break dates. Based on Zaghdoudi, Maktouf & Ochi (2023).

Overview

Traditional structural break tests require specifying break dates a priori or rely on multiple regime-switching models. The Fourier approach offers an elegant alternative by using trigonometric functions to approximate unknown structural changes smoothly.

🌊
Why Fourier?

A Fourier expansion can approximate any bounded periodic function, making it ideal for modeling gradual structural changes, policy shifts, or slowly evolving institutional factors.

Theoretical Background

The Fourier approach to structural breaks was pioneered by Becker, Enders & Lee (2006) and later applied to cointegration analysis. The key insight is that any smooth function can be approximated by a Fourier series:

\[\alpha(t) = \alpha_0 + \sum_{k=1}^{n} \left[ \gamma_k \sin\left(\frac{2\pi k t}{T}\right) + \delta_k \cos\left(\frac{2\pi k t}{T}\right) \right]\]

In practice, a single frequency (k=1, 2, or 3) is typically sufficient to capture most structural variations, keeping the model parsimonious.

Fourier Approximation

For a sample of size T, we construct trigonometric terms at frequency \(k\):

\[\text{sin}_k(t) = \sin\left(\frac{2\pi k t}{T}\right)\] \[\text{cos}_k(t) = \cos\left(\frac{2\pi k t}{T}\right)\]

The frequency \(k\) controls the periodicity of the breaks:

Model Specification

The Fourier NARDL model augments the standard NARDL with Fourier terms:

\[\Delta y_t = \rho y_{t-1} + \theta^+ x_{t-1}^+ + \theta^- x_{t-1}^- + \gamma \sin\left(\frac{2\pi k t}{T}\right) + \delta \cos\left(\frac{2\pi k t}{T}\right) + \ldots + \varepsilon_t\]

The Fourier terms (\(\gamma, \delta\)) capture time-varying intercepts (and potentially time-varying coefficients) that would otherwise bias the estimates if structural breaks are ignored.

Basic Usage

Python
from nardl_fourier import FourierNARDL # Fit Fourier NARDL model model = FourierNARDL( data=df, depvar='coal', exog_vars=['gdp'], decomp_vars=['oil_price'], maxlag=4, max_freq=3, # Maximum Fourier frequency to try ic='AIC', case=3 ) # View optimal frequency print(f"Optimal Fourier frequency: k* = {model.best_freq}") # Full summary print(model.summary())

Additional Parameters

Parameter Type Description
max_freq int Maximum Fourier frequency to test (default: 3)

All other parameters are inherited from the standard NARDL class.

Optimal Frequency Selection

The optimal frequency \(k^*\) is selected automatically using the information criterion specified (AIC, BIC, or HQIC). The model estimates all frequencies from 1 to max_freq and selects the one that minimizes the criterion.

Python
# Access optimal frequency and IC value print(f"Optimal frequency: k* = {model.best_freq}") print(f"Best {model.ic}: {model.best_ic:.4f}") # The Fourier terms in the model print("\\nFourier Terms Coefficients:") for name in model.model.params.index: if 'sin' in name or 'cos' in name: print(f" {name}: {model.model.params[name]:.6f}")

Fourier Terms Significance Test

To verify that the Fourier terms add explanatory power, we test the joint significance of the sine and cosine terms:

\[H_0: \gamma = \delta = 0 \quad \text{(no structural breaks)}\]
Python
# Access Fourier significance test if hasattr(model, 'fourier_test') and model.fourier_test: ft = model.fourier_test print("Fourier Terms Joint Significance Test:") print(f" H₀: γ = δ = 0") print(f" F-statistic: {ft['f_statistic']:.4f}") print(f" p-value: {ft['p_value']:.4f}") print(f" Significant: {ft['significant']}")
⚠️
Interpretation

If the Fourier terms are not significant (high p-value), structural breaks may not be present, and the standard NARDL may be sufficient.

Complete Example

Python
import pandas as pd import numpy as np from nardl_fourier import FourierNARDL, ResultsTable # Simulate data with structural break np.random.seed(42) n = 200 t = np.arange(n) # Create a smooth structural shift shift = 2 * np.sin(2 * np.pi * t / n) df = pd.DataFrame({ 'coal': shift + np.cumsum(np.random.randn(n) * 0.5), 'oil_price': np.cumsum(np.random.randn(n)), 'gdp': np.cumsum(np.random.randn(n)) }) # Fit Fourier NARDL model = FourierNARDL( data=df, depvar='coal', exog_vars=['gdp'], decomp_vars=['oil_price'], maxlag=4, max_freq=3, ic='AIC' ) # Summary print(model.summary()) # Compare with standard NARDL from nardl_fourier import NARDL standard_model = NARDL( data=df, depvar='coal', exog_vars=['gdp'], decomp_vars=['oil_price'], maxlag=4, ic='AIC' ) print("\\nModel Comparison:") print(f" Standard NARDL R²: {standard_model.model.rsquared:.4f}") print(f" Fourier NARDL R²: {model.model.rsquared:.4f}") print(f" Improvement: {(model.model.rsquared - standard_model.model.rsquared)*100:.2f}%")
🚀
Next: Bootstrap Tests

To eliminate the inconclusive zone problem in bounds tests, check out the Bootstrap Fourier NARDL which combines Fourier approximation with bootstrap-based inference.