ds@mcpanalytics.aiEmail your data and your question and get an answer about your own data, with the method shown.Email your data and question. Get an answer.

Chi-Square Test: Test Associations Between Categorical Variables

By MCP Analytics Team | Published , updated

You have two categorical variables and a simple question: are they related, or independent? Does marketing channel affect whether customers convert? Does department predict employee satisfaction level? Does treatment type relate to patient outcome? The chi-square test of independence gives evidence of a link, a p-value, and a score for the size of the link. A cell view can show how the counts compare with the counts expected if the variables were not linked. Upload a CSV to see a report.

Worked Example (Simulated): Are Department and Satisfaction Linked?

Question: In this made-up staff survey, are department and satisfaction linked?

Inputs: The data are made up. The script uses seed 2026 and draws 400 pairs. Each pair has a department (Sales, Engineering or Support) and a satisfaction level (Satisfied, Neutral or Dissatisfied).

Built in: The script types in the chance of each department and satisfaction pair, and those chances are not independent. The population Cramer's V implied by the typed chances is 0.2451. So a link is built in, and evidence of a link is the expected outcome, not a finding about real staff.

Method: The script counts each pair. It runs a Pearson chi-square test, a count test for a link between group labels, and finds Cramer's V, a score for the size of the link.

Complete Python script:

import numpy as np
from scipy.stats import chi2_contingency

# Simulated data, seed 2026: draw 400 employee category pairs.
rng = np.random.default_rng(2026)
rows = ["Sales", "Engineering", "Support"]
cols = ["Satisfied", "Neutral", "Dissatisfied"]
prob = np.array([
    [0.15, 0.10, 0.05],
    [0.10, 0.18, 0.08],
    [0.08, 0.10, 0.16],
])
observed = rng.multinomial(400, prob.ravel()).reshape(3, 3)
chi2, p_value, dof, expected = chi2_contingency(observed, correction=False)
k = min(observed.shape[0] - 1, observed.shape[1] - 1)
cramers_v = np.sqrt(chi2 / (observed.sum() * k))

print("Simulated data, seed 2026; n =", observed.sum())
print("Rows: Department; columns: Satisfaction")
print("Observed counts:")
print("Department      Satisfied  Neutral  Dissatisfied")
for name, row in zip(rows, observed):
    print(f"{name:<15}{row[0]:>10}{row[1]:>9}{row[2]:>14}")
print("Expected counts:")
print(np.array2string(expected, precision=2, suppress_small=True))
p_text = "p below 0.001" if p_value < 0.001 else f"p = {p_value:.4f}"
print(f"Pearson chi-square = {chi2:.4f}; df = {dof}; {p_text}")
print(f"Cramer's V = {cramers_v:.4f}")
probabilities = prob / prob.sum()
row_share = probabilities.sum(axis=1, keepdims=True)
col_share = probabilities.sum(axis=0, keepdims=True)
expected_share = row_share * col_share
gap = (probabilities - expected_share) ** 2 / expected_share
built_in_v = np.sqrt(gap.sum() / 2)
print(f"Built-in Cramer's V from the cell chances: {built_in_v:.4f}")

Printed output from running the script with python3:

Simulated data, seed 2026; n = 400
Rows: Department; columns: Satisfaction
Observed counts:
Department      Satisfied  Neutral  Dissatisfied
Sales                  57       33            19
Engineering            39       70            43
Support                38       29            72
Expected counts:
[[36.52 35.97 36.52]
 [50.92 50.16 50.92]
 [46.56 45.87 46.56]]
Pearson chi-square = 53.6814; df = 4; p below 0.001
Cramer's V = 0.2590
Built-in Cramer's V from the cell chances: 0.2451

Result: The output gives chi-square 53.6814 with 4 degrees of freedom, a count used by the test, and a p-value below 0.001. Cramer's V is 0.2590, close to the 0.2451 built in. The expected counts show what each cell would hold if department and satisfaction were not linked; the observed counts sit well away from them, for example 72 against 46.56 for Support and Dissatisfied. In this run the test picks up the built-in link.

Limit: The data are made up. They do not show how real staff feel. The test does not show that a person's department causes a view.

Sources

What Is the Chi-Square Test of Independence?

The chi-square test of independence answers one question: are two categorical variables associated, or are they statistically independent? "Independent" means that knowing the value of one variable tells you nothing about the other. If marketing channel and conversion status are independent, then the conversion rate is the same regardless of whether a customer arrived through paid search, social media, or email. If they are not independent, at least one channel converts at a meaningfully different rate.

The test works by building a contingency table: a grid that counts how many observations fall into each combination of categories. The example above uses made-up data on department (Sales, Engineering, Support) and satisfaction level (Satisfied, Neutral, Dissatisfied). Its contingency table has 3 rows and 3 columns, with 9 cells. Each cell holds a count, for example, the number of salespeople who said "Satisfied."

The chi-square test then compares these observed counts to the counts you would expect if the two variables were completely independent. Expected counts are calculated from the row and column totals. If 60% of all employees are Satisfied and 30% work in Sales, then under independence you would expect 18% of the total (0.60 x 0.30) to be Satisfied salespeople. The test measures how far the observed counts deviate from these expected counts across every cell. Large deviations produce a large chi-square statistic and a small p-value, meaning the association is unlikely to be random noise.

When to Use the Chi-Square Test

Use the chi-square test whenever both of your variables are categorical and you want to know if they are related. The most common business scenarios include:

Survey analysis. Does satisfaction level differ by department? Does product preference vary by age group? Does the distribution of responses to one question depend on responses to another? Surveys produce categorical data by nature, making them a natural fit for chi-square testing.

Marketing and A/B testing. Does marketing channel affect whether someone converts (yes/no)? Does ad creative relate to click outcome? When your outcome is categorical (converted or did not, clicked or did not, churned or stayed) chi-square is the right test. It handles any number of categories on either side: three channels versus two outcomes, five ad variants versus three engagement levels.

Healthcare and clinical research. Is treatment type associated with patient outcome (recovered, improved, no change)? Does a risk factor relate to disease status?

Retail and operations. Does product category relate to return rate (returned vs. kept)? Does store location affect which payment method customers choose? Does day of week influence whether an order ships on time? Any time you cross-tabulate two categorical dimensions and wonder if the pattern is real, chi-square gives you the answer.

A key advantage over running separate proportion tests: chi-square handles any number of categories on both variables in a single test, keeping your false positive rate controlled. If you have five departments and four satisfaction levels, that is 20 cells, running individual tests for each would inflate your error rate. Chi-square tests the whole table at once.

What Data Do You Need?

You need a CSV with at least two categorical columns. When you upload, you will map your columns to the analysis: one variable becomes the row variable and the other becomes the column variable. You also select a primary target: the variable whose distribution you are most interested in explaining. For example, if you want to know whether department predicts satisfaction, satisfaction is the target and department is the grouping variable.

Both variables must be categorical (text labels or discrete categories, not continuous numbers). Common examples: department names, satisfaction ratings (High/Medium/Low), yes/no flags, product categories, regions, survey response options. If you have a numeric variable like revenue, you will need to bin it into categories first (e.g., Low/Medium/High revenue bands).

For reliable results, the chi-square approximation depends on the expected cell counts being large enough. This is not about observed counts. It is about what the counts would be under independence. With very small samples or rare categories, some expected counts can be small, which makes the chi-square approximation unreliable. Check the expected counts under the no-link model before you trust the p-value.

How to Read the Report

The report is organized into the parts below, each building on the last to give you a picture of the association between your variables.

Analysis Overview. A summary of the dataset: how many rows survived preprocessing, which variables were tested, and the significance level used (default 0.05). This is your orientation: confirm that the right columns were analyzed and that the sample size is adequate.

Executive Summary (TL;DR). A plain-language summary of the key findings generated by the AI. Which variable pairs showed significant associations? How strong were they? This section is designed to be shared directly with stakeholders who do not need the statistical details.

Chi-Square Test Results. The core statistical output. For each variable pair tested, you see the chi-square statistic, degrees of freedom, and p-value. The chi-square statistic measures the total squared deviation between observed and expected counts, scaled by expected counts. Degrees of freedom depend on the table dimensions: (rows - 1) x (columns - 1). The p-value is the probability of seeing a chi-square statistic this large if the variables were truly independent. A p-value below 0.05 means you can reject independence. The variables are associated.

Effect Sizes. Statistical significance alone does not tell you how strong the association is. A large sample can produce a tiny p-value for a trivially small association. Cramer's V solves this. It ranges from 0 (no association) to 1 (perfect association). For 2x2 tables, the phi coefficient is equivalent to Cramer's V, since both variables have exactly two levels.

Contingency Table Heatmap. A color-coded grid of observed counts. Darker cells mean higher counts. This visualization immediately shows you where the data concentrates, which combinations of categories are most and least common. It is the visual version of the raw contingency table.

Standardized Residuals. This is where the cell-level detail lives. A standardized residual is a scaled gap between the observed and the expected count. A large positive residual flags a cell with more observations than expected under independence, and a large negative residual flags a cell with fewer. Treat a large residual as a cell to check, not as proof. The heatmap makes these stand out visually: look for the darkest red and blue cells.

Group Distribution (Grouped Bar Chart). A bar chart showing the distribution of the target variable within each level of the grouping variable. This is the most intuitive visualization. You can see at a glance whether, for example, the proportion of satisfied employees differs across departments. Side-by-side bars make the comparison easy without needing to interpret numbers.

Observed vs. Expected (Contingency Details). The full contingency table with both observed counts and expected counts side by side. This is the data behind the chi-square calculation. Cells where observed counts diverge sharply from expected counts are the ones contributing most to the chi-square statistic. This table lets you verify the test by hand if needed.

When to Use Something Else

If your sample is very small (particularly if expected cell counts are small) consider Fisher's exact test. It computes the exact probability rather than relying on the chi-square approximation, making it valid for small samples. Fisher's test is most commonly used for 2x2 tables but can handle larger tables with specialized algorithms. Check the expected counts to see when to switch.

If your outcome variable is numeric rather than categorical (for example, comparing revenue across departments rather than satisfaction categories) use ANOVA (three or more groups) or a t-test (two groups). These tests compare means of continuous variables, which chi-square cannot do.

If you want to go beyond testing association and actually predict which category a new observation will fall into, consider logistic regression or Naive Bayes classification. Chi-square tells you whether an association exists; classification models tell you the direction and let you make predictions on new data.

If your categorical variables are ordinal. That is, they have a natural order like "Low < Medium < High": the standard chi-square test ignores that ordering. A Cochran-Armitage trend test or ordinal regression would be more appropriate because they incorporate the ordering and are more powerful at detecting linear trends. However, for a quick check of whether any association exists, chi-square still works. It is just not optimized for ordered categories.

If you have matched or paired data (for example, the same patients assessed before and after treatment) use McNemar's test instead. Chi-square assumes independent observations, and paired data violates that assumption.

The R Code Behind the Analysis

Every report names its method and includes the R code that produced it, so you can reproduce, audit and cite it.

The analysis uses chisq.test() from base R for the Pearson chi-square statistic, p-value, expected frequencies, and standardized residuals. Effect sizes are computed with cramersV() from the lsr package and cross-checked via the vcd package. The contingency table is built with table() and xtabs(): standard R functions used in every statistics textbook. Every step is visible in the code tab of your report, so you or a statistician can verify exactly what was done.

Your turn

Bring your own data and the question you actually need answered.

MCP AnalyticsData science by email Send us your data and your question, and we will reply. ds@mcpanalytics.ai