Repository: scipy/scipy
File: scipy/stats/_morestats.py (Python)

Code before:
    Ni = xp.concat(Ni, axis=0)
    Ni = xpx.at(Ni)[Ni == 0].set(xp.nan)
    ssq = xp.concat(ssq, axis=0)
    dtype = Ni.dtype
    Ntot = xp.sum(Ni, axis=0)
    spsq = xp.sum((Ni - 1)*ssq, axis=0, dtype=dtype) / (Ntot - k)
    numer = ((Ntot - k) * xp.log(spsq)
             - xp.sum((Ni - 1)*xp.log(ssq), axis=0, dtype=dtype))
    denom = (1 + 1/(3*(k - 1))
             * ((xp.sum(1/(Ni - 1), axis=0)) - 1/(Ntot - k)))
    T = numer / denom

    chi2 = _SimpleChi2(xp.asarray(k-1, dtype=dtype, device=xp_device(T)))
    pvalue = _get_pvalue(T, chi2, alternative='greater', symmetric=False, xp=xp)

    T = xp.clip(T, min=0., max=xp.inf)
    T = T[()] if T.ndim == 0 else T
    pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue

    return BartlettResult(T, pvalue)


LeveneResult = namedtuple('LeveneResult', ('statistic', 'pvalue'))


@xp_capabilities()
@_axis_nan_policy_factory(LeveneResult, n_samples=None)
def levene(*samples, center='median', proportiontocut=0.05, axis=0):
    r"""Perform Levene test for equal variances.

    The Levene test tests the null hypothesis that all input samples
    are from populations with equal variances.  Levene's test is an
    alternative to Bartlett's test `bartlett` in the case where
    there are significant deviations from normality.

    Parameters
    ----------
    sample1, sample2, ... : array_like
        The sample data, possibly with different lengths.
    center : {'mean', 'median', 'trimmed'}, optional
        Which statistics to use to center data points within each sample.  Default
        is 'median'.
    proportiontocut : float, optional
        When `center` is 'trimmed', this gives the proportion of data points
        to cut from each end. (See `scipy.stats.trim_mean`.)
        Default is 0.05.
    axis : int or tuple of ints, default: 0
        If an int or tuple of ints, the axis or axes of the input along which
        to compute the statistic. The statistic of each axis-slice (e.g. row)
        of the input will appear in a corresponding element of the output.
        If ``None``, the input will be raveled before computing the statistic.

Review comment (bug):
Torch GPU fails in CI because it doesn't have `fdtrc`. I imagine JAX will fail, too.
```suggestion
@xp_capabilities(cpu_only=True, exceptions=['cupy'])
```
I still find this a bit indirect - I would prefer to declare that this fails with Torch and JAX on GPU rather than make exceptions to `cpu_only=True`. But @lucascolley, you mentioned during our SDG call that you think that making exceptions is the preferred way?

Code after:
    Ni = xp.concat(Ni, axis=0)
    Ni = xpx.at(Ni)[Ni == 0].set(xp.nan)
    ssq = xp.concat(ssq, axis=0)
    dtype = Ni.dtype
    Ntot = xp.sum(Ni, axis=0)
    spsq = xp.sum((Ni - 1)*ssq, axis=0, dtype=dtype) / (Ntot - k)
    numer = ((Ntot - k) * xp.log(spsq)
             - xp.sum((Ni - 1)*xp.log(ssq), axis=0, dtype=dtype))
    denom = (1 + 1/(3*(k - 1))
             * ((xp.sum(1/(Ni - 1), axis=0)) - 1/(Ntot - k)))
    T = numer / denom

    chi2 = _SimpleChi2(xp.asarray(k-1, dtype=dtype, device=xp_device(T)))
    pvalue = _get_pvalue(T, chi2, alternative='greater', symmetric=False, xp=xp)

    T = xp.clip(T, min=0., max=xp.inf)
    T = T[()] if T.ndim == 0 else T
    pvalue = pvalue[()] if pvalue.ndim == 0 else pvalue

    return BartlettResult(T, pvalue)


LeveneResult = namedtuple('LeveneResult', ('statistic', 'pvalue'))


@xp_capabilities(cpu_only=True, exceptions=['cupy'])
@_axis_nan_policy_factory(LeveneResult, n_samples=None)
def levene(*samples, center='median', proportiontocut=0.05, axis=0):
    r"""Perform Levene test for equal variances.

    The Levene test tests the null hypothesis that all input samples
    are from populations with equal variances.  Levene's test is an
    alternative to Bartlett's test `bartlett` in the case where
    there are significant deviations from normality.

    Parameters
    ----------
    sample1, sample2, ... : array_like
        The sample data, possibly with different lengths.
    center : {'mean', 'median', 'trimmed'}, optional
        Which statistics to use to center data points within each sample.  Default
        is 'median'.
    proportiontocut : float, optional
        When `center` is 'trimmed', this gives the proportion of data points
        to cut from each end. (See `scipy.stats.trim_mean`.)
        Default is 0.05.
    axis : int or tuple of ints, default: 0
        If an int or tuple of ints, the axis or axes of the input along which
        to compute the statistic. The statistic of each axis-slice (e.g. row)
        of the input will appear in a corresponding element of the output.
        If ``None``, the input will be raveled before computing the statistic.