Fourier-Johansen: Johansen-type Cointegration Tests with a Fourier Function
A Python implementation of the Johansen-Fourier cointegration test that extends the pioneering Johansen (1991) cointegration test to allow for structural breaks using Fourier functions.
Reference
Pascalau, R., Lee, J., Nazlioglu, S., Lu, Y. O. (2022). "Johansen-type Cointegration Tests with a Fourier Function". Journal of Time Series Analysis 43(5): 828-852. DOI: 10.1111/jtsa.12640
Author
Dr Merwan Roudane
📧 merwanroudane920@gmail.com
🔗 GitHub
Installation
pip install fourier-johansen
Or install from source:
git clone https://github.com/merwanroudane/fourierjohansen.git
cd fourierjohansen
pip install -e .
Features
- Standard Johansen Test: Original Johansen (1991) cointegration test
- Johansen-Fourier Test: Cointegration test with Fourier function for smooth structural breaks
- SC-VECM Test: Test for sharp/discrete breaks (Harris et al. 2016)
- SBC Model Selection: Automatic selection among Johansen, SC-VECM, and Fourier models
- Union of Rejections: Combined test strategy for unknown break types
- Publication-ready Output: LaTeX, Markdown, and formatted text tables
Quick Start
import numpy as np
from fourier_johansen import johansen_fourier
# Create sample data
np.random.seed(42)
T = 200
x1 = np.cumsum(np.random.randn(T))
x2 = x1 + np.random.randn(T) * 0.5 # Cointegrated with x1
X = np.column_stack([x1, x2])
# Run Johansen-Fourier test
result = johansen_fourier(X, model=3, k=2, f=1, option=1)
print(result)
Detailed Examples
1. Standard Johansen Test
from fourier_johansen import johansen
# Generate cointegrated data
np.random.seed(42)
T = 200
x1 = np.cumsum(np.random.randn(T))
x2 = x1 + np.random.randn(T) * 0.5
x3 = np.cumsum(np.random.randn(T))
X = np.column_stack([x1, x2, x3])
# Test with restricted constant model
result = johansen(X, model=2, k=2)
print(result)
# Model options:
# 1 = None (no deterministic terms)
# 2 = Restricted constant (RC) - recommended
# 3 = Unrestricted constant
# 4 = Restricted trend (RT)
# 5 = Unrestricted trend
Output:
=================================================================
Johansen Cointegration Test Results
=================================================================
# Variables : 3
Model : Restricted Constant (RC)
VAR Lags : 2
VECM Lags : 1
Observations: 197
-----------------------------------------------------------------
Rank Eigenvalue Lambda-max Trace CV(5%)
-----------------------------------------------------------------
0 -4434.7013
1 0.052138 10.5234 21.4789* 35.070
2 0.033045 6.5678 10.8347 20.160
3 0.021234 4.2669 4.0931 9.140
=================================================================
Note: * indicates rejection of null at 5% level
2. Johansen-Fourier Test (Main Feature)
from fourier_johansen import johansen_fourier
# Create data with smooth structural break
np.random.seed(42)
T = 200
t = np.arange(T)
break_term = 5 * np.sin(2 * np.pi * t / T) # Smooth Fourier break
x1 = np.cumsum(np.random.randn(T)) + break_term
x2 = x1 + np.random.randn(T) * 0.5 + break_term
X = np.column_stack([x1, x2])
# Single frequency
result = johansen_fourier(X, model=3, k=2, f=1, option=1)
print(result)
# Cumulative frequencies (recommended when break form is unknown)
result_cum = johansen_fourier(X, model=3, k=2, f=2, option=2)
print(result_cum)
# Model options:
# 1 = Constant (unrestricted)
# 2 = Trend (unrestricted)
# 3 = Restricted Constant (RC) - most common
# 4 = Restricted Trend (RT)
# Get cointegration rank
rank = result.get_cointegration_rank()
print(f"Estimated cointegration rank: {rank}")
Output:
===========================================================================
Johansen-Fourier Cointegration Test Results
===========================================================================
# Variables : 2
Model : Restricted Constant (RC)
Frequency : 1 (Single)
VAR Lags : 2
VECM Lags : 1
Observations: 197
---------------------------------------------------------------------------
Rank Fourier Fourier CV(5%) CV(5%) Log-Lik
Lambda Trace Lambda Trace
---------------------------------------------------------------------------
0 -856.234
1 28.591* 57.342* 22.195 48.930 -842.123
2 20.404* 28.751* 14.742 29.586 -831.456
===========================================================================
Note: * indicates rejection of null at 5% level
3. SC-VECM Test (For Sharp Breaks)
from fourier_johansen import sc_vecm
# Create data with sharp break
np.random.seed(42)
T = 200
break_point = 100
x1 = np.cumsum(np.random.randn(T))
x1[break_point:] += 5 # Sharp level shift
x2 = x1 + np.random.randn(T) * 0.5
X = np.column_stack([x1, x2])
# Test rank 0
result = sc_vecm(r=0, y=X, max_lag=4, lambda_L=0.1)
print(result)
Output:
======================================================================
SC-VECM Cointegration Test Results
======================================================================
# Variables : 2
Observations : 200
Rank Tested : 0
Selected Model: Break
Break Location: 98 (fraction: 0.490)
----------------------------------------------------------------------
No Break With Break
----------------------------------------------------------------------
Trace Statistic 18.2345 55.6789
SBC 1234.567 1198.234
Optimal Lag 2 2
CV (5%) 23.453 37.630
======================================================================
Decision at 5% level: Reject H0
4. SBC Model Selection
from fourier_johansen import sbc_test
# Run SBC test - automatically selects best model
result = sbc_test(r=0, y=X, max_lag=4, lambda_L=0.1, f_max=3, option=2)
print(result)
Output:
===========================================================================
SBC Model Selection Test Results
===========================================================================
# Variables : 2
Observations : 200
Rank Tested : 0
Selected Model: Fourier
---------------------------------------------------------------------------
Johansen SC-VECM Fourier
---------------------------------------------------------------------------
Trace Statistic 18.2345 55.6789 62.3456
SBC 1234.5670 1198.2340 1156.7890
===========================================================================
Selected: FOURIER
Trace from selected model: 62.3456
Critical value (5%): 48.9300
Decision: Reject H0 (cointegration detected)
5. Union of Rejections Test
from fourier_johansen import union_test
# Combine Fourier and SC-VECM tests
result = union_test(X, model=3, k=2, f=2, option=2, lambda_loc=0.5, r=0)
print(result)
Output:
======================================================================
Union of Rejections Test Results
======================================================================
# Variables : 2
Observations : 200
Rank Tested : 0
Scale Factor : 1.0720
----------------------------------------------------------------------
Fourier SC-VECM
----------------------------------------------------------------------
Trace Statistic 62.3456 55.6789
CV (5%, original) 48.9300 37.6300
CV (5%, scaled) 52.4528 40.3394
Individual Reject Yes Yes
======================================================================
UNION TEST RESULT: REJECT H0
(Rejects if either scaled test rejects)
6. Export to LaTeX for Publications
from fourier_johansen import johansen_fourier, to_latex, to_markdown
result = johansen_fourier(X, model=3, k=2, f=1, option=1)
# Export as LaTeX table
latex_table = to_latex(result)
print(latex_table)
# Export as Markdown
markdown_table = to_markdown(result)
print(markdown_table)
LaTeX Output:
\begin{table}[htbp]
\centering
\caption{Johansen-Fourier Cointegration Test Results}
\begin{tabular}{ccccc}
\hline\hline
Rank & Eigenvalue & $\lambda_{max}$ & Trace & CV (5\%) \\
\hline
1 & 0.134567 & 28.5914$^{*}$ & 57.3421$^{*}$ & 48.930 \\
2 & 0.098765 & 20.4043$^{*}$ & 28.7507 & 29.586 \\
\hline\hline
\end{tabular}
\begin{tablenotes}
\small
\item Note: $^{*}$ indicates rejection of the null hypothesis at the 5\% significance level.
\item Fourier frequency: 1 (single).
\end{tablenotes}
\end{table}
7. Complete Analysis Workflow
import numpy as np
import pandas as pd
from fourier_johansen import (
johansen, johansen_fourier, sc_vecm, sbc_test, union_test,
to_latex, to_markdown
)
# Load your data
# data = pd.read_csv('your_data.csv')
# X = data[['var1', 'var2', 'var3']].values
# For demonstration, create synthetic data
np.random.seed(42)
T = 200
t = np.arange(T)
break_term = 3 * np.sin(2 * np.pi * t / T)
x1 = np.cumsum(np.random.randn(T)) + break_term
x2 = x1 + np.random.randn(T) * 0.5 + break_term * 0.8
x3 = np.cumsum(np.random.randn(T))
X = np.column_stack([x1, x2, x3])
print("=" * 60)
print(" COMPLETE COINTEGRATION ANALYSIS")
print("=" * 60)
# Step 1: Standard Johansen (baseline)
print("\n[1] Standard Johansen Test")
print("-" * 40)
joh_result = johansen(X, model=2, k=2)
print(joh_result)
# Step 2: Johansen-Fourier with single frequency
print("\n[2] Johansen-Fourier Test (Single Frequency)")
print("-" * 40)
jf_result = johansen_fourier(X, model=3, k=2, f=1, option=1)
print(jf_result)
# Step 3: Johansen-Fourier with cumulative frequencies
print("\n[3] Johansen-Fourier Test (Cumulative Frequencies)")
print("-" * 40)
jf_cum_result = johansen_fourier(X, model=3, k=2, f=2, option=2)
print(jf_cum_result)
# Step 4: SC-VECM for comparison
print("\n[4] SC-VECM Test")
print("-" * 40)
scvecm_result = sc_vecm(r=0, y=X, max_lag=4, lambda_L=0.1)
print(scvecm_result)
# Step 5: SBC model selection
print("\n[5] SBC Model Selection")
print("-" * 40)
sbc_result = sbc_test(r=0, y=X, max_lag=4, lambda_L=0.1, f_max=3)
print(sbc_result)
# Step 6: Union test
print("\n[6] Union of Rejections Test")
print("-" * 40)
union_result = union_test(X, model=3, k=2, f=2, option=2, r=0)
print(union_result)
# Summary
print("\n" + "=" * 60)
print(" SUMMARY OF RESULTS")
print("=" * 60)
print(f"Standard Johansen rank: {sum(joh_result.trace > joh_result.cv_trace)}")
print(f"Johansen-Fourier rank: {jf_result.get_cointegration_rank()}")
print(f"SBC selected model: {sbc_result.selected_model}")
print(f"Union test rejects H0: {union_result.reject_h0}")
Model Specifications
Johansen-Fourier Models
| Model | Description | When to Use |
|---|---|---|
| 1 | Constant (unrestricted) | General case with constant in VAR |
| 2 | Trend (unrestricted) | Data with linear trends |
| 3 | Restricted Constant (RC) | Most common - constant in cointegrating eq. only |
| 4 | Restricted Trend (RT) | Trend in cointegrating relationship |
Fourier Frequency Options
| Option | Description | Recommendation |
|---|---|---|
option=1 |
Single frequency | When break type is known |
option=2 |
Cumulative frequencies | Recommended when break type is unknown |
Frequency Selection
- Use
f=1, 2, or 3for most applications - Higher frequencies may lead to overfitting
- Use SBC to select optimal frequency automatically
Critical Values
All critical values are from:
- Johansen (1991) for standard test
- Pascalau et al. (2022) Online Appendix for Fourier tests
- Harris et al. (2016) for SC-VECM
Citation
If you use this library in your research, please cite:
@article{pascalau2022johansen,
title={Johansen-type cointegration tests with a Fourier function},
author={Pascalau, Razvan and Lee, Junsoo and Nazlioglu, Saban and Lu, Yan Olivia},
journal={Journal of Time Series Analysis},
volume={43},
number={5},
pages={828--852},
year={2022},
publisher={Wiley},
doi={10.1111/jtsa.12640}
}
License
MIT License
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Changelog
Version 0.0.1 (2026-01-21)
- Initial release
- Implemented Johansen-Fourier cointegration test
- Added SC-VECM test for sharp breaks
- Added SBC model selection
- Added Union of rejections strategy
- Publication-ready output formatting
Metadata
Release files for fourier-johansen 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| fourier_johansen-0.0.1.tar.gz | 31.9 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fourier_johansen-0.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 66.8 kB
Release files / fourier_johansen-0.0.1.tar.gz
| Download URL | fourier_johansen-0.0.1.tar.gz |
|---|---|
| Size | 31.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
41f2774f55f0f73dde3bd4c0ddc401cd44554c3ea4ba155fcc837c70d5ab0694
|
|
BLAKE2b-256 checksum How to use checksums |
cec625def0f161d86ba1563de3487681fae6361c029d4a528d76a33f0c6f63df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Release files / fourier_johansen-0.0.1-py3-none-any.whl
| Download URL | fourier_johansen-0.0.1-py3-none-any.whl |
|---|---|
| Size | 34.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5accbe615589cb9a58651eed8c8e5b2a4138f517efbee3c1b647a96664b892e1
|
|
BLAKE2b-256 checksum How to use checksums |
b0cfed17aa1ef91f30fda573071aec95c424b12bf811d1f05443b18c2128a7f4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.7
|
Security for fourier-johansen 0.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.