fjohansen 0.1.0__py3-none-any.whl
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/__init__.py +127 -0
- fjohansen/core.py +345 -0
- fjohansen/critical_values.py +251 -0
- fjohansen/data.py +188 -0
- fjohansen/frequency_select.py +395 -0
- fjohansen/models.py +194 -0
- fjohansen/plotting.py +404 -0
- fjohansen/simulation.py +261 -0
- fjohansen/tables.py +237 -0
- fjohansen/utils.py +104 -0
- fjohansen-0.1.0.dist-info/METADATA +571 -0
- fjohansen-0.1.0.dist-info/RECORD +14 -0
- fjohansen-0.1.0.dist-info/WHEEL +5 -0
- fjohansen-0.1.0.dist-info/top_level.txt +1 -0
fjohansen/__init__.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
fjohansen
|
|
3
|
+
=========
|
|
4
|
+
|
|
5
|
+
Python implementation of the Johansen cointegration test with Fourier-type
|
|
6
|
+
smooth nonlinear deterministic trends restricted to cointegrating relations,
|
|
7
|
+
as developed in:
|
|
8
|
+
|
|
9
|
+
Kurita, T. and Shintani, M. (2025), "Johansen test with Fourier-type
|
|
10
|
+
smooth nonlinear trends in cointegrating relations", *Econometric
|
|
11
|
+
Reviews*, 44(10), 1589-1616. https://doi.org/10.1080/07474938.2025.2530640
|
|
12
|
+
|
|
13
|
+
The library also bundles the FGLS Wald test of
|
|
14
|
+
|
|
15
|
+
Perron, P., Shintani, M. and Yabu, T. (2017, 2021), "Testing for flexible
|
|
16
|
+
nonlinear trends with an integrated or stationary noise component",
|
|
17
|
+
*Oxford Bulletin of Economics and Statistics* / Working Paper.
|
|
18
|
+
|
|
19
|
+
used by Kurita & Shintani as a frequency-selection pre-step.
|
|
20
|
+
|
|
21
|
+
Package metadata
|
|
22
|
+
----------------
|
|
23
|
+
:author: Merwan Roudane
|
|
24
|
+
:repo: https://github.com/merwanroudane/fjohansen
|
|
25
|
+
:license: MIT
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
__version__ = "0.1.0"
|
|
31
|
+
__author__ = "Merwan Roudane"
|
|
32
|
+
__url__ = "https://github.com/merwanroudane/fjohansen"
|
|
33
|
+
|
|
34
|
+
from .core import JohansenFourier, JohansenFourierResults, fit_johansen_fourier
|
|
35
|
+
from .critical_values import CNR_TABLE_B1, p_value, quantile, moments
|
|
36
|
+
from .frequency_select import (
|
|
37
|
+
psy_wald_test,
|
|
38
|
+
select_frequencies,
|
|
39
|
+
select_frequencies_univariate,
|
|
40
|
+
FrequencySelectionResult,
|
|
41
|
+
PSY_TAU_TABLE,
|
|
42
|
+
)
|
|
43
|
+
from .models import MODELS, ModelSpec, build_design_matrices
|
|
44
|
+
from .simulation import (
|
|
45
|
+
simulate_limit_distribution,
|
|
46
|
+
simulate_limit_moments,
|
|
47
|
+
clear_cache,
|
|
48
|
+
)
|
|
49
|
+
from .data import (
|
|
50
|
+
generate_nf_dgp1,
|
|
51
|
+
generate_nf_dgp2,
|
|
52
|
+
generate_nf_dgp3,
|
|
53
|
+
generate_nf_dgp4,
|
|
54
|
+
generate_f_dgp1,
|
|
55
|
+
generate_f_dgp2,
|
|
56
|
+
sample_jgb_data,
|
|
57
|
+
)
|
|
58
|
+
from .tables import (
|
|
59
|
+
format_trace_table,
|
|
60
|
+
format_trace_latex,
|
|
61
|
+
format_trace_html,
|
|
62
|
+
rich_print,
|
|
63
|
+
)
|
|
64
|
+
from .plotting import (
|
|
65
|
+
set_paper_style,
|
|
66
|
+
plot_series,
|
|
67
|
+
plot_limit_density,
|
|
68
|
+
plot_recursive_rejection,
|
|
69
|
+
plot_eigenvalues,
|
|
70
|
+
plot_long_run,
|
|
71
|
+
plot_risk_premium,
|
|
72
|
+
plot_residual_diagnostics,
|
|
73
|
+
)
|
|
74
|
+
from .utils import fourier_basis
|
|
75
|
+
|
|
76
|
+
__all__ = [
|
|
77
|
+
"__version__",
|
|
78
|
+
"__author__",
|
|
79
|
+
"__url__",
|
|
80
|
+
# core
|
|
81
|
+
"JohansenFourier",
|
|
82
|
+
"JohansenFourierResults",
|
|
83
|
+
"fit_johansen_fourier",
|
|
84
|
+
# critical values
|
|
85
|
+
"p_value",
|
|
86
|
+
"quantile",
|
|
87
|
+
"moments",
|
|
88
|
+
"CNR_TABLE_B1",
|
|
89
|
+
# frequency selection
|
|
90
|
+
"psy_wald_test",
|
|
91
|
+
"select_frequencies",
|
|
92
|
+
"select_frequencies_univariate",
|
|
93
|
+
"FrequencySelectionResult",
|
|
94
|
+
"PSY_TAU_TABLE",
|
|
95
|
+
# models
|
|
96
|
+
"MODELS",
|
|
97
|
+
"ModelSpec",
|
|
98
|
+
"build_design_matrices",
|
|
99
|
+
# simulation
|
|
100
|
+
"simulate_limit_distribution",
|
|
101
|
+
"simulate_limit_moments",
|
|
102
|
+
"clear_cache",
|
|
103
|
+
# data
|
|
104
|
+
"generate_nf_dgp1",
|
|
105
|
+
"generate_nf_dgp2",
|
|
106
|
+
"generate_nf_dgp3",
|
|
107
|
+
"generate_nf_dgp4",
|
|
108
|
+
"generate_f_dgp1",
|
|
109
|
+
"generate_f_dgp2",
|
|
110
|
+
"sample_jgb_data",
|
|
111
|
+
# tables
|
|
112
|
+
"format_trace_table",
|
|
113
|
+
"format_trace_latex",
|
|
114
|
+
"format_trace_html",
|
|
115
|
+
"rich_print",
|
|
116
|
+
# plotting
|
|
117
|
+
"set_paper_style",
|
|
118
|
+
"plot_series",
|
|
119
|
+
"plot_limit_density",
|
|
120
|
+
"plot_recursive_rejection",
|
|
121
|
+
"plot_eigenvalues",
|
|
122
|
+
"plot_long_run",
|
|
123
|
+
"plot_risk_premium",
|
|
124
|
+
"plot_residual_diagnostics",
|
|
125
|
+
# utils
|
|
126
|
+
"fourier_basis",
|
|
127
|
+
]
|
fjohansen/core.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""
|
|
2
|
+
``JohansenFourier`` -- the main public class.
|
|
3
|
+
|
|
4
|
+
Performs the reduced-rank regression of Kurita & Shintani (2025) for a
|
|
5
|
+
cointegrated VAR(k) with restricted Fourier-type smooth nonlinear trends in
|
|
6
|
+
the cointegrating space.
|
|
7
|
+
|
|
8
|
+
Usage
|
|
9
|
+
-----
|
|
10
|
+
>>> from fjohansen import JohansenFourier
|
|
11
|
+
>>> res = JohansenFourier(data, k=3, n=5, model='CNR').fit()
|
|
12
|
+
>>> print(res.summary())
|
|
13
|
+
>>> res.plot_eigenvalues()
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from dataclasses import dataclass, field
|
|
19
|
+
from typing import Optional, Sequence
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import pandas as pd
|
|
23
|
+
|
|
24
|
+
from .critical_values import p_value, quantile
|
|
25
|
+
from .models import MODELS, ModelSpec, build_design_matrices
|
|
26
|
+
from .utils import ensure_array, ols_residuals
|
|
27
|
+
|
|
28
|
+
__all__ = ["JohansenFourier", "JohansenFourierResults"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Results container
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
@dataclass
|
|
35
|
+
class JohansenFourierResults:
|
|
36
|
+
"""Estimated quantities returned by :meth:`JohansenFourier.fit`."""
|
|
37
|
+
|
|
38
|
+
spec: ModelSpec
|
|
39
|
+
p: int
|
|
40
|
+
k: int
|
|
41
|
+
n: int
|
|
42
|
+
T_eff: int
|
|
43
|
+
|
|
44
|
+
eigenvalues: np.ndarray # length p, sorted descending
|
|
45
|
+
eigenvectors: np.ndarray # raw eigenvectors of S_11^{-1/2} ...
|
|
46
|
+
|
|
47
|
+
trace_stats: pd.DataFrame # one row per r in 0..p-1
|
|
48
|
+
selected_rank: Optional[int] = None
|
|
49
|
+
|
|
50
|
+
alpha: Optional[np.ndarray] = None
|
|
51
|
+
beta: Optional[np.ndarray] = None # X_{t-1} block
|
|
52
|
+
delta: Optional[np.ndarray] = None # Fourier block (or None)
|
|
53
|
+
gamma_const: Optional[np.ndarray] = None
|
|
54
|
+
gamma_trend: Optional[np.ndarray] = None
|
|
55
|
+
Gamma: Optional[list[np.ndarray]] = None
|
|
56
|
+
Phi_unrestricted: Optional[np.ndarray] = None # coeffs on Z2
|
|
57
|
+
Sigma: Optional[np.ndarray] = None # residual covariance
|
|
58
|
+
|
|
59
|
+
residuals: Optional[np.ndarray] = None # T_eff x p
|
|
60
|
+
fitted_long_run: Optional[np.ndarray] = None # beta' X + delta' F + gamma
|
|
61
|
+
series_names: Sequence[str] = field(default_factory=list)
|
|
62
|
+
t_index: Optional[np.ndarray] = None
|
|
63
|
+
|
|
64
|
+
# ------------------------------------------------------------------
|
|
65
|
+
# Pretty summary
|
|
66
|
+
# ------------------------------------------------------------------
|
|
67
|
+
def summary(self, sig_level: float = 0.05) -> str:
|
|
68
|
+
"""Return a formatted text summary akin to Table 3 of the paper."""
|
|
69
|
+
from .tables import format_trace_table, format_header
|
|
70
|
+
head = format_header(self)
|
|
71
|
+
body = format_trace_table(self.trace_stats, sig_level=sig_level)
|
|
72
|
+
if self.selected_rank is not None:
|
|
73
|
+
tail = (
|
|
74
|
+
f"\nSelected cointegrating rank (sequential 5%-test): "
|
|
75
|
+
f"r = {self.selected_rank}\n"
|
|
76
|
+
f"=> Number of common stochastic trends p - r "
|
|
77
|
+
f"= {self.p - self.selected_rank}.\n"
|
|
78
|
+
)
|
|
79
|
+
else:
|
|
80
|
+
tail = ""
|
|
81
|
+
return head + "\n" + body + tail
|
|
82
|
+
|
|
83
|
+
def to_latex(self, **kw) -> str:
|
|
84
|
+
from .tables import format_trace_latex
|
|
85
|
+
return format_trace_latex(self.trace_stats, spec=self.spec, n=self.n, **kw)
|
|
86
|
+
|
|
87
|
+
def to_html(self, **kw) -> str:
|
|
88
|
+
from .tables import format_trace_html
|
|
89
|
+
return format_trace_html(self.trace_stats, spec=self.spec, n=self.n, **kw)
|
|
90
|
+
|
|
91
|
+
# ------------------------------------------------------------------
|
|
92
|
+
# Convenience plots (forwarders)
|
|
93
|
+
# ------------------------------------------------------------------
|
|
94
|
+
def plot_eigenvalues(self, **kw):
|
|
95
|
+
from .plotting import plot_eigenvalues
|
|
96
|
+
return plot_eigenvalues(self, **kw)
|
|
97
|
+
|
|
98
|
+
def plot_long_run(self, **kw):
|
|
99
|
+
from .plotting import plot_long_run
|
|
100
|
+
return plot_long_run(self, **kw)
|
|
101
|
+
|
|
102
|
+
def plot_residual_diagnostics(self, **kw):
|
|
103
|
+
from .plotting import plot_residual_diagnostics
|
|
104
|
+
return plot_residual_diagnostics(self, **kw)
|
|
105
|
+
|
|
106
|
+
def plot_risk_premium(self, **kw):
|
|
107
|
+
from .plotting import plot_risk_premium
|
|
108
|
+
return plot_risk_premium(self, **kw)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Main test class
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
class JohansenFourier:
|
|
115
|
+
"""
|
|
116
|
+
Johansen cointegrating-rank test with Fourier-type smooth nonlinear
|
|
117
|
+
deterministic trends restricted to the cointegrating space.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
data : array_like or DataFrame, shape (T, p)
|
|
122
|
+
Multivariate I(1) time series in levels.
|
|
123
|
+
k : int
|
|
124
|
+
VAR order in levels (``k - 1`` lagged differences will be used).
|
|
125
|
+
n : int, default 1
|
|
126
|
+
Number of Fourier frequencies. Use ``n = 0`` to recover the standard
|
|
127
|
+
Johansen models (set ``model='constant'`` or ``'linear'`` instead).
|
|
128
|
+
model : {'CNR', 'LNR', 'CNU', 'LNU', 'constant', 'linear'}, default 'CNR'
|
|
129
|
+
Deterministic specification (see :mod:`fjohansen.models`).
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
def __init__(
|
|
133
|
+
self,
|
|
134
|
+
data,
|
|
135
|
+
k: int,
|
|
136
|
+
n: int = 1,
|
|
137
|
+
model: str = "CNR",
|
|
138
|
+
):
|
|
139
|
+
self.X, self.series_names = ensure_array(data)
|
|
140
|
+
self.k = int(k)
|
|
141
|
+
self.n = int(n)
|
|
142
|
+
self.spec = MODELS[model.upper().replace(" ", "_")]
|
|
143
|
+
if not self.spec.has_fourier and self.n > 0:
|
|
144
|
+
# silently zero-out n for standard models so the table look-up
|
|
145
|
+
# uses the right column.
|
|
146
|
+
self.n = 0
|
|
147
|
+
|
|
148
|
+
# ------------------------------------------------------------------
|
|
149
|
+
def fit(
|
|
150
|
+
self,
|
|
151
|
+
sig_level: float = 0.05,
|
|
152
|
+
select_rank: bool = True,
|
|
153
|
+
compute_pvalues: bool = True,
|
|
154
|
+
compute_estimates: bool = True,
|
|
155
|
+
n_sims: int = 5_000,
|
|
156
|
+
) -> JohansenFourierResults:
|
|
157
|
+
"""
|
|
158
|
+
Run the reduced-rank regression and the trace test.
|
|
159
|
+
|
|
160
|
+
Parameters
|
|
161
|
+
----------
|
|
162
|
+
sig_level : float, default 0.05
|
|
163
|
+
Significance level used for the sequential rank selection.
|
|
164
|
+
select_rank : bool, default True
|
|
165
|
+
If True, pick rank via Johansen's sequential procedure
|
|
166
|
+
(r = 0, 1, ..., p - 1).
|
|
167
|
+
compute_pvalues : bool, default True
|
|
168
|
+
If True, compute p-values via the Gamma approximation.
|
|
169
|
+
compute_estimates : bool, default True
|
|
170
|
+
If True, return alpha, beta, delta and the residuals at the
|
|
171
|
+
selected rank.
|
|
172
|
+
n_sims : int, default 30_000
|
|
173
|
+
Number of replications used to simulate the limit distribution
|
|
174
|
+
for cells outside Table B1.
|
|
175
|
+
"""
|
|
176
|
+
Z0, Z1, Z2, info = build_design_matrices(
|
|
177
|
+
self.X, k=self.k, n=self.n, model=self.spec
|
|
178
|
+
)
|
|
179
|
+
p = info["p"]
|
|
180
|
+
T_eff = info["T_eff"]
|
|
181
|
+
|
|
182
|
+
# --- partial-out Z2 from Z0 and Z1 ------------------------------
|
|
183
|
+
R0 = ols_residuals(Z0, Z2)
|
|
184
|
+
R1 = ols_residuals(Z1, Z2)
|
|
185
|
+
|
|
186
|
+
# --- product moments -------------------------------------------
|
|
187
|
+
S00 = R0.T @ R0 / T_eff
|
|
188
|
+
S11 = R1.T @ R1 / T_eff
|
|
189
|
+
S01 = R0.T @ R1 / T_eff
|
|
190
|
+
S10 = S01.T
|
|
191
|
+
|
|
192
|
+
# --- generalized eigenvalue problem ----------------------------
|
|
193
|
+
# det(lambda * S11 - S10 S00^{-1} S01) = 0
|
|
194
|
+
# Equivalent to standard eig of S11^{-1/2} S10 S00^{-1} S01 S11^{-1/2}.
|
|
195
|
+
try:
|
|
196
|
+
L = np.linalg.cholesky(S11)
|
|
197
|
+
L_inv = np.linalg.solve(L, np.eye(S11.shape[0]))
|
|
198
|
+
except np.linalg.LinAlgError as exc:
|
|
199
|
+
raise np.linalg.LinAlgError(
|
|
200
|
+
"S11 is not positive-definite; the model is probably "
|
|
201
|
+
"mis-specified (e.g. multi-collinear deterministics)."
|
|
202
|
+
) from exc
|
|
203
|
+
S00_inv = np.linalg.pinv(S00)
|
|
204
|
+
M = L_inv @ S10 @ S00_inv @ S01 @ L_inv.T # symmetric in theory
|
|
205
|
+
M = (M + M.T) / 2.0
|
|
206
|
+
eigvals, eigvecs = np.linalg.eigh(M) # ascending
|
|
207
|
+
order = np.argsort(eigvals)[::-1]
|
|
208
|
+
eigvals = np.clip(eigvals[order], 0.0, 1.0)
|
|
209
|
+
eigvecs = eigvecs[:, order]
|
|
210
|
+
# Transform back to original metric: v_i = L^{-T} u_i, normalised by
|
|
211
|
+
# v' S11 v = I.
|
|
212
|
+
V = L_inv.T @ eigvecs
|
|
213
|
+
|
|
214
|
+
# --- trace statistics ------------------------------------------
|
|
215
|
+
rows = []
|
|
216
|
+
for r in range(p):
|
|
217
|
+
tail = eigvals[r:]
|
|
218
|
+
stat = float(-T_eff * np.sum(np.log1p(-tail)))
|
|
219
|
+
p_r = p - r
|
|
220
|
+
cv95 = quantile("95%", p_r, self.n, model=self.spec.code)
|
|
221
|
+
cv99 = quantile("99%", p_r, self.n, model=self.spec.code)
|
|
222
|
+
pval = (
|
|
223
|
+
p_value(stat, p_r, self.n, model=self.spec.code, n_sims=n_sims)
|
|
224
|
+
if compute_pvalues
|
|
225
|
+
else np.nan
|
|
226
|
+
)
|
|
227
|
+
rows.append(
|
|
228
|
+
dict(
|
|
229
|
+
null=f"r <= {r}",
|
|
230
|
+
eigenvalue=eigvals[r],
|
|
231
|
+
trace_stat=stat,
|
|
232
|
+
cv_95=cv95,
|
|
233
|
+
cv_99=cv99,
|
|
234
|
+
p_value=pval,
|
|
235
|
+
)
|
|
236
|
+
)
|
|
237
|
+
trace_df = pd.DataFrame(rows)
|
|
238
|
+
|
|
239
|
+
# --- sequential rank selection ---------------------------------
|
|
240
|
+
selected_rank = None
|
|
241
|
+
if select_rank:
|
|
242
|
+
for r in range(p):
|
|
243
|
+
row = trace_df.iloc[r]
|
|
244
|
+
pv = row["p_value"]
|
|
245
|
+
stat = row["trace_stat"]
|
|
246
|
+
cv = quantile(1 - sig_level, p - r, self.n, model=self.spec.code)
|
|
247
|
+
reject = (pv < sig_level) if np.isfinite(pv) else (stat > cv)
|
|
248
|
+
if not reject:
|
|
249
|
+
selected_rank = r
|
|
250
|
+
break
|
|
251
|
+
if selected_rank is None:
|
|
252
|
+
selected_rank = p # full rank, stationary system
|
|
253
|
+
|
|
254
|
+
# --- estimates at the chosen rank ------------------------------
|
|
255
|
+
alpha = beta_X = delta_F = gamma_c = gamma_t = None
|
|
256
|
+
Gamma_list = None
|
|
257
|
+
Phi_un = None
|
|
258
|
+
Sigma = None
|
|
259
|
+
residuals = None
|
|
260
|
+
fitted_long_run = None
|
|
261
|
+
if compute_estimates and selected_rank is not None and selected_rank > 0:
|
|
262
|
+
r = selected_rank
|
|
263
|
+
beta_full = V[:, :r] # shape (q1, r)
|
|
264
|
+
# alpha = S_{01} beta (beta' S_{11} beta)^{-1}
|
|
265
|
+
mid = beta_full.T @ S11 @ beta_full
|
|
266
|
+
alpha = S01 @ beta_full @ np.linalg.pinv(mid)
|
|
267
|
+
# Split beta_full into (beta on X, delta on F, gamma on const/trend)
|
|
268
|
+
offset = 0
|
|
269
|
+
beta_X = beta_full[offset : offset + p, :]
|
|
270
|
+
offset += p
|
|
271
|
+
if self.spec.z1_fourier and self.n > 0:
|
|
272
|
+
delta_F = beta_full[offset : offset + 2 * self.n, :]
|
|
273
|
+
offset += 2 * self.n
|
|
274
|
+
if self.spec.z1_constant:
|
|
275
|
+
gamma_c = beta_full[offset : offset + 1, :]
|
|
276
|
+
offset += 1
|
|
277
|
+
if self.spec.z1_trend:
|
|
278
|
+
gamma_t = beta_full[offset : offset + 1, :]
|
|
279
|
+
offset += 1
|
|
280
|
+
|
|
281
|
+
# Estimate Phi (coefficients on Z2) and residuals.
|
|
282
|
+
if Z2.shape[1] > 0:
|
|
283
|
+
# Conditional on (alpha beta'), regress Z0 - alpha beta' Z1
|
|
284
|
+
# on Z2. Equivalent to regressing (Z0 | Z2, alpha beta' Z1).
|
|
285
|
+
rhs = np.concatenate(
|
|
286
|
+
[Z2, Z1 @ beta_full], axis=1
|
|
287
|
+
)
|
|
288
|
+
coef, *_ = np.linalg.lstsq(rhs, Z0, rcond=None)
|
|
289
|
+
Phi_un = coef[: Z2.shape[1], :]
|
|
290
|
+
# second block is alpha (it should match the closed-form)
|
|
291
|
+
# Long-run fitted: beta_full' Z1 (without alpha, this is the
|
|
292
|
+
# cointegrating relation).
|
|
293
|
+
fitted_long_run = Z1 @ beta_full
|
|
294
|
+
# Residuals from the full ECM:
|
|
295
|
+
ecm = Z0 - (Z1 @ beta_full) @ alpha.T
|
|
296
|
+
if Z2.shape[1] > 0 and Phi_un is not None:
|
|
297
|
+
ecm = ecm - Z2 @ Phi_un
|
|
298
|
+
residuals = ecm
|
|
299
|
+
Sigma = residuals.T @ residuals / T_eff
|
|
300
|
+
|
|
301
|
+
# Pack Gamma_i for convenience
|
|
302
|
+
if Z2.shape[1] > 0 and Phi_un is not None:
|
|
303
|
+
Gamma_list = []
|
|
304
|
+
idx = 0
|
|
305
|
+
for _ in range(self.k - 1):
|
|
306
|
+
Gamma_list.append(Phi_un[idx : idx + p, :].T)
|
|
307
|
+
idx += p
|
|
308
|
+
|
|
309
|
+
return JohansenFourierResults(
|
|
310
|
+
spec=self.spec,
|
|
311
|
+
p=p,
|
|
312
|
+
k=self.k,
|
|
313
|
+
n=self.n,
|
|
314
|
+
T_eff=T_eff,
|
|
315
|
+
eigenvalues=eigvals,
|
|
316
|
+
eigenvectors=V,
|
|
317
|
+
trace_stats=trace_df,
|
|
318
|
+
selected_rank=selected_rank,
|
|
319
|
+
alpha=alpha,
|
|
320
|
+
beta=beta_X,
|
|
321
|
+
delta=delta_F,
|
|
322
|
+
gamma_const=gamma_c,
|
|
323
|
+
gamma_trend=gamma_t,
|
|
324
|
+
Gamma=Gamma_list,
|
|
325
|
+
Phi_unrestricted=Phi_un,
|
|
326
|
+
Sigma=Sigma,
|
|
327
|
+
residuals=residuals,
|
|
328
|
+
fitted_long_run=fitted_long_run,
|
|
329
|
+
series_names=self.series_names,
|
|
330
|
+
t_index=info["t_index"],
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
# ---------------------------------------------------------------------------
|
|
335
|
+
# Convenience function
|
|
336
|
+
# ---------------------------------------------------------------------------
|
|
337
|
+
def fit_johansen_fourier(
|
|
338
|
+
data,
|
|
339
|
+
k: int,
|
|
340
|
+
n: int = 1,
|
|
341
|
+
model: str = "CNR",
|
|
342
|
+
**fit_kwargs,
|
|
343
|
+
) -> JohansenFourierResults:
|
|
344
|
+
"""One-shot wrapper around :class:`JohansenFourier`."""
|
|
345
|
+
return JohansenFourier(data, k=k, n=n, model=model).fit(**fit_kwargs)
|