Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

raise notfittederror in stacking estimators #353

Merged
merged 1 commit into from
Mar 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/sources/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ The fit method of the SequentialFeatureSelector now optionally accepts **fit_par


- Replaces `plot_decision_regions` colors by a colorblind-friendly palette and adds contour lines for decision regions. ([#348](https://github.com/rasbt/mlxtend/issues/348))
- All stacking estimators now raise `NonFittedErrors` if any method for inference is called prior to fitting the estimator. ([#353](https://github.com/rasbt/mlxtend/issues/353))


##### Bug Fixes

- -




### Version 0.11.0 (2018-03-14)
Expand Down
7 changes: 4 additions & 3 deletions mlxtend/classifier/stacking_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
#
# License: BSD 3 clause

from ..externals.estimator_checks import check_is_fitted
from ..externals.name_estimators import _name_estimators
from ..externals import six
from sklearn.base import BaseEstimator
from sklearn.base import ClassifierMixin
from sklearn.base import TransformerMixin
from sklearn.base import clone
from sklearn.utils.validation import check_is_fitted
from ..externals.name_estimators import _name_estimators
from ..externals import six
import numpy as np


Expand Down Expand Up @@ -188,6 +188,7 @@ def predict_meta_features(self, X):
Returns the meta-features for test data.

"""
check_is_fitted(self, 'clfs_')
if self.use_probas:
probas = np.asarray([clf.predict_proba(X)
for clf in self.clfs_])
Expand Down
3 changes: 2 additions & 1 deletion mlxtend/classifier/stacking_cv_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
# License: BSD 3 clause

from ..externals.name_estimators import _name_estimators
from ..externals.estimator_checks import check_is_fitted
from sklearn.base import BaseEstimator
from sklearn.base import ClassifierMixin
from sklearn.base import TransformerMixin
from sklearn.base import clone
from sklearn.utils.validation import check_is_fitted
from sklearn.externals import six
from sklearn.model_selection._split import check_cv
import numpy as np
Expand Down Expand Up @@ -321,6 +321,7 @@ def predict(self, X):
Predicted class labels.

"""
check_is_fitted(self, 'clfs_')
all_model_predictions = self.predict_meta_features(X)
if not self.use_features_in_secondary:
return self.meta_clf_.predict(all_model_predictions)
Expand Down
9 changes: 8 additions & 1 deletion mlxtend/classifier/tests/test_stacking_classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
# License: BSD 3 clause

from mlxtend.classifier import StackingClassifier
from mlxtend.externals.estimator_checks import NotFittedError
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import cross_val_score
from sklearn.exceptions import NotFittedError
import numpy as np
from sklearn import datasets
from mlxtend.utils import assert_raises
Expand Down Expand Up @@ -197,6 +197,13 @@ def test_not_fitted():
sclf.predict_proba,
iris.data)

assert_raises(NotFittedError,
"This StackingClassifier instance is not fitted yet."
" Call 'fit' with appropriate arguments"
" before using this method.",
sclf.predict_meta_features,
iris.data)


def test_verbose():
np.random.seed(123)
Expand Down
16 changes: 11 additions & 5 deletions mlxtend/classifier/tests/test_stacking_cv_classifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@
#
# License: BSD 3 clause

from mlxtend.classifier import StackingCVClassifier

import pandas as pd
import numpy as np
from mlxtend.classifier import StackingCVClassifier
from mlxtend.externals.estimator_checks import NotFittedError
from mlxtend.utils import assert_raises
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import GaussianNB
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
import numpy as np
from sklearn import datasets
from mlxtend.utils import assert_raises
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import KFold
from sklearn.exceptions import NotFittedError
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split

Expand Down Expand Up @@ -203,6 +202,13 @@ def test_not_fitted():
sclf.predict_proba,
iris.data)

assert_raises(NotFittedError,
"This StackingCVClassifier instance is not fitted yet."
" Call 'fit' with appropriate arguments"
" before using this method.",
sclf.predict_meta_features,
iris.data)


def test_verbose():
np.random.seed(123)
Expand Down
73 changes: 73 additions & 0 deletions mlxtend/externals/estimator_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

# Source: https://github.com/scikit-learn/scikit-learn

"""Utilities for input validation"""

# Authors: Olivier Grisel
# Gael Varoquaux
# Andreas Mueller
# Lars Buitinck
# Alexandre Gramfort
# Nicolas Tresegnie
# License: BSD 3 clause


class NotFittedError(ValueError, AttributeError):
"""Exception class to raise if estimator is used before fitting.
This class inherits from both ValueError and AttributeError to help with
exception handling and backward compatibility.
Examples
--------
>>> from sklearn.svm import LinearSVC
>>> from sklearn.exceptions import NotFittedError
>>> try:
... LinearSVC().predict([[1, 2], [2, 3], [3, 4]])
... except NotFittedError as e:
... print(repr(e))
... # doctest: +NORMALIZE_WHITESPACE +ELLIPSIS
NotFittedError('This LinearSVC instance is not fitted yet',)
.. versionchanged:: 0.18
Moved from sklearn.utils.validation.
"""


def check_is_fitted(estimator, attributes, msg=None, all_or_any=all):
"""Perform is_fitted validation for estimator.
Checks if the estimator is fitted by verifying the presence of
"all_or_any" of the passed attributes and raises a NotFittedError with the
given message.
Parameters
----------
estimator : estimator instance.
estimator instance for which the check is performed.
attributes : attribute name(s) given as string or a list/tuple of strings
Eg.:
``["coef_", "estimator_", ...], "coef_"``
msg : string
The default error message is, "This %(name)s instance is not fitted
yet. Call 'fit' with appropriate arguments before using this method."
For custom messages if "%(name)s" is present in the message string,
it is substituted for the estimator name.
Eg. : "Estimator, %(name)s, must be fitted before sparsifying".
all_or_any : callable, {all, any}, default all
Specify whether all or any of the given attributes must exist.
Returns
-------
None
Raises
------
NotFittedError
If the attributes are not found.
"""
if msg is None:
msg = ("This %(name)s instance is not fitted yet. Call 'fit' with "
"appropriate arguments before using this method.")

if not hasattr(estimator, 'fit'):
raise TypeError("%s is not an estimator instance." % (estimator))

if not isinstance(attributes, (list, tuple)):
attributes = [attributes]

if not all_or_any([hasattr(estimator, attr) for attr in attributes]):
raise NotFittedError(msg % {'name': type(estimator).__name__})
15 changes: 6 additions & 9 deletions mlxtend/regressor/stacking_cv_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
#
# License: BSD 3 clause

from ..externals.estimator_checks import check_is_fitted
from ..externals import six
from ..externals.name_estimators import _name_estimators
from sklearn.base import BaseEstimator
from sklearn.base import RegressorMixin
from sklearn.base import TransformerMixin
from sklearn.base import clone
from sklearn.exceptions import NotFittedError
from sklearn.model_selection._split import check_cv
from ..externals import six
from ..externals.name_estimators import _name_estimators

import numpy as np


Expand Down Expand Up @@ -203,9 +204,7 @@ def predict(self, X):
# the meta-model from that info.
#

if not hasattr(self, 'regr_'):
raise NotFittedError("Estimator not fitted, "
"call `fit` before exploiting the model.")
check_is_fitted(self, 'regr_')

meta_features = np.column_stack([
regr.predict(X) for regr in self.regr_
Expand Down Expand Up @@ -233,9 +232,7 @@ def predict_meta_features(self, X):
of regressors.

"""
if not hasattr(self, 'regr_'):
raise NotFittedError("Estimator not fitted, "
"call `fit` before exploiting the model.")
check_is_fitted(self, 'regr_')
return np.column_stack([regr.predict(X) for regr in self.regr_])

def get_params(self, deep=True):
Expand Down
11 changes: 5 additions & 6 deletions mlxtend/regressor/stacking_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
#
# License: BSD 3 clause

from ..externals.estimator_checks import check_is_fitted
from ..externals.name_estimators import _name_estimators
from ..externals import six
from sklearn.base import BaseEstimator
from sklearn.base import RegressorMixin
from sklearn.base import TransformerMixin
from sklearn.base import clone
from sklearn.exceptions import NotFittedError
from ..externals.name_estimators import _name_estimators
from ..externals import six
import numpy as np


Expand Down Expand Up @@ -183,9 +183,7 @@ def predict_meta_features(self, X):
of regressors.

"""
if not hasattr(self, 'regr_'):
raise NotFittedError("Estimator not fitted, "
"call `fit` before exploiting the model.")
check_is_fitted(self, 'regr_')
return np.column_stack([r.predict(X) for r in self.regr_])

def predict(self, X):
Expand All @@ -202,5 +200,6 @@ def predict(self, X):
y_target : array-like, shape = [n_samples] or [n_samples, n_targets]
Predicted target values.
"""
check_is_fitted(self, 'regr_')
meta_features = self.predict_meta_features(X)
return self.meta_regr_.predict(meta_features)
21 changes: 4 additions & 17 deletions mlxtend/regressor/tests/test_stacking_cv_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
# License: BSD 3 clause

import numpy as np
from mlxtend.externals.estimator_checks import NotFittedError
from mlxtend.regressor import StackingCVRegressor
from mlxtend.utils import assert_raises
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge
from sklearn.svm import SVR
from sklearn.exceptions import NotFittedError
from sklearn.model_selection import GridSearchCV, train_test_split
from mlxtend.utils import assert_raises


# Some test data
Expand Down Expand Up @@ -180,27 +180,14 @@ def test_not_fitted_predict():
store_train_meta_features=True)
X_train, X_test, y_train, y_test = train_test_split(X2, y, test_size=0.3)

expect = ("Estimator not fitted, "
"call `fit` before exploiting the model.")
expect = ("This StackingCVRegressor instance is not fitted yet. Call "
"'fit' with appropriate arguments before using this method.")

assert_raises(NotFittedError,
expect,
stregr.predict,
X_train)


def test_not_fitted_predict_meta_features():
lr = LinearRegression()
svr_rbf = SVR(kernel='rbf')
ridge = Ridge(random_state=1)
stregr = StackingCVRegressor(regressors=[lr, ridge],
meta_regressor=svr_rbf,
store_train_meta_features=True)
X_train, X_test, y_train, y_test = train_test_split(X2, y, test_size=0.3)

expect = ("Estimator not fitted, "
"call `fit` before exploiting the model.")

assert_raises(NotFittedError,
expect,
stregr.predict_meta_features,
Expand Down
26 changes: 7 additions & 19 deletions mlxtend/regressor/tests/test_stacking_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
#
# License: BSD 3 clause

from mlxtend.externals.estimator_checks import NotFittedError
from mlxtend.utils import assert_raises
from mlxtend.regressor import StackingRegressor
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge
from sklearn.svm import SVR
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split
import numpy as np
from numpy.testing import assert_almost_equal
from nose.tools import raises
from sklearn.model_selection import GridSearchCV
from sklearn.model_selection import train_test_split
from sklearn.exceptions import NotFittedError
from mlxtend.utils import assert_raises



# Generating a sample dataset
Expand Down Expand Up @@ -220,27 +221,14 @@ def test_not_fitted_predict():
store_train_meta_features=True)
X_train, X_test, y_train, y_test = train_test_split(X2, y, test_size=0.3)

expect = ("Estimator not fitted, "
"call `fit` before exploiting the model.")
expect = ("This StackingRegressor instance is not fitted yet. Call "
"'fit' with appropriate arguments before using this method.")

assert_raises(NotFittedError,
expect,
stregr.predict,
X_train)


def test_not_fitted_predict_meta_features():
lr = LinearRegression()
svr_rbf = SVR(kernel='rbf')
ridge = Ridge(random_state=1)
stregr = StackingRegressor(regressors=[lr, ridge],
meta_regressor=svr_rbf,
store_train_meta_features=True)
X_train, X_test, y_train, y_test = train_test_split(X2, y, test_size=0.3)

expect = ("Estimator not fitted, "
"call `fit` before exploiting the model.")

assert_raises(NotFittedError,
expect,
stregr.predict_meta_features,
Expand Down