How to report chi-square test results in APA style
A complete results paragraph for a chi-square test of independence: counts, expected counts, the statistic, Cramer's V and the odds ratio with intervals.
Updated . Written by Matt Gencer.
On this page
A chi-square test of independence is reported with five things: the table of counts, a statement that the expected counts were large enough, the statistic with its degrees of freedom and sample size, the p-value, and an effect size with its confidence interval. Many templates stop at the third and fourth.
This post builds one complete results paragraph from a chi-square test on the birthwt data in R’s MASS package. If you are still deciding whether a chi-square test or Fisher’s exact test fits your table, start with the guide to choosing between chi-square and Fisher’s exact test; this post assumes the choice is made. For the same reporting approach applied to other tests, see the APA reporting template for every common test.
The short answer
Here is the finished sentence for the worked example, in APA style:
Low birth weight was more common among mothers recorded as smoking during pregnancy (30 of 74) than among those who did not (29 of 115), χ²(1, N = 189) = 4.24, p = .040, Cramer’s V = .16, 95% CI [.04, 1.00] (one-sided). The odds of low birth weight were about twice as high in the smoking group, OR = 2.02, 95% CI [1.08, 3.78].
The worked example
The data are the birthwt data frame in the MASS package: 189 births recorded at one hospital in 1986, originally published by Hosmer and Lemeshow. Each birth is classified on two yes-or-no variables: whether the mother was recorded as smoking during pregnancy, and whether the baby weighed less than 2,500 grams (low birth weight). That is a two-by-two table from one sample, the design a chi-square test of independence is for.
Two design points matter for the write-up. Each row is one birth and nothing links two rows, so the analysis treats observations as independent; had the same people been classified twice, the table would be paired and McNemar’s test would apply (see the McNemar test guide). And the data are observational: smoking was not assigned, so the test can show an association, not that smoking caused it.
The engine ran Pearson’s chi-square test with smoking status as rows and birth weight category as columns (method reference: chi-square test of association). No row had a missing value, so all 189 births were analysed.
Step 1: report the table of counts
The statistic says the variables are associated; only the table says which way and by how much.
| Low birth weight | Not low birth weight | Total | |
|---|---|---|---|
| Smoked during pregnancy | 30 | 44 | 74 |
| Did not smoke | 29 | 86 | 115 |
| Total | 59 | 130 | 189 |
In a paper, this goes in a table, and the text gives the key comparison in words. If you add percentages, say which way they run: percentages within each smoking group answer “how common was low birth weight in each group”, the question asked here. Never report percentages without the counts. The test line then goes in the text or in a note under the table.
Step 2: report the expected counts
The chi-square test compares the observed counts with the counts expected if the two variables were independent. Each expected count is its row total times its column total divided by the grand total, and the p-value’s large-sample approximation can be poor when some are small.
The usual guidance, which traces to Cochran (1954), is that every expected count should be at least 5 in a 2x2 table. Campbell (2007) notes that Cochran called the 5 an arbitrary choice, so treat it as a convention, not a guarantee. The engine checks it, and here it passed comfortably: the smallest expected count was 23.1, in the smoking and low birth weight cell, against the floor of 5. Passing it does not establish independent observations or a meaningful association.
What to write: one clause is enough when the check passes clearly, for example “All expected cell counts exceeded 5 (smallest 23.1).” For the full expected table, R prints it in one line:
library(MASS)
tab <- table(birthwt$smoke, birthwt$low)
chisq.test(tab)$expected # the four expected counts
Step 3: the test statistic line
The core of the report is χ²(df, N = n) = statistic, p = value. For the example: χ²(1, N = 189) = 4.24, p = .040.
Degrees of freedom. For a table with r rows and c columns, df = (r - 1)(c - 1), the count Fisher (1922) established. A 2x2 table has 1.
Sample size. N is the number of observations in the table, not the rows in your file before missing values were dropped; say how many cases were excluded and why.
The statistic, and which version it is. On a 2x2 table, R’s chisq.test() applies Yates’ continuity correction by default (Yates, 1934). The 4.24 reported here is the corrected value. Some software prints the uncorrected one (so does R with correct = FALSE), and some prints both, so a reader reproducing your number needs to know which you used. A clause in the methods (“with Yates’ continuity correction”) settles it.
There is no single right answer on whether to correct. Campbell’s (2007) simulations recommended neither Yates’ version nor plain Pearson but the “N - 1” chi-square when every expected count is at least 1, with Fisher’s exact test otherwise. Whichever you use, name it and choose it beforehand.
Formatting. The APA numbers and statistics guide asks for two decimals on chi-square, exact p-values to two or three decimals, p < .001 for anything smaller, and no leading zero for a quantity that cannot exceed 1. The engine’s p-value of 0.0396 becomes p = .040.
What the p-value says. If smoking status and low birth weight were independent in the population these births came from, a statistic at least this large would turn up in about 4% of samples of this size. It is not the probability that the variables are independent, a misreading Greenland et al. (2016) put first in their list of common misinterpretations.
Where the numbers sit in your software’s output. Whatever you use, copy the statistic, the degrees of freedom and the p-value from the same row, and take N from the table total if your output does not print it (R and SciPy do not). In R, chisq.test() prints them as X-squared, df and p-value, and its header line says whether Yates’ correction was applied. For a 2x2 table SPSS prints several rows under the crosstab test: the Pearson Chi-Square row is the uncorrected statistic and the Continuity Correction row is the Yates version, so pick the row that matches what you say in the methods. In Python, scipy.stats.chi2_contingency returns the statistic, p-value, degrees of freedom and expected counts, and applies the continuity correction only when the table has one degree of freedom (its correction argument turns that off).
Step 4: report an effect size with its interval
A p-value does not say how strong an association is, so the report needs an effect size with a confidence interval.
Cramer’s V
Cramer’s V (Cramer, 1946) works for a table of any size and runs from 0 (no association) to 1. For a 2x2 table it equals the absolute value of the phi coefficient. The engine computed V = 0.1614, with a 95% confidence interval from 0.0397 to 1. In APA style, V = .16, 95% CI [.04, 1.00].
Two details belong in the report or its methods:
- V uses the uncorrected statistic. The engine computes V with the effectsize package (Ben-Shachar et al., 2020) from the uncorrected Pearson statistic, not the Yates-corrected 4.24. For a 2x2 table, N × V² recovers the statistic V came from: 189 × 0.1614² ≈ 4.92, not 4.24. So V and the χ² in one sentence come from slightly different statistics.
- The interval is one-sided. V cannot exceed 1, so the interval defaults to one-sided with its upper limit fixed at 1. The lower limit is the informative part: values of V below 0.0397 are not compatible with these data at the 95% level. Write “(one-sided)” so no reader takes the 1 as an estimate.
The engine labels this association weak; such labels are conventions, and whether a V of this size matters belongs in the discussion.
The 2x2 measures: odds ratio, risk ratio, risk difference
For a 2x2 table the engine reports three standard measures, comparing mothers who smoked with those who did not:
| Measure | Estimate | 95% CI |
|---|---|---|
| Odds ratio | 2.022 | 1.081 to 3.783 |
| Risk ratio | 1.608 | 1.058 to 2.443 |
| Risk difference | 0.1532 | 0.01607 to 0.2904 |
Every cell is well above 5, so the engine used Wald intervals (log scale for the ratios). Which measure to report depends on how the data were sampled, as the odds ratio vs relative risk guide explains. The risk ratio and risk difference are interpretable only if the sample was not selected by the outcome; under case-control sampling, only the odds ratio is.
The dataset’s documentation does not say how the births were selected, and 59 low birth weight births out of 189 (31%) is a high share for an unselected series, so selection by outcome cannot be ruled out. The example sentence therefore leads with the odds ratio, which holds under either scheme. If your sample was drawn without regard to the outcome, the risk difference is often the clearest number: here, RD = .15, 95% CI [.02, .29].
Note the width: the odds ratio is compatible with values from just above 1 (1.081) to 3.783, so “OR = 2.02” alone overstates how much this sample pins down.
The complete results paragraph
Together, the steps give this paragraph:
Of the 189 births, 59 were of low birth weight. Low birth weight was more common among mothers recorded as smoking during pregnancy (30 of 74) than among those who did not (29 of 115). A chi-square test of independence with Yates’ continuity correction showed a statistically significant association between smoking status and low birth weight, χ²(1, N = 189) = 4.24, p = .040. The association was weak, Cramer’s V = .16, 95% CI [.04, 1.00] (one-sided). The odds of low birth weight were higher in the smoking group, OR = 2.02, 95% CI [1.08, 3.78]. All expected cell counts exceeded 5 (smallest 23.1).
The matching methods sentence: “Association was tested with Pearson’s chi-square test of independence with Yates’ continuity correction, in R (chisq.test); effect sizes were Cramer’s V with a one-sided 95% confidence interval and the odds ratio with a Wald interval on the log scale; the expected-count threshold was 5.”
Templates for other outcomes
A result that is not statistically significant. Report it with the same parts, and word it as “not detected”, never as “no association”:
No statistically significant association between [variable A] and [variable B] was detected, χ²(df, N = n) = [value], p = [value], Cramer’s V = [value], 95% CI [lower, 1.00] (one-sided).
The interval matters more here, because it shows which sizes of association the data still allow.
A goodness-of-fit test. When one categorical variable is compared with proportions you specified in advance (equal shares, or a known population split), the test is a goodness-of-fit test, not a test of independence. Degrees of freedom are the number of categories minus 1, and the report must say which proportions were expected:
The distribution of [variable] differed from the expected [equal / stated] proportions, χ²(df, N = n) = [value], p = [value].
Report the observed and expected counts per category. Cramer’s V as used here needs two variables, so describe the largest departures from the expected counts in words instead.
When expected counts are small: reporting Fisher’s exact test
If any expected count falls below your threshold, Fisher’s exact test is the usual replacement for a 2x2 table, and the Fisher’s exact test reference page describes how the engine runs it. It computes the p-value exactly from the table’s margins instead of from an approximation, so there is no test statistic and no degrees of freedom to report. The sentence changes shape:
Fisher’s exact test showed a [statistically significant / no statistically significant] association between [variable A] and [variable B], p = [exact value], OR = [estimate], 95% CI [lower, upper].
Fill it from R:
ft <- fisher.test(tab)
ft$p.value # exact two-sided p-value
ft$estimate # conditional maximum likelihood odds ratio
ft$conf.int # its exact conditional confidence interval
The odds ratio fisher.test() reports is the conditional maximum likelihood estimate, which differs slightly from the cross-product odds ratio; say which you report and keep it consistent. Report the table of counts here too.
We did not run Fisher’s test on birthwt: its smallest expected count is far above 5.
Larger tables
For a table larger than 2x2, three things change in the report:
- Degrees of freedom grow. A 3x4 table has (3 - 1)(4 - 1) = 6, and it goes in the parentheses the same way.
- There are no 2x2 measures. Cramer’s V is the effect size, and V on a large table is hard to interpret on its own, so describe the pattern in words.
- The test is omnibus. A significant result says the variables are associated somewhere, not which cells drive it. Report the standardized residuals (
chisq.test(tab)$stdresin R) to show which cells depart most from independence. Under independence these behave roughly like standard normal values, so an absolute value above about 2 matches the 5% level for one cell taken alone. Say which cutoff you used, and use a stricter one when the table has many cells, since each cell is a separate look. Treat any follow-up comparisons between pairs of rows as additional tests that need a correction, as the multiple comparisons guide explains.
Common mistakes
- Reporting only χ² and p. Without counts and an effect size, direction and size are unknown.
- Not saying whether Yates’ correction was applied. On a 2x2 table the two statistics differ.
- Calling a weak association strong because p is small. The p-value reflects sample size as much as strength; read the effect size and its interval.
- Writing “p = .000”. Report p < .001.
- Using chi-square on paired data. Two classifications of the same people need McNemar’s test.
- Running one chi-square per variable and reporting only the significant ones. That is many tests; say so, or adjust for it.
- Treating a non-significant result as evidence of no association. A p-value above .05 means the data are compatible with independence, not that the variables are independent. The engine’s sensitivity figure for this design: with 189 births, a test at the 0.05 level reaches 80% power only for associations of Cohen’s w = 0.20 or larger (for a 2x2 table, w equals V), so weaker associations could easily be missed.
- Implying cause. A chi-square test on observational data shows association. To account for other variables, the next step is a model such as logistic regression.
Rerunning the example
The R script below the results reruns the chi-square test and the sensitivity power calculation from a fixed copy of the birthwt data, with the same numbers on every run. It does not recompute Cramer’s V or the 2x2 measures; check V with effectsize::cramers_v(tab, adjust = FALSE). For what makes an analysis reproducible, see the guide to reproducible statistical analysis.
Worked example
Data: Hosmer DW, Lemeshow S (1989). Applied Logistic Regression. New York: Wiley. Data as distributed in Venables WN, Ripley BD (2002). Modern Applied Statistics with S, 4th ed. New York: Springer (R package MASS). https://doi.org/10.1007/978-0-387-21706-2. Seed: 20261021.
Run on Celeus engine 0.30.0, seed 20261021, public data.
Output
# Results - Test: Chi-squared test of association - n: 189 - statistic: 4.236 - p-value: 0.0396 - effect size: Cramer's V = 0.1614 (95% CI 0.0397 to 1) (one-sided CI (upper bound fixed at 1)) - seed: 20261021 ## Assumption checks - expected cell counts >= 5: passed=TRUE (all expected counts >= 5) ## Effect measures (1 vs 0; event = 1) | Measure | Estimate | 95% CI | Method | |---|---|---|---| | Risk ratio | 1.608 | 1.058 to 2.443 | log-scale Wald | | Risk difference | 0.1532 | 0.01607 to 0.2904 | Wald | | Odds ratio | 2.022 | 1.081 to 3.783 | log-scale Wald | - RR and RD assume the groups were sampled by exposure (cohort, trial, cross-sectional); if participants were sampled by outcome (case-control), only the odds ratio is interpretable, and it approximates the risk ratio only when the event is rare. ## Power (sensitivity, not observed power) - minimum detectable effect at n=189: Cohen's w = 0.2038 - to detect a conventional medium effect: 88 total to detect w=0.30 - alpha = 0.05, target power = 0.8
R script
# Celeus - reproduce.R (run from this package folder; bundled data in data/)
# Test: Chi-squared test of association | engine 0.30.0 | catalog c39-d3cd
# Research context: not provided for this run.
# Reproduces the recorded result deterministically.
set.seed(20261021)
.data_path <- "data/how-to-report-a-chi-square-test.csv"
if (!file.exists(.data_path)) stop("Run reproduce.R from the package folder (the bundled data is in data/).")
data <- readr::read_csv(.data_path, show_col_types = FALSE) # needs the readr package
clean_cells <- function (df)
{
ctrl_collapse <- "[\r\n\t\v\f]+"
zero_width_chars <- intToUtf8(c(8203L, 65279L), multiple = TRUE)
uni_space_chars <- intToUtf8(c(160L, 8239L, 8199L), multiple = TRUE)
clean_text <- function(x) {
out <- x
for (ch in zero_width_chars) out <- gsub(ch, "", out,
fixed = TRUE)
out <- gsub(ctrl_collapse, " ", out)
for (ch in uni_space_chars) out <- gsub(ch, " ", out,
fixed = TRUE)
trimws(gsub(" +", " ", out))
}
changed_cols <- character(0)
cells_cleaned <- 0L
retyped <- character(0)
for (j in seq_along(df)) {
col <- df[[j]]
if (!is.character(col))
next
cleaned <- clean_text(col)
n_changed <- sum(!is.na(col) & !is.na(cleaned) & col !=
cleaned, na.rm = TRUE)
if (n_changed > 0L) {
changed_cols <- c(changed_cols, names(df)[j])
cells_cleaned <- cells_cleaned + n_changed
}
df[[j]] <- cleaned
non_blank <- cleaned[!is.na(cleaned) & nzchar(cleaned)]
if (length(non_blank) && all(grepl("^[+-]?(0|[1-9][0-9]*)([.][0-9]+)?$",
non_blank))) {
parsed <- suppressWarnings(as.numeric(cleaned))
was_blank <- is.na(cleaned) | !nzchar(cleaned)
lost <- (is.na(parsed) | is.infinite(parsed)) & !was_blank
if (!any(lost)) {
finite <- parsed[!is.na(parsed)]
all_whole <- all(finite == floor(finite))
in_range <- all(abs(finite) <= .Machine$integer.max)
df[[j]] <- if (all_whole && in_range)
as.integer(parsed)
else parsed
retyped <- c(retyped, names(df)[j])
}
}
}
names(df) <- clean_text(names(df))
if (length(changed_cols)) {
attr(df, "ingest_clean_columns") <- changed_cols
attr(df, "ingest_clean_cells_cleaned") <- cells_cleaned
}
if (length(retyped))
attr(df, "ingest_clean_retyped") <- retyped
df
}
data <- clean_cells(data)
# Complete-case analysis: drop rows missing any analysis value.
.acols <- c("smoke", "low")
data <- data[stats::complete.cases(data[, .acols, drop = FALSE]), , drop = FALSE]
result <- chisq.test(table(data[["smoke"]], data[["low"]]))
print(result)
# Sensitivity: minimum detectable effect at n=189 (power=0.8, alpha=0.05). NOT observed power.
power <- pwr::pwr.chisq.test(N = 189, df = 1, sig.level = 0.05, power = 0.8)
print(power)Common questions
How do I report a chi-square test of independence in a results section?
Give the table of counts, say the expected counts were large enough, then report the statistic as χ²(df, N = n) = value, p = value, followed by an effect size with its confidence interval, such as Cramer's V or, for a 2x2 table, the odds ratio.
Do I need to report the expected counts?
Report at least that every expected count met the threshold you used, ideally with the smallest one. Expected counts are what justify the chi-square approximation, so a reader who sees only the statistic cannot tell whether the test was appropriate for the table.
Should I report the chi-square with or without Yates' correction?
Either can be defended, but say which one you report. R applies Yates' continuity correction to 2x2 tables by default, which makes the statistic smaller than the uncorrected Pearson value. A reader comparing your number with their own software needs to know which version it is.
What effect size goes with a chi-square test?
Cramer's V is the general choice and works for any table size; for a 2x2 table it equals the absolute value of phi. For a 2x2 table, also report the odds ratio, and the risk ratio or risk difference when the sampling design allows them, each with a 95% confidence interval.
How do I report a chi-square goodness-of-fit test, or a result that is not significant?
For goodness of fit, give the degrees of freedom as the number of categories minus 1 (when the expected proportions were set in advance, not estimated from the data), name the expected proportions, and report the statistic and p-value in the same format. For a non-significant result, say that no association was detected, keep the effect size and its interval, and avoid concluding that none exists.
How do I report Fisher's exact test instead?
Fisher's exact test has no test statistic or degrees of freedom to report. Name the test, give the exact p-value, and for a 2x2 table report the odds ratio with its confidence interval, noting that R's fisher.test gives the conditional maximum likelihood estimate.
Sources
- American Psychological Association (2020). Numbers and statistics guide, APA Style 7th edition. apastyle.apa.org
- 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 50(302):157-175. doi:10.1080/14786440009463897
- Fisher RA (1922). On the interpretation of chi-square from contingency tables, and the calculation of P. Journal of the Royal Statistical Society 85(1):87-94. doi:10.2307/2340521
- Yates F (1934). Contingency tables involving small numbers and the chi-square test. Supplement to the Journal of the Royal Statistical Society 1(2):217-235. doi:10.2307/2983604
- Cochran WG (1954). Some methods for strengthening the common chi-square tests. Biometrics 10(4):417-451. doi:10.2307/3001616
- Campbell I (2007). Chi-squared and Fisher-Irwin tests of two-by-two tables with small sample recommendations. Statistics in Medicine 26(19):3661-3675. doi:10.1002/sim.2832
- Cramer H (1946). Mathematical Methods of Statistics. Princeton: Princeton University Press. doi:10.1515/9781400883868
- Ben-Shachar MS, Ludecke D, Makowski D (2020). effectsize: estimation of effect size indices and standardized parameters. Journal of Open Source Software 5(56):2815. doi:10.21105/joss.02815
- Greenland S, Senn SJ, Rothman KJ, Carlin JB, Poole C, Goodman SN, Altman DG (2016). Statistical tests, P values, confidence intervals, and power: a guide to misinterpretations. European Journal of Epidemiology 31(4):337-350. doi:10.1007/s10654-016-0149-3
Methods discussed
Try it on your own data
Upload a dataset and Celeus suggests a method, checks its assumptions, runs it in R, and gives you the script to rerun it.
Try it in Celeus