Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion aeon/classification/dictionary_based/_redcomets.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ def _build_univariate_ensemble(self, X, y):
sfa_clfs.append((rf, weight))

sax_transforms = [
SAX(n_segments=w, alphabet_size=a, znormalized=True) for w, a in sax_lenses
SAX(n_segments=w, alphabet_size=a, znormalize=False) for w, a in sax_lenses
]

sax_clfs = []
Expand Down
56 changes: 43 additions & 13 deletions aeon/transformations/collection/dictionary_based/_sax.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
__maintainer__ = []
__all__ = ["SAX", "_invert_sax_symbols"]

import warnings

import numpy as np
import scipy.stats
from numba import get_num_threads, njit, prange, set_num_threads
Expand Down Expand Up @@ -42,11 +44,17 @@ class SAX(BaseCollectionTransformer):
the parameters of the used distribution, if the used
distribution is "Gaussian" and this parameter is None
then the default setup is {"scale" : 1.0}
znormalized : bool, default = True,
Whether the input is already z-normalized. If False, each complete
series is normalized before PAA when ``window_size=None``. When
windowing is enabled, each extracted window is normalized independently
before PAA, as required by the sliding-window SAX formulation.
znormalized : bool, default = "deprecated",
Deprecated, use ``znormalize`` instead. Whether the input is already
z-normalized. If False, each complete series is normalized before PAA
when ``window_size=None``. When windowing is enabled, each extracted
window is normalized independently before PAA, as required by the
sliding-window SAX formulation.

Note the meaning is inverted compared to ``znormalize``:
``znormalized=True`` (skip normalization) is equivalent to
``znormalize=False``, and ``znormalized=False`` is equivalent to
``znormalize=True``. Will be removed in a future version.
window_size : int, default = None,
The size of the sliding window to use when transforming the time series,
if this parameter is None then the whole time series is used to
Expand All @@ -57,6 +65,12 @@ class SAX(BaseCollectionTransformer):
only used when the window_size parameter is not None.
n_jobs : int, default = 1,
The number of jobs to run in parallel for both `fit` and `transform`.
znormalize : bool, default = True,
Whether to z-normalize the input. If True, each complete series is
normalized before PAA when ``window_size=None``. When windowing is
enabled, each extracted window is normalized independently before PAA,
as required by the sliding-window SAX formulation. If False, the input
is assumed to already be normalized and is used as-is.

Notes
-----
Expand Down Expand Up @@ -92,10 +106,11 @@ def __init__(
alphabet: list = None,
distribution: str = "Gaussian",
distribution_params: dict = None,
znormalized: bool = True,
znormalized="deprecated",
window_size: int = None,
stride: int = 1,
n_jobs: int = 1,
znormalize: bool = True,
):
self.n_segments = n_segments

Expand All @@ -111,6 +126,21 @@ def __init__(
self.n_jobs = n_jobs
self.distribution_params = distribution_params
self.znormalized = znormalized
self.znormalize = znormalize

if znormalized != "deprecated":
warnings.warn(
"The 'znormalized' parameter is deprecated and will be removed "
"in a future version, use 'znormalize' instead. Note the "
"meaning is inverted: znormalized=True (skip normalization) is "
"equivalent to znormalize=False, and znormalized=False is "
"equivalent to znormalize=True.",
FutureWarning,
stacklevel=2,
)
self._znormalize = not znormalized
else:
self._znormalize = znormalize

self.window_size = window_size
self.stride = stride
Expand Down Expand Up @@ -181,7 +211,7 @@ def _get_paa(self, X):
X_paa : np.ndarray of shape = (n_cases, n_channels, n_segments)
The output of the PAA transformation
"""
if not self.znormalized:
if self._znormalize:
X = self._z_normalize(X)

paa = PAA(n_segments=self.n_segments, n_jobs=self.n_jobs)
Expand Down Expand Up @@ -235,7 +265,7 @@ def _transform(self, X, y=None):
self.window_size,
)

if self.znormalized:
if not self._znormalize:
X_windows_normalized = X_windows_3d
self._window_means_ = None
self._window_stds_ = None
Expand Down Expand Up @@ -333,12 +363,12 @@ def inverse_sax(
window_means : np.ndarray, optional
Per-window means with shape
(n_cases, n_channels, n_windows, 1). Required to restore the
original scale when ``znormalized=False`` unless the statistics
original scale when ``znormalize=True`` unless the statistics
were stored by the most recent call to ``transform``.
window_stds : np.ndarray, optional
Per-window standard deviations with shape
(n_cases, n_channels, n_windows, 1). Required to restore the
original scale when ``znormalized=False`` unless the statistics
original scale when ``znormalize=True`` unless the statistics
were stored by the most recent call to ``transform``.

Returns
Expand Down Expand Up @@ -411,7 +441,7 @@ def inverse_sax(
"covered by the SAX windows"
)

if self.znormalized:
if not self._znormalize:
if window_means is None:
window_means = np.zeros(
(
Expand Down Expand Up @@ -442,7 +472,7 @@ def inverse_sax(
raise ValueError(
"window_means and window_stds are required to "
"denormalize windowed SAX output when "
"znormalized=False"
"znormalize=True"
)

window_means = np.asarray(
Expand Down Expand Up @@ -483,7 +513,7 @@ def inverse_sax(
breakpoints_mid=self.breakpoints_mid,
window_means=window_means,
window_stds=window_stds,
denormalize=not self.znormalized,
denormalize=self._znormalize,
)

raise ValueError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -724,9 +724,26 @@ def test_alphabet_length_must_match_alphabet_size():
)


def test_sax_default_znormalized_is_true():
"""Test that SAX treats input as pre-normalized by default."""
assert SAX().znormalized is True
def test_sax_default_znormalize_is_true():
"""Test that SAX normalizes the input by default."""
sax = SAX()
assert sax.znormalize is True
assert sax.znormalized == "deprecated"
assert sax._znormalize is True


def test_sax_znormalized_deprecation_warning():
"""Test that passing the deprecated znormalized parameter warns and maps.

Maps onto the equivalent znormalize value (inverted meaning).
"""
with pytest.warns(FutureWarning, match="znormalized"):
sax_true = SAX(znormalized=True)
assert sax_true._znormalize is False

with pytest.warns(FutureWarning, match="znormalized"):
sax_false = SAX(znormalized=False)
assert sax_false._znormalize is True


def test_sax_get_test_params():
Expand Down
Loading