mixle.inference.nonparametric module

Classical nonparametric (rank-based) hypothesis tests.

Distribution-free two-sample, k-sample, paired, repeated-measures, ordered-alternative, and goodness-of-fit tests, each returning a small result object with the statistic, p-value, and – where standard – an effect size. Statistics are computed here (mid-ranks for ties); tail probabilities use exact nulls wherever they are computable at reasonable cost (the Wilcoxon signed-rank subset-sum DP at n <= 300 without ties or zeros; the runs test’s closed-form pmf at n <= 5000; the two-sample KS at small samples) and the asymptotic reference distributions (normal / chi-square / Student-t / Kolmogorov) with tie corrections otherwise. Not every asymptotic branch carries a continuity correction (the Page test does not), and the remaining small-sample normal approximations are approximations – see each test’s docstring.

Two independent samples : mann_whitney_u() (Wilcoxon rank-sum), brunner_munzel(),
                          cliffs_delta(), ks_2samp()
k independent samples   : kruskal_wallis(), mood_median_test(), dunn_test() (post-hoc)
Paired / one sample     : wilcoxon_signed_rank(), sign_test()
Repeated measures       : friedman_test()
Ordered alternatives    : jonckheere_terpstra() (independent), page_trend_test() (repeated)
Goodness of fit / 1-samp: ks_1samp(), runs_test()
class MannWhitneyResult(statistic, statistic2, zscore, pvalue, rank_biserial, alternative, null_hypothesis='exchangeability (F = G); use brunner_munzel for unequal shapes')[source]

Bases: object

Result of a two-sample Mann-Whitney U test.

Parameters:
class WilcoxonResult(statistic, zscore, pvalue, rank_biserial, alternative, method='normal')[source]

Bases: object

Result of a paired or one-sample Wilcoxon signed-rank test.

method names the null actually used (STAT-RR17-15): "exact" (full enumeration, the n <= 300 no-ties/no-zeros regime) or "normal" (tie/zero-corrected approximation). zscore is signed FOR THE STATED ALTERNATIVE – positive means the data lean toward it – so an all-positive sample under alternative='greater' reports a positive z next to its small exact p, where the old min(W+, W-)-based z reported -2.02 beside p = 0.03125 and a rank-biserial of +1.

Parameters:
class DunnResult(comparisons, zscores, pvalues, p_adjust)[source]

Bases: object

Post-hoc Dunn pairwise comparisons after Kruskal-Wallis.

Parameters:
class TestResult(statistic, pvalue, extra=<factory>)[source]

Bases: object

Generic statistic + p-value result; extra carries test-specific fields (effect size, df, …).

Parameters:
mann_whitney_u(x, y, *, alternative='two-sided', use_continuity=True)[source]

Mann-Whitney U / Wilcoxon rank-sum test for two independent samples.

THE NULL IS FULL EXCHANGEABILITY (F = G), not mere stochastic equality: the reference variance is derived under identical distributions, so with equal P(X > Y) = 1/2 but UNEQUAL shapes/spreads and unbalanced sample sizes the level is not controlled – measured 12.5% rejection at nominal 5% one way and 1.5% with the sizes reversed (STAT-RR17-17). For the stochastic-equality null under unequal shapes use brunner_munzel(), whose variance estimates each group separately. Uses mid-ranks for ties, the tie-corrected normal approximation, and (default) a continuity correction. alternative is 'two-sided', 'greater' (x > y), or 'less'. The rank-biserial correlation 2*U1/(n1 n2) - 1 is reported as the effect size; null_hypothesis on the result names the null in force.

Parameters:
Return type:

MannWhitneyResult

cliffs_delta(x, y)[source]

Cliff’s delta effect size in [-1, 1]: P(x > y) - P(x < y) (rank-based, ties count as 0).

Parameters:
Return type:

float

brunner_munzel(x, y, *, alternative='two-sided', distribution='t')[source]

Brunner-Munzel test: the generalized Wilcoxon test that does not assume equal variances/shapes.

Tests the stochastic-equality null P(x < y) + 0.5 P(x = y) = 1/2. alternative is 'two-sided', 'greater' (x > y), or 'less' – the same direction convention as mann_whitney_u() (and scipy). distribution='t' uses a Satterthwaite t reference, 'normal' the normal approximation. The t reference is the better of the two but is still LIBERAL in small samples – below roughly 10 observations per group its true level runs above nominal (this is documented behavior of the approximation, not an implementation defect), so treat sub-10-per-group p-values near the threshold as optimistic; the studentized-permutation version of the test is the small-sample fix and is not implemented here. Reports the estimated relative effect p_hat = P(x < y) + 0.5 P(x = y) in extra.

Completely separated samples (all of one group beyond all of the other) make the variance estimate 0 and the studentized statistic undefined – and NO valid p-value exists under this test’s own null. The permutation tail 1/C(n1+n2, n1) an earlier revision reported is exact only under FULL exchangeability (F = G), a strictly stronger hypothesis than Brunner-Munzel’s stochastic equality: with X = +/-1 equiprobable and Y = 0, P(X < Y) = 0.5 holds exactly, yet all-X-below-Y has probability 2^(-n1) – at n1 = 4 the old report rejected 12.5% of that null at nominal 5% while quoting p = 0.0002 (STAT-RR19-04). The result therefore reports pvalue = nan, statistic = +/-inf, extra['method'] = 'separation-no-valid-p-under-stochastic-equality', the observed p_hat, and – for callers prepared to ASSERT the stronger F = G null – extra['p_exchangeability'], the exact permutation bound labeled as exactly that. Use mann_whitney_u() or a permutation test when exchangeability is the hypothesis you mean.

Parameters:
Return type:

TestResult

ks_2samp(x, y, *, alternative='two-sided')[source]

Two-sample Kolmogorov-Smirnov test: max gap between the two empirical CDFs.

The p-value is exact at small samples and asymptotic otherwise (scipy method='auto'). The null distribution is distribution-free for CONTINUOUS data; with ties (discrete or rounded data) the p-value is conservative.

Parameters:
Return type:

TestResult

ks_1samp(x, cdf, *, alternative='two-sided')[source]

One-sample Kolmogorov-Smirnov goodness-of-fit test against a fully-specified cdf callable.

cdf may be vectorized (ndarray -> ndarray) or scalar-only, such as the .cdf method of a fitted univariate mixle distribution; a scalar-only callable is evaluated element-wise. Mixtures, categoricals, composites, and sequence models define no cdf: for those, pass the CDF as your own callable (for a univariate mixture, the weighted sum of the component CDFs).

The p-value is ASYMPTOTIC in every mode: two-sided uses the limiting Kolmogorov distribution (scipy’s method='asymp') and the one-sided modes use the large-sample exponential bound exp(-2 n D^2). No small-sample (Marsaglia-Tsang-Wang exact) correction is applied, so at small n the reported p-value is LARGER than the exact one – it understates the evidence against the hypothesized cdf and overstates goodness-of-fit (e.g. ~26% relative near p=0.005 at n=50; the gap shrinks as n grows). For an exact small-sample p-value use scipy.stats.ks_1samp(x, cdf, method='exact'); the statistic here matches scipy’s to machine precision either way.

Parameters:
Return type:

TestResult

kruskal_wallis(*samples)[source]

Kruskal-Wallis H test: the rank-based k-sample generalization of Mann-Whitney (one-way ANOVA).

Tie-corrected H with a chi-square(k-1) reference. extra carries df and the epsilon_squared effect size (H - k + 1)/(N - k).

Parameters:

samples (Any)

Return type:

TestResult

mood_median_test(*samples, ties='below')[source]

Mood’s median test: contingency test that k samples sit alike around the POOLED median.

Cross-tabulates each observation as above / (at-or-below) the pooled grand median and tests independence of the resulting 2xk table. extra carries the grand_median, the reference used (method), and min_expected_count.

What rejection means – precisely: the groups differ in their probability of exceeding the pooled median. That is implied by unequal medians under a shift model, but it is NOT “the medians differ” unconditionally: groups with identical medians and different shapes near that median can reject, and the test has famously low power besides. Reference: the median split fixes BOTH margins of the table, so for k = 2 the conditional exact (Fisher) p-value is the calibrated one and is used automatically whenever any expected cell count is below 5 (the uncorrected chi-square over-rejects there); for k > 2 the chi-square approximation is all that is implemented – check min_expected_count and treat p-values from tables with expected counts below ~5 as approximate.

Parameters:
Return type:

TestResult

dunn_test(*samples, p_adjust='holm')[source]

Dunn’s post-hoc test: all pairwise rank-mean comparisons after a Kruskal-Wallis rejection.

Uses the pooled-rank z statistic with the shared tie-corrected variance, and adjusts the pairwise p-values by 'holm', 'bonferroni', or 'none'.

Each pairwise z is calibrated under the GLOBAL null (all k groups exchangeable), because both the pooled ranks and the shared variance come from the full pooled sample. That is exactly the null a post-hoc test runs under after rejecting it, so read the p-values as screening under partial nulls, not as k(k-1)/2 self-contained two-sample tests: when one group genuinely differs, its observations shift the pooled ranks and the shared variance for every OTHER pair too – with two identical groups plus one concentrated third, the identical pair’s nominal-5% rejection rate is ~24%. For a confirmatory conclusion about a specific pair, test that pair directly (brunner_munzel() uses only the two groups involved) with your own multiplicity adjustment; use Dunn to rank candidates, not to certify them.

Parameters:
  • samples (Any)

  • p_adjust (str)

Return type:

DunnResult

wilcoxon_signed_rank(x, y=None, *, alternative='two-sided', zero_method='wilcox', correction=False)[source]

Wilcoxon signed-rank test for paired samples (or one sample vs 0).

Ranks |d| for d = x - y (mid-ranks for ties), splits into positive / negative rank sums, and uses the EXACT subset-sum null when it is available – n <= 300 with no zero differences and no tied |d| – and the tie-corrected normal approximation otherwise (the same regime switch SciPy and R make; the normal approximation is level-violating at very small n, where its smallest attainable p sits below the exact one). zero_method='wilcox' drops zero differences (and their ranks); 'pratt' keeps them in the ranking but drops them from the sums, with the matching Pratt/Cureton zero corrections applied to the null mean and variance (as scipy does). correction applies only to the normal branch (exact tails need no continuity repair). The matched-pairs rank-biserial correlation is reported as the effect size.

Parameters:
Return type:

WilcoxonResult

sign_test(x, y=None, *, alternative='two-sided')[source]

Sign test for paired samples (or one sample vs 0): exact binomial test on the signs of x - y.

Only the directions of the differences are used (ties dropped), so it is maximally robust but less powerful than the signed-rank test. extra carries n_positive and n (non-zero pairs).

Parameters:
Return type:

TestResult

friedman_test(*measurements)[source]

Friedman test for k related samples (repeated measures): the rank-based repeated-measures ANOVA.

Pass each treatment as a separate equal-length array (one value per block). Ranks within each block, tie-corrects, and uses a chi-square(k-1) reference. extra carries df and Kendall’s W concordance effect size.

Parameters:

measurements (Any)

Return type:

TestResult

jonckheere_terpstra(*samples, alternative='increasing')[source]

Jonckheere-Terpstra test for an ORDERED alternative across independent samples.

More powerful than Kruskal-Wallis when the groups are expected to shift monotonically in the given order. alternative='increasing' / 'decreasing' / 'two-sided'. Uses the tie-corrected normal approximation of the J statistic (sum of pairwise Mann-Whitney counts over ordered pairs).

The group ORDER is part of the hypothesis and must be chosen BEFORE looking at the data (the same pre-specification the sibling page_trend_test() states): the level calculation assumes one fixed ordering, and picking the ordering that best matches the observed group means – k!/2 candidate orderings – turns a nominal 5% test into a data-dredged one. If the ordering came from the data, this test’s p-value does not mean what it says.

The null variance is the published Jonckheere-Terpstra one (Lehmann 1975), whose leading term is [n(n-1)(2n+5) - sum n_i(n_i-1)(2n_i+5)] / 72 – algebraically the same as the equivalent [n^2(2n+3) - sum n_i^2(2n_i+3)] / 72 form. This implementation previously used n(n-1)(2n+3) - sum n_i(n_i-1)(2n_i+3), mixing the two (MXR-080-1599): a hybrid that matches neither published expression and, critically, does NOT reduce to the Mann-Whitney variance for two ordered groups. For two groups of five with complete separation it understated the variance as 21.5278 against the required n1*n2*(n+1)/12 = 22.9167, reporting z=2.69408, p=0.003529 where the rank-sum test gives z=2.61116, p=0.004512. The tied branch inherited the same altered base terms, including in its sum t_j(t_j-1)(2t_j+5) tie correction.

Reproducing that cross-check: the quoted rank-sum numbers use the normal approximation WITHOUT a continuity correction, because this J statistic’s normal reference carries none – call mann_whitney_u() with use_continuity=False to match (its DEFAULT is True, which reports a different, corrected p on the same data). The difference between the defaults is the correction convention, not a defect in either test.

Parameters:
  • samples (Any)

  • alternative (str)

Return type:

TestResult

page_trend_test(*measurements, decreasing=False)[source]

Page’s trend test for an ORDERED alternative in repeated measures.

Like Friedman but for a pre-specified ordering of the k treatments (the columns, in order). Tests L = sum_j j * R_j against the normal approximation. Set decreasing=True to predict the reverse ordering. extra carries the z-score.

Ties are handled exactly, not ignored: within-block midranks enter L, and the null variance is the exact permutation variance of the observed (possibly tied) midranks – per block, Var = sum_j (w_j - w-bar)^2 * sum_i (r_i - r-bar)^2 / (k - 1) under uniform within-block permutation, summed over blocks. With no ties this reduces algebraically to the textbook n k^2 (k+1)(k^2-1) / 144; with ties the textbook constant overstates the variance (tied midranks vary less), which made the test conservative and its z biased toward 0.

Parameters:
  • measurements (Any)

  • decreasing (bool)

Return type:

TestResult

runs_test(x, *, cutoff='median')[source]

Wald-Wolfowitz runs test for randomness of a binary/dichotomized sequence.

Dichotomizes x about its median (or a supplied numeric cutoff) and tests whether the run count departs from what independence predicts (too few runs => clustering/trend; too many => over-alternation). Two-sided. The null is EXACT (the closed-form run-count distribution given (n1, n2), big-integer arithmetic) for all n1 + n2 <= 5000 – the uncorrected normal approximation is level-violating at small and sparse sizes (exhaustive enumeration at n1 = n2 = 5 rejects 20/252 = 7.94%, and at n = 61, n1 = 3 9.83%, at nominal 5%; STAT-RR17-16/STAT-RR19-14). The normal approximation applies only above 5000 (STAT-RR22-16: this sentence previously still said 60 while execution switched at 5001). extra carries the run count, z-score, and method ("exact" / "normal").

Parameters:
Return type:

TestResult