nstat.extras.spatial.marked_gof
Marked / discrete-time-rescaling goodness-of-fit (pure NumPy/SciPy).
A Python-only companion to the discrete-time / marked goodness-of-fit framework. Its centre of gravity is the discrete-time-rescaling correction of Haslinger, Pipa & Brown (2010), which fixes a real bug in the naive KS test at finite bin width.
The problem
Continuous time-rescaling says the rescaled inter-event times \(z_j = \Lambda(t_j) - \Lambda(t_{j-1})\) are i.i.d. Exp(1), so a KS test against Exp(1) (equivalently Unif(0,1) after the CDF transform) is exact. But on discretized time with bin width \(\Delta\) and per-bin spike probabilities \(p_k\), the naive bin-summed compensator is not Exp(1) even for the true model — the discreteness biases the statistic by \(O(\bar\lambda\Delta)\), so the naive KS false-rejects a correct model.
The fix (the randomized PIT)
With i.i.d. \(r_j \sim \text{Unif}(0,1)\), the corrected variate
is exactly Unif(0,1) under the true model. A KS test on \(\{u_j\}\) (averaged over several \(r_j\) draws, or compared to a Monte-Carlo reference band) has the correct size.
Mark spaces
Marked rescaling factors as (rescaled time) × (conditional mark). A finite mark space (counting reference measure → mark = channel) is the multivariate-rescaling case (Gerhard-Haslinger-Pipa 2011); a continuous mark space (Lebesgue reference measure → e.g. a waveform amplitude) is rescaled through the conditional mark CDF \(F(m \mid \cdot)\). A mis-specified \(p(m\mid\cdot)\) shows up as a mark-axis KS departure while the time axis still passes.
All functions are pure NumPy/SciPy.
References
Haslinger R, Pipa G, Brown E (2010). Discrete time rescaling theorem: determining goodness of fit for discrete time statistical models of neural spiking. Neural Computation 22(10):2477.
Gerhard F, Haslinger R, Pipa G (2011). Applying the multivariate time-rescaling theorem to neural population models. Neural Computation 23(6):1452.
Tao L et al. (2018). Marked time-rescaling for population goodness-of-fit.
Functions
|
Discrete-time-corrected uniform variates (Haslinger-Pipa-Brown 2010). |
|
Discrete-time + marked goodness-of-fit in one call. |
|
Discrete-time per-channel GoF and population coupling test, in one call. |
|
Per-channel discrete-time rescaling for a finite (channel) mark space. |
|
Autocorrelation of the rescaled-time variates with a Bartlett band. |
|
Naive bin-summed rescaled times \(1 - e^{-\sum p_k}\). |
Classes
|
Bundled result of |
|
Result of a discrete-time / marked goodness-of-fit run. |
|
Autocorrelation of the rescaled-time variates with a Bartlett band. |
- class nstat.extras.spatial.marked_gof.MarkedGOFResult(ks_uncorrected, ks_corrected, ks_mark, ks_band, inside_uncorrected, inside_corrected, inside_mark, n_events, u_corrected, u_uncorrected)[source]
Bases:
objectResult of a discrete-time / marked goodness-of-fit run.
- Variables:
ks_uncorrected (float) – KS statistic of the naive (bin-summed) rescaled times vs Unif(0,1) — biased upward at finite bin width; expected to false-reject.
ks_corrected (float) – KS statistic of the discrete-time-corrected times (averaged over the randomization draws), vs Unif(0,1) — should pass for the true model.
ks_mark (float | None) – KS statistic on the mark axis (rescaled marks through the conditional mark CDF), vs Unif(0,1).
Noneif no marks given.ks_band (float) – The two-sided KS critical value at
alphafornevents (Kolmogorov asymptoticc(alpha)/sqrt(n)).inside_uncorrected (bool) – Whether each statistic falls inside the band (
< ks_band).inside_*True ⇒ the test does NOT reject.inside_corrected (bool) – See
inside_uncorrected.inside_mark (bool | None) – See
inside_uncorrected.n_events (int) – Number of inter-event intervals used.
u_corrected (numpy.ndarray) – The corrected variates from the first randomization draw (for plotting the empirical CDF).
u_uncorrected (numpy.ndarray) – The uncorrected variates (for the side-by-side plot).
- Parameters:
- class nstat.extras.spatial.marked_gof.CoupledMarkedGOFResult(per_channel, population)[source]
Bases:
objectBundled result of
multivariate_gof_with_coupling().- Variables:
per_channel (dict[int, nstat.extras.spatial.marked_gof.MarkedGOFResult]) – Mapping
channel_index -> MarkedGOFResultreturned bymultivariate_time_rescaling()— the discrete-time-corrected KS test for each channel in isolation.population (object) –
PopulationTimeRescaleResultreturned bynstat.population_time_rescale()— the ground-process KS and marked-region Pearson \(\chi^2\) over the joint \((\tau, k)\) space, which catches cross-channel coupling misfit invisible to the per-channel test.
- Parameters:
per_channel (dict[int, MarkedGOFResult])
population (object)
- per_channel: dict[int, MarkedGOFResult]
- class nstat.extras.spatial.marked_gof.RescaledACFResult(lags, acf, band, inside_band)[source]
Bases:
objectAutocorrelation of the rescaled-time variates with a Bartlett band.
The discrete-time-rescaling KS test of
marked_time_rescaling()checks the marginal distribution of the rescaled variates (Unif(0,1)) but is blind to serial dependence (Brown et al. 2002). This dataclass carries the lag-1..``n_lags`` autocorrelation of the normal-score-transformed uniforms, \(z_j = \Phi^{-1}(u_j)\), together with the asymptotic \(\pm 1.96/\sqrt{n}\) Bartlett band (Andersen 1997; Truccolo et al. 2005) and a per-lag in-band flag.- Variables:
lags (numpy.ndarray) – Integer lags
1..n_lags, shape(n_lags,).acf (numpy.ndarray) – Sample autocorrelation of \(z_j\) at each lag, shape
(n_lags,).band (float) – Two-sided \(\pm 1.96 / \sqrt{n}\) Bartlett band — a scalar, applied symmetrically about zero.
inside_band (numpy.ndarray) – Boolean
(n_lags,)mask:Truewhere|acf| < band.
- Parameters:
- nstat.extras.spatial.marked_gof.uncorrected_rescaled(spike_bins, p_k)[source]
Naive bin-summed rescaled times \(1 - e^{-\sum p_k}\).
This is the statistic that false-rejects at finite bin width — the bug the discrete-time correction fixes. Provided so a demo can show the uncorrected curve straying outside the KS band.
- nstat.extras.spatial.marked_gof.corrected_rescaled(spike_bins, p_k, rng=None)[source]
Discrete-time-corrected uniform variates (Haslinger-Pipa-Brown 2010).
\[u_j = \Bigl[\prod_{k=k_{j-1}+1}^{k_j-1}(1-p_k)\Bigr] (1 - r_j\, p_{k_j}), \quad r_j \sim \text{Unif}(0,1).\]Exactly Unif(0,1) under the true model.
- nstat.extras.spatial.marked_gof.marked_time_rescaling(spike_bins, marks, p_k, mark_cdf=None, *, decoded=None, n_draws=25, alpha=0.05, rng=None)[source]
Discrete-time + marked goodness-of-fit in one call.
Runs the time axis both the naive (uncorrected) way and the discrete-time-corrected way (Haslinger-Pipa-Brown 2010), so the bin-width bias is visible side by side, and — if marks and a conditional mark CDF are supplied — the mark axis via \(F(m \mid \cdot)\).
Typical call:
from nstat.extras.spatial import marked_gof ks = marked_gof.marked_time_rescaling(sb, spike_m, p_k, x_hat)
where
x_hatis the decoded position at each event (passed asdecoded); the conditional mark CDF is thenmark_cdf(m, x).- Parameters:
spike_bins (ndarray) – Sorted integer event-bin indices.
marks (ndarray | None) – Per-event mark values (continuous-mark case), or
Noneto run only the time axis. For a finite (channel) mark space, seemultivariate_time_rescaling().p_k (ndarray) – Per-bin spike probabilities \(p_k = \lambda_k \Delta\).
mark_cdf – Callable giving the conditional mark CDF. Either
mark_cdf(m)(mark-only) ormark_cdf(m, x)whendecodedis supplied — the latter is the conditional mark CDFF(m | x_hat).decoded – Optional per-event covariate (e.g. decoded position) forwarded as the second argument to
mark_cdf.n_draws (int) – Number of randomization draws to average the corrected KS over.
alpha (float) – KS test level (sets the band).
rng (Generator | None) – Random generator.
- Return type:
- nstat.extras.spatial.marked_gof.multivariate_time_rescaling(spike_bins_per_channel, p_k_per_channel, *, n_draws=25, alpha=0.05, rng=None)[source]
Per-channel discrete-time rescaling for a finite (channel) mark space.
The finite-mark / multivariate case (Gerhard-Haslinger-Pipa 2011): rescale each channel by its own compensator computed under the joint history. Returns one
MarkedGOFResultper channel.Note
A coupling-blind fit can pass every per-channel KS while the joint model is wrong (Gerhard-Haslinger-Pipa 2011, §4): per-channel passing is necessary, not sufficient. The population-level diagnostic that closes that gap is
nstat.population_time_rescale()(Tao, Weber, Arai & Eden 2018); seemultivariate_gof_with_coupling()for a single call that runs both tests on the same data.- Parameters:
spike_bins_per_channel – Sequence of per-channel sorted event-bin index arrays.
p_k_per_channel – Sequence of per-channel per-bin spike-probability arrays.
n_draws (int) – As in
marked_time_rescaling().alpha (float) – As in
marked_time_rescaling().rng (Generator | None) – As in
marked_time_rescaling().
- Returns:
Channel index → result.
- Return type:
See also
nstat.population_time_rescalePopulation-level (Tao et al. 2018) coupling diagnostic that catches misfit invisible to per-channel KS.
multivariate_gof_with_couplingConvenience wrapper that runs this function and
nstat.population_time_rescale()on the same data and returns both results.
- nstat.extras.spatial.marked_gof.multivariate_gof_with_coupling(spike_bins_per_channel, p_k_per_channel, *, n_draws=25, alpha=0.05, n_tau_bins=4, rng=None)[source]
Discrete-time per-channel GoF and population coupling test, in one call.
Per-channel rescaling (Gerhard-Haslinger-Pipa 2011) catches finite-bin bias in each channel’s marginal fit but is blind to inter-channel coupling: a model can pass every per-channel KS and still get the joint distribution wrong. The population marked-region \(\chi^2\) of Tao, Weber, Arai & Eden (2018) — implemented by
nstat.population_time_rescale()— closes that gap. This wrapper runs both on the same data so a caller does not have to convert the inputs by hand.Conversion of the per-channel inputs to the population-test format:
counts_list[c] = np.bincount(spike_bins_per_channel[c], minlength=T)where \(T\) is the length ofp_k_per_channel[c].lam_per_bin_list[c] = p_k_per_channel[c]. This treats the per-bin spike probability \(p_k = 1 - e^{-\lambda_k\Delta}\) as the per-bin model-expected count \(\lambda_k\Delta\), which is exact in the small-\(p_k\) regime that the discrete-time correction itself assumes.
- Parameters:
spike_bins_per_channel – Sequence of per-channel sorted event-bin index arrays.
p_k_per_channel – Sequence of per-channel per-bin spike-probability arrays; all arrays must have the same length \(T\).
n_draws (int) – Forwarded to
multivariate_time_rescaling().alpha (float) – Forwarded to
multivariate_time_rescaling().rng (Generator | None) – Forwarded to
multivariate_time_rescaling().n_tau_bins (int) – Number of equal-\(\tau\) cells per channel in the population \(\chi^2\) partition (forwarded to
nstat.population_time_rescale()).> 1resolves within-channel timing and is what makes the test sensitive to coupling. Default 4 is a reasonable coupling-sensitive choice for typical recordings; raise / lower to trade coupling sensitivity against the expected-count-per-cell requirement of the asymptotic \(\chi^2\).
- Returns:
per_channel(dict) pluspopulation(nstat.PopulationTimeRescaleResult).- Return type:
- Raises:
ValueError – If
spike_bins_per_channelandp_k_per_channelhave unequal length or if the per-channelp_karrays disagree in length.
See also
multivariate_time_rescalingper-channel discrete-time test alone.
nstat.population_time_rescalepopulation coupling test alone.
References
Gerhard F, Haslinger R, Pipa G (2011), Neural Computation 23(6):1452. Tao L, Weber KM, Arai K, Eden UT (2018), J Comput Neurosci 45:147.
- nstat.extras.spatial.marked_gof.rescaled_acf(u_rescaled, *, n_lags=20)[source]
Autocorrelation of the rescaled-time variates with a Bartlett band.
Steps:
Apply the normal-score transform \(z_j = \Phi^{-1}(u_j)\) (clamped to \([10^{-12},\,1 - 10^{-12}]\) so the tails are finite). Under the true model, \(u_j \sim \text{Unif}(0,1)\) i.i.d., so \(z_j \sim \mathcal{N}(0,1)\) i.i.d.
Compute the centred sample autocorrelation of \(z_j\) at lags 1 through
n_lagsvianumpy.correlate().Flag in-band lags using the asymptotic \(\pm 1.96 / \sqrt{n}\) Bartlett band.
- Parameters:
u_rescaled (ndarray) – The rescaled-time uniforms, typically
MarkedGOFResult.u_corrected. Must contain at leastn_lags + 2values.n_lags (int) – Number of positive lags to report.
- Return type:
- Raises:
ValueError – If
u_rescaledhas fewer thann_lags + 2values.
Notes
Confidence: high on the in-band behaviour under the true model (the asymptotic Bartlett band is exact in the i.i.d. limit) and on the band-violation behaviour under serial dependence. Use as a complement to the marginal KS test, not a replacement.