Skip to content
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

[ENH] Remover overloaded operators from base transformer #1562

Closed
wants to merge 10 commits into from
7 changes: 0 additions & 7 deletions aeon/pipeline/_make_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,6 @@ def make_pipeline(*steps):
>>> pipe = make_pipeline(ExponentTransformer(), Catch22Classifier())
>>> type(pipe).__name__
'ClassifierPipeline'

Example 3: transformer pipeline
>>> from aeon.pipeline import make_pipeline
>>> from aeon.transformations.exponent import ExponentTransformer
>>> pipe = make_pipeline(ExponentTransformer(), ExponentTransformer())
>>> type(pipe).__name__
'TransformerPipeline'
"""
if len(steps) == 1 and isinstance(steps[0], list):
steps = steps[0]
Expand Down
33 changes: 1 addition & 32 deletions aeon/pipeline/tests/test_make_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,11 @@
from aeon.base import BaseEstimator
from aeon.classification import DummyClassifier
from aeon.clustering import TimeSeriesKMeans
from aeon.forecasting.base import BaseForecaster
from aeon.forecasting.naive import NaiveForecaster
from aeon.pipeline import make_pipeline
from aeon.regression import DummyRegressor
from aeon.testing.utils.data_gen import make_example_2d_numpy, make_example_3d_numpy
from aeon.transformations.base import BaseTransformer
from aeon.testing.utils.data_gen import make_example_3d_numpy
from aeon.transformations.collection import PaddingTransformer, Tabularizer
from aeon.transformations.collection.feature_based import SevenNumberSummaryTransformer
from aeon.transformations.exponent import ExponentTransformer


@pytest.mark.parametrize(
Expand Down Expand Up @@ -47,30 +43,3 @@ def test_make_pipeline(pipeline):

assert isinstance(est, BaseEstimator)
assert isinstance(o, np.ndarray)


@pytest.mark.parametrize(
"pipeline",
[
[ExponentTransformer(), NaiveForecaster()],
[ExponentTransformer(), ExponentTransformer(power=3)],
],
)
def test_make_pipeline_legacy(pipeline):
"""Test that make_pipeline works for some legacy interfaces."""
assert isinstance(pipeline[-1], BaseTransformer) or isinstance(
pipeline[-1], BaseForecaster
)

X, y = make_example_2d_numpy()

est = make_pipeline(pipeline)
est.fit(X, y)

if hasattr(est, "predict"):
o = est.predict([0, 1, 2, 3])
else:
o = est.transform(X)

assert isinstance(est, BaseEstimator)
assert isinstance(o, np.ndarray)
201 changes: 0 additions & 201 deletions aeon/transformations/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,6 @@ class name: BaseTransformer
from aeon.datatypes._series_as_panel import convert_to_scitype
from aeon.datatypes._vec_df import _VectorizedDF
from aeon.utils.index_functions import update_data
from aeon.utils.sklearn import (
is_sklearn_classifier,
is_sklearn_regressor,
is_sklearn_transformer,
)
from aeon.utils.validation import abstract_types, is_univariate_series, validate_input
from aeon.utils.validation._dependencies import _check_estimator_deps

Expand Down Expand Up @@ -139,202 +134,6 @@ def __init__(self, _output_convert="auto"):
super().__init__()
_check_estimator_deps(self)

def __mul__(self, other):
"""Magic * method, return (right) concatenated TransformerPipeline.

Implemented for `other` being a transformer, otherwise returns `NotImplemented`.

Parameters
----------
other: `aeon` transformer, must inherit from BaseTransformer
otherwise, `NotImplemented` is returned

Returns
-------
TransformerPipeline object, concatenation of `self` (first) with `other` (last).
not nested, contains only non-TransformerPipeline `aeon` transformers
"""
from aeon.transformations.compose import TransformerPipeline

# we wrap self in a pipeline, and concatenate with the other
# the TransformerPipeline does the rest, e.g., case distinctions on other
if (
isinstance(other, BaseTransformer)
or is_sklearn_classifier(other)
or is_sklearn_regressor(other)
or is_sklearn_transformer(other)
):
self_as_pipeline = TransformerPipeline(steps=[self])
return self_as_pipeline * other
else:
return NotImplemented

def __rmul__(self, other):
"""Magic * method, return (left) concatenated TransformerPipeline.

Implemented for `other` being a transformer, otherwise returns `NotImplemented`.

Parameters
----------
other: `aeon` transformer, must inherit from BaseTransformer
otherwise, `NotImplemented` is returned

Returns
-------
TransformerPipeline object, concatenation of `other` (first) with `self` (last).
not nested, contains only non-TransformerPipeline `aeon` transformers
"""
from aeon.transformations.compose import TransformerPipeline

# we wrap self in a pipeline, and concatenate with the other
# the TransformerPipeline does the rest, e.g., case distinctions on other
if isinstance(other, BaseTransformer) or is_sklearn_transformer(other):
self_as_pipeline = TransformerPipeline(steps=[self])
return other * self_as_pipeline
else:
return NotImplemented

def __or__(self, other):
"""Magic | method, return MultiplexTranformer.

Implemented for `other` being either a MultiplexTransformer or a transformer.

Parameters
----------
other: `aeon` transformer or aeon MultiplexTransformer

Returns
-------
MultiplexTransformer object
"""
from aeon.transformations.compose import MultiplexTransformer

if isinstance(other, BaseTransformer):
multiplex_self = MultiplexTransformer([self])
return multiplex_self | other
else:
return NotImplemented

def __add__(self, other):
"""Magic + method, return (right) concatenated FeatureUnion.

Implemented for `other` being a transformer, otherwise returns `NotImplemented`.

Parameters
----------
other: `aeon` transformer, must inherit from BaseTransformer
otherwise, `NotImplemented` is returned

Returns
-------
FeatureUnion object, concatenation of `self` (first) with `other` (last).
not nested, contains only non-TransformerPipeline `aeon` transformers
"""
from aeon.transformations.compose import FeatureUnion

# we wrap self in a pipeline, and concatenate with the other
# the FeatureUnion does the rest, e.g., case distinctions on other
if isinstance(other, BaseTransformer):
self_as_pipeline = FeatureUnion(transformer_list=[self])
return self_as_pipeline + other
else:
return NotImplemented

def __radd__(self, other):
"""Magic + method, return (left) concatenated FeatureUnion.

Implemented for `other` being a transformer, otherwise returns `NotImplemented`.

Parameters
----------
other: `aeon` transformer, must inherit from BaseTransformer
otherwise, `NotImplemented` is returned

Returns
-------
FeatureUnion object, concatenation of `other` (first) with `self` (last).
not nested, contains only non-FeatureUnion `aeon` transformers
"""
from aeon.transformations.compose import FeatureUnion

# we wrap self in a pipeline, and concatenate with the other
# the TransformerPipeline does the rest, e.g., case distinctions on other
if isinstance(other, BaseTransformer):
self_as_pipeline = FeatureUnion(transformer_list=[self])
return other + self_as_pipeline
else:
return NotImplemented

def __invert__(self):
"""Magic unary ~ (inversion) method, return InvertTransform of self.

Returns
-------
`InvertTransform` object, containing `self`.
"""
from aeon.transformations.compose import InvertTransform

return InvertTransform(self)

def __neg__(self):
"""Magic unary - (negation) method, return OptionalPassthrough of self.

Intuition: `OptionalPassthrough` is "not having transformer", as an option.

Returns
-------
`OptionalPassthrough` object, containing `self`, with `passthrough=False`.
The `passthrough` parameter can be set via `set_params`.
"""
from aeon.transformations.compose import OptionalPassthrough

return OptionalPassthrough(self, passthrough=False)

def __getitem__(self, key):
"""Magic [...] method, return column subsetted transformer.

First index does intput subsetting, second index does output subsetting.

Keys must be valid inputs for `columns` in `ColumnSubset`.

Parameters
----------
key: valid input for `columns` in `ColumnSubset`, or pair thereof
keys can also be a :-slice, in which case it is considered as not passed

Returns
-------
the following TransformerPipeline object:
ColumnSubset(columns1) * self * ColumnSubset(columns2)
where `columns1` is first or only item in `key`, and `columns2` is the last
if only one item is passed in `key`, only `columns1` is applied to input
"""
from aeon.transformations.subset import ColumnSelect

def is_noneslice(obj):
res = isinstance(obj, slice)
res = res and obj.start is None and obj.stop is None and obj.step is None
return res

if isinstance(key, tuple):
if not len(key) == 2:
raise ValueError(
"there should be one or two keys when calling [] or getitem, "
"e.g., mytrafo[key], or mytrafo[key1, key2]"
)
columns1 = key[0]
columns2 = key[1]
if is_noneslice(columns1) and is_noneslice(columns2):
return self
elif is_noneslice(columns2):
return ColumnSelect(columns1) * self
elif is_noneslice(columns1):
return self * ColumnSelect(columns2)
else:
return ColumnSelect(columns1) * self * ColumnSelect(columns2)
else:
return ColumnSelect(key) * self

def fit(self, X, y=None):
"""Fit transformer to X, optionally to y.

Expand Down
49 changes: 0 additions & 49 deletions aeon/transformations/compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,32 +109,6 @@ class TransformerPipeline(_HeterogenousMetaEstimator, BaseTransformer):
Example 1, option A: construct without strings (unique names are generated for
the two components t1 and t2)
>>> pipe = TransformerPipeline(steps = [t1, t2])

Example 1, option B: construct with strings to give custom names to steps
>>> pipe = TransformerPipeline(
... steps = [
... ("trafo1", t1),
... ("trafo2", t2),
... ]
... )

Example 1, option C: for quick construction, the * dunder method can be used
>>> pipe = t1 * t2

Example 2: sklearn transformers can be used in the pipeline.
If applied to Series, sklearn transformers are applied by series instance.
If applied to Table, sklearn transformers are applied to the table as a whole.
>>> from sklearn.preprocessing import StandardScaler
>>> from aeon.transformations.summarize import SummaryTransformer

This applies the scaler per series, then summarizes:
>>> pipe = StandardScaler() * SummaryTransformer()

This applies the sumamrization, then scales the full summary table:
>>> pipe = SummaryTransformer() * StandardScaler()

This scales the series, then summarizes, then scales the full summary table:
>>> pipe = StandardScaler() * SummaryTransformer() * StandardScaler()
"""

_tags = {
Expand Down Expand Up @@ -656,29 +630,6 @@ class FitInTransform(BaseTransformer):
fitted on the inverse_transform data. This is required to have a non-
state changing transform() method of FitInTransform.

Examples
--------
>>> from aeon.datasets import load_longley
>>> from aeon.forecasting.naive import NaiveForecaster
>>> from aeon.forecasting.base import ForecastingHorizon
>>> from aeon.forecasting.compose import ForecastingPipeline
>>> from aeon.forecasting.model_selection import temporal_train_test_split
>>> from aeon.transformations.compose import FitInTransform
>>> from aeon.transformations.impute import Imputer
>>> y, X = load_longley()
>>> y_train, y_test, X_train, X_test = temporal_train_test_split(y, X)
>>> fh = ForecastingHorizon(y_test.index, is_relative=False)
>>> # we want to fit the Imputer only on the predict (=transform) data.
>>> # note that NaiveForecaster cant use X data, this is just a show case.
>>> pipe = ForecastingPipeline(
... steps=[
... ("imputer", FitInTransform(Imputer(method="mean"))),
... ("forecaster", NaiveForecaster()),
... ]
... )
>>> pipe.fit(y_train, X_train)
ForecastingPipeline(...)
>>> y_pred = pipe.predict(fh=fh, X=X_test)
"""

def __init__(self, transformer, skip_inverse_transform=True):
Expand Down
6 changes: 4 additions & 2 deletions aeon/transformations/lag.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@ class Lag(BaseTransformer):
>>> from aeon.datasets import load_airline
>>> from aeon.transformations.impute import Imputer
>>> from aeon.transformations.lag import Lag
>>> from sklearn.pipeline import make_pipeline
>>> X = load_airline()
>>>
>>> t = Lag([2, 4, -1]) * Imputer("nearest")
>>> t = make_pipeline(Lag([2, 4, -1]),Imputer("nearest"))
>>> Xt = t.fit_transform(X)
"""

Expand Down Expand Up @@ -380,9 +381,10 @@ class ReducerTransform(BaseTransformer):
>>> from aeon.datasets import load_airline
>>> from aeon.transformations.impute import Imputer
>>> from aeon.transformations.lag import Lag
>>> from sklearn.pipeline import make_pipeline
>>> X = load_airline()
>>>
>>> t = Lag([2, 4, -1]) * Imputer("nearest")
>>> t = make_pipeline(Lag([2, 4, -1]),Imputer("nearest"))
>>> Xt = t.fit_transform(X)
"""

Expand Down
Loading
Loading