Overview of the base class structure¶
aeon uses a core inheritance hierarchy of classes across the toolkit, with specialised sub classes in each module. The basic class hierarchy is shown in the following diagram.

Scikit-learn BaseEstimator and aeon BaseAeonEstimator¶
To make sense of this, we break it down from the top. Everything inherits from sklearn BaseEstimator, which mainly handles the mechanisms for getting and setting parameters using the set_params and get_params methods. These methods are used when the estimators interact with other classes such as GridSearchCV, and is also used in aeon’s
ComposableEstimatorMixin, which we’ll talk about later.
Then we have aeon’s BaseAeonEstimator class. This class handles the following for all aeon’s estimator:
management of tags, setting, getting, interaction with sklearn’s tags, etc.
cloning and resetting of the estimator
creation of test instances using test parameters specified by each estimators. For example, this is used to define fast-running estimator (e.g. a forest classifier with only 2 trees) for the CI/CD pipelines.
A word on aeon’s estimator tag system¶
Tags in aeon are used for various purposes, to display estimators capabilities in the documentations, to use specific tests based on each estimator’s capabilities. You can check all existing tags in aeon and the developer documentation on the testing framework to know more about how we use tags.
One of the main use of tags is input or output formatting and checking made by base classes. Some important tags in this regard are :
X_inner_typetag, used to specify the expected type of the input data (Arrays, DataFrames, Lists) used in the implementation. For example, this allows estimator to take both numpy arrays and pandas DataFrames as input, while the implementation uses numpy arrays only.output_data_typetag, used to specify the expected type of the output data (e.g. tabular, series, collections). This is mostly used by transformations estimators.capability:multivariatetag, which indicates whether the estimator can handle multivariate time series data.capability:unequal_lengthtag, which indicates whether the estimator can handle collection of unequal length time series data.capability:multithreadingtag, which indicates whether the estimator can handle multithreading for parallel processing.
We will give some examples using these tags in the following sections.
BaseCollectionEstimator and BaseSeriesEstimator¶
We distinguish between two types of inputs for aeon estimators, series and collections:
Series represent single time series as a 2D format
(n_channels, n_timepoints), some estimators can also use 1D format as(n_timepoints)when they don’t support multivariate series. Series estimators also have anaxisparameter, which allow the input shape to be transposed such as the 2D format becomes(n_timepoints, n_channels)instead.Collections represent an ensemble of time series as a 3D format
(n_samples, n_channels, n_timepoints). Again, this can sometime be represented as a 2D format such as(n_samples, n_timepoints)for univariate estimators. Preferably, this should be avoided to clear any confusion on the meaning of axes and the possible confusion with with 2D single series. More information on this problem can be found in this notebook.
For example, if we go back to the base class schema BaseClassifier inherit from BaseCollectionEstimator. This means that during fit and predict, all estimators inheriting from BaseClassifier will take time series collection as inputs.
Collection base estimators¶
The BaseCollectionEstimator defines methods to check the shape of the input, extract metadata (e.g. whether the collection is multivariate) and check compatibility of the input against tags of the estimators. For example, when you do the following :
[ ]:
from aeon.classification.dictionary_based import TemporalDictionaryEnsemble
from aeon.testing.data_generation import make_example_3d_numpy_list
# TDE does not support unequal length collections
# as it sets "capability: unequal_length":False
X_unequal, y_unequal = make_example_3d_numpy_list()
try:
TemporalDictionaryEnsemble().fit(X_unequal, y_unequal)
except ValueError as e:
print(e)
Data seen by instance of TemporalDictionaryEnsemble has unequal length series, but TemporalDictionaryEnsemble cannot handle these characteristics.
What happens here is that TemporalDictionaryEnsemble inherits from BaseClassifier, which itself inherits from BaseCollectionEstimator. During fit and predict, BaseClassifier calls _preprocess_collection, a function defined in BaseCollectionEstimator. This function extracts the input metadata (whether it is multivariate, of unequal lengths etc.) and compare it against TemporalDictionaryEnsemble tags. These state that the estimator does not support unequal
lengths collections, and hence an exception is raised.
This is the base class for all classifiers. It uses the standard fit, predict and predict_proba structure from sklearn. fit and predict call the abstract methods _fit and _predict which are implemented in the subclass to define the classification algorithm. All of the common format checking and conversion are done in final functions such as fit, predict and are made before calling the abstract methods _fit and _predict.
When implementing a new classifier inheriting from BaseClassifier, you thus only have to implement the __init__, _fit and _predict methods that handle the classification logic of the classifier. You will also need to set the correct tags to allow the check and conversion to be done for you. Note that each base class also defines some attributes that are commonly used in the estimators, for example BaseClassifier exposes classes_, n_classes_, _class_dictionary that
we can use in our new classifier:
[ ]:
from numpy.random import default_rng
from aeon.classification import BaseClassifier
from aeon.testing.data_generation import (
make_example_3d_numpy,
make_example_dataframe_list,
)
class RandomClassifier(BaseClassifier):
"""A dummy classifier returning random predictions."""
_tags = {
"capability:multivariate": True, # allow multivariate collections
"capability:unequal_length": True, # allow multivariate collections
"X_inner_type": ["np-list", "numpy3D"], # Specify data format used internally
}
def __init__(self, random_state: int = 42):
self.random_state = random_state
super().__init__()
def _fit(self, X, y):
self.rng = default_rng(self.random_state)
return self
def _predict(self, X):
# generate a random int between 0 and n_classes-1 and use _class_dictionary
# to convert it to class label
return [
self._class_dictionary[i]
for i in self.rng.integers(low=0, high=self.n_classes_, size=len(X))
]
X, y = make_example_3d_numpy(n_channels=2)
print(RandomClassifier().fit_predict(X, y))
X, y = make_example_dataframe_list()
print(RandomClassifier().fit(X, y).predict(X))
[1 0 1 1 0 0 1 0 1 0]
[0, 1, 1, 0, 0, 1, 0, 1, 0, 0]
Further reading
These base classes are mostly similar to BaseClassifier in how they use the checks and conversion operations from BaseCollectionEstimator.
BaseRegressoralso defines afitandpredictmethod and requires_fitand_predictmethods to be implemented by child classes. The difference is that it has nopredict_probamethod yet, as we still need to decide how to model probabilistic regression for time series. The tests onyare also different, as we can have floats has values fory.BaseClustereralso hasfitandpredict, but does not take inputyas child classes can be unsupervised estimators. It does includepredict_proba.BaseCollectionAnomalyDetectoralso hasfitandpredict, but does not take inputyas child classes can be unsupervised estimators.
Further reading
Rather than fit andpredict, the BaseCollectionTransformer implements fit, transform and fit_transform methods, which are required since this base class also inherits the BaseTransformer class. It will require child classes to define _fitand _transform methods. The output of the transform method is not fixed and should be specified with the output_data_type.
For example, if the output is another collection of time series (e.g. after using SAX), then output_data_type must take the Collection value (note that this is the default value for all BaseCollectionTransformer child classes). If the output is not time series anymore, but rather a 2D array of features extracted from each input time series, such as in Rocket or RandomShapeletTransform, then the output_data_type must take the Tabular.
Further reading
Series base estimators¶
Series estimators are similar to collection estimators, but they take single time series as input. They inherit from BaseSeriesEstimator, which perform similar operations to BaseCollectionEstimator, such as input checks and conversions, but for single time series.
One important difference is the use of the axis parameter, which allows each estimator to define whether it works with the (n_channels, n_timepoints) or the (n_timepoints, n_channels) 2D format. We need to have the axis parameter, because for 2D series, there is no way to infer whether an input 2D time series is in the (n_channels, n_timepoints) or (n_timepoints, n_channels) format.
To understand its uses, we need to distinguish between the axis parameter set during initialisation of the estimator, and the axis parameter used in the fit, predict and other methods:
axisgiven during initialisation is used to define the internal format used by the estimator,axisgiven when call functions is used to transpose the input time series if needed, to match the format internally used by the estimator.
Note that the axis value represent the dimension in which the number of timepoints is stored, so axis=0 means that the timepoints are stored in the first dimension, and axis=1 means that the timepoints are stored in the second dimension (i.e. (n_channels, n_timepoints)).
Further reading
Let’s take the example of BaseSeriesTransformer, which is the base class for all series transformers. It implements the fit, transform and fit_transform methods, and requires child classes to implement _fit and _transform. Let use demonstrate the use of the axis parameter with an example:
[ ]:
from aeon.testing.data_generation import make_example_dataframe_series
from aeon.transformations.series import BaseSeriesTransformer
class DummySeriesTransformer(BaseSeriesTransformer):
"""A dummy series transformer that keeps every second timepoint."""
_tags = {
"capability:multivariate": True, # allow multivariate series
"X_inner_type": "pd.DataFrame", # Specify data format used internally
"fit_is_empty": True, # we don't need to define _fit
}
def __init__(self):
super().__init__(axis=1) # Set axis to 1 for (n_channels, n_timepoints) format
def _transform(self, X, y=None):
print(X.shape)
X = X.iloc[:, ::2] # Example transformation: keep every second timepoint
print(X.shape)
return X
X = make_example_dataframe_series(n_channels=2, n_timepoints=10).T
print(X.shape) # Is (n_timepoints, n_channels), which is axis=0
print(DummySeriesTransformer().fit_transform(X, axis=0).shape)
(10, 2)
(2, 10)
(2, 5)
(5, 2)
What we see is that X starts of shape (n_timepoints, n_channels), which is the default format for the input time series.
The DummySeriesTransformer is initialised with axis=1, meaning that internally, when calling fit and transform, the input is converted to (n_channels, n_timepoints) format before being passed to the _fit and _transform functions. The output is then converted back to the original format (n_timepoints, n_channels) before returning it.
Note that as we specified pd.DataFrame as the X_inner_type in the tags, if the input is not a DataFrame, the estimator will convert it to a DataFrame before applying the transformation. This allows you to define and use a single input shape and type in the function you implement, while still allowing the estimator to handle different input formats.
[20]:
from aeon.testing.data_generation import make_example_2d_numpy_series
X = make_example_2d_numpy_series(n_channels=1, n_timepoints=10)
transformer = DummySeriesTransformer()
print(transformer.fit_transform(X, axis=1).shape)
(1, 10)
(1, 5)
(1, 5)
The BaseForecaster class inherits from BaseSeriesEstimator which provides the checks and conversion functions for single time series inputs. Forecasters predict future values of a time series horizon steps ahead. The horizon parameter is defined during initialization to specify how many steps ahead the forecaster should predict. The main methods are :
fit(y, exog=None): Trains the forecaster on input datapredict(y, exog=None): Makes predictions on new dataforecast(y, exog=None): Combines fit and predict into one operation
For child classes, the _fit and _predict methods must be implemented to define the forecasting logic.
It also provides two main forecasting strategies:
direct_forecast(): Makes predictions by training separate models for each future step. See the direct forecasting notebook for more information on this strategy.iterative_forecast(): Makes predictions recursively using a single trained model. See the itreative forecasting notebook for more information on this strategy.
Further reading
The BaseSeriesAnomalyDetector and the BaseSegmenter base classes both implements the fit, predict but only requires child classes to implement the _predict, as most anomaly detector and segmenters are unsupervised estimators. Both also inherit from BaseSeriesEstimator, which provides the checking and conversion functions we have seen before
Further reading
Generated using nbsphinx. The Jupyter notebook can be found here.