Written and maintained by CASRAI Editorial Board
Last updated
A chi-squared test compares counts you observed with counts you would expect under a stated hypothesis. This page works three complete chi-squared examples by hand, with every expected count, every term of the sum, the degrees of freedom, the critical value and the decision shown, then repeats the calculations in Excel, R and Python so you can check your own numbers. The datasets are illustrative, built for teaching, and are not real study results.
Definition in one paragraph
A chi-squared (χ²) test measures how far observed category counts sit from the counts a null hypothesis predicts. In a goodness-of-fit test, the null hypothesis is that the data follow a specified distribution. In a test of independence, the null hypothesis is that two categorical variables are unrelated. Large gaps between observed and expected counts give a large χ² and a small p-value. For the underlying distribution, see the chi-square probability distribution; for the broader method, see the chi-square test guide.
Formula, with every symbol explained
χ² = Σ (O − E)² / E
- O: the observed count in a cell.
- E: the expected count in that cell if the null hypothesis is true.
- Σ: add the term across every cell.
What changes between the two tests is how E and the degrees of freedom (df) are found. The NIST/SEMATECH e-Handbook gives the following rules.
| Test | Expected count E | Degrees of freedom |
|---|---|---|
| Goodness of fit | Sample size × hypothesised proportion for the category | k − c, where k is the number of non-empty cells and c is the number of estimated parameters plus 1 |
| Independence (r × c table) | (row total × column total) / grand total | (r − 1)(c − 1) |
You reject the null hypothesis at significance level α when χ² exceeds the critical value for the right df. How to find a critical value covers the lookup.
Worked example 1: goodness of fit (is a die fair?)
A die is rolled 120 times. If it is fair, each face has probability 1/6, so each face is expected 120 × 1/6 = 20 times. The null hypothesis is that the die is fair.
| Face | O | E | O − E | (O − E)² / E |
|---|---|---|---|---|
| 1 | 15 | 20 | −5 | 25 / 20 = 1.25 |
| 2 | 25 | 20 | 5 | 25 / 20 = 1.25 |
| 3 | 22 | 20 | 2 | 4 / 20 = 0.20 |
| 4 | 18 | 20 | −2 | 4 / 20 = 0.20 |
| 5 | 24 | 20 | 4 | 16 / 20 = 0.80 |
| 6 | 16 | 20 | −4 | 16 / 20 = 0.80 |
χ² = 1.25 + 1.25 + 0.20 + 0.20 + 0.80 + 0.80 = 4.50. No parameter was estimated from the data, so c = 1 and df = 6 − 1 = 5. The critical value at α = 0.05 is 11.0705. Because 4.50 < 11.0705 (p = .480), you do not reject the null hypothesis: these counts are consistent with a fair die.
Worked example 2: test of independence in a 2 × 2 table
160 participants are split evenly between a new training programme (group A) and usual practice (group B), and each is recorded as improved or not improved. The null hypothesis is that improvement is independent of group.
| Improved | Not improved | Row total | |
|---|---|---|---|
| Group A | 48 | 32 | 80 |
| Group B | 30 | 50 | 80 |
| Column total | 78 | 82 | 160 |
Expected counts. E(A, improved) = 80 × 78 / 160 = 39; E(A, not improved) = 80 × 82 / 160 = 41; the same two values repeat for group B.
Sum. Every cell differs from its expected count by 9, so each squared difference is 81: 81/39 + 81/41 + 81/39 + 81/41 = 2.0769 + 1.9756 + 2.0769 + 1.9756 = 8.105. With r = c = 2, df = (2 − 1)(2 − 1) = 1, and the critical value at α = 0.05 is 3.8415. Since 8.105 > 3.8415 (p = .004), you reject the null hypothesis: improvement and group are associated.
The effect size for a 2 × 2 table is φ = √(χ² / n) = √(8.105 / 160) = 0.225. See Cramér’s V for tables larger than 2 × 2.
Continuity correction. SciPy applies the Yates correction only when df = 1 and correction=True, and R’s chisq.test applies it by default only to 2 × 2 tables. With the correction, this table gives χ² = 7.230, p = .007; the conclusion does not change. The uncorrected figure is reported above.
Worked example 3: a larger table (3 × 3)
300 researchers are classified by career stage and by where they mainly keep working data. The null hypothesis is that storage choice is independent of career stage.
| Institutional server | Cloud service | Local drive | Row total | |
|---|---|---|---|---|
| Doctoral | 30 | 45 | 25 | 100 |
| Postdoctoral | 42 | 38 | 20 | 100 |
| Faculty | 18 | 37 | 45 | 100 |
| Column total | 90 | 120 | 90 | 300 |
Expected counts. Each row total is 100, so E = 100 × column total / 300: 30 for the institutional server column, 40 for cloud, and 30 for local drive, in every row.
| Institutional server | Cloud service | Local drive | |
|---|---|---|---|
| Doctoral | 0 / 30 = 0.000 | 25 / 40 = 0.625 | 25 / 30 = 0.833 |
| Postdoctoral | 144 / 30 = 4.800 | 4 / 40 = 0.100 | 100 / 30 = 3.333 |
| Faculty | 144 / 30 = 4.800 | 9 / 40 = 0.225 | 225 / 30 = 7.500 |
Each entry is (O − E)² / E. The sum is 1.458 + 8.233 + 12.525 = 22.217. Here df = (3 − 1)(3 − 1) = 4, and the critical value at α = 0.05 is 9.4877. Since 22.217 > 9.4877 (p = .0002), you reject the null hypothesis. The largest single term, 7.500, is the faculty and local-drive cell (45 observed against 30 expected), so that cell contributes most to the result. Standardized residuals make this cell-by-cell reading more formal. Cramér’s V = √(22.217 / (300 × 2)) = 0.192.
A published check. The NIST/SEMATECH e-Handbook works a 3 × 4 table of 309 wafer defects across three shifts. It gets an expected count of 94 × 74 / 309 = 22.51 for one cell, χ² = 19.18 on 6 df against a critical value of 12.5916, and p = 0.00387. Use it to confirm your own implementation.
Interpretation
| What you see | What it means |
|---|---|
| χ² is larger than the critical value for your df | Reject the null hypothesis at that α |
| χ² is at or below the critical value | Do not reject; the data are consistent with the null hypothesis |
| One or two cells carry most of the sum | Those cells drive the result; inspect them first |
| Significant result in a table | The variables are associated; a χ² test does not show direction or cause |
Assumptions and common mistakes
- Use counts, not percentages. The formula divides by E, so the raw counts must go in.
- Keep expected counts large enough. The SciPy documentation states the test should be used only if observed and expected frequencies in each cell are at least 5, and suggests permutation or Monte Carlo methods for small two-way tables. Microsoft’s Excel documentation gives the same threshold of 5 as a suggestion.
- Use the right df. A goodness-of-fit test uses k − c and an independence test uses (r − 1)(c − 1); mixing them gives the wrong critical value.
- Observations must be independent. Each case belongs to exactly one cell.
How to report it (APA style)
Report the statistic with its df, the sample size and the p-value, with an italic p and no leading zero:
- Example 1: χ²(5, N = 120) = 4.50, p = .480.
- Example 2: χ²(1, N = 160) = 8.11, p = .004, φ = .23.
- Example 3: χ²(4, N = 300) = 22.22, p < .001, Cramér’s V = .19.
How to calculate chi-squared in Excel, R or Python
Excel
Put the observed counts in B2:C3 and build a matching expected-count block in B6:C7 (each cell is its row total times its column total divided by the grand total). Then:
=CHISQ.TEST(B2:C3, B6:C7) -- returns the p-value
=CHISQ.INV.RT(0.05, 1) -- critical value for df = 1
Microsoft documents that CHISQ.TEST takes the observed range and the expected range and returns the p-value. For setting up the table, see crosstabs in Excel.
R
chisq.test(c(15, 25, 22, 18, 24, 16)) # example 1, equal probabilities by default
chisq.test(matrix(c(48, 30, 32, 50), nrow = 2), correct = FALSE) # example 2
chisq.test(matrix(c(30, 42, 18, 45, 38, 37, 25, 20, 45), nrow = 3)) # example 3
The matrix is filled column by column. The returned object holds statistic, parameter (df), p.value, expected and stdres. More on this function in hypothesis testing with R.
Python
from scipy.stats import chisquare, chi2_contingency
print(chisquare([15, 25, 22, 18, 24, 16])) # statistic 4.5, p 0.480
stat, p, dof, expected = chi2_contingency([[48, 32], [30, 50]], correction=False)
print(stat, p, dof) # 8.105, 0.0044, 1
stat, p, dof, expected = chi2_contingency([[30, 45, 25], [42, 38, 20], [18, 37, 45]])
print(stat, p, dof) # 22.217, 0.00018, 4
FAQ
What is the difference between a goodness-of-fit and an independence example?
Goodness of fit has one categorical variable and compares its counts with a stated distribution; independence has two categorical variables and compares a table with the counts expected if they were unrelated.
What if an expected count is below 5?
The SciPy documentation advises using the test only when observed and expected frequencies in each cell are at least 5, and suggests permutation or Monte Carlo methods for small two-way tables.
When is the continuity correction used?
SciPy applies it only when df = 1 and correction=True. R applies it by default only to 2 × 2 tables, subtracting one half from each |O − E|.
Sources
- NIST/SEMATECH e-Handbook of Statistical Methods, 1.3.5.15 Chi-Square Goodness-of-Fit Test, itl.nist.gov/div898/handbook/eda/section3/eda35f.htm (checked 2026-10-29).
- NIST/SEMATECH e-Handbook, 7.4.5 Chi-square test of independence and homogeneity, itl.nist.gov/div898/handbook/prc/section4/prc45.htm (checked 2026-10-29).
- SciPy documentation,
scipy.stats.chi2_contingency, docs.scipy.org/doc/scipy/reference/generated/scipy.stats.chi2_contingency.html (checked 2026-10-29). - R documentation,
chisq.test, stat.ethz.ch/R-manual/R-devel/library/stats/html/chisq.test.html (checked 2026-10-29). - Microsoft Support, CHISQ.TEST function, support.microsoft.com (checked 2026-10-29).








