fjohansen 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fjohansen-0.1.0/CHANGELOG.md +33 -0
- fjohansen-0.1.0/LICENSE +21 -0
- fjohansen-0.1.0/MANIFEST.in +17 -0
- fjohansen-0.1.0/PKG-INFO +571 -0
- fjohansen-0.1.0/README.md +535 -0
- fjohansen-0.1.0/examples/example_limit_distributions.py +38 -0
- fjohansen-0.1.0/examples/example_section5_monte_carlo.py +68 -0
- fjohansen-0.1.0/examples/example_section6_jgb.py +58 -0
- fjohansen-0.1.0/fjohansen/__init__.py +127 -0
- fjohansen-0.1.0/fjohansen/core.py +345 -0
- fjohansen-0.1.0/fjohansen/critical_values.py +251 -0
- fjohansen-0.1.0/fjohansen/data.py +188 -0
- fjohansen-0.1.0/fjohansen/frequency_select.py +395 -0
- fjohansen-0.1.0/fjohansen/models.py +194 -0
- fjohansen-0.1.0/fjohansen/plotting.py +404 -0
- fjohansen-0.1.0/fjohansen/simulation.py +261 -0
- fjohansen-0.1.0/fjohansen/tables.py +237 -0
- fjohansen-0.1.0/fjohansen/utils.py +104 -0
- fjohansen-0.1.0/fjohansen.egg-info/SOURCES.txt +19 -0
- fjohansen-0.1.0/pyproject.toml +62 -0
- fjohansen-0.1.0/setup.cfg +4 -0
- fjohansen-0.1.0/tests/test_basic.py +118 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to **fjohansen** will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2025-05-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial public release.
|
|
12
|
+
- Johansen cointegration test with Fourier-type smooth nonlinear trends
|
|
13
|
+
restricted to cointegrating relations (Kurita & Shintani 2025).
|
|
14
|
+
- Six model variants: `CNR`, `LNR`, `CNU`, `LNU`, `constant`, `linear`.
|
|
15
|
+
- Bundled Table B1 critical values (CNR) and pre-computed moment tables
|
|
16
|
+
for the other model variants.
|
|
17
|
+
- Vectorised limit-distribution simulator with persistent disk cache.
|
|
18
|
+
- Gamma-approximation p-values (Doornik 1998).
|
|
19
|
+
- Perron-Shintani-Yabu (2021) FGLS Wald test with Prais-Winsten
|
|
20
|
+
transformation, Roy-Fuller bias correction and super-efficient AR(1)
|
|
21
|
+
estimator.
|
|
22
|
+
- General-to-specific frequency selection for univariate and multivariate
|
|
23
|
+
panels.
|
|
24
|
+
- Publication-quality output: ASCII summary, LaTeX (booktabs), HTML, and
|
|
25
|
+
Rich-coloured terminal tables.
|
|
26
|
+
- Paper-style figures: eigenvalues, long-run relations, recursive
|
|
27
|
+
rejection curves, limit-distribution densities, residual diagnostics,
|
|
28
|
+
risk-premium decomposition.
|
|
29
|
+
- Section 5 DGPs (NF-DGP-1..4, F-DGP-1, F-DGP-2) and a synthetic JGB-yield
|
|
30
|
+
surrogate.
|
|
31
|
+
- Three runnable example scripts reproducing Sections 5, 6 and the
|
|
32
|
+
limit-distribution figures of the paper.
|
|
33
|
+
- Test suite (13 tests).
|
fjohansen-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Merwan Roudane
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
include README.md
|
|
2
|
+
include LICENSE
|
|
3
|
+
include pyproject.toml
|
|
4
|
+
include CHANGELOG.md
|
|
5
|
+
recursive-include fjohansen *.py
|
|
6
|
+
recursive-include examples *.py
|
|
7
|
+
recursive-include tests *.py
|
|
8
|
+
recursive-exclude * __pycache__
|
|
9
|
+
recursive-exclude * *.py[cod]
|
|
10
|
+
recursive-exclude * *.so
|
|
11
|
+
recursive-exclude * .DS_Store
|
|
12
|
+
prune .git
|
|
13
|
+
prune .github
|
|
14
|
+
prune build
|
|
15
|
+
prune dist
|
|
16
|
+
prune *.egg-info
|
|
17
|
+
prune .pytest_cache
|
fjohansen-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: fjohansen
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Johansen cointegration test with Fourier-type smooth nonlinear trends in cointegrating relations (Kurita & Shintani, 2025).
|
|
5
|
+
Author: Merwan Roudane
|
|
6
|
+
Maintainer: Merwan Roudane
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/merwanroudane/fjohansen
|
|
9
|
+
Project-URL: Repository, https://github.com/merwanroudane/fjohansen
|
|
10
|
+
Project-URL: Issues, https://github.com/merwanroudane/fjohansen/issues
|
|
11
|
+
Keywords: cointegration,Johansen,Fourier,nonlinear trend,VAR,econometrics,time series,unit root
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: numpy>=1.22
|
|
25
|
+
Requires-Dist: scipy>=1.8
|
|
26
|
+
Requires-Dist: pandas>=1.4
|
|
27
|
+
Requires-Dist: matplotlib>=3.5
|
|
28
|
+
Requires-Dist: statsmodels>=0.13
|
|
29
|
+
Provides-Extra: pretty
|
|
30
|
+
Requires-Dist: rich>=13.0; extra == "pretty"
|
|
31
|
+
Requires-Dist: tabulate>=0.9; extra == "pretty"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
34
|
+
Requires-Dist: black; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff; extra == "dev"
|
|
36
|
+
|
|
37
|
+
# fjohansen
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/fjohansen/)
|
|
40
|
+
[](https://pypi.org/project/fjohansen/)
|
|
41
|
+
[](https://opensource.org/licenses/MIT)
|
|
42
|
+
[](https://github.com/merwanroudane/fjohansen)
|
|
43
|
+
|
|
44
|
+
**`fjohansen`** is a Python implementation of the **Johansen cointegration test
|
|
45
|
+
with Fourier-type smooth nonlinear deterministic trends restricted to
|
|
46
|
+
cointegrating relations**, as developed in
|
|
47
|
+
|
|
48
|
+
> **Kurita, T. & Shintani, M. (2025)**. *Johansen test with Fourier-type
|
|
49
|
+
> smooth nonlinear trends in cointegrating relations.* **Econometric Reviews**,
|
|
50
|
+
> 44(10), 1589–1616. [DOI: 10.1080/07474938.2025.2530640](https://doi.org/10.1080/07474938.2025.2530640)
|
|
51
|
+
|
|
52
|
+
The library also bundles the FGLS Wald test of
|
|
53
|
+
|
|
54
|
+
> **Perron, P., Shintani, M. & Yabu, T. (2017, 2021)**. *Testing for flexible
|
|
55
|
+
> nonlinear trends with an integrated or stationary noise component.*
|
|
56
|
+
|
|
57
|
+
used by Kurita & Shintani as a frequency-selection pre-step.
|
|
58
|
+
|
|
59
|
+
> **Author:** [Merwan Roudane](https://github.com/merwanroudane)
|
|
60
|
+
> **Repository:** <https://github.com/merwanroudane/fjohansen>
|
|
61
|
+
> **PyPI:** <https://pypi.org/project/fjohansen/>
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 📋 Table of contents
|
|
66
|
+
|
|
67
|
+
1. [Features](#-features)
|
|
68
|
+
2. [Installation](#-installation)
|
|
69
|
+
3. [Quick start](#-quick-start)
|
|
70
|
+
4. [Mathematical background](#-mathematical-background)
|
|
71
|
+
5. [Model variants](#-model-variants)
|
|
72
|
+
6. [Full API reference](#-full-api-reference)
|
|
73
|
+
- [Core test class](#core-test-class-fjjohansenfourier)
|
|
74
|
+
- [Frequency selection](#frequency-selection-perron-shintani-yabu-2021)
|
|
75
|
+
- [Critical values & p-values](#critical-values--p-values)
|
|
76
|
+
- [Limit-distribution simulator](#limit-distribution-simulator)
|
|
77
|
+
- [Tables and exports](#tables-and-exports)
|
|
78
|
+
- [Plotting](#plotting)
|
|
79
|
+
- [Data-generating processes](#data-generating-processes)
|
|
80
|
+
- [Low-level utilities](#low-level-utilities)
|
|
81
|
+
7. [Reproducing the paper](#-reproducing-the-paper)
|
|
82
|
+
8. [Performance & caching](#-performance--caching)
|
|
83
|
+
9. [Citation](#-citation)
|
|
84
|
+
10. [License](#-license)
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ✨ Features
|
|
89
|
+
|
|
90
|
+
* **Six model variants** — `CNR`, `LNR`, `CNU`, `LNU` (Fourier in / out of the
|
|
91
|
+
cointegrating space) plus standard constant- and linear-trend restricted
|
|
92
|
+
Johansen models for direct comparison.
|
|
93
|
+
* **Trace statistics** with hard-coded critical values from Table B1 of the
|
|
94
|
+
paper and Gamma-approximation *p-values* (Doornik 1998).
|
|
95
|
+
* **Sequential cointegrating-rank selection** following Johansen's standard
|
|
96
|
+
procedure.
|
|
97
|
+
* **PSY (2021) FGLS Wald test** with Prais–Winsten transformation, Roy–Fuller
|
|
98
|
+
bias correction and super-efficient AR(1) estimator + general-to-specific
|
|
99
|
+
algorithm for picking the number of Fourier frequencies.
|
|
100
|
+
* **Publication-quality output**
|
|
101
|
+
* ASCII table summary (`.summary()`)
|
|
102
|
+
* LaTeX booktabs export (`.to_latex()`)
|
|
103
|
+
* Styled HTML export (`.to_html()`)
|
|
104
|
+
* Colourised terminal output (`rich_print()`)
|
|
105
|
+
* Paper-style figures (eigenvalues, long-run relations, recursive
|
|
106
|
+
rejection curves, limit-distribution densities, residual diagnostics,
|
|
107
|
+
risk-premium decomposition).
|
|
108
|
+
* **All paper DGPs** (NF-DGP-1..4, F-DGP-1, F-DGP-2) plus a synthetic JGB-yield
|
|
109
|
+
surrogate for replication.
|
|
110
|
+
* **Fast** — vectorised simulator, bundled moment tables for every common
|
|
111
|
+
cell, persistent disk cache. Cold-start fit on the 6-variable JGB panel
|
|
112
|
+
is < 0.05 s.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 📦 Installation
|
|
117
|
+
|
|
118
|
+
### From PyPI (recommended)
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pip install fjohansen
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
with optional pretty-printing extras:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
pip install "fjohansen[pretty]" # rich + tabulate
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### From GitHub (latest dev)
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
git clone https://github.com/merwanroudane/fjohansen.git
|
|
134
|
+
cd fjohansen
|
|
135
|
+
pip install -e .
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Dependencies
|
|
139
|
+
|
|
140
|
+
* Python ≥ 3.9
|
|
141
|
+
* `numpy`, `scipy`, `pandas`, `matplotlib`, `statsmodels`
|
|
142
|
+
* optional: `rich`, `tabulate` (for `pretty` console output)
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## 🚀 Quick start
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
import fjohansen as fj
|
|
150
|
+
|
|
151
|
+
# 1. Load (or simulate) a multivariate I(1) panel
|
|
152
|
+
data = fj.sample_jgb_data(T=108)
|
|
153
|
+
|
|
154
|
+
# 2. Pick the number of Fourier frequencies (PSY 2021)
|
|
155
|
+
sel = fj.select_frequencies(data, n_max=5, p_d=1, sig_level=0.10)
|
|
156
|
+
print(f"Selected n = {sel.n_selected}")
|
|
157
|
+
|
|
158
|
+
# 3. Run the Johansen-Fourier (CNR) test
|
|
159
|
+
res = fj.JohansenFourier(data, k=3, n=sel.n_selected, model="CNR").fit()
|
|
160
|
+
|
|
161
|
+
# 4. Output
|
|
162
|
+
print(res.summary()) # Table-3 style ASCII
|
|
163
|
+
fj.rich_print(res) # coloured terminal
|
|
164
|
+
open("out.tex", "w").write(res.to_latex())
|
|
165
|
+
open("out.html","w").write(res.to_html())
|
|
166
|
+
|
|
167
|
+
# 5. Figures
|
|
168
|
+
res.plot_eigenvalues()
|
|
169
|
+
res.plot_long_run()
|
|
170
|
+
res.plot_residual_diagnostics()
|
|
171
|
+
res.plot_risk_premium()
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## 🔬 Mathematical background
|
|
177
|
+
|
|
178
|
+
For a *p*-dimensional I(1) process $X_t$ the package estimates
|
|
179
|
+
|
|
180
|
+
$$
|
|
181
|
+
\Delta X_t \;=\; \alpha\!\left(\beta' X_{t-1} + \delta' F_{t,T} + \gamma'\right)
|
|
182
|
+
\;+\;\sum_{i=1}^{k-1}\Gamma_i\,\Delta X_{t-i}\;+\;\Phi D_t\;+\;\varepsilon_t,
|
|
183
|
+
$$
|
|
184
|
+
|
|
185
|
+
where the Fourier basis
|
|
186
|
+
|
|
187
|
+
$$
|
|
188
|
+
F_{t,T} = \bigl[\sin\tfrac{2\pi t}{T},\ \cos\tfrac{2\pi t}{T},\ldots,
|
|
189
|
+
\sin\tfrac{2\pi n t}{T},\ \cos\tfrac{2\pi n t}{T}\bigr]'
|
|
190
|
+
$$
|
|
191
|
+
|
|
192
|
+
captures slow, smooth nonlinear movement *inside* the cointegrating
|
|
193
|
+
relations. The test statistic
|
|
194
|
+
|
|
195
|
+
$$
|
|
196
|
+
-2\log\!\mathit{LR}(r\mid p; n) \;=\; -T\!\!\sum_{i=r+1}^{p}\log\bigl(1-\hat\lambda_i\bigr)
|
|
197
|
+
$$
|
|
198
|
+
|
|
199
|
+
obtained from the Gaussian reduced-rank regression follows the limiting
|
|
200
|
+
distribution
|
|
201
|
+
|
|
202
|
+
$$
|
|
203
|
+
\operatorname{tr}\!\left\{\int_0^1\!\mathrm{d}B_u\,G_u'
|
|
204
|
+
\!\left(\!\int_0^1 G_u G_u'\,\mathrm{d}u\!\right)^{-1}\!\!\int_0^1 G_u\,\mathrm{d}B_u'\right\}
|
|
205
|
+
$$
|
|
206
|
+
|
|
207
|
+
(Proposition 3.1 of Kurita & Shintani, 2025). `fjohansen` simulates this
|
|
208
|
+
distribution, fits a Gamma matching its two leading moments (Doornik 1998),
|
|
209
|
+
and returns the resulting *p*-values.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 🧩 Model variants
|
|
214
|
+
|
|
215
|
+
| Code | $Z_1$ (cointegrating space) | $Z_2$ (unrestricted) |
|
|
216
|
+
|-------------|-------------------------------------------------------|------------------------------------------------|
|
|
217
|
+
| `CNR` | $(X_{t-1}',\ F_{t,T}',\ 1)'$ | $\Delta X$-lags only |
|
|
218
|
+
| `LNR` | $(X_{t-1}',\ F_{t,T}',\ t)'$ | $\Delta X$-lags + constant |
|
|
219
|
+
| `CNU` | $(X_{t-1}',\ 1)'$ | $\Delta X$-lags + $F_{t,T}$ |
|
|
220
|
+
| `LNU` | $(X_{t-1}',\ t)'$ | $\Delta X$-lags + constant + $F_{t,T}$ |
|
|
221
|
+
| `constant` | $(X_{t-1}',\ 1)'$ | $\Delta X$-lags only |
|
|
222
|
+
| `linear` | $(X_{t-1}',\ t)'$ | $\Delta X$-lags + constant |
|
|
223
|
+
|
|
224
|
+
* `CNR` / `LNR` correspond to eqs. (6)–(7) of the paper (the main contribution).
|
|
225
|
+
* `CNU` / `LNU` correspond to Section 4 (Fourier unrestricted, no cointegration possible inside it).
|
|
226
|
+
* `constant` / `linear` are the classical Johansen specifications.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 📚 Full API reference
|
|
231
|
+
|
|
232
|
+
### Core test class: `fj.JohansenFourier`
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
class JohansenFourier(
|
|
236
|
+
data, # array-like or DataFrame, shape (T, p)
|
|
237
|
+
k: int, # VAR order in levels
|
|
238
|
+
n: int = 1, # number of Fourier frequencies (0 = standard Johansen)
|
|
239
|
+
model: str = "CNR", # one of CNR | LNR | CNU | LNU | constant | linear
|
|
240
|
+
)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Methods:
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
.fit(
|
|
247
|
+
sig_level: float = 0.05,
|
|
248
|
+
select_rank: bool = True,
|
|
249
|
+
compute_pvalues: bool = True,
|
|
250
|
+
compute_estimates: bool = True,
|
|
251
|
+
n_sims: int = 5_000,
|
|
252
|
+
) -> JohansenFourierResults
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`JohansenFourierResults` exposes:
|
|
256
|
+
|
|
257
|
+
| Attribute | Type | Description |
|
|
258
|
+
|--------------------------|-----------------|---------------------------------------------------------|
|
|
259
|
+
| `eigenvalues` | `ndarray` | Generalised eigenvalues $\hat\lambda_i$, descending. |
|
|
260
|
+
| `trace_stats` | `DataFrame` | Per-null trace, $\lambda$, p-value, 5% and 1% c.v. |
|
|
261
|
+
| `selected_rank` | `int` | Rank chosen by the sequential 5%-rule. |
|
|
262
|
+
| `alpha`, `beta` | `ndarray` | Loading and cointegrating-vector matrices. |
|
|
263
|
+
| `delta` | `ndarray` | Coefficients on the Fourier block ($2n \times r$). |
|
|
264
|
+
| `gamma_const`, `gamma_trend` | `ndarray` | Restricted intercept / slope inside $\beta$-space. |
|
|
265
|
+
| `Gamma` | `list[ndarray]` | Coefficients on $\Delta X_{t-i}$. |
|
|
266
|
+
| `Phi_unrestricted` | `ndarray` | Coefficients on the $Z_2$ block. |
|
|
267
|
+
| `Sigma` | `ndarray` | Residual covariance matrix. |
|
|
268
|
+
| `residuals` | `ndarray` | $T_{\text{eff}}\times p$ residuals. |
|
|
269
|
+
| `fitted_long_run` | `ndarray` | Estimated cointegrating relations $\beta'X+\delta'F+\gamma$. |
|
|
270
|
+
| `t_index` | `ndarray` | Effective-sample time index. |
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
.summary(sig_level=0.05) -> str # ASCII (Table-3 style)
|
|
274
|
+
.to_latex(caption=None, label=None) -> str
|
|
275
|
+
.to_html(caption=None) -> str
|
|
276
|
+
.plot_eigenvalues()
|
|
277
|
+
.plot_long_run()
|
|
278
|
+
.plot_residual_diagnostics()
|
|
279
|
+
.plot_risk_premium(index=None, titles=None)
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
#### Example
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
res = fj.JohansenFourier(data, k=3, n=5, model="CNR").fit(sig_level=0.05)
|
|
286
|
+
print(res.summary())
|
|
287
|
+
|
|
288
|
+
# r = res.selected_rank -- number of cointegrating relations
|
|
289
|
+
# beta = res.beta -- (p, r) cointegrating vectors
|
|
290
|
+
# delta = res.delta -- (2n, r) Fourier loadings
|
|
291
|
+
# alpha = res.alpha -- (p, r) adjustment speeds
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
### Frequency selection (Perron-Shintani-Yabu 2021)
|
|
297
|
+
|
|
298
|
+
```python
|
|
299
|
+
fj.psy_wald_test(
|
|
300
|
+
y, # univariate series, shape (T,)
|
|
301
|
+
k_freqs, # sequence of frequency indices, e.g. [1, 2, 3]
|
|
302
|
+
p_d: int = 1, # polynomial order: 0 = const, 1 = const+t
|
|
303
|
+
subset_freq=None, # if int, test only this frequency's two coefficients
|
|
304
|
+
version="upper-biased",
|
|
305
|
+
p_T_max=None,
|
|
306
|
+
)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Returns a `PSYTestResult` with `.stat`, `.df`, `.p_value`,
|
|
310
|
+
`.alpha_hat`, `.alpha_corrected`, `.alpha_S`.
|
|
311
|
+
|
|
312
|
+
```python
|
|
313
|
+
fj.select_frequencies_univariate(
|
|
314
|
+
y, # univariate
|
|
315
|
+
n_max=5,
|
|
316
|
+
p_d=1,
|
|
317
|
+
sig_level=0.10,
|
|
318
|
+
version="upper-biased",
|
|
319
|
+
) -> FrequencySelectionResult
|
|
320
|
+
|
|
321
|
+
fj.select_frequencies(
|
|
322
|
+
data, # multivariate (T, p)
|
|
323
|
+
n_max=5,
|
|
324
|
+
p_d=1,
|
|
325
|
+
sig_level=0.10,
|
|
326
|
+
version="upper-biased",
|
|
327
|
+
) -> FrequencySelectionResult
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
`FrequencySelectionResult` attributes: `n_selected`, `per_series` (DataFrame
|
|
331
|
+
of per-column n), `detail` (DataFrame of every step's W-stat / p-value).
|
|
332
|
+
|
|
333
|
+
#### Example
|
|
334
|
+
|
|
335
|
+
```python
|
|
336
|
+
sel = fj.select_frequencies(data, n_max=5, p_d=1, sig_level=0.10)
|
|
337
|
+
print(sel.per_series) # per-series chosen n
|
|
338
|
+
print(sel.detail) # full step-by-step table
|
|
339
|
+
print(f"Final n = {sel.n_selected}")
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
### Critical values & p-values
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
fj.quantile(level, p_minus_r, n, model="CNR")
|
|
348
|
+
# level can be 0.95 or '95%'
|
|
349
|
+
# Uses Table B1 of the paper when available, otherwise Gamma approx.
|
|
350
|
+
|
|
351
|
+
fj.p_value(stat, p_minus_r, n, model="CNR")
|
|
352
|
+
# Gamma-approximation p-value of an observed trace statistic.
|
|
353
|
+
|
|
354
|
+
fj.moments(p_minus_r, n, model="CNR") -> (mean, var)
|
|
355
|
+
# First two moments of the limiting distribution.
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
The full Table B1 is also exposed as `fj.CNR_TABLE_B1[(p_minus_r, n)]`.
|
|
359
|
+
|
|
360
|
+
#### Example
|
|
361
|
+
|
|
362
|
+
```python
|
|
363
|
+
cv95 = fj.quantile("95%", p_minus_r=5, n=1, model="CNR") # 110.64
|
|
364
|
+
pv = fj.p_value(120.0, p_minus_r=5, n=1, model="CNR") # ~0.03
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
### Limit-distribution simulator
|
|
370
|
+
|
|
371
|
+
```python
|
|
372
|
+
fj.simulate_limit_distribution(
|
|
373
|
+
p_minus_r,
|
|
374
|
+
n,
|
|
375
|
+
model="CNR",
|
|
376
|
+
n_sims=5_000,
|
|
377
|
+
grid_size=300,
|
|
378
|
+
seed=12345,
|
|
379
|
+
use_cache=True,
|
|
380
|
+
) -> ndarray of shape (n_sims,)
|
|
381
|
+
|
|
382
|
+
fj.simulate_limit_moments(p_minus_r, n, model="CNR", ...) -> (mean, var, draws)
|
|
383
|
+
|
|
384
|
+
fj.clear_cache() -> int # wipe ~/.fjohansen/cache
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
The simulator is **fully vectorised**: all `n_sims` replications run in a
|
|
388
|
+
single `numpy.matmul` call. Results are cached on disk under
|
|
389
|
+
`~/.fjohansen/cache/` (override via the `FJOHANSEN_CACHE` env var) so
|
|
390
|
+
identical calls are instant.
|
|
391
|
+
|
|
392
|
+
#### Example
|
|
393
|
+
|
|
394
|
+
```python
|
|
395
|
+
draws = fj.simulate_limit_distribution(p_minus_r=3, n=2, model="LNR",
|
|
396
|
+
n_sims=10_000)
|
|
397
|
+
import matplotlib.pyplot as plt
|
|
398
|
+
plt.hist(draws, bins=100, density=True)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
### Tables and exports
|
|
404
|
+
|
|
405
|
+
```python
|
|
406
|
+
fj.format_trace_table(df, sig_level=0.05) -> str
|
|
407
|
+
fj.format_trace_latex(df, spec, n, caption=None, label=None) -> str
|
|
408
|
+
fj.format_trace_html(df, spec, n, caption=None) -> str
|
|
409
|
+
fj.rich_print(results) -> None # Rich-coloured terminal output
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
#### Example
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
print(res.summary())
|
|
416
|
+
# ========================================================================
|
|
417
|
+
# Johansen-Fourier Cointegration Test (Kurita & Shintani, 2025)
|
|
418
|
+
# ========================================================================
|
|
419
|
+
# Model : CNR (Constant + Nonlinear restricted)
|
|
420
|
+
# Variables (p) : 6
|
|
421
|
+
# Lag order (k) : 3
|
|
422
|
+
# ...
|
|
423
|
+
# H0 eigenvalue trace stat p-value 5% c.v. 1% c.v.
|
|
424
|
+
# --------------------------------------------------------------------
|
|
425
|
+
# r <= 0 0.567 376.154 <0.001** 224.55 238.17
|
|
426
|
+
# r <= 1 0.534 293.944 <0.001** 177.87 190.44
|
|
427
|
+
# ...
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
### Plotting
|
|
433
|
+
|
|
434
|
+
```python
|
|
435
|
+
fj.set_paper_style() # called automatically on import
|
|
436
|
+
|
|
437
|
+
fj.plot_series(data, title=None, ncols=2) # Fig. 11
|
|
438
|
+
fj.plot_limit_density(p_minus_r, n_values=(0,1,2,3,4), model="CNR") # Figs. 1-2
|
|
439
|
+
fj.plot_recursive_rejection({label: (Ts, rates)}, nominal=0.05) # Figs. 3-10
|
|
440
|
+
|
|
441
|
+
# Bound on a JohansenFourierResults:
|
|
442
|
+
res.plot_eigenvalues()
|
|
443
|
+
res.plot_long_run()
|
|
444
|
+
res.plot_residual_diagnostics() # Fig. 12
|
|
445
|
+
res.plot_risk_premium(index=data.index) # Fig. 13
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Every function returns a `matplotlib.figure.Figure` you can save with
|
|
449
|
+
`fig.savefig("out.pdf")`.
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
### Data-generating processes
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
# Non-Fourier DGPs from Section 5.1 (bivariate, T x 2 DataFrame)
|
|
457
|
+
fj.generate_nf_dgp1(T=400, seed=0) # no cointegration
|
|
458
|
+
fj.generate_nf_dgp2(T=400, seed=0) # level shift
|
|
459
|
+
fj.generate_nf_dgp3(T=400, seed=0) # exponential transition (0.4 T)
|
|
460
|
+
fj.generate_nf_dgp4(T=400, seed=0) # sharper transition (0.8 T)
|
|
461
|
+
|
|
462
|
+
# Fourier DGPs from Section 5.2 (4-variate)
|
|
463
|
+
fj.generate_f_dgp1(T=400, seed=0) # r=1, n=1
|
|
464
|
+
fj.generate_f_dgp2(T=400, seed=0) # r=2, n=2
|
|
465
|
+
|
|
466
|
+
# Synthetic JGB-yield surrogate matching Fig. 11
|
|
467
|
+
fj.sample_jgb_data(T=108, seed=7) # 6 yields, monthly 1986-12..1995-11
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### Low-level utilities
|
|
473
|
+
|
|
474
|
+
```python
|
|
475
|
+
fj.fourier_basis(T, n, t_start=1) -> ndarray of shape (T, 2n)
|
|
476
|
+
|
|
477
|
+
fj.build_design_matrices(X, k, n, model) -> (Z0, Z1, Z2, info)
|
|
478
|
+
# Build the matrices used in Johansen's reduced rank regression.
|
|
479
|
+
|
|
480
|
+
# Inspect or extend the model registry:
|
|
481
|
+
fj.MODELS # dict[str, ModelSpec]
|
|
482
|
+
fj.ModelSpec # dataclass describing a deterministic spec
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## 🧪 Reproducing the paper
|
|
488
|
+
|
|
489
|
+
| Paper section | API entry-point |
|
|
490
|
+
|------------------------------|-------------------------------------------------------------|
|
|
491
|
+
| §3 / Prop. 3.1 limit dist. | `simulate_limit_distribution`, `plot_limit_density` |
|
|
492
|
+
| §4 unrestricted Fourier | `model='CNU'`, `model='LNU'` |
|
|
493
|
+
| §5 Monte Carlo DGPs | `generate_nf_dgp1..4`, `generate_f_dgp1..2` |
|
|
494
|
+
| §6 JGB application | `sample_jgb_data` + `JohansenFourier(..., 'CNR')` |
|
|
495
|
+
| App. B critical values | `CNR_TABLE_B1`, `quantile(...)` |
|
|
496
|
+
|
|
497
|
+
The `examples/` directory has three runnable scripts:
|
|
498
|
+
|
|
499
|
+
| File | What it reproduces |
|
|
500
|
+
|----------------------------------------------|--------------------------------------------------------------------------|
|
|
501
|
+
| `examples/example_section6_jgb.py` | Full §6 workflow on the synthetic JGB data; writes LaTeX + HTML tables. |
|
|
502
|
+
| `examples/example_section5_monte_carlo.py` | F-DGP-1 rank-selection rates: CNR(n=1) vs. standard linear model. |
|
|
503
|
+
| `examples/example_limit_distributions.py` | A simplified version of Fig. 1. |
|
|
504
|
+
|
|
505
|
+
---
|
|
506
|
+
|
|
507
|
+
## ⚡ Performance & caching
|
|
508
|
+
|
|
509
|
+
Cold-start times on a laptop (Windows, NumPy with OpenBLAS, 6-variable JGB panel):
|
|
510
|
+
|
|
511
|
+
| Operation | Cold start | Warm cache |
|
|
512
|
+
|--------------------------------------------|------------|------------|
|
|
513
|
+
| `JohansenFourier(..., 'CNR', n=3).fit()` | 0.01 s | 0.01 s |
|
|
514
|
+
| `JohansenFourier(..., 'LNR', n=3).fit()` | 0.01 s | 0.01 s |
|
|
515
|
+
| Any 4-d / 8-d cell in the bundled tables | 0.01 s | 0.01 s |
|
|
516
|
+
| `simulate_limit_distribution(.., 5000)` | 0.5–2 s | 0.02 s |
|
|
517
|
+
| `select_frequencies(panel, n_max=5)` | 0.06 s | 0.06 s |
|
|
518
|
+
|
|
519
|
+
The disk cache lives under `~/.fjohansen/cache/`. Wipe it with
|
|
520
|
+
`fj.clear_cache()` or set the `FJOHANSEN_CACHE` environment variable to
|
|
521
|
+
relocate it.
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## 📑 Citation
|
|
526
|
+
|
|
527
|
+
If you use `fjohansen` in academic work, please cite the underlying paper
|
|
528
|
+
**and** the package:
|
|
529
|
+
|
|
530
|
+
```bibtex
|
|
531
|
+
@article{kurita_shintani_2025,
|
|
532
|
+
author = {Takamitsu Kurita and Mototsugu Shintani},
|
|
533
|
+
title = {Johansen test with Fourier-type smooth nonlinear trends in cointegrating relations},
|
|
534
|
+
journal = {Econometric Reviews},
|
|
535
|
+
year = {2025},
|
|
536
|
+
volume = {44},
|
|
537
|
+
number = {10},
|
|
538
|
+
pages = {1589--1616},
|
|
539
|
+
doi = {10.1080/07474938.2025.2530640}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
@article{perron_shintani_yabu_2017,
|
|
543
|
+
author = {Pierre Perron and Mototsugu Shintani and Tomoyoshi Yabu},
|
|
544
|
+
title = {Testing for flexible nonlinear trends with an integrated or stationary noise component},
|
|
545
|
+
journal = {Oxford Bulletin of Economics and Statistics},
|
|
546
|
+
volume = {79},
|
|
547
|
+
pages = {822--850},
|
|
548
|
+
year = {2017},
|
|
549
|
+
doi = {10.1111/obes.12169}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
@software{roudane_fjohansen,
|
|
553
|
+
author = {Merwan Roudane},
|
|
554
|
+
title = {fjohansen: Johansen test with Fourier-type smooth nonlinear trends (Python)},
|
|
555
|
+
year = {2025},
|
|
556
|
+
url = {https://github.com/merwanroudane/fjohansen}
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
## 🐛 Issues & contributions
|
|
563
|
+
|
|
564
|
+
Bug reports, questions and pull requests welcome at
|
|
565
|
+
<https://github.com/merwanroudane/fjohansen/issues>.
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## 📜 License
|
|
570
|
+
|
|
571
|
+
MIT — see [LICENSE](LICENSE).
|