Learn · Part C · Chapter 11

Chi-square: goodness of fit and independence

Does the mix match the forecast? Does one category depend on another? Observed against expected counts.

Observed against expected: Helion's contacts by channel against the capacity plan
Playback speed

Four pairs of bars compare the contacts each channel received with the number the capacity plan's shares predict for 1,600 contacts: phone 742 against 736, chat 388 against 384, email 271 against 288 and self-serve 199 against 192. One channel at a time, the gap is squared and divided by the expected count, and that contribution joins a running total: 0.049, 0.042, 1.003 and 0.255. The total, χ² = 1.35, then lands on the χ² curve with 3 degrees of freedom, far from the critical value 7.815: p = 0.72, no reason to doubt the plan.

What you just saw

Helion's capacity plan assumed a contact mix of 46 % phone, 24 % chat, 18 % email and 12 % self-serve, and the call centre was staffed on it. During the experiment 1,600 customers got in touch. Does the mix match the plan?

The animation first drew the counts each channel actually received, then the counts the plan predicts for 1,600 contacts: 736 phone, 384 chat, 288 email and 192 self-serve. Then, one channel at a time, it measured the miss and added it to a running total. Email missed the most, 271 against 288, and even its contribution was only 1.003. The total, χ² = 1.35, landed far to the left of the critical value on the χ² curve: p = 0.72.

Here, unusually, a large p-value is the good news: the observed mix is consistent with the plan the staffing model was built on, so no re-plan is required.

Observed against expected

Chi-square tests work on counts of categories: never on means, percentages or measurements. If your table holds anything other than whole-number frequencies, you have the wrong test. Every chi-square test compares the observed count in each cell, O, with the expected count, E, that H₀ predicts:

In words: for each cell, square the miss (so misses in both directions count) and divide by the expected count (so each miss is judged against how many were expected there); then add the cells. Dividing by E is what makes a 10-count miss matter far more in a cell expecting 20 than in one expecting 2,000. For the phone channel:

If H₀ is true, χ² follows the χ² curve with the right degrees of freedom, and large values are the surprising ones: the p-value is the area to the right.

Goodness of fit: do the counts match a plan?

A goodness-of-fit test asks whether observed counts match a distribution fixed in advance. H₀ is the plan. The expected counts come from multiplying each hypothesised share by the total observed count, and the degrees of freedom are the number of categories less one (once three counts are known, the fourth is fixed by the total):

Goodness-of-fit contributions: email is the largest contributor, and even it is small
ChannelObservedExpectedContribution to χ²
Phone7427360.049
Chat3883840.042
Email2712881.003
Self-serve1991920.255

χ² = 1.35 on 3 degrees of freedom, against a critical value of 7.815 at α = 0.05: p = 0.72, we fail to reject H₀. Note the wording. This does not prove the forecast is correct, only that the data give no reason to abandon it. The distinction from chapter 3 matters as much in a capacity meeting as in a statistics exam.

Independence: does one category depend on another?

Helion's second counting question: is a customer's segment associated with how they pay? H₀ says the two are independent: the split of payment methods looks the same inside every segment. The expected counts then come from the table's margins alone, and the animation below fills them in cell by cell:

Expected from the margins: Helion's customers by segment and payment method
Playback speed

A table of 12,000 customers by segment (Corporate, Residential, SME) and payment method (bank transfer, card, cash, direct debit) shows its row and column totals. One cell at a time, the count independence would predict is filled in as row total × column total ÷ 12,000, from 213.2 for Corporate customers paying by bank transfer to 4,133.7 for Residential customers paying by direct debit. Then each cell turns into its standardised residual, (observed − expected) / √expected, shaded and labelled with its sign and size: the largest, +24.4, is Corporate customers paying by bank transfer. The summary gives χ² = 1,298.3 on 6 degrees of freedom and Cramér's V = 0.23.

In words: if segment and payment method were unrelated, a cell's share of its row would be the column's share of the whole table. For Corporate customers paying by bank transfer: 975 Corporate customers, 2,624 bank-transfer payers, 12,000 in all, so 975 × 2,624 / 12,000 = 213.2 expected, against 570 observed. With 3 segments and 4 methods, df = 2 × 3 = 6.

The test of independence assumes independent observations, one row per customer (never test a table where the same person is counted twice), and expected counts of at least 5 in essentially every cell. Here the smallest expected count is 46.5, comfortably above 5, so the χ² approximation is safe. If cells are sparse, collapse categories or use Fisher's exact test (chapter 10).

The result: χ² = 1,298.3 on 6 degrees of freedom, p = 2.5 × 10⁻²⁷⁷. The association is real.

Where the association lives: the residuals

Always follow a significant chi-square with residuals The omnibus χ² tells you that an association exists. It does not tell you what the association is, and only the second thing is actionable. Standardised residuals, (O − E) / √E, show which cells drive the result: a residual beyond about ±2 marks a cell that contributes materially. Row percentages tell the same story in a form a business audience can read directly.

The standardised residuals show what the association is: Corporate customers concentrate heavily in bank transfer (+24.4; 58.5 % of them pay that way, against 14.1 % of Residential customers), while Residential customers concentrate in direct debit (+4.3) and card (+6.6), and avoid bank transfer (−15.2). That is the finding a collections strategy can be built on. A single organisation-wide "move everyone to direct debit" campaign would aim at the wrong behaviour for an entire segment. The p-value could never have said that; the residuals do.

How strong? Cramér's V

With 12,000 customers, even a weak association gives a huge χ². Cramér's V rescales χ² to run from 0 (no association) to 1 (each segment pays one way only), whatever the sample size:

V = 0.23: a real association of small-to-medium strength. Segment shapes how customers pay, but it is far from the whole story.

Why this is true: Why a 2 × 2 chi-square test is the two-proportion z test in disguise

A 2 × 2 table, such as Helion's portal by paperless or not, can be tested for independence with χ² or as two proportions with z (chapter 10). Without any correction they are the same test: χ² = z², and the p-values are identical.

scipy's chi2_contingency applies Yates' continuity correction to 2 × 2 tables by default, so its χ² comes out slightly smaller than z² (16.62 against 16.77 for Helion). Pass correction=False when you want the two to match.

Why it matters

Counts of categories are everywhere in a business: contacts by channel, customers by segment, claims by region. The goodness-of-fit test tells a capacity planner whether the plan still holds; the test of independence, read through its residuals, tells a collections team which segment to target with which payment method. In both, the test is the start of the reading, not the end.

When this goes wrong

Chi-square tests need counts, one per independent unit: running one on percentages throws away the sample size and gives nonsense, and counting the same customer in two cells breaks independence. Small expected counts (below 5) make the χ² curve a poor approximation: collapse categories or use an exact test. And with large samples almost any association is significant, so read V and the residuals before anyone acts on a p-value like 2.5 × 10⁻²⁷⁷.

In code

What it computes Python (scipy, statsmodels) Excel
Goodness of fit against the plan's shares chisquare(f_obs=[742, 388, 271, 199], f_exp=np.array([0.46, 0.24, 0.18, 0.12]) * 1600) =CHISQ.TEST(B2:B5, C2:C5)
Each channel's contribution (observed - expected)**2 / expected =(B2-C2)^2/C2
Independence, from the table of counts chi2_contingency(pd.crosstab(df['segment'], df['payment_method'])) =CHISQ.TEST(B2:E4, B8:E10)
Expected count of one cell row_total * column_total / total =$F2*B$5/$F$5
Standardised residuals (table - expected) / np.sqrt(expected) =(B2-B8)/SQRT(B8)
Cramér's V np.sqrt(chi2 / (n * (min(table.shape) - 1))) =SQRT(G1/(12000*(MIN(3,4)-1)))

chisquare and chi2_contingency are in scipy.stats. Excel's CHISQ.TEST needs the expected table built by formula, returns only the p-value, and picks its own degrees of freedom: k − 1 for one row, which is wrong when a parameter was estimated from the data. G1 holds χ².

Try this Type your own counts into the chi-square explorer, starting from Helion's segment × payment table, and read the residuals it prints.
Try this Replay the expected counts filling from the margins and stop at the residuals: find the cell that drives the result, then the ones that push the other way.
Try this Watch the channel mix again and step through the four contributions: which one would have to grow, and by how much, to reach the critical value?
Remember: Chi-square tests compare observed counts with the counts H₀ expects: from a plan's shares for goodness of fit, from the margins for independence. χ² = Σ (O − E)² / E, read on the χ² curve. Use counts, never percentages; check that expected counts are at least 5; and after a significant result, read the standardised residuals and Cramér's V to see where the association lives and how strong it is.

Practise

4 exercises

Quiz

Three questions on the chapter. Your best score is kept in this browser.

1. Which table can go into a chi-square test?
2. In a test of independence, how is a cell's expected count found?
3. The channel mix gives p = 0.72 against the capacity plan. What should the planner conclude?
Answer every question first.
An unhandled error has occurred. Reload 🗙

Reconnecting…

Your connection to Hypothesis Testing Lab dropped, probably a Wi-Fi blip. Hold on while it reconnects.

The connection could not be restored. The page will reload in a few seconds.