You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python Pandas量化两列数据分布的统计差异?

Hey there! 既然你已经用柱状图直观对比了两列分类数据的分布,咱们来聊聊怎么量化它们的统计差异。针对你给出的示例数据,我整理了几种实用的方法,从统计检验到差异指标,再到结合可视化的优化:

1. 统计显著性检验:判断分布是否真的不同

如果想知道两列数据的分布是否存在统计学上的显著差异,分类数据最常用的是卡方检验(Chi-square Test),它通过对比观测频数和期望频数来判断分布是否一致。

代码实现:

import pandas as pd
from scipy.stats import chi2_contingency

# 你的示例数据
df = pd.DataFrame({'a':['cat','dog','bird','cat','dog','dog','dog'], 'b':['cat','cat','cat','bird','dog','dog','dog']})

# 第一步:生成列联表(Contingency Table),展示两列数据的交叉频数
contingency_table = pd.crosstab(df['a'], df['b'])
print("列联表:")
print(contingency_table)

# 第二步:执行卡方检验
chi2_stat, p_val, dof, expected_freq = chi2_contingency(contingency_table)

# 输出结果
print(f"\n卡方统计量: {chi2_stat:.4f}")
print(f"p值: {p_val:.4f}")
print(f"自由度: {dof}")
print("期望频数(如果分布一致的理想情况):")
print(expected_freq.round(2))

结果解释:

  • p值是核心判断依据:如果p值 < 0.05(常用的显著性水平),说明两列分布存在显著差异;反之则没有足够证据证明差异存在。
  • 你的示例数据中,p值大约是0.65,说明这两列的分布没有显著统计差异。

如果你偏好更稳健的检验,也可以用对数似然比检验,只需要在chi2_contingency中添加参数lambda_='log-likelihood'即可,逻辑和卡方检验一致。

2. 量化差异大小:描述差异的程度

如果不需要显著性判断,只是想直观描述两列分布的差异程度,可以用这些指标:

相对频率差异

直接计算每个类别在两列中的占比差值,能清晰看到每个类别的差异方向和大小:

# 计算每列的相对频率(占比)
freq_a = df['a'].value_counts(normalize=True).sort_index()
freq_b = df['b'].value_counts(normalize=True).sort_index()

# 合并并计算差异
freq_diff = pd.DataFrame({
    'a占比': freq_a, 
    'b占比': freq_b, 
    'a-b差值': freq_a - freq_b
}).round(4)

print("\n相对频率差异:")
print(freq_diff)

结果中,a-b差值为正表示该类别在a列占比更高,负则相反。

KL散度(Kullback-Leibler Divergence)

衡量两个概率分布的“距离”,注意它是非对称的(即从a到b的差异和从b到a的差异结果不同):

from scipy.special import rel_entr

# 计算KL散度:D(a||b) 表示用b的分布去拟合a的分布时的信息损失
kl_ab = sum(rel_entr(freq_a, freq_b))
# 计算KL散度:D(b||a)
kl_ba = sum(rel_entr(freq_b, freq_a))

print(f"\nKL散度(a相对于b): {kl_ab:.4f}")
print(f"KL散度(b相对于a): {kl_ba:.4f}")

数值越大,说明两个分布的差异越大。

Jensen-Shannon散度

是KL散度的对称版本,取值范围在0到1之间,越接近1表示差异越大:

from scipy.spatial.distance import jensenshannon

js_div = jensenshannon(freq_a, freq_b)
print(f"\nJensen-Shannon散度: {js_div:.4f}")
3. 结合可视化:让差异更直观

既然你已经画了柱状图,可以在图上标注频数或差异值,让可视化更有信息量:

import matplotlib.pyplot as plt

# 先计算每列的频数
counts_a = df['a'].value_counts().sort_index()
counts_b = df['b'].value_counts().sort_index()

# 绘图:调整柱子宽度避免重叠
fig, ax = plt.subplots()
bar_width = 0.35
x = range(len(counts_a.index))

# 绘制两列的柱状图
bars_a = ax.bar([i - bar_width/2 for i in x], counts_a, width=bar_width, color='blue', alpha=0.8, label='a')
bars_b = ax.bar([i + bar_width/2 for i in x], counts_b, width=bar_width, color='maroon', alpha=1, label='b')

# 标注每个柱子的频数
for bar in bars_a:
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2., height,
            f'{height}', ha='center', va='bottom')

for bar in bars_b:
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2., height,
            f'{height}', ha='center', va='bottom')

# 标注两列的差值
for i in x:
    diff = counts_a.iloc[i] - counts_b.iloc[i]
    ax.text(i, max(counts_a.iloc[i], counts_b.iloc[i]) + 0.1,
            f'diff: {diff}', ha='center', va='bottom', color='red')

# 调整坐标轴和图例
ax.set_xticks(x)
ax.set_xticklabels(counts_a.index)
ax.legend()
plt.show()

这样你的柱状图不仅能看到分布,还能直接看到每个类别的数量差异,一目了然。

内容的提问来源于stack exchange,提问作者jxn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:50:50