Repository: scikit-learn/scikit-learn
File: sklearn/ensemble/tests/test_bagging.py (Python)

Code before:
    sample_weight = np.ones_like(y)
    reg = BaggingRegressor(bootstrap=False)
    warn_msg = (
        "When fitting BaggingRegressor with sample_weight "
        "it is recommended to use bootstrap=True"
    )
    with pytest.warns(UserWarning, match=warn_msg):
        reg.fit(X, y, sample_weight=sample_weight)


def test_invalid_sample_weight_max_samples_bootstrap_combinations():
    X, y = iris.data, iris.target

    # Case 1: small weights and fractional max_samples lead to a small
    # number of bootstrap samples, which raises a UserWarning.
    clf = BaggingClassifier(max_samples=1.0)
    sample_weight = np.ones_like(y) / (2 * len(y))
    expected_msg = (
        "Using the fractional value max_samples=1.0 when "
        r"the total sum of sample weights is 0.5(\d*) "
        r"results in a low number \(1\) of bootstrap samples. "
        "We recommend passing `max_samples` as an integer."
    )
    with pytest.warns(UserWarning, match=expected_msg):
        clf.fit(X, y, sample_weight=sample_weight)

    # Case 2: large weights and bootstrap=False would lead to sampling without
    # replacement more than the number of samples, which is not allowed.
    clf = BaggingClassifier(bootstrap=False, max_samples=1.0)
    sample_weight = np.ones_like(y)
    sample_weight[-1] = 2
    expected_msg = re.escape(
        "max_samples=151 must be <= n_samples=150 to be able to sample without "
        "replacement."
    )
    with pytest.raises(ValueError, match=expected_msg):
        with pytest.warns(
            UserWarning, match="When fitting BaggingClassifier with sample_weight"
        ):
            clf.fit(X, y, sample_weight=sample_weight)


class EstimatorAcceptingSampleWeight(BaseEstimator):
    """Fake estimator accepting sample_weight"""

    def fit(self, X, y, sample_weight=None):
        """Record values passed during fit"""
        self.X_ = X
        self.y_ = y
        self.sample_weight_ = sample_weight


Review comment (suggestion):
Instead of raising an error when `sample_weigth.sum() < 1` as in #31414, here we always return something $\geq 1$, but we raise a warning if for any reason (small sample_weight, or small dataset or small max_samples value) the final number of bootstrap samples is "small" (now it is hardcoded in `_get_n_samples_bootstrap` than less than 10 is small).


Code after:

    X, y = diabetes.data, diabetes.target
    sample_weight = np.ones_like(y)
    reg = BaggingRegressor(bootstrap=False)
    warn_msg = (
        "When fitting BaggingRegressor with sample_weight "
        "it is recommended to use bootstrap=True"
    )
    with pytest.warns(UserWarning, match=warn_msg):
        reg.fit(X, y, sample_weight=sample_weight)


def test_invalid_sample_weight_max_samples_bootstrap_combinations():
    X, y = iris.data, iris.target

    # Case 1: small weights and fractional max_samples lead to a small
    # number of bootstrap samples, which raises a UserWarning.
    clf = BaggingClassifier(max_samples=1.0)
    sample_weight = np.ones_like(y) / (2 * len(y))
    expected_msg = (
        "Using the fractional value max_samples=1.0 when "
        r"the total sum of sample weights is 0.5(\d*) "
        r"results in a low number \(1\) of bootstrap samples. "
        "We recommend passing `max_samples` as an integer."
    )
    with pytest.warns(UserWarning, match=expected_msg):
        clf.fit(X, y, sample_weight=sample_weight)

    # Case 2: large weights and bootstrap=False would lead to sampling without
    # replacement more than the number of samples, which is not allowed.
    clf = BaggingClassifier(bootstrap=False, max_samples=1.0)
    sample_weight = np.ones_like(y)
    sample_weight[-1] = 2
    expected_msg = re.escape(
        "max_samples=151 must be <= n_samples=150 to be able to sample without "
        "replacement."
    )
    with pytest.raises(ValueError, match=expected_msg):
        with pytest.warns(
            UserWarning, match="When fitting BaggingClassifier with sample_weight"
        ):
            clf.fit(X, y, sample_weight=sample_weight)


class EstimatorAcceptingSampleWeight(BaseEstimator):
    """Fake estimator accepting sample_weight"""

    def fit(self, X, y, sample_weight=None):
        """Record values passed during fit"""
        self.X_ = X
        self.y_ = y