How to test independence with a chi-square contingency test in SciPy (with Code)
The Short Answer (TL;DR)
Pass a table of observed counts to scipy.stats.chi2_contingency and inspect the returned expected counts, degrees of freedom and p-value. Set correction=False explicitly for the uncorrected Pearson statistic used here, and check that the sampling design and expected counts support its chi-square approximation.
from scipy import stats
observed = [[24, 16, 10], [12, 18, 20]]
print(stats.chi2_contingency(observed, correction=False, lambda_=None, method=None))
Detailed Implementation
This example uses synthetic data and does not need a network connection. Install the recorded package versions in a separate Python environment before running it.
python -m pip install numpy==2.5.4 scipy==1.18.1
from pathlib import Path
import numpy as np
from scipy import stats
# Rows: two independent groups; columns: three mutually exclusive outcomes.
observed = np.array([[24, 16, 10], [12, 18, 20]], dtype=int)
if observed.ndim != 2 or min(observed.shape) < 2:
raise ValueError("Use a two-way count table with at least two rows and columns.")
if not np.issubdtype(observed.dtype, np.integer) or np.any(observed < 0):
raise ValueError("Use nonnegative integer counts, not percentages or expected counts.")
if np.any(observed.sum(axis=0)==0) or np.any(observed.sum(axis=1)==0):
raise ValueError("Resolve empty rows or columns before testing independence.")
result = stats.chi2_contingency(observed, correction=False,
lambda_=None, method=None)
report = (
f"Observed counts: {observed.tolist()}\n"
f"Total independent observations: {observed.sum()}\n"
f"Expected counts under independence: {result.expected_freq.tolist()}\n"
f"Smallest expected count: {result.expected_freq.min():.6f}\n"
f"Pearson chi-square statistic: {result.statistic:.6f}\n"
f"Degrees of freedom: {result.dof}\n"
f"Approximate p-value: {result.pvalue:.8g}\n"
)
print(report)
Path("chi-square-independence-output.txt").write_text(report, encoding="utf-8")
The output contains one hundred observations, expected rows [18, 17, 15], and two degrees of freedom. The Pearson statistic and approximate probability agree with the independent cell-contribution and upper-tail calculations. Reordering rows or columns preserves the result.
What belongs in the table?
Each independent unit contributes to exactly one row-and-column category. The example has two groups and three mutually exclusive outcomes, with row totals of fifty and a grand total of one hundred. Supply observed counts in a consistent category order. A table of percentages, duplicated units, fractional weights or expected frequencies represents a different input or sampling problem.
When is the approximation suitable?
Expected counts come from the row and column totals under independence. Here their minimum is fifteen and every observed count is at least ten. SciPy documents at least five observed and expected counts per cell as a commonly quoted guideline, not a substitute for checking the design. Sparse tables may require an exact or resampling approach. Paired outcomes, clustered observations or weighted surveys require methods that account for that structure.
What does correction=False specify?
The default continuity correction affects tables with one degree of freedom, including a two-by-two table. This two-by-three example has two degrees of freedom and explicitly reports the uncorrected Pearson statistic. Keep the correction and p-value method in the report when comparing results. A small p-value is evidence against independence under the model; it does not quantify a causal effect or identify a specific cell as the cause.
Mathematical Context
For cell i,j, multiply row total R_i by column total C_j and divide by the grand total N to obtain E_ij. The expected table retains the observed margins. Add (O_ij-E_ij) squared divided by E_ij over all cells to obtain Pearson X squared. The two row totals are fifty, and the column totals are thirty-six, thirty-four and thirty, so both expected rows are eighteen, seventeen and fifteen. The reference degrees of freedom are (rows-1)(columns-1)=2. The checks independently reconstruct the expected table and statistic. With two chi-square degrees of freedom the upper-tail probability is exp(-X squared/2), which gives a second calculation of the reported approximate p-value.
\[ E_{ij}=\frac{R_iC_j}{N},\qquad X^2=\sum_{i,j}\frac{(O_{ij}-E_{ij})^2}{E_{ij}} \]
Reproducibility
Executed with Python 3.12.14; numpy 2.5.4, scipy 1.18.1. Automated checks confirm the stated output properties.
Primary documentation
- SciPy chi-square contingency test and correction options
- SciPy expected frequencies from marginal sums
- SciPy chi-square reference distribution