12-02: Exercises — Chi-Square Test of Independence¶
Notes reference: 12-02: Chi-Square Test of Independence
Q1: Expected counts¶
A 2 × 3 table has row totals 120 and 80, column totals 90, 70, 40, and grand total 200.
Compute all six expected counts and verify they reproduce the margins.
Solution
| Expected | Col 1 | Col 2 | Col 3 | Row total |
|---|---|---|---|---|
| Row 1 | 120×90/200 = 54.0 |
120×70/200 = 42.0 |
120×40/200 = 24.0 |
120.0 |
| Row 2 | 80×90/200 = 36.0 |
80×70/200 = 28.0 |
80×40/200 = 16.0 |
80.0 |
| Col total | 90.0 | 70.0 | 40.0 | 200.0 |
The expected table reproduces every margin exactly — that is the defining
property, and the fastest way to check your arithmetic.
df = (2 − 1)(3 − 1) = 2
All E ≥ 5 ✓
' Observed in B2:D3, row totals in E, column totals in row 4
=$E2*B$4/$E$4 ' B7: expected — the mixed $ makes it fill correctly
Q2: Full test of independence¶
Is smoking status related to exercise habit? A survey of 300 adults:
| Never exercises | Sometimes | Regularly | Total | |
|---|---|---|---|---|
| Smoker | 32 | 28 | 15 | 75 |
| Non-smoker | 48 | 92 | 85 | 225 |
| Total | 80 | 120 | 100 | 300 |
Test at α = 0.05.
Solution
Expected counts
| Expected | Never | Sometimes | Regularly |
|---|---|---|---|
| Smoker | 75×80/300 = 20 |
75×120/300 = 30 |
75×100/300 = 25 |
| Non-smoker | 225×80/300 = 60 |
225×120/300 = 90 |
225×100/300 = 75 |
All expected counts ≥ 5 ✓ · df = (2−1)(3−1) = 2
Contributions
| Cell | O |
E |
(O−E)²/E |
|---|---|---|---|
| Smoker / Never | 32 | 20 | 7.2000 |
| Smoker / Sometimes | 28 | 30 | 0.1333 |
| Smoker / Regularly | 15 | 25 | 4.0000 |
| Non-smoker / Never | 48 | 60 | 2.4000 |
| Non-smoker / Sometimes | 92 | 90 | 0.0444 |
| Non-smoker / Regularly | 85 | 75 | 1.3333 |
| χ² = 15.1111 |
STEP 4 critical χ²(0.05, 2) = 5.9915
15.1111 > 5.9915 → REJECT H₀
p-value = P(χ²₂ > 15.1111) = 0.000524
STEP 5 There is very strong evidence at the 5% level of an association
between smoking status and exercise habit.
WHERE THE ASSOCIATION IS
Smoker / Never exercises 7.20 ← 32 observed vs. 20 expected
Smoker / Regularly 4.00 ← 15 observed vs. 25 expected
Smokers are over-represented among non-exercisers and
under-represented among regular exercisers.
EFFECT SIZE (Cramér's V)
V = √( χ² / (n × min(r−1, c−1)) ) = √(15.1111 / (300 × 1))
= √0.050370 = 0.2244 → a SMALL-to-medium association
CAUSATION: this is an OBSERVATIONAL survey. It shows association only.
Age, income, and education plausibly drive both behaviours.
' Observed in B2:D3
=$E2*B$4/$E$4 ' expected table in B7:D8
=SUMPRODUCT((B2:D3-B7:D8)^2/B7:D8) ' χ² -> 15.1111
=(2-1)*(3-1) ' df -> 2
=CHISQ.INV.RT(0.05, 2) ' 5.99146
=CHISQ.TEST(B2:D3, B7:D8) ' p -> 0.000524
=SQRT(15.1111/(300*1)) ' Cramér's V -> 0.2244
tab <- matrix(c(32, 28, 15,
48, 92, 85), nrow = 2, byrow = TRUE,
dimnames = list(smoking = c("Smoker","Non-smoker"),
exercise = c("Never","Sometimes","Regularly")))
test <- chisq.test(tab)
test # X-squared = 15.111, df = 2, p-value = 0.0005243
test$expected
round(test$residuals^2, 3) # cell contributions
round(test$stdres, 2) # adjusted residuals; |value| > 2 is influential
sqrt(test$statistic / (sum(tab) * min(dim(tab) - 1))) # Cramér's V
mosaicplot(tab, shade = TRUE)
tab = pd.DataFrame([[32,28,15],[48,92,85]],
index=["Smoker","Non-smoker"],
columns=["Never","Sometimes","Regularly"])
chi2, p, dof, expected = stats.chi2_contingency(tab)
chi2, dof, p
((tab - expected)**2 / expected).round(3)
np.sqrt(chi2 / (tab.values.sum() * (min(tab.shape) - 1)))
Q3: A test that does not reject¶
Is preference for a product related to region?
| North | South | East | Total | |
|---|---|---|---|---|
| Prefer A | 45 | 52 | 43 | 140 |
| Prefer B | 35 | 38 | 37 | 110 |
| Total | 80 | 90 | 80 | 250 |
Test at α = 0.05.
Solution
EXPECTED
Prefer A: 140×80/250 = 44.8 140×90/250 = 50.4 140×80/250 = 44.8
Prefer B: 110×80/250 = 35.2 110×90/250 = 39.6 110×80/250 = 35.2
CONTRIBUTIONS
(45−44.8)²/44.8 = 0.00089
(52−50.4)²/50.4 = 0.05079
(43−44.8)²/44.8 = 0.07232
(35−35.2)²/35.2 = 0.00114
(38−39.6)²/39.6 = 0.06465
(37−35.2)²/35.2 = 0.09205
─────────
χ² = 0.28184
df = (2−1)(3−1) = 2
critical χ²(0.05, 2) = 5.9915
0.28184 < 5.9915 → FAIL TO REJECT H₀
p-value = P(χ²₂ > 0.28184) = 0.8685
CONCLUSION: no evidence of an association. Preference appears the same
across regions — which is itself a useful marketing finding: one national
campaign, not three regional ones.
Cramér's V = √(0.28184/250) = 0.0336 — essentially zero.
Q4: Test of homogeneity¶
Three clinics each treat 100 patients. Outcomes:
| Improved | No change | Worse | Total | |
|---|---|---|---|---|
| Clinic A | 62 | 28 | 10 | 100 |
| Clinic B | 55 | 33 | 12 | 100 |
| Clinic C | 41 | 40 | 19 | 100 |
| Total | 158 | 101 | 41 | 300 |
Test at α = 0.05 whether the three clinics have the same outcome distribution.
Solution
STEP 1 H₀: the three clinics have the SAME outcome distribution
H₁: at least one clinic differs
α = 0.05
NOTE: this is a test of HOMOGENEITY, not independence, because the
row totals (100 each) were FIXED BY DESIGN — three separate samples,
not one sample cross-classified. The arithmetic is identical; only
the wording of the hypotheses changes.
EXPECTED (each row total is 100, so E = 100 × col total / 300)
Improved: 158/3 = 52.667 each clinic
No change: 101/3 = 33.667
Worse: 41/3 = 13.667
CONTRIBUTIONS
A: (62−52.667)²/52.667 = 1.6540 (28−33.667)²/33.667 = 0.9538
(10−13.667)²/13.667 = 0.9837
B: (55−52.667)²/52.667 = 0.1034 (33−33.667)²/33.667 = 0.0132
(12−13.667)²/13.667 = 0.2033
C: (41−52.667)²/52.667 = 2.5842 (40−33.667)²/33.667 = 1.1917
(19−13.667)²/13.667 = 2.0813
────────────────────
χ² = 9.7686
df = (3−1)(3−1) = 4
critical χ²(0.05, 4) = 9.4877
9.7686 > 9.4877 → REJECT H₀
p-value = P(χ²₄ > 9.7686) = 0.0446
CONCLUSION
There is (just) sufficient evidence at the 5% level that the clinics
differ in outcome distribution. Clinic C contributes most of the
statistic: fewer "Improved" (41 vs. 52.7) and more "Worse"
(19 vs. 13.7) than expected.
p = 0.045 is a borderline result. Report it as such rather than as
a firm finding, and follow up with more data before acting on it.
Cramér's V = √(9.7686/(300 × 2)) = √0.016281 = 0.1276 — small.
tab <- matrix(c(62,28,10, 55,33,12, 41,40,19), nrow = 3, byrow = TRUE,
dimnames = list(clinic = c("A","B","C"),
outcome = c("Improved","No change","Worse")))
chisq.test(tab) # X-squared = 9.7686, df = 4, p-value = 0.04456
round(chisq.test(tab)$expected, 3)
round(chisq.test(tab)$residuals^2, 3)
barplot(prop.table(tab, 1), beside = TRUE,
col = c("#5B2A86","#8A5FBF","#0FA3A3"), legend.text = rownames(tab))
Q5: A 2 × 2 table with small counts¶
A pilot study: 9 of 12 patients on treatment improved; 4 of 11 on control improved. Test at α = 0.05.
Solution
| Improved | Not improved | Total | |
|---|---|---|---|
| Treatment | 9 | 3 | 12 |
| Control | 4 | 7 | 11 |
| Total | 13 | 10 | 23 |
EXPECTED
Treatment / Improved = 12 × 13/23 = 6.783
Treatment / Not = 12 × 10/23 = 5.217
Control / Improved = 11 × 13/23 = 6.217
Control / Not = 11 × 10/23 = 4.783
CONDITION CHECK: all E ≥ 5? The smallest is 4.783 < 5. ✗ MARGINAL
ORDINARY CHI-SQUARE (no correction)
χ² = (9−6.783)²/6.783 + (3−5.217)²/5.217
+ (4−6.217)²/6.217 + (7−4.783)²/4.783
= 0.7246 + 0.9421 + 0.7906 + 1.0289
= 3.4862
df = 1, critical 3.8415, p = 0.0619 → fail to reject
WITH YATES' CONTINUITY CORRECTION (R's default on 2×2)
χ²_Yates = 2.0913, p = 0.1481 → fail to reject
FISHER'S EXACT TEST — the right tool here
p = 0.0995 (two-sided) → fail to reject
ALL THREE AGREE: not significant at α = 0.05.
WHY THE THREE DIFFER SO MUCH
With n = 23 the chi-square approximation to the exact distribution
is poor. Yates' correction over-corrects; Fisher's test computes the
exact hypergeometric probability and needs no approximation.
RECOMMENDATION for any 2×2 table with an expected count below 5:
report FISHER'S EXACT TEST.
Note also the practical picture: 75% improved on treatment vs. 36% on
control. That is a large apparent effect — the study is simply too
small to establish it. Compute the sample size needed and run a
proper trial.
tab <- matrix(c(9, 3, 4, 7), nrow = 2, byrow = TRUE)
chisq.test(tab) # Yates correction ON by default
chisq.test(tab, correct = FALSE) # uncorrected
fisher.test(tab) # EXACT — use this one
chisq.test(tab)$expected # shows the 4.78 cell
stats.chi2_contingency([[9,3],[4,7]]) # Yates by default
stats.chi2_contingency([[9,3],[4,7]], correction=False)
stats.fisher_exact([[9,3],[4,7]]) # exact
Q6: Build the table from raw data¶
You have a data frame with columns gender and product. Produce the contingency table, the test, and the row percentages.
Solution
' 1. Insert ▸ PivotTable
' Rows = gender
' Columns = product
' Values = product (set to Count)
' 2. Copy the interior counts (WITHOUT the Grand Total row/column)
' to a clean block, add margins with SUM.
' 3. Build the expected table: =$E2*B$5/$E$5
' 4. =CHISQ.TEST(observed_range, expected_range)
'
' Row percentages: right-click a PivotTable value ▸
' Show Values As ▸ % of Row Total
'
' Straight from raw data without a pivot:
=COUNTIFS($A:$A, $F2, $B:$B, G$1)
tab <- table(df$gender, df$product)
addmargins(tab)
test <- chisq.test(tab)
test
test$expected
round(prop.table(tab, 1), 3) # row percentages
round(test$residuals^2, 3) # contributions
# Check the condition before trusting the result
any(test$expected < 5)
tab = pd.crosstab(df["gender"], df["product"])
tab_with_margins = pd.crosstab(df["gender"], df["product"], margins=True)
chi2, p, dof, expected = stats.chi2_contingency(tab)
pd.DataFrame(expected, index=tab.index, columns=tab.columns).round(3)
pd.crosstab(df["gender"], df["product"], normalize="index").round(3)
(expected < 5).any()
Q7: Independence vs. homogeneity¶
Explain the difference using two study designs that produce the same 2 × 2 table.
Solution
THE SAME TABLE, TWO DIFFERENT STUDIES
DESIGN 1 — TEST OF INDEPENDENCE
Draw ONE random sample of 200 people. Record TWO variables on each:
smoker (yes/no) and has-asthma (yes/no).
Nobody decides in advance how many smokers there will be —
BOTH margins are random.
H₀: smoking and asthma are INDEPENDENT
Question: "are these two variables related?"
DESIGN 2 — TEST OF HOMOGENEITY
Deliberately recruit 100 smokers AND 100 non-smokers. Record whether
each has asthma.
The ROW TOTALS ARE FIXED BY DESIGN — you chose 100 and 100.
H₀: the two POPULATIONS have the same asthma rate
Question: "do these groups differ?"
WHAT IS IDENTICAL
• expected counts: row × col / grand
• the χ² statistic
• df = (r−1)(c−1)
• the p-value and the decision
WHAT DIFFERS
• the wording of H₀ and of the conclusion
• what you may generalize to: Design 2 says nothing about the
PREVALENCE of smoking, because you fixed it yourself.
PRACTICAL UPSHOT: software cannot tell them apart — chisq.test() gives
the same output either way. YOU supply the interpretation from the design.
Q8: Effect size and sample size¶
A 2 × 2 table shows χ² = 4.2 with n = 500. Another shows χ² = 4.2 with n = 50. Compare.
Solution
BOTH have df = 1 and the same p-value:
p = P(χ²₁ > 4.2) = 0.0404 → both reject at α = 0.05
BUT THE ASSOCIATIONS ARE COMPLETELY DIFFERENT IN STRENGTH:
n = 500: φ = √(4.2/500) = √0.0084 = 0.0917 → NEGLIGIBLE
n = 50: φ = √(4.2/50) = √0.084 = 0.2898 → SMALL-to-MEDIUM
(for a 2×2 table, Cramér's V = φ)
INTERPRETATION
With n = 500, a trivially weak association reaches significance
because the test has a lot of power. With n = 50, the SAME statistic
reflects a genuinely noticeable pattern.
χ² alone tells you only whether the pattern is distinguishable from
noise — it conflates EFFECT SIZE with SAMPLE SIZE.
ALWAYS REPORT
χ², df, p, n, AND an effect size (φ or Cramér's V), plus the
percentage table so the reader can see the pattern for themselves.
BENCHMARKS for Cramér's V with min(r−1, c−1) = 1:
0.10 small · 0.30 medium · 0.50 large
⬅️ Previous: 12-01: Exercises — Chi-Square Goodness-of-Fit Test ➡️ Next: 12-03: Exercises — One-Way ANOVA