Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
d381d5e
arima first
TonyBagnall May 24, 2025
3a0552b
move utils
TonyBagnall May 24, 2025
0ac5380
make functions private
TonyBagnall May 24, 2025
44b36a7
Modularise SARIMA model
May 28, 2025
6d18de9
Add ARIMA forecaster to forecasting package
May 28, 2025
b7e6424
Add example to ARIMA forecaster, this also tests the forecaster is pr…
May 28, 2025
e33fa4d
Basic ARIMA model
May 28, 2025
f613f7e
Convert ARIMA to numba version
May 28, 2025
a6b708c
Merge branch 'main' into arb/base_arima
alexbanwell1 May 28, 2025
9eb00f6
Adjust parameters to allow modification in fit
May 28, 2025
d4ed4b1
Update example and return native python type
May 28, 2025
2893e1b
Fix examples for tests
May 28, 2025
9801e8b
Fix Nelder-Mead Optimisation Algorithm Example
May 28, 2025
2f928c7
Fix Nelder-Mead Optimisation Algorithm Example #2
May 28, 2025
94cd5b3
Remove Nelder-Mead Example due to issues with numba caching functions
May 28, 2025
0d0d63f
Fix return type issue
May 28, 2025
39a3ed2
Address PR Feedback
May 28, 2025
05a2785
Ignore small tolerances in floating point value in output of example
May 28, 2025
73966ab
Fix kpss_test example
May 28, 2025
a0f090d
Fix kpss_test example #2
May 28, 2025
6884703
Update documentation for ARIMAForecaster, change constant_term to be …
Jun 2, 2025
44a8647
Merge branch 'main' into arb/base_arima
alexbanwell1 Jun 2, 2025
9af3a56
Modify ARIMA to allow predicting multiple values by updating the stat…
Jun 8, 2025
4c63af5
Merge branch 'main' into arb/base_arima
TonyBagnall Jun 9, 2025
e898f2f
Fix bug using self.d rather than self.d_
Jun 9, 2025
11c4987
Merge branch 'arb/base_arima' of https://github.com/aeon-toolkit/aeon…
Jun 9, 2025
6314a6f
Merge branch 'main' into arb/base_arima
TonyBagnall Jun 11, 2025
72b7980
Merge branch 'main' into arb/base_arima
TonyBagnall Jun 11, 2025
3c644a0
refactor ARIMA
TonyBagnall Jun 11, 2025
350252e
Merge branch 'main' into arb/base_arima
MatthewMiddlehurst Jun 16, 2025
1bd6a32
Merge branch 'main' into arb/base_arima
TonyBagnall Jun 16, 2025
b91d135
docstring
TonyBagnall Jun 16, 2025
420cd72
Merge branch 'main' into arb/base_arima
TonyBagnall Jun 18, 2025
061f286
Merge branch 'main' into arb/base_arima
TonyBagnall Jun 21, 2025
149c0ad
find forecast_ in fit
TonyBagnall Jun 21, 2025
745806e
Merge branch 'main' into arb/base_arima
MatthewMiddlehurst Jul 4, 2025
1d300a4
Merge branch 'main' into arb/base_arima
TonyBagnall Jul 10, 2025
9d8b24f
remove optional y
TonyBagnall Jul 10, 2025
d9b1e7a
add iterative
TonyBagnall Jul 10, 2025
5e4f138
Merge branch 'main' into arb/base_arima
TonyBagnall Jul 16, 2025
1b10109
Merge branch 'main' into arb/base_arima
TonyBagnall Jul 16, 2025
2a962d8
typo
TonyBagnall Jul 16, 2025
6f8cd55
Merge branch 'arb/base_arima' of https://github.com/aeon-toolkit/aeon…
TonyBagnall Jul 16, 2025
c7616a4
typo
TonyBagnall Jul 17, 2025
f29c809
calculate forecast_
TonyBagnall Jul 17, 2025
5a2ee8d
use differenced
TonyBagnall Jul 17, 2025
42e699c
example
TonyBagnall Jul 17, 2025
d1caed3
iterative
TonyBagnall Jul 17, 2025
9f2a85d
arima tests
TonyBagnall Jul 17, 2025
ca30d17
revert to float
TonyBagnall Jul 17, 2025
46d5ebc
switch nelder_mead version
TonyBagnall Jul 17, 2025
9648b99
isolate loss function
TonyBagnall Jul 17, 2025
e8157d6
isolate loss function
TonyBagnall Jul 17, 2025
f45400c
remove the utils version of nelder mead
TonyBagnall Jul 17, 2025
1509989
set self.c_ correctly
TonyBagnall Jul 19, 2025
a2578d9
numba optimise
TonyBagnall Jul 19, 2025
35e5b1c
numba optimise
TonyBagnall Jul 19, 2025
79c8c2d
numba optimise
TonyBagnall Jul 19, 2025
fc4f704
Add missing _extract_paras file
Jul 23, 2025
a31b217
Merge branch 'main' into arb/base_arima
TonyBagnall Jul 26, 2025
724992b
initial residuals bug and proper differencing d>1
TonyBagnall Jul 26, 2025
755f927
loss function ignores zeroed start points
TonyBagnall Jul 27, 2025
6ef61da
Merge branch 'main' into arb/base_arima
TonyBagnall Jul 27, 2025
f218106
fix test
TonyBagnall Jul 28, 2025
6a13e4f
revert FLOAT_CMP
TonyBagnall Jul 28, 2025
9721a5c
refactor into forecasting
TonyBagnall Jul 28, 2025
0f5caa6
refactor into stats and utils
TonyBagnall Jul 28, 2025
6418e4e
API
TonyBagnall Jul 28, 2025
6d99d76
remove example
TonyBagnall Jul 28, 2025
5a79946
Merge branch 'arb/base_arima' into ajb/forecasting_refactor
TonyBagnall Jul 28, 2025
6c5655a
Merge branch 'main' into ajb/forecasting_refactor
TonyBagnall Jul 28, 2025
9d6055c
fix test
TonyBagnall Jul 28, 2025
b8e4abd
fix test
TonyBagnall Jul 28, 2025
574533c
notebook
TonyBagnall Jul 28, 2025
37c3b91
notebook
TonyBagnall Jul 28, 2025
047aa60
notebook
TonyBagnall Jul 28, 2025
e2f658c
Merge branch 'main' into ajb/forecasting_refactor
MatthewMiddlehurst Jul 29, 2025
2820b45
imports and API
TonyBagnall Jul 30, 2025
794d44e
example
TonyBagnall Jul 30, 2025
e3e3f2f
notebook
TonyBagnall Jul 30, 2025
a8a0564
remove duplicate test
TonyBagnall Jul 30, 2025
1f408ab
Merge branch 'main' into ajb/forecasting_refactor
alexbanwell1 Jul 31, 2025
2f70e54
Add inverse differencing to differencing series transformer
Jul 31, 2025
25c5191
Fix undifferencing in ARIMA
Jul 31, 2025
5042fff
Add as maintainer
Jul 31, 2025
bcc9809
Merge branch 'main' into arb/arima_undifferencing_fix
alexbanwell1 Jul 31, 2025
d98cc99
Revert unwanted changes from other PR
Jul 31, 2025
1c7f0d8
#2
Jul 31, 2025
653f0f6
Temporarily exclude check_transform_inverse_transform_equivalent from…
Aug 1, 2025
bbb2ef3
Convert to github name
alexbanwell1 Aug 1, 2025
ab3a9e5
Merge branch 'main' into arb/arima_undifferencing_fix
alexbanwell1 Aug 1, 2025
39218ec
Merge main/ into arima_undifferencing_fix
Aug 4, 2025
0a092cd
Move undifferencing function to forecasting utils
Aug 4, 2025
6827358
Correct undifferencing function call
Aug 4, 2025
ab563a5
Convert DifferenceTransformer to store X values in .transform and use…
Aug 4, 2025
c7d8ec5
Merge branch 'arb/arima_undifferencing_fix' into arb/inverse_differen…
Aug 4, 2025
0e58877
Use new _undifference version
Aug 4, 2025
e7dbfce
Comment out inverse_transform tests
Aug 4, 2025
41446e5
Merge branch 'arb/arima_undifferencing_fix' into arb/inverse_differen…
Aug 4, 2025
5dade40
Add back in inverse_transform_tests
Aug 4, 2025
cc302e1
Remove DifferenceTransformer from the exclusion list
Aug 4, 2025
ffee4c4
Remove Commented out tests
alexbanwell1 Aug 4, 2025
6263e44
Merge branch 'arb/arima_undifferencing_fix' into arb/inverse_differen…
Aug 4, 2025
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
39 changes: 13 additions & 26 deletions aeon/forecasting/stats/_arima.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from aeon.forecasting.base import BaseForecaster
from aeon.forecasting.utils._extract_paras import _extract_arma_params
from aeon.forecasting.utils._nelder_mead import nelder_mead
from aeon.transformations.series._diff import _undifference


class ARIMA(BaseForecaster):
Expand Down Expand Up @@ -126,18 +127,11 @@ def _fit(self, y, exog=None):
differenced_forecast = self.fitted_values_[-1]

if self.d == 0:
forecast_value = differenced_forecast
elif self.d == 1:
forecast_value = differenced_forecast + self._series[-1]
else: # for d > 1, iteratively undifference
forecast_value = differenced_forecast
last_vals = self._series[-self.d :]
for _ in range(self.d):
forecast_value += last_vals[-1] - last_vals[-2]
# Shift values to avoid appending to list (efficient)
last_vals = np.roll(last_vals, -1)
last_vals[-1] = forecast_value # Extract the parameter values
self.forecast_ = forecast_value
self.forecast_ = differenced_forecast
else:
self.forecast_ = _undifference(
np.array([differenced_forecast]), self._series[-self.d :]
)[self.d]
if self.use_constant:
self.c_ = formatted_params[0][0]
self.phi_ = formatted_params[1][: self.p]
Expand Down Expand Up @@ -198,12 +192,12 @@ def _predict(self, y, exog=None):
forecast_diff = c + ar_forecast + ma_forecast

# Undifference the forecast
if d == 0:
if self.d == 0:
return forecast_diff
elif d == 1:
return forecast_diff + y[-1]
else:
return forecast_diff + np.sum(y[-d:])
return _undifference(np.array([forecast_diff]), self._series[-self.d :])[
self.d
]

def _forecast(self, y, exog=None):
"""Forecast one ahead for time series y."""
Expand Down Expand Up @@ -242,17 +236,10 @@ def iterative_forecast(self, y, prediction_horizon):

# Correct differencing using forecast values
y_forecast_diff = forecast_series[n : n + h]
d = self.d
if d == 0:
if self.d == 0:
return y_forecast_diff
else: # Correct undifferencing
# Start with last d values from original y
undiff = list(self._series[-d:])
for i in range(h):
# Take the last d values and sum them
reconstructed = y_forecast_diff[i] + sum(undiff[-d:])
undiff.append(reconstructed)
return np.array(undiff[d:])
else:
return _undifference(y_forecast_diff, self._series[-self.d :])[self.d :]


@njit(cache=True, fastmath=True)
Expand Down
108 changes: 107 additions & 1 deletion aeon/transformations/series/_diff.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
"""Difference Transformer."""

import numpy as np
from numba import njit

from aeon.transformations.series.base import BaseSeriesTransformer

__maintainer__ = ["TinaJin0228"]
__maintainer__ = ["TinaJin0228", "alexbanwell1"]
__all__ = ["DifferenceTransformer"]


Expand Down Expand Up @@ -59,13 +62,15 @@ class DifferenceTransformer(BaseSeriesTransformer):

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

def __init__(self, order=1):
self.order = order
super().__init__(axis=1)
self.x_ = None

def _transform(self, X, y=None):
"""
Expand All @@ -84,9 +89,110 @@ def _transform(self, X, y=None):
raise ValueError(
f"`order` must be a positive integer, but got {self.order}"
)
self.x_ = X

diff_X = np.diff(X, n=self.order, axis=1)

Xt = diff_X

return Xt

def _inverse_transform(self, X, y=None):
"""
Inverse transform to reconstruct the original time series.

Parameters
----------
X : Time series to inverse transform. With shape (n_channels, n_timepoints).
y : If provided, should contain the first `order` values of the original series.
If None, the first `order` values will be taken from values
stored in _transform.

Returns
-------
Xt : np.ndarray
Reconstructed original time series.
"""
if y is None:
y = self.x_
if y is None or y.shape[1] < self.order:
raise ValueError(
f"Inverse transform requires first {self.order} original \
data values. If y is supplied, then those values will be used. \
If not if .transform() has been called, the X series from that \
shall be used. But inverse_transform called with y=None and \
no previous .transform() call."
)
if y.shape[0] != X.shape[0]:
raise ValueError(
f"y must have the same number of channels as X. "
f"Got X.shape[0]={X.shape[0]}, y.shape[0]={y.shape[0]}"
)
X = np.array(X, dtype=np.float64)
y = np.array(y, dtype=np.float64)
initial_values = y[:, : self.order]

return np.array(
[
_undifference(diff_X, initial_value)
for diff_X, initial_value in zip(X, initial_values)
]
)


@njit(cache=True, fastmath=True)
def _comb(n, k):
"""
Calculate the binomial coefficient C(n, k) = n! / (k! * (n - k)!).

Parameters
----------
n : int
The total number of items.
k : int
The number of items to choose.

Returns
-------
int
The binomial coefficient C(n, k).
"""
if k < 0 or k > n:
return 0
if k > n - k:
k = n - k # Take advantage of symmetry
c = 1
for i in range(k):
c = c * (n - i) // (i + 1)
return c


@njit(cache=True, fastmath=True)
def _undifference(diff, initial_values):
"""
Reconstruct original time series from an n-th order differenced series.

Parameters
----------
diff : array-like
n-th order differenced series of length N - n
initial_values : array-like
The first n values of the original series before differencing (length n)

Returns
-------
original : np.ndarray
Reconstructed original series of length N
"""
n = len(initial_values)
kernel = np.array(
[(-1) ** (k + 1) * _comb(n, k) for k in range(1, n + 1)],
dtype=initial_values.dtype,
)
original = np.empty((n + len(diff)), dtype=initial_values.dtype)
original[:n] = initial_values

for i, d in enumerate(diff):
original[n + i] = np.dot(kernel, original[i : n + i][::-1]) + d

return original
15 changes: 15 additions & 0 deletions aeon/transformations/series/tests/test_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,41 @@ def test_diff():
dt1 = DifferenceTransformer(order=1)
Xt1 = dt1.fit_transform(X)
expected1 = np.array([[3.0, 5.0, 7.0, 9.0, 11.0]])
X_hat1 = dt1.inverse_transform(Xt1, X)

np.testing.assert_allclose(
Xt1, expected1, equal_nan=True, err_msg="Value mismatch for order 1"
)
np.testing.assert_allclose(
X_hat1, X, equal_nan=True, err_msg="Inverse transform failed for order 1"
)

dt2 = DifferenceTransformer(order=2)
Xt2 = dt2.fit_transform(X)
expected2 = np.array([[2.0, 2.0, 2.0, 2.0]])
X_hat2 = dt2.inverse_transform(Xt2, X)

np.testing.assert_allclose(
Xt2, expected2, equal_nan=True, err_msg="Value mismatch for order 2"
)
np.testing.assert_allclose(
X_hat2, X, equal_nan=True, err_msg="Inverse transform failed 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]])
Y_hat1 = dt1.inverse_transform(Yt1, Y)
np.testing.assert_allclose(
Yt1,
expected3,
equal_nan=True,
err_msg="Value mismatch for order 1,multivariate",
)
np.testing.assert_allclose(
Y_hat1,
Y,
equal_nan=True,
err_msg="Inverse transform failed for order 1,multivariate",
)
Loading