MEDIAN_TEST

Overview

The MEDIAN_TEST function performs Mood’s median test (also known as the Westenberg-Mood or Brown-Mood median test) to determine whether two or more independent samples come from populations with the same median. This is a nonparametric hypothesis test that makes no assumptions about the underlying distribution of the data, making it suitable for ordinal data or continuous data that may not follow a normal distribution.

Mood’s median test is a special case of Pearson’s chi-squared test. The procedure works by first computing the grand median of all observations across all samples combined. Each data point is then classified as being either above or below this grand median, creating a 2×k contingency table (where k is the number of sample groups). A chi-squared test is then applied to this contingency table to determine whether the observed frequencies differ significantly from expected frequencies under the null hypothesis of equal medians.

The ties parameter controls how values exactly equal to the grand median are handled: they can be classified as “below” (default), “above”, or “ignore” (excluded from the analysis). This choice can affect the p-value, particularly when many observations equal the median.

This implementation uses SciPy’s median_test function, which internally calls scipy.stats.chi2_contingency to compute the test statistic and p-value. The function returns the chi-squared statistic, the p-value, and the grand median.

While Mood’s median test has relatively low statistical power compared to alternatives like the Kruskal-Wallis test or Mann-Whitney U test, it remains useful when the research question specifically concerns medians, when distributions have different shapes or variances, or when data contains extreme outliers or “off the scale” observations.

This example function is provided as-is without any representation of accuracy.

Excel Usage

=MEDIAN_TEST(samples, ties)
  • samples (list[list], required): 2D list where each inner list is a sample group to compare.
  • ties (str, optional, default: “below”): How to classify values equal to the grand median.

Returns (list[list]): 2D list [[stat, p, median, contingency]], or error string.

Examples

Example 1: Demo case 1

Inputs:

samples
1 2 3
4 5 6

Excel formula:

=MEDIAN_TEST({1,2,3;4,5,6})

Expected output:

Result
2.6667 0.1025 3.5

Example 2: Demo case 2

Inputs:

samples
1 2 3
4 5 6
7 8 9

Excel formula:

=MEDIAN_TEST({1,2,3;4,5,6;7,8,9})

Expected output:

Result
6.3 0.0429 5

Example 3: Demo case 3

Inputs:

samples ties
1 2 3 above
4 5 6

Excel formula:

=MEDIAN_TEST({1,2,3;4,5,6}, "above")

Expected output:

Result
2.6667 0.1025 3.5

Example 4: Demo case 4

Inputs:

samples ties
1 2 3 ignore
4 5 6

Excel formula:

=MEDIAN_TEST({1,2,3;4,5,6}, "ignore")

Expected output:

Result
2.6667 0.1025 3.5

Python Code

import math

from scipy.stats import median_test as scipy_median_test

def median_test(samples, ties='below'):
    """
    Performs Mood's median test to determine if two or more independent samples come from populations with the same median.

    See: https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.median_test.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        samples (list[list]): 2D list where each inner list is a sample group to compare.
        ties (str, optional): How to classify values equal to the grand median. Valid options: Below, Above, Ignore. Default is 'below'.

    Returns:
        list[list]: 2D list [[stat, p, median, contingency]], or error string.
    """
    def to2d(x):
        return [[x]] if not isinstance(x, list) else x

    samples = to2d(samples)

    if not isinstance(samples, list) or not all(isinstance(g, list) for g in samples):
        return "Invalid input: samples must be a 2D list."
    if len(samples) < 2:
        return "Invalid input: samples must contain at least two groups."
    if any(len(g) < 2 for g in samples):
        return "Invalid input: each sample group must have at least two values."

    valid_ties = ("below", "above", "ignore")
    if ties not in valid_ties:
        return f"Invalid input: ties must be one of {valid_ties}."

    try:
        samples_float = [[float(x) for x in g] for g in samples]
    except (TypeError, ValueError):
        return "Invalid input: all sample values must be numeric."

    try:
        stat, pvalue, median, _ = scipy_median_test(*samples_float, ties=ties)
        stat, pvalue, median = float(stat), float(pvalue), float(median)
    except Exception as e:
        return f"Median test error: {e}"

    if math.isnan(stat) or math.isinf(stat) or math.isnan(pvalue) or math.isinf(pvalue) or math.isnan(median) or math.isinf(median):
        return "Invalid output: result contains NaN or infinite value."

    return [[stat, pvalue, median]]

Online Calculator