Skip to content

[ENH] add a difference transformer to series transformations #2729

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
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
8 changes: 8 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions aeon/transformations/series/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"SIVSeriesTransformer",
"PCASeriesTransformer",
"WarpingSeriesTransformer",
"DifferenceTransformer",
]

from aeon.transformations.series._acf import (
Expand All @@ -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
Expand Down
94 changes: 94 additions & 0 deletions aeon/transformations/series/_diff.py
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions aeon/transformations/series/tests/test_diff.py
Original file line number Diff line number Diff line change
@@ -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]])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you also test multivariate series in another test perhaps


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",
)