在统计学和数据科学中,Contingency Table(列联表) 是一种用来展示两个或多个分类变量之间频率分布的表格,它的核心作用是帮助我们分析这些变量之间是否存在关联性。

在 Python 中,处理列联表和相关分析的工具主要来自以下几个库:
- Pandas: 用于创建和操作列联表。
- SciPy (stats module): 用于执行统计检验,判断变量是否独立。
- Seaborn: 用于可视化列联表(如热图)。
下面我将分步介绍如何使用这些工具。
使用 Pandas 创建列联表
Pandas 提供了非常方便的函数来创建列联表,最常用的是 crosstab() 和 pivot_table()。
示例数据
我们先创建一个示例数据集,来研究性别和吸烟习惯之间的关系。

import pandas as pd
import numpy as np
# 创建一个示例 DataFrame
data = {
'Gender': ['Male', 'Female', 'Male', 'Female', 'Male', 'Female', 'Male', 'Female', 'Male', 'Female'] * 10,
'Smoking_Status': ['Smoker', 'Non-Smoker', 'Non-Smoker', 'Smoker', 'Smoker', 'Non-Smoker', 'Non-Smoker', 'Non-Smoker', 'Smoker', 'Smoker'] * 10
}
df = pd.DataFrame(data)
print("原始数据前10行:")
print(df.head(10))
a) pd.crosstab()
这是创建列联表最直接、最常用的方法。
# 创建一个简单的列联表
# index: 行变量, columns: 列变量
contingency_table = pd.crosstab(index=df['Gender'], columns=df['Smoking_Status'])
print("\n使用 pd.crosstab() 创建的列联表:")
print(contingency_table)
输出:
使用 pd.crosstab() 创建的列联表:
Smoking_Status Non-Smoker Smoker
Gender
Female 50 50
Male 50 50
这个表格显示了每个性别中,吸烟者和非吸烟者的人数。
b) pd.pivot_table()
pivot_table 更灵活,可以用于更复杂的聚合,但创建基础的列联表也很方便。
# 使用 pivot_table 创建列联表
# aggfunc='size' 会计算每个组合的出现次数
contingency_table_pt = pd.pivot_table(
df,
index='Gender',
columns='Smoking_Status',
aggfunc='size',
fill_value=0 # 将缺失值填充为0
)
print("\n使用 pd.pivot_table() 创建的列联表:")
print(contingency_table_pt)
输出结果与 crosstab 相同。
c) 添加边际总和
为了更直观地看到总数,我们可以添加行和列的总和。
# margins=True 会添加行和列的总和
contingency_table_with_margins = pd.crosstab(
index=df['Gender'],
columns=df['Smoking_Status'],
margins=True,
margins_name="Total"
)
print("\n带有边际总和的列联表:")
print(contingency_table_with_margins)
输出:
带有边际总和的列联表:
Smoking_Status Non-Smoker Smoker Total
Gender
Female 50 50 100
Male 50 50 100
Total 100 100 200
使用 SciPy 进行卡方检验 (Chi-Squared Test)
创建列联表后,我们通常想知道这两个变量是否独立。卡方检验 (Chi-Squared Test of Independence) 是最常用的方法。
- 原假设 (H₀): 两个变量是独立的(即性别与吸烟习惯无关)。
- 备择假设 (H₁): 两个变量不是独立的(即性别与吸烟习惯有关)。
p-value(p值)小于我们选择的显著性水平(通常是 0.05),我们就可以拒绝原假设,认为两个变量之间存在显著关联。
from scipy.stats import chi2_contingency
# 使用之前创建的列联表(不带边际总和)
contingency_table_for_test = pd.crosstab(index=df['Gender'], columns=df['Smoking_Status'])
# 执行卡方检验
# chi2: 卡方统计量
# p: p值
# dof: 自由度
# expected: 期望频数表(在原假设下期望的值)
chi2, p, dof, expected = chi2_contingency(contingency_table_for_test)
print("\n--- 卡方检验结果 ---")
print(f"卡方统计量: {chi2:.4f}")
print(f"P值: {p:.4f}")
print(f"自由度: {dof}")
print("\n期望频数表:")
print(expected)
# 解释结果
alpha = 0.05
print(f"\n显著性水平 (alpha): {alpha}")
if p < alpha:
print(f"因为 P值 ({p:.4f}) < {alpha},我们拒绝原假设。")
print("性别与吸烟习惯之间存在显著关联。")
else:
print(f"因为 P值 ({p:.4f}) >= {alpha},我们无法拒绝原假设。")
print("没有足够的证据表明性别与吸烟习惯之间存在关联。")
分析结果:
在我们的示例数据中,因为数据是随机生成的,p-value 会很大(远大于 0.05),我们无法拒绝原假设,认为它们是独立的,如果你修改数据,让男性和女性的吸烟比例有明显差异,p-value 就会变小,从而得出它们存在关联的结论。
使用 Seaborn 进行可视化
“热图”是可视化列联表的最佳方式,颜色深浅可以直观地表示频数的高低。
import seaborn as sns
import matplotlib.pyplot as plt
# 设置绘图风格
sns.set(style="whitegrid")
# 创建热图
plt.figure(figsize=(8, 6))
ax = sns.heatmap(
contingency_table,
annot=True, # 在格子上显示数值
fmt='d', # 数值的格式为整数
cmap='viridis' # 颜色映射
)
ax.set_title('Gender vs. Smoking Status (Heatmap)', fontsize=16)
ax.set_xlabel('Smoking Status', fontsize=12)
ax.set_ylabel('Gender', fontsize=12)
plt.show()
这会生成一个热图,让你一眼就能看出各个组合的频数分布。
总结与工作流程
一个完整的关于 "contingency" 的分析流程如下:
- 加载数据: 使用 Pandas 将你的数据读入 DataFrame。
- 创建列联表: 使用
pd.crosstab()快速创建两个分类变量的交叉表。 - 可视化 (可选但推荐): 使用
seaborn.heatmap()将列联表绘制成热图,进行初步探索。 - 执行统计检验: 使用
scipy.stats.chi2_contingency()对列联表进行卡方检验,以获得 p-value。 - 解释结果: 根据 p-value 和你设定的显著性水平,判断两个变量之间是否存在统计上的显著关联。
核心工具对应关系:
| 任务 | Pandas 函数 | SciPy 函数 | Seaborn 函数 |
|---|---|---|---|
| 创建列联表 | pd.crosstab() |
- | - |
| 统计检验 | - | chi2_contingency() |
- |
| 可视化 | - | - | sns.heatmap() |
掌握了这些工具,你就可以在 Python 中熟练地进行分类变量之间的关联性分析了。
