Nonparametric, time-varying and quantile causality tests (NPCQ, TVNCQ, TVGC) with journal-quality tables and plots
Project description
nonparmtvcsq
Nonparametric, time-varying and quantile causality 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 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 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 · Repo https://github.com/merwanroudane/nonparmtvcsq
Table of contents
- Why this package
- Installation
- 60-second quickstart
- The methods & the maths
- API reference & full syntax
- Mapping from R / Stata to Python
- Reproducing the reference papers
- Compatibility & validation
- Performance notes
- References
- Citation
- 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); - 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
# 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).
import nonparmtvcsq as ntc
print(ntc.__version__) # 0.1.0
60-second quickstart
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
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
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
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
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
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
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
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).
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
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
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:
- 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. - Ö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"). - 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.
Compatibility & validation
Validated against R by sourcing the nonParQuantileCausality tar.gz directly:
dpillbandwidth matchesKernSmooth::dpillto ~1e-10;lprq2matchesquantreg::rqto ~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.
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). Usestep>1and a moderatewindowto keep runs short; passprogress=Trueto watch it. - TVGC's recursive-evolving sequence is
O(T²)Wald fits per cause; the bootstrap reuses itboottimes with an automatic coarserboot_step. Lowerboot(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
@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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nonparmtvcsq-0.1.0.tar.gz.
File metadata
- Download URL: nonparmtvcsq-0.1.0.tar.gz
- Upload date:
- Size: 55.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c7d35d183aa7a445bba5b71a1db492fa7315a53b921f965490f6eb44fce6ec11
|
|
| MD5 |
4927ed64953b0fff60b1b293e17cb045
|
|
| BLAKE2b-256 |
00bd4fac3656128f5728ab10cb981cba0d67c023dbcc1f98a8b98517f413c2bf
|
File details
Details for the file nonparmtvcsq-0.1.0-py3-none-any.whl.
File metadata
- Download URL: nonparmtvcsq-0.1.0-py3-none-any.whl
- Upload date:
- Size: 48.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85241d1fcabd4e4ca05a939d5f7069d18d681c0375b2695cd72d7b8ae6734f81
|
|
| MD5 |
2d7f9d3e4f9d38c1ad0b185268403281
|
|
| BLAKE2b-256 |
fb1736e9dd247834df2ea450c8852f0d5584759228656b9355e4d18f3cf8b79e
|