braintools.analysis.stats.phase_randomize

braintools.analysis.stats.phase_randomize#

braintools.analysis.stats.phase_randomize(key, x, axis=0, same_phases=False)#

Randomise the Fourier phases of a real signal, keeping its power spectrum.

The signal is transformed with a real FFT along axis, every Fourier coefficient is multiplied by \(e^{i\phi_f}\) with \(\phi_f \sim U(0, 2\pi)\), and the result is transformed back [1]. Because only the non-negative frequencies are randomised and the inverse real FFT restores Hermitian symmetry, the output is real and has exactly the amplitude spectrum \(|X_f|\) (hence the power spectrum and autocorrelation) of the input, while any nonlinear or phase structure is destroyed. The DC term and, for even lengths, the Nyquist term keep phase 0, so they stay real and unchanged.

With same_phases=True all channels share one set of random phases, which also preserves the cross-spectra and hence the (circular) cross-correlations between channels [2]. Statistics recomputed on many randomised copies give the null distribution of a significance test (the copies are called surrogate data in the literature).

Parameters:
  • key (Array) – PRNG key, from jax.random.key or jax.random.PRNGKey.

  • x (Any) – Real signal; time runs along axis. Any unit is kept.

  • axis (int) – Time axis.

  • same_phases (bool) – If True, draw one phase per frequency and share it across all other axes (channels).

Returns:

Randomised signal with the shape, dtype and unit of x.

Return type:

Any

Raises:
  • TypeError – If x is complex or axis is not an integer.

  • ValueError – If x is 0-d or axis is out of bounds.

See also

jitter_spikes

Randomised copies of spike trains.

bootstrap_ci

Bootstrap confidence intervals.

Notes

Works under jax.jit and jax.vmap (vmap over keys for many copies) and is differentiable with respect to x (the map is linear in x for a fixed key).

References

Examples

>>> import jax
>>> import jax.numpy as jnp
>>> from braintools.analysis.stats import phase_randomize
>>> x = jax.random.normal(jax.random.key(0), (256, 3))
>>> y = phase_randomize(jax.random.key(1), x)
>>> y.shape
(256, 3)
>>> power = lambda s: jnp.abs(jnp.fft.rfft(s, axis=0))
>>> bool(jnp.allclose(power(x), power(y), atol=1e-3))
True
>>> bool(jnp.allclose(x, y))
False