diff --git a/.all-contributorsrc b/.all-contributorsrc index 95453ca9e6..cb56aead70 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -2685,6 +2685,14 @@ ] }, { + "login": "TinaJin0228", + "name": "Tina Jin", + "avatar_url": "https://avatars.githubusercontent.com/TinaJin0228", + "profile": "https://github.com/TinaJin0228", + "contributions": [ + "code", + "doc" + ] "login": "HaroonAzamFiza", "name": "HaroonAzamFiza", "avatar_url": "https://avatars.githubusercontent.com/u/183639840?v=4", diff --git a/aeon/transformations/series/__init__.py b/aeon/transformations/series/__init__.py index 8b71ba9fc8..01bb5faa55 100644 --- a/aeon/transformations/series/__init__.py +++ b/aeon/transformations/series/__init__.py @@ -21,6 +21,7 @@ "SIVSeriesTransformer", "PCASeriesTransformer", "WarpingSeriesTransformer", + "DifferenceTransformer", ] from aeon.transformations.series._acf import ( @@ -32,6 +33,7 @@ from aeon.transformations.series._boxcox import BoxCoxTransformer from aeon.transformations.series._clasp import ClaSPTransformer from aeon.transformations.series._dft import DFTSeriesTransformer +from aeon.transformations.series._diff import DifferenceTransformer from aeon.transformations.series._dobin import Dobin from aeon.transformations.series._exp_smoothing import ExpSmoothingSeriesTransformer from aeon.transformations.series._gauss import GaussSeriesTransformer diff --git a/aeon/transformations/series/_diff.py b/aeon/transformations/series/_diff.py new file mode 100644 index 0000000000..9c2240f437 --- /dev/null +++ b/aeon/transformations/series/_diff.py @@ -0,0 +1,94 @@ +import numpy as np + +from aeon.transformations.series.base import BaseSeriesTransformer + +__maintainer__ = ["Tina Jin"] +__all__ = ["DifferenceTransformer"] + + +class DifferenceTransformer(BaseSeriesTransformer): + """ + Calculates the n-th order difference of a time series. + + Transforms a time series X into a series Y representing the difference + calculated `order` times. + + The time series are supposed to be all in rows, + with shape (n_channels, n_timepoints) + + - Order 1: Y[t] = X[t] - X[t-1] + - Order 2: Y[t] = (X[t] - X[t-1]) - (X[t-1] - X[t-2]) = X[t] - 2*X[t-1] + X[t-2] + - ... and so on. + + The transformed series will be shorter than the input series by `order` + elements along the time axis. + + Parameters + ---------- + order : int, default=1 + The order of differencing. Must be a positive integer. + + Notes + ----- + This transformer assumes the input series does not contain NaN values where + the difference needs to be computed. + + Examples + -------- + >>> import numpy as np + >>> from aeon.transformations.series._diff import DifferenceTransformer + >>> X1 = np.array([[1, 3, 2, 5, 4, 7, 6, 9, 8, 10]]) # Shape (1, 10) + >>> dt = DifferenceTransformer() + >>> Xt1 = dt.fit_transform(X1) + >>> print(Xt1) # Shape (1, 9) + [[ 2 -1 3 -1 3 -1 3 -1 2]] + + >>> X2 = np.array([[1, 3, 2, 5, 4, 7, 6, 9, 8, 10]]) # Shape (1, 10) + >>> dt2 = DifferenceTransformer(order=2) + >>> Xt2 = dt2.fit_transform(X2) + >>> print(Xt2) # Shape (1, 8) + [[-3 4 -4 4 -4 4 -4 3]] + + >>> X3 = np.array([[1, 2, 3, 4, 5], [5, 4, 3, 2, 1]]) # Shape (2, 5) + >>> dt = DifferenceTransformer() + >>> Xt3 = dt.fit_transform(X3) + >>> print(Xt3) # Shape (2, 4) + [[ 1 1 1 1] + [-1 -1 -1 -1]] + """ + + _tags = { + "capability:multivariate": True, + "X_inner_type": "np.ndarray", + "fit_is_empty": True, + } + + def __init__(self, order=1): + self.order = order + super().__init__(axis=1) + + def _transform(self, X, y=None): + """ + Perform the n-th order differencing transformation. + + Parameters + ---------- + X : Time series to transform. With shape (n_channels, n_timepoints). + y : ignored argument for interface compatibility + + Returns + ------- + Xt : np.ndarray + Transformed version of X, containing the n-th order difference. + Shape will be (n_channels, n_timepoints - order). + """ + if not isinstance(self.order, int) or self.order < 1: + raise ValueError( + f"`order` must be a positive integer, but got {self.order}" + ) + + diff_X = np.diff(X, n=self.order, axis=1) + + Xt = diff_X + + return Xt diff --git a/aeon/transformations/series/tests/test_diff.py b/aeon/transformations/series/tests/test_diff.py new file mode 100644 index 0000000000..9f54fccf7d --- /dev/null +++ b/aeon/transformations/series/tests/test_diff.py @@ -0,0 +1,37 @@ +"""Tests for Difference transformation.""" + +import numpy as np + +from aeon.transformations.series._diff import DifferenceTransformer + + +def test_diff(): + """Tests basic first and second order differencing.""" + X = np.array([[1.0, 4.0, 9.0, 16.0, 25.0, 36.0]]) + + dt1 = DifferenceTransformer(order=1) + Xt1 = dt1.fit_transform(X) + expected1 = np.array([[3.0, 5.0, 7.0, 9.0, 11.0]]) + + np.testing.assert_allclose( + Xt1, expected1, equal_nan=True, err_msg="Value mismatch for order 1" + ) + + dt2 = DifferenceTransformer(order=2) + Xt2 = dt2.fit_transform(X) + expected2 = np.array([[2.0, 2.0, 2.0, 2.0]]) + + np.testing.assert_allclose( + Xt2, expected2, equal_nan=True, err_msg="Value mismatch for order 2" + ) + + Y = np.array([[1, 2, 3, 4], [5, 3, 1, 8]]) + + Yt1 = dt1.fit_transform(Y) + expected3 = np.array([[1, 1, 1], [-2, -2, 7]]) + np.testing.assert_allclose( + Yt1, + expected3, + equal_nan=True, + err_msg="Value mismatch for order 1,multivariate", + )