Cramér’s V

Cramér's V turns a chi-square statistic into a number between 0 and 1 that does not grow with the sample:

\[ V = \sqrt{\frac{\chi^2}{n\,(k - 1)}}, \quad k = \min(\text{rows}, \text{columns}) \]

It is the effect size that chi-square by itself does not provide.

What the normalization removes

import numpy as np
from scipy.stats import chi2_contingency

def cramers_v(table):
    table = np.asarray(table)
    chi2_stat = chi2_contingency(table, correction=False).statistic
    n = table.sum()
    k = min(table.shape)
    return float(np.sqrt(chi2_stat / (n * (k - 1))))

base = np.array([[300, 200], [280, 220]])
print(f"{'sample size':>13}{'chi-square':>13}{'p-value':>13}{'Cramer V':>12}")
for k in (1, 2, 10, 100):
    t = base * k
    r = chi2_contingency(t, correction=False)
    print(f"{t.sum():>13,}{r.statistic:>13.4f}{r.pvalue:>13.6f}{cramers_v(t):>12.6f}")
"""V is the effect size chi-square is missing. It answers 'how strong', and it
   still does not answer 'in which direction'."""

print(f"    perfect independence {cramers_v(np.array([[250, 250], [250, 250]])):.6f}")
print(f"    perfect association  {cramers_v(np.array([[500, 0], [0, 500]])):.6f}")
print(f"    the worked case      {cramers_v(base):.6f}   (a weak association)")

rng = np.random.default_rng(0)
print(f"{'categories':>12}{'rows':>8}{'mean Cramer V':>16}{'90th pct':>11}")
for cats in (2, 5, 10, 20):
    vals = []
    for _ in range(300):
        x = rng.integers(0, cats, 200)
        y = rng.integers(0, cats, 200)
        t = np.zeros((cats, cats), int)
        np.add.at(t, (x, y), 1)
        keep = (t.sum(axis=1) > 0)
        t = t[keep][:, t.sum(axis=0) > 0]
        if min(t.shape) > 1:
            vals.append(cramers_v(t))
    print(f"{cats:>12}{200:>8}{np.mean(vals):>16.4f}{np.percentile(vals, 90):>11.4f}")
#   sample size   chi-square      p-value    Cramer V
#         1,000       1.6420     0.200046    0.040522
#         2,000       3.2841     0.069955    0.040522
#        10,000      16.4204     0.000051    0.040522
#       100,000     164.2036     0.000000    0.040522
#     perfect independence 0.000000
#     perfect association  1.000000
#     the worked case      0.040522   (a weak association)
#   categories    rows   mean Cramer V   90th pct
#            2     200          0.0570     0.1123
#            5     200          0.1409     0.1774
#           10     200          0.2129     0.2347
#           20     200          0.3077     0.3189

The first table is the whole argument for the measure. The same proportions scaled from 1,000 rows to 100,000 send the chi-square statistic from 1.6420 to 164.2036 and the p-value from 0.2000 to effectively zero — while V stays at 0.040522 in every row.

That is what an effect size is for. The significance test answers whether the association exists, V answers how strong it is, and the two questions have genuinely different answers on a large table: this table has a small descriptive V and strong evidence against independence under the test assumptions. Its practical importance depends on the variables and decision.

The endpoints behave as advertised. A perfectly independent table gives exactly 0.000000, a perfectly associated one gives 1.000000, and the worked example gives 0.040522 — small numerically, with a very small p-value at 100,000 rows under the test assumptions. Neither that p-value nor a generic interpretation band establishes its practical importance.

It is biased upward on sparse tables

The last block is the caution that the usual interpretation bands leave out. Two variables generated to be completely independent, over 200 rows, still produce a positive V — and the size grows with the number of categories: 0.0570 with two, 0.1409 with five, 0.2129 with ten, 0.3077 with twenty.

At twenty categories the average V from data with no association in it is 0.3077, which the customary rule of thumb calls a moderate association. Generic bands can be misleading when sampling bias is substantial. The mechanism is straightforward: the asymptotic chi-square null distribution has an expected value equal to its degrees of freedom, so more cells and fewer rows inflate V systematically.

Two responses work. Compare V against the distribution of V under independence for your table shape and sample size, which is what the block above computes. Or use a bias-corrected variant, which subtracts the expected null contribution before taking the square root. Well-filled cells can reduce sampling bias, but interpretation still needs the variable definitions, uncertainty, and a decision-relevant magnitude.

What it still does not tell you

V inherits chi-square's blindness to direction. It reports that two categorical variables are associated and by how much, and it cannot say which categories go together — that reading comes from the standardized residuals of the table, cell by cell.

V is a descriptive association statistic whenever the contingency-table calculation is defined. Sparse expected counts do not by themselves invalidate that number; they affect the chi-square approximation used for a p-value, and raw sample V can be strongly positively biased. Population inference also requires an appropriate sampling design and treatment of dependent observations. Report bias and uncertainty separately from the descriptive value.

Finally, the interpretation bands themselves are conventions rather than results. They vary by field, and nothing in the mathematics says 0.3 is moderate. Where a decision depends on the strength of an association, the defensible practice is to state what magnitude would change the decision before computing V — the same discipline that separates statistical significance from practical significance.

References


Discover more from Insightful Data Lab

Subscribe to get the latest posts sent to your email.

Similar Posts

Questions, corrections, or additional insights?

This site uses Akismet to reduce spam. Learn how your comment data is processed.