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:
- Observations are independent within and among groups
- Observations within each group are normally distributed
- 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 groupcontrol(list[list], required): 2D list (column vector) for the control groupdunnett_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}"