asycaus 1.0.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.
- asycaus/__init__.py +64 -0
- asycaus/all_tests.py +160 -0
- asycaus/dynamic.py +131 -0
- asycaus/efficient.py +86 -0
- asycaus/engine.py +486 -0
- asycaus/fourier.py +94 -0
- asycaus/plots.py +233 -0
- asycaus/quantile.py +94 -0
- asycaus/spectral.py +96 -0
- asycaus/static.py +132 -0
- asycaus/tables.py +311 -0
- asycaus-1.0.0.dist-info/LICENSE +21 -0
- asycaus-1.0.0.dist-info/METADATA +305 -0
- asycaus-1.0.0.dist-info/RECORD +16 -0
- asycaus-1.0.0.dist-info/WHEEL +5 -0
- asycaus-1.0.0.dist-info/top_level.txt +1 -0
asycaus/__init__.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
asycaus
|
|
3
|
+
=======
|
|
4
|
+
|
|
5
|
+
Asymmetric Granger-causality suite for Python. Python mirror of the Stata
|
|
6
|
+
package `asycaus` by the same author.
|
|
7
|
+
|
|
8
|
+
Quick start
|
|
9
|
+
-----------
|
|
10
|
+
|
|
11
|
+
>>> import numpy as np, asycaus
|
|
12
|
+
>>> rng = np.random.default_rng(0)
|
|
13
|
+
>>> x = np.cumsum(rng.standard_normal(300))
|
|
14
|
+
>>> y = np.r_[0, 0.5*x[:-1] + rng.standard_normal(299)]
|
|
15
|
+
>>> r = asycaus.static(y, x, shock="both", boot=200, plot=False)
|
|
16
|
+
|
|
17
|
+
Available tests
|
|
18
|
+
---------------
|
|
19
|
+
|
|
20
|
+
asycaus.static Hatemi-J (2012) static asymmetric (leverage bootstrap)
|
|
21
|
+
asycaus.dynamic Hatemi-J (2021) rolling / recursive
|
|
22
|
+
asycaus.fourier Nazlioglu et al. (2016) Fourier-augmented TY
|
|
23
|
+
asycaus.spectral Bahmani-Oskooee et al. (2016) BC frequency-domain
|
|
24
|
+
asycaus.quantile Fang et al. (2026) quantile asymmetric
|
|
25
|
+
asycaus.efficient Hatemi-J (2024) SUR (Pos / Neg / Joint / Pos=Neg)
|
|
26
|
+
asycaus.all_tests Run every test, return unified summary
|
|
27
|
+
|
|
28
|
+
Author : Dr Merwan Roudane <merwanroudane920@gmail.com>
|
|
29
|
+
GitHub : https://github.com/merwanroudane/asycaus
|
|
30
|
+
License: MIT
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
__version__ = version("asycaus")
|
|
37
|
+
except PackageNotFoundError:
|
|
38
|
+
__version__ = "1.0.0"
|
|
39
|
+
|
|
40
|
+
__author__ = "Dr Merwan Roudane"
|
|
41
|
+
__email__ = "merwanroudane920@gmail.com"
|
|
42
|
+
__license__ = "MIT"
|
|
43
|
+
__url__ = "https://github.com/merwanroudane/asycaus"
|
|
44
|
+
|
|
45
|
+
from . import engine
|
|
46
|
+
from . import tables
|
|
47
|
+
from . import plots
|
|
48
|
+
from .static import static, StaticResult
|
|
49
|
+
from .dynamic import dynamic, DynamicResult
|
|
50
|
+
from .fourier import fourier, FourierResult
|
|
51
|
+
from .spectral import spectral, SpectralResult
|
|
52
|
+
from .quantile import quantile, QuantileResult
|
|
53
|
+
from .efficient import efficient, EfficientResult
|
|
54
|
+
from .all_tests import all_tests, AllResult
|
|
55
|
+
from .engine import pos_neg_components
|
|
56
|
+
|
|
57
|
+
__all__ = [
|
|
58
|
+
"static", "dynamic", "fourier", "spectral", "quantile",
|
|
59
|
+
"efficient", "all_tests", "pos_neg_components",
|
|
60
|
+
"StaticResult", "DynamicResult", "FourierResult", "SpectralResult",
|
|
61
|
+
"QuantileResult", "EfficientResult", "AllResult",
|
|
62
|
+
"engine", "tables", "plots",
|
|
63
|
+
"__version__", "__author__", "__email__", "__license__", "__url__",
|
|
64
|
+
]
|
asycaus/all_tests.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""asycaus.all_tests — run the full battery and print a unified summary."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import Any
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from .static import static, StaticResult
|
|
9
|
+
from .dynamic import dynamic, DynamicResult
|
|
10
|
+
from .fourier import fourier, FourierResult
|
|
11
|
+
from .spectral import spectral, SpectralResult
|
|
12
|
+
from .quantile import quantile, QuantileResult
|
|
13
|
+
from .efficient import efficient, EfficientResult
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class AllResult:
|
|
18
|
+
static_: StaticResult | None = None
|
|
19
|
+
fourier_: FourierResult | None = None
|
|
20
|
+
efficient_: EfficientResult | None = None
|
|
21
|
+
spectral_: SpectralResult | None = None
|
|
22
|
+
quantile_: QuantileResult | None = None
|
|
23
|
+
dynamic_: DynamicResult | None = None
|
|
24
|
+
summary: pd.DataFrame = field(default_factory=pd.DataFrame)
|
|
25
|
+
depvar: str = "y"
|
|
26
|
+
causvar: str = "x"
|
|
27
|
+
|
|
28
|
+
def print(self):
|
|
29
|
+
from .tables import print_all_summary
|
|
30
|
+
print_all_summary(self)
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def plot(self, *, save=None):
|
|
34
|
+
from .plots import plot_dashboard
|
|
35
|
+
return plot_dashboard(self, save=save)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def all_tests(
|
|
39
|
+
y, x,
|
|
40
|
+
max_lag: int = 4,
|
|
41
|
+
ic: str = "hjc",
|
|
42
|
+
intorder: int = 1,
|
|
43
|
+
boot: int = 500,
|
|
44
|
+
seed: int | None = 12345,
|
|
45
|
+
kmax: int = 5,
|
|
46
|
+
nfreq: int = 50,
|
|
47
|
+
quantiles=(0.1, 0.25, 0.5, 0.75, 0.9),
|
|
48
|
+
window: int | None = None,
|
|
49
|
+
lnform: bool = False,
|
|
50
|
+
skip_dynamic: bool = False,
|
|
51
|
+
skip_spectral: bool = False,
|
|
52
|
+
skip_quantile: bool = False,
|
|
53
|
+
show: bool = True,
|
|
54
|
+
plot: bool = False,
|
|
55
|
+
) -> AllResult:
|
|
56
|
+
"""Run every asymmetric-causality test on the same (y, x) pair and print a
|
|
57
|
+
unified summary table at the end. Dashboard plot optional via `plot=True`.
|
|
58
|
+
"""
|
|
59
|
+
print("\n" + "=" * 78)
|
|
60
|
+
print(f"{'ASYMMETRIC CAUSALITY BATTERY':^78}")
|
|
61
|
+
print(f"{'Author: Dr Merwan Roudane':^78}")
|
|
62
|
+
print("=" * 78)
|
|
63
|
+
print(f" Direction tested: causvar -> depvar")
|
|
64
|
+
|
|
65
|
+
common = dict(max_lag=max_lag, ic=ic, intorder=intorder, lnform=lnform,
|
|
66
|
+
show=False, plot=False)
|
|
67
|
+
|
|
68
|
+
print("\n[1/6] Static Asymmetric Causality (Hatemi-J 2012)...")
|
|
69
|
+
sr = static(y, x, shock="both", boot=boot, seed=seed, **common)
|
|
70
|
+
|
|
71
|
+
print("\n[2/6] Fourier Asymmetric TY (Nazlioglu et al. 2016)...")
|
|
72
|
+
fr = fourier(y, x, shock="both", kmax=kmax, form="single", **common)
|
|
73
|
+
|
|
74
|
+
print("\n[3/6] Efficient Asymmetric (Hatemi-J 2024)...")
|
|
75
|
+
er = efficient(y, x, max_lag=max_lag, ic=ic, intorder=intorder,
|
|
76
|
+
lnform=lnform, show=False, plot=False)
|
|
77
|
+
|
|
78
|
+
sp = None
|
|
79
|
+
if not skip_spectral:
|
|
80
|
+
print("\n[4/6] Spectral Asymmetric (Bahmani-Oskooee et al. 2016)...")
|
|
81
|
+
sp = spectral(y, x, shock="both", nfreq=nfreq, max_lag=max_lag, ic=ic,
|
|
82
|
+
lnform=lnform, show=False, plot=False)
|
|
83
|
+
|
|
84
|
+
qr = None
|
|
85
|
+
if not skip_quantile:
|
|
86
|
+
print("\n[5/6] Quantile Asymmetric (Fang et al. 2026)...")
|
|
87
|
+
qr = quantile(y, x, shock="both", quantiles=quantiles,
|
|
88
|
+
max_lag=max_lag, ic=ic, intorder=intorder,
|
|
89
|
+
lnform=lnform, show=False, plot=False)
|
|
90
|
+
|
|
91
|
+
dy = None
|
|
92
|
+
if not skip_dynamic:
|
|
93
|
+
print("\n[*] Dynamic Asymmetric (Hatemi-J 2021, Pos shocks)...")
|
|
94
|
+
try:
|
|
95
|
+
dy = dynamic(y, x, shock="pos", mode="rolling",
|
|
96
|
+
window=window, max_lag=max_lag, ic=ic,
|
|
97
|
+
intorder=intorder, boot=min(boot, 200), seed=seed,
|
|
98
|
+
lnform=lnform, show=False, plot=False, progress=False)
|
|
99
|
+
except Exception as ex:
|
|
100
|
+
print(f" (dynamic skipped: {ex})")
|
|
101
|
+
dy = None
|
|
102
|
+
|
|
103
|
+
# Build unified summary -------------------------------------------------
|
|
104
|
+
rows = []
|
|
105
|
+
for s in ["Positive", "Negative"]:
|
|
106
|
+
if s in sr.table.index:
|
|
107
|
+
row = sr.table.loc[s]
|
|
108
|
+
rows.append({"Test": "Static (Hatemi-J 2012)",
|
|
109
|
+
"Shock": s[:3], "Statistic": row["Wald"],
|
|
110
|
+
"p-value": row["asy_p"],
|
|
111
|
+
"Decision": row["decision_5pct"]})
|
|
112
|
+
for s in ["Positive", "Negative"]:
|
|
113
|
+
if s in fr.table.index:
|
|
114
|
+
row = fr.table.loc[s]
|
|
115
|
+
rows.append({"Test": "Fourier (Nazlioglu 2016)",
|
|
116
|
+
"Shock": s[:3], "Statistic": row["Wald"],
|
|
117
|
+
"p-value": row["asy_p"],
|
|
118
|
+
"Decision": row["decision_5pct"]})
|
|
119
|
+
eff = er.raw
|
|
120
|
+
rows.extend([
|
|
121
|
+
{"Test": "Efficient Pos only (HJ 2024)", "Shock": "Pos",
|
|
122
|
+
"Statistic": eff["W_pos"], "p-value": eff["p_pos"],
|
|
123
|
+
"Decision": "Reject" if eff["p_pos"] < 0.05 else "Fail to reject"},
|
|
124
|
+
{"Test": "Efficient Neg only (HJ 2024)", "Shock": "Neg",
|
|
125
|
+
"Statistic": eff["W_neg"], "p-value": eff["p_neg"],
|
|
126
|
+
"Decision": "Reject" if eff["p_neg"] < 0.05 else "Fail to reject"},
|
|
127
|
+
{"Test": "Efficient Joint (HJ 2024)", "Shock": "both",
|
|
128
|
+
"Statistic": eff["W_joint"], "p-value": eff["p_joint"],
|
|
129
|
+
"Decision": "Reject" if eff["p_joint"] < 0.05 else "Fail to reject"},
|
|
130
|
+
{"Test": "Efficient Pos=Neg (HJ 2024)", "Shock": "diff",
|
|
131
|
+
"Statistic": eff["W_diff"], "p-value": eff["p_diff"],
|
|
132
|
+
"Decision": "Reject" if eff["p_diff"] < 0.05 else "Fail to reject"},
|
|
133
|
+
])
|
|
134
|
+
if sp is not None:
|
|
135
|
+
for s in sp.summary.index:
|
|
136
|
+
pct = sp.summary.loc[s, "pct_reject_5pct"]
|
|
137
|
+
rows.append({"Test": "Spectral (BCRanjbar 2016)",
|
|
138
|
+
"Shock": s[:3], "Statistic": f"{pct:.2%} freq.",
|
|
139
|
+
"p-value": pct,
|
|
140
|
+
"Decision": "Reject at some" if pct > 0 else "Fail to reject"})
|
|
141
|
+
if qr is not None:
|
|
142
|
+
for s in ["Positive", "Negative"]:
|
|
143
|
+
sub = qr.table[qr.table["shock"] == s]
|
|
144
|
+
if len(sub):
|
|
145
|
+
nrej = (sub["asy_p"] < 0.05).sum()
|
|
146
|
+
pct = nrej / len(sub)
|
|
147
|
+
rows.append({"Test": "Quantile (Fang et al. 2026)",
|
|
148
|
+
"Shock": s[:3], "Statistic": f"{pct:.2%} quant.",
|
|
149
|
+
"p-value": pct,
|
|
150
|
+
"Decision": "Reject at some" if nrej > 0 else "Fail to reject"})
|
|
151
|
+
|
|
152
|
+
summary = pd.DataFrame(rows)
|
|
153
|
+
out = AllResult(static_=sr, fourier_=fr, efficient_=er, spectral_=sp,
|
|
154
|
+
quantile_=qr, dynamic_=dy, summary=summary)
|
|
155
|
+
if show:
|
|
156
|
+
out.print()
|
|
157
|
+
if plot:
|
|
158
|
+
try: out.plot()
|
|
159
|
+
except Exception: pass
|
|
160
|
+
return out
|
asycaus/dynamic.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""asycaus.dynamic — Hatemi-J (2021) rolling / recursive asymmetric causality."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
import math
|
|
7
|
+
import numpy as np
|
|
8
|
+
import pandas as pd
|
|
9
|
+
|
|
10
|
+
from .engine import (pos_neg_components, select_lag, wald_test,
|
|
11
|
+
bootstrap_critical_values, ic_label)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class DynamicResult:
|
|
16
|
+
table: pd.DataFrame
|
|
17
|
+
depvar: str = "y"
|
|
18
|
+
causvar: str = "x"
|
|
19
|
+
shock: str = "pos"
|
|
20
|
+
mode: str = "Rolling window"
|
|
21
|
+
window: int = 0
|
|
22
|
+
smin: int = 0
|
|
23
|
+
nsub: int = 0
|
|
24
|
+
ic: str = "HJC"
|
|
25
|
+
boot: int = 200
|
|
26
|
+
intorder: int = 1
|
|
27
|
+
|
|
28
|
+
def print(self):
|
|
29
|
+
from .tables import print_dynamic_table
|
|
30
|
+
print_dynamic_table(self)
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def plot(self, *, ax=None, save=None):
|
|
34
|
+
from .plots import plot_dynamic
|
|
35
|
+
return plot_dynamic(self, ax=ax, save=save)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def dynamic(
|
|
39
|
+
y, x,
|
|
40
|
+
shock: str = "pos",
|
|
41
|
+
mode: str = "rolling",
|
|
42
|
+
window: int | None = None,
|
|
43
|
+
max_lag: int = 4,
|
|
44
|
+
ic: str = "hjc",
|
|
45
|
+
intorder: int = 1,
|
|
46
|
+
boot: int = 200,
|
|
47
|
+
seed: int | None = 12345,
|
|
48
|
+
lnform: bool = False,
|
|
49
|
+
show: bool = True,
|
|
50
|
+
plot: bool = True,
|
|
51
|
+
progress: bool = True,
|
|
52
|
+
) -> DynamicResult:
|
|
53
|
+
"""Hatemi-J (2021) dynamic asymmetric Granger-causality.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
mode : {'rolling','recursive'}, default 'rolling'.
|
|
58
|
+
window : int or None
|
|
59
|
+
Window length S. Defaults to Phillips-Shi-Yu (2015) lower bound
|
|
60
|
+
S = ceil(T*(0.01 + 1.8/sqrt(T))).
|
|
61
|
+
progress : bool
|
|
62
|
+
Print "subsample k/N" every 10 windows.
|
|
63
|
+
"""
|
|
64
|
+
y = np.asarray(y, dtype=float).ravel()
|
|
65
|
+
x = np.asarray(x, dtype=float).ravel()
|
|
66
|
+
if lnform:
|
|
67
|
+
y = np.log(y); x = np.log(x)
|
|
68
|
+
Y = np.column_stack([y, x])
|
|
69
|
+
|
|
70
|
+
if shock.lower() in {"pos", "positive"}:
|
|
71
|
+
Zfull = pos_neg_components(Y, positive=True); s_lbl = "Positive components"
|
|
72
|
+
s_short = "pos"
|
|
73
|
+
elif shock.lower() in {"neg", "negative"}:
|
|
74
|
+
Zfull = pos_neg_components(Y, positive=False); s_lbl = "Negative components"
|
|
75
|
+
s_short = "neg"
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError("shock must be 'pos' or 'neg'")
|
|
78
|
+
|
|
79
|
+
Tcomp = Zfull.shape[0]
|
|
80
|
+
if Tcomp < 10:
|
|
81
|
+
raise ValueError("Too few observations after differencing.")
|
|
82
|
+
|
|
83
|
+
smin = math.ceil(Tcomp * (0.01 + 1.8 / math.sqrt(Tcomp)))
|
|
84
|
+
if window is None:
|
|
85
|
+
window = smin
|
|
86
|
+
min_window = max_lag + intorder + 3
|
|
87
|
+
if window < min_window:
|
|
88
|
+
raise ValueError(f"window must be at least {min_window}.")
|
|
89
|
+
|
|
90
|
+
nsub = Tcomp - window + 1
|
|
91
|
+
if nsub < 1:
|
|
92
|
+
raise ValueError("window too large for the sample.")
|
|
93
|
+
|
|
94
|
+
mode = mode.lower()
|
|
95
|
+
if mode not in {"rolling", "recursive"}:
|
|
96
|
+
raise ValueError("mode must be 'rolling' or 'recursive'.")
|
|
97
|
+
mode_lbl = "Rolling window" if mode == "rolling" else "Recursive"
|
|
98
|
+
|
|
99
|
+
rows = []
|
|
100
|
+
for k in range(1, nsub + 1):
|
|
101
|
+
if progress and (k % 10 == 0 or k == nsub):
|
|
102
|
+
print(f" subsample {k}/{nsub}")
|
|
103
|
+
if mode == "rolling":
|
|
104
|
+
s_idx, e_idx = k - 1, k - 1 + window
|
|
105
|
+
else:
|
|
106
|
+
s_idx, e_idx = 0, window + k - 1
|
|
107
|
+
Zsub = Zfull[s_idx:e_idx, :]
|
|
108
|
+
p = select_lag(Zsub, 1, max_lag, ic)
|
|
109
|
+
W, _ = wald_test(Zsub, p, intorder, dep_idx=0, cause_idx=1)
|
|
110
|
+
cv = bootstrap_critical_values(Zsub, p, intorder, 0, 1, B=boot,
|
|
111
|
+
seed=(seed or 0) + k)
|
|
112
|
+
rows.append({
|
|
113
|
+
"sub_start": s_idx + 1, "sub_end": e_idx, "lag": p,
|
|
114
|
+
"Wald": W, "cv10": cv["cv10"], "cv5": cv["cv5"], "cv1": cv["cv1"],
|
|
115
|
+
"ratio_5pct": W / cv["cv5"] if cv["cv5"] else np.nan,
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
table = pd.DataFrame(rows)
|
|
119
|
+
res = DynamicResult(
|
|
120
|
+
table=table, depvar="y", causvar="x", shock=s_short,
|
|
121
|
+
mode=mode_lbl, window=window, smin=smin, nsub=nsub,
|
|
122
|
+
ic=ic_label(ic), boot=boot, intorder=intorder,
|
|
123
|
+
)
|
|
124
|
+
if show:
|
|
125
|
+
res.print()
|
|
126
|
+
if plot:
|
|
127
|
+
try:
|
|
128
|
+
res.plot()
|
|
129
|
+
except Exception:
|
|
130
|
+
pass
|
|
131
|
+
return res
|
asycaus/efficient.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""asycaus.efficient — Hatemi-J (2024) efficient SUR asymmetric causality."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from .engine import pos_neg_components, select_lag, efficient_sur, ic_label
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class EfficientResult:
|
|
13
|
+
table: pd.DataFrame
|
|
14
|
+
raw: dict
|
|
15
|
+
depvar: str = "y"
|
|
16
|
+
causvar: str = "x"
|
|
17
|
+
ic: str = "HJC"
|
|
18
|
+
intorder: int = 1
|
|
19
|
+
lag: int = 1
|
|
20
|
+
|
|
21
|
+
def print(self):
|
|
22
|
+
from .tables import print_efficient_table
|
|
23
|
+
print_efficient_table(self)
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
def plot(self, *, ax=None, save=None):
|
|
27
|
+
from .plots import plot_efficient
|
|
28
|
+
return plot_efficient(self, ax=ax, save=save)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def efficient(
|
|
32
|
+
y, x,
|
|
33
|
+
max_lag: int = 8,
|
|
34
|
+
ic: str = "hjc",
|
|
35
|
+
intorder: int = 1,
|
|
36
|
+
lnform: bool = False,
|
|
37
|
+
show: bool = True,
|
|
38
|
+
plot: bool = True,
|
|
39
|
+
) -> EfficientResult:
|
|
40
|
+
"""Hatemi-J (2024) efficient asymmetric causality test (SUR).
|
|
41
|
+
|
|
42
|
+
Tests four hypotheses jointly within one SUR system:
|
|
43
|
+
H1: no causality via positive shocks
|
|
44
|
+
H2: no causality via negative shocks
|
|
45
|
+
H3: joint no causality (H1 AND H2)
|
|
46
|
+
H4: Pos = Neg causal coefficients -- the formal asymmetry test.
|
|
47
|
+
"""
|
|
48
|
+
y = np.asarray(y, dtype=float).ravel()
|
|
49
|
+
x = np.asarray(x, dtype=float).ravel()
|
|
50
|
+
if lnform:
|
|
51
|
+
y = np.log(y); x = np.log(x)
|
|
52
|
+
Y = np.column_stack([y, x])
|
|
53
|
+
|
|
54
|
+
Zpos = pos_neg_components(Y, positive=True)
|
|
55
|
+
Zneg = pos_neg_components(Y, positive=False)
|
|
56
|
+
p_pos = select_lag(Zpos, 1, max_lag, ic)
|
|
57
|
+
p_neg = select_lag(Zneg, 1, max_lag, ic)
|
|
58
|
+
p = max(p_pos, p_neg)
|
|
59
|
+
|
|
60
|
+
out = efficient_sur(Zpos, Zneg, p, intorder, 0, 1)
|
|
61
|
+
|
|
62
|
+
rows = [
|
|
63
|
+
{"hypothesis": "No causality via POS shocks", "Wald": out["W_pos"],
|
|
64
|
+
"df": p, "asy_p": out["p_pos"],
|
|
65
|
+
"decision_5pct": "Reject" if out["p_pos"] < 0.05 else "Fail to reject"},
|
|
66
|
+
{"hypothesis": "No causality via NEG shocks", "Wald": out["W_neg"],
|
|
67
|
+
"df": p, "asy_p": out["p_neg"],
|
|
68
|
+
"decision_5pct": "Reject" if out["p_neg"] < 0.05 else "Fail to reject"},
|
|
69
|
+
{"hypothesis": "Joint no causality", "Wald": out["W_joint"],
|
|
70
|
+
"df": 2 * p, "asy_p": out["p_joint"],
|
|
71
|
+
"decision_5pct": "Reject" if out["p_joint"] < 0.05 else "Fail to reject"},
|
|
72
|
+
{"hypothesis": "POS = NEG causal effects", "Wald": out["W_diff"],
|
|
73
|
+
"df": p, "asy_p": out["p_diff"],
|
|
74
|
+
"decision_5pct": "Reject" if out["p_diff"] < 0.05 else "Fail to reject"},
|
|
75
|
+
]
|
|
76
|
+
table = pd.DataFrame(rows).set_index("hypothesis")
|
|
77
|
+
res = EfficientResult(table=table, raw=out, ic=ic_label(ic),
|
|
78
|
+
intorder=intorder, lag=p)
|
|
79
|
+
if show:
|
|
80
|
+
res.print()
|
|
81
|
+
if plot:
|
|
82
|
+
try:
|
|
83
|
+
res.plot()
|
|
84
|
+
except Exception:
|
|
85
|
+
pass
|
|
86
|
+
return res
|