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

Code before:
        X : ndarray of shape (n_samples,) or (n_samples, n_classes)
            Training data.

            This should be the output of `decision_function` or `predict_proba`.
            If the input appears to be probabilities (i.e., values between 0 and 1
            that sum to 1 across classes), it will be converted to logits using
            `np.log(p + eps)`.

            Binary decision function outputs (1D) will be converted to two-class
            logits of the form (-x, x). For shapes of the form (n_samples, 1), the
            same process applies.

        y : array-like of shape (n_samples,)
            Training target.

        sample_weight : array-like of shape (n_samples,), default=None
            Sample weights. If None, then samples are equally weighted.

        Returns
        -------
        self : object
            Returns an instance of self.
        """
        xp, _, xp_device = get_namespace_and_device(X, y)
        X, y = indexable(X, y)
        check_consistent_length(X, y)
        logits = _convert_to_logits(X)  # guarantees xp.float64 or xp.float32

        dtype_ = logits.dtype
        labels = column_or_1d(y, dtype=dtype_)

        if sample_weight is not None:
            sample_weight = _check_sample_weight(sample_weight, labels, dtype=dtype_)
            sample_weight = xp.asarray(sample_weight, device=xp_device)

        halfmulti_loss = HalfMultinomialLoss(
            sample_weight=sample_weight, n_classes=logits.shape[1]
        )

        def log_loss(log_beta=0.0):
            """Compute the log loss as a parameter of the inverse temperature
            (beta).

            Parameters
            ----------
            log_beta : float
                The current logarithm of the inverse temperature value during
                optimisation.

            Returns
            -------

Review comment (suggestion):
I think we should allow `y` not to be in the same namespace and device as `X`. For instance, `X` could be a torch tensor of numeric features allocated on a specific device via a previous transformer in a pipeline while `y` is a NumPy array with an `object` dtype storing `str` instances or a pandas `Series` with a categorical dtype passed unchanged to the `fit` method of the pipeline. Neither are expressible in array API, but both are still valid way to assign class labels to data points of the training set. This is what we documented as "y and sample_weight follow X" in our doc: https://scikit-learn.org/dev/modules/array_api.html#:~:text=will%20be%20converted

To support this usage pattern, we would first need to label encode `y` and then move the result to the same namespace and device as `X` using `ensure_common_namespace_device(X, labels sample_weight)` to get the labels.



Code after:
        Parameters
        ----------
        X : ndarray of shape (n_samples,) or (n_samples, n_classes)
            Training data.

            This should be the output of `decision_function` or `predict_proba`.
            If the input appears to be probabilities (i.e., values between 0 and 1
            that sum to 1 across classes), it will be converted to logits using
            `np.log(p + eps)`.

            Binary decision function outputs (1D) will be converted to two-class
            logits of the form (-x, x). For shapes of the form (n_samples, 1), the
            same process applies.

        y : array-like of shape (n_samples,)
            Training target.

        sample_weight : array-like of shape (n_samples,), default=None
            Sample weights. If None, then samples are equally weighted.

        Returns
        -------
        self : object
            Returns an instance of self.
        """
        xp, _, xp_device = get_namespace_and_device(X, y)
        X, y = indexable(X, y)
        check_consistent_length(X, y)
        logits = _convert_to_logits(X)  # guarantees xp.float64 or xp.float32

        dtype_ = logits.dtype
        labels = column_or_1d(y, dtype=dtype_)

        if sample_weight is not None:
            sample_weight = _check_sample_weight(sample_weight, labels, dtype=dtype_)

        if _is_numpy_namespace(xp):
            multinomial_loss = HalfMultinomialLoss(n_classes=logits.shape[1])
        else:
            multinomial_loss = partial(_half_multinomial_loss, xp=xp)

        def log_loss(log_beta=0.0):
            """Compute the log loss as a parameter of the inverse temperature
            (beta).

            Parameters
            ----------
            log_beta : float
                The current logarithm of the inverse temperature value during
                optimisation.