DUNNETT

Overview

The DUNNETT function performs Dunnett’s test, a statistical procedure for comparing the means of multiple experimental groups against a single control group while controlling the family-wise error rate. This test is particularly useful in experimental designs where researchers want to determine whether any treatment groups differ significantly from a baseline or control condition.

Dunnett’s test was introduced by Charles W. Dunnett in his 1955 paper “A Multiple Comparison Procedure for Comparing Several Treatments with a Control” published in the Journal of the American Statistical Association. Unlike performing multiple independent t-tests, which inflates the probability of Type I errors, Dunnett’s test adjusts for multiple comparisons to maintain the desired significance level across all tests simultaneously.

This implementation uses the SciPy library’s scipy.stats.dunnett function. For detailed documentation, see the SciPy dunnett reference. The source code is available on GitHub.

The test relies on several key assumptions:

  1. Observations are independent within and among groups
  2. Observations within each group are normally distributed
  3. The distributions have equal (homogeneous) variances

The function returns test statistics and p-values for each comparison between an experimental group and the control. When pairwise comparisons between all groups are needed, consider using Tukey’s HSD test instead. However, when only comparisons against a control are required, Dunnett’s test offers higher statistical power.

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

Excel Usage

=DUNNETT(samples, control, dunnett_alt)
  • samples (list[list], required): 2D list where each column is an experimental group
  • control (list[list], required): 2D list (column vector) for the control group
  • dunnett_alt (str, optional, default: “two-sided”): The alternative hypothesis type

Returns (list[list]): 2D list of comparisons vs control, or error message string.

Examples

Example 1: Demo case 1

Inputs:

samples control
1.2 2.1 1.1
1.5 2.3 1.4
1.3 2.2 1.2

Excel formula:

=DUNNETT({1.2,2.1;1.5,2.3;1.3,2.2}, {1.1;1.4;1.2})

Expected output:

Result
0.8911 8.6143
0.6028 0.00016

Example 2: Demo case 2

Inputs:

samples control dunnett_alt
1.2 2.1 1.1 less
1.5 2.3 1.4
1.3 2.2 1.2

Excel formula:

=DUNNETT({1.2,2.1;1.5,2.3;1.3,2.2}, {1.1;1.4;1.2}, "less")

Expected output:

Result
0.8911 8.6143
0.9073 1

Example 3: Demo case 3

Inputs:

samples control dunnett_alt
1.2 2.1 1.1 greater
1.5 2.3 1.4
1.3 2.2 1.2

Excel formula:

=DUNNETT({1.2,2.1;1.5,2.3;1.3,2.2}, {1.1;1.4;1.2}, "greater")

Expected output:

Result
0.8911 8.6143
0.3145 0.00007

Example 4: Demo case 4

Inputs:

samples control dunnett_alt
1.2 2.1 1.1 two-sided
1.5 2.3 1.4
1.3 2.2 1.2

Excel formula:

=DUNNETT({1.2,2.1;1.5,2.3;1.3,2.2}, {1.1;1.4;1.2}, "two-sided")

Expected output:

Result
0.8911 8.6143
0.6028 0.00016

Python Code

from scipy.stats import dunnett as scipy_dunnett
import numpy as np

def dunnett(samples, control, dunnett_alt='two-sided'):
    """
    Performs Dunnett's test for multiple comparisons of means against a control group.

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

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

    Args:
        samples (list[list]): 2D list where each column is an experimental group
        control (list[list]): 2D list (column vector) for the control group
        dunnett_alt (str, optional): The alternative hypothesis type Valid options: Two-sided, Less, Greater. Default is 'two-sided'.

    Returns:
        list[list]: 2D list of comparisons vs control, or error message string.
    """
    def to2d(x):
        """Normalize scalar or list to 2D list."""
        return [[x]] if not isinstance(x, list) else x

    # Normalize control in case Excel passes single element as scalar
    control = to2d(control)

    # Validate samples structure
    if not isinstance(samples, list) or len(samples) < 2:
        return "Error: samples must be a 2D list with at least two rows."

    if not all(isinstance(row, list) for row in samples):
        return "Error: samples must be a 2D list."

    n_rows = len(samples)
    n_cols = len(samples[0]) if n_rows > 0 else 0

    if n_cols < 2:
        return "Error: samples must have at least two columns (experimental groups)."

    for row in samples:
        if len(row) != n_cols:
            return "Error: All rows in samples must have the same number of columns."

    # Convert samples to groups (transpose and convert to float)
    try:
        groups = [[float(samples[row][col]) for row in range(n_rows)] for col in range(n_cols)]
    except (TypeError, ValueError):
        return "Error: samples must contain only numeric values."

    # Validate control structure
    if not isinstance(control, list) or len(control) < 2:
        return "Error: control must be a 2D list with at least two rows."

    if not all(isinstance(row, list) for row in control):
        return "Error: control must be a 2D list."

    if not all(len(row) == 1 for row in control):
        return "Error: control must have exactly one column."

    # Convert control to 1D list of floats
    try:
        control_group = [float(row[0]) for row in control]
    except (TypeError, ValueError):
        return "Error: control must contain only numeric values."

    # Validate alternative parameter
    if dunnett_alt not in ('two-sided', 'less', 'greater'):
        return "Error: alternative must be 'two-sided', 'less', or 'greater'."
    # Run Dunnett's test with fixed random state for reproducibility
    try:
        result = scipy_dunnett(*groups, control=control_group, alternative=dunnett_alt, random_state=42)
    except ValueError as e:
        return f"Error: Invalid data for Dunnett's test: {e}"
    except Exception as e:
        return f"Error running Dunnett's test: {e}"

    # Extract and validate results
    try:
        import math

        stat = result.statistic
        pvals = result.pvalue

        # Convert to lists
        stat_list = stat.tolist() if hasattr(stat, 'tolist') else list(stat)
        pvals_list = pvals.tolist() if hasattr(pvals, 'tolist') else list(pvals)

        # Validate output values
        for val in stat_list + pvals_list:
            if isinstance(val, float) and (math.isnan(val) or math.isinf(val)):
                return "Error: Computation resulted in invalid values (NaN or Inf)."

        return [stat_list, pvals_list]
    except Exception as e:
        return f"Error: processing results: {e}"

Online Calculator