Answer

Can you run a chi-square test on weighted survey estimates?

Inspired by a question on Cross Validated ·

chi-squarehypothesis testingsamplingeffect size

The short answer

No. A chi-square test treats every count as an independently observed case, so plugging in weighted totals (millions of estimated people) makes the statistic depend on the units: the same table gives a chi-square 1,000 times larger in persons than in thousands. The uncertainty comes from the people actually interviewed and the survey design. Compare the two proportions using their standard errors (or a design-based test such as Rao-Scott), and report the size of the gap.

The short answer

Official statistics such as labour-force or census-style tables usually come from a survey: tens of thousands of people are interviewed, and each one is given a weight saying how many people in the population they stand for. The published table then shows estimated totals in thousands or millions. Those numbers are estimates of the population, not counts of observations.

A Pearson chi-square test assumes each cell holds a count of independently sampled cases. Give it 139 million estimated people and it believes you observed 139 million people, so the standard errors it implies are absurdly small and almost any gap comes out with a microscopic p-value. The test is answering a question about a sample that never existed.

The real uncertainty depends on how many people were actually interviewed and how the survey was designed (clustering, stratification, unequal weights). So the right comparison uses the standard errors of the two estimated proportions, which statistical agencies usually publish or explain how to compute.

Why weighted totals break the test

The clearest symptom is that the answer depends on the units. Multiply every cell of a table by a constant k and the proportions do not change at all, but the Pearson statistic is multiplied by exactly k. A table reported in thousands and the same table reported in persons describe the same population, yet the second gives a chi-square 1,000 times larger. A valid test cannot change its verdict because you moved a decimal point.

See it in R and Python

The code imagines a survey of 6,000 people (3,100 men and 2,900 women) weighted up to a population of 70 million men and 65 million women. In the sample, 40% of men and 43% of women hold a degree. It runs the chi-square test the wrong way, on the weighted totals, and then the right way, on the people interviewed, first as if the survey were a simple random sample and then with an assumed design effect of 1.5. Finally it simulates 2,000 surveys in which men and women truly have the same rate (41%) and counts how often each approach declares a significant difference.

R

set.seed(331972)

# A survey of 6,000 people stands in for 135 million (70 million men, 65 million women)
n   <- c(men = 3100, women = 2900)        # people actually interviewed
deg <- c(men = 1240, women = 1247)        # of whom hold a degree (40% vs 43%)
w   <- c(men = 70e6, women = 65e6) / n    # survey weight: people each respondent represents

# 1. WRONG: chi-square on the weighted population estimates
est <- cbind(degree = deg * w, no_degree = (n - deg) * w)
naive_thousands <- chisq.test(est / 1000, correct = FALSE)$statistic
naive_persons   <- chisq.test(est, correct = FALSE)$statistic
round(c(thousands = unname(naive_thousands), persons = unname(naive_persons),
        ratio = unname(naive_persons / naive_thousands)), 1)

# 2. RIGHT (simple random sample): test on the people actually interviewed
pt <- prop.test(deg, n, correct = FALSE)
round(c(chisq = unname(pt$statistic), p = pt$p.value), 4)
p_hat <- deg / n
round(c(p_hat, diff = unname(p_hat[2] - p_hat[1]), ci = -rev(pt$conf.int),
        ratio = unname(p_hat[1] / p_hat[2])), 4)

# 3. With a design effect (clustering, unequal weights) the SEs are larger
deff <- 1.5
se <- sqrt(p_hat * (1 - p_hat) / n * deff)
se_diff <- sqrt(sum(se^2))
z <- unname(p_hat[2] - p_hat[1]) / se_diff
round(c(se_diff = se_diff, z = z, p = 2 * pnorm(-abs(z)),
        lo = unname(p_hat[2] - p_hat[1]) - 1.96 * se_diff,
        hi = unname(p_hat[2] - p_hat[1]) + 1.96 * se_diff), 4)

# 4. When the two rates are truly equal, how often does each approach reject?
sims <- replicate(2000, {
  d <- rbinom(2, n, 0.41)
  tab <- cbind(d, n - d)
  c(naive = chisq.test(tab * w, correct = FALSE)$p.value < 0.05,
    right = chisq.test(tab, correct = FALSE)$p.value < 0.05)
})
rowMeans(sims)

Python

import numpy as np
from scipy import stats

rng = np.random.default_rng(331972)

# A survey of 6,000 people stands in for 135 million (70 million men, 65 million women)
n = np.array([3100, 2900])                # people actually interviewed (men, women)
deg = np.array([1240, 1247])              # of whom hold a degree (40% vs 43%)
w = np.array([70e6, 65e6]) / n            # survey weight: people each respondent represents

# 1. WRONG: chi-square on the weighted population estimates
est = np.column_stack([deg * w, (n - deg) * w])
naive_thousands = stats.chi2_contingency(est / 1000, correction=False)[0]
naive_persons = stats.chi2_contingency(est, correction=False)[0]
print({"thousands": round(naive_thousands, 1), "persons": round(naive_persons, 1),
       "ratio": round(naive_persons / naive_thousands, 1)})

# 2. RIGHT (simple random sample): test on the people actually interviewed
tab = np.column_stack([deg, n - deg])
chi2, p = stats.chi2_contingency(tab, correction=False)[:2]
print({"chisq": round(chi2, 4), "p": round(p, 4)})
p_hat = deg / n
diff = p_hat[1] - p_hat[0]
se_srs = np.sqrt(np.sum(p_hat * (1 - p_hat) / n))   # unpooled, as in R's prop.test CI
z975 = stats.norm.ppf(0.975)
print({"men": round(p_hat[0], 4), "women": round(p_hat[1], 4), "diff": round(diff, 4),
       "ci1": round(diff - z975 * se_srs, 4), "ci2": round(diff + z975 * se_srs, 4),
       "ratio": round(p_hat[0] / p_hat[1], 4)})

# 3. With a design effect (clustering, unequal weights) the SEs are larger
deff = 1.5
se = np.sqrt(p_hat * (1 - p_hat) / n * deff)
se_diff = np.sqrt(np.sum(se ** 2))
z = diff / se_diff
print({"se_diff": round(se_diff, 4), "z": round(z, 4), "p": round(2 * stats.norm.sf(abs(z)), 4),
       "lo": round(diff - 1.96 * se_diff, 4), "hi": round(diff + 1.96 * se_diff, 4)})

# 4. When the two rates are truly equal, how often does each approach reject?
naive, right = [], []
for _ in range(2000):
    d = rng.binomial(n, 0.41)
    t = np.column_stack([d, n - d])
    naive.append(stats.chi2_contingency(t * w[:, None], correction=False)[1] < 0.05)
    right.append(stats.chi2_contingency(t, correction=False)[1] < 0.05)
print({"naive": np.mean(naive), "right": np.mean(right)})

The figures below come from one seeded run of the R code. Parts 1 to 3 involve no randomness, and the Python version reproduces them exactly. Python's random numbers differ from R's, so its simulation gives slightly different rejection rates (99.15% and 4.45%), but the same pattern.

What to do instead

If you truly have every member of a population (a full register rather than a survey), there is no sampling error to test, and the observed difference is simply the difference. A test then only makes sense if you are asking about an underlying process that could have produced other populations. For choosing a test when you do have a sample, see the test chooser, and for how the chi-square test of independence works, see goodness of fit vs test of independence.

How to report a difference in proportions in APA style (7th edition)

Give both percentages, the difference with its confidence interval, the test statistic and the p-value, and say in the method section how the standard errors account for the survey design. Using the design-based example above:

"Using survey weights and an assumed design effect of 1.5, an estimated 43.0% of women and 40.0% of men held a degree, a difference of 3.0 percentage points, 95% CI [-0.05, 6.05], z = 1.92, p = .054."

If the data were a simple random sample, a chi-square test on the unweighted counts is fine: "Degree holding differed by sex, χ²(1, N = 6,000) = 5.56, p = .018; 43.0% of women and 40.0% of men held a degree (difference = 3.0 points, 95% CI [0.51, 5.49])." Never report N as the weighted population total. For more on reporting survey results, see the DASS blog.

Related tools and guides

More answered questions

Working with your own data?

General answers only go so far. Send us your situation and we'll reply by email within two business days.

Ask your question

Written by AskStats with AI assistance. This is general information, not advice for your specific data or study. When the results matter, check your approach with a qualified statistician.