tab <- apply(HairEyeColor, c(1, 2), sum)
tab Eye
Hair Brown Blue Hazel Green
Black 68 20 15 5
Brown 119 84 54 29
Red 26 17 14 14
Blond 7 94 10 16
Rverse Analytics
July 2, 2026
When both of your variables are categorical, the chi-square test of independence asks: are they associated, or independent? Here’s the full workflow in R. (Unsure this is the right test? Check the which-test tool.)
We’ll use the built-in HairEyeColor data, collapsed to a hair-colour × eye-colour table:
A small p-value means hair colour and eye colour are not independent — there’s an association. The test compares the observed counts to the counts you’d expect if the two variables were unrelated.
The mosaic plot makes the association visible — tile areas are proportional to counts, and the pattern departs clearly from a plain grid.
The chi-square approximation gets unreliable when expected cell counts are small (a common rule: any expected count < 5). R even warns you. In that case, use the exact alternative:
Fisher's Exact Test for Count Data
data: matrix(c(8, 2, 1, 9), nrow = 2)
p-value = 0.005477
alternative hypothesis: true odds ratio is not equal to 1
95 percent confidence interval:
2.057999 1740.081669
sample estimates:
odds ratio
27.32632
Check expected counts with chisq.test(tab)$expected, and switch to fisher.test() for small tables.
Report the statistic, degrees of freedom, p-value and — crucially — an effect size such as Cramér’s V, since a large sample can make a trivial association “significant.”
Categorical data with more structure than a single table? That’s where we come in.