From abb417e635708e27981a008ad7db0b7f18f2d9b1 Mon Sep 17 00:00:00 2001 From: AmjadAAYD Date: Sat, 22 Aug 2026 19:42:38 +0100 Subject: [PATCH 1/2] Rename SAX znormalized to znormalize, default to normalizing --- .../dictionary_based/_redcomets.py | 2 +- .../collection/dictionary_based/_sax.py | 56 ++++++++++++++----- .../dictionary_based/tests/test_sax.py | 21 ++++++- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/aeon/classification/dictionary_based/_redcomets.py b/aeon/classification/dictionary_based/_redcomets.py index b2e584cab1..9639dcbfc3 100644 --- a/aeon/classification/dictionary_based/_redcomets.py +++ b/aeon/classification/dictionary_based/_redcomets.py @@ -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 = [] diff --git a/aeon/transformations/collection/dictionary_based/_sax.py b/aeon/transformations/collection/dictionary_based/_sax.py index 0bced4be37..406d6bbb5a 100644 --- a/aeon/transformations/collection/dictionary_based/_sax.py +++ b/aeon/transformations/collection/dictionary_based/_sax.py @@ -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 @@ -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 @@ -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 ----- @@ -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 @@ -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 @@ -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) @@ -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 @@ -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 @@ -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( ( @@ -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( @@ -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( diff --git a/aeon/transformations/collection/dictionary_based/tests/test_sax.py b/aeon/transformations/collection/dictionary_based/tests/test_sax.py index db5d3a6ecf..9992bd5cf3 100644 --- a/aeon/transformations/collection/dictionary_based/tests/test_sax.py +++ b/aeon/transformations/collection/dictionary_based/tests/test_sax.py @@ -724,9 +724,24 @@ 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 + 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(): From a3a7d61d502682076b747935d6f3806f0c6452d9 Mon Sep 17 00:00:00 2001 From: AmjadAAYD Date: Sat, 22 Aug 2026 20:13:37 +0100 Subject: [PATCH 2/2] Fix docstring formatting (D205) --- .../collection/dictionary_based/tests/test_sax.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/aeon/transformations/collection/dictionary_based/tests/test_sax.py b/aeon/transformations/collection/dictionary_based/tests/test_sax.py index 9992bd5cf3..61e62dd4e1 100644 --- a/aeon/transformations/collection/dictionary_based/tests/test_sax.py +++ b/aeon/transformations/collection/dictionary_based/tests/test_sax.py @@ -733,8 +733,10 @@ def test_sax_default_znormalize_is_true(): def test_sax_znormalized_deprecation_warning(): - """Test that passing the deprecated znormalized parameter warns and maps - onto the equivalent znormalize value (inverted meaning).""" + """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