diff --git a/doc/whats_new/v1.1.rst b/doc/whats_new/v1.1.rst index e2ff5bc76fce2..b9651a1e1b6f8 100644 --- a/doc/whats_new/v1.1.rst +++ b/doc/whats_new/v1.1.rst @@ -187,6 +187,11 @@ Changelog multilabel classification. :pr:`19689` by :user:`Guillaume Lemaitre `. +- |Enhancement| :class:`linear_model.Ridge` and :class:`linear_model.RidgeClassifier` + now raise consistent error message when passed invalid values for `alpha`, + `max_iter` and `tol`. + :pr:`21341` by :user:`Arturo Amor `. + :mod:`sklearn.linear_model` ........................... diff --git a/sklearn/linear_model/_ridge.py b/sklearn/linear_model/_ridge.py index 1a501e8404f62..09ef46ffc7ebe 100644 --- a/sklearn/linear_model/_ridge.py +++ b/sklearn/linear_model/_ridge.py @@ -13,6 +13,7 @@ import warnings import numpy as np +import numbers from scipy import linalg from scipy import sparse from scipy import optimize @@ -26,6 +27,7 @@ from ..utils.extmath import row_norms from ..utils import check_array from ..utils import check_consistent_length +from ..utils import check_scalar from ..utils import compute_sample_weight from ..utils import column_or_1d from ..utils.validation import check_is_fitted @@ -557,6 +559,17 @@ def _ridge_regression( # we implement sample_weight via a simple rescaling. X, y = _rescale_data(X, y, sample_weight) + # Some callers of this method might pass alpha as single + # element array which already has been validated. + if alpha is not None and not isinstance(alpha, (np.ndarray, tuple)): + alpha = check_scalar( + alpha, + "alpha", + target_type=numbers.Real, + min_val=0.0, + include_boundaries="left", + ) + # There should be either 1 or n_targets penalties alpha = np.asarray(alpha, dtype=X.dtype).ravel() if alpha.size not in [1, n_targets]: @@ -742,6 +755,13 @@ def fit(self, X, y, sample_weight=None): if sample_weight is not None: sample_weight = _check_sample_weight(sample_weight, X, dtype=X.dtype) + if self.max_iter is not None: + self.max_iter = check_scalar( + self.max_iter, "max_iter", target_type=numbers.Integral, min_val=1 + ) + + self.tol = check_scalar(self.tol, "tol", target_type=numbers.Real, min_val=0.0) + # when X is sparse we only remove offset from y X, y, X_offset, y_offset, X_scale = self._preprocess_data( X, diff --git a/sklearn/linear_model/tests/test_ridge.py b/sklearn/linear_model/tests/test_ridge.py index 58d2804d89aca..1160e2db57fc6 100644 --- a/sklearn/linear_model/tests/test_ridge.py +++ b/sklearn/linear_model/tests/test_ridge.py @@ -335,6 +335,42 @@ def test_ridge_individual_penalties(): ridge.fit(X, y) +@pytest.mark.parametrize( + "params, err_type, err_msg", + [ + ({"alpha": -1}, ValueError, "alpha == -1, must be >= 0.0"), + ( + {"alpha": "1"}, + TypeError, + "alpha must be an instance of , not ", + ), + ({"max_iter": 0}, ValueError, "max_iter == 0, must be >= 1."), + ( + {"max_iter": "1"}, + TypeError, + "max_iter must be an instance of , not ", + ), + ({"tol": -1.0}, ValueError, "tol == -1.0, must be >= 0."), + ( + {"tol": "1"}, + TypeError, + "tol must be an instance of , not ", + ), + ], +) +def test_ridge_params_validation(params, err_type, err_msg): + """Check the parameters validation in Ridge.""" + + rng = np.random.RandomState(42) + n_samples, n_features, n_targets = 20, 10, 5 + X = rng.randn(n_samples, n_features) + y = rng.randn(n_samples, n_targets) + + with pytest.raises(err_type, match=err_msg): + Ridge(**params).fit(X, y) + + @pytest.mark.parametrize("n_col", [(), (1,), (3,)]) def test_X_CenterStackOp(n_col): rng = np.random.RandomState(0)