tvccointreg 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.
@@ -0,0 +1,72 @@
1
+ """
2
+ tvccointreg
3
+ ===========
4
+
5
+ Time-Varying-Coefficient regression and **Generalized Cointegration**, after
6
+
7
+ Hall, S. G., Swamy, P. A. V. B., & Tavlas, G. S. (2015).
8
+ *A Note on Generalizing the Concept of Cointegration.*
9
+
10
+ and the surrounding Swamy time-varying-coefficient literature.
11
+
12
+ The package estimates a model that is linear in variables but with time-varying
13
+ coefficients (the Swamy-Mehta / Granger representation of any nonlinear
14
+ relationship), decomposes each coefficient into a **bias-free** structural part
15
+ plus omitted-variable and measurement-error biases via *coefficient drivers*,
16
+ and tests for generalized cointegration with **standard** (chi-square / normal)
17
+ inference.
18
+
19
+ Quick start
20
+ -----------
21
+ >>> from tvccointreg import TVCModel, DriverSpec
22
+ >>> from tvccointreg.datasets import simulate_nonlinear_cointegration
23
+ >>> sim = simulate_nonlinear_cointegration(T=300, seed=1)
24
+ >>> spec = DriverSpec(names=list(sim.drivers.columns),
25
+ ... bias_free=["x_lag"], omitted=["w"], measurement=[])
26
+ >>> model = TVCModel(sim.y, sim.X, sim.drivers, driver_spec=spec)
27
+ >>> res = model.fit()
28
+ >>> print(res.summary())
29
+ >>> res.coint_test()
30
+ """
31
+ from .core import TVCModel, TVCResults
32
+ from .drivers import DriverSpec, default_spec
33
+ from .cointegration import CointResult
34
+ from .datasets import (
35
+ simulate_nonlinear_cointegration,
36
+ simulate_spurious,
37
+ SimulatedData,
38
+ )
39
+ from . import colormaps
40
+ from .colormaps import (
41
+ parula_colors,
42
+ matlab_jet_colors,
43
+ turbo_colors,
44
+ bluered_colors,
45
+ sinha_colors,
46
+ resolve_colorscale,
47
+ get_cmap,
48
+ )
49
+
50
+ __version__ = "0.1.0"
51
+ __author__ = "Dr Merwan Roudane"
52
+ __email__ = "merwanroudane920@gmail.com"
53
+
54
+ __all__ = [
55
+ "TVCModel",
56
+ "TVCResults",
57
+ "DriverSpec",
58
+ "default_spec",
59
+ "CointResult",
60
+ "simulate_nonlinear_cointegration",
61
+ "simulate_spurious",
62
+ "SimulatedData",
63
+ "colormaps",
64
+ "parula_colors",
65
+ "matlab_jet_colors",
66
+ "turbo_colors",
67
+ "bluered_colors",
68
+ "sinha_colors",
69
+ "resolve_colorscale",
70
+ "get_cmap",
71
+ "__version__",
72
+ ]
@@ -0,0 +1,119 @@
1
+ """
2
+ Generalized cointegration testing (Hall, Swamy & Tavlas, 2015).
3
+
4
+ Generalized cointegration between ``y`` and a regressor ``x_j`` holds iff the
5
+ *bias-free* component of the time-varying coefficient on ``x_j`` is nonzero
6
+ (eqs. 5-6). In the operational TVC model the bias-free component is
7
+
8
+ gamma_jt^BF = pi_j0 + sum_{d in bias_free} pi_jd z_dt ,
9
+
10
+ so the hypotheses are:
11
+
12
+ * **Joint (Wald) test** H0 : pi_j0 = 0 and pi_jd = 0 for all bias-free d.
13
+ Rejecting implies the bias-free coefficient is not identically zero -> the two
14
+ variables are generalized-cointegrated. Because inference rests on the
15
+ stationary errors of the driver equations (Section 3.3), the statistic has a
16
+ standard chi-square distribution -- no Dickey-Fuller critical values.
17
+
18
+ * **Average-effect test** H0 : mean_t gamma_jt^BF = 0.
19
+ A one-degree-of-freedom, easily interpreted statistic for "the average
20
+ structural derivative is zero", using the delta method.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import dataclass
25
+ from typing import Optional
26
+
27
+ import numpy as np
28
+ from scipy import stats
29
+
30
+
31
+ @dataclass
32
+ class CointResult:
33
+ name: str
34
+ # joint Wald test of the bias-free block
35
+ wald_stat: float
36
+ wald_df: int
37
+ wald_pvalue: float
38
+ # average bias-free effect
39
+ avg_effect: float
40
+ avg_se: float
41
+ avg_tstat: float
42
+ avg_pvalue: float
43
+ cointegrated: bool
44
+
45
+ def as_row(self) -> dict:
46
+ return {
47
+ "regressor": self.name,
48
+ "avg_bias_free": self.avg_effect,
49
+ "std_err": self.avg_se,
50
+ "t_stat": self.avg_tstat,
51
+ "wald": self.wald_stat,
52
+ "df": self.wald_df,
53
+ "p_value": self.wald_pvalue,
54
+ "cointegrated": self.cointegrated,
55
+ }
56
+
57
+
58
+ def wald_test(pi_block: np.ndarray, cov_block: np.ndarray,
59
+ mask: np.ndarray) -> tuple:
60
+ """Chi-square Wald test that the masked subset of ``pi_block`` is zero."""
61
+ sub = pi_block[mask]
62
+ cov_sub = cov_block[np.ix_(mask, mask)]
63
+ cov_inv = np.linalg.pinv(cov_sub)
64
+ stat = float(sub @ cov_inv @ sub)
65
+ df = int(mask.sum())
66
+ pval = float(stats.chi2.sf(stat, df))
67
+ return stat, df, pval
68
+
69
+
70
+ def average_effect(pi_block: np.ndarray, cov_block: np.ndarray,
71
+ Zbar: np.ndarray, mask: np.ndarray) -> tuple:
72
+ """
73
+ Average bias-free effect and its delta-method standard error.
74
+
75
+ ``Zbar`` is the mean driver design vector (length q). The average bias-free
76
+ coefficient is ``Zbar[mask] @ pi_block[mask]`` with variance
77
+ ``Zbar[mask] @ cov_block[mask, mask] @ Zbar[mask]``.
78
+ """
79
+ m = Zbar[mask]
80
+ sub = pi_block[mask]
81
+ cov_sub = cov_block[np.ix_(mask, mask)]
82
+ eff = float(m @ sub)
83
+ var = float(m @ cov_sub @ m)
84
+ se = float(np.sqrt(max(var, 0.0)))
85
+ t = eff / se if se > 0 else np.nan
86
+ pval = float(2 * stats.norm.sf(abs(t))) if se > 0 else np.nan
87
+ return eff, se, t, pval
88
+
89
+
90
+ def test_coefficient(name: str, pi_block: np.ndarray, cov_block: np.ndarray,
91
+ Zbar: np.ndarray, bias_free_mask: np.ndarray,
92
+ alpha: float = 0.05) -> CointResult:
93
+ """Run both the joint Wald and the average-effect tests for one regressor."""
94
+ wstat, wdf, wp = wald_test(pi_block, cov_block, bias_free_mask)
95
+ eff, se, t, ap = average_effect(pi_block, cov_block, Zbar, bias_free_mask)
96
+ return CointResult(
97
+ name=name, wald_stat=wstat, wald_df=wdf, wald_pvalue=wp,
98
+ avg_effect=eff, avg_se=se, avg_tstat=t, avg_pvalue=ap,
99
+ cointegrated=bool(wp < alpha),
100
+ )
101
+
102
+
103
+ def adf_pvalue(series: np.ndarray) -> Optional[dict]:
104
+ """
105
+ Augmented Dickey-Fuller test on a residual series (used to check the paper's
106
+ claim that the driver-equation errors are stationary). Returns ``None`` if
107
+ ``statsmodels`` is not installed.
108
+ """
109
+ try:
110
+ from statsmodels.tsa.stattools import adfuller
111
+ except Exception:
112
+ return None
113
+ series = np.asarray(series, dtype=float)
114
+ series = series[np.isfinite(series)]
115
+ if series.size < 10:
116
+ return None
117
+ stat, pvalue, *_ = adfuller(series, autolag="AIC")
118
+ return {"adf_stat": float(stat), "p_value": float(pvalue),
119
+ "stationary": bool(pvalue < 0.05)}
@@ -0,0 +1,195 @@
1
+ """
2
+ Color maps for ``tvccointreg``.
3
+
4
+ Reproduces MATLAB's R2014b *Parula* colormap (the package default) together with
5
+ ``jet``, ``turbo``, ``bluered`` and a ``sinha`` diverging map. All helpers return
6
+ either a list of hex strings (for matplotlib) or a list of ``(value, hex)`` stops
7
+ (for plotly-style colorscales), built with ``matplotlib``'s
8
+ ``LinearSegmentedColormap`` for smooth interpolation between the anchor stops.
9
+
10
+ The Parula anchors are the canonical 64-stop RGB control points of the original
11
+ MATLAB Parula colormap (sub-sampled to the well-known 17 anchors that reproduce
12
+ it to the eye).
13
+ """
14
+ from __future__ import annotations
15
+
16
+ from typing import List, Tuple
17
+
18
+ import numpy as np
19
+ from matplotlib.colors import LinearSegmentedColormap, to_hex
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # Anchor RGB stops (0-1 scale)
23
+ # ---------------------------------------------------------------------------
24
+ _PARULA_ANCHORS = [
25
+ (0.2081, 0.1663, 0.5292),
26
+ (0.2116, 0.1898, 0.5777),
27
+ (0.2123, 0.2138, 0.6270),
28
+ (0.2081, 0.2386, 0.6771),
29
+ (0.1959, 0.2645, 0.7279),
30
+ (0.1707, 0.2919, 0.7792),
31
+ (0.1253, 0.3242, 0.8303),
32
+ (0.0591, 0.3598, 0.8683),
33
+ (0.0117, 0.3875, 0.8820),
34
+ (0.0060, 0.4086, 0.8828),
35
+ (0.0165, 0.4266, 0.8786),
36
+ (0.0329, 0.4430, 0.8720),
37
+ (0.0498, 0.4586, 0.8641),
38
+ (0.0629, 0.4737, 0.8554),
39
+ (0.0723, 0.4887, 0.8467),
40
+ (0.0779, 0.5040, 0.8384),
41
+ (0.0793, 0.5200, 0.8312),
42
+ (0.0749, 0.5375, 0.8263),
43
+ (0.0641, 0.5570, 0.8240),
44
+ (0.0488, 0.5772, 0.8228),
45
+ (0.0343, 0.5966, 0.8199),
46
+ (0.0265, 0.6137, 0.8135),
47
+ (0.0239, 0.6287, 0.8038),
48
+ (0.0231, 0.6418, 0.7913),
49
+ (0.0228, 0.6535, 0.7768),
50
+ (0.0267, 0.6642, 0.7607),
51
+ (0.0384, 0.6743, 0.7436),
52
+ (0.0590, 0.6838, 0.7254),
53
+ (0.0843, 0.6928, 0.7062),
54
+ (0.1133, 0.7015, 0.6859),
55
+ (0.1453, 0.7098, 0.6646),
56
+ (0.1801, 0.7177, 0.6424),
57
+ (0.2178, 0.7250, 0.6193),
58
+ (0.2586, 0.7317, 0.5954),
59
+ (0.3022, 0.7376, 0.5712),
60
+ (0.3482, 0.7424, 0.5473),
61
+ (0.3953, 0.7459, 0.5244),
62
+ (0.4420, 0.7481, 0.5033),
63
+ (0.4871, 0.7491, 0.4840),
64
+ (0.5300, 0.7491, 0.4661),
65
+ (0.5709, 0.7485, 0.4494),
66
+ (0.6100, 0.7473, 0.4337),
67
+ (0.6473, 0.7456, 0.4188),
68
+ (0.6834, 0.7435, 0.4044),
69
+ (0.7184, 0.7411, 0.3905),
70
+ (0.7525, 0.7384, 0.3768),
71
+ (0.7858, 0.7356, 0.3633),
72
+ (0.8185, 0.7327, 0.3498),
73
+ (0.8507, 0.7299, 0.3360),
74
+ (0.8824, 0.7274, 0.3217),
75
+ (0.9139, 0.7258, 0.3063),
76
+ (0.9450, 0.7261, 0.2886),
77
+ (0.9739, 0.7314, 0.2666),
78
+ (0.9938, 0.7455, 0.2403),
79
+ (0.9990, 0.7653, 0.2164),
80
+ (0.9955, 0.7861, 0.1967),
81
+ (0.9880, 0.8066, 0.1794),
82
+ (0.9789, 0.8271, 0.1633),
83
+ (0.9697, 0.8481, 0.1475),
84
+ (0.9626, 0.8705, 0.1309),
85
+ (0.9589, 0.8949, 0.1132),
86
+ (0.9598, 0.9218, 0.0948),
87
+ (0.9661, 0.9514, 0.0755),
88
+ (0.9763, 0.9831, 0.0538),
89
+ ]
90
+
91
+ _JET_ANCHORS = [
92
+ (0.0, 0.0, 0.5), (0.0, 0.0, 1.0), (0.0, 0.5, 1.0), (0.0, 1.0, 1.0),
93
+ (0.5, 1.0, 0.5), (1.0, 1.0, 0.0), (1.0, 0.5, 0.0), (1.0, 0.0, 0.0),
94
+ (0.5, 0.0, 0.0),
95
+ ]
96
+
97
+ _TURBO_ANCHORS = [
98
+ (0.18995, 0.07176, 0.23217), (0.25107, 0.25237, 0.63374),
99
+ (0.27628, 0.42118, 0.89123), (0.25862, 0.57958, 0.99876),
100
+ (0.15844, 0.73551, 0.92305), (0.09267, 0.86554, 0.71774),
101
+ (0.19659, 0.94901, 0.47860), (0.42778, 0.99419, 0.20760),
102
+ (0.64362, 0.98999, 0.23356), (0.80473, 0.89180, 0.17813),
103
+ (0.93301, 0.74785, 0.16275), (0.99314, 0.55582, 0.13455),
104
+ (0.93590, 0.33310, 0.05878), (0.79747, 0.16523, 0.01756),
105
+ (0.61088, 0.05475, 0.00787), (0.47960, 0.01583, 0.01055),
106
+ ]
107
+
108
+ _BLUERED_ANCHORS = [
109
+ (0.0196, 0.1882, 0.3804), (0.1294, 0.4000, 0.6745),
110
+ (0.4039, 0.6627, 0.8118), (0.8431, 0.8980, 0.9412),
111
+ (0.9686, 0.9686, 0.9686), (0.9922, 0.8588, 0.7804),
112
+ (0.9569, 0.6471, 0.5098), (0.8392, 0.3765, 0.3020),
113
+ (0.6980, 0.0941, 0.1686),
114
+ ]
115
+
116
+ # A warm sequential map in the spirit of Sinha et al. (2023) quantile heatmaps.
117
+ _SINHA_ANCHORS = [
118
+ (0.031, 0.188, 0.420), (0.129, 0.443, 0.710), (0.420, 0.682, 0.839),
119
+ (0.780, 0.863, 0.937), (0.996, 0.878, 0.565), (0.992, 0.682, 0.380),
120
+ (0.957, 0.427, 0.263), (0.835, 0.243, 0.310), (0.620, 0.004, 0.259),
121
+ ]
122
+
123
+ _REGISTRY = {
124
+ "parula": _PARULA_ANCHORS,
125
+ "jet": _JET_ANCHORS,
126
+ "turbo": _TURBO_ANCHORS,
127
+ "bluered": _BLUERED_ANCHORS,
128
+ "sinha": _SINHA_ANCHORS,
129
+ }
130
+
131
+
132
+ def _make_cmap(name: str) -> LinearSegmentedColormap:
133
+ key = name.lower()
134
+ if key not in _REGISTRY:
135
+ raise KeyError(
136
+ f"Unknown colormap '{name}'. Available: {sorted(_REGISTRY)}"
137
+ )
138
+ return LinearSegmentedColormap.from_list(key, _REGISTRY[key], N=256)
139
+
140
+
141
+ def get_cmap(name: str = "parula") -> LinearSegmentedColormap:
142
+ """Return a matplotlib ``LinearSegmentedColormap`` for ``name``."""
143
+ return _make_cmap(name)
144
+
145
+
146
+ def _palette(anchors, n: int) -> List[str]:
147
+ cmap = LinearSegmentedColormap.from_list("tmp", anchors, N=256)
148
+ xs = np.linspace(0.0, 1.0, max(int(n), 1))
149
+ return [to_hex(cmap(x)) for x in xs]
150
+
151
+
152
+ def parula_colors(n: int = 64) -> List[str]:
153
+ """``n`` evenly spaced hex colors from the MATLAB Parula colormap."""
154
+ return _palette(_PARULA_ANCHORS, n)
155
+
156
+
157
+ def matlab_jet_colors(n: int = 64) -> List[str]:
158
+ """``n`` evenly spaced hex colors from the MATLAB jet colormap."""
159
+ return _palette(_JET_ANCHORS, n)
160
+
161
+
162
+ def turbo_colors(n: int = 64) -> List[str]:
163
+ """``n`` evenly spaced hex colors from Google's turbo colormap."""
164
+ return _palette(_TURBO_ANCHORS, n)
165
+
166
+
167
+ def bluered_colors(n: int = 64) -> List[str]:
168
+ """``n`` evenly spaced hex colors from a blue-white-red diverging map."""
169
+ return _palette(_BLUERED_ANCHORS, n)
170
+
171
+
172
+ def sinha_colors(n: int = 64) -> List[str]:
173
+ """``n`` evenly spaced hex colors from a Sinha-style diverging map."""
174
+ return _palette(_SINHA_ANCHORS, n)
175
+
176
+
177
+ def resolve_colorscale(name: str = "Parula", n: int = 64) -> List[Tuple[float, str]]:
178
+ """
179
+ Return a plotly-style colorscale: a list of ``(value, hex)`` stops with
180
+ ``value`` running over ``[0, 1]``. Useful for 3D / heatmap / contour plots.
181
+ """
182
+ colors = _palette(_REGISTRY[name.lower()], n)
183
+ values = np.linspace(0.0, 1.0, len(colors))
184
+ return [(float(v), c) for v, c in zip(values, colors)]
185
+
186
+
187
+ __all__ = [
188
+ "get_cmap",
189
+ "parula_colors",
190
+ "matlab_jet_colors",
191
+ "turbo_colors",
192
+ "bluered_colors",
193
+ "sinha_colors",
194
+ "resolve_colorscale",
195
+ ]