Contingency Tables Method Card Template (Statistics, R and Python)
Chi-square goodness of fit and independence, standardised residuals, Yates defaults, Fisher's exact, McNemar, Cramér's V, phi, odds ratio and relative risk, Simpson's paradox
사용법: Use when both variables are categorical and you have a table of counts: survey cross-tabs, pass/fail by group, before/after yes/no answers. Fill the question and data table first, check expected counts, then follow the fully solved study-place example, the half-solved 2 x 2 workshop example and the unsolved bakery exercise. The main pitfall is not stating the test version: R and SciPy both apply the Yates correction to 2 x 2 tables by default, and statsmodels' McNemar defaults to the exact test.
미리보기
Contingency Tables Method Card: [Your study question or course]
A method card for categorical data in tables of counts: the chi-square goodness-of-fit test, the chi-square test of independence with expected counts and standardised residuals, the Yates continuity correction and how R and Python apply it by default, Fisher's exact test, McNemar's test for paired proportions, and the measures of association you should report (phi, Cramér's V, odds ratio and relative risk with confidence intervals). It closes with Simpson's paradox, the classic trap of collapsing a table over a third variable. It is for students preparing for a statistics exam, master's and PhD students with survey or classification data, and analysts who count things. It speeds up learning by putting the decisions in order (which table, which test, which effect size, which sentence), by showing one example fully solved, one half solved and one left for you, and by ending with self-test questions whose answers sit in a separate key. The worked examples use fictional data; replace them with your own. Source basis: Pearson (1900), Fisher (1922, 1935), Yates (1934), McNemar (1947), Cramér (1946), Agresti's categorical data texts (by name), the NIST/SEMATECH e-Handbook of Statistical Methods, and the R and SciPy/statsmodels documentation, all written up in original words and examples.
Document control
| Field | Value |
|---|---|
| Course or project | [STAT 101 / thesis chapter 4 / client survey] |
| Learner or analyst | [Your name] |
| Data set | [Lakemoor University study-location survey (fictional) / your data set and source] |
| Software and versions | R 4.5.1 (stats); Python 3.14 with numpy 2.5.4, scipy 1.18.1, statsmodels 0.15.0 |
| Date | YYYY-MM-DD |
| Version | [0.1] |
When to use it
One-sentence rule: use a chi-square or exact test on a contingency table when every unit is counted in exactly one cell, the variables are categorical, and you want to know whether the observed counts differ from what a hypothesis (a fixed distribution, or independence of two variables) predicts.
There are four common situations, and the design decides the test:
- Goodness of fit: one categorical variable, compared with a fixed set of proportions (equal days of the week, a genetic ratio, last year's market shares).
- Test of independence (or homogeneity): two categorical variables measured on the same independent units, for example study location by year of study. The arithmetic is identical whether you sampled one group and classified it twice (independence) or sampled separate groups and classified each once (homogeneity of proportions).
- Fisher's exact test: the same question as independence, but computed exactly from the hypergeometric distribution. Use it when expected counts are small, especially in a 2 x 2 table.
- McNemar's test: the same units classified twice on a yes/no outcome (before and after, two raters, matched pairs). It only uses the pairs that changed.
| Use when | Do not use when |
|---|---|
| Outcome and predictor are both categorical (nominal or ordinal treated as nominal) | The outcome is a continuous measurement: use a t-test or ANOVA |
| Each unit contributes exactly one count to one cell | The same person appears in several cells (repeated choices, multiple responses): the counts are not independent |
| Expected counts are mostly 5 or more (chi-square) | Expected counts are small: use Fisher's exact test or an exact or Monte Carlo p-value |
| The table holds raw counts | The table holds percentages, means or rates: convert back to counts first |
| Pairs of yes/no answers from the same units (McNemar) | Paired data analysed as if independent: that wastes the pairing and gives the wrong standard error |
Alternatives. For an ordered categorical variable, a trend test (Cochran-Armitage) or an ordinal model keeps the order information. For more than two variables, or when you need to adjust for covariates, use logistic regression (see academic/stats-logistic-regression) or a log-linear model. For a single proportion or two proportions, the same question is often clearer as a proportion test with a confidence interval (academic/stats-proportion-tests). For stratified 2 x 2 tables, the Cochran-Mantel-Haenszel test combines strata without the Simpson's paradox trap.
The question and the data
Research question (Example A). At Lakemoor University (fictional), is the place where students mainly study (library, home, café) related to their year of study (first year or final year)?
| Element | Example A | Your study |
|---|---|---|
| Unit | One student, surveyed once | [ ] |
| Variable 1 (rows) | Year of study: First, Final (nominal, 2 levels) | [ ] |
| Variable 2 (columns) | Main study place: Library, Home, Café (nominal, 3 levels) | [ ] |
| Sampling | Random sample of 120 students from the enrolment list | [ ] |
| Design | Cross-sectional; each student in exactly one cell | [ ] |
| Hypotheses | H0: study place is independent of year. H1: it is not | [ ] |
Observed counts (the inline data set used in all code below):
| Year | Library | Home | Café | Row total |
|---|---|---|---|---|
| Example: First | 32 | 18 | 12 | 62 |
| Example: Final | 16 | 34 | 8 | 58 |
| Column total | 48 | 52 | 20 | 120 |
Row percentages tell the story before any test: 52% of first-year students mainly study in the library against 28% of final-year students, while 29% of first-years study at home against 59% of final-years. The café share is similar (19% and 14%). The chart below shows the same table as stacked percentages: compare bar segments, not bar heights.
Assumptions and how to check each
| Assumption | How to check | R | Python | If violated |
|---|---|---|---|---|
| Each unit counted once (independence) | Design: one row of raw data per unit; table total equals number of units | sum(tab) == nrow(raw) | tab.sum() == len(raw) | Paired: McNemar. Clustered: logistic mixed model or cluster-robust methods |
| Counts, not percentages | Table entries are integers that sum to n | all(tab == round(tab)) | np.all(tab == tab.round()) | Rebuild counts from the raw data |
| Expected counts large enough | No expected count below 1, and at most 20% below 5 (Cochran's rule of thumb) | chisq.test(tab)$expected | chi2_contingency(tab).expected_freq | Fisher's exact test; simulate.p.value = TRUE in R; merge sparse categories only if it makes substantive sense |
| Categories fixed before seeing data | Analysis plan lists categories | Plan document | Plan document | Report merging decisions as post hoc |
| Mutually exclusive, exhaustive categories | Every unit fits one and only one cell | table(raw$x, useNA = "ifany") | pd.crosstab(..., dropna=False) | Add an "Other" or "Missing" category and report it |
The expected-count rule is about expected counts under H0, not observed counts. A table can have an observed zero and still be fine for chi-square if the expected count in that cell is large. In Example A the smallest expected count is 9.67, so the chi-square approximation is safe.
How it works
Plain language. If the two variables were unrelated, each row would split across the columns in the same proportions as the whole sample. That gives an expected count for every cell: row total times column total divided by the grand total. The chi-square statistic adds up, over all cells, how far each observed count is from its expected count, scaled by the expected count so that a gap of 5 in a cell expecting 10 counts more than a gap of 5 in a cell expecting 100. A large total means the table is far from independence. Under H0 and with large enough expected counts, the statistic follows a chi-square distribution whose degrees of freedom count how many cells are free to vary once the margins are fixed.
The chi-square test answers only "is there some association?". It does not say where. Two tools do that: the row percentages and the standardised (adjusted) residuals. A standardised residual is roughly a z-score for one cell: values beyond about ±2 mark the cells that drive the result (with many cells, use a stricter cut-off such as a Bonferroni-adjusted one).
Formulas. For an r x c table with observed counts , row totals , column totals and grand total :
Standardised (adjusted) residual for one cell:
Goodness of fit with categories and hypothesised proportions : and (minus one more for every parameter estimated from the data).
Effect sizes. Cramér's V scales chi-square to the range 0 to 1; phi is its signed 2 x 2 special case:
For a 2 x 2 table with cells (first row) and (second row), where the first column is the event:
A 95% CI is , and the same for RR.
Yates continuity correction (2 x 2 only): . McNemar with discordant counts (yes then no) and (no then yes):
| Symbol | Meaning |
|---|---|
| , | Observed and expected count in row , column |
| , , | Row total, column total, grand total (sample size n) |
| , , | Number of rows, columns, categories |
| Degrees of freedom | |
| Standardised (adjusted) residual, about N(0, 1) under H0 | |
| , | Cramér's V, phi coefficient |
| , | Odds ratio, relative risk (risk ratio) |
| , (McNemar) | Discordant pair counts |
Goodness of fit in code
Harbour City Library (fictional) logged book returns over one week: Monday 42, Tuesday 30, Wednesday 28, Thursday 35, Friday 45 (n = 180). Under H0 every weekday is equally likely, so each expected count is 180 / 5 = 36. By hand: with df = 4.
returns <- c(Mon = 42, Tue = 30, Wed = 28, Thu = 35, Fri = 45)
chisq.test(returns, p = rep(1/5, 5))
import numpy as np
from scipy import stats
print(stats.chisquare(np.array([42, 30, 28, 35, 45]))) # equal expected counts by default
(output abridged)
R: X-squared = 6.0556, df = 4, p-value = 0.195
Python: Power_divergenceResult(statistic=np.float64(6.055555555555555), pvalue=np.float64(0.19503758921047))
The returns do not differ detectably from an even spread, χ²(4) = 6.06, p = .195. "Not detectably different" is not "equal": with n = 180 the test has limited power to detect a modest Friday peak.
Paired proportions: McNemar
Fifty Lakemoor students answered the same p-value question before and after a lecture. Cross-classify each student by their before and after answers:
| Before \ After | Correct | Wrong |
|---|---|---|
| Correct | 18 | 4 |
| Wrong | 15 | 13 |
Only the 19 students who changed carry information about change: 15 improved and 4 got worse. The question is whether 15 versus 4 is more lopsided than a fair coin would give.
mc <- matrix(c(18, 4, 15, 13), nrow = 2, byrow = TRUE)
mcnemar.test(mc) # continuity-corrected by default
mcnemar.test(mc, correct = FALSE)
binom.test(4, 19) # exact version
import numpy as np
from statsmodels.stats.contingency_tables import mcnemar
mc = np.array([[18, 4], [15, 13]])
print(mcnemar(mc)) # exact=True by default
print(mcnemar(mc, exact=False, correction=True))
(output abridged)
R mcnemar.test (corrected): McNemar's chi-squared = 5.2632, df = 1, p-value = 0.02178
R mcnemar.test (correct = FALSE): McNemar's chi-squared = 6.3684, df = 1, p-value = 0.01162
R binom.test(4, 19): p-value = 0.01921
Python mcnemar() default (exact): statistic 4.0, pvalue 0.0192108
Python exact=False, correction: statistic 5.2631579, pvalue 0.0217815
Different defaults: R's mcnemar.test gives the continuity-corrected chi-square (p = .022), while statsmodels'
mcnemar defaults to the exact binomial test (p = .019, and its "statistic" is the smaller discordant count, 4).
Always state which version you report. With fewer than about 25 discordant pairs (a rule of thumb), prefer the exact
version.
Simpson's paradox
Lakemoor's tutoring centre compares two tutors by pass rate. Tutor A mostly took students on the hard course; Tutor B mostly took students on the easy course.
| Course | Tutor A passed / students | Tutor A rate | Tutor B passed / students | Tutor B rate |
|---|---|---|---|---|
| Example: Easy course | 18 / 20 | 90.0% | 50 / 60 | 83.3% |
| Example: Hard course | 30 / 60 | 50.0% | 8 / 20 | 40.0% |
| Example: Both combined | 48 / 80 | 60.0% | 58 / 80 | 72.5% |
Tutor A has the higher pass rate in each course, yet the lower rate overall. The reversal happens because course
difficulty is related to both tutor and pass rate (a confounder): the combined table mixes different case loads. The
lesson is not that the combined table is "wrong", but that collapsing over a variable that is associated with both of
the others answers a different question. Analyse stratified tables (Cochran-Mantel-Haenszel, mantelhaen.test in R,
StratifiedTable in statsmodels) or use logistic regression with the confounder as a covariate.
Worked example A (fully solved): chi-square test of independence
Hypotheses. H0: main study place is independent of year of study (the study-place distribution is the same for first-year and final-year students). H1: the distributions differ. α = 0.05.
Checks. Each of the 120 students appears once. The smallest expected count is 9.67 (Final, Café), so all expected counts exceed 5 and the chi-square approximation is appropriate. The table is 2 x 3, so df = (2 − 1)(3 − 1) = 2 and no continuity correction applies in either R or Python.
R code and output.
loc <- matrix(c(32, 18, 12,
16, 34, 8), nrow = 2, byrow = TRUE,
dimnames = list(year = c("First", "Final"),
place = c("Library", "Home", "Cafe")))
addmargins(loc)
ind <- chisq.test(loc)
ind
round(ind$expected, 2)
round(ind$stdres, 2) # standardised (adjusted) residuals
V <- sqrt(unname(ind$statistic) / (sum(loc) * (min(dim(loc)) - 1)))
cat("Cramer's V =", round(V, 3), "\n")
(output abridged: addmargins table omitted, labels in parentheses added)
Pearson's Chi-squared test
data: loc
X-squared = 10.935, df = 2, p-value = 0.004221
place (expected counts)
year Library Home Cafe
First 24.8 26.87 10.33
Final 23.2 25.13 9.67
place (standardised residuals)
year Library Home Cafe
First 2.68 -3.27 0.82
Final -2.68 3.27 -0.82
Cramer's V = 0.302
Python code and output.
import numpy as np
from scipy import stats
from scipy.stats.contingency import association
from statsmodels.stats.contingency_tables import Table
loc = np.array([[32, 18, 12],
[16, 34, 8]])
res = stats.chi2_contingency(loc) # correction only applies when df = 1
print(f"chi2 = {res.statistic:.3f}, df = {res.dof}, p = {res.pvalue:.4f}")
print(np.round(res.expected_freq, 2))
print(np.round(Table(loc).standardized_resids, 2))
print("Cramer's V =", round(association(loc, method="cramer"), 3))
chi2 = 10.935, df = 2, p = 0.0042
[[24.8 26.87 10.33]
[23.2 25.13 9.67]]
[[ 2.68 -3.27 0.82]
[-2.68 3.27 -0.82]]
Cramer's V = 0.302
By hand. Expected count for First and Library: 62 x 48 / 120 = 24.8. The six cell contributions are:
| Cell | O | E | (O − E)² / E |
|---|---|---|---|
| First, Library | 32 | 24.80 | 51.84 / 24.80 = 2.090 |
| First, Home | 18 | 26.87 | 78.62 / 26.87 = 2.926 |
| First, Café | 12 | 10.33 | 2.78 / 10.33 = 0.269 |
| Final, Library | 16 | 23.20 | 51.84 / 23.20 = 2.234 |
| Final, Home | 34 | 25.13 | 78.62 / 25.13 = 3.128 |
| Final, Café | 8 | 9.67 | 2.78 / 9.67 = 0.287 |
| Total | 10.935 |
Effect size. Cramér's V = √(10.935 / (120 x 1)) = 0.302. For a table with min(r, c) − 1 = 1, Cohen's (1988) rough benchmarks for w (equal to V here) are 0.1 small, 0.3 medium, 0.5 large, so this is a medium association. Treat benchmarks as a last resort; the row percentages (52% versus 28% library users) are the more meaningful effect. A confidence interval for V needs a bootstrap or a noncentral chi-square inversion (see academic/stats-resampling-methods); at minimum, report the percentage differences with their own intervals.
Interpretation. Study place is associated with year of study. The standardised residuals show where: first-year students are over-represented in the library (z = 2.68) and under-represented at home (z = −3.27), and the reverse holds for final-year students. The café cells (z = ±0.82) contribute little. The data are cross-sectional, so this is an association, not evidence that moving into the final year causes students to leave the library.
APA-style report. Main study place differed by year of study, χ²(2, N = 120) = 10.94, p = .004, Cramér's V = .30. First-year students studied mainly in the library more often than final-year students (52% vs 28%), whereas final-year students more often studied at home (59% vs 29%); standardised residuals for these cells exceeded |2.6|.
Worked example B (half solved): 2 x 2 table, Yates, Fisher, odds ratio
Question. Eighty Lakemoor students in STAT 101 chose whether to attend an optional revision workshop. Is attendance associated with passing the exam?
| Workshop | Pass | Fail | Total |
|---|---|---|---|
| Example: Yes | 34 | 6 | 40 |
| Example: No | 26 | 14 | 40 |
| Total | 60 | 20 | 80 |
Step 1: expected counts. Every expected count is row total x column total / n. For Yes and Pass: 40 x 60 / 80 = [ ]. For Yes and Fail: [ ]. Smallest expected count: [ ]. Is the chi-square approximation acceptable? [ ]
Step 2: run the tests.
ws <- matrix(c(34, 6, 26, 14), nrow = 2, byrow = TRUE,
dimnames = list(workshop = c("Yes", "No"), result = c("Pass", "Fail")))
chisq.test(ws) # 2 x 2: Yates correction applied by default
chisq.test(ws, correct = FALSE)
fisher.test(ws)
import numpy as np
from scipy import stats
from statsmodels.stats.contingency_tables import Table2x2
ws = np.array([[34, 6], [26, 14]])
yates = stats.chi2_contingency(ws) # correction=True is the default
plain = stats.chi2_contingency(ws, correction=False)
fe = stats.fisher_exact(ws)
t22 = Table2x2(ws)
print(f"Py Yates: chi2 = {yates.statistic:.4f}, p = {yates.pvalue:.4f}")
print(f"Py plain: chi2 = {plain.statistic:.4f}, p = {plain.pvalue:.4f}")
print(f"Py Fisher: OR (sample) = {fe.statistic:.3f}, p = {fe.pvalue:.4f}")
print(f"Py Table2x2: OR = {t22.oddsratio:.3f}, CI {np.round(t22.oddsratio_confint(), 3)}; "
f"RR = {t22.riskratio:.3f}, CI {np.round(t22.riskratio_confint(), 3)}")
(output abridged: R lines condensed from the three printouts)
R Yates: X-squared = 3.2667, df = 1, p-value = 0.0707
R uncorrected: X-squared = 4.2667, df = 1, p-value = 0.03887
R Fisher: p-value = 0.06917; odds ratio 3.008656; 95% CI 0.9298537 10.9296717
Py Yates: chi2 = 3.2667, p = 0.0707
Py plain: chi2 = 4.2667, p = 0.0389
Py Fisher: OR (sample) = 3.051, p = 0.0692
Py Table2x2: OR = 3.051, CI [1.032 9.022]; RR = 1.308, CI [1.006 1.699]
Step 3: read the output. The uncorrected chi-square gives p = [ ], below 0.05; the Yates-corrected chi-square
gives p = [ ] and Fisher's exact test gives p = [ ], both above 0.05. Note the defaults: in a 2 x 2 table both R
chisq.test and SciPy chi2_contingency apply the Yates correction unless you switch it off.
Step 4: effect sizes. Sample odds ratio = (34 x 14) / (6 x 26) = [ ]. Relative risk of passing = (34/40) / (26/40)
= [ ]. Phi = (34 x 14 − 6 x 26) / √(40 x 40 x 60 x 20) = [ ]. Why does R's fisher.test report an odds ratio of 3.01
instead of 3.05? [ ]
Step 5: report. Write one sentence that states the test you chose and why, the odds ratio with its 95% CI, and a cautious interpretation that respects the fact that students chose whether to attend: [ ]
Exercise C (unsolved): shift and defective loaves
Brightwell Bakery (fictional) inspected 100 loaves from each of three shifts and recorded whether each loaf was defective.
| Shift | OK | Defective | Total |
|---|---|---|---|
| Example: Early | 88 | 12 | 100 |
| Example: Day | 90 | 10 | 100 |
| Example: Night | 72 | 28 | 100 |
Tasks:
- State H0 and H1 in words.
- Compute the expected counts and check the expected-count rule.
- Compute χ², df and the p-value; use R or Python to check.
- Compute Cramér's V and the standardised residuals. Which cells drive the result?
- Write an APA-style sentence and one practical recommendation for the bakery.
Reading software output
| Output line | What it means | What to report |
|---|---|---|
Pearson's Chi-squared test vs ... with Yates' continuity correction | Which statistic R computed; the second appears only for 2 x 2 tables with correct = TRUE (default) | Name the version in your methods |
X-squared = 10.935 / chi2 = 10.935 | The chi-square statistic | χ²(df, N = n) = value, two decimals |
df = 2 / res.dof | Degrees of freedom, (r − 1)(c − 1) | In brackets after χ² |
p-value = 0.004221 | Probability of a statistic at least this large if H0 is true | p = .004 (three decimals, or p < .001) |
$expected / expected_freq | Expected counts under H0 | The smallest one, in the methods, to justify the test |
$stdres / standardized_resids | Adjusted residuals, about N(0, 1) under H0 | Cells beyond about ±2 (or a Bonferroni cut-off) |
$residuals (R) | Pearson residuals (O − E)/√E; their squares sum to χ² | Rarely reported; not the same as stdres |
fisher.test odds ratio | Conditional maximum-likelihood OR, not the sample OR ad/bc | Report which OR you give, with the CI |
scipy.stats.fisher_exact statistic | The sample (unconditional) OR ad/bc | Same caution |
statsmodels mcnemar statistic | With exact=True (default) this is min(b, c), not a chi-square | Report the exact p-value and the discordant counts |
Reporting template
Fill-in sentence (independence). [Variable 1] was [associated / not detectably associated] with [variable 2], χ²([df], N = [n]) = [value], p = [.xxx], Cramér's V = [.xx]. [Describe the largest percentage difference and the cells with standardised residuals beyond |2|.]
Fill-in sentence (2 x 2). [Group 1] had [higher / lower] odds of [outcome] than [group 2], OR = [x.xx], 95% CI [[lo], [hi]]; [risk 1]% vs [risk 2]%, RR = [x.xx], 95% CI [[lo], [hi]]; [test name], p = [.xxx].
Fill-in sentence (McNemar). Of [n] [units], [b] changed from [A] to [B] and [c] from [B] to [A]; [exact McNemar / corrected McNemar] p = [.xxx].
| Row category | Column 1 n (%) | Column 2 n (%) | Column 3 n (%) | Total n |
|---|---|---|---|---|
| Example: First year | 32 (52%) | 18 (29%) | 12 (19%) | 62 |
| Example: Final year | 16 (28%) | 34 (59%) | 8 (14%) | 58 |
| [Category] | [ ] | [ ] | [ ] | [ ] |
Results checklist:
- Table of counts with row (or column) percentages, chosen to match the question
- Test named, including the correction or exact method used, and software defaults stated
- Smallest expected count reported (or the reason an exact test was used)
- Statistic, df, N and p in APA style
- Effect size: Cramér's V or phi, and for 2 x 2 tables the OR or RR with 95% CI
- Cells driving the association identified (residuals or percentage differences)
- Causal language avoided unless the design was an experiment
- Possible confounders considered (Simpson's paradox check)
Common mistakes and misconceptions
- Running chi-square on percentages. The statistic depends on the counts; a table of percentages always looks like n = 100 and gives a wrong p-value.
- Counting the same person twice. Multiple-response questions ("tick all that apply") break independence; each unit must contribute one count.
- Checking observed instead of expected counts. The rule of thumb is about expected counts; an observed 0 is not by itself a problem.
- Not knowing the default. R's
chisq.testand SciPy'schi2_contingencyboth apply Yates to 2 x 2 tables by default, so two analysts can report p = .071 and p = .039 for the same table. State the version. - "Significant chi-square means a strong relationship." With a large n, a trivial association is significant; report V or an OR and the percentages.
- Reading the chi-square as directional. It is an omnibus test; use residuals or percentages to say where the association is.
- Confusing odds ratio and relative risk. OR = 3.05 does not mean "three times as likely" to pass; the RR here is only 1.31. The OR approximates the RR only when the outcome is rare.
- Using an independent test for paired data. Before/after answers from the same students need McNemar, not a chi-square of "before" versus "after" totals.
- Ignoring a confounder. Simpson's paradox can reverse a comparison; check whether a third variable is linked to both of yours.
- "p = .004 means a 0.4% chance H0 is true." The p-value assumes H0 is true; it is not the probability of H0.
Self-test questions
- A 3 x 4 table has n = 200. How many degrees of freedom does the chi-square test of independence have?
- Why can a 2 x 2 table give p = .039 in one report and p = .071 in another with the same data and the same software?
- A cell has an observed count of 2 and an expected count of 8.5. Is the chi-square test invalid? Explain.
- Twenty people rated two logos as "like" or "dislike" (each person rated both). Which test, and which counts does it use?
- In a study of a rare side effect (1% vs 2%), the OR is 2.02. What is the RR, roughly, and why are they close?
- Explain Simpson's paradox to a friend in two sentences, using an example that is not in this card.
My notes and next steps
| Item | My entry |
|---|---|
| Confidence (1-5) in choosing the right test | [ ] |
| Confidence (1-5) in reading residuals and effect sizes | [ ] |
| What I still find confusing | [ ] |
| Questions for my tutor or supervisor | [ ] |
| Mistakes logged in academic/stats-error-log | [Link or entry IDs] |
| Next review date (spacing) | YYYY-MM-DD |
| Next template to study | [academic/stats-proportion-tests or academic/stats-logistic-regression] |
Sources and further reading
Structure aligned with the method-card shape of this statistics library; methods based on the original papers and the public-domain NIST/SEMATECH e-Handbook, written in original words with fictional examples.
- Pearson, K. (1900). On the criterion that a given system of deviations from the probable in the case of a correlated system of variables is such that it can be reasonably supposed to have arisen from random sampling. Philosophical Magazine, Series 5, 50(302), 157–175.
- Fisher, R. A. (1922). On the interpretation of χ² from contingency tables, and the calculation of P. Journal of the Royal Statistical Society, 85(1), 87–94.
- Fisher, R. A. (1935). The Design of Experiments. Oliver and Boyd.
- Yates, F. (1934). Contingency tables involving small numbers and the χ² test. Supplement to the Journal of the Royal Statistical Society, 1(2), 217–235.
- McNemar, Q. (1947). Note on the sampling error of the difference between correlated proportions or percentages. Psychometrika, 12(2), 153–157.
- Cramér, H. (1946). Mathematical Methods of Statistics. Princeton University Press.
- Simpson, E. H. (1951). The interpretation of interaction in contingency tables. Journal of the Royal Statistical Society, Series B, 13(2), 238–241.
- Cohen, J. (1988). Statistical Power Analysis for the Behavioral Sciences (2nd ed.). Lawrence Erlbaum Associates.
- Agresti, A. Categorical Data Analysis and An Introduction to Categorical Data Analysis (Wiley): further reading, by name.
- NIST/SEMATECH e-Handbook of Statistical Methods, section 1.3.5.15 Chi-Square Goodness-of-Fit Test, https://www.itl.nist.gov/div898/handbook/ (accessed 2026-10-25). Public domain.
- R Core Team. Documentation for
chisq.test,fisher.test,mcnemar.test,mantelhaen.test(R 4.5.1). - SciPy documentation for
scipy.stats.chi2_contingency,chisquare,fisher_exact,contingency.association(1.18.1); statsmodels documentation forTable,Table2x2,mcnemar,StratifiedTable(0.15.0). - Further reading (by name only): de Smith, M. J. (2024). Statistical Analysis Handbook (2024 edition, StatsRef); OpenIntro Statistics; APA Publication Manual (7th ed.) and APA JARS-Quant.
Answer key
Worked example B.
- Step 1: Yes and Pass 30; Yes and Fail 40 x 20 / 80 = 10; No row: 30 and 10. Smallest expected count 10, so the chi-square approximation is acceptable (Fisher's test is still a fine choice for a 2 x 2 table).
- Step 3: uncorrected p = .039; Yates p = .071; Fisher p = .069.
- Step 4: OR = 476 / 156 = 3.05 (95% CI 1.03 to 9.02); RR = 0.85 / 0.65 = 1.31 (95% CI 1.01 to 1.70); phi = (476 −
156) / √1,920,000 = 320 / 1385.6 = 0.23. R's
fisher.testreports the conditional maximum-likelihood estimate of the OR (3.01) with an exact CI [0.93, 10.93], not the sample OR ad/bc. - Step 5 (one acceptable version): Students who attended the workshop passed more often (85% vs 65%), OR = 3.05, 95% CI [1.03, 9.02], but Fisher's exact test did not reach significance, p = .069; because attendance was self-selected, motivated students may both attend and pass, so the association is not evidence that the workshop caused higher pass rates. Note how the Wald CI for the OR and the exact Fisher test give slightly different messages near the boundary: choose the method before you see the data.
Exercise C. H0: the defect rate is the same on all three shifts. Expected counts: 83.3 OK and 16.7 defective for every shift (overall defect rate 50/300). Chi-square: χ²(2, N = 300) = 14.02, p < .001 (p = 0.0009). Cramér's V = √(14.016 / 300) = 0.22. Standardised residuals for "Defective": Early −1.53, Day −2.19, Night 3.72. The Night shift drives the result (28% defective vs 12% and 10%). Recommendation: investigate the night process (oven calibration, staffing, proofing time) before blaming random variation.
Self-test.
- df = (3 − 1)(4 − 1) = 6.
- One report used the Yates continuity correction (the default for 2 x 2 tables in both R and SciPy) and the other switched it off.
- No. The rule concerns expected counts; 8.5 is above 5. Check the other cells' expected counts.
- McNemar's test (exact version for small numbers). It uses only the discordant pairs: people who liked one logo and disliked the other.
- RR = 2 / 1 = 2.0. When the outcome is rare, odds (p / (1 − p)) are almost equal to the probability, so the OR is close to the RR.
- Example: a hospital with better survival in every severity group can have worse survival overall if it treats far more severe cases. The comparison reverses because the third variable (severity) is linked to both the hospital and the outcome.
Revision history
| Version | Date | Author | Change |
|---|---|---|---|
| 0.1 | YYYY-MM-DD | [Your name] | First version from the template |
| [0.2] | YYYY-MM-DD | [ ] | [Replaced example data with my own] |
관련 템플릿
Statistical Analysis Plan Template (Statistics, Thesis or Project)
Statistical analysis plan for a thesis or project: estimands, hypotheses, sample size, primary model, pre-set assumption fallbacks, missing data, multiplicity, sensitivity, deviations log
학술Statistical Analysis Report Template (Statistics, Thesis or Client)
Complete statistical analysis report for a thesis results chapter or client: plain-language summary, methods, descriptives, main analyses, assumption checks, sensitivity, limitations, reproducibility appendix, reviewer checklist
학술ANCOVA Method Card Template (Statistics, R and Python)
Analysis of covariance: covariate adjustment, homogeneity of slopes, adjusted means by hand and with emmeans, ANCOVA vs change scores, Lord's paradox, reporting