Metadata-Version: 2.4
Name: nonparmtvcsq
Version: 0.1.0
Summary: Nonparametric, time-varying and quantile causality tests (NPCQ, TVNCQ, TVGC) with journal-quality tables and plots
Author-email: Merwan Roudane <merwanroudane920@gmail.com>
Maintainer-email: Merwan Roudane <merwanroudane920@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/merwanroudane/nonparmtvcsq
Project-URL: Repository, https://github.com/merwanroudane/nonparmtvcsq
Project-URL: Issues, https://github.com/merwanroudane/nonparmtvcsq/issues
Keywords: causality,quantile regression,Granger causality,nonparametric,time-varying,econometrics,causality in quantiles
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering :: Mathematics
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.21
Requires-Dist: scipy>=1.7
Requires-Dist: pandas>=1.3
Requires-Dist: statsmodels>=0.13
Requires-Dist: matplotlib>=3.4
Requires-Dist: plotly>=5.0
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == "test"
Dynamic: license-file

# nonparmtvcsq

**Non**para**m**etric, **t**ime-**v**arying and quantile **c**ausality tests in
Python — with publication-quality tables and visualisations.

> One library for three causality toolkits used across the applied-econometrics
> literature, faithfully reproduced from their R and Stata sources and unified
> behind one clean API.

| Tool | What it does | Source it reproduces |
|---|---|---|
| **NPCQ** &nbsp;`np_quantile_causality` | Nonparametric causality-**in-quantiles** (mean / variance), single sample | R package `nonParQuantileCausality` (Balcilar) — validated to machine precision |
| **TVNCQ** `tv_np_quantile_causality` | **Time-varying** (rolling-window) causality-in-quantiles → time×quantile surface | Olasehinde-Williams/Özkan/Adebayo papers (2023–2026) |
| **TVGC** &nbsp;`tvgc` | **Time-varying Granger causality** (forward / rolling / recursive-evolving Wald) | Stata command `tvgc` (Otero & Baum); Shi–Phillips–Hurn |

Author: **Dr Merwan Roudane** · merwanroudane920@gmail.com ·
GitHub [@merwanroudane](https://github.com/merwanroudane) ·
Repo <https://github.com/merwanroudane/nonparmtvcsq>

---

## Table of contents

1. [Why this package](#why-this-package)
2. [Installation](#installation)
3. [60-second quickstart](#60-second-quickstart)
4. [The methods & the maths](#the-methods--the-maths)
5. [API reference & full syntax](#api-reference--full-syntax)
   - [`np_quantile_causality` (NPCQ)](#1-np_quantile_causality--npcq)
   - [`tv_np_quantile_causality` (TVNCQ)](#2-tv_np_quantile_causality--tvncq)
   - [`tvgc` (TVGC)](#3-tvgc--time-varying-granger-causality)
   - [Result objects](#result-objects)
   - [Tables](#tables)
   - [Plots & colors](#plots--colors)
   - [Datasets](#datasets)
   - [Numerical building blocks](#numerical-building-blocks)
6. [Mapping from R / Stata to Python](#mapping-from-r--stata-to-python)
7. [Reproducing the reference papers](#reproducing-the-reference-papers)
8. [Compatibility & validation](#compatibility--validation)
9. [Performance notes](#performance-notes)
10. [References](#references)
11. [Citation](#citation)
12. [License](#license)

---

## Why this package

The three reference papers all use the **same engine** — the Jeong–Härdle–Wang
(2012) nonparametric causality-in-quantiles test, extended by Balcilar et al.
(2018) to **mean and variance**, and made **time-varying** by rolling windows.
The R package only ships the single-sample test; the time-varying layer lived in
ad-hoc scripts; and `tvgc` (the parametric time-varying Granger test) lived in
Stata. `nonparmtvcsq` puts all of it in one Python library with:

- exact numerical compatibility with the R source (see
  [`docs/COMPATIBILITY.md`](docs/COMPATIBILITY.md));
- **beautiful, journal-ready output** — Parula/Jet/Turbo colormaps, 3-D
  surfaces, heatmaps, contour plots and LaTeX/HTML tables;
- a single, documented, well-tested API.

---

## Installation

```bash
# from source (this folder)
pip install .

# or editable for development
pip install -e ".[test]"
```

**Requirements:** Python ≥ 3.9, `numpy`, `scipy`, `pandas`, `statsmodels`,
`matplotlib`, `plotly` (all installed automatically).

```python
import nonparmtvcsq as ntc
print(ntc.__version__)   # 0.1.0
```

---

## 60-second quickstart

```python
import numpy as np
import pandas as pd
import nonparmtvcsq as ntc

# bundled daily Gold/Oil prices (3,237 obs) -> log returns
df  = np.log(ntc.load_gold_oil()).diff().dropna()
oil, gold = df["Oil"].values, df["Gold"].values

# 1) NPCQ: does Oil_{t-1} cause Gold_t across quantiles? (mean & variance)
m = ntc.np_quantile_causality(oil, gold, type="mean")
v = ntc.np_quantile_causality(oil, gold, type="variance")
print(m.summary())            # journal-style console table
m.plot(savepath="npcq.png")   # statistic vs quantile + 5% CV band

# 2) TVNCQ: time-varying version -> a (time x quantile) surface
tv = ntc.tv_np_quantile_causality(oil, gold, type="mean",
                                  window=250, step=10,
                                  time_index=df.index.values)
tv.plot_heatmap(savepath="tvncq.png")          # Parula heatmap
tv.plot_surface().write_html("tvncq_3d.html")  # interactive 3-D

# 3) TVGC: time-varying Granger causality (forward/rolling/recursive)
g = ntc.tvgc(pd.DataFrame({"Gold": gold, "Oil": oil}),
             depvar="Gold", causes=["Oil"], p=2, d=1, boot=199, seed=1)
print(g.summary())
g.plot(var="Oil", savepath="tvgc.png")
```

---

## The methods & the maths

### NPCQ — causality in quantiles

For a candidate cause `x` and effect `y`, define `Q_θ(y_t | ·)` the θ-conditional
quantile. The null of **no causality at quantile θ** is

```
H0:  P{ F_{y_t | x_{t-1}, y_{t-1}}( Q_θ(y_t | y_{t-1}) | x_{t-1}, y_{t-1} ) = θ } = 1
```

i.e. once you know `y_{t-1}`, adding `x_{t-1}` does not change the conditional
quantile. The feasible test statistic is a kernel quadratic form in the
indicator residuals `ε̂ = 1{y_t ≤ Q̂_θ(y_t|y_{t-1})} − θ`,

```
Ĵ_θ  ∝  ε̂ᵀ K ε̂ ,   K = Gaussian product kernel over (y_{t-1}, x_{t-1})
```

rescaled to an asymptotic **N(0, 1)** statistic, so `|stat| > 1.96` rejects at
5%. With `type="variance"` the series are squared first (the Balcilar et al.
m = 2 extension), giving **causality in variance / volatility**.

The conditional quantile `Q̂_θ` is a local-linear quantile regression
(`quantreg::rq` in R; an exact LP here), the bandwidth is the Ruppert–Sheather–
Wand plug-in (`KernSmooth::dpill`, ported exactly), and one lag of each series is
used (first-order Granger set-up).

### TVNCQ — time-varying causality in quantiles

Structural change breaks the constant-relationship assumption. The hybrid
**rolling-windows** approach computes the NPCQ statistic inside every fixed-width
sub-sample, producing a `(n_windows × n_quantiles)` surface of statistics indexed
by *(time, quantile)* — exactly the 3-D plots in the reference papers.

### TVGC — time-varying Granger causality

A parametric counterpart: in a **lag-augmented VAR** (Toda–Yamamoto, so it is
robust to unit roots) it computes three sequences of Wald statistics for
`H0: x does not Granger-cause y`:

- **Forward expanding (FE)** — start fixed, end grows;
- **Rolling (RO)** — fixed-width window slides;
- **Recursive evolving (RE)** — supremum over all start/end pairs (Shi–Phillips–
  Hurn sup test).

Critical values come from a fixed-regressor **wild bootstrap** under the null.

---

## API reference & full syntax

### 1. `np_quantile_causality` — NPCQ

```python
ntc.np_quantile_causality(
    x, y,                     # 1-D arrays, same length: cause x, effect y
    type="mean",              # "mean" (m=1) or "variance" (m=2)
    q=None,                   # quantile grid; default np.arange(0.05, 0.96, 0.05)
    hm=None,                  # fixed base bandwidth; None -> dpill plug-in
    cv=1.96,                  # reference critical value (5% two-sided N(0,1))
    method="lp",              # "lp" exact (R-compatible) | "sm" statsmodels (fast)
) -> NPCQResult
```

```python
res = ntc.np_quantile_causality(oil, gold, type="mean",
                                q=np.arange(0.1, 0.91, 0.1))
res.statistic            # np.ndarray of N(0,1) statistics, one per quantile
res.quantiles            # the grid
res.bandwidth            # base bandwidth used
res.significant_quantiles(cv=1.96)   # quantiles where H0 is rejected
res.to_frame()           # tidy DataFrame (quantile, statistic, reject_5pct)
print(res.summary())     # console table with significance stars
res.plot()               # matplotlib figure
res.plot_interactive()   # plotly figure
```

### 2. `tv_np_quantile_causality` — TVNCQ

```python
ntc.tv_np_quantile_causality(
    x, y,
    type="mean",
    q=None,                   # default np.arange(0.05, 0.96, 0.05)
    window=None,              # window width; default floor(0.25*T)
    hm=None,                  # fixed base bandwidth (None -> per-window dpill)
    time_index=None,          # dates/labels aligned to x; surface uses window end
    step=1,                   # step between window starts
    cv=1.96,
    method="sm",              # "sm" fast default | "lp" exact (slower)
    progress=False,           # True -> progress line; or a callable(i, n)
) -> TVNCQResult
```

```python
tv = ntc.tv_np_quantile_causality(oil, gold, window=250, step=10,
                                  time_index=df.index.values, progress=True)
tv.statistic             # (n_windows, n_quantiles) matrix
tv.reject_share()        # fraction of windows rejecting H0, per quantile
tv.to_frame()            # long tidy DataFrame (time, quantile, statistic, reject)
tv.plot_heatmap()        # static Parula heatmap (matplotlib)
tv.plot_contour()        # filled contour with CV iso-line
tv.plot_surface()        # interactive 3-D Plotly surface
```

### 3. `tvgc` — time-varying Granger causality

```python
ntc.tvgc(
    data,                     # DataFrame (cols = series) or 2-D array
    depvar=None,              # dependent variable name (default: first column)
    causes=None,              # candidate causes (default: all other columns)
    p=2,                      # VAR lag order (the lags actually tested)
    d=1,                      # extra Toda-Yamamoto augmentation lags (untested)
    window=None,              # min / rolling width; default floor(0.2*T)
    trend=False,              # include a linear trend (besides the constant)
    robust=False,             # White heteroskedasticity-robust covariance
    boot=199,                 # bootstrap reps for critical values (>=20)
    seed=None,
    step=1,                   # step in the test sequences
    boot_step=None,           # coarser step inside the bootstrap (auto if None)
    time_index=None,          # labels aligned to data rows
) -> TVGCResult
```

```python
g = ntc.tvgc(pd.DataFrame({"Gold": gold, "Oil": oil}),
             depvar="Gold", causes=["Oil"], p=2, d=1, boot=199, seed=1)
g.sup                    # (n_causes, 3) max Wald for FE / RO / RE
g.cv90, g.cv95, g.cv99   # bootstrap critical values, same shape
g.to_frame()             # summary DataFrame
g.series_frame("Oil")    # per-window FE/RO/RE series for one cause
print(g.summary())       # console table with bootstrap CVs and significance
g.plot(var="Oil")        # 3-panel FE/RO/RE figure with CV lines
```

### Result objects

| Class | Key attributes | Methods |
|---|---|---|
| `NPCQResult` | `statistic, quantiles, bandwidth, type, n, cv` | `to_frame, summary, significant_quantiles, plot, plot_interactive` |
| `TVNCQResult` | `statistic (T×Q), quantiles, window, time_index` | `to_frame, reject_share, plot_heatmap, plot_contour, plot_surface` |
| `TVGCResult` | `sup, cv90/95/99, forward/rolling/recursive, ends` | `to_frame, series_frame, summary, plot` |

### Tables

```python
from nonparmtvcsq import tables
print(tables.npcq_summary_table(res))         # plain text (also res.summary())
print(tables.npcq_to_latex(res, label="t1"))  # LaTeX table environment
print(tables.tvncq_summary_table(tv))
print(tables.tvgc_summary_table(g))
print(tables.tvgc_to_latex(g))
res.to_frame().to_html("npcq.html")           # any pandas exporter works
```

### Plots & colors

Static figures use a serif "journal" theme; surfaces/heatmaps/contours default
to **Parula**. All plot functions accept `savepath=...` (300 dpi PNG/PDF/SVG).

```python
from nonparmtvcsq import plotting, colors

plotting.plot_npcq(res, cv=1.96, color="#1f4e79", savepath="fig.pdf")
plotting.plot_tvncq_heatmap(tv, colorscale="Parula")
plotting.plot_tvncq_surface(tv, colorscale="Turbo")
plotting.plot_tvgc(g, var="Oil")
plotting.show_colormaps()                      # gradient reference card

colors.parula_colors(64)      # 64 hex stops of MATLAB R2014b Parula
colors.matlab_jet_colors(n)   # jet
colors.turbo_colors(n)        # turbo
colors.bluered_colors(n)      # diverging blue-white-red
colors.sinha_colors(n)        # Sinha et al. (2023) style palette
colors.get_cmap("Parula")     # matplotlib ListedColormap
colors.resolve_colorscale("Parula", 32)        # Plotly colorscale
```

### Datasets

```python
ntc.load_gold_oil()                # DataFrame: Gold, Oil (3237 daily obs)
ntc.simulate_causal(n=400, beta=0.5, type="mean", seed=1)   # (x, y) with x->y
```

### Numerical building blocks

```python
ntc.dpill(x, y, gridsize=None)                 # RSW plug-in bandwidth (= R)
ntc.silverman_bandwidth(x)
ntc.lprq2(x, y, h, tau, x0, method="lp")        # local-linear quantile smoother
ntc.weighted_quantile_regression(X, y, tau, weights=None, method="lp")
```

---

## Mapping from R / Stata to Python

**R `nonParQuantileCausality` → Python**

| R | Python |
|---|---|
| `np_quantile_causality(x, y, type="mean", q, hm)` | `ntc.np_quantile_causality(x, y, type="mean", q=..., hm=...)` |
| `plot(obj)` | `obj.plot()` |
| `lprq2_(...)` (internal) | `ntc.lprq2(...)` |
| `KernSmooth::dpill(x, y)` | `ntc.dpill(x, y)` |
| `data(gold_oil)` | `ntc.load_gold_oil()` |

**Stata `tvgc` → Python**

| Stata `tvgc y x, ...` | Python `ntc.tvgc(...)` |
|---|---|
| `p(2)` / `d(1)` | `p=2` / `d=1` |
| `window(k)` | `window=k` |
| `trend` | `trend=True` |
| `robust` | `robust=True` |
| `boot(199)` / `seed(1)` | `boot=199` / `seed=1` |
| `r(gcres)` (max Wald FE/RO/RE) | `g.sup` |
| `r(gccv95)` | `g.cv95` |
| `graph` panels | `g.plot(var=...)` |

---

## Reproducing the reference papers

The package implements the methodology of:

1. **Olasehinde-Williams, Olanipekun & Özkan (2024)**, *Computational Economics*
   64:947–977 — *Stock Market Response to Quantitative Easing: … Rolling Windows
   Nonparametric Causality-in-Quantiles.* → use `tv_np_quantile_causality`.
2. **Özkan & Adebayo (2026)**, *Environment, Development and Sustainability* —
   *…time-varying asymmetric impact of fossil-fuel price volatility on high
   cleantech investments* (TVNCinQ). → `tv_np_quantile_causality(type="mean"/"variance")`.
3. **Olasehinde-Williams, Özkan & Akadiri (2023)**, *Environmental Science and
   Pollution Research* 30:55326–55339 — *Effects of climate policy uncertainty
   on sustainable investment.* → `np_quantile_causality` + `tv_np_quantile_causality`.

A full worked script is in [`examples/quickstart.py`](examples/quickstart.py).

---

## Compatibility & validation

Validated against R by sourcing the `nonParQuantileCausality` tar.gz directly:

- **`dpill` bandwidth** matches `KernSmooth::dpill` to ~**1e-10**;
- **`lprq2`** matches `quantreg::rq` to ~**1e-15**;
- **NPCQ statistic** matches to machine precision at the majority of quantiles;
  small deviations only at "tie" quantiles where the local fit interpolates the
  evaluation point (an instability present in R too).

Details and the reproduction recipe: [`docs/COMPATIBILITY.md`](docs/COMPATIBILITY.md).
Enforced by `tests/test_r_compat.py`.

---

## Performance notes

- NPCQ with `method="lp"` solves one small LP per (observation × quantile); for
  T ≈ 200 and a 0.05 grid that's a few seconds. Use a **coarser quantile grid**
  for speed.
- TVNCQ multiplies that by the number of windows — it defaults to `method="sm"`
  (fast statsmodels solver). Use `step>1` and a moderate `window` to keep runs
  short; pass `progress=True` to watch it.
- TVGC's recursive-evolving sequence is `O(T²)` Wald fits per cause; the
  bootstrap reuses it `boot` times with an automatic coarser `boot_step`. Lower
  `boot` (e.g. 99) while exploring, raise it (199–999) for final results.

---

## References

- Jeong, K., Härdle, W. K., & Wang, S. (2012). Nonparametric quantile causality.
  *Econometric Theory*, 28(4), 861–887.
- Balcilar, M., Gupta, R., & Pierdzioch, C. (2016). Does uncertainty move the
  gold price? *Resources Policy*, 49, 74–80.
  <https://doi.org/10.1016/j.resourpol.2016.04.004>
- Balcilar, M., Gupta, R., Kyei, C., & Wohar, M. E. (2016). *Open Economies
  Review*, 27(2), 229–250. <https://doi.org/10.1007/s11079-016-9388-x>
- Nishiyama, Y., Hitomi, K., Kawasaki, Y., & Jeong, K. (2011). A consistent
  nonparametric test for nonlinear causality. *J. Econometrics*, 165(1), 112–127.
- Shi, S., Phillips, P. C. B., & Hurn, S. (2018). Change detection and the causal
  impact of the yield curve. *J. Time Series Analysis*, 39(6), 966–987.
- Shi, S., Hurn, S., & Phillips, P. C. B. (2020). Causal change detection in
  possibly integrated systems. *Empirical Economics*, 59, 595–625.
- Toda, H. Y., & Yamamoto, T. (1995). Statistical inference in vector
  autoregressions with possibly integrated processes. *J. Econometrics*, 66.
- Ruppert, D., Sheather, S. J., & Wand, M. P. (1995). An effective bandwidth
  selector for local least squares regression. *JASA*, 90(432), 1257–1270.
- Olasehinde-Williams, G., Olanipekun, I., & Özkan, O. (2024). *Computational
  Economics*, 64, 947–977.
- Özkan, O., & Adebayo, T. S. (2026). *Environment, Development and
  Sustainability.*
- Olasehinde-Williams, G., Özkan, O., & Akadiri, S. S. (2023). *Environmental
  Science and Pollution Research*, 30, 55326–55339.

---

## Citation

```bibtex
@software{roudane_nonparmtvcsq_2026,
  author  = {Roudane, Merwan},
  title   = {nonparmtvcsq: Nonparametric, time-varying and quantile causality
             tests in Python},
  year    = {2026},
  version = {0.1.0},
  url     = {https://github.com/merwanroudane/nonparmtvcsq}
}
```

---

## License

MIT © 2026 Merwan Roudane. See [LICENSE](LICENSE).
