How to Perform the Brown-Forsythe Test in R
Before running an ANOVA, you need to verify that your groups have equal variances. The Brown-Forsythe test is one of the most reliable methods for checking this assumption, particularly when your...
Key Insights
- The Brown-Forsythe test uses the median instead of the mean, making it more robust than Levene’s test when your data contains outliers or deviates from normality.
- In R, you can perform the Brown-Forsythe test using either
leveneTest()from thecarpackage withcenter = medianor the dedicatedbf.test()function fromonewaytests. - A non-significant result (p > 0.05) indicates equal variances across groups, validating the homogeneity assumption required for standard ANOVA.
Introduction to the Brown-Forsythe Test
Before running an ANOVA, you need to verify that your groups have equal variances. The Brown-Forsythe test is one of the most reliable methods for checking this assumption, particularly when your data isn’t perfectly normal.
The test was introduced by Morton Brown and Alan Forsythe in 1974 as a modification of Levene’s test. The key difference lies in the center statistic: while the original Levene’s test uses the group mean, the Brown-Forsythe variant uses the group median. This single change makes the test considerably more robust to non-normal distributions and outliers.
The null hypothesis states that all group variances are equal. The alternative hypothesis states that at least one group has a different variance. When the p-value falls below your significance threshold (typically 0.05), you reject the null hypothesis and conclude that the homogeneity of variance assumption is violated.
Use the Brown-Forsythe test when you suspect your data contains outliers, when sample sizes are unequal, or when you’re uncertain about the underlying distribution. It’s the safer default choice compared to Bartlett’s test, which assumes normality, or the mean-based Levene’s test.
Prerequisites and Setup
You’ll need two packages to follow along with this tutorial. The car package provides the most commonly used implementation, while onewaytests offers a dedicated Brown-Forsythe function with additional output.
# Install packages if needed
install.packages("car")
install.packages("onewaytests")
# Load the packages
library(car)
library(onewaytests)
Let’s create a sample dataset to work with throughout this article. We’ll simulate a scenario where three different fertilizer treatments are applied to plants, and we measure their growth in centimeters.
# Set seed for reproducibility
set.seed(42)
# Create sample data: plant growth under three fertilizer treatments
fertilizer_data <- data.frame(
growth = c(
rnorm(30, mean = 15, sd = 2), # Treatment A
rnorm(30, mean = 18, sd = 2.5), # Treatment B
rnorm(30, mean = 20, sd = 3) # Treatment C
),
treatment = factor(rep(c("A", "B", "C"), each = 30))
)
# Quick look at the data structure
str(fertilizer_data)
head(fertilizer_data)
# Summary statistics by group
aggregate(growth ~ treatment, data = fertilizer_data,
FUN = function(x) c(mean = mean(x), sd = sd(x), n = length(x)))
This dataset intentionally has slightly different standard deviations across groups, which gives us something meaningful to test.
Assumptions and When to Use
The Brown-Forsythe test has minimal assumptions, which contributes to its robustness:
-
Independent observations: Each measurement must be independent of others. This is a study design issue, not something you can test statistically.
-
Continuous dependent variable: Your outcome variable should be measured on a continuous scale (interval or ratio).
-
Categorical grouping variable: You need a factor variable defining your groups.
Notice what’s absent from this list: the Brown-Forsythe test does not assume normality within groups. This is its primary advantage over Bartlett’s test, which is highly sensitive to non-normality and will often reject the null hypothesis simply because your data isn’t normal rather than because variances differ.
Here’s a comparison of the three main variance homogeneity tests:
| Test | Center Statistic | Normality Assumption | Robustness |
|---|---|---|---|
| Bartlett’s | — | Required | Low |
| Levene’s (mean) | Mean | Not required | Moderate |
| Brown-Forsythe | Median | Not required | High |
Use the Brown-Forsythe test as your default choice before ANOVA. Reserve Bartlett’s test for situations where you’ve already confirmed normality and need maximum power.
Performing the Test Using the car Package
The car package’s leveneTest() function handles both the standard Levene’s test and the Brown-Forsythe variant. The center parameter controls which version runs.
# Brown-Forsythe test using car package
bf_result <- leveneTest(growth ~ treatment,
data = fertilizer_data,
center = median)
# Display results
print(bf_result)
The output looks like this:
Levene's Test for Homogeneity of Variance (center = median)
Df F value Pr(>F)
group 2 1.8234 0.1672
87
Let’s break down this output:
- Df: The degrees of freedom. The first row shows degrees of freedom for the between-group comparison (k - 1, where k is the number of groups). The second row shows the residual degrees of freedom (N - k).
- F value: The test statistic. Larger values indicate greater differences in variances across groups.
- Pr(>F): The p-value. This is what you use to make your decision.
In this example, p = 0.1672, which exceeds our significance level of 0.05. We fail to reject the null hypothesis and conclude that the variances are homogeneous across treatment groups. This means we can proceed with standard ANOVA.
You can also use formula notation with the response variable and grouping variable separated:
# Alternative syntax
bf_result_alt <- leveneTest(fertilizer_data$growth,
fertilizer_data$treatment,
center = median)
print(bf_result_alt)
Alternative: Using the onewaytests Package
The onewaytests package provides a dedicated bf.test() function that offers more detailed output, including group-specific statistics.
# Brown-Forsythe test using onewaytests package
bf_detailed <- bf.test(growth ~ treatment, data = fertilizer_data)
# Display results
print(bf_detailed)
The output from onewaytests is more comprehensive:
Brown-Forsythe Test (alpha = 0.05)
-------------------------------------------------------------
data : growth and treatment
statistic : 1.823442
num df : 2
denom df : 87
p.value : 0.1672341
Result : Difference is not statistically significant.
-------------------------------------------------------------
This package also provides descriptive statistics for each group:
# Get descriptive statistics alongside the test
bf_detailed$statistic
bf_detailed$p.value
bf_detailed$parameter
The onewaytests package is particularly useful when you want a clear, interpretable output for reports or when you’re teaching statistics. The explicit “Result” statement removes ambiguity about interpretation.
Interpreting Results and Making Decisions
Understanding what to do with your Brown-Forsythe results is crucial. Here’s a complete workflow that demonstrates the decision-making process:
# Complete analysis workflow
perform_variance_check <- function(data, formula, alpha = 0.05) {
# Run Brown-Forsythe test
bf_test <- leveneTest(formula, data = data, center = median)
p_value <- bf_test$`Pr(>F)`[1]
cat("Brown-Forsythe Test Results\n")
cat("===========================\n")
cat(sprintf("F-statistic: %.4f\n", bf_test$`F value`[1]))
cat(sprintf("p-value: %.4f\n", p_value))
cat(sprintf("Significance level: %.2f\n\n", alpha))
if (p_value > alpha) {
cat("Decision: FAIL TO REJECT null hypothesis\n")
cat("Interpretation: Variances are homogeneous across groups.\n")
cat("Recommendation: Proceed with standard ANOVA.\n")
return(list(homogeneous = TRUE, p_value = p_value))
} else {
cat("Decision: REJECT null hypothesis\n")
cat("Interpretation: Variances differ significantly across groups.\n")
cat("Recommendation: Use Welch's ANOVA or a non-parametric alternative.\n")
return(list(homogeneous = FALSE, p_value = p_value))
}
}
# Run the workflow
result <- perform_variance_check(fertilizer_data, growth ~ treatment)
When variances are homogeneous (p > 0.05), proceed with standard one-way ANOVA:
# Standard ANOVA (appropriate when Brown-Forsythe is non-significant)
if (result$homogeneous) {
anova_result <- aov(growth ~ treatment, data = fertilizer_data)
summary(anova_result)
}
When variances are heterogeneous (p ≤ 0.05), use Welch’s ANOVA instead:
# Welch's ANOVA (appropriate when Brown-Forsythe is significant)
if (!result$homogeneous) {
welch_result <- oneway.test(growth ~ treatment,
data = fertilizer_data,
var.equal = FALSE)
print(welch_result)
}
Let’s create a dataset with unequal variances to see this in action:
# Create data with clearly unequal variances
set.seed(123)
unequal_var_data <- data.frame(
score = c(
rnorm(25, mean = 50, sd = 5), # Group 1: low variance
rnorm(25, mean = 55, sd = 15), # Group 2: high variance
rnorm(25, mean = 52, sd = 3) # Group 3: very low variance
),
group = factor(rep(1:3, each = 25))
)
# Test this data
result_unequal <- perform_variance_check(unequal_var_data, score ~ group)
This will show a significant result, directing you toward Welch’s ANOVA.
Conclusion
The Brown-Forsythe test should be your go-to method for checking variance homogeneity before ANOVA. Its robustness to non-normality and outliers makes it reliable across a wide range of data conditions.
Key takeaways:
- Use
leveneTest(y ~ group, data, center = median)from thecarpackage for quick testing. - Use
bf.test()fromonewaytestswhen you need detailed output for reports. - A p-value above 0.05 means variances are equal—proceed with standard ANOVA.
- A p-value at or below 0.05 means variances differ—switch to Welch’s ANOVA.
Don’t skip the variance check. Running standard ANOVA on data with heterogeneous variances inflates your Type I error rate, potentially leading to false positives. The Brown-Forsythe test takes seconds to run and protects the validity of your subsequent analysis.