diff --git a/sklearn/linear_model/_bayes.py b/sklearn/linear_model/_bayes.py index e9a88d6e2a65b..85f5dab656c43 100644 --- a/sklearn/linear_model/_bayes.py +++ b/sklearn/linear_model/_bayes.py @@ -6,6 +6,7 @@ # License: BSD 3 clause from math import log +import numbers import numpy as np from scipy import linalg @@ -13,6 +14,7 @@ from ..base import RegressorMixin from ._base import _deprecate_normalize from ..utils.extmath import fast_logdet +from ..utils import check_scalar from scipy.linalg import pinvh from ..utils.validation import _check_sample_weight @@ -205,6 +207,103 @@ def __init__( self.copy_X = copy_X self.verbose = verbose + def _check_params(self): + """Check validity of parameters and raise ValueError + or TypeError if not valid.""" + + check_scalar( + self.n_iter, + name="n_iter", + target_type=numbers.Integral, + min_val=1, + ) + + check_scalar( + self.tol, + name="tol", + target_type=numbers.Real, + min_val=0.0, + include_boundaries="neither", + ) + + check_scalar( + self.alpha_1, + name="alpha_1", + target_type=numbers.Real, + min_val=0.0, + include_boundaries="left", + ) + + check_scalar( + self.alpha_2, + name="alpha_2", + target_type=numbers.Real, + min_val=0.0, + include_boundaries="left", + ) + + check_scalar( + self.lambda_1, + name="lambda_1", + target_type=numbers.Real, + min_val=0.0, + include_boundaries="left", + ) + + check_scalar( + self.lambda_2, + name="lambda_2", + target_type=numbers.Real, + min_val=0.0, + include_boundaries="left", + ) + + if self.alpha_init is not None: + check_scalar( + self.alpha_init, + name="alpha_init", + target_type=numbers.Real, + include_boundaries="neither", + ) + + if self.lambda_init is not None: + check_scalar( + self.lambda_init, + name="lambda_init", + target_type=numbers.Real, + include_boundaries="neither", + ) + + check_scalar( + self.compute_score, + name="compute_score", + target_type=(np.bool_, bool), + ) + + check_scalar( + self.fit_intercept, + name="fit_intercept", + target_type=(np.bool_, bool), + ) + + self._normalize = _deprecate_normalize( + self.normalize, default=False, estimator_name=self.__class__.__name__ + ) + + check_scalar( + self.copy_X, + name="copy_X", + target_type=(np.bool_, bool), + ) + + check_scalar( + self.verbose, + name="verbose", + target_type=(numbers.Integral, np.bool_, bool), + min_val=0, + max_val=1, + ) + def fit(self, X, y, sample_weight=None): """Fit the model. @@ -226,16 +325,7 @@ def fit(self, X, y, sample_weight=None): self : object Returns the instance itself. """ - self._normalize = _deprecate_normalize( - self.normalize, default=False, estimator_name=self.__class__.__name__ - ) - - if self.n_iter < 1: - raise ValueError( - "n_iter should be greater than or equal to 1. Got {!r}.".format( - self.n_iter - ) - ) + self._check_params() X, y = self._validate_data(X, y, dtype=[np.float64, np.float32], y_numeric=True) diff --git a/sklearn/linear_model/tests/test_bayes.py b/sklearn/linear_model/tests/test_bayes.py index 4044aefc3e446..8c14869e7052d 100644 --- a/sklearn/linear_model/tests/test_bayes.py +++ b/sklearn/linear_model/tests/test_bayes.py @@ -21,13 +21,93 @@ diabetes = datasets.load_diabetes() -def test_n_iter(): - """Check value of n_iter.""" +@pytest.mark.parametrize( + "params, err_type, err_msg", + [ + ({"n_iter": 0}, ValueError, "n_iter == 0, must be >= 1."), + ({"n_iter": 2.5}, TypeError, "n_iter must be an instance of int, not float."), + ({"tol": -1}, ValueError, "tol == -1, must be > 0"), + ({"tol": "-1"}, TypeError, "tol must be an instance of float, not str."), + ( + {"alpha_1": "-1"}, + TypeError, + "alpha_1 must be an instance of float, not str.", + ), + ( + {"alpha_2": "-1"}, + TypeError, + "alpha_2 must be an instance of float, not str.", + ), + ( + {"lambda_1": "-1"}, + TypeError, + "lambda_1 must be an instance of float, not str.", + ), + ( + {"lambda_2": "-1"}, + TypeError, + "lambda_2 must be an instance of float, not str.", + ), + ( + {"alpha_init": "-1"}, + TypeError, + "alpha_init must be an instance of float, not str.", + ), + ( + {"lambda_init": "-1"}, + TypeError, + "lambda_init must be an instance of float, not str.", + ), + ( + {"compute_score": 2}, + TypeError, + "compute_score must be an instance of {numpy.bool_, bool}, not int.", + ), + ( + {"compute_score": 0.5}, + TypeError, + "compute_score must be an instance of {numpy.bool_, bool}, not float.", + ), + ( + {"fit_intercept": 2}, + TypeError, + "fit_intercept must be an instance of {numpy.bool_, bool}, not int.", + ), + ( + {"fit_intercept": 0.5}, + TypeError, + "fit_intercept must be an instance of {numpy.bool_, bool}, not float.", + ), + ( + {"normalize": -1}, + ValueError, + "Leave 'normalize' to its default value or set it to True or False", + ), + ( + {"copy_X": 2}, + TypeError, + "copy_X must be an instance of {numpy.bool_, bool}, not int.", + ), + ( + {"copy_X": 0.5}, + TypeError, + "copy_X must be an instance of {numpy.bool_, bool}, not float.", + ), + ({"verbose": -1}, ValueError, "verbose == -1, must be >= 0"), + ({"verbose": 2}, ValueError, "verbose == 2, must be <= 1"), + ( + {"verbose": 0.5}, + TypeError, + "verbose must be an instance of {int, numpy.bool_, bool}, not float.", + ), + ], +) +def test_bayesian_ridge_scalar_params_validation(params, err_type, err_msg): + """Check the scalar parameters of BayesianRidge.""" X = np.array([[1], [2], [6], [8], [10]]) y = np.array([1, 2, 6, 8, 10]) - clf = BayesianRidge(n_iter=0) - msg = "n_iter should be greater than or equal to 1." - with pytest.raises(ValueError, match=msg): + clf = BayesianRidge(**params) + with pytest.raises(err_type, match=err_msg): clf.fit(X, y)