BRUNNERMUNZEL
Overview
The BRUNNERMUNZEL function performs the Brunner-Munzel test, a nonparametric statistical test for comparing two independent samples. The test evaluates the null hypothesis that when values are drawn one by one from each group, the probability of obtaining a large value is equal in both groups.
Unlike the commonly used Wilcoxon-Mann-Whitney U test, the Brunner-Munzel test does not require the assumption of equal variances (equivariance) between the two groups. This makes it particularly useful when comparing distributions that may differ in shape or spread, not just location. The test was introduced by Brunner and Munzel in their 2000 paper addressing the nonparametric Behrens-Fisher problem [1].
This implementation uses the SciPy library’s brunnermunzel function. For complete details, see the SciPy brunnermunzel documentation.
The function returns the W statistic and a p-value. The p-value can be computed using either the t-distribution (recommended for sample sizes ≤ 50) or the normal distribution as an asymptotic approximation. Brunner and Munzel recommend using the t-distribution for small samples; for very small samples (n < 10), a permuted version of the test may be more appropriate [2].
The test supports three alternative hypotheses: two-sided (testing for any difference), less (testing if sample x tends to have smaller values than y), and greater (testing if sample x tends to have larger values than y).
References:
- Brunner, E. and Munzel, U. “The nonparametric Behrens-Fisher problem: Asymptotic theory and a small-sample approximation”. Biometrical Journal. Vol. 42 (2000): 17-25.
- Neubert, K. and Brunner, E. “A studentized permutation test for the non-parametric Behrens-Fisher problem”. Computational Statistics and Data Analysis. Vol. 51 (2007): 5192-5204.
This example function is provided as-is without any representation of accuracy.
Excel Usage
=BRUNNERMUNZEL(x, y, bm_alternative, bm_distribution)
x(list[list], required): First sample data as a 2D list. Must contain at least two numeric values.y(list[list], required): Second sample data as a 2D list. Must contain at least two numeric values.bm_alternative(str, optional, default: “two-sided”): Defines the alternative hypothesis for the test. Determines whether to test for two-sided difference, x less than y, or x greater than y.bm_distribution(str, optional, default: “t”): Statistical distribution used to compute the p-value. The t-distribution accounts for finite sample sizes; normal distribution is asymptotic approximation.
Returns (list[list]): 2D list [[statistic, p_value]], or error message string.
Examples
Example 1: Two-sided test with t-distribution (default parameters)
Inputs:
| x | y | ||
|---|---|---|---|
| 1.2 | 2.3 | 2.1 | 3.2 |
| 3.4 | 4.5 | 4.3 | 5.4 |
Excel formula:
=BRUNNERMUNZEL({1.2,2.3;3.4,4.5}, {2.1,3.2;4.3,5.4})
Expected output:
| Result | |
|---|---|
| 0.5477 | 0.6036 |
Example 2: One-sided test (less) with t-distribution
Inputs:
| x | y | bm_alternative | ||
|---|---|---|---|---|
| 1.2 | 2.3 | 2.1 | 3.2 | less |
| 3.4 | 4.5 | 4.3 | 5.4 |
Excel formula:
=BRUNNERMUNZEL({1.2,2.3;3.4,4.5}, {2.1,3.2;4.3,5.4}, "less")
Expected output:
| Result | |
|---|---|
| 0.5477 | 0.3018 |
Example 3: Two-sided test with normal distribution
Inputs:
| x | y | bm_distribution | ||
|---|---|---|---|---|
| 1.2 | 2.3 | 2.1 | 3.2 | normal |
| 3.4 | 4.5 | 4.3 | 5.4 |
Excel formula:
=BRUNNERMUNZEL({1.2,2.3;3.4,4.5}, {2.1,3.2;4.3,5.4}, "normal")
Expected output:
| Result | |
|---|---|
| 0.5477 | 0.5839 |
Example 4: One-sided test (greater) with t-distribution
Inputs:
| x | y | bm_alternative | bm_distribution | ||
|---|---|---|---|---|---|
| 1.2 | 2.3 | 2.1 | 3.2 | greater | t |
| 3.4 | 4.5 | 4.3 | 5.4 |
Excel formula:
=BRUNNERMUNZEL({1.2,2.3;3.4,4.5}, {2.1,3.2;4.3,5.4}, "greater", "t")
Expected output:
| Result | |
|---|---|
| 0.5477 | 0.6982 |
Python Code
import math
from scipy.stats import brunnermunzel as scipy_brunnermunzel
def brunnermunzel(x, y, bm_alternative='two-sided', bm_distribution='t'):
"""
Computes the Brunner-Munzel nonparametric test for two independent samples.
See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.brunnermunzel.html
This example function is provided as-is without any representation of accuracy.
Args:
x (list[list]): First sample data as a 2D list. Must contain at least two numeric values.
y (list[list]): Second sample data as a 2D list. Must contain at least two numeric values.
bm_alternative (str, optional): Defines the alternative hypothesis for the test. Determines whether to test for two-sided difference, x less than y, or x greater than y. Valid options: Two-sided, Less, Greater. Default is 'two-sided'.
bm_distribution (str, optional): Statistical distribution used to compute the p-value. The t-distribution accounts for finite sample sizes; normal distribution is asymptotic approximation. Valid options: T-distribution, Normal. Default is 't'.
Returns:
list[list]: 2D list [[statistic, p_value]], or error message string.
"""
def to2d(val):
"""Normalize input to 2D list."""
return [[val]] if not isinstance(val, list) else val
# Normalize inputs to 2D lists
x = to2d(x)
y = to2d(y)
# Validate x and y are 2D lists
if not (isinstance(x, list) and all(isinstance(row, list) for row in x)):
return "Invalid input: x must be a 2D list."
if not (isinstance(y, list) and all(isinstance(row, list) for row in y)):
return "Invalid input: y must be a 2D list."
# Flatten x and y
try:
x_flat = [float(item) for row in x for item in row]
y_flat = [float(item) for row in y for item in row]
except (TypeError, ValueError) as e:
return f"Invalid input: x and y must contain only numeric values. {e}"
if len(x_flat) < 2:
return "Invalid input: x must contain at least two values."
if len(y_flat) < 2:
return "Invalid input: y must contain at least two values."
# Validate bm_alternative
if bm_alternative not in ('two-sided', 'less', 'greater'):
return "Invalid input: bm_alternative must be 'two-sided', 'less', or 'greater'."
# Validate bm_distribution
if bm_distribution not in ('t', 'normal'):
return "Invalid input: bm_distribution must be 't' or 'normal'."
try:
result = scipy_brunnermunzel(x_flat, y_flat, alternative=bm_alternative, distribution=bm_distribution)
stat, pval = float(result.statistic), float(result.pvalue)
# If result is nan/inf and distribution is 't', retry with 'normal'
if bm_distribution == 't' and (math.isnan(stat) or math.isinf(stat) or math.isnan(pval) or math.isinf(pval)):
try:
result2 = scipy_brunnermunzel(x_flat, y_flat, alternative=bm_alternative, distribution='normal')
stat2, pval2 = float(result2.statistic), float(result2.pvalue)
if math.isnan(stat2) or math.isinf(stat2) or math.isnan(pval2) or math.isinf(pval2):
return "Invalid result: statistic or pvalue is NaN or infinite."
return [[stat2, pval2]]
except Exception as e2:
return f"scipy.stats.brunnermunzel error (normal fallback): {e2}"
# Disallow nan/inf
if math.isnan(stat) or math.isinf(stat) or math.isnan(pval) or math.isinf(pval):
return "Invalid result: statistic or pvalue is NaN or infinite."
return [[stat, pval]]
except Exception as e:
return f"scipy.stats.brunnermunzel error: {e}"