diff --git a/doc/whats_new/v0.22.rst b/doc/whats_new/v0.22.rst index af81950908925..5ec301674b35b 100644 --- a/doc/whats_new/v0.22.rst +++ b/doc/whats_new/v0.22.rst @@ -657,6 +657,10 @@ Changelog - |Fix| :class:`multioutput.MultiOutputClassifier` now has attribute ``classes_``. :pr:`14629` by :user:`Agamemnon Krasoulis `. +- |Fix| :class:`multioutput.MultiOutputClassifier` now has `predict_proba` + as property and can be checked with `hasattr`. + :issue:`15488` :pr:`15490` by :user:`Rebekah Kim ` + :mod:`sklearn.naive_bayes` ............................... diff --git a/sklearn/multioutput.py b/sklearn/multioutput.py index 07427559a7766..a6e8fc3c5dc16 100644 --- a/sklearn/multioutput.py +++ b/sklearn/multioutput.py @@ -360,7 +360,8 @@ def fit(self, X, Y, sample_weight=None): self.classes_ = [estimator.classes_ for estimator in self.estimators_] return self - def predict_proba(self, X): + @property + def predict_proba(self): """Probability estimates. Returns prediction probabilities for each class of each output. @@ -382,9 +383,11 @@ def predict_proba(self, X): check_is_fitted(self) if not all([hasattr(estimator, "predict_proba") for estimator in self.estimators_]): - raise ValueError("The base estimator should implement " - "predict_proba method") + raise AttributeError("The base estimator should " + "implement predict_proba method") + return self._predict_proba + def _predict_proba(self, X): results = [estimator.predict_proba(X) for estimator in self.estimators_] return results diff --git a/sklearn/tests/test_multioutput.py b/sklearn/tests/test_multioutput.py index 76af96de0d98a..cd87ad3fc863d 100644 --- a/sklearn/tests/test_multioutput.py +++ b/sklearn/tests/test_multioutput.py @@ -175,6 +175,22 @@ def test_multi_output_classification_partial_fit_parallelism(): assert est1 is not est2 +# check multioutput has predict_proba +def test_hasattr_multi_output_predict_proba(): + # default SGDClassifier has loss='hinge' + # which does not expose a predict_proba method + sgd_linear_clf = SGDClassifier(random_state=1, max_iter=5) + multi_target_linear = MultiOutputClassifier(sgd_linear_clf) + multi_target_linear.fit(X, y) + assert not hasattr(multi_target_linear, "predict_proba") + + # case where predict_proba attribute exists + sgd_linear_clf = SGDClassifier(loss='log', random_state=1, max_iter=5) + multi_target_linear = MultiOutputClassifier(sgd_linear_clf) + multi_target_linear.fit(X, y) + assert hasattr(multi_target_linear, "predict_proba") + + # check predict_proba passes def test_multi_output_predict_proba(): sgd_linear_clf = SGDClassifier(random_state=1, max_iter=5) @@ -199,7 +215,7 @@ def custom_scorer(estimator, X, y): multi_target_linear = MultiOutputClassifier(sgd_linear_clf) multi_target_linear.fit(X, y) err_msg = "The base estimator should implement predict_proba method" - with pytest.raises(ValueError, match=err_msg): + with pytest.raises(AttributeError, match=err_msg): multi_target_linear.predict_proba(X) @@ -378,7 +394,8 @@ def test_multi_output_exceptions(): # and predict_proba are called moc = MultiOutputClassifier(LinearSVC(random_state=0)) assert_raises(NotFittedError, moc.predict, y) - assert_raises(NotFittedError, moc.predict_proba, y) + with pytest.raises(NotFittedError): + moc.predict_proba assert_raises(NotFittedError, moc.score, X, y) # ValueError when number of outputs is different # for fit and score