-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add FunctionalTableTransformer (#901)
Closes #858 ### Summary of Changes This implements a Transformer that wraps a Callable[[Table], Table] so operations on Tables can be inserted into the operation order of a SequentialTableTransformer. Note that this transformer inherently cannot be invertible. Since there is no type checking at runtime, callables with wrong argument or return types will only throw an exception upon calling transform but not during init. --------- Co-authored-by: Simon <[email protected]> Co-authored-by: srose <[email protected]> Co-authored-by: megalinter-bot <[email protected]> Co-authored-by: Lars Reimann <[email protected]>
- Loading branch information
1 parent
134e7d8
commit 37905be
Showing
4 changed files
with
224 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
src/safeds/data/tabular/transformation/_functional_table_transformer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
from __future__ import annotations | ||
|
||
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 | ||
|
||
|
||
class FunctionalTableTransformer(TableTransformer): | ||
""" | ||
Wraps a callable so that it conforms to the TableTransformer interface. | ||
Parameters | ||
---------- | ||
transformer: | ||
A callable that receives a table and returns a table. | ||
""" | ||
|
||
# ------------------------------------------------------------------------------------------------------------------ | ||
# Dunder methods | ||
# ------------------------------------------------------------------------------------------------------------------ | ||
|
||
def __init__( | ||
self, | ||
transformer: Callable[[Table], Table], | ||
) -> None: | ||
super().__init__(None) | ||
self._transformer = transformer | ||
|
||
def __hash__(self) -> int: | ||
return _structural_hash( | ||
super().__hash__(), | ||
self._transformer, | ||
) | ||
|
||
# ------------------------------------------------------------------------------------------------------------------ | ||
# Properties | ||
# ------------------------------------------------------------------------------------------------------------------ | ||
|
||
@property | ||
def is_fitted(self) -> bool: | ||
"""FunctionalTableTransformer is always considered to be fitted.""" | ||
return True | ||
|
||
# ------------------------------------------------------------------------------------------------------------------ | ||
# Learning and transformation | ||
# ------------------------------------------------------------------------------------------------------------------ | ||
|
||
def fit(self, table: Table) -> FunctionalTableTransformer: # noqa: ARG002 | ||
""" | ||
**Note:** For FunctionalTableTransformer this is a no-OP. | ||
Parameters | ||
---------- | ||
table: | ||
Required only to be consistent with other transformers. | ||
Returns | ||
------- | ||
fitted_transformer: | ||
Returns self, because this transformer is always fitted. | ||
""" | ||
return self | ||
|
||
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 | ||
------ | ||
Exception: | ||
Raised when the wrapped callable encounters an error. | ||
""" | ||
return self._transformer(table) | ||
|
||
def fit_and_transform(self, table: Table) -> tuple[FunctionalTableTransformer, Table]: | ||
""" | ||
**Note:** For the FunctionalTableTransformer this is the same as transform(). | ||
Parameters | ||
---------- | ||
table: | ||
The table on which the callable is to be executed. | ||
Returns | ||
------- | ||
fitted_transformer: | ||
Return self because the transformer is always fitted. | ||
transformed_table: | ||
The transformed table. | ||
""" | ||
fitted_transformer = self | ||
transformed_table = self.transform(table) | ||
return fitted_transformer, transformed_table |
102 changes: 102 additions & 0 deletions
102
tests/safeds/data/tabular/transformation/test_functional_table_transformer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
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: | ||
return table.remove_columns(["col1"]) | ||
|
||
|
||
class TestInit: | ||
def test_should_not_raise_type_error(self) -> None: | ||
FunctionalTableTransformer(valid_callable) | ||
|
||
|
||
class TestFit: | ||
def test_should_return_self(self) -> None: | ||
table = Table( | ||
{ | ||
"col1": [1, 2, 3], | ||
"col2": [1, 2, 3], | ||
}, | ||
) | ||
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_specific_error_when_error_in_method(self) -> None: | ||
table = Table( | ||
{ | ||
"col2": [1, 2, 3], | ||
}, | ||
) | ||
transformer = FunctionalTableTransformer(valid_callable) | ||
with pytest.raises(ColumnNotFoundError): | ||
transformer.transform(table) | ||
|
||
def test_should_not_modify_original_table(self) -> None: | ||
table = Table( | ||
{ | ||
"col1": [1, 2, 3], | ||
"col2": [1, 2, 3], | ||
}, | ||
) | ||
transformer = FunctionalTableTransformer(valid_callable) | ||
transformer.transform(table) | ||
assert table == Table( | ||
{ | ||
"col1": [1, 2, 3], | ||
"col2": [1, 2, 3], | ||
}, | ||
) | ||
|
||
def test_should_return_modified_table(self) -> None: | ||
table = Table( | ||
{ | ||
"col1": [1, 2, 3], | ||
"col2": [1, 2, 3], | ||
}, | ||
) | ||
transformer = FunctionalTableTransformer(valid_callable) | ||
transformed_table = transformer.transform(table) | ||
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) | ||
assert transformer.fit_and_transform(table)[0] is transformer | ||
|
||
def test_should_not_modify_original_table(self) -> None: | ||
table = Table( | ||
{ | ||
"col1": [1, 2, 3], | ||
"col2": [1, 2, 3], | ||
}, | ||
) | ||
transformer = FunctionalTableTransformer(valid_callable) | ||
transformer.fit_and_transform(table) | ||
assert table == Table( | ||
{ | ||
"col1": [1, 2, 3], | ||
"col2": [1, 2, 3], | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters