Reference
Transforms¶
The signal layer that turns a raw Trajectory (or a bare array)
into analysable features: spectral estimators, preprocessing, and a feature
bank. These feed the from-a-signal quantifiers documented in the
Analysis section.
Every entry point is shape-preserving where it makes sense — pass a
Trajectory in and the sampling interval is read from its metadata — and is
re-exported from tsdynamics.transforms.
Spectral¶
power_spectral_density
¶
power_spectral_density(
data: Any,
*,
fs: float | None = None,
dt: float | None = None,
method: str = "welch",
window: str = "hann",
nperseg: int | None = None,
detrend: str | bool = "constant",
scaling: str = "density",
) -> tuple[ndarray, ndarray]
One-sided power spectral density of a signal.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
Signal with time along axis 0; a multi-channel
TYPE:
|
fs
|
Sampling frequency or spacing. At most one; if neither is given and
TYPE:
|
dt
|
Sampling frequency or spacing. At most one; if neither is given and
TYPE:
|
method
|
Welch's averaged-periodogram estimator (lower variance) or a single raw periodogram (full frequency resolution, higher variance).
TYPE:
|
window
|
Window passed to the estimator (any :func:
TYPE:
|
nperseg
|
Welch segment length. Defaults to
TYPE:
|
detrend
|
Per-segment detrending (
TYPE:
|
scaling
|
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
freqs
|
Frequency bins, in the same units as
TYPE:
|
psd
|
Power at each frequency, one column per channel.
TYPE:
|
Notes
The estimate is one-sided: for a real-valued signal the power of each
pair of conjugate (positive/negative) frequencies is folded onto the single
positive bin, so every interior bin's power is doubled relative to the
two-sided spectrum. The DC (0) and — when present — Nyquist (fs / 2)
bins have no conjugate twin and are not doubled. This is SciPy's default
return_onesided=True convention, so a sinusoid of amplitude A carries
total power A**2 / 2 (its time-average) at its line. With
scaling="density" the value is a power spectral density (units²/Hz, so
integrating over frequency recovers the signal variance); with
scaling="spectrum" it is a power spectrum (units², so summing over bins
recovers the variance).
Examples:
>>> import numpy as np
>>> t = np.linspace(0, 10, 2000, endpoint=False)
>>> f, p = power_spectral_density(np.sin(2 * np.pi * 5 * t), fs=200.0)
>>> round(float(f[np.argmax(p)])) # dominant line at 5 Hz
5
Source code in src/tsdynamics/transforms/spectral.py
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | |
spectral_entropy
¶
spectral_entropy(
data: Any,
*,
fs: float | None = None,
dt: float | None = None,
normalize: bool = True,
base: float = 2.0,
**psd_kwargs: Any,
) -> Any
Shannon entropy of the normalised power spectrum (Powell & Percival 1979).
The PSD is normalised to a probability distribution over frequency,
p_i = P_i / Σ P_i, and its Shannon entropy H = -Σ p_i log_base p_i is
returned. With normalize=True the result is divided by log_base(n_freqs)
so it lands in [0, 1]: 0 for a pure tone (all power in one bin), 1
for a flat (white) spectrum. A useful regular-vs-chaotic discriminator.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
Signal with time along axis 0.
TYPE:
|
fs
|
Sampling frequency / spacing (see :func:
TYPE:
|
dt
|
Sampling frequency / spacing (see :func:
TYPE:
|
normalize
|
Divide by
TYPE:
|
base
|
Logarithm base (2 → bits). Only affects the result when
TYPE:
|
**psd_kwargs
|
Forwarded to :func:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float or ndarray
|
Spectral entropy; scalar for a single channel, |
Source code in src/tsdynamics/transforms/spectral.py
spectral_centroid
¶
spectral_centroid(
data: Any,
*,
fs: float | None = None,
dt: float | None = None,
**psd_kwargs: Any,
) -> Any
Power-weighted mean frequency (the spectral "centre of mass").
centroid = Σ f_i P_i / Σ P_i — the frequency about which the spectrum is
balanced.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
Signal with time along axis 0.
TYPE:
|
fs
|
Sampling frequency / spacing (see :func:
TYPE:
|
dt
|
Sampling frequency / spacing (see :func:
TYPE:
|
**psd_kwargs
|
Forwarded to :func:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float or ndarray
|
Centroid frequency; scalar for a single channel, |
Source code in src/tsdynamics/transforms/spectral.py
dominant_frequency
¶
dominant_frequency(
data: Any,
*,
fs: float | None = None,
dt: float | None = None,
exclude_dc: bool = True,
**psd_kwargs: Any,
) -> Any
Frequency carrying the most power.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
Signal with time along axis 0.
TYPE:
|
fs
|
Sampling frequency / spacing (see :func:
TYPE:
|
dt
|
Sampling frequency / spacing (see :func:
TYPE:
|
exclude_dc
|
Ignore the zero-frequency bin so a non-zero mean does not masquerade as the dominant component.
TYPE:
|
**psd_kwargs
|
Forwarded to :func:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float or ndarray
|
Peak frequency; scalar for a single channel, |
Source code in src/tsdynamics/transforms/spectral.py
Preprocessing¶
detrend
¶
Remove a constant or linear trend from each channel.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
Signal with time along axis 0.
TYPE:
|
method
|
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Trajectory or ndarray
|
Same type and shape as |
Source code in src/tsdynamics/transforms/preprocessing.py
normalize
¶
Rescale each channel by a per-channel statistic.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
Signal with time along axis 0.
TYPE:
|
method
|
Channels with zero spread (constant signals) are passed through their
location shift unscaled rather than producing
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Trajectory or ndarray
|
Same type and shape as |
Source code in src/tsdynamics/transforms/preprocessing.py
butter_filter
¶
butter_filter(
x: Any,
cutoff: float | tuple[float, float] | Any,
*,
btype: str,
fs: float | None = None,
dt: float | None = None,
order: int = 4,
) -> Any
Zero-phase Butterworth filter.
Designs a Butterworth filter of the given order and applies it forward and backward (zero phase) as second-order sections.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Signal with time along axis 0.
TYPE:
|
cutoff
|
Cutoff frequency in the same units as |
btype
|
Filter band type.
TYPE:
|
fs
|
Sampling frequency / spacing (see :func:
TYPE:
|
dt
|
Sampling frequency / spacing (see :func:
TYPE:
|
order
|
Butterworth order (per band edge). Must be a positive integer.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
Trajectory or ndarray
|
Same type and shape as |
| RAISES | DESCRIPTION |
|---|---|
InvalidParameterError
|
If |
ValueError
|
If |
Notes
Zero-phase filtering doubles the effective order and needs the signal to be
longer than the filter's padding (a few times order); very short signals
raise from :func:scipy.signal.sosfiltfilt.
References
Butterworth, S. (1930). On the theory of filter amplifiers. Experimental Wireless and the Wireless Engineer, 7, 536-541.
Source code in src/tsdynamics/transforms/preprocessing.py
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | |
lowpass
¶
Zero-phase Butterworth low-pass filter (see :func:butter_filter).
highpass
¶
Zero-phase Butterworth high-pass filter (see :func:butter_filter).
bandpass
¶
Zero-phase Butterworth band-pass filter (see :func:butter_filter).
bandstop
¶
Zero-phase Butterworth band-stop filter (see :func:butter_filter).
Feature extraction¶
The FEATURE_FUNCTIONS registry maps a feature name to its estimator;
extract_features
evaluates a selection of them into a flat dict, and
feature_names lists what is
available.
extract_features
¶
extract_features(
data: Any,
*,
fs: float | None = None,
dt: float | None = None,
features: Sequence[str] | None = None,
) -> dict[str, Any]
Compute a named bag of scalar features for each channel of a signal.
| PARAMETER | DESCRIPTION |
|---|---|
data
|
Signal with time along axis 0; a multi-channel
TYPE:
|
fs
|
Sampling frequency / spacing, needed by the frequency-domain features
(see :func:
TYPE:
|
dt
|
Sampling frequency / spacing, needed by the frequency-domain features
(see :func:
TYPE:
|
features
|
Which features to compute (names from :data:
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
dict
|
Maps each requested feature name to its value: a |
Examples:
>>> import numpy as np
>>> feats = extract_features(np.random.default_rng(0).standard_normal(2000))
>>> round(feats["mean"], 1)
0.0
Source code in src/tsdynamics/transforms/features.py
feature_names
¶
hjorth_parameters
¶
Hjorth activity, mobility and complexity per channel (Hjorth 1970).
The three descriptors are built from the variances of the signal x and
its first / second discrete differences (x', x''):
- activity —
var(x), the signal's power. - mobility —
sqrt(var(x') / var(x)), the standard deviation of the slope relative to that of the amplitude; a proxy for the mean frequency. - complexity —
mobility(x') / mobility(x), i.e.sqrt(var(x'') / var(x')) / sqrt(var(x') / var(x)); how much the signal's frequency content departs from a pure tone. It is1for an ideal sinusoid and grows as the spectrum broadens.
The differences are taken with :func:numpy.diff (unit sample spacing), so
mobility is in cycles per sample. A constant channel (zero variance) has no
shape and returns 0 for all three rather than dividing by zero.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Signal with time along axis 0.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
dict
|
Keys |
References
Hjorth, B. (1970). EEG analysis based on time domain properties. Electroencephalography and Clinical Neurophysiology, 29(3), 306-310.
Examples:
>>> import numpy as np
>>> t = np.linspace(0, 10, 4000, endpoint=False)
>>> round(hjorth_parameters(np.sin(2 * np.pi * 3 * t))["complexity"], 2)
1.0
Source code in src/tsdynamics/transforms/features.py
zero_crossing_rate
¶
Fraction of adjacent samples that change sign, per channel.
A crossing is a flip of the < 0 / >= 0 partition between consecutive
samples; the rate is the crossing count divided by n_samples - 1, so it
lies in [0, 1]. For a clean tone of frequency f sampled at fs it
is approximately 2 f / fs. Signed zeros are normalised (-0.0 is
treated as +0.0) so a sample grazing zero cannot fabricate a crossing.
| PARAMETER | DESCRIPTION |
|---|---|
x
|
Signal with time along axis 0.
TYPE:
|
| RETURNS | DESCRIPTION |
|---|---|
float or ndarray
|
Scalar for a single channel, |
Examples:
>>> import numpy as np
>>> t = np.linspace(0, 1, 200, endpoint=False)
>>> round(float(zero_crossing_rate(np.sin(2 * np.pi * 5 * t))), 2)
0.05