From c566203f44e84075a9f70351b4b21214f941d265 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 5 Jul 2024 12:21:59 +0200 Subject: [PATCH 01/18] Functionality done, tests and proper error handling missing --- .../_functional_table_transformer.py | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/safeds/data/tabular/transformation/_functional_table_transformer.py diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py new file mode 100644 index 000000000..eeb2403ab --- /dev/null +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -0,0 +1,113 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Self + +from safeds._utils import _structural_hash + +from ._table_transformer import TableTransformer + +if TYPE_CHECKING: + from safeds.data.tabular.containers import Table + +class FunctionalTableTransformer(TableTransformer): + """ + Learn a transformation for a set of columns in a `Table` and transform another `Table` with the same columns. + + Parameters + ---------- + column_names: + The list of columns used to fit the transformer. If `None`, all suitable columns are used. + """ + + # ------------------------------------------------------------------------------------------------------------------ + # Dunder methods + # ------------------------------------------------------------------------------------------------------------------ + + def __init__(self, + func: callable[[Table], Table], + ) -> None: + super().__init__(None) + self._func: callable[[Table], Table] = func + + + + def __hash__(self) -> int: + return _structural_hash( + super().__hash__(), + self._func, + ) + + # ------------------------------------------------------------------------------------------------------------------ + # Properties + # ------------------------------------------------------------------------------------------------------------------ + + @property + def is_fitted(self) -> bool: + """FunctionalTableTransformer is always considered to be fitted.""" + return True + + # ------------------------------------------------------------------------------------------------------------------ + # Learning and transformation + # ------------------------------------------------------------------------------------------------------------------ + + def fit(self, table: Table) -> Self: + """ + **Note:** For FunctionalTableTransformer this is a no-OP. + + Parameters + ---------- + table: + Required only to be consistent with other transformers. + + Returns + ------- + fitted_transformer: + self is always fitted. + + """ + fitted_transformer = self + return fitted_transformer + + def transform(self, table: Table) -> Table: + """ + Apply the callable to a table. + + **Note:** The given table is not modified. + + Parameters + ---------- + table: + The table on which on which the callable is executed. + + Returns + ------- + transformed_table: + The transformed table. + + Raises + ------ + #TODO Implement Errors from the table methods + + """ + transformed_table = self._func.__call__(table) + return transformed_table + + def fit_and_transform(self, table: Table) -> tuple[Self, Table]: + """ + **Note:** For the FunctionalTableTransformer this is the same as transform(). + + Parameters + ---------- + table: + The table on which the callable . + + Returns + ------- + fitted_transformer: + self is always fitted. + transformed_table: + The transformed table. + """ + fitted_transformer = self + transformed_table = self.transform(table) + return fitted_transformer, transformed_table From b48ff4c32337c0a83b52c659deaa4f85646c63e2 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 5 Jul 2024 14:38:31 +0200 Subject: [PATCH 02/18] some tests done --- .../test_functional_table_transformer.py | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/safeds/data/tabular/transformation/test_functional_table_transformer.py diff --git a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py new file mode 100644 index 000000000..11e166e5f --- /dev/null +++ b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py @@ -0,0 +1,78 @@ +import pytest +from safeds.data.tabular.containers import Table +from safeds.data.tabular.transformation._functional_table_transformer import FunctionalTableTransformer +from safeds.exceptions import ColumnNotFoundError, ColumnTypeError, TransformerNotFittedError + + +class TestInit: + def should_raise_type_error(self): + assert None is not None + + + +class TestFit: + def valid_callable(table: Table) -> Table: + new_table = table.remove_columns(["col1"]) + return new_table + def should_return_self(self) -> FunctionalTableTransformer: + table = Table( + { + "col1": [1, 2, 3], + "col2": [1, 2, 3], + }, + ) + transformer = FunctionalTableTransformer(self.valid_callable()) + assert transformer.fit(table) is transformer + +class TestTransform: + def valid_callable(table: Table) -> Table: + new_table = table.remove_columns(["col1"]) + return new_table + def sshould_raise_generic_error(self): + #TODO: implement try catch for main method + #TODO: test with a callable like remove columns where we can check error returning if we return non generic error + assert None is not None + +class TestFitAndTransform: + def valid_callable(table: Table) -> Table: + new_table = table.remove_columns(["col1"]) + return new_table + + def should_return_self(self): + table = Table( + { + "col1": [1, 2, 3], + "col2": [1, 2, 3], + + }, + ) + transformer = FunctionalTableTransformer(self.valid_callable()) + assert transformer.fit_and_transform(table)[0] is transformer + + def should_not_modify_original_table(self): + table = Table( + { + "col1": [1, 2, 3], + "col2": [1, 2, 3], + + }, + ) + transformer = FunctionalTableTransformer(self.valid_callable()) + transformer.fit_and_transform(table) + assert table == Table( + { + "col1": [1, 2, 3], + "col2": [1, 2, 3], + + }, + ) + + + + + + + + + + \ No newline at end of file From 2e1e64a24d2b9936e6738ecdc9e99d4a803efd6d Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 5 Jul 2024 14:54:24 +0200 Subject: [PATCH 03/18] more tests added --- .../test_functional_table_transformer.py | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py index 11e166e5f..8c22a7f5e 100644 --- a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py @@ -8,12 +8,11 @@ class TestInit: def should_raise_type_error(self): assert None is not None - - class TestFit: def valid_callable(table: Table) -> Table: new_table = table.remove_columns(["col1"]) return new_table + def should_return_self(self) -> FunctionalTableTransformer: table = Table( { @@ -24,14 +23,48 @@ def should_return_self(self) -> FunctionalTableTransformer: transformer = FunctionalTableTransformer(self.valid_callable()) assert transformer.fit(table) is transformer +class TestIsFitted: + def valid_callable(table: Table) -> Table: + new_table = table.remove_columns(["col1"]) + return new_table + + def should_always_be_fitted(self) -> bool: + transformer = FunctionalTableTransformer(self.valid_callable()) + assert transformer.is_fitted + class TestTransform: def valid_callable(table: Table) -> Table: new_table = table.remove_columns(["col1"]) return new_table - def sshould_raise_generic_error(self): - #TODO: implement try catch for main method - #TODO: test with a callable like remove columns where we can check error returning if we return non generic error - assert None is not None + + def should_raise_generic_error(self): + table = Table( + { + "col2": [1, 2, 3], + + }, + ) + transformer = FunctionalTableTransformer(self.valid_callable()) + with pytest.raises(Exception), match=r"The underlying function encountered an error"): + transformer.transform(table) + + def should_not_modify_original_table(self): + table = Table( + { + "col1": [1, 2, 3], + "col2": [1, 2, 3], + + }, + ) + transformer = FunctionalTableTransformer(self.valid_callable()) + transformer.transform(table) + assert table == Table( + { + "col1": [1, 2, 3], + "col2": [1, 2, 3], + + }, + ) class TestFitAndTransform: def valid_callable(table: Table) -> Table: @@ -67,7 +100,7 @@ def should_not_modify_original_table(self): }, ) - + From cd339b1ab7361a483c3611fc984c61bbd4b3111d Mon Sep 17 00:00:00 2001 From: srose <118634249+wastedareas@users.noreply.github.com> Date: Fri, 5 Jul 2024 14:56:19 +0200 Subject: [PATCH 04/18] transform now except errors from _func and raises generic exception --- .../transformation/_functional_table_transformer.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index eeb2403ab..50bf7b0c4 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -86,11 +86,16 @@ def transform(self, table: Table) -> Table: Raises ------ - #TODO Implement Errors from the table methods + Exception: + Raised when the called _func encounters an error. + #TODO Switch to non-generic exception """ - transformed_table = self._func.__call__(table) - return transformed_table + try: + transformed_table = self._func.__call__(table) + return transformed_table + except Exception as e: + raise Exception("The underlying function encountered an error") from e def fit_and_transform(self, table: Table) -> tuple[Self, Table]: """ From 05c41bfe31ff3c33e74ef1f91bd7850ea1c1fa8d Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 5 Jul 2024 15:02:56 +0200 Subject: [PATCH 05/18] fix: added the functional table transformer to init.py --- src/safeds/data/tabular/transformation/__init__.py | 4 ++++ .../transformation/test_functional_table_transformer.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/safeds/data/tabular/transformation/__init__.py b/src/safeds/data/tabular/transformation/__init__.py index a837d3165..40196f261 100644 --- a/src/safeds/data/tabular/transformation/__init__.py +++ b/src/safeds/data/tabular/transformation/__init__.py @@ -6,6 +6,7 @@ if TYPE_CHECKING: from ._discretizer import Discretizer + from ._functional_table_transformer import FunctionalTableTransformer from ._invertible_table_transformer import InvertibleTableTransformer from ._k_nearest_neighbors_imputer import KNearestNeighborsImputer from ._label_encoder import LabelEncoder @@ -16,10 +17,12 @@ from ._standard_scaler import StandardScaler from ._table_transformer import TableTransformer + apipkg.initpkg( __name__, { "Discretizer": "._discretizer:Discretizer", + "FunctionalTableTransformer": "._functional_table_transformer", "InvertibleTableTransformer": "._invertible_table_transformer:InvertibleTableTransformer", "LabelEncoder": "._label_encoder:LabelEncoder", "OneHotEncoder": "._one_hot_encoder:OneHotEncoder", @@ -34,6 +37,7 @@ __all__ = [ "Discretizer", + "FunctionalTableTransformer", "InvertibleTableTransformer", "LabelEncoder", "OneHotEncoder", diff --git a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py index 8c22a7f5e..427c70e11 100644 --- a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py @@ -1,6 +1,6 @@ import pytest from safeds.data.tabular.containers import Table -from safeds.data.tabular.transformation._functional_table_transformer import FunctionalTableTransformer +from safeds.data.tabular.transformation import FunctionalTableTransformer from safeds.exceptions import ColumnNotFoundError, ColumnTypeError, TransformerNotFittedError @@ -45,7 +45,7 @@ def should_raise_generic_error(self): }, ) transformer = FunctionalTableTransformer(self.valid_callable()) - with pytest.raises(Exception), match=r"The underlying function encountered an error"): + with pytest.raises(Exception, match=r"The underlying function encountered an error"): transformer.transform(table) def should_not_modify_original_table(self): From 1def33c7ffd6db792f9fed183f35cd2c2be61f8d Mon Sep 17 00:00:00 2001 From: srose <118634249+wastedareas@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:50:08 +0200 Subject: [PATCH 06/18] Tests are now getting recognized --- .../test_functional_table_transformer.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py index 427c70e11..c9098e295 100644 --- a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py @@ -5,15 +5,19 @@ class TestInit: - def should_raise_type_error(self): - assert None is not None + def invalid_callable(i: int) -> float: + return float(i) + + def test_should_raise_type_error(self): + with pytest.raises(TypeError): + _transformer = FunctionalTableTransformer(self.invalid_callable()) class TestFit: def valid_callable(table: Table) -> Table: new_table = table.remove_columns(["col1"]) return new_table - def should_return_self(self) -> FunctionalTableTransformer: + def test_should_return_self(self) -> FunctionalTableTransformer: table = Table( { "col1": [1, 2, 3], @@ -28,7 +32,7 @@ def valid_callable(table: Table) -> Table: new_table = table.remove_columns(["col1"]) return new_table - def should_always_be_fitted(self) -> bool: + def test_should_always_be_fitted(self) -> bool: transformer = FunctionalTableTransformer(self.valid_callable()) assert transformer.is_fitted @@ -37,7 +41,7 @@ def valid_callable(table: Table) -> Table: new_table = table.remove_columns(["col1"]) return new_table - def should_raise_generic_error(self): + def test_should_raise_generic_error(self): table = Table( { "col2": [1, 2, 3], @@ -48,7 +52,7 @@ def should_raise_generic_error(self): with pytest.raises(Exception, match=r"The underlying function encountered an error"): transformer.transform(table) - def should_not_modify_original_table(self): + def test_should_not_modify_original_table(self): table = Table( { "col1": [1, 2, 3], @@ -71,7 +75,7 @@ def valid_callable(table: Table) -> Table: new_table = table.remove_columns(["col1"]) return new_table - def should_return_self(self): + def test_should_return_self(self): table = Table( { "col1": [1, 2, 3], @@ -82,7 +86,7 @@ def should_return_self(self): transformer = FunctionalTableTransformer(self.valid_callable()) assert transformer.fit_and_transform(table)[0] is transformer - def should_not_modify_original_table(self): + def test_should_not_modify_original_table(self): table = Table( { "col1": [1, 2, 3], From 09424c741fe4d2bc566e9ac4a5c5939ac8abac32 Mon Sep 17 00:00:00 2001 From: Simon Date: Thu, 11 Jul 2024 16:46:51 +0200 Subject: [PATCH 07/18] actually works now, only valid callable check and test for it is missing --- .../data/tabular/transformation/__init__.py | 2 +- .../_functional_table_transformer.py | 15 ++-- .../test_functional_table_transformer.py | 81 ++++++++++++------- 3 files changed, 60 insertions(+), 38 deletions(-) diff --git a/src/safeds/data/tabular/transformation/__init__.py b/src/safeds/data/tabular/transformation/__init__.py index 40196f261..bf705e10d 100644 --- a/src/safeds/data/tabular/transformation/__init__.py +++ b/src/safeds/data/tabular/transformation/__init__.py @@ -22,7 +22,7 @@ __name__, { "Discretizer": "._discretizer:Discretizer", - "FunctionalTableTransformer": "._functional_table_transformer", + "FunctionalTableTransformer": "._functional_table_transformer:FunctionalTableTransformer", "InvertibleTableTransformer": "._invertible_table_transformer:InvertibleTableTransformer", "LabelEncoder": "._label_encoder:LabelEncoder", "OneHotEncoder": "._one_hot_encoder:OneHotEncoder", diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index 50bf7b0c4..1f5d52912 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -5,9 +5,10 @@ from safeds._utils import _structural_hash from ._table_transformer import TableTransformer +from safeds.data.tabular.containers import Table -if TYPE_CHECKING: - from safeds.data.tabular.containers import Table +#if TYPE_CHECKING: +# from safeds.data.tabular.containers import Table class FunctionalTableTransformer(TableTransformer): """ @@ -24,10 +25,10 @@ class FunctionalTableTransformer(TableTransformer): # ------------------------------------------------------------------------------------------------------------------ def __init__(self, - func: callable[[Table], Table], + funct, ) -> None: super().__init__(None) - self._func: callable[[Table], Table] = func + self._func = funct @@ -50,7 +51,7 @@ def is_fitted(self) -> bool: # Learning and transformation # ------------------------------------------------------------------------------------------------------------------ - def fit(self, table: Table) -> Self: + def fit(self, table: Table) -> FunctionalTableTransformer: """ **Note:** For FunctionalTableTransformer this is a no-OP. @@ -92,12 +93,12 @@ def transform(self, table: Table) -> Table: """ try: - transformed_table = self._func.__call__(table) + transformed_table = self._func(table) return transformed_table except Exception as e: raise Exception("The underlying function encountered an error") from e - def fit_and_transform(self, table: Table) -> tuple[Self, Table]: + def fit_and_transform(self, table: Table) -> tuple[FunctionalTableTransformer, Table]: """ **Note:** For the FunctionalTableTransformer this is the same as transform(). diff --git a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py index c9098e295..c947d6108 100644 --- a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py @@ -4,55 +4,69 @@ from safeds.exceptions import ColumnNotFoundError, ColumnTypeError, TransformerNotFittedError +#def invalid_callable(i: int) -> float: +# return float(i) + +#def valid_callable(table) -> Table: +# new_table = table.remove_columns(["col1"]) +# return new_table + class TestInit: - def invalid_callable(i: int) -> float: + def invalid_callable(self, i: int) -> float: return float(i) + + def valid_callable(self, table) -> Table: + new_table = table.remove_columns(self, ["col1"]) + return new_table - def test_should_raise_type_error(self): - with pytest.raises(TypeError): - _transformer = FunctionalTableTransformer(self.invalid_callable()) + #def test_should_raise_type_error(self) -> None: + # with pytest.raises(TypeError): + # transformer = FunctionalTableTransformer(invalid_callable) + + def test_should_not_raise_type_error(self) -> None: + transformer = FunctionalTableTransformer(self.valid_callable) class TestFit: - def valid_callable(table: Table) -> Table: + def valid_callable(self, table: Table) -> Table: new_table = table.remove_columns(["col1"]) return new_table - def test_should_return_self(self) -> FunctionalTableTransformer: + def test_should_return_self(self) -> None: table = Table( { "col1": [1, 2, 3], "col2": [1, 2, 3], }, ) - transformer = FunctionalTableTransformer(self.valid_callable()) + transformer = FunctionalTableTransformer(self.valid_callable) assert transformer.fit(table) is transformer class TestIsFitted: - def valid_callable(table: Table) -> Table: + def valid_callable(self, table: Table) -> Table: new_table = table.remove_columns(["col1"]) return new_table - def test_should_always_be_fitted(self) -> bool: - transformer = FunctionalTableTransformer(self.valid_callable()) + def test_should_always_be_fitted(self) -> None: + transformer = FunctionalTableTransformer(self.valid_callable) assert transformer.is_fitted class TestTransform: - def valid_callable(table: Table) -> Table: + def valid_callable(self, table: Table) -> Table: new_table = table.remove_columns(["col1"]) return new_table - def test_should_raise_generic_error(self): + def test_should_raise_generic_error(self) -> None: table = Table( { "col2": [1, 2, 3], }, ) - transformer = FunctionalTableTransformer(self.valid_callable()) + transformer = FunctionalTableTransformer(self.valid_callable) with pytest.raises(Exception, match=r"The underlying function encountered an error"): transformer.transform(table) - def test_should_not_modify_original_table(self): + def test_should_not_modify_original_table(self) -> None: table = Table( { "col1": [1, 2, 3], @@ -60,7 +74,7 @@ def test_should_not_modify_original_table(self): }, ) - transformer = FunctionalTableTransformer(self.valid_callable()) + transformer = FunctionalTableTransformer(self.valid_callable) transformer.transform(table) assert table == Table( { @@ -70,12 +84,29 @@ def test_should_not_modify_original_table(self): }, ) + def test_should_return_modified_table(self) -> None: + table = Table( + { + "col1": [1, 2, 3], + "col2": [1, 2, 3], + + }, + ) + transformer = FunctionalTableTransformer(self.valid_callable) + transformed_table = transformer.transform(table) + assert transformed_table == Table( + { + "col2": [1, 2, 3], + + }, + ) + class TestFitAndTransform: - def valid_callable(table: Table) -> Table: + def valid_callable(self, table: Table) -> Table: new_table = table.remove_columns(["col1"]) return new_table - def test_should_return_self(self): + def test_should_return_self(self) -> None: table = Table( { "col1": [1, 2, 3], @@ -83,10 +114,10 @@ def test_should_return_self(self): }, ) - transformer = FunctionalTableTransformer(self.valid_callable()) + transformer = FunctionalTableTransformer(self.valid_callable) assert transformer.fit_and_transform(table)[0] is transformer - def test_should_not_modify_original_table(self): + def test_should_not_modify_original_table(self) -> None: table = Table( { "col1": [1, 2, 3], @@ -94,7 +125,7 @@ def test_should_not_modify_original_table(self): }, ) - transformer = FunctionalTableTransformer(self.valid_callable()) + transformer = FunctionalTableTransformer(self.valid_callable) transformer.fit_and_transform(table) assert table == Table( { @@ -103,13 +134,3 @@ def test_should_not_modify_original_table(self): }, ) - - - - - - - - - - \ No newline at end of file From f6dcc52fa889e1920cb5480d62f5524b1b4dfb68 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 12 Jul 2024 10:04:54 +0200 Subject: [PATCH 08/18] Ready for pull request --- .../_functional_table_transformer.py | 36 +++++----- .../test_functional_table_transformer.py | 66 +++++++------------ 2 files changed, 42 insertions(+), 60 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index 1f5d52912..376ca2aea 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -1,14 +1,16 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Self +from typing import TYPE_CHECKING from safeds._utils import _structural_hash +if TYPE_CHECKING: + from collections.abc import Callable + + from safeds.data.tabular.containers import Table + from ._table_transformer import TableTransformer -from safeds.data.tabular.containers import Table -#if TYPE_CHECKING: -# from safeds.data.tabular.containers import Table class FunctionalTableTransformer(TableTransformer): """ @@ -16,8 +18,8 @@ class FunctionalTableTransformer(TableTransformer): Parameters ---------- - column_names: - The list of columns used to fit the transformer. If `None`, all suitable columns are used. + funct: + The Callable that receives a table and returns a table that is to be wrapped. """ # ------------------------------------------------------------------------------------------------------------------ @@ -25,7 +27,7 @@ class FunctionalTableTransformer(TableTransformer): # ------------------------------------------------------------------------------------------------------------------ def __init__(self, - funct, + funct: Callable[[Table], Table], ) -> None: super().__init__(None) self._func = funct @@ -51,7 +53,7 @@ def is_fitted(self) -> bool: # Learning and transformation # ------------------------------------------------------------------------------------------------------------------ - def fit(self, table: Table) -> FunctionalTableTransformer: + def fit(self, table: Table) -> FunctionalTableTransformer: # noqa: ARG002 """ **Note:** For FunctionalTableTransformer this is a no-OP. @@ -63,11 +65,10 @@ def fit(self, table: Table) -> FunctionalTableTransformer: Returns ------- fitted_transformer: - self is always fitted. + Returns self, because this transformer is always fitted. """ - fitted_transformer = self - return fitted_transformer + return self def transform(self, table: Table) -> Table: """ @@ -88,15 +89,14 @@ def transform(self, table: Table) -> Table: Raises ------ Exception: - Raised when the called _func encounters an error. - #TODO Switch to non-generic exception + Raised when the wrapped callable encounters an error. """ try: - transformed_table = self._func(table) - return transformed_table + return self._func(table) except Exception as e: - raise Exception("The underlying function encountered an error") from e + #TODO Evaluate if switch to non-generic exception is useful, as _func can be any callable + raise Exception("The underlying function encountered an error") from e # noqa: TRY002 def fit_and_transform(self, table: Table) -> tuple[FunctionalTableTransformer, Table]: """ @@ -105,12 +105,12 @@ def fit_and_transform(self, table: Table) -> tuple[FunctionalTableTransformer, T Parameters ---------- table: - The table on which the callable . + The table on which the callable is to be executed. Returns ------- fitted_transformer: - self is always fitted. + Return self because the transformer is always fitted. transformed_table: The transformed table. """ diff --git a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py index c947d6108..c2b0f8178 100644 --- a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py @@ -1,36 +1,19 @@ import pytest from safeds.data.tabular.containers import Table from safeds.data.tabular.transformation import FunctionalTableTransformer -from safeds.exceptions import ColumnNotFoundError, ColumnTypeError, TransformerNotFittedError -#def invalid_callable(i: int) -> float: -# return float(i) +def invalid_callable(i: int) -> float: + return float(i) -#def valid_callable(table) -> Table: -# new_table = table.remove_columns(["col1"]) -# return new_table +def valid_callable(table) -> Table: + return table.remove_columns(["col1"]) class TestInit: - def invalid_callable(self, i: int) -> float: - return float(i) - - def valid_callable(self, table) -> Table: - new_table = table.remove_columns(self, ["col1"]) - return new_table - - #def test_should_raise_type_error(self) -> None: - # with pytest.raises(TypeError): - # transformer = FunctionalTableTransformer(invalid_callable) - def test_should_not_raise_type_error(self) -> None: - transformer = FunctionalTableTransformer(self.valid_callable) + FunctionalTableTransformer(valid_callable) class TestFit: - def valid_callable(self, table: Table) -> Table: - new_table = table.remove_columns(["col1"]) - return new_table - def test_should_return_self(self) -> None: table = Table( { @@ -38,31 +21,34 @@ def test_should_return_self(self) -> None: "col2": [1, 2, 3], }, ) - transformer = FunctionalTableTransformer(self.valid_callable) + transformer = FunctionalTableTransformer(valid_callable) assert transformer.fit(table) is transformer class TestIsFitted: - def valid_callable(self, table: Table) -> Table: - new_table = table.remove_columns(["col1"]) - return new_table - def test_should_always_be_fitted(self) -> None: - transformer = FunctionalTableTransformer(self.valid_callable) + transformer = FunctionalTableTransformer(valid_callable) assert transformer.is_fitted class TestTransform: - def valid_callable(self, table: Table) -> Table: - new_table = table.remove_columns(["col1"]) - return new_table - - def test_should_raise_generic_error(self) -> None: + def test_should_raise_generic_error_when_error_in_method(self) -> None: + table = Table( + { + "col2": [1, 2, 3], + + }, + ) + transformer = FunctionalTableTransformer(valid_callable) + with pytest.raises(Exception, match=r"The underlying function encountered an error"): + transformer.transform(table) + + def test_should_raise_generic_error_when_callable_wrong_type(self) -> None: table = Table( { "col2": [1, 2, 3], }, ) - transformer = FunctionalTableTransformer(self.valid_callable) + transformer = FunctionalTableTransformer(invalid_callable) with pytest.raises(Exception, match=r"The underlying function encountered an error"): transformer.transform(table) @@ -74,7 +60,7 @@ def test_should_not_modify_original_table(self) -> None: }, ) - transformer = FunctionalTableTransformer(self.valid_callable) + transformer = FunctionalTableTransformer(valid_callable) transformer.transform(table) assert table == Table( { @@ -92,7 +78,7 @@ def test_should_return_modified_table(self) -> None: }, ) - transformer = FunctionalTableTransformer(self.valid_callable) + transformer = FunctionalTableTransformer(valid_callable) transformed_table = transformer.transform(table) assert transformed_table == Table( { @@ -102,10 +88,6 @@ def test_should_return_modified_table(self) -> None: ) class TestFitAndTransform: - def valid_callable(self, table: Table) -> Table: - new_table = table.remove_columns(["col1"]) - return new_table - def test_should_return_self(self) -> None: table = Table( { @@ -114,7 +96,7 @@ def test_should_return_self(self) -> None: }, ) - transformer = FunctionalTableTransformer(self.valid_callable) + transformer = FunctionalTableTransformer(valid_callable) assert transformer.fit_and_transform(table)[0] is transformer def test_should_not_modify_original_table(self) -> None: @@ -125,7 +107,7 @@ def test_should_not_modify_original_table(self) -> None: }, ) - transformer = FunctionalTableTransformer(self.valid_callable) + transformer = FunctionalTableTransformer(valid_callable) transformer.fit_and_transform(table) assert table == Table( { From f0af934e50c3bdae4ea10df33ca8939c4010a60c Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 12 Jul 2024 10:34:05 +0200 Subject: [PATCH 09/18] Added type declaration to valid_callable in tests --- .../tabular/transformation/test_functional_table_transformer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py index c2b0f8178..e1144c26a 100644 --- a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py @@ -6,7 +6,7 @@ def invalid_callable(i: int) -> float: return float(i) -def valid_callable(table) -> Table: +def valid_callable(table: Table) -> Table: return table.remove_columns(["col1"]) class TestInit: From 53aec431ca5d88f2276810b4e038c705762cce40 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 12 Jul 2024 11:01:57 +0200 Subject: [PATCH 10/18] removed superfluous test for wrong callable type --- .../test_functional_table_transformer.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py index e1144c26a..22a3e29f0 100644 --- a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py @@ -3,9 +3,6 @@ from safeds.data.tabular.transformation import FunctionalTableTransformer -def invalid_callable(i: int) -> float: - return float(i) - def valid_callable(table: Table) -> Table: return table.remove_columns(["col1"]) @@ -41,17 +38,6 @@ def test_should_raise_generic_error_when_error_in_method(self) -> None: with pytest.raises(Exception, match=r"The underlying function encountered an error"): transformer.transform(table) - def test_should_raise_generic_error_when_callable_wrong_type(self) -> None: - table = Table( - { - "col2": [1, 2, 3], - - }, - ) - transformer = FunctionalTableTransformer(invalid_callable) - with pytest.raises(Exception, match=r"The underlying function encountered an error"): - transformer.transform(table) - def test_should_not_modify_original_table(self) -> None: table = Table( { From 99a548be82230d6f68bacb7696c4ae8a45dbe1f8 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:03:39 +0000 Subject: [PATCH 11/18] style: apply automated linter fixes --- .../_functional_table_transformer.py | 17 ++++++++--------- .../test_functional_table_transformer.py | 15 ++++++--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index 376ca2aea..0354ae717 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -26,14 +26,13 @@ class FunctionalTableTransformer(TableTransformer): # Dunder methods # ------------------------------------------------------------------------------------------------------------------ - def __init__(self, - funct: Callable[[Table], Table], - ) -> None: + def __init__( + self, + funct: Callable[[Table], Table], + ) -> None: super().__init__(None) self._func = funct - - def __hash__(self) -> int: return _structural_hash( super().__hash__(), @@ -53,7 +52,7 @@ def is_fitted(self) -> bool: # Learning and transformation # ------------------------------------------------------------------------------------------------------------------ - def fit(self, table: Table) -> FunctionalTableTransformer: # noqa: ARG002 + def fit(self, table: Table) -> FunctionalTableTransformer: # noqa: ARG002 """ **Note:** For FunctionalTableTransformer this is a no-OP. @@ -66,7 +65,7 @@ def fit(self, table: Table) -> FunctionalTableTransformer: # noqa: ARG002 ------- fitted_transformer: Returns self, because this transformer is always fitted. - + """ return self @@ -90,12 +89,12 @@ def transform(self, table: Table) -> Table: ------ Exception: Raised when the wrapped callable encounters an error. - + """ try: return self._func(table) except Exception as e: - #TODO Evaluate if switch to non-generic exception is useful, as _func can be any callable + # TODO Evaluate if switch to non-generic exception is useful, as _func can be any callable raise Exception("The underlying function encountered an error") from e # noqa: TRY002 def fit_and_transform(self, table: Table) -> tuple[FunctionalTableTransformer, Table]: diff --git a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py index 22a3e29f0..a62bc61e0 100644 --- a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py @@ -6,9 +6,11 @@ def valid_callable(table: Table) -> Table: return table.remove_columns(["col1"]) + class TestInit: def test_should_not_raise_type_error(self) -> None: - FunctionalTableTransformer(valid_callable) + FunctionalTableTransformer(valid_callable) + class TestFit: def test_should_return_self(self) -> None: @@ -21,17 +23,18 @@ def test_should_return_self(self) -> None: transformer = FunctionalTableTransformer(valid_callable) assert transformer.fit(table) is transformer + class TestIsFitted: def test_should_always_be_fitted(self) -> None: transformer = FunctionalTableTransformer(valid_callable) assert transformer.is_fitted + class TestTransform: def test_should_raise_generic_error_when_error_in_method(self) -> None: table = Table( { "col2": [1, 2, 3], - }, ) transformer = FunctionalTableTransformer(valid_callable) @@ -43,7 +46,6 @@ def test_should_not_modify_original_table(self) -> None: { "col1": [1, 2, 3], "col2": [1, 2, 3], - }, ) transformer = FunctionalTableTransformer(valid_callable) @@ -52,7 +54,6 @@ def test_should_not_modify_original_table(self) -> None: { "col1": [1, 2, 3], "col2": [1, 2, 3], - }, ) @@ -61,7 +62,6 @@ def test_should_return_modified_table(self) -> None: { "col1": [1, 2, 3], "col2": [1, 2, 3], - }, ) transformer = FunctionalTableTransformer(valid_callable) @@ -69,17 +69,16 @@ def test_should_return_modified_table(self) -> None: assert transformed_table == Table( { "col2": [1, 2, 3], - }, ) + class TestFitAndTransform: def test_should_return_self(self) -> None: table = Table( { "col1": [1, 2, 3], "col2": [1, 2, 3], - }, ) transformer = FunctionalTableTransformer(valid_callable) @@ -90,7 +89,6 @@ def test_should_not_modify_original_table(self) -> None: { "col1": [1, 2, 3], "col2": [1, 2, 3], - }, ) transformer = FunctionalTableTransformer(valid_callable) @@ -99,6 +97,5 @@ def test_should_not_modify_original_table(self) -> None: { "col1": [1, 2, 3], "col2": [1, 2, 3], - }, ) From 0168bfee26babc563cf1bd60896d0592e6e7987f Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 12 Jul 2024 11:23:28 +0200 Subject: [PATCH 12/18] refactored parameter name funct to transformer --- .../tabular/transformation/_functional_table_transformer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index 376ca2aea..9adc3785d 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -18,7 +18,7 @@ class FunctionalTableTransformer(TableTransformer): Parameters ---------- - funct: + transformer: The Callable that receives a table and returns a table that is to be wrapped. """ @@ -27,10 +27,10 @@ class FunctionalTableTransformer(TableTransformer): # ------------------------------------------------------------------------------------------------------------------ def __init__(self, - funct: Callable[[Table], Table], + transformer: Callable[[Table], Table], ) -> None: super().__init__(None) - self._func = funct + self._func = transformer From 6eee85c2aad7cfb97d1852af9c3eaab8aa6409a3 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 12 Jul 2024 11:24:59 +0200 Subject: [PATCH 13/18] refactored self._func to self._transformer --- .../tabular/transformation/_functional_table_transformer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index 9adc3785d..1657eba7e 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -30,14 +30,14 @@ def __init__(self, transformer: Callable[[Table], Table], ) -> None: super().__init__(None) - self._func = transformer + self._transformer = transformer def __hash__(self) -> int: return _structural_hash( super().__hash__(), - self._func, + self._transformer, ) # ------------------------------------------------------------------------------------------------------------------ @@ -93,7 +93,7 @@ def transform(self, table: Table) -> Table: """ try: - return self._func(table) + return self._transformer(table) except Exception as e: #TODO Evaluate if switch to non-generic exception is useful, as _func can be any callable raise Exception("The underlying function encountered an error") from e # noqa: TRY002 From 1a264d673f30e07f744e18408ddb631fae9f4088 Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 12 Jul 2024 11:28:48 +0200 Subject: [PATCH 14/18] Changed the docstring to better represent the purpose of the class --- .../tabular/transformation/_functional_table_transformer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index 1657eba7e..756b296c7 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -14,12 +14,12 @@ class FunctionalTableTransformer(TableTransformer): """ - Learn a transformation for a set of columns in a `Table` and transform another `Table` with the same columns. + Wraps a Callable[[Table], Table] so that it can be used in a SequentialTableTransformer. Parameters ---------- transformer: - The Callable that receives a table and returns a table that is to be wrapped. + The Callable that receives a table and returns a table. """ # ------------------------------------------------------------------------------------------------------------------ From a433b82bf54b3652e1ec8b864195e8d06bd9273e Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 12 Jul 2024 11:41:15 +0200 Subject: [PATCH 15/18] removed the wrapping generic exception in transform() --- .../tabular/transformation/_functional_table_transformer.py | 6 +----- .../transformation/test_functional_table_transformer.py | 5 +++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index 756b296c7..a233713c7 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -92,11 +92,7 @@ def transform(self, table: Table) -> Table: Raised when the wrapped callable encounters an error. """ - try: - return self._transformer(table) - except Exception as e: - #TODO Evaluate if switch to non-generic exception is useful, as _func can be any callable - raise Exception("The underlying function encountered an error") from e # noqa: TRY002 + return self._transformer(table) def fit_and_transform(self, table: Table) -> tuple[FunctionalTableTransformer, Table]: """ diff --git a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py index 22a3e29f0..5c9667a8b 100644 --- a/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_functional_table_transformer.py @@ -1,6 +1,7 @@ import pytest from safeds.data.tabular.containers import Table from safeds.data.tabular.transformation import FunctionalTableTransformer +from safeds.exceptions import ColumnNotFoundError def valid_callable(table: Table) -> Table: @@ -27,7 +28,7 @@ def test_should_always_be_fitted(self) -> None: assert transformer.is_fitted class TestTransform: - def test_should_raise_generic_error_when_error_in_method(self) -> None: + def test_should_raise_specific_error_when_error_in_method(self) -> None: table = Table( { "col2": [1, 2, 3], @@ -35,7 +36,7 @@ def test_should_raise_generic_error_when_error_in_method(self) -> None: }, ) transformer = FunctionalTableTransformer(valid_callable) - with pytest.raises(Exception, match=r"The underlying function encountered an error"): + with pytest.raises(ColumnNotFoundError): transformer.transform(table) def test_should_not_modify_original_table(self) -> None: From 22f4d8623f3c7ae482539a8aefe7d368b4ccceda Mon Sep 17 00:00:00 2001 From: Simon Date: Fri, 12 Jul 2024 11:58:40 +0200 Subject: [PATCH 16/18] added FunctionalTableTransformer to test_table_transformer.py --- .../data/tabular/transformation/test_table_transformer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/safeds/data/tabular/transformation/test_table_transformer.py b/tests/safeds/data/tabular/transformation/test_table_transformer.py index d03b157e1..4e5ce7ecd 100644 --- a/tests/safeds/data/tabular/transformation/test_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_table_transformer.py @@ -4,6 +4,7 @@ from safeds.data.tabular.containers import Table from safeds.data.tabular.transformation import ( Discretizer, + FunctionalTableTransformer, KNearestNeighborsImputer, LabelEncoder, OneHotEncoder, @@ -50,6 +51,8 @@ def transformers_non_numeric() -> list[TableTransformer]: LabelEncoder(column_names="col1"), ] +def valid_callable_for_functional_table_transformer(table: Table) -> Table: + return table.remove_columns(["col1"]) def transformers() -> list[TableTransformer]: """ @@ -69,6 +72,7 @@ def transformers() -> list[TableTransformer]: + [ SimpleImputer(strategy=SimpleImputer.Strategy.mode()), KNearestNeighborsImputer(neighbor_count=3, value_to_replace=None), + FunctionalTableTransformer(valid_callable_for_functional_table_transformer), ] ) From b458737a063942fa5435ae8a64b3a1c27927f549 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Fri, 12 Jul 2024 11:04:35 +0000 Subject: [PATCH 17/18] style: apply automated linter fixes --- .../transformation/_functional_table_transformer.py | 9 ++++----- .../tabular/transformation/test_table_transformer.py | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index 3104e5932..cf2472a79 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -26,14 +26,13 @@ class FunctionalTableTransformer(TableTransformer): # Dunder methods # ------------------------------------------------------------------------------------------------------------------ - def __init__(self, - transformer: Callable[[Table], Table], - ) -> None: + def __init__( + self, + transformer: Callable[[Table], Table], + ) -> None: super().__init__(None) self._transformer = transformer - - def __hash__(self) -> int: return _structural_hash( super().__hash__(), diff --git a/tests/safeds/data/tabular/transformation/test_table_transformer.py b/tests/safeds/data/tabular/transformation/test_table_transformer.py index 4e5ce7ecd..0f741092d 100644 --- a/tests/safeds/data/tabular/transformation/test_table_transformer.py +++ b/tests/safeds/data/tabular/transformation/test_table_transformer.py @@ -51,9 +51,11 @@ def transformers_non_numeric() -> list[TableTransformer]: LabelEncoder(column_names="col1"), ] + def valid_callable_for_functional_table_transformer(table: Table) -> Table: return table.remove_columns(["col1"]) + def transformers() -> list[TableTransformer]: """ Return the list of all transformers to test. From 2e2154b651e79fcc0897b3dcb680d5ddc4e00ad7 Mon Sep 17 00:00:00 2001 From: Lars Reimann Date: Fri, 12 Jul 2024 13:16:56 +0200 Subject: [PATCH 18/18] docs: minor improvements to docstrings --- .../tabular/transformation/_functional_table_transformer.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/safeds/data/tabular/transformation/_functional_table_transformer.py b/src/safeds/data/tabular/transformation/_functional_table_transformer.py index cf2472a79..7c4add23c 100644 --- a/src/safeds/data/tabular/transformation/_functional_table_transformer.py +++ b/src/safeds/data/tabular/transformation/_functional_table_transformer.py @@ -14,12 +14,12 @@ class FunctionalTableTransformer(TableTransformer): """ - Wraps a Callable[[Table], Table] so that it can be used in a SequentialTableTransformer. + Wraps a callable so that it conforms to the TableTransformer interface. Parameters ---------- transformer: - The Callable that receives a table and returns a table. + A callable that receives a table and returns a table. """ # ------------------------------------------------------------------------------------------------------------------ @@ -65,7 +65,6 @@ def fit(self, table: Table) -> FunctionalTableTransformer: # noqa: ARG002 ------- fitted_transformer: Returns self, because this transformer is always fitted. - """ return self @@ -89,7 +88,6 @@ def transform(self, table: Table) -> Table: ------ Exception: Raised when the wrapped callable encounters an error. - """ return self._transformer(table)