From d577b0c0b15ddff407f51da3f40bdf7f69be5f66 Mon Sep 17 00:00:00 2001 From: alex-senger Date: Fri, 21 Apr 2023 15:19:26 +0200 Subject: [PATCH 01/10] feat: add alpha parameter to `elastic_net_regression` --- .../regression/_elastic_net_regression.py | 13 ++++++++--- .../regression/test_elastic_net_regression.py | 22 +++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 tests/safeds/ml/classical/regression/test_elastic_net_regression.py diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index 05de26dfb..2189efbbe 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -1,5 +1,6 @@ from __future__ import annotations +from warnings import warn from typing import TYPE_CHECKING from sklearn.linear_model import ElasticNet as sk_ElasticNet @@ -15,7 +16,13 @@ class ElasticNetRegression(Regressor): """Elastic net regression.""" - def __init__(self) -> None: + def __init__(self, alpha: float = 1.0) -> None: + if alpha < 0: + raise ValueError("alpha must be positive") + if alpha == 0: + warn("alpha=0 is equivalent to LinearRegression. Use it instead.", UserWarning) + + self._alpha = alpha self._wrapped_regressor: sk_ElasticNet | None = None self._feature_names: list[str] | None = None self._target_name: str | None = None @@ -41,10 +48,10 @@ def fit(self, training_set: TaggedTable) -> ElasticNetRegression: LearningError If the training data contains invalid values or if the training failed. """ - wrapped_regressor = sk_ElasticNet() + wrapped_regressor = sk_ElasticNet(alpha=self._alpha) fit(wrapped_regressor, training_set) - result = ElasticNetRegression() + result = ElasticNetRegression(alpha=self._alpha) result._wrapped_regressor = wrapped_regressor result._feature_names = training_set.features.column_names result._target_name = training_set.target.name diff --git a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py new file mode 100644 index 000000000..d0f609d95 --- /dev/null +++ b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py @@ -0,0 +1,22 @@ +import pytest + +from safeds.data.tabular.containers import Table +from safeds.ml.classical.regression import ElasticNetRegression + + +def test_alpha_invalid() -> None: + with pytest.raises(ValueError, match="alpha must be positive"): + ElasticNetRegression(alpha=-1.0) + + +def test_alpha_valid() -> None: + training_set = Table.from_dict({"col1": [1, 2, 3, 4], "col2": [1, 2, 3, 4]}) + tagged_training_set = training_set.tag_columns(target_name="col1", feature_names=["col2"]) + + elastic_net_regression = ElasticNetRegression(alpha=1.0).fit(tagged_training_set) + assert elastic_net_regression._wrapped_regressor is not None + assert elastic_net_regression._wrapped_regressor.alpha == elastic_net_regression._alpha + + + + From ce14ed10e9f9ced022217cf0577a59fbff783091 Mon Sep 17 00:00:00 2001 From: alex-senger Date: Fri, 21 Apr 2023 16:07:27 +0200 Subject: [PATCH 02/10] feat: add warning test --- .../ml/classical/regression/test_elastic_net_regression.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py index d0f609d95..da8b530df 100644 --- a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py +++ b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py @@ -9,6 +9,11 @@ def test_alpha_invalid() -> None: ElasticNetRegression(alpha=-1.0) +def test_alpha_warning() -> None: + with pytest.warns(UserWarning, match="alpha=0 is equivalent to LinearRegression. Use it instead."): + ElasticNetRegression(alpha=0.0) + + def test_alpha_valid() -> None: training_set = Table.from_dict({"col1": [1, 2, 3, 4], "col2": [1, 2, 3, 4]}) tagged_training_set = training_set.tag_columns(target_name="col1", feature_names=["col2"]) From 759d88ece4e0cf855017bdbb529ed12930094c68 Mon Sep 17 00:00:00 2001 From: alex-senger Date: Fri, 21 Apr 2023 16:41:06 +0200 Subject: [PATCH 03/10] fix: set `stacklevel` of warning --- src/safeds/ml/classical/regression/_elastic_net_regression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index 2189efbbe..32e490734 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -20,7 +20,7 @@ def __init__(self, alpha: float = 1.0) -> None: if alpha < 0: raise ValueError("alpha must be positive") if alpha == 0: - warn("alpha=0 is equivalent to LinearRegression. Use it instead.", UserWarning) + warn("alpha=0 is equivalent to LinearRegression. Use it instead.", UserWarning, stacklevel=1) self._alpha = alpha self._wrapped_regressor: sk_ElasticNet | None = None From f80f46aab9aeb2a2997158ee3424e1463c75c633 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:42:47 +0000 Subject: [PATCH 04/10] style: apply automated linter fixes --- .../ml/classical/regression/_elastic_net_regression.py | 2 +- .../ml/classical/regression/test_elastic_net_regression.py | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index 32e490734..51f7af933 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -1,7 +1,7 @@ from __future__ import annotations -from warnings import warn from typing import TYPE_CHECKING +from warnings import warn from sklearn.linear_model import ElasticNet as sk_ElasticNet diff --git a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py index da8b530df..58d07d8b0 100644 --- a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py +++ b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py @@ -1,5 +1,4 @@ import pytest - from safeds.data.tabular.containers import Table from safeds.ml.classical.regression import ElasticNetRegression @@ -21,7 +20,3 @@ def test_alpha_valid() -> None: elastic_net_regression = ElasticNetRegression(alpha=1.0).fit(tagged_training_set) assert elastic_net_regression._wrapped_regressor is not None assert elastic_net_regression._wrapped_regressor.alpha == elastic_net_regression._alpha - - - - From 3bfd3e93af7c11868c0584df9ef7e30e0d6fe550 Mon Sep 17 00:00:00 2001 From: alex-senger Date: Fri, 28 Apr 2023 11:24:03 +0200 Subject: [PATCH 05/10] fix: improve documentation and warning --- .../regression/_elastic_net_regression.py | 16 ++++++++++++++-- .../regression/test_elastic_net_regression.py | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index 32e490734..d8c22f248 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -14,13 +14,25 @@ class ElasticNetRegression(Regressor): - """Elastic net regression.""" + """Elastic net regression. + + Parameters + ---------- + alpha : float + Controls the regularization of the model. The higher the value, the more regularized it becomes. + + Raises + ------ + ValueError + If alpha is negative. + """ def __init__(self, alpha: float = 1.0) -> None: if alpha < 0: raise ValueError("alpha must be positive") if alpha == 0: - warn("alpha=0 is equivalent to LinearRegression. Use it instead.", UserWarning, stacklevel=1) + warn("Setting alpha to zero makes this model equivalent to LinearRegression. You should use " + "LinearRegression instead for better numerical stability.", UserWarning, stacklevel=1) self._alpha = alpha self._wrapped_regressor: sk_ElasticNet | None = None diff --git a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py index da8b530df..b9c8d8c6d 100644 --- a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py +++ b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py @@ -10,7 +10,8 @@ def test_alpha_invalid() -> None: def test_alpha_warning() -> None: - with pytest.warns(UserWarning, match="alpha=0 is equivalent to LinearRegression. Use it instead."): + with pytest.warns(UserWarning, match="Setting alpha to zero makes this model equivalent to LinearRegression. You " + "should use LinearRegression instead for better numerical stability."): ElasticNetRegression(alpha=0.0) From afb22f62972332091440dc921c2b060aaf4f9a90 Mon Sep 17 00:00:00 2001 From: alex-senger Date: Fri, 28 Apr 2023 11:49:54 +0200 Subject: [PATCH 06/10] fix: set stacklevel to 2 --- src/safeds/ml/classical/regression/_elastic_net_regression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index 860b94fc6..78957b31e 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -32,7 +32,7 @@ def __init__(self, alpha: float = 1.0) -> None: raise ValueError("alpha must be positive") if alpha == 0: warn("Setting alpha to zero makes this model equivalent to LinearRegression. You should use " - "LinearRegression instead for better numerical stability.", UserWarning, stacklevel=1) + "LinearRegression instead for better numerical stability.", UserWarning, stacklevel=2) self._alpha = alpha self._wrapped_regressor: sk_ElasticNet | None = None From 55c87e14d92fbf796f0d20e8eb4a7b792c15bf01 Mon Sep 17 00:00:00 2001 From: megalinter-bot <129584137+megalinter-bot@users.noreply.github.com> Date: Fri, 28 Apr 2023 11:15:21 +0000 Subject: [PATCH 07/10] style: apply automated linter fixes --- .../regression/_elastic_net_regression.py | 22 ++++++++++++++----- .../regression/test_elastic_net_regression.py | 21 +++++++++++++----- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index 588e6ca8d..03a204707 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -37,8 +37,14 @@ def __init__(self, alpha: float = 1.0, lasso_ratio: float = 0.5) -> None: if alpha < 0: raise ValueError("alpha must be positive") if alpha == 0: - warn("Setting alpha to zero makes this model equivalent to LinearRegression. You should use " - "LinearRegression instead for better numerical stability.", UserWarning, stacklevel=2) + warn( + ( + "Setting alpha to zero makes this model equivalent to LinearRegression. You should use " + "LinearRegression instead for better numerical stability." + ), + UserWarning, + stacklevel=2, + ) self._alpha = alpha @@ -46,14 +52,18 @@ def __init__(self, alpha: float = 1.0, lasso_ratio: float = 0.5) -> None: raise ValueError("lasso_ratio must be between 0 and 1.") elif lasso_ratio == 0: warnings.warn( - "ElasticNetRegression with lasso_ratio = 0 is essentially RidgeRegression." - " Use RidgeRegression instead for better numerical stability.", + ( + "ElasticNetRegression with lasso_ratio = 0 is essentially RidgeRegression." + " Use RidgeRegression instead for better numerical stability." + ), stacklevel=1, ) elif lasso_ratio == 1: warnings.warn( - "ElasticNetRegression with lasso_ratio = 0 is essentially LassoRegression." - " Use LassoRegression instead for better numerical stability.", + ( + "ElasticNetRegression with lasso_ratio = 0 is essentially LassoRegression." + " Use LassoRegression instead for better numerical stability." + ), stacklevel=1, ) self.lasso_ratio = lasso_ratio diff --git a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py index e012725a3..0107b1868 100644 --- a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py +++ b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py @@ -9,8 +9,13 @@ def test_alpha_invalid() -> None: def test_alpha_warning() -> None: - with pytest.warns(UserWarning, match="Setting alpha to zero makes this model equivalent to LinearRegression. You " - "should use LinearRegression instead for better numerical stability."): + with pytest.warns( + UserWarning, + match=( + "Setting alpha to zero makes this model equivalent to LinearRegression. You " + "should use LinearRegression instead for better numerical stability." + ), + ): ElasticNetRegression(alpha=0.0) @@ -41,8 +46,10 @@ def test_lasso_ratio_invalid() -> None: def test_lasso_ratio_zero() -> None: with pytest.warns( UserWarning, - match="ElasticNetRegression with lasso_ratio = 0 is essentially RidgeRegression." - " Use RidgeRegression instead for better numerical stability.", + match=( + "ElasticNetRegression with lasso_ratio = 0 is essentially RidgeRegression." + " Use RidgeRegression instead for better numerical stability." + ), ): ElasticNetRegression(lasso_ratio=0) @@ -50,8 +57,10 @@ def test_lasso_ratio_zero() -> None: def test_lasso_ratio_one() -> None: with pytest.warns( UserWarning, - match="ElasticNetRegression with lasso_ratio = 0 is essentially LassoRegression." - " Use LassoRegression instead for better numerical stability.", + match=( + "ElasticNetRegression with lasso_ratio = 0 is essentially LassoRegression." + " Use LassoRegression instead for better numerical stability." + ), ): ElasticNetRegression(lasso_ratio=1) From c9e9034962d632494405450aa4e3b4db2a3ae3a5 Mon Sep 17 00:00:00 2001 From: alex-senger Date: Fri, 28 Apr 2023 13:59:32 +0200 Subject: [PATCH 08/10] fix: change docstrings, stacklevel and test names to fit naming conventions --- .../regression/_elastic_net_regression.py | 6 +++--- .../regression/test_elastic_net_regression.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index 03a204707..e6f628d94 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -23,7 +23,7 @@ class ElasticNetRegression(Regressor): Controls the regularization of the model. The higher the value, the more regularized it becomes. lasso_ratio: float - Number between 0 and 1 that controls the ratio between Lasso- and Ridge Regression. + Number between 0 and 1 that controls the ratio between Lasso- and Ridge regularization. lasso_ratio=0 is essentially RidgeRegression lasso_ratio=1 is essentially LassoRegression @@ -56,7 +56,7 @@ def __init__(self, alpha: float = 1.0, lasso_ratio: float = 0.5) -> None: "ElasticNetRegression with lasso_ratio = 0 is essentially RidgeRegression." " Use RidgeRegression instead for better numerical stability." ), - stacklevel=1, + stacklevel=2, ) elif lasso_ratio == 1: warnings.warn( @@ -64,7 +64,7 @@ def __init__(self, alpha: float = 1.0, lasso_ratio: float = 0.5) -> None: "ElasticNetRegression with lasso_ratio = 0 is essentially LassoRegression." " Use LassoRegression instead for better numerical stability." ), - stacklevel=1, + stacklevel=2, ) self.lasso_ratio = lasso_ratio diff --git a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py index 0107b1868..78e3ef3f4 100644 --- a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py +++ b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py @@ -3,12 +3,12 @@ from safeds.ml.classical.regression import ElasticNetRegression -def test_alpha_invalid() -> None: +def test_should_throw_value_error_alpha() -> None: with pytest.raises(ValueError, match="alpha must be positive"): ElasticNetRegression(alpha=-1.0) -def test_alpha_warning() -> None: +def test_should_throw_warning_alpha() -> None: with pytest.warns( UserWarning, match=( @@ -19,7 +19,7 @@ def test_alpha_warning() -> None: ElasticNetRegression(alpha=0.0) -def test_alpha_valid() -> None: +def test_should_give_alpha_to_sklearn() -> None: training_set = Table.from_dict({"col1": [1, 2, 3, 4], "col2": [1, 2, 3, 4]}) tagged_training_set = training_set.tag_columns(target_name="col1", feature_names=["col2"]) @@ -28,7 +28,7 @@ def test_alpha_valid() -> None: assert elastic_net_regression._wrapped_regressor.alpha == elastic_net_regression._alpha -def test_lasso_ratio_valid() -> None: +def test_should_give_lasso_ratio_to_sklearn() -> None: training_set = Table.from_dict({"col1": [1, 2, 3, 4], "col2": [1, 2, 3, 4]}) tagged_training_set = training_set.tag_columns(target_name="col1", feature_names=["col2"]) lasso_ratio = 0.3 @@ -38,12 +38,12 @@ def test_lasso_ratio_valid() -> None: assert elastic_net_regression._wrapped_regressor.l1_ratio == lasso_ratio -def test_lasso_ratio_invalid() -> None: +def test_should_throw_value_error_lasso_ratio() -> None: with pytest.raises(ValueError, match="lasso_ratio must be between 0 and 1."): ElasticNetRegression(lasso_ratio=-1.0) -def test_lasso_ratio_zero() -> None: +def test_should_throw_warning_lasso_ratio_zero() -> None: with pytest.warns( UserWarning, match=( @@ -54,7 +54,7 @@ def test_lasso_ratio_zero() -> None: ElasticNetRegression(lasso_ratio=0) -def test_lasso_ratio_one() -> None: +def test_should_throw_warning_lasso_ratio_one() -> None: with pytest.warns( UserWarning, match=( From db176f54f8fd8aac90558173e40caa965fe297f4 Mon Sep 17 00:00:00 2001 From: Alex Senger <91055000+alex-senger@users.noreply.github.com> Date: Fri, 28 Apr 2023 14:15:25 +0200 Subject: [PATCH 09/10] Update src/safeds/ml/classical/regression/_elastic_net_regression.py Co-authored-by: Lars Reimann --- src/safeds/ml/classical/regression/_elastic_net_regression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/safeds/ml/classical/regression/_elastic_net_regression.py b/src/safeds/ml/classical/regression/_elastic_net_regression.py index e6f628d94..cdb3644e6 100644 --- a/src/safeds/ml/classical/regression/_elastic_net_regression.py +++ b/src/safeds/ml/classical/regression/_elastic_net_regression.py @@ -35,7 +35,7 @@ class ElasticNetRegression(Regressor): def __init__(self, alpha: float = 1.0, lasso_ratio: float = 0.5) -> None: if alpha < 0: - raise ValueError("alpha must be positive") + raise ValueError("alpha must be non-negative") if alpha == 0: warn( ( From dc46455d89c6194fe7b508c387847442d05a672c Mon Sep 17 00:00:00 2001 From: alex-senger Date: Fri, 28 Apr 2023 14:26:11 +0200 Subject: [PATCH 10/10] fix: change test value error message --- .../ml/classical/regression/test_elastic_net_regression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py index 78e3ef3f4..7b93a11a9 100644 --- a/tests/safeds/ml/classical/regression/test_elastic_net_regression.py +++ b/tests/safeds/ml/classical/regression/test_elastic_net_regression.py @@ -4,7 +4,7 @@ def test_should_throw_value_error_alpha() -> None: - with pytest.raises(ValueError, match="alpha must be positive"): + with pytest.raises(ValueError, match="alpha must be non-negative"): ElasticNetRegression(alpha=-1.0)