Traktor/myenv/Lib/site-packages/sklearn/ensemble/tests/test_bagging.py

977 lines
31 KiB
Python
Raw Normal View History

2024-05-26 05:12:46 +02:00
"""
Testing for the bagging ensemble module (sklearn.ensemble.bagging).
"""
# Author: Gilles Louppe
# License: BSD 3 clause
from itertools import cycle, product
import joblib
import numpy as np
import pytest
import sklearn
from sklearn.base import BaseEstimator
from sklearn.datasets import load_diabetes, load_iris, make_hastie_10_2
from sklearn.dummy import DummyClassifier, DummyRegressor
from sklearn.ensemble import (
AdaBoostClassifier,
AdaBoostRegressor,
BaggingClassifier,
BaggingRegressor,
HistGradientBoostingClassifier,
HistGradientBoostingRegressor,
RandomForestClassifier,
RandomForestRegressor,
)
from sklearn.feature_selection import SelectKBest
from sklearn.linear_model import LogisticRegression, Perceptron
from sklearn.model_selection import GridSearchCV, ParameterGrid, train_test_split
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import FunctionTransformer, scale
from sklearn.random_projection import SparseRandomProjection
from sklearn.svm import SVC, SVR
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
from sklearn.utils import check_random_state
from sklearn.utils._testing import assert_array_almost_equal, assert_array_equal
from sklearn.utils.fixes import CSC_CONTAINERS, CSR_CONTAINERS
rng = check_random_state(0)
# also load the iris dataset
# and randomly permute it
iris = load_iris()
perm = rng.permutation(iris.target.size)
iris.data = iris.data[perm]
iris.target = iris.target[perm]
# also load the diabetes dataset
# and randomly permute it
diabetes = load_diabetes()
perm = rng.permutation(diabetes.target.size)
diabetes.data = diabetes.data[perm]
diabetes.target = diabetes.target[perm]
def test_classification():
# Check classification for various parameter settings.
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, random_state=rng
)
grid = ParameterGrid(
{
"max_samples": [0.5, 1.0],
"max_features": [1, 4],
"bootstrap": [True, False],
"bootstrap_features": [True, False],
}
)
estimators = [
None,
DummyClassifier(),
Perceptron(max_iter=20),
DecisionTreeClassifier(max_depth=2),
KNeighborsClassifier(),
SVC(),
]
# Try different parameter settings with different base classifiers without
# doing the full cartesian product to keep the test durations low.
for params, estimator in zip(grid, cycle(estimators)):
BaggingClassifier(
estimator=estimator,
random_state=rng,
n_estimators=2,
**params,
).fit(X_train, y_train).predict(X_test)
@pytest.mark.parametrize(
"sparse_container, params, method",
product(
CSR_CONTAINERS + CSC_CONTAINERS,
[
{
"max_samples": 0.5,
"max_features": 2,
"bootstrap": True,
"bootstrap_features": True,
},
{
"max_samples": 1.0,
"max_features": 4,
"bootstrap": True,
"bootstrap_features": True,
},
{"max_features": 2, "bootstrap": False, "bootstrap_features": True},
{"max_samples": 0.5, "bootstrap": True, "bootstrap_features": False},
],
["predict", "predict_proba", "predict_log_proba", "decision_function"],
),
)
def test_sparse_classification(sparse_container, params, method):
# Check classification for various parameter settings on sparse input.
class CustomSVC(SVC):
"""SVC variant that records the nature of the training set"""
def fit(self, X, y):
super().fit(X, y)
self.data_type_ = type(X)
return self
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
scale(iris.data), iris.target, random_state=rng
)
X_train_sparse = sparse_container(X_train)
X_test_sparse = sparse_container(X_test)
# Trained on sparse format
sparse_classifier = BaggingClassifier(
estimator=CustomSVC(kernel="linear", decision_function_shape="ovr"),
random_state=1,
**params,
).fit(X_train_sparse, y_train)
sparse_results = getattr(sparse_classifier, method)(X_test_sparse)
# Trained on dense format
dense_classifier = BaggingClassifier(
estimator=CustomSVC(kernel="linear", decision_function_shape="ovr"),
random_state=1,
**params,
).fit(X_train, y_train)
dense_results = getattr(dense_classifier, method)(X_test)
assert_array_almost_equal(sparse_results, dense_results)
sparse_type = type(X_train_sparse)
types = [i.data_type_ for i in sparse_classifier.estimators_]
assert all([t == sparse_type for t in types])
def test_regression():
# Check regression for various parameter settings.
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
diabetes.data[:50], diabetes.target[:50], random_state=rng
)
grid = ParameterGrid(
{
"max_samples": [0.5, 1.0],
"max_features": [0.5, 1.0],
"bootstrap": [True, False],
"bootstrap_features": [True, False],
}
)
for estimator in [
None,
DummyRegressor(),
DecisionTreeRegressor(),
KNeighborsRegressor(),
SVR(),
]:
for params in grid:
BaggingRegressor(estimator=estimator, random_state=rng, **params).fit(
X_train, y_train
).predict(X_test)
@pytest.mark.parametrize("sparse_container", CSR_CONTAINERS + CSC_CONTAINERS)
def test_sparse_regression(sparse_container):
# Check regression for various parameter settings on sparse input.
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
diabetes.data[:50], diabetes.target[:50], random_state=rng
)
class CustomSVR(SVR):
"""SVC variant that records the nature of the training set"""
def fit(self, X, y):
super().fit(X, y)
self.data_type_ = type(X)
return self
parameter_sets = [
{
"max_samples": 0.5,
"max_features": 2,
"bootstrap": True,
"bootstrap_features": True,
},
{
"max_samples": 1.0,
"max_features": 4,
"bootstrap": True,
"bootstrap_features": True,
},
{"max_features": 2, "bootstrap": False, "bootstrap_features": True},
{"max_samples": 0.5, "bootstrap": True, "bootstrap_features": False},
]
X_train_sparse = sparse_container(X_train)
X_test_sparse = sparse_container(X_test)
for params in parameter_sets:
# Trained on sparse format
sparse_classifier = BaggingRegressor(
estimator=CustomSVR(), random_state=1, **params
).fit(X_train_sparse, y_train)
sparse_results = sparse_classifier.predict(X_test_sparse)
# Trained on dense format
dense_results = (
BaggingRegressor(estimator=CustomSVR(), random_state=1, **params)
.fit(X_train, y_train)
.predict(X_test)
)
sparse_type = type(X_train_sparse)
types = [i.data_type_ for i in sparse_classifier.estimators_]
assert_array_almost_equal(sparse_results, dense_results)
assert all([t == sparse_type for t in types])
assert_array_almost_equal(sparse_results, dense_results)
class DummySizeEstimator(BaseEstimator):
def fit(self, X, y):
self.training_size_ = X.shape[0]
self.training_hash_ = joblib.hash(X)
def predict(self, X):
return np.ones(X.shape[0])
def test_bootstrap_samples():
# Test that bootstrapping samples generate non-perfect base estimators.
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
diabetes.data, diabetes.target, random_state=rng
)
estimator = DecisionTreeRegressor().fit(X_train, y_train)
# without bootstrap, all trees are perfect on the training set
ensemble = BaggingRegressor(
estimator=DecisionTreeRegressor(),
max_samples=1.0,
bootstrap=False,
random_state=rng,
).fit(X_train, y_train)
assert estimator.score(X_train, y_train) == ensemble.score(X_train, y_train)
# with bootstrap, trees are no longer perfect on the training set
ensemble = BaggingRegressor(
estimator=DecisionTreeRegressor(),
max_samples=1.0,
bootstrap=True,
random_state=rng,
).fit(X_train, y_train)
assert estimator.score(X_train, y_train) > ensemble.score(X_train, y_train)
# check that each sampling correspond to a complete bootstrap resample.
# the size of each bootstrap should be the same as the input data but
# the data should be different (checked using the hash of the data).
ensemble = BaggingRegressor(estimator=DummySizeEstimator(), bootstrap=True).fit(
X_train, y_train
)
training_hash = []
for estimator in ensemble.estimators_:
assert estimator.training_size_ == X_train.shape[0]
training_hash.append(estimator.training_hash_)
assert len(set(training_hash)) == len(training_hash)
def test_bootstrap_features():
# Test that bootstrapping features may generate duplicate features.
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
diabetes.data, diabetes.target, random_state=rng
)
ensemble = BaggingRegressor(
estimator=DecisionTreeRegressor(),
max_features=1.0,
bootstrap_features=False,
random_state=rng,
).fit(X_train, y_train)
for features in ensemble.estimators_features_:
assert diabetes.data.shape[1] == np.unique(features).shape[0]
ensemble = BaggingRegressor(
estimator=DecisionTreeRegressor(),
max_features=1.0,
bootstrap_features=True,
random_state=rng,
).fit(X_train, y_train)
for features in ensemble.estimators_features_:
assert diabetes.data.shape[1] > np.unique(features).shape[0]
def test_probability():
# Predict probabilities.
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, random_state=rng
)
with np.errstate(divide="ignore", invalid="ignore"):
# Normal case
ensemble = BaggingClassifier(
estimator=DecisionTreeClassifier(), random_state=rng
).fit(X_train, y_train)
assert_array_almost_equal(
np.sum(ensemble.predict_proba(X_test), axis=1), np.ones(len(X_test))
)
assert_array_almost_equal(
ensemble.predict_proba(X_test), np.exp(ensemble.predict_log_proba(X_test))
)
# Degenerate case, where some classes are missing
ensemble = BaggingClassifier(
estimator=LogisticRegression(), random_state=rng, max_samples=5
).fit(X_train, y_train)
assert_array_almost_equal(
np.sum(ensemble.predict_proba(X_test), axis=1), np.ones(len(X_test))
)
assert_array_almost_equal(
ensemble.predict_proba(X_test), np.exp(ensemble.predict_log_proba(X_test))
)
def test_oob_score_classification():
# Check that oob prediction is a good estimation of the generalization
# error.
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, random_state=rng
)
for estimator in [DecisionTreeClassifier(), SVC()]:
clf = BaggingClassifier(
estimator=estimator,
n_estimators=100,
bootstrap=True,
oob_score=True,
random_state=rng,
).fit(X_train, y_train)
test_score = clf.score(X_test, y_test)
assert abs(test_score - clf.oob_score_) < 0.1
# Test with few estimators
warn_msg = (
"Some inputs do not have OOB scores. This probably means too few "
"estimators were used to compute any reliable oob estimates."
)
with pytest.warns(UserWarning, match=warn_msg):
clf = BaggingClassifier(
estimator=estimator,
n_estimators=1,
bootstrap=True,
oob_score=True,
random_state=rng,
)
clf.fit(X_train, y_train)
def test_oob_score_regression():
# Check that oob prediction is a good estimation of the generalization
# error.
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
diabetes.data, diabetes.target, random_state=rng
)
clf = BaggingRegressor(
estimator=DecisionTreeRegressor(),
n_estimators=50,
bootstrap=True,
oob_score=True,
random_state=rng,
).fit(X_train, y_train)
test_score = clf.score(X_test, y_test)
assert abs(test_score - clf.oob_score_) < 0.1
# Test with few estimators
warn_msg = (
"Some inputs do not have OOB scores. This probably means too few "
"estimators were used to compute any reliable oob estimates."
)
with pytest.warns(UserWarning, match=warn_msg):
regr = BaggingRegressor(
estimator=DecisionTreeRegressor(),
n_estimators=1,
bootstrap=True,
oob_score=True,
random_state=rng,
)
regr.fit(X_train, y_train)
def test_single_estimator():
# Check singleton ensembles.
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
diabetes.data, diabetes.target, random_state=rng
)
clf1 = BaggingRegressor(
estimator=KNeighborsRegressor(),
n_estimators=1,
bootstrap=False,
bootstrap_features=False,
random_state=rng,
).fit(X_train, y_train)
clf2 = KNeighborsRegressor().fit(X_train, y_train)
assert_array_almost_equal(clf1.predict(X_test), clf2.predict(X_test))
def test_error():
# Test support of decision_function
X, y = iris.data, iris.target
base = DecisionTreeClassifier()
assert not hasattr(BaggingClassifier(base).fit(X, y), "decision_function")
def test_parallel_classification():
# Check parallel classification.
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, random_state=0
)
ensemble = BaggingClassifier(
DecisionTreeClassifier(), n_jobs=3, random_state=0
).fit(X_train, y_train)
# predict_proba
y1 = ensemble.predict_proba(X_test)
ensemble.set_params(n_jobs=1)
y2 = ensemble.predict_proba(X_test)
assert_array_almost_equal(y1, y2)
ensemble = BaggingClassifier(
DecisionTreeClassifier(), n_jobs=1, random_state=0
).fit(X_train, y_train)
y3 = ensemble.predict_proba(X_test)
assert_array_almost_equal(y1, y3)
# decision_function
ensemble = BaggingClassifier(
SVC(decision_function_shape="ovr"), n_jobs=3, random_state=0
).fit(X_train, y_train)
decisions1 = ensemble.decision_function(X_test)
ensemble.set_params(n_jobs=1)
decisions2 = ensemble.decision_function(X_test)
assert_array_almost_equal(decisions1, decisions2)
ensemble = BaggingClassifier(
SVC(decision_function_shape="ovr"), n_jobs=1, random_state=0
).fit(X_train, y_train)
decisions3 = ensemble.decision_function(X_test)
assert_array_almost_equal(decisions1, decisions3)
def test_parallel_regression():
# Check parallel regression.
rng = check_random_state(0)
X_train, X_test, y_train, y_test = train_test_split(
diabetes.data, diabetes.target, random_state=rng
)
ensemble = BaggingRegressor(DecisionTreeRegressor(), n_jobs=3, random_state=0).fit(
X_train, y_train
)
ensemble.set_params(n_jobs=1)
y1 = ensemble.predict(X_test)
ensemble.set_params(n_jobs=2)
y2 = ensemble.predict(X_test)
assert_array_almost_equal(y1, y2)
ensemble = BaggingRegressor(DecisionTreeRegressor(), n_jobs=1, random_state=0).fit(
X_train, y_train
)
y3 = ensemble.predict(X_test)
assert_array_almost_equal(y1, y3)
def test_gridsearch():
# Check that bagging ensembles can be grid-searched.
# Transform iris into a binary classification task
X, y = iris.data, iris.target
y[y == 2] = 1
# Grid search with scoring based on decision_function
parameters = {"n_estimators": (1, 2), "estimator__C": (1, 2)}
GridSearchCV(BaggingClassifier(SVC()), parameters, scoring="roc_auc").fit(X, y)
def test_estimator():
# Check estimator and its default values.
rng = check_random_state(0)
# Classification
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, random_state=rng
)
ensemble = BaggingClassifier(None, n_jobs=3, random_state=0).fit(X_train, y_train)
assert isinstance(ensemble.estimator_, DecisionTreeClassifier)
ensemble = BaggingClassifier(
DecisionTreeClassifier(), n_jobs=3, random_state=0
).fit(X_train, y_train)
assert isinstance(ensemble.estimator_, DecisionTreeClassifier)
ensemble = BaggingClassifier(Perceptron(), n_jobs=3, random_state=0).fit(
X_train, y_train
)
assert isinstance(ensemble.estimator_, Perceptron)
# Regression
X_train, X_test, y_train, y_test = train_test_split(
diabetes.data, diabetes.target, random_state=rng
)
ensemble = BaggingRegressor(None, n_jobs=3, random_state=0).fit(X_train, y_train)
assert isinstance(ensemble.estimator_, DecisionTreeRegressor)
ensemble = BaggingRegressor(DecisionTreeRegressor(), n_jobs=3, random_state=0).fit(
X_train, y_train
)
assert isinstance(ensemble.estimator_, DecisionTreeRegressor)
ensemble = BaggingRegressor(SVR(), n_jobs=3, random_state=0).fit(X_train, y_train)
assert isinstance(ensemble.estimator_, SVR)
def test_bagging_with_pipeline():
estimator = BaggingClassifier(
make_pipeline(SelectKBest(k=1), DecisionTreeClassifier()), max_features=2
)
estimator.fit(iris.data, iris.target)
assert isinstance(estimator[0].steps[-1][1].random_state, int)
class DummyZeroEstimator(BaseEstimator):
def fit(self, X, y):
self.classes_ = np.unique(y)
return self
def predict(self, X):
return self.classes_[np.zeros(X.shape[0], dtype=int)]
def test_bagging_sample_weight_unsupported_but_passed():
estimator = BaggingClassifier(DummyZeroEstimator())
rng = check_random_state(0)
estimator.fit(iris.data, iris.target).predict(iris.data)
with pytest.raises(ValueError):
estimator.fit(
iris.data,
iris.target,
sample_weight=rng.randint(10, size=(iris.data.shape[0])),
)
def test_warm_start(random_state=42):
# Test if fitting incrementally with warm start gives a forest of the
# right size and the same results as a normal fit.
X, y = make_hastie_10_2(n_samples=20, random_state=1)
clf_ws = None
for n_estimators in [5, 10]:
if clf_ws is None:
clf_ws = BaggingClassifier(
n_estimators=n_estimators, random_state=random_state, warm_start=True
)
else:
clf_ws.set_params(n_estimators=n_estimators)
clf_ws.fit(X, y)
assert len(clf_ws) == n_estimators
clf_no_ws = BaggingClassifier(
n_estimators=10, random_state=random_state, warm_start=False
)
clf_no_ws.fit(X, y)
assert set([tree.random_state for tree in clf_ws]) == set(
[tree.random_state for tree in clf_no_ws]
)
def test_warm_start_smaller_n_estimators():
# Test if warm start'ed second fit with smaller n_estimators raises error.
X, y = make_hastie_10_2(n_samples=20, random_state=1)
clf = BaggingClassifier(n_estimators=5, warm_start=True)
clf.fit(X, y)
clf.set_params(n_estimators=4)
with pytest.raises(ValueError):
clf.fit(X, y)
def test_warm_start_equal_n_estimators():
# Test that nothing happens when fitting without increasing n_estimators
X, y = make_hastie_10_2(n_samples=20, random_state=1)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=43)
clf = BaggingClassifier(n_estimators=5, warm_start=True, random_state=83)
clf.fit(X_train, y_train)
y_pred = clf.predict(X_test)
# modify X to nonsense values, this should not change anything
X_train += 1.0
warn_msg = "Warm-start fitting without increasing n_estimators does not"
with pytest.warns(UserWarning, match=warn_msg):
clf.fit(X_train, y_train)
assert_array_equal(y_pred, clf.predict(X_test))
def test_warm_start_equivalence():
# warm started classifier with 5+5 estimators should be equivalent to
# one classifier with 10 estimators
X, y = make_hastie_10_2(n_samples=20, random_state=1)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=43)
clf_ws = BaggingClassifier(n_estimators=5, warm_start=True, random_state=3141)
clf_ws.fit(X_train, y_train)
clf_ws.set_params(n_estimators=10)
clf_ws.fit(X_train, y_train)
y1 = clf_ws.predict(X_test)
clf = BaggingClassifier(n_estimators=10, warm_start=False, random_state=3141)
clf.fit(X_train, y_train)
y2 = clf.predict(X_test)
assert_array_almost_equal(y1, y2)
def test_warm_start_with_oob_score_fails():
# Check using oob_score and warm_start simultaneously fails
X, y = make_hastie_10_2(n_samples=20, random_state=1)
clf = BaggingClassifier(n_estimators=5, warm_start=True, oob_score=True)
with pytest.raises(ValueError):
clf.fit(X, y)
def test_oob_score_removed_on_warm_start():
X, y = make_hastie_10_2(n_samples=100, random_state=1)
clf = BaggingClassifier(n_estimators=5, oob_score=True)
clf.fit(X, y)
clf.set_params(warm_start=True, oob_score=False, n_estimators=10)
clf.fit(X, y)
with pytest.raises(AttributeError):
getattr(clf, "oob_score_")
def test_oob_score_consistency():
# Make sure OOB scores are identical when random_state, estimator, and
# training data are fixed and fitting is done twice
X, y = make_hastie_10_2(n_samples=200, random_state=1)
bagging = BaggingClassifier(
KNeighborsClassifier(),
max_samples=0.5,
max_features=0.5,
oob_score=True,
random_state=1,
)
assert bagging.fit(X, y).oob_score_ == bagging.fit(X, y).oob_score_
def test_estimators_samples():
# Check that format of estimators_samples_ is correct and that results
# generated at fit time can be identically reproduced at a later time
# using data saved in object attributes.
X, y = make_hastie_10_2(n_samples=200, random_state=1)
bagging = BaggingClassifier(
LogisticRegression(),
max_samples=0.5,
max_features=0.5,
random_state=1,
bootstrap=False,
)
bagging.fit(X, y)
# Get relevant attributes
estimators_samples = bagging.estimators_samples_
estimators_features = bagging.estimators_features_
estimators = bagging.estimators_
# Test for correct formatting
assert len(estimators_samples) == len(estimators)
assert len(estimators_samples[0]) == len(X) // 2
assert estimators_samples[0].dtype.kind == "i"
# Re-fit single estimator to test for consistent sampling
estimator_index = 0
estimator_samples = estimators_samples[estimator_index]
estimator_features = estimators_features[estimator_index]
estimator = estimators[estimator_index]
X_train = (X[estimator_samples])[:, estimator_features]
y_train = y[estimator_samples]
orig_coefs = estimator.coef_
estimator.fit(X_train, y_train)
new_coefs = estimator.coef_
assert_array_almost_equal(orig_coefs, new_coefs)
def test_estimators_samples_deterministic():
# This test is a regression test to check that with a random step
# (e.g. SparseRandomProjection) and a given random state, the results
# generated at fit time can be identically reproduced at a later time using
# data saved in object attributes. Check issue #9524 for full discussion.
iris = load_iris()
X, y = iris.data, iris.target
base_pipeline = make_pipeline(
SparseRandomProjection(n_components=2), LogisticRegression()
)
clf = BaggingClassifier(estimator=base_pipeline, max_samples=0.5, random_state=0)
clf.fit(X, y)
pipeline_estimator_coef = clf.estimators_[0].steps[-1][1].coef_.copy()
estimator = clf.estimators_[0]
estimator_sample = clf.estimators_samples_[0]
estimator_feature = clf.estimators_features_[0]
X_train = (X[estimator_sample])[:, estimator_feature]
y_train = y[estimator_sample]
estimator.fit(X_train, y_train)
assert_array_equal(estimator.steps[-1][1].coef_, pipeline_estimator_coef)
def test_max_samples_consistency():
# Make sure validated max_samples and original max_samples are identical
# when valid integer max_samples supplied by user
max_samples = 100
X, y = make_hastie_10_2(n_samples=2 * max_samples, random_state=1)
bagging = BaggingClassifier(
KNeighborsClassifier(),
max_samples=max_samples,
max_features=0.5,
random_state=1,
)
bagging.fit(X, y)
assert bagging._max_samples == max_samples
def test_set_oob_score_label_encoding():
# Make sure the oob_score doesn't change when the labels change
# See: https://github.com/scikit-learn/scikit-learn/issues/8933
random_state = 5
X = [[-1], [0], [1]] * 5
Y1 = ["A", "B", "C"] * 5
Y2 = [-1, 0, 1] * 5
Y3 = [0, 1, 2] * 5
x1 = (
BaggingClassifier(oob_score=True, random_state=random_state)
.fit(X, Y1)
.oob_score_
)
x2 = (
BaggingClassifier(oob_score=True, random_state=random_state)
.fit(X, Y2)
.oob_score_
)
x3 = (
BaggingClassifier(oob_score=True, random_state=random_state)
.fit(X, Y3)
.oob_score_
)
assert [x1, x2] == [x3, x3]
def replace(X):
X = X.astype("float", copy=True)
X[~np.isfinite(X)] = 0
return X
def test_bagging_regressor_with_missing_inputs():
# Check that BaggingRegressor can accept X with missing/infinite data
X = np.array(
[
[1, 3, 5],
[2, None, 6],
[2, np.nan, 6],
[2, np.inf, 6],
[2, -np.inf, 6],
]
)
y_values = [
np.array([2, 3, 3, 3, 3]),
np.array(
[
[2, 1, 9],
[3, 6, 8],
[3, 6, 8],
[3, 6, 8],
[3, 6, 8],
]
),
]
for y in y_values:
regressor = DecisionTreeRegressor()
pipeline = make_pipeline(FunctionTransformer(replace), regressor)
pipeline.fit(X, y).predict(X)
bagging_regressor = BaggingRegressor(pipeline)
y_hat = bagging_regressor.fit(X, y).predict(X)
assert y.shape == y_hat.shape
# Verify that exceptions can be raised by wrapper regressor
regressor = DecisionTreeRegressor()
pipeline = make_pipeline(regressor)
with pytest.raises(ValueError):
pipeline.fit(X, y)
bagging_regressor = BaggingRegressor(pipeline)
with pytest.raises(ValueError):
bagging_regressor.fit(X, y)
def test_bagging_classifier_with_missing_inputs():
# Check that BaggingClassifier can accept X with missing/infinite data
X = np.array(
[
[1, 3, 5],
[2, None, 6],
[2, np.nan, 6],
[2, np.inf, 6],
[2, -np.inf, 6],
]
)
y = np.array([3, 6, 6, 6, 6])
classifier = DecisionTreeClassifier()
pipeline = make_pipeline(FunctionTransformer(replace), classifier)
pipeline.fit(X, y).predict(X)
bagging_classifier = BaggingClassifier(pipeline)
bagging_classifier.fit(X, y)
y_hat = bagging_classifier.predict(X)
assert y.shape == y_hat.shape
bagging_classifier.predict_log_proba(X)
bagging_classifier.predict_proba(X)
# Verify that exceptions can be raised by wrapper classifier
classifier = DecisionTreeClassifier()
pipeline = make_pipeline(classifier)
with pytest.raises(ValueError):
pipeline.fit(X, y)
bagging_classifier = BaggingClassifier(pipeline)
with pytest.raises(ValueError):
bagging_classifier.fit(X, y)
def test_bagging_small_max_features():
# Check that Bagging estimator can accept low fractional max_features
X = np.array([[1, 2], [3, 4]])
y = np.array([1, 0])
bagging = BaggingClassifier(LogisticRegression(), max_features=0.3, random_state=1)
bagging.fit(X, y)
def test_bagging_get_estimators_indices():
# Check that Bagging estimator can generate sample indices properly
# Non-regression test for:
# https://github.com/scikit-learn/scikit-learn/issues/16436
rng = np.random.RandomState(0)
X = rng.randn(13, 4)
y = np.arange(13)
class MyEstimator(DecisionTreeRegressor):
"""An estimator which stores y indices information at fit."""
def fit(self, X, y):
self._sample_indices = y
clf = BaggingRegressor(estimator=MyEstimator(), n_estimators=1, random_state=0)
clf.fit(X, y)
assert_array_equal(clf.estimators_[0]._sample_indices, clf.estimators_samples_[0])
@pytest.mark.parametrize(
"bagging, expected_allow_nan",
[
(BaggingClassifier(HistGradientBoostingClassifier(max_iter=1)), True),
(BaggingRegressor(HistGradientBoostingRegressor(max_iter=1)), True),
(BaggingClassifier(LogisticRegression()), False),
(BaggingRegressor(SVR()), False),
],
)
def test_bagging_allow_nan_tag(bagging, expected_allow_nan):
"""Check that bagging inherits allow_nan tag."""
assert bagging._get_tags()["allow_nan"] == expected_allow_nan
@pytest.mark.parametrize(
"model",
[
BaggingClassifier(
estimator=RandomForestClassifier(n_estimators=1), n_estimators=1
),
BaggingRegressor(
estimator=RandomForestRegressor(n_estimators=1), n_estimators=1
),
],
)
def test_bagging_with_metadata_routing(model):
"""Make sure that metadata routing works with non-default estimator."""
with sklearn.config_context(enable_metadata_routing=True):
model.fit(iris.data, iris.target)
@pytest.mark.parametrize(
"model",
[
BaggingClassifier(
estimator=AdaBoostClassifier(n_estimators=1, algorithm="SAMME"),
n_estimators=1,
),
BaggingRegressor(estimator=AdaBoostRegressor(n_estimators=1), n_estimators=1),
],
)
def test_bagging_without_support_metadata_routing(model):
"""Make sure that we still can use an estimator that does not implement the
metadata routing."""
model.fit(iris.data, iris.target)