tvccointreg 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.
- tvccointreg-0.1.0/LICENSE +21 -0
- tvccointreg-0.1.0/PKG-INFO +516 -0
- tvccointreg-0.1.0/README.md +480 -0
- tvccointreg-0.1.0/pyproject.toml +66 -0
- tvccointreg-0.1.0/setup.cfg +4 -0
- tvccointreg-0.1.0/tests/test_cointegration.py +44 -0
- tvccointreg-0.1.0/tests/test_decomposition.py +43 -0
- tvccointreg-0.1.0/tests/test_estimation.py +43 -0
- tvccointreg-0.1.0/tvccointreg/__init__.py +72 -0
- tvccointreg-0.1.0/tvccointreg/cointegration.py +119 -0
- tvccointreg-0.1.0/tvccointreg/colormaps.py +195 -0
- tvccointreg-0.1.0/tvccointreg/core.py +330 -0
- tvccointreg-0.1.0/tvccointreg/datasets.py +125 -0
- tvccointreg-0.1.0/tvccointreg/drivers.py +105 -0
- tvccointreg-0.1.0/tvccointreg/estimation.py +164 -0
- tvccointreg-0.1.0/tvccointreg/plotting.py +189 -0
- tvccointreg-0.1.0/tvccointreg/tables.py +199 -0
- tvccointreg-0.1.0/tvccointreg.egg-info/PKG-INFO +516 -0
- tvccointreg-0.1.0/tvccointreg.egg-info/SOURCES.txt +20 -0
- tvccointreg-0.1.0/tvccointreg.egg-info/dependency_links.txt +1 -0
- tvccointreg-0.1.0/tvccointreg.egg-info/requires.txt +12 -0
- tvccointreg-0.1.0/tvccointreg.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dr 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,516 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tvccointreg
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Time-Varying Coefficient regression and Generalized Cointegration (Hall, Swamy & Tavlas)
|
|
5
|
+
Author-email: Dr Merwan Roudane <merwanroudane920@gmail.com>
|
|
6
|
+
Maintainer-email: Dr Merwan Roudane <merwanroudane920@gmail.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/merwanroudane/tvccointreg
|
|
9
|
+
Project-URL: Repository, https://github.com/merwanroudane/tvccointreg
|
|
10
|
+
Project-URL: Issues, https://github.com/merwanroudane/tvccointreg/issues
|
|
11
|
+
Keywords: econometrics,cointegration,generalized-cointegration,time-varying-coefficient,random-coefficient-model,nonstationary,Swamy,coefficient-drivers,nonlinear-cointegration
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering
|
|
21
|
+
Classifier: Operating System :: OS Independent
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy>=1.21
|
|
26
|
+
Requires-Dist: scipy>=1.7
|
|
27
|
+
Requires-Dist: pandas>=1.3
|
|
28
|
+
Requires-Dist: matplotlib>=3.5
|
|
29
|
+
Provides-Extra: adf
|
|
30
|
+
Requires-Dist: statsmodels>=0.13; extra == "adf"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
33
|
+
Requires-Dist: statsmodels>=0.13; extra == "dev"
|
|
34
|
+
Requires-Dist: build; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# tvccointreg
|
|
38
|
+
|
|
39
|
+
**Time-Varying-Coefficient regression and Generalized Cointegration in Python.**
|
|
40
|
+
|
|
41
|
+
`tvccointreg` implements the *generalized cointegration* framework of
|
|
42
|
+
|
|
43
|
+
> **Hall, S. G., Swamy, P. A. V. B., & Tavlas, G. S. (2015).**
|
|
44
|
+
> *A Note on Generalizing the Concept of Cointegration.*
|
|
45
|
+
|
|
46
|
+
together with the surrounding Swamy time-varying-coefficient (TVC) literature
|
|
47
|
+
(Swamy & Mehta 1975; Swamy, Tavlas, Hall & co-authors 2003–2014; Granger 2008).
|
|
48
|
+
|
|
49
|
+
Conventional cointegration (Engle–Granger 1987) is an *inherently linear*
|
|
50
|
+
concept: it can only recover a structural relationship if that relationship
|
|
51
|
+
happens to be linear in unit-root variables. Most economic theory, however,
|
|
52
|
+
implies **nonlinear** relationships among variables that are **nonstationary but
|
|
53
|
+
not necessarily unit-root**. `tvccointreg` lets you:
|
|
54
|
+
|
|
55
|
+
- estimate a model that is **linear in variables but with time-varying
|
|
56
|
+
coefficients** — the Swamy–Mehta / Granger representation of *any* nonlinear
|
|
57
|
+
relationship (eq. 7);
|
|
58
|
+
- **decompose** each coefficient into a **bias-free structural component** plus
|
|
59
|
+
**omitted-variable bias** and **measurement-error bias** using *coefficient
|
|
60
|
+
drivers* (eq. 8);
|
|
61
|
+
- **test for generalized cointegration** — i.e. whether the bias-free structural
|
|
62
|
+
partial derivative is nonzero — with **standard** χ²/normal inference (no
|
|
63
|
+
Dickey–Fuller tables);
|
|
64
|
+
- produce **journal-quality tables** (text / LaTeX booktabs / HTML) and
|
|
65
|
+
**publication-quality plots** with the MATLAB **Parula** colormap by default.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Table of contents
|
|
70
|
+
|
|
71
|
+
- [Installation](#installation)
|
|
72
|
+
- [The 60-second tour](#the-60-second-tour)
|
|
73
|
+
- [Concepts: how the paper maps to the code](#concepts-how-the-paper-maps-to-the-code)
|
|
74
|
+
- [The estimator](#the-estimator)
|
|
75
|
+
- [API reference](#api-reference)
|
|
76
|
+
- [Detailed syntax](#detailed-syntax)
|
|
77
|
+
- [Visualizations](#visualizations)
|
|
78
|
+
- [Worked example: spurious vs. real](#worked-example-spurious-vs-real)
|
|
79
|
+
- [Testing](#testing)
|
|
80
|
+
- [Citing](#citing)
|
|
81
|
+
- [References](#references)
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Installation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# from a clone of the repo
|
|
89
|
+
git clone https://github.com/merwanroudane/tvccointreg.git
|
|
90
|
+
cd tvccointreg
|
|
91
|
+
pip install -e .
|
|
92
|
+
|
|
93
|
+
# with the optional ADF / test extras
|
|
94
|
+
pip install -e ".[dev]"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Requirements: `numpy`, `scipy`, `pandas`, `matplotlib`. `statsmodels` is optional
|
|
98
|
+
(used only for the ADF stationarity diagnostic and some examples).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## The 60-second tour
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from tvccointreg import TVCModel, DriverSpec
|
|
106
|
+
from tvccointreg.datasets import simulate_nonlinear_cointegration
|
|
107
|
+
|
|
108
|
+
# 1) A nonlinear relationship between nonstationary variables.
|
|
109
|
+
sim = simulate_nonlinear_cointegration(T=300, seed=1, nonlinearity=0.4)
|
|
110
|
+
|
|
111
|
+
# 2) Partition the drivers into the three sets (Assumption 1 / eq. 8).
|
|
112
|
+
spec = DriverSpec(
|
|
113
|
+
names=list(sim.drivers.columns),
|
|
114
|
+
bias_free=["x_lag", "y_lag"], # true coefficient variation
|
|
115
|
+
omitted=["w"], # omitted-variable bias
|
|
116
|
+
measurement=[], # measurement-error bias
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# 3) Fit by iteratively rescaled GLS.
|
|
120
|
+
res = TVCModel(sim.y, sim.X, sim.drivers, driver_spec=spec).fit()
|
|
121
|
+
|
|
122
|
+
# 4) Journal table + generalized cointegration test.
|
|
123
|
+
print(res.summary())
|
|
124
|
+
res.coint_test()
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
==============================================================================
|
|
129
|
+
Time-Varying-Coefficient Regression / Generalized Cointegration
|
|
130
|
+
==============================================================================
|
|
131
|
+
Dep. variable: y No. observations: 300
|
|
132
|
+
No. coefficients: 2 No. drivers: 3
|
|
133
|
+
Estimator: Iteratively rescaled GLS Covariance: GLS
|
|
134
|
+
R-squared: 0.8311 Log-likelihood: -78.07
|
|
135
|
+
Converged: True Resid ADF p-value: 0.0000
|
|
136
|
+
==============================================================================
|
|
137
|
+
Variable Coef (mean) Bias-free Std.Err t p-value G-Coint
|
|
138
|
+
------------------------------------------------------------------------------
|
|
139
|
+
x 0.3283 0.3392 0.0698 4.8614 0.0000 *** Yes
|
|
140
|
+
==============================================================================
|
|
141
|
+
Signif.: *** p<0.01 ** p<0.05 * p<0.10
|
|
142
|
+
Bias-free = structural derivative; G-Coint via Wald test on it.
|
|
143
|
+
==============================================================================
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
On this DGP the recovered bias-free coefficient correlates **0.99** with the
|
|
147
|
+
true time-varying derivative, and the residuals are stationary (ADF p ≈ 0) —
|
|
148
|
+
exactly the standard-inference result the paper proves.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Concepts: how the paper maps to the code
|
|
153
|
+
|
|
154
|
+
| Paper (Hall–Swamy–Tavlas 2015) | Symbol | In `tvccointreg` |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| TVC model, eq. (7) | `y_t = γ_0t + γ_1t x_1t + … + γ_{K-1,t} x_{K-1,t}` | `TVCModel(y, X, drivers)` |
|
|
157
|
+
| Coefficient-driver eq., eq. (8) | `γ_jt = π_j0 + Σ_d π_jd z_dt + ε_jt` | `DriverSpec(...)` + the drivers matrix |
|
|
158
|
+
| Three components of a coefficient | bias-free / omitted-variable / measurement-error | `DriverSpec(bias_free=, omitted=, measurement=)` |
|
|
159
|
+
| Concentrated model | `y_t = w_t'π + u_t`, `u_t = Σ_j x_jt ε_jt` | `estimation.build_design`, `estimation.irgls` |
|
|
160
|
+
| Variance structure | `Var(u_t) = Σ_j σ_j² x_jt²` | `estimation.variance_components` (Hildreth–Houck–Swamy) |
|
|
161
|
+
| Consistent estimator (§3.3) | iteratively rescaled GLS | `TVCModel.fit(method="irgls")` |
|
|
162
|
+
| Bias-free component | `γ_jt^BF = π_j0 + Σ_{d∈BF} π_jd z_dt` | `res.bias_free_coefficients()` |
|
|
163
|
+
| Generalized cointegration, eqs. (5)–(6) | `∂y/∂x ≠ 0` ⇔ bias-free component ≠ 0 | `res.coint_test()` |
|
|
164
|
+
| Standard inference (§3.3) | χ² Wald / normal, **not** Dickey–Fuller | Wald + delta-method t in `cointegration.py` |
|
|
165
|
+
| Drivers vs. instruments (Table 1) | drivers *should* be correlated with the misspecification | the `omitted` / `measurement` sets |
|
|
166
|
+
|
|
167
|
+
### The definition, in one line
|
|
168
|
+
|
|
169
|
+
> *y* and *x* are **generalized-cointegrated** if, holding all other relevant
|
|
170
|
+
> preexisting conditions *w* constant, the **bias-free** part of `∂y/∂x` is
|
|
171
|
+
> nonzero.
|
|
172
|
+
|
|
173
|
+
Cointegration is thus a property of the **real-world structural relationship**,
|
|
174
|
+
not of any particular statistical model — and it survives nonlinearity *and*
|
|
175
|
+
omitted regressors.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## The estimator
|
|
180
|
+
|
|
181
|
+
Substituting the driver equation (8) into the TVC model (7) gives a model that
|
|
182
|
+
is linear in the stacked parameter vector `π`:
|
|
183
|
+
|
|
184
|
+
```
|
|
185
|
+
y_t = w_t' π + u_t , w_t = ( x_0t·z_t , x_1t·z_t , … ) , u_t = Σ_j x_jt ε_jt
|
|
186
|
+
Var(u_t | x_t) = Σ_j σ_j² x_jt² (Hildreth–Houck–Swamy structure)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`tvccointreg` then:
|
|
190
|
+
|
|
191
|
+
1. **OLS** start on the concentrated design `W`.
|
|
192
|
+
2. **Variance components** `σ_j²` from a non-negative regression of squared
|
|
193
|
+
residuals on squared regressors (`scipy.optimize.nnls`).
|
|
194
|
+
3. **GLS** with weights `1/h_t`, `h_t = Σ_j σ_j² x_jt²`; iterate to convergence
|
|
195
|
+
(*iteratively rescaled GLS*).
|
|
196
|
+
4. **Recover** the time-varying coefficients
|
|
197
|
+
`γ_jt = z_t'π_j + ε̂_jt`, where the random part is the best linear predictor
|
|
198
|
+
`ε̂_jt = σ_j² x_jt u_t / h_t`.
|
|
199
|
+
5. **Decompose** `γ_jt` into bias-free / omitted / measurement parts using the
|
|
200
|
+
driver-set masks, and **test** the bias-free block.
|
|
201
|
+
|
|
202
|
+
Inference uses the GLS covariance `(W'Ω⁻¹W)⁻¹` (the paper's standard-inference
|
|
203
|
+
result) or a heteroskedasticity-robust sandwich (`cov_type="robust"`).
|
|
204
|
+
|
|
205
|
+
> **Note / honest caveat.** As in the paper, validity hinges on *Assumption 1* —
|
|
206
|
+
> that the chosen drivers genuinely span the bias components. This is an
|
|
207
|
+
> identifying assumption, not something the data can confirm; choose drivers that
|
|
208
|
+
> are plausibly correlated with the suspected misspecification (Table 1).
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## API reference
|
|
213
|
+
|
|
214
|
+
### `TVCModel(y, X, drivers, driver_spec=None, driver_sets=None, add_const=True, index=None)`
|
|
215
|
+
Build a model. `y` (T,), `X` (T, K−1), `drivers` (T, m). A constant regressor and
|
|
216
|
+
a constant driver are added automatically. Accepts NumPy arrays or pandas
|
|
217
|
+
objects (column names are picked up automatically).
|
|
218
|
+
|
|
219
|
+
- **`.fit(method="irgls", max_iter=100, tol=1e-8, cov_type="gls", verbose=False)`**
|
|
220
|
+
→ `TVCResults`.
|
|
221
|
+
|
|
222
|
+
### `DriverSpec(names, bias_free=[], omitted=[], measurement=[])`
|
|
223
|
+
Three-set partition of the drivers. Any driver not listed defaults to
|
|
224
|
+
`bias_free`. `.describe()` prints the partition.
|
|
225
|
+
|
|
226
|
+
### `TVCResults`
|
|
227
|
+
| Method | Returns |
|
|
228
|
+
|---|---|
|
|
229
|
+
| `.summary(fmt="text"|"latex"|"html")` | journal-style table |
|
|
230
|
+
| `.coef_table()` | per-regressor average coefficient frame |
|
|
231
|
+
| `.coint_test(alpha=0.05, skip_const=True)` | tidy DataFrame of test results (and `.coint_results_`) |
|
|
232
|
+
| `.tv_coefficients(include_random=True)` | T×K time-varying coefficients |
|
|
233
|
+
| `.bias_free_coefficients()` | T×K bias-free (structural) coefficients |
|
|
234
|
+
| `.components(regressor)` | bias-free / omitted / measurement / random / total |
|
|
235
|
+
| `.coefficient_se()` | pointwise standard errors |
|
|
236
|
+
| `.diagnostics()` | R², log-lik, convergence, residual ADF |
|
|
237
|
+
| `.plot_coefficients(...)` | TVC paths with CI bands |
|
|
238
|
+
| `.plot_decomposition(regressor)` | the three-component decomposition |
|
|
239
|
+
| `.plot_fit()` | actual vs fitted + residuals |
|
|
240
|
+
| `.plot_coint_heatmap()` | Parula heatmap of bias-free paths |
|
|
241
|
+
|
|
242
|
+
### Datasets
|
|
243
|
+
- `simulate_nonlinear_cointegration(T, seed, nonlinearity, omitted, measurement_error)`
|
|
244
|
+
- `simulate_spurious(T, seed)`
|
|
245
|
+
|
|
246
|
+
### Colormaps
|
|
247
|
+
`parula_colors(n)`, `matlab_jet_colors(n)`, `turbo_colors(n)`, `bluered_colors(n)`,
|
|
248
|
+
`sinha_colors(n)`, `resolve_colorscale("Parula", n)`, `get_cmap("parula")`.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Detailed syntax
|
|
253
|
+
|
|
254
|
+
**Using raw NumPy arrays and reading off a specific test:**
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
import numpy as np
|
|
258
|
+
from tvccointreg import TVCModel, DriverSpec
|
|
259
|
+
|
|
260
|
+
res = TVCModel(y, X, Z, # y:(T,), X:(T,K-1), Z:(T,m)
|
|
261
|
+
driver_sets={"bias_free": ["z1"],
|
|
262
|
+
"omitted": ["z2"],
|
|
263
|
+
"measurement": ["z3"]}).fit()
|
|
264
|
+
|
|
265
|
+
ct = res.coint_test()
|
|
266
|
+
print(ct.loc["x1", "cointegrated"], ct.loc["x1", "p_value"])
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Robust covariance and OLS (homoskedastic) comparison:**
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
res_robust = TVCModel(y, X, Z, driver_spec=spec).fit(cov_type="robust")
|
|
273
|
+
res_ols = TVCModel(y, X, Z, driver_spec=spec).fit(method="ols")
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**Exporting a LaTeX table for a paper:**
|
|
277
|
+
|
|
278
|
+
```python
|
|
279
|
+
with open("table1.tex", "w") as f:
|
|
280
|
+
f.write(res.summary(fmt="latex"))
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Pulling the time-varying coefficient path of one regressor:**
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
gamma_x = res.tv_coefficients()["x"] # pandas Series indexed by t
|
|
287
|
+
bf_x = res.bias_free_coefficients()["x"]
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## Visualizations
|
|
293
|
+
|
|
294
|
+
All plots default to the MATLAB **Parula** colormap and return a matplotlib
|
|
295
|
+
`Figure`.
|
|
296
|
+
|
|
297
|
+
| | |
|
|
298
|
+
|---|---|
|
|
299
|
+
| `plot_coefficients()` | `plot_decomposition("x")` |
|
|
300
|
+
|  |  |
|
|
301
|
+
| `plot_fit()` | `plot_coint_heatmap()` |
|
|
302
|
+
|  |  |
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Worked example: spurious vs. real
|
|
307
|
+
|
|
308
|
+
`examples/spurious_vs_real.py` reproduces the central point of the paper:
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
================ SPURIOUS (independent random walks) ================
|
|
312
|
+
Naive OLS slope = 1.125 (p = 5.97e-46) <- spuriously 'significant'
|
|
313
|
+
Generalized cointegration: NO (p = 0.708)
|
|
314
|
+
|
|
315
|
+
================ GENUINE generalized cointegration ================
|
|
316
|
+
Generalized cointegration: YES (p = 1.34e-35)
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
A naive OLS regression of one random walk on another is wildly "significant",
|
|
320
|
+
yet the generalized-cointegration test on the bias-free component correctly
|
|
321
|
+
finds **no** structural relationship.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Empirical application: the US consumption function (real data)
|
|
326
|
+
|
|
327
|
+
`examples/real_data_consumption.py` applies the method to **real US quarterly
|
|
328
|
+
macro data** (`statsmodels` `macrodata`, 1959Q1–2009Q3, 202 obs after lagging).
|
|
329
|
+
We model the long-run relationship between **real personal consumption** and
|
|
330
|
+
**real disposable income** (both in logs, both strongly trending / I(1)):
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
import numpy as np, pandas as pd
|
|
334
|
+
from statsmodels.datasets import macrodata
|
|
335
|
+
from tvccointreg import TVCModel, DriverSpec
|
|
336
|
+
|
|
337
|
+
d = macrodata.load_pandas().data
|
|
338
|
+
d.index = pd.period_range("1959Q1", periods=len(d), freq="Q")
|
|
339
|
+
zscore = lambda s: (s - s.mean()) / s.std(ddof=0)
|
|
340
|
+
|
|
341
|
+
y = np.log(d["realcons"]).rename("log_cons")
|
|
342
|
+
X = np.log(d[["realdpi"]]).rename(columns={"realdpi": "log_inc"})
|
|
343
|
+
drivers = pd.DataFrame({
|
|
344
|
+
"inc_lag": zscore(np.log(d["realdpi"]).shift(1)),
|
|
345
|
+
"trend": zscore(pd.Series(np.arange(len(d)), index=d.index)),
|
|
346
|
+
"realint": zscore(d["realint"]),
|
|
347
|
+
"unemp": zscore(d["unemp"]),
|
|
348
|
+
"cons_lag": zscore(np.log(d["realcons"]).shift(1)),
|
|
349
|
+
})
|
|
350
|
+
df = pd.concat([y, X, drivers], axis=1).dropna()
|
|
351
|
+
|
|
352
|
+
spec = DriverSpec(
|
|
353
|
+
names=["inc_lag", "trend", "realint", "unemp", "cons_lag"],
|
|
354
|
+
bias_free=["inc_lag", "trend"], # true elasticity variation
|
|
355
|
+
omitted=["realint", "unemp"], # interest-rate / labour-market channels
|
|
356
|
+
measurement=["cons_lag"], # dynamics / measurement
|
|
357
|
+
)
|
|
358
|
+
res = TVCModel(df["log_cons"], df[["log_inc"]], df[spec.names],
|
|
359
|
+
driver_spec=spec, index=df.index.astype(str)).fit()
|
|
360
|
+
print(res.summary())
|
|
361
|
+
print(res.coint_test())
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
**Result (verbatim output):**
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
==============================================================================
|
|
368
|
+
Time-Varying-Coefficient Regression / Generalized Cointegration
|
|
369
|
+
==============================================================================
|
|
370
|
+
Dep. variable: log_cons No. observations: 202
|
|
371
|
+
No. coefficients: 2 No. drivers: 5
|
|
372
|
+
Estimator: Iteratively rescaled GLS Covariance: GLS
|
|
373
|
+
R-squared: 0.9999 Log-likelihood: 752.32
|
|
374
|
+
Converged: True Resid ADF p-value: 0.0000
|
|
375
|
+
==============================================================================
|
|
376
|
+
Variable Coef (mean) Bias-free Std.Err t p-value G-Coint
|
|
377
|
+
------------------------------------------------------------------------------
|
|
378
|
+
log_inc 0.3886 0.3886 0.0528 7.3626 0.0000 *** Yes
|
|
379
|
+
==============================================================================
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
| regressor | avg_bias_free | std_err | t_stat | wald | df | p_value | cointegrated |
|
|
383
|
+
|---|---|---|---|---|---|---|---|
|
|
384
|
+
| log_inc | 0.3886 | 0.0528 | 7.3626 | 56.9504 | 3 | 0.0000 | **True** |
|
|
385
|
+
|
|
386
|
+
**Reading the result.**
|
|
387
|
+
|
|
388
|
+
- **Generalized cointegration is confirmed** between consumption and income
|
|
389
|
+
(Wald = 56.95, 3 df, *p* ≈ 0): the bias-free structural elasticity is firmly
|
|
390
|
+
nonzero, so the long-run relationship is genuine, not spurious.
|
|
391
|
+
- **Standard inference is valid here**: the composite residuals are stationary
|
|
392
|
+
(ADF *p* = 1.9 × 10⁻⁷), exactly the condition under which Hall–Swamy–Tavlas
|
|
393
|
+
show the TVC test uses ordinary χ²/normal critical values rather than
|
|
394
|
+
Dickey–Fuller ones.
|
|
395
|
+
- The **bias-free income elasticity drifts down over the sample**, from **0.43
|
|
396
|
+
(1959Q2)** to **0.34 (2009Q3)** — the *direct* income channel after the
|
|
397
|
+
dynamics (`cons_lag`) and omitted business-cycle conditions (`realint`,
|
|
398
|
+
`unemp`) are stripped out into the bias terms. A declining direct sensitivity
|
|
399
|
+
of consumption to current income over five decades is consistent with greater
|
|
400
|
+
consumption smoothing / financial deepening.
|
|
401
|
+
|
|
402
|
+
| Bias-free income elasticity over time | Coefficient decomposition |
|
|
403
|
+
|---|---|
|
|
404
|
+
|  |  |
|
|
405
|
+
|
|
406
|
+
Run it yourself:
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
python examples/real_data_consumption.py
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## Testing
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
pip install -e ".[dev]"
|
|
418
|
+
pytest -q
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
The suite checks coefficient recovery, that the three components sum exactly to
|
|
422
|
+
the total coefficient, detection of genuine generalized cointegration, rejection
|
|
423
|
+
of spurious relationships, and the Parula palette/colorscale helpers.
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
## Publishing (maintainer notes)
|
|
428
|
+
|
|
429
|
+
The package is PyPI-ready (`python -m build` + `twine check` both pass).
|
|
430
|
+
|
|
431
|
+
**First release — manual upload with an API token**
|
|
432
|
+
|
|
433
|
+
1. Create accounts on [TestPyPI](https://test.pypi.org/) and
|
|
434
|
+
[PyPI](https://pypi.org/), and generate an API token for each
|
|
435
|
+
(Account settings → API tokens).
|
|
436
|
+
2. Build and check:
|
|
437
|
+
```bash
|
|
438
|
+
python -m build
|
|
439
|
+
python -m twine check dist/*
|
|
440
|
+
```
|
|
441
|
+
3. Dry-run on TestPyPI first, then install from there to confirm:
|
|
442
|
+
```bash
|
|
443
|
+
python -m twine upload --repository testpypi dist/*
|
|
444
|
+
pip install --index-url https://test.pypi.org/simple/ \
|
|
445
|
+
--extra-index-url https://pypi.org/simple/ tvccointreg
|
|
446
|
+
```
|
|
447
|
+
4. Upload to the real PyPI:
|
|
448
|
+
```bash
|
|
449
|
+
python -m twine upload dist/*
|
|
450
|
+
```
|
|
451
|
+
When prompted, use `__token__` as the username and the API token (starting
|
|
452
|
+
with `pypi-`) as the password.
|
|
453
|
+
|
|
454
|
+
**Subsequent releases — automated via Trusted Publishing (recommended)**
|
|
455
|
+
|
|
456
|
+
`.github/workflows/publish.yml` publishes automatically when you publish a
|
|
457
|
+
GitHub Release. One-time setup: on PyPI add a *Trusted Publisher*
|
|
458
|
+
(Project → Publishing) for repository `merwanroudane/tvccointreg`, workflow
|
|
459
|
+
`publish.yml`, environment `pypi`. After that, no tokens or secrets are needed —
|
|
460
|
+
just bump `version` in `pyproject.toml` and `__version__`, tag, and publish a
|
|
461
|
+
Release.
|
|
462
|
+
|
|
463
|
+
> **Remember:** a version number can only be uploaded to PyPI **once**. Bump the
|
|
464
|
+
> version for every release.
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Citing
|
|
469
|
+
|
|
470
|
+
If you use this package, please cite both the package and the underlying paper.
|
|
471
|
+
|
|
472
|
+
```bibtex
|
|
473
|
+
@software{roudane_tvccointreg,
|
|
474
|
+
author = {Merwan Roudane},
|
|
475
|
+
title = {tvccointreg: Time-Varying-Coefficient Regression and
|
|
476
|
+
Generalized Cointegration in Python},
|
|
477
|
+
year = {2026},
|
|
478
|
+
url = {https://github.com/merwanroudane/tvccointreg}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
@incollection{hall_swamy_tavlas_2015,
|
|
482
|
+
author = {Hall, Stephen G. and Swamy, P. A. V. B. and Tavlas, George S.},
|
|
483
|
+
title = {A Note on Generalizing the Concept of Cointegration},
|
|
484
|
+
year = {2015}
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## References
|
|
491
|
+
|
|
492
|
+
- Hall, S. G., Swamy, P. A. V. B., & Tavlas, G. S. (2015). *A Note on
|
|
493
|
+
Generalizing the Concept of Cointegration.*
|
|
494
|
+
- Hall, S. G., Swamy, P. A. V. B., & Tavlas, G. S. (2014). *Time Varying
|
|
495
|
+
Coefficient Models; A Proposal for Selecting the Coefficient Driver Sets.*
|
|
496
|
+
University of Leicester WP 14/18.
|
|
497
|
+
- Swamy, P. A. V. B., & Mehta, J. S. (1975). *Bayesian and non-Bayesian Analysis
|
|
498
|
+
of Switching Regressions and of Random Coefficient Regression Models.* JASA.
|
|
499
|
+
- Swamy, P. A. V. B., Tavlas, G. S., Hall, S. G., et al. (2010). *Nonparametric
|
|
500
|
+
Nonstationary Regression.*
|
|
501
|
+
- Granger, C. W. J. (2008). *Non-linear Models: Where Do We Go Next —
|
|
502
|
+
Time-Varying Parameter Models?* Studies in Nonlinear Dynamics & Econometrics.
|
|
503
|
+
- Hildreth, C., & Houck, J. P. (1968). *Some Estimators for a Linear Model with
|
|
504
|
+
Random Coefficients.* JASA.
|
|
505
|
+
- Engle, R. F., & Granger, C. W. J. (1987). *Co-integration and Error
|
|
506
|
+
Correction.* Econometrica.
|
|
507
|
+
- Cramér, H. (1946). *Mathematical Methods of Statistics.*
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## Author
|
|
512
|
+
|
|
513
|
+
**Dr Merwan Roudane** · merwanroudane920@gmail.com ·
|
|
514
|
+
[github.com/merwanroudane](https://github.com/merwanroudane)
|
|
515
|
+
|
|
516
|
+
Released under the MIT License.
|