一、Kruskal-Wallis H 检验核心认知
1. 适用场景(与 ANOVA 检验的区别)
-
传统单因素 ANOVA 检验要求数据满足正态分布和方差齐性,但实际数据(如用户消费金额、产品满意度评分)常存在偏态或异常值,此时需用非参数检验替代;
-
Kruskal-Wallis H 检验(简称 K-W 检验)是非参数版的单因素 ANOVA,核心用于检验:多个独立样本的总体分布是否存在显著差异(无需假设数据正态分布、方差齐性);
-
核心逻辑:将所有样本数据混合排序并分配秩次,通过比较各组的秩和是否存在显著差异,判断组间是否有统计学差异(原假设 H0:各组总体分布相同;备择假设 H1:至少两组总体分布不同)。
2. 关键适用条件
-
样本:3 组及以上独立样本(若仅 2 组,建议用 Mann-Whitney U 检验,即非参数版独立样本 t 检验);
-
数据类型:数值型变量(连续型或离散型有序数据,如评分 1-5 分);
-
核心优势:抗异常值、不依赖分布假设,适用场景比 ANOVA 更广泛(如电商用户不同年龄段的消费差异、不同营销渠道的转化效果对比)。
二、Python 实操:Kruskal-Wallis H 检验完整流程
以 “3 个营销渠道(A/B/C)的用户转化金额” 为例(数据非正态分布,方差不齐,不满足 ANOVA 条件),演示全流程:
1. 数据准备与前提验证
import pandas as pd
import numpy as np
from scipy import stats
import seaborn as sns
import matplotlib.pyplot as plt
\# 设置中文显示
plt.rcParams\['font.sans-serif'] = \['SimHei']
plt.rcParams\['axes.unicode\_minus'] = False
\# 模拟数据(3 个渠道的转化金额,故意加入偏态和异常值)
data = pd.DataFrame({
  'channel': \['A']\*30 + \['B']\*30 + \['C']\*30,
  'conversion\_amount': np.concatenate(\[
  np.random.lognormal(3, 1, 30), # 渠道 A:对数正态分布(偏态)
  np.random.lognormal(2.5, 1.2, 30), # 渠道 B:对数正态分布(偏态+方差更大)
  np.random.lognormal(3.2, 0.8, 30) # 渠道 C:对数正态分布(偏态)
  ])
})
\# 前提验证:1. 正态性检验(Shapiro-Wilk 检验);2. 方差齐性检验(Levene 检验)
for channel in \['A', 'B', 'C']:
  stat\_shapiro, p\_shapiro = stats.shapiro(data\[data\['channel']==channel]\['conversion\_amount'])
  print(f"渠道 {channel} 正态性检验 p 值:{p\_shapiro:.4f}") # 均 05,拒绝正态假设
stat\_levene, p\_levene = stats.levene(
  data\[data\['channel']=='A']\['conversion\_amount'],
  data\[data\['channel']=='B']\['conversion\_amount'],
  data\[data\['channel']=='C']\['conversion\_amount']
)
print(f"方差齐性检验 p 值:{p\_levene:.4f}") # 5,拒绝方差齐性假设
\# 结论:数据不满足 ANOVA 条件,改用 Kruskal-Wallis H 检验
2. Kruskal-Wallis H 检验执行
\# 提取各组数据
group\_a = data\[data\['channel']=='A']\['conversion\_amount']
group\_b = data\[data\['channel']=='B']\['conversion\_amount']
group\_c = data\[data\['channel']=='C']\['conversion\_amount']
\# 执行 Kruskal-Wallis H 检验(scipy 内置函数)
stat\_kw, p\_kw = stats.kruskal(group\_a, group\_b, group\_c)
\# 输出结果
print("="\*50)
print("Kruskal-Wallis H 检验结果")
print("="\*50)
print(f"H 统计量:{stat\_kw:.4f}")
print(f"p 值:{p\_kw:.4f}")
print("="\*50)
\# 结论判断(显著性水平 α=0.05)
alpha = 0.05
if p\_kw (f"p 值({p\_kw:.4f})alpha}),拒绝原假设!")
  print("结论:至少有两个营销渠道的用户转化金额总体分布存在显著差异")
else:
  print(f"p 值({p\_kw:.4f})≥ α({alpha}),不能拒绝原假设!")
  print("结论:三个营销渠道的用户转化金额总体分布无显著差异")
3. 事后检验(关键补充!)
Kruskal-Wallis H 检验仅能判断 “是否存在组间差异”,但无法明确 “哪两组有差异”,需通过事后检验定位:
\# 方法 1:Dunn 检验(最常用,校正多重比较误差)
from statsmodels.stats.multicomp import pairwise\_tukeyhsd
from scipy.stats import rankdata
\# 对所有数据进行秩转换(Dunn 检验基于秩次)
data\['rank'] = rankdata(data\['conversion\_amount'])
\# 执行 Dunn 检验(statsmodels 无直接函数,用 Tukey HSD 替代秩次检验,结果一致)
tukey\_result = pairwise\_tukeyhsd(
  endog=data\['rank'], # 秩次数据
  groups=data\['channel'],
  alpha=0.05
)
print("\n事后检验(Tukey HSD 基于秩次)结果:")
print(tukey\_result)
\# 方法 2:Bonferroni 校正的 Mann-Whitney U 检验(两两对比)
print("\nBonferroni 校正的两两 Mann-Whitney U 检验:")
p\_values = \[]
pairs = \[('A', 'B'), ('A', 'C'), ('B', 'C')]
for pair in pairs:
  stat\_u, p\_u = stats.mannwhitneyu(
  data\[data\['channel']==pair\[0]]\['conversion\_amount'],
  data\[data\['channel']==pair\[1]]\['conversion\_amount']
  )
  p\_corrected = p\_u \* 3 # Bonferroni 校正:3 组共 3 对,乘以 3
  p\_values.append(p\_corrected)
  print(f"{pair\[0]} vs {pair\[1]}:校正后 p 值 = {p\_corrected:.4f}(原 p 值 = {p\_u:.4f})")
  if p\_corrected 
  print(f" → 存在显著差异")
  else:
  print(f" → 无显著差异")
4. 结果可视化(辅助解读)
\# 箱线图:直观展示各组数据分布差异
sns.boxplot(x='channel', y='conversion\_amount', data=data)
plt.title('不同营销渠道用户转化金额分布')
plt.xlabel('营销渠道')
plt.ylabel('转化金额')
plt.show()
\# 秩和条形图:展示各组秩和差异(K-W 检验的核心依据)
rank\_sum = data.groupby('channel')\['rank'].sum().reset\_index()
sns.barplot(x='channel', y='rank', data=rank\_sum)
plt.title('不同营销渠道的秩和对比')
plt.xlabel('营销渠道')
plt.ylabel('秩和(越大表示整体数值越高)')
plt.show()