Skip to content

Commit

Permalink
[DEP] convert Scaled Logit (#1552)
Browse files Browse the repository at this point in the history
* convert Fourier

* docstring

* docstring

* docstring
  • Loading branch information
TonyBagnall authored May 23, 2024
1 parent 5e2c9af commit 9289d15
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 7 deletions.
201 changes: 201 additions & 0 deletions aeon/transformations/series/_scaled_logit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
"""Implements the scaled logit transformation."""

__maintainer__ = []
__all__ = ["ScaledLogitSeriesTransformer"]

from copy import deepcopy
from warnings import warn

import numpy as np

from aeon.transformations.series.base import BaseSeriesTransformer


class ScaledLogitSeriesTransformer(BaseSeriesTransformer):
r"""Scaled logit transform or Log transform.
If both lower_bound and upper_bound are not None, a scaled logit transform is
applied to the data. Otherwise, the transform applied is a log transform variation
that ensures the resulting values from the inverse transform are bounded
accordingly. The transform is applied to all scalar elements of the input array
individually.
Combined with an aeon.forecasting.compose.TransformedTargetForecaster, it ensures
that the forecast stays between the specified bounds (lower_bound, upper_bound).
Default is lower_bound = upper_bound = None, i.e., the identity transform.
The logarithm transform is obtained for lower_bound = 0, upper_bound = None.
Parameters
----------
lower_bound : float, optional, default=None
lower bound of inverse transform function
upper_bound : float, optional, default=None
upper bound of inverse transform function
See Also
--------
aeon.transformations.boxcox.LogTransformer :
Transformer input data using natural log. Can help normalize data and
compress variance of the series.
aeon.transformations.boxcox.BoxCoxTransformer :
Applies Box-Cox power transformation. Can help normalize data and
compress variance of the series.
aeon.transformations.exponent.ExponentTransformer :
Transform input data by raising it to an exponent. Can help compress
variance of series if a fractional exponent is supplied.
aeon.transformations.exponent.SqrtTransformer :
Transform input data by taking its square root. Can help compress
variance of input series.
Notes
-----
| The scaled logit transform is applied if both upper_bound and lower_bound are
| not None:
| :math:`log(\frac{x - a}{b - x})`, where a is the lower and b is the upper bound.
| If upper_bound is None and lower_bound is not None the transform applied is
| a log transform of the form:
| :math:`log(x - a)`
| If lower_bound is None and upper_bound is not None the transform applied is
| a log transform of the form:
| :math:`- log(b - x)`
| The transform is independent of the axis, so the data can be shape
| `` (n_timepoints, n_channels)`` (axis == 0) or
| ``(n_channels, n_timepoints)`` (axis ==1)
References
----------
.. [1] Hyndsight - Forecasting within limits:
https://robjhyndman.com/hyndsight/forecasting-within-limits/
.. [2] Hyndman, R.J., & Athanasopoulos, G. (2021) Forecasting: principles and
practice, 3rd edition, OTexts: Melbourne, Australia. OTexts.com/fpp3.
Accessed on January 24th 2022.
Examples
--------
>>> import numpy as np
>>> from aeon.datasets import load_airline
>>> import aeon.transformations.series._scaled_logit as sl
>>> from aeon.forecasting.trend import PolynomialTrendForecaster
>>> from aeon.forecasting.compose import TransformedTargetForecaster
>>> y = load_airline()
>>> fcaster = TransformedTargetForecaster([
... ("scaled_logit", sl.ScaledLogitSeriesTransformer(0, 650)),
... ("poly", PolynomialTrendForecaster(degree=2))
... ])
>>> fcaster.fit(y)
TransformedTargetForecaster(...)
>>> y_pred = fcaster.predict(fh = np.arange(32))
"""

_tags = {
"X_inner_type": "np.ndarray",
"fit_is_empty": True,
"capability:multivariate": True,
"capability:inverse_transform": True,
}

def __init__(self, lower_bound=None, upper_bound=None):
self.lower_bound = lower_bound
self.upper_bound = upper_bound

super().__init__()

def _transform(self, X, y=None):
"""Transform X and return a transformed version.
private _transform containing core logic, called from transform
Parameters
----------
X : 2D np.ndarray
Time series of shape (n_timepoints, n_channels)
y : Ignored argument for interface compatibility
Returns
-------
transformed version of X
"""
if self.upper_bound is not None and np.any(X >= self.upper_bound):
warn(
"X in ScaledLogitSeriesTransformer should not have values "
"greater than upper_bound",
RuntimeWarning,
)

if self.lower_bound is not None and np.any(X <= self.lower_bound):
warn(
"X in ScaledLogitSeriesTransformer should not have values "
"lower than lower_bound",
RuntimeWarning,
)

if self.upper_bound and self.lower_bound:
X_transformed = np.log((X - self.lower_bound) / (self.upper_bound - X))
elif self.upper_bound is not None:
X_transformed = -np.log(self.upper_bound - X)
elif self.lower_bound is not None:
X_transformed = np.log(X - self.lower_bound)
else:
X_transformed = deepcopy(X)

return X_transformed

def _inverse_transform(self, X, y=None):
"""Inverse transform, inverse operation to transform.
private _inverse_transform containing core logic, called from inverse_transform
Parameters
----------
X : 2D np.ndarray
Data to be inverse transformed
y : Ignored argument for interface compatibility
Returns
-------
inverse transformed version of X
"""
if self.upper_bound and self.lower_bound:
X_inv_transformed = (self.upper_bound * np.exp(X) + self.lower_bound) / (
np.exp(X) + 1
)
elif self.upper_bound is not None:
X_inv_transformed = self.upper_bound - np.exp(-X)
elif self.lower_bound is not None:
X_inv_transformed = np.exp(X) + self.lower_bound
else:
X_inv_transformed = deepcopy(X)

return X_inv_transformed

@classmethod
def get_test_params(cls, parameter_set="default"):
"""Return testing parameter settings for the estimator.
Parameters
----------
parameter_set : str, default="default"
Name of the set of test parameters to return, for use in tests. If no
special parameters are defined for a value, will return `"default"` set.
Returns
-------
params : dict or list of dict, default = {}
Parameters to create testing instances of the class
Each dict are parameters to construct an "interesting" test instance, i.e.,
`MyClass(**params)` or `MyClass(**params[i])` creates a valid test instance.
`create_test_instance` uses the first (or only) dictionary in `params`
"""
test_params = [
{"lower_bound": None, "upper_bound": None},
{"lower_bound": -(10**6), "upper_bound": None},
{"lower_bound": None, "upper_bound": 10**6},
{"lower_bound": -(10**6), "upper_bound": 10**6},
]
return test_params
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import pytest

from aeon.datasets import load_airline
from aeon.transformations.scaledlogit import ScaledLogitTransformer
from aeon.transformations.series._scaled_logit import ScaledLogitSeriesTransformer

TEST_SERIES = np.array([30, 40, 60])

Expand All @@ -22,9 +22,9 @@
(None, None, TEST_SERIES),
],
)
def test_scaledlogit_transform(lower, upper, output):
def test_scaled_logit_transform(lower, upper, output):
"""Test that we get the right output."""
transformer = ScaledLogitTransformer(lower, upper)
transformer = ScaledLogitSeriesTransformer(lower, upper)
y_transformed = transformer.fit_transform(TEST_SERIES)
assert np.all(output == y_transformed)

Expand All @@ -36,20 +36,21 @@ def test_scaledlogit_transform(lower, upper, output):
0,
300,
(
"X in ScaledLogitTransformer should not have values greater"
"X in ScaledLogitSeriesTransformer should not have values greater"
"than upper_bound"
),
),
(
300,
700,
"X in ScaledLogitTransformer should not have values lower than lower_bound",
"X in ScaledLogitSeriesTransformer should not have values lower than "
"lower_bound",
),
],
)
def test_scaledlogit_bound_errors(lower, upper, message):
def test_scaled_logit_bound_errors(lower, upper, message):
"""Tests all exceptions."""
y = load_airline()
with pytest.warns(RuntimeWarning):
ScaledLogitTransformer(lower, upper).fit_transform(y)
ScaledLogitSeriesTransformer(lower, upper).fit_transform(y)
warn(message, RuntimeWarning)

0 comments on commit 9289d15

Please sign in to comment.