| import numpy as np |
| from functools import partial |
| from scipy import stats |
|
|
|
|
| def _bws_input_validation(x, y, alternative, method): |
| ''' Input validation and standardization for bws test''' |
| x, y = np.atleast_1d(x, y) |
| if x.ndim > 1 or y.ndim > 1: |
| raise ValueError('`x` and `y` must be exactly one-dimensional.') |
| if np.isnan(x).any() or np.isnan(y).any(): |
| raise ValueError('`x` and `y` must not contain NaNs.') |
| if np.size(x) == 0 or np.size(y) == 0: |
| raise ValueError('`x` and `y` must be of nonzero size.') |
|
|
| z = stats.rankdata(np.concatenate((x, y))) |
| x, y = z[:len(x)], z[len(x):] |
|
|
| alternatives = {'two-sided', 'less', 'greater'} |
| alternative = alternative.lower() |
| if alternative not in alternatives: |
| raise ValueError(f'`alternative` must be one of {alternatives}.') |
|
|
| method = stats.PermutationMethod() if method is None else method |
| if not isinstance(method, stats.PermutationMethod): |
| raise ValueError('`method` must be an instance of ' |
| '`scipy.stats.PermutationMethod`') |
|
|
| return x, y, alternative, method |
|
|
|
|
| def _bws_statistic(x, y, alternative, axis): |
| '''Compute the BWS test statistic for two independent samples''' |
| |
| |
|
|
| Ri, Hj = np.sort(x, axis=axis), np.sort(y, axis=axis) |
| n, m = Ri.shape[axis], Hj.shape[axis] |
| i, j = np.arange(1, n+1), np.arange(1, m+1) |
|
|
| Bx_num = Ri - (m + n)/n * i |
| By_num = Hj - (m + n)/m * j |
|
|
| if alternative == 'two-sided': |
| Bx_num *= Bx_num |
| By_num *= By_num |
| else: |
| Bx_num *= np.abs(Bx_num) |
| By_num *= np.abs(By_num) |
|
|
| Bx_den = i/(n+1) * (1 - i/(n+1)) * m*(m+n)/n |
| By_den = j/(m+1) * (1 - j/(m+1)) * n*(m+n)/m |
|
|
| Bx = 1/n * np.sum(Bx_num/Bx_den, axis=axis) |
| By = 1/m * np.sum(By_num/By_den, axis=axis) |
|
|
| B = (Bx + By) / 2 if alternative == 'two-sided' else (Bx - By) / 2 |
|
|
| return B |
|
|
|
|
| def bws_test(x, y, *, alternative="two-sided", method=None): |
| r'''Perform the Baumgartner-Weiss-Schindler test on two independent samples. |
| |
| The Baumgartner-Weiss-Schindler (BWS) test is a nonparametric test of |
| the null hypothesis that the distribution underlying sample `x` |
| is the same as the distribution underlying sample `y`. Unlike |
| the Kolmogorov-Smirnov, Wilcoxon, and Cramer-Von Mises tests, |
| the BWS test weights the integral by the variance of the difference |
| in cumulative distribution functions (CDFs), emphasizing the tails of the |
| distributions, which increases the power of the test in many applications. |
| |
| Parameters |
| ---------- |
| x, y : array-like |
| 1-d arrays of samples. |
| alternative : {'two-sided', 'less', 'greater'}, optional |
| Defines the alternative hypothesis. Default is 'two-sided'. |
| Let *F(u)* and *G(u)* be the cumulative distribution functions of the |
| distributions underlying `x` and `y`, respectively. Then the following |
| alternative hypotheses are available: |
| |
| * 'two-sided': the distributions are not equal, i.e. *F(u) ≠ G(u)* for |
| at least one *u*. |
| * 'less': the distribution underlying `x` is stochastically less than |
| the distribution underlying `y`, i.e. *F(u) >= G(u)* for all *u*. |
| * 'greater': the distribution underlying `x` is stochastically greater |
| than the distribution underlying `y`, i.e. *F(u) <= G(u)* for all |
| *u*. |
| |
| Under a more restrictive set of assumptions, the alternative hypotheses |
| can be expressed in terms of the locations of the distributions; |
| see [2] section 5.1. |
| method : PermutationMethod, optional |
| Configures the method used to compute the p-value. The default is |
| the default `PermutationMethod` object. |
| |
| Returns |
| ------- |
| res : PermutationTestResult |
| An object with attributes: |
| |
| statistic : float |
| The observed test statistic of the data. |
| pvalue : float |
| The p-value for the given alternative. |
| null_distribution : ndarray |
| The values of the test statistic generated under the null hypothesis. |
| |
| See also |
| -------- |
| scipy.stats.wilcoxon, scipy.stats.mannwhitneyu, scipy.stats.ttest_ind |
| |
| Notes |
| ----- |
| When ``alternative=='two-sided'``, the statistic is defined by the |
| equations given in [1]_ Section 2. This statistic is not appropriate for |
| one-sided alternatives; in that case, the statistic is the *negative* of |
| that given by the equations in [1]_ Section 2. Consequently, when the |
| distribution of the first sample is stochastically greater than that of the |
| second sample, the statistic will tend to be positive. |
| |
| References |
| ---------- |
| .. [1] Neuhäuser, M. (2005). Exact Tests Based on the |
| Baumgartner-Weiss-Schindler Statistic: A Survey. Statistical Papers, |
| 46(1), 1-29. |
| .. [2] Fay, M. P., & Proschan, M. A. (2010). Wilcoxon-Mann-Whitney or t-test? |
| On assumptions for hypothesis tests and multiple interpretations of |
| decision rules. Statistics surveys, 4, 1. |
| |
| Examples |
| -------- |
| We follow the example of table 3 in [1]_: Fourteen children were divided |
| randomly into two groups. Their ranks at performing a specific tests are |
| as follows. |
| |
| >>> import numpy as np |
| >>> x = [1, 2, 3, 4, 6, 7, 8] |
| >>> y = [5, 9, 10, 11, 12, 13, 14] |
| |
| We use the BWS test to assess whether there is a statistically significant |
| difference between the two groups. |
| The null hypothesis is that there is no difference in the distributions of |
| performance between the two groups. We decide that a significance level of |
| 1% is required to reject the null hypothesis in favor of the alternative |
| that the distributions are different. |
| Since the number of samples is very small, we can compare the observed test |
| statistic against the *exact* distribution of the test statistic under the |
| null hypothesis. |
| |
| >>> from scipy.stats import bws_test |
| >>> res = bws_test(x, y) |
| >>> print(res.statistic) |
| 5.132167152575315 |
| |
| This agrees with :math:`B = 5.132` reported in [1]_. The *p*-value produced |
| by `bws_test` also agrees with :math:`p = 0.0029` reported in [1]_. |
| |
| >>> print(res.pvalue) |
| 0.002913752913752914 |
| |
| Because the p-value is below our threshold of 1%, we take this as evidence |
| against the null hypothesis in favor of the alternative that there is a |
| difference in performance between the two groups. |
| ''' |
|
|
| x, y, alternative, method = _bws_input_validation(x, y, alternative, |
| method) |
| bws_statistic = partial(_bws_statistic, alternative=alternative) |
|
|
| permutation_alternative = 'less' if alternative == 'less' else 'greater' |
| res = stats.permutation_test((x, y), bws_statistic, |
| alternative=permutation_alternative, |
| **method._asdict()) |
|
|
| return res |
|
|