Pandas & NumPy Academy · 课时

独立性的卡方检验

使用列联表频数表中的 chi2_contingency,检验两个分类变量是否相互独立

第 3 / 4 课13 个步骤

独立性的卡方检验 是 CoddyKit 上的免费 Pandas & NumPy Academy 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Pandas & NumPy Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Pandas & NumPy Academy 课程共包含 4 节课。

检验分类变量之间的关系

独立性卡方检验用于检验两个分类变量在统计上是否相互独立,或它们之间是否存在关联。例如:“客户流失是否与订阅层级相互独立?”或“产品偏好是否与年龄组相互独立?”与比较数值均值的 t 检验不同,卡方检验会将列联表中的观测频数与变量相互独立时我们预期得到的频数进行比较。

使用 Pandas 构建列联表

列联表(也称交叉表)显示两个分类变量的每种组合对应的观测数量。pd.crosstab(df['var1'], df['var2']) 可以直接从 DataFrame 构建此表。每个单元格都包含同时出现行类别和列类别的观测数量。这就是 scipy.stats.chi2_contingency() 的输入。

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'subscription': np.random.choice(['free', 'basic', 'pro'], 300),
    'churned': np.random.choice(['yes', 'no'], 300, p=[0.3, 0.7])
})

# Build contingency table
ct = pd.crosstab(df['subscription'], df['churned'])
print(ct)
print()
print('Row totals:', ct.sum(axis=1).to_dict())

运行 chi2_contingency()

scipy.stats.chi2_contingency(observed) 接受观测到的列联表(NumPy 数组或 Pandas DataFrame),并返回四个值:卡方统计量、p 值、自由度和预期频数表。零假设是 H₀: the two variables are independent。如果 p ≤ 0.05,我们将拒绝独立性假设,并得出存在统计显著关联的结论。

import pandas as pd
import numpy as np
from scipy import stats

np.random.seed(0)
df = pd.DataFrame({
    'tier': np.random.choice(['free', 'basic', 'pro'], 300),
    'churned': np.random.choice(['yes', 'no'], 300, p=[0.3, 0.7])
})

ct = pd.crosstab(df['tier'], df['churned'])
chi2, p, dof, expected = stats.chi2_contingency(ct)

print(f'Chi-squared: {chi2:.3f}')
print(f'p-value: {p:.4f}')
print(f'Degrees of freedom: {dof}')
print('Association significant?', 'Yes' if p < 0.05 else 'No')

理解预期频数

预期频数表示在两个变量完全独立的情况下,各单元格中的数量应呈现的样子。对于每个单元格,expected = (row_total × column_total) / grand_total。卡方统计量通过计算观测数量与预期数量之间差值的平方和,再用预期数量进行标准化来衡量差异。卡方值较大表示观测数量明显偏离独立性;卡方值较小表示数据与独立性相符。

import pandas as pd
import numpy as np
from scipy import stats

observed = pd.DataFrame({
    'converted': [50, 30],
    'not_converted': [150, 170]
}, index=['variant', 'control'])

chi2, p, dof, expected = stats.chi2_contingency(observed)
print('Observed:')
print(observed)
print()
print('Expected (under independence):')
print(pd.DataFrame(expected,
                   index=observed.index,
                   columns=observed.columns).round(1))

卡方检验中的自由度

对于有 r 行和 c 列的列联表,自由度为 df = (r-1) × (c-1)。2×2 表的 df=1;3×4 表的 df=6。卡方的临界值会随自由度增加而增大:当 df=1 且 α=0.05 时,临界值约为 3.84;当 df=6 且 α=0.05 时,临界值约为 12.6。chi2_contingency 函数会自动处理这些计算,但了解公式有助于您理解:为什么较大列联表中的卡方值必须更大,结果才会达到显著。

from scipy import stats

# Critical values of chi-squared for alpha=0.05
for df in [1, 2, 3, 4, 6, 9]:
    critical = stats.chi2.ppf(0.95, df=df)
    print(f'df={df}: critical value = {critical:.3f}')

最小预期频数假设

只有当所有预期频数至少为 5时,卡方检验才可靠(有些资料认为只要至少为 1,且低于 5 的单元格不超过 20% 即可)。较小的预期数量会使卡方近似不准确。当这一假设不成立时,对于 2×2 表请使用 Fisher 精确检验(scipy.stats.fisher_exact()),或合并少数类别以增加预期数量。请始终检查 chi2_contingency 返回的预期频数矩阵。

import numpy as np
from scipy import stats

# Table with small expected counts
observed = np.array([[2, 3], [100, 95]])
chi2, p, dof, expected = stats.chi2_contingency(observed)

print('Expected frequencies:')
print(expected)
print('Min expected:', expected.min())

if expected.min() < 5:
    print('Warning: Expected frequency < 5. Use Fisher\'s exact test.')
    odds_ratio, p_fisher = stats.fisher_exact(observed)
    print(f'Fisher\'s exact p: {p_fisher:.4f}')

小样本的 Fisher 精确检验

scipy.stats.fisher_exact(table) 会在独立性零假设下,精确计算观测到给定 2×2 表(或更极端表格)的概率,不使用任何近似。无论样本量或单元格数量如何,它始终有效——p 值是精确值而非近似值。代价是计算量较大:该方法会枚举所有可能的表格,因此在总数量较大时速度较慢。对于任何单元格数量低于 5 的 2×2 表,请始终优先使用 Fisher 精确检验,而不是卡方检验。

import numpy as np
from scipy import stats

# Small clinical trial: treatment vs. outcome
observed = np.array([
    [3, 12],   # treated: 3 improved, 12 did not
    [1, 18]    # control: 1 improved, 18 did not
])

odds_ratio, p = stats.fisher_exact(observed, alternative='two-sided')
print(f'Odds ratio: {odds_ratio:.3f}')
print(f'p-value: {p:.4f}')
print('Association significant?', 'Yes' if p < 0.05 else 'No')

衡量关联强度:Cramér's V

与 t 检验中的 Cohen's d 类似,仅凭卡方统计量无法衡量关联的强度,因为它会受到样本量影响而增大。Cramér's V 将卡方值标准化到 0–1 的范围:0 表示无关联,1 表示完全关联。V = sqrt(chi2 / (n × min(r-1, c-1)))。参考标准为:< 0.1 表示弱,0.1–0.3 表示中等,> 0.3 表示强。请始终将 Cramér's V 与 p 值一同报告,以完整呈现结果。

import pandas as pd
import numpy as np
from scipy import stats

np.random.seed(0)
df = pd.DataFrame({
    'tier': np.random.choice(['free', 'basic', 'pro'], 500),
    'churn': np.random.choice(['yes', 'no'], 500, p=[0.3, 0.7])
})
ct = pd.crosstab(df['tier'], df['churn'])

chi2, p, dof, _ = stats.chi2_contingency(ct)
n = ct.sum().sum()
min_dim = min(ct.shape[0]-1, ct.shape[1]-1)
cramers_v = np.sqrt(chi2 / (n * min_dim))

print(f'chi2={chi2:.3f}, p={p:.4f}')
print(f'Cramer\'s V: {cramers_v:.4f}')
print('Association strength:', 'strong' if cramers_v > 0.3 else 'moderate' if cramers_v > 0.1 else 'weak')

卡方拟合优度检验

卡方检验的另一种应用是拟合优度检验:它检验观测频数是否符合假设的分布。例如:“骰子点数是否均匀分布?”或“我们的网站流量是否遵循预期的星期分布模式?”请使用 scipy.stats.chisquare(f_obs, f_exp),其中 f_exp 是预期频数。零假设是数据遵循指定的分布。

import numpy as np
from scipy import stats

# Observed: counts for each weekday over 70 weeks
observed = np.array([980, 1050, 1020, 1080, 1120, 850, 900])
# Expected: uniform distribution
expected = np.full(7, observed.sum() / 7)

chi2, p = stats.chisquare(f_obs=observed, f_exp=expected)
print('Observed:', observed)
print('Expected (uniform):', expected.round(0))
print(f'chi2={chi2:.3f}, p={p:.4f}')
print('Uniform?', 'Yes' if p > 0.05 else 'No - some days significantly busier')

使用 pd.crosstab 和 normalise

报告卡方结果时,显示比例而不是原始数量会很有帮助,这样读者可以看到相对关联。pd.crosstab(var1, var2, normalize='index') 显示行比例(每个行类别中,各有多少比例落入每个列类别)。normalize='columns' 显示列比例。在运行检验之前直观比较这些比例,有助于您预判关联的方向,并结合上下文解读结果。

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'tier': np.random.choice(['free', 'basic', 'pro'], 300),
    'churned': np.random.choice(['yes', 'no'], 300, p=[0.3, 0.7])
})

# Row-proportions: churn rate within each tier
prop_table = pd.crosstab(df['tier'], df['churned'], normalize='index')
print('Churn rate by tier:')
print((prop_table * 100).round(1))

A/B 检验中的卡方检验

卡方检验是A/B 检验转化率的标准检验方法。请构建一个 2×2 列联表,其中行 =(对照组、变体组),列 =(已转化、未转化)。卡方检验可以告诉您两组之间的转化率是否存在显著差异。对于大样本,这等价于双比例 z 检验。对于小样本(任何预期数量 < 5),请改用 Fisher 精确检验。

import numpy as np
from scipy import stats

# A/B test: control vs. variant, conversions vs. non-conversions
control_conv = 45
control_total = 500
variant_conv = 63
variant_total = 500

contingency = np.array([
    [control_conv, control_total - control_conv],
    [variant_conv, variant_total - variant_conv]
])

chi2, p, dof, expected = stats.chi2_contingency(contingency)
print(f'Control rate: {control_conv/control_total:.1%}')
print(f'Variant rate: {variant_conv/variant_total:.1%}')
print(f'p-value: {p:.4f}')
print('Variant significantly better?', 'Yes' if p < 0.05 else 'No')

快速检查

检验您对本课数据分析概念的理解。

课程回顾

本课中您学到了:pd.crosstab() + chi2_contingency() 根据观测频数与预期频数的比较,检验两个分类变量是否相互独立;当预期数量低于 5 时,Fisher 精确检验是稳妥的替代方法;Cramér's V 可以独立于样本量衡量关联强度。接下来,我们将使用 ANOVA 和事后检验比较三个或更多组的均值。

免费开始

用 AI 导师学习 Python — 免费

在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。

课程
30
课程
120

常见问题解答

「独立性的卡方检验」课时是免费的吗?

是的 — 「独立性的卡方检验」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Pandas & NumPy Academy 课程的其余内容,请升级到 CoddyKit PRO。 Pandas & NumPy Academy 课程共包含 4 节课。

「独立性的卡方检验」这节课中我会学到什么?

使用列联表频数表中的 chi2_contingency,检验两个分类变量是否相互独立 你通过在浏览器中直接运行的动手代码来练习 Pandas & NumPy Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Pandas & NumPy Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Pandas & NumPy Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。

「独立性的卡方检验」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Pandas & NumPy Academy 课中编写并运行代码吗?

能。每节 Pandas & NumPy Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 描述性统计与正态性检验
  2. 用于比较均值的 t 检验
  3. 独立性的卡方检验
  4. ANOVA 与事后检验
← 返回 Pandas & NumPy Academy