Code
# Attach the four-level region factor to USArrests; the levels
# are the values the categorical variable can take.
dat <- USArrests
dat$Region <- state.region
levels(dat$Region)
## [1] "Northeast" "South" "North Central" "West"So far we have used regression with cardinal explanatory variables. We now turn to factor variables that label which group each observation belongs to: regions, industries, treatment arms. The chapter builds up from the ANOVA decomposition of variation between and within groups to its nonparametric Kruskal-Wallis analog. The next chapter rewrites these comparisons as regressions and develops permutation inference for a factor variable.
So far we have used cardinal explanatory variables where \(4-3=2-1\); many useful predictors are instead labels of group membership that do not arithmetic this way.
Factor variables are useful when group identity itself predicts the outcome (region, industry, treatment arm) and we want to compare group means within a regression framework. They split into two flavors:
One common case is if you have observations of individuals over time periods, then you may have two factor variables. An unordered factor that indicates who an individual is; for example \(\hat{D}_{i}=\mathbf{1}( \text{Individual} = i)\), and an ordered factor that indicates the time period; for example \(\hat{D}_{t}=\mathbf{1}( \text{Time} \in [\text{month}~ t, \text{month}~ t+1) )\). There are many other cases you see factor variables, including spatial ID’s in purely cross sectional data.
Be careful not to handle categorical data as if they were cardinal. E.g., generate city data with Leipzig=1, Lausanne=2, LosAngeles=3, … and then include city as if it were a cardinal number (that’s a big no-no). The same applied to ordinal data; PopulationLeipzig=2, PopulationLausanne=3, PopulationLosAngeles=1.
In this chapter, we use state.region (Northeast, South, North Central, West) as an unordered factor to group the USArrests data by US Census region.
# Attach the four-level region factor to USArrests; the levels
# are the values the categorical variable can take.
dat <- USArrests
dat$Region <- state.region
levels(dat$Region)
## [1] "Northeast" "South" "North Central" "West"With multiple groups, begin with a summary figure (such as a boxplot) and then run a global test of whether at least one group differs.
# Step 1: Visual summary
boxplot(Murder ~ Region, data = dat,
col = hcl.colors(4, alpha = .45),
ylab = 'Murder arrests (per 100k)', xlab = 'Region', main = NA)
title('Murder rates by region', font.main = 1)
In Comparing Groups, we tested whether two groups had the same mean. We now extend this to \(G\) groups. The Analysis of Variance (ANOVA) approach asks: are the observed differences in group means larger than we would expect from random variation alone?
If group means differ, some of the spread in \(Y\) must come from differences between groups rather than within them. We can split each piece out.
The decomposition is useful as the foundation for any test of “do group means differ?”: if they do not, \(\hat{BSS}\) should be small relative to \(\hat{WSS}\). The equality holds exactly because the cross-term \(\sum_{g,i}(\hat{Y}_{ig}-\hat{M}_{g})(\hat{M}_{g}-\hat{M}_{Y})\) vanishes, since the within-group deviations sum to zero around their own group mean.
In R, aov() fits the ANOVA model and summary() returns the standard ANOVA table. The ANOVA table reports \(\hat{BSS}\) (“Sum Sq” for Region), \(\hat{WSS}\) (“Sum Sq” for Residuals).
# Fit ANOVA
fit_aov <- aov(Murder ~ Region, data = dat)
fit_aov
## Call:
## aov(formula = Murder ~ Region, data = dat)
##
## Terms:
## Region Residuals
## Sum of Squares 391.2357 538.3171
## Deg. of Freedom 3 46
##
## Residual standard error: 3.420898
## Estimated effects may be unbalancedWe have the decomposition; now we need a single number that tells us when the between-group piece is “large” relative to the within-group piece.
The ANOVA F-statistic is useful as the global “do any means differ?” test: one number and one \(p\)-value, regardless of how many groups you have. The numerator is the average between-group variation per degree of freedom, the denominator is the average within-group variation, and a large \(\hat{F}\) means group means are more spread out than individual-level noise would predict.
F_fun <- function(Y,g){
M_Y <- mean(Y)
M_g <- aggregate(Y, list(g), mean)$x
n_g <- aggregate(Y, list(g), length)$x
BSS <- sum(n_g * (M_g - M_Y)^2)
WSS <- sum(aggregate(Y, list(g), function(y) sum((y - mean(y))^2))$x)
n <- length(Y)
G <- nlevels(g)
F_obs <- (BSS/(G-1)) / (WSS/(n-G))
}
F_obs <- F_fun(Y=dat$Murder, g=dat$Region)
F_obs
## [1] 11.14389To compute a \(p\)-value, we can use permutation or bootstrap methods to build a null distribution, just as in previous chapters. The Permutation Tests section in the next chapter instead shuffles the observed labels without replacement, preserving the number of states in each region.
Under additional parametric assumptions (independent observations, equal variances, normal errors), \(\hat{F}\) (under the null) follows an \(F_{G-1,\;n-G}\) distribution. From that theoretical null distribution, we can then compute p-values (which is the default report method in R).
summary(fit_aov)
## Df Sum Sq Mean Sq F value Pr(>F)
## Region 3 391.2 130.4 11.14 1.28e-05 ***
## Residuals 46 538.3 11.7
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
p_param <- 1 - pf(F_obs, G-1, n-G)
# Compare parametric and bootstrap p-values
cbind(F_obs, p_param, p_boot)
## F_obs p_param p_boot
## [1,] 11.14389 0.0001093623 0ANOVA tests whether group means differ; sometimes the entire distributions differ in ways the mean would miss.
When data are skewed, heavy-tailed, or only ordinal, a test built on means may overlook differences that show up in the rank order.
The Kruskal-Wallis test is useful when the equal-variance normal-error assumption is implausible, as with income, response times, or other heavy-tailed outcomes. Rank-based tests need only the orderings of the data. A significant result tells us that some distribution differs, but not which one; the global rejection has to be followed up with pairwise tests.
To conduct the test, first denote individuals \(i=1,...n\) with overall ranks \(\hat{r}_1,....\hat{r}_{n}\). Each individual belongs to group \(g=1,...G\), and each group \(g\) has \(n_{g}\) individuals with average rank \(\bar{r}_{g} = \sum_{i} \hat{r}_{i} /n_{g}\). The Kruskal Wallis statistic is \[\begin{aligned} \hat{KW} &= (n-1) \frac{\sum_{g=1}^{G} n_{g}( \bar{r}_{g} - \bar{r} )^2 }{\sum_{i=1}^{n} ( \hat{r}_{i} - \bar{r} )^2}, \end{aligned}\] where \(\bar{r} = \frac{n+1}{2}\) is the grand mean rank.
In the special case with only two groups, the Kruskal Wallis test reduces to the Mann–Whitney U test (also known as the Wilcoxon rank-sum test). In this case, we can write the hypotheses in terms of individual outcomes in each group, \(Y_i\) in one group \(Y_j\) in the other; \(H_0: Prob(Y_i > Y_j)=Prob(Y_j > Y_i)\) versus \(H_A: Prob(Y_i > Y_j) \neq Prob(Y_j > Y_i)\). The corresponding test statistic is \[\begin{aligned} \hat{U} &= \min(\hat{U}_1, \hat{U}_2) \\ \hat{U}_g &= \sum_{i\in g}\sum_{j\in -g} \Bigl[\mathbf 1( \hat{Y}_{i} > \hat{Y}_{j}) + \tfrac12\mathbf 1(\hat{Y}_{i} = \hat{Y}_{j})\Bigr]. \end{aligned}\]
Interpretation template:
kw <- kruskal.test(Murder ~ Region, data = dat)
kw
##
## Kruskal-Wallis rank sum test
##
## data: Murder by Region
## Kruskal-Wallis chi-squared = 19.345, df = 3, p-value = 0.000232A significant Kruskal-Wallis result tells us that some group differs, not which. To answer which, use a post-hoc pairwise test together with a multiple-comparisons correction (see Multiple Testing in a later chapter).
Comment the script you wrote for this chapter, then restart R and check that it runs from a clean session, then check the script with AI as explained in Working with AI. Write three sentences from memory on the main statistical idea of this chapter, and ask the assistant what is wrong, vague, or missing. Finish with your own questions about whatever you found hardest.
An ANOVA \(F\)-test rejects the null that all group means are equal. Does this tell you which group is different, or only that at least one differs?
Using the USArrests dataset with state.region as the grouping variable, compute the between-group sum of squares (\(\hat{BSS}\)) and within-group sum of squares (\(\hat{WSS}\)) for Assault by hand (using aggregate()). Verify your \(\hat{F}\) statistic matches the output of summary(aov(Assault ~ state.region, data = ...)).
This chapter introduced ANOVA: the decomposition of variation between and within groups, the \(F\)-statistic that compares the two, and the nonparametric Kruskal-Wallis analog. The worked three-group example with \(A=\{4,5,6\}, B=\{8,7,9\}, C=\{5,6,7\}\) made the arithmetic concrete: by hand we got \(\hat{BSS}=14\) and \(\hat{WSS}=6\), which aov(Y ~ g) reproduces exactly. The Kruskal-Wallis test replaced outcomes with ranks when the equal-variance normal-error assumptions were doubtful. The next chapter rewrites group comparisons as regressions, compares treatment and effect coding, and builds a permutation test for the factor as a whole.