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

Code before:
MAX_INT = np.iinfo(np.int32).max


def _get_n_samples_bootstrap(n_samples, max_samples, sample_weight):
    """
    Get the number of samples in a bootstrap sample.

    Parameters
    ----------
    n_samples : int
        Number of samples in the dataset.

    max_samples : None, int or float
        The maximum number of samples to draw.

        - If None, then draw `n_samples` samples.
        - If int, then draw `max_samples` samples.
        - If float, then draw `max_samples * n_samples` unweighted samples or
          `max_samples * sample_weight.sum()` weighted samples.

    sample_weight : array of shape (n_samples,) or None
        Sample weights with frequency semantics when `max_samples` is explicitly
        set to a float or integer value. When keeping the `max_samples=None` default
        value, the equivalence between fitting with integer weighted data points or
        integer repeated data points is no longer guaranteed because the effective
        bootstrap size is no longer guaranteed to be equivalent.

    Returns
    -------
    n_samples_bootstrap : int
        The total number of samples to draw for the bootstrap sample.
    """
    if max_samples is None:
        return n_samples
    elif isinstance(max_samples, Integral):
        return max_samples
    elif not isinstance(max_samples, Real):
        raise ValueError(
            f"max_samples must be None, int or float got {type(max_samples)}"
        )

    if sample_weight is None:
        weighted_n_samples = n_samples
        weighted_n_samples_msg = f"the number of samples is {weighted_n_samples} "
    else:
        weighted_n_samples = sample_weight.sum()
        weighted_n_samples_msg = (
            f"the total sum of sample weights is {weighted_n_samples} "
        )

    # max_samples Real fractional value relative to weighted_n_samples

Review comment (suggestion):
For reviewers, I updated the notebooks of our sample weight analyzer, in particular this want to highlight the fact that `max_samples=None` breaks frequency weights semantics:

https://github.com/snath-xoc/sample-weight-audit-nondet/blob/main/reports/sklearn_estimators_sample_weight_audit_investigative_plots.ipynb

The p-value is very small: the null-hypothesis that fitting with integer weights is equivalent to fitting with repeated data points is rejected.

Editing the cell to select either `max_samples=1.0` or `max_samples=100` restores large p-values, even when increasing `n_stochastic_fits` meaning that we cannot reject the null-hypothesis that frequency weights semantics are respected.

Code after:
MAX_INT = np.iinfo(np.int32).max


def _get_n_samples_bootstrap(n_samples, max_samples, sample_weight):
    """
    Get the number of samples in a bootstrap sample.

    Parameters
    ----------
    n_samples : int
        Number of samples in the dataset.

    max_samples : None, int or float
        The maximum number of samples to draw.

        - If None, then draw `n_samples` samples.
        - If int, then draw `max_samples` samples.
        - If float, then draw `max_samples * n_samples` unweighted samples or
          `max_samples * sample_weight.sum()` weighted samples.

    sample_weight : array of shape (n_samples,) or None
        Sample weights with frequency semantics when `max_samples` is explicitly
        set to a float or integer value. When keeping the `max_samples=None` default
        value, the equivalence between fitting with integer weighted data points or
        integer repeated data points is no longer guaranteed because the effective
        bootstrap size is no longer guaranteed to be equivalent.

    Returns
    -------
    n_samples_bootstrap : int
        The total number of samples to draw for the bootstrap sample.
    """
    if max_samples is None:
        return n_samples
    elif isinstance(max_samples, Integral):
        return max_samples

    if sample_weight is None:
        weighted_n_samples = n_samples
        weighted_n_samples_msg = f"the number of samples is {weighted_n_samples} "
    else:
        weighted_n_samples = sample_weight.sum()
        weighted_n_samples_msg = (
            f"the total sum of sample weights is {weighted_n_samples} "
        )

    # max_samples Real fractional value relative to weighted_n_samples
    n_samples_bootstrap = max(int(max_samples * weighted_n_samples), 1)
    # Warn when number of bootstrap samples is suspiciously small
    # This heuristic for "suspiciously small" might be adapted if found
    # unsuitable in practice