最强总结!十大统计检验方法 !!

文摘   2024-10-09 11:36   北京  

哈喽,我是小白~

今儿和大家聊聊统计检验方法的一些内容,在前几天已经分享过一次,今儿进一步对一些内容进行细化,让大家有一个更加深刻的理解。

统计检验方法能够帮助验证模型的性能差异是否具备统计显著性,避免结果偶然。通过这些方法,可以确保实验结果具有可重复性和稳健性,从而支持科学结论的有效性。此外,统计检验还可以帮助发现不同模型、特征或算法改进的真实影响,避免误导性优化。

今天分享的内容,涉及到:

  • t 检验(t-test)
  • 卡方检验(Chi-Square Test)
  • 方差分析(ANOVA)
  • Mann-Whitney U 检验
  • Kolmogorov-Smirnov 检验(K-S 检验)
  • Wilcoxon 符号秩检验
  • Kruskal-Wallis 检验
  • Fisher 精确检验
  • McNemar 检验
  • Cochran's Q 检验

一起来看下细节~

1. t 检验(t-test)

t 检验是用于比较两个样本均值的假设检验方法,假设数据服从正态分布。它包括单样本 t 检验、独立样本 t 检验和配对样本 t 检验。

原理

t 检验基于样本均值与总体均值的差异,考虑了样本标准误的影响。其关键思想是,如果两个样本均值的差异在合理范围内,则认为它们来自相同的总体,否则就认为它们有显著差异。

核心公式

独立样本 t 检验的统计量为:

  •  分别是样本 1 和样本 2 的均值
  •  分别是两个样本的方差
  •  是两个样本的样本数量

公式推导

假设两个独立样本来自相同的总体。根据中心极限定理,样本均值近似服从正态分布。假设样本方差未知,我们使用样本方差代替总体方差进行估计。

1. 均值的差异:样本均值的差异为 

2. 标准误的计算:对于两个样本均值的标准误,公式为

3. t 值计算:将均值差异除以标准误,得到 t 值:

4. t 分布:通过计算得到的 t 值,可以根据 t 分布表来确定 p 值,p 值反映了均值差异的显著性。

5. 自由度:t 分布的自由度为 

如果计算出的 t 值超过临界值,则拒绝零假设,即认为两个样本的均值显著不同。

Python实现

假设我们想检验一种新的教学方法是否比传统方法更有效。在这个实验中,我们有两个独立的学生组:一个组使用传统教学方法,另一个组使用新的教学方法。我们想比较这两组学生在最终考试中的平均成绩,以确定新的教学方法是否有显著提高学生的成绩。

数据集描述:

  • 传统方法组:学生使用传统教学方法,他们的成绩从某个正态分布中随机生成。
  • 新方法组:学生使用新的教学方法,他们的成绩从另一个正态分布中随机生成。

我们将使用两个样本的独立 t 检验来比较这两个组的平均成绩,假设新方法能提高学生的平均分数。

import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats

# 设置随机种子,保证结果的可重复性
np.random.seed(42)

# 生成虚拟数据
# 传统方法组,正态分布,均值为70,标准差为10,样本量为50
traditional_scores = np.random.normal(loc=70, scale=10, size=50)

# 新方法组,正态分布,均值为75,标准差为12,样本量为50
new_method_scores = np.random.normal(loc=75, scale=12, size=50)

# 独立样本t检验
t_stat, p_value = stats.ttest_ind(traditional_scores, new_method_scores)

# 打印t检验结果
print(f"T-statistic: {t_stat:.3f}, P-value: {p_value:.3f}")

# 创建一个图形,包含多个子图
plt.figure(figsize=(128))

# 1. 箱线图 (Boxplot)
plt.subplot(221)
sns.boxplot(data=[traditional_scores, new_method_scores], palette="Set2")
plt.xticks([01], ['Traditional Method''New Method'])
plt.title('Boxplot of Scores')

# 2. 直方图 (Histogram)
plt.subplot(222)
sns.histplot(traditional_scores, color="blue", label="Traditional", kde=True, bins=10, stat="density", alpha=0.6)
sns.histplot(new_method_scores, color="red", label="New Method", kde=True, bins=10, stat="density", alpha=0.6)
plt.legend()
plt.title('Histogram of Scores')
plt.xlabel('Score')
plt.ylabel('Density')

# 3. 均值柱状图 (Barplot of Means with Standard Error)
means = [np.mean(traditional_scores), np.mean(new_method_scores)]
errors = [stats.sem(traditional_scores), stats.sem(new_method_scores)]  # 标准误
plt.subplot(212)
plt.bar([01], means, yerr=errors, capsize=10, color=["blue""red"], alpha=0.7)
plt.xticks([01], ['Traditional Method''New Method'])
plt.title('Mean Scores with Standard Error')
plt.ylabel('Mean Score')

# 调整布局,防止图像重叠
plt.tight_layout()

# 展示图形
plt.show()
  1. 数据生成:我们生成了两组虚拟数据,分别代表传统方法组和新方法组的考试成绩。

  2. t 检验:通过 scipy.stats.ttest_ind 进行独立样本的 t 检验,检验两组平均值是否有显著差异。

  • 箱线图:展示两组的成绩分布,便于观察中位数、四分位距和离群点。可以清晰地展示数据的集中趋势和离散趋势,帮助判断数据的分布和潜在的异常值。
  • 直方图:展示两组成绩的频率分布,并叠加核密度估计曲线,帮助观察数据的形态及正态性。结合核密度估计曲线,能够显示数据的分布形状,便于验证假设数据的正态性,这对于 t 检验非常重要。
  • 均值柱状图:展示每组的平均分和标准误,直观地比较均值的差异。结合标准误误差条,能直观地展示两组均值的差异及其不确定性,从而帮助评估组间差异的大小。

2. 卡方检验(Chi-Square Test)

卡方检验用于判断两个分类变量之间是否存在统计显著的关系。常用于频数数据或分类数据的独立性检验和拟合优度检验。

原理

卡方检验通过比较观测值与期望值,衡量分类变量的独立性。如果实际观测值与期望值的差异很大,则认为变量之间存在关联。

核心公式

卡方统计量的计算公式为:

  •  是观察值(观测频数)
  •  是期望值(期望频数)

公式推导

1. 零假设:假设分类变量是独立的。

2. 期望频数的计算:期望频数  的计算为:

3. 差异计算:计算每个单元格的观测频数  和期望频数  的差异平方。

4. 归一化差异:用期望频数  归一化差异,计算卡方统计量。

5. 卡方分布:最终的卡方统计量服从卡方分布,其自由度为 ,其中  是行数, 是列数。

通过查卡方分布表,若卡方统计量超出临界值,则拒绝零假设,认为两个变量不独立。

Python实现

有一组虚拟数据集,包含两个分类变量:产品类型(A, B, C)和客户满意度(高, 中, 低)。我们想检验产品类型与客户满意度是否存在显著的关联。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import chi2_contingency
from statsmodels.graphics.mosaicplot import mosaic

# 生成虚拟数据
np.random.seed(42)
data = pd.DataFrame({
    'Product_Type': np.random.choice(['A''B''C'], size=300, p=[0.30.40.3]),
    'Customer_Satisfaction': np.random.choice(['High''Medium''Low'], size=300, p=[0.40.30.3])
})

# 生成交叉表
contingency_table = pd.crosstab(data['Product_Type'], data['Customer_Satisfaction'])

# 卡方检验
chi2, p, dof, expected = chi2_contingency(contingency_table)

# 输出结果
print(f"Chi-Square Test Statistic: {chi2}")
print(f"P-value: {p}")
print(f"Degrees of Freedom: {dof}")
print(f"Expected Frequencies: \n{expected}")

# 创建图像
fig, axes = plt.subplots(22, figsize=(1410))

# 1. 分组柱状图
contingency_table.plot(kind='bar', stacked=False, ax=axes[00], color=['#FF6F61''#6B5B95''#88B04B'])
axes[00].set_title('Grouped Bar Plot')
axes[00].set_ylabel('Count')
axes[00].set_xlabel('Product Type')

# 2. 热力图显示观察值和期望值的差异
sns.heatmap(contingency_table - expected, annot=True, cmap='coolwarm', ax=axes[01])
axes[01].set_title('Heatmap of Observed vs Expected')

# 3. 马赛克图
mosaic(data, ['Product_Type''Customer_Satisfaction'], ax=axes[10], title='Mosaic Plot')

# 4. 堆积柱状图
contingency_table.plot(kind='bar', stacked=True, ax=axes[11], color=['#FFA07A''#20B2AA''#9370DB'])
axes[11].set_title('Stacked Bar Plot')
axes[11].set_ylabel('Count')
axes[11].set_xlabel('Product Type')

# 调整布局
plt.tight_layout()
plt.show()

  1. 分组柱状图:该图展示了每种产品类型下客户满意度的分布,帮助我们直观比较不同分类的观测频次。

  2. 热力图:展示了观察值与期望值之间的差异,颜色鲜明的热力图可以直观地显示是否有显著偏差。

  3. 马赛克图:展示了类别之间的相对比例,图形的面积代表不同类别组合的比例,是一个直观的方式来展示两个分类变量之间的关系。

  4. 堆积柱状图:展示了不同类别之间的累积分布,便于查看各类别组合的总计情况。

这些数据分析图形综合展示了分类变量的频数分布、期望与实际值的差异,以及两者的关联性。

3. 方差分析(ANOVA)

方差分析用于比较三个或更多样本的均值差异,常用于多组数据的比较。单因素 ANOVA 用于单一因子对响应变量的影响分析。

原理

ANOVA 通过比较组间方差和组内方差,判断组间的均值差异是否显著。组间方差大,说明不同组之间差异显著;组内方差反映了组内个体之间的波动。

核心公式

单因素方差分析的 F 值为:

  • :组间均方
  • :组内均方
  • :组间平方和
  • :组内平方和

公式推导

1. 零假设:假设所有组的均值相等,即 

2. 组间平方和(SSB):计算各组均值和总体均值的偏差平方:

3. 组内平方和(SSW):计算组内数据和各组均值的偏差平方:

4. 均方计算:组间均方(MSB)和组内均方(MSW)分别为组间平方和和组内平方和除以自由度:

5. F 值计算:F 统计量为组间均方与组内均方的比值:

6. F 分布:F 统计量服从  分布,通过查表判断是否拒绝零假设。

Python实现

使用 ANOVA 来检验不同教学方法的平均成绩是否存在显著差异。为此,我们将生成虚拟数据,并通过可视化展示数据和结果。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as stats
from statsmodels.formula.api import ols
from statsmodels.stats.anova import anova_lm
import statsmodels.api as sm

# 设置随机种子,生成可重复的虚拟数据
np.random.seed(42)

# 创建虚拟数据集:三个教学方法,每个方法下有 30 名学生的成绩
n_students = 30
method_A = np.random.normal(loc=75, scale=10, size=n_students)  # 方法 A 的成绩
method_B = np.random.normal(loc=80, scale=12, size=n_students)  # 方法 B 的成绩
method_C = np.random.normal(loc=70, scale=9, size=n_students)   # 方法 C 的成绩

# 构造数据集
df = pd.DataFrame({
    'Score': np.concatenate([method_A, method_B, method_C]),
    'Method': ['A']*n_students + ['B']*n_students + ['C']*n_students
})

# 1. 方差分析(ANOVA)模型
model = ols('Score ~ Method', data=df).fit()
anova_results = anova_lm(model)

# 2. 提取残差和拟合值
df['Residuals'] = model.resid
df['Fitted'] = model.fittedvalues

# 设置绘图风格
sns.set(style="whitegrid", palette="Set2")

# 创建画布和子图 (3 行 1 列)
fig, axes = plt.subplots(31, figsize=(1015))

# 3. 绘制箱线图(Boxplot):不同教学方法的成绩分布
sns.boxplot(x='Method', y='Score', data=df, ax=axes[0], linewidth=2.5, width=0.5)
axes[0].set_title('Boxplot of Scores by Teaching Method', fontsize=14)
axes[0].set_xlabel('Teaching Method', fontsize=12)
axes[0].set_ylabel('Scores', fontsize=12)

# 4. 绘制均值和标准误差图
mean_se_df = df.groupby('Method').agg(Mean=('Score''mean'), SE=('Score''sem')).reset_index()
axes[1].errorbar(x=mean_se_df['Method'], y=mean_se_df['Mean'], 
                 yerr=mean_se_df['SE'], fmt='o', ecolor='red', capsize=5, markersize=8, color='blue')
axes[1].set_title('Mean and Standard Error of Scores by Teaching Method', fontsize=14)
axes[1].set_xlabel('Teaching Method', fontsize=12)
axes[1].set_ylabel('Mean Scores', fontsize=12)

# 5. 绘制残差图:残差 vs 拟合值
sns.residplot(x='Fitted', y='Residuals', data=df, ax=axes[2], lowess=True
              scatter_kws={'color''purple'}, line_kws={'color''orange''lw'2})
axes[2].set_title('Residual Plot', fontsize=14)
axes[2].set_xlabel('Fitted Values', fontsize=12)
axes[2].set_ylabel('Residuals', fontsize=12)

# 调整图形布局
plt.tight_layout()
plt.show()

# 输出方差分析结果
print("ANOVA Results:\n", anova_results)
  1. 虚拟数据生成:创建了三个不同教学方法的学生成绩数据集,假设方法 A、B 和 C 的成绩分别服从正态分布,均值和标准差不同。

  2. ANOVA 检验:通过 statsmodels 库中的 ols 函数拟合线性模型,使用 anova_lm 进行方差分析。

  • 箱线图:通过箱线图,我们可以看到每个教学方法的成绩分布情况,便于判断是否有极端值以及分布的对称性。
  • 均值和标准误图:展示了每组的平均成绩及其误差范围,帮助我们观察不同教学方法的平均成绩是否存在显著差异。
  • 残差图:通过残差图,我们可以检查方差分析模型的残差是否均匀分布,从而判断模型拟合的合理性。如果残差不随拟合值的增大而增大,模型拟合是合理的。

4. Mann-Whitney U 检验

Mann-Whitney U 检验是一种非参数检验,用于比较两个独立样本的中位数差异。它不要求数据服从正态分布,是 t 检验的非参数替代方法。

原理

该方法基于样本秩的比较,如果两个样本来自同一总体,两个样本的秩序统计量应该混合分布。如果存在中位数差异,则高秩数据偏向某个样本。

核心公式

Mann-Whitney U 检验的 U 值公式为:

  •  是两个样本的大小
  •  是样本 1 的秩和

公式推导

1. 秩赋值:将两个样本数据合并,按大小为它们赋予秩(小值秩小)。 

2. 秩和计算:计算样本 1 和样本 2 的秩和 。 

3. U 值计算

  1. 选择较小的 值,并查表确定其 p 值。

Python实现

两个不同的疗法(治疗A和治疗B),用于治疗患者的慢性疼痛。每个疗法的样本量为50人,并测量了在疗法应用后每个患者的疼痛评分。因为数据不是正态分布的,所以我们选择了非参数检验中的Mann-Whitney U检验来比较这两种疗法的疗效是否有显著差异。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import mannwhitneyu

# 设置随机种子,便于复现
np.random.seed(42)

# 生成两个虚拟的疼痛评分数据集 (非正态分布)
# 疗法A的疼痛评分
treatment_A = np.random.beta(a=2, b=5, size=50) * 10  # Beta分布, 放大到0-10范围
# 疗法B的疼痛评分
treatment_B = np.random.beta(a=2.5, b=4, size=50) * 10

# 创建数据框
data = pd.DataFrame({
    'Pain Score': np.concatenate([treatment_A, treatment_B]),
    'Treatment': ['A'] * 50 + ['B'] * 50
})

# Mann-Whitney U检验
stat, p_value = mannwhitneyu(treatment_A, treatment_B)
print(f'Mann-Whitney U检验的统计量: {stat}, p值: {p_value}')

# 画图
plt.figure(figsize=(1410))

# 子图1:箱线图
plt.subplot(221)
sns.boxplot(x='Treatment', y='Pain Score', data=data, palette='Set2')
plt.title('Boxplot of Pain Scores for Two Treatments', fontsize=14)
plt.xlabel('Treatment', fontsize=12)
plt.ylabel('Pain Score', fontsize=12)

# 子图2:小提琴图
plt.subplot(222)
sns.violinplot(x='Treatment', y='Pain Score', data=data, palette='Set1')
plt.title('Violin Plot of Pain Scores for Two Treatments', fontsize=14)
plt.xlabel('Treatment', fontsize=12)
plt.ylabel('Pain Score', fontsize=12)

# 子图3:累积分布函数(CDF)
plt.subplot(212)
sns.ecdfplot(treatment_A, label='Treatment A', color='blue', lw=2)
sns.ecdfplot(treatment_B, label='Treatment B', color='orange', lw=2)
plt.title('Cumulative Distribution Function (CDF)', fontsize=14)
plt.xlabel('Pain Score', fontsize=12)
plt.ylabel('CDF', fontsize=12)
plt.legend(title='Treatment')

# 调整布局并展示图形
plt.tight_layout()
plt.show()

  1. 箱线图:通过中位数和四分位数来展示数据集的集中趋势和离散程度,帮助快速发现两个疗法的中心趋势是否存在差异。

  2. 小提琴图:相比箱线图,小提琴图增加了数据分布的密度信息,能展示出数据的分布形状和波动情况,帮助发现每组数据的对称性和集中情况。

  3. 累积分布函数图(CDF):展示了每个治疗组在特定疼痛评分下的累积概率,可以帮助我们查看两组数据在每个评分区间的累积分布差异。

这些数据分析图形能够从多个角度验证治疗A和治疗B的疼痛评分是否存在显著差异,结合Mann-Whitney U检验的结果,得出统计结论。

5. Kolmogorov-Smirnov 检验(K-S 检验)

Kolmogorov-Smirnov 检验用于比较两个分布的差异,或检验样本是否来自某个已知分布。K-S 检验常用于正态性检验。

原理

K-S 检验基于两个累积分布函数(CDF)之间的最大差异。通过比较样本分布的累积分布函数和理论分布的累积分布函数,计算最大差异值 

核心公式

K-S 检验统计量为:

  •  分别为两个分布的累积分布函数

公式推导

1. 累积分布函数计算:对于每个样本,计算样本的经验累积分布函数(ECDF)。

2. 最大差异计算:在每个数据点上,计算两个累积分布函数之间的绝对差值,并找到其最大值 

3. 查表:通过查 Kolmogorov 分布表,判断  值是否显著。

Python实现

生成两个虚拟数据集,一个是正态分布,另一个是指数分布,目的是使用 K-S 检验来判断它们的分布是否显著不同。

  • 数据集 1: 正态分布 (均值为 0, 标准差为 1)
  • 数据集 2: 指数分布 (λ = 1)
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import kstest, norm, expon

# 生成虚拟数据
np.random.seed(42)
sample_size = 1000

# 数据集1:正态分布
data_norm = np.random.normal(loc=0, scale=1, size=sample_size)

# 数据集2:指数分布
data_expon = np.random.exponential(scale=1, size=sample_size)

# 执行K-S检验
ks_stat, p_value = kstest(data_norm, 'expon', args=(01))

# 生成x轴
x = np.linspace(-281000)

# 获取累积分布函数 (CDF)
cdf_norm = norm.cdf(x, loc=0, scale=1)
cdf_expon = expon.cdf(x, scale=1)

# 获取概率密度函数 (PDF)
pdf_norm = norm.pdf(x, loc=0, scale=1)
pdf_expon = expon.pdf(x, scale=1)

# 绘制图形
fig, ax = plt.subplots(22, figsize=(1210))

# 图1:累积分布函数 (CDF)
ax[00].plot(x, cdf_norm, color='blue', label='Normal CDF', linewidth=2)
ax[00].plot(x, cdf_expon, color='red', label='Exponential CDF', linewidth=2)
ax[00].set_title('Cumulative Distribution Function (CDF)', fontsize=14)
ax[00].legend()
ax[00].grid(True)

# 图2:概率密度函数 (PDF)
ax[01].plot(x, pdf_norm, color='green', label='Normal PDF', linewidth=2)
ax[01].plot(x, pdf_expon, color='orange', label='Exponential PDF', linewidth=2)
ax[01].set_title('Probability Density Function (PDF)', fontsize=14)
ax[01].legend()
ax[01].grid(True)

# 图3:经验累积分布 (Empirical CDF)
ecdf_norm_x, ecdf_norm_y = np.sort(data_norm), np.arange(1, sample_size+1) / sample_size
ecdf_expon_x, ecdf_expon_y = np.sort(data_expon), np.arange(1, sample_size+1) / sample_size

ax[10].step(ecdf_norm_x, ecdf_norm_y, color='blue', label='Empirical CDF (Normal)', where='post')
ax[10].step(ecdf_expon_x, ecdf_expon_y, color='red', label='Empirical CDF (Exponential)', where='post')
ax[10].set_title('Empirical Cumulative Distribution Function', fontsize=14)
ax[10].legend()
ax[10].grid(True)

# 图4:K-S统计量差异点
# 找到差异最大的点(即K-S统计量对应的地方)
ks_diff_point = np.argmax(np.abs(cdf_norm - cdf_expon))

ax[11].plot(x, np.abs(cdf_norm - cdf_expon), color='purple', label='K-S Statistic Difference', linewidth=2)
ax[11].scatter(x[ks_diff_point], np.abs(cdf_norm[ks_diff_point] - cdf_expon[ks_diff_point]), 
                 color='black', zorder=5, label=f'Max Difference: {np.abs(cdf_norm[ks_diff_point] - cdf_expon[ks_diff_point]):.4f}', s=100)
ax[11].set_title('K-S Statistic Difference Plot', fontsize=14)
ax[11].legend()
ax[11].grid(True)

# 显示结果
plt.tight_layout()
plt.show()

# 打印K-S检验结果
print(f"K-S Statistic: {ks_stat:.4f}, P-value: {p_value:.4f}")

虚拟数据生成data_norm 是从正态分布生成的数据集,均值为 0,标准差为 1。data_expon 是从指数分布生成的数据集,参数 λ=1。

  • 图1:累积分布函数 (CDF) 显示两个分布的累积分布曲线,便于直观比较两个分布的差异。
  • 图2:概率密度函数 (PDF) 显示数据的概率分布,查看其不同取值范围内的频率分布情况。
  • 图3:经验累积分布 (Empirical CDF) 是基于实际样本计算的经验累积分布,展示数据的实际分布情况。
  • 图4:K-S 统计量差异点图,展示了累积分布函数的差异,并标出了差异最大的点,该点对应 K-S 检验中的统计量。

6. Wilcoxon 符号秩检验

Wilcoxon 符号秩检验是一种非参数检验,用于比较两个相关样本的中位数差异。通常用于配对样本的比较。

原理

该检验根据两个样本之间的差异值,对差异值进行排序并赋予秩值,判断正负差异是否显著不同。

核心公式

Wilcoxon 符号秩检验的统计量  为:

公式推导

1. 差异计算:对于配对样本,计算每对样本的差异值 

2. 秩赋值:将差异值按绝对值排序,赋予秩值。

3. 符号赋值:根据差异的符号,将正差异和负差异分别赋值。

4. 统计量计算:计算正差异的秩和 ,并通过查表判断显著性。

Python实现

假设我们有一组患者,在服用两种不同的药物治疗前后,我们记录了他们的体重变化。我们想使用 Wilcoxon 符号秩检验来比较两种药物对体重的影响是否有显著差异。

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.stats import wilcoxon

# 生成虚拟数据集
np.random.seed(42)
n = 30  # 样本数量
before_treatment = np.random.normal(7510, n)  # 治疗前体重
after_treatment = before_treatment + np.random.normal(-25, n)  # 治疗后体重(假设有体重下降)

# 将数据放入DataFrame
df = pd.DataFrame({
    'Before Treatment': before_treatment,
    'After Treatment': after_treatment
})

# 进行Wilcoxon符号秩检验
stat, p_value = wilcoxon(before_treatment, after_treatment)
print(f"Wilcoxon test statistic: {stat}, p-value: {p_value}")

# 画出多个图形在一个图中
plt.figure(figsize=(148))

# 1. 箱线图(展示治疗前后体重的分布)
plt.subplot(221)
sns.boxplot(data=df, palette="Set2")
plt.title('Boxplot of Before and After Treatment')
plt.ylabel('Weight (kg)')
plt.grid(True)

# 2. 密度图(展示数据的概率密度分布)
plt.subplot(222)
sns.kdeplot(before_treatment, label='Before Treatment', color='blue', shade=True)
sns.kdeplot(after_treatment, label='After Treatment', color='red', shade=True)
plt.title('Density Plot of Before and After Treatment')
plt.xlabel('Weight (kg)')
plt.legend()
plt.grid(True)

# 3. 散点图(展示治疗前后配对样本的体重变化)
plt.subplot(223)
plt.scatter(np.arange(n), before_treatment, color='blue', label='Before Treatment')
plt.scatter(np.arange(n), after_treatment, color='red', label='After Treatment')
for i in range(n):
    plt.plot([i, i], [before_treatment[i], after_treatment[i]], color='gray', linestyle='--')
plt.title('Paired Scatter Plot of Before and After Treatment')
plt.xlabel('Sample Index')
plt.ylabel('Weight (kg)')
plt.legend()
plt.grid(True)

# 4. 体重变化的差值分布图
plt.subplot(224)
weight_diff = after_treatment - before_treatment
sns.histplot(weight_diff, kde=True, color='purple', bins=10)
plt.title('Distribution of Weight Change (After - Before)')
plt.xlabel('Weight Change (kg)')
plt.grid(True)

# 调整布局并显示图像
plt.tight_layout()
plt.show()

  1. 箱线图:用于比较两组体重数据的中位数和分布差异。如果箱线图有显著差异,可能说明两组数据存在系统性变化。

  2. 密度图:展示治疗前后体重的分布形态,可以直观地看出数据的集中趋势和分布情况。

  3. 散点图:每个点对应一个患者的体重变化,配对散点图连线显示了每个样本在治疗前后的体重差异,有助于可视化个体变化。

  4. 体重变化的差值分布图:展示体重变化的分布,帮助我们理解总体上是增加还是减少体重。

7. Kruskal-Wallis 检验

Kruskal-Wallis 检验是一种非参数检验方法,用于比较三个或更多独立组的中位数差异,属于非参数方差分析(ANOVA)的扩展版本。它不要求数据服从正态分布,也不需要组间方差齐性假设。

原理

Kruskal-Wallis 检验通过比较样本的秩次,将所有样本的观测值排序并赋予秩次,然后比较不同组的秩和。如果各组的秩和差异显著,则认为这些组的中位数存在显著差异。

核心公式

Kruskal-Wallis 检验的统计量  计算公式为:

  •  是第  组的秩和
  •  是第  组的样本大小
  •  是总样本数,即 

公式推导

1. 秩排序:将所有样本的观测值  (即第  组的第  个观测值)按大小排序,并赋予秩次。如果有相同的观测值,则平均赋秩。

2. 计算秩和:每组数据的秩和  计算为:

3. 统计量  计算:将每组的秩和代入公式,得到统计量 

4. 检验结果:Kruskal-Wallis 检验的统计量  近似服从卡方分布,其自由度为 ,其中  是组数。通过查卡方分布表,判断  是否显著。如果  超过临界值,则拒绝零假设,认为组间存在显著差异。

Python实现

咱们使用虚拟数据集,通过绘制以下数据分析图来说明问题~

  1. 箱线图 (Boxplot):用于显示不同组的数据分布,突出数据的中位数、四分位数和离群值。

  2. 小提琴图 (Violin Plot):展示数据的概率密度分布和数据分布的形状。

  3. 条形图 (Bar Plot):展示不同组的均值和标准误差,可以用来简单展示数据差异。

  4. Kruskal-Wallis检验结果展示图:在图像上标出显著性检验结果。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.stats import kruskal

# 设置随机种子以确保结果可重复
np.random.seed(42)

# 创建虚拟数据集 (3组数据)
group_1 = np.random.normal(loc=5, scale=1.5, size=100)
group_2 = np.random.normal(loc=7, scale=2.0, size=100)
group_3 = np.random.normal(loc=6, scale=1.2, size=100)

# 将数据整合到一个DataFrame中
df = pd.DataFrame({
    'Value': np.concatenate([group_1, group_2, group_3]),
    'Group': ['Group 1']*100 + ['Group 2']*100 + ['Group 3']*100
})

# 进行Kruskal-Wallis检验
stat, p_value = kruskal(group_1, group_2, group_3)

# 创建图形
plt.figure(figsize=(1510))

# 1. 箱线图 (Boxplot)
plt.subplot(221)
sns.boxplot(x='Group', y='Value', data=df, palette='Set2')
plt.title('Boxplot of Groups')
plt.ylabel('Value')
plt.grid(True)

# 2. 小提琴图 (Violin Plot)
plt.subplot(222)
sns.violinplot(x='Group', y='Value', data=df, palette='Set1')
plt.title('Violin Plot of Groups')
plt.ylabel('Value')
plt.grid(True)

# 3. 条形图 (Bar Plot)
plt.subplot(223)
sns.barplot(x='Group', y='Value', data=df, ci='sd', palette='Set3')
plt.title('Bar Plot with Error Bars (Mean ± SD)')
plt.ylabel('Mean Value')
plt.grid(True)

# 4. Kruskal-Wallis 检验结果展示
plt.subplot(224)
sns.boxplot(x='Group', y='Value', data=df, palette='coolwarm')
plt.text(0.5, max(df['Value']) - 1f'Kruskal-Wallis H-statistic: {stat:.2f}', fontsize=12, ha='center')
plt.text(0.5, max(df['Value']) - 2f'p-value: {p_value:.4f}', fontsize=12, ha='center')
plt.title('Kruskal-Wallis Test Result')
plt.ylabel('Value')
plt.grid(True)

# 调整图形布局
plt.tight_layout()
plt.show()

  1. 箱线图 (Boxplot):箱线图可以直观地展示每组数据的中位数、四分位范围和离群值。用于对比不同组数据的离散性及位置差异。

  2. 小提琴图 (Violin Plot):相比箱线图,小提琴图增加了数据的密度估计,使得可以看到每组数据的分布形态,尤其是某些组可能的多峰分布。

  3. 条形图 (Bar Plot):简单展示各组的均值和标准误差,能让人更容易看出数据的集中趋势与变异情况。

  4. Kruskal-Wallis 检验结果展示图:在数据分布图形上标注Kruskal-Wallis检验结果,说明各组之间是否存在显著差异。这种展示方式可以将统计结果和数据分布可视化地结合在一起。

8. Fisher 精确检验

Fisher 精确检验用于处理两个分类变量之间的独立性检验,特别适用于小样本数据或  的列联表。它通过计算每种可能的样本排列概率,准确判断两个变量是否独立。

原理

Fisher 精确检验基于超几何分布,计算在  列联表中,实际观察到的样本排列是否与假设的独立性一致。检验通过所有可能的排列概率之和,得出是否存在显著性差异。

核心公式

Fisher 精确检验的公式为:

  •  是  列联表中的频数
  •  是样本总数,即 
  •  是组合数

公式推导

1. 列联表定义:给定  的列联表:

2. 超几何分布:假设行变量和列变量独立,则样本中的各元素服从超几何分布。列联表中的元素表示抽样结果,而超几何分布计算某个特定组合发生的概率。

3. 组合数计算:Fisher 精确检验通过计算所有可能的列联表配置,并基于每种配置的概率计算总体显著性。

  • 每种可能组合的概率为:

4. p 值计算:将所有更极端的组合概率相加,得到总的 p 值。如果 p 值小于显著性水平 ,则拒绝独立性假设。

Python实现

假设我们想研究一个医院中不同年龄段(青年、中年、老年)患者是否在不同性别(男性、女性)之间的就诊频率有显著差异。

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.stats import fisher_exact
from statsmodels.graphics.mosaicplot import mosaic
from matplotlib import colors

# 设置随机种子
np.random.seed(42)

# 虚拟数据集
data = {
    'Age Group': np.random.choice(['Youth''Middle-aged''Senior'], size=100),
    'Gender': np.random.choice(['Male''Female'], size=100)
}

df = pd.DataFrame(data)

# 选择两个年龄组
df_filtered = df[df['Age Group'].isin(['Youth''Middle-aged'])]

# 生成交叉表
contingency_table = pd.crosstab(df_filtered['Age Group'], df_filtered['Gender'])

# 进行 Fisher 精确检验
oddsratio, p_value = fisher_exact(contingency_table)

print(f'Fisher Exact Test p-value: {p_value}')

# 绘制分析图形
fig, axes = plt.subplots(13, figsize=(186))

# 图1:堆叠条形图
contingency_table.plot(kind='bar', stacked=True, ax=axes[0], color=['#FF9999','#66B3FF'])
axes[0].set_title('Stacked Bar Plot of Age Group vs Gender')
axes[0].set_xlabel('Age Group')
axes[0].set_ylabel('Count')
axes[0].legend(title='Gender')

# 图2:热图
sns.heatmap(contingency_table, annot=True, fmt='d', cmap='YlGnBu', cbar=True, ax=axes[1])
axes[1].set_title('Heatmap of Age Group vs Gender')

# 图3:马赛克图
props = lambda key: {'color': ['#FF9999','#66B3FF'][key[1] == 'Female']}
mosaic(df_filtered, ['Age Group''Gender'], ax=axes[2], properties=props)
axes[2].set_title('Mosaic Plot of Age Group vs Gender')

# 调整布局
plt.tight_layout()
plt.show()

  1. 堆叠条形图:通过条形的高度和颜色来比较不同性别在各个年龄段的分布,观察是否存在显著的比例差异。

  2. 热图:交叉表的频数通过颜色强度展示,便于直观观察哪些组合的频次较高或较低。

  3. 马赛克图:通过各矩形区域的大小展示不同年龄组和性别的组合频率,面积越大,频率越高,清晰呈现比例关系。

9. McNemar 检验

McNemar 检验用于分析配对样本的分类变量,特别是比较两种处理结果在二分类变量上的差异。它常用于评估两种诊断方法或实验前后差异。

原理

McNemar 检验基于配对样本的  列联表,通过比较两种结果的差异,判断处理前后的变化是否显著。特别关注变化的方向和不对称性。

核心公式

McNemar 检验的统计量为:

  •  和  是列联表中配对样本的不同响应数

公式推导

1. 列联表定义:对于配对样本数据,构造  列联表:

其中  和  表示处理前后响应一致的个数, 和  表示响应改变的个数。

2. 检验差异:McNemar 检验重点分析  和  的不对称性,表示样本在两个处理结果之间的不同变化。通过检验  和  的差异是否显著,判断处理是否有效。

3. 统计量计算:计算 McNemar 统计量:

4. 自由度与显著性:该统计量服从自由度为 1 的卡方分布。如果  超过临界值,则认为两个处理结果有显著差异。

Python实现

通过分析一个假设的二分类变量的变化(例如,测试前后某个治疗方法的效果),并展示数据的分布、变化情况及其统计结果。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.stats.contingency_tables import mcnemar

# 设置随机种子以保证结果可重现
np.random.seed(42)

# 生成虚拟数据
# 前后状态: 0 = 未好转, 1 = 好转
data = {
    'Before': [0] * 14 + [1] * 36,  # 14人未好转,36人好转
    'After': [0] * 10 + [1] * 26 + [0] * 14  # 10人好转,26人未好转
}

df = pd.DataFrame(data)

# 进行 McNemar 检验
# 形成 2x2 频数表
table = pd.crosstab(df['Before'], df['After'])
print("2x2 Contingency Table:")
print(table)

# McNemar 检验
result = mcnemar(table, exact=False)
print(f"McNemar Test Results: Statistic = {result.statistic}, p-value = {result.pvalue}")

# 可视化
plt.figure(figsize=(148))

# 堆叠条形图
plt.subplot(221)
sns.countplot(data=df, x='Before', hue='After', palette='bright')
plt.title('Change in Status Before and After Treatment')
plt.xticks([01], ['Not Improved''Improved'])
plt.ylabel('Count')

# 混淆矩阵
plt.subplot(222)
sns.heatmap(table, annot=True, fmt='d', cmap='viridis', cbar=False, xticklabels=['Not Improved''Improved'], yticklabels=['Not Improved''Improved'])
plt.title('Confusion Matrix')
plt.xlabel('Post-Treatment Status')
plt.ylabel('Pre-Treatment Status')

# 比例饼图
plt.subplot(22, (34))
proportions = table.values.flatten()
labels = ['Not Improved-Not Improved''Not Improved-Improved''Improved-Not Improved''Improved-Improved']
plt.pie(proportions, labels=labels, autopct='%1.1f%%', startangle=90, colors=sns.color_palette('bright', len(labels)))
plt.title('Proportional Change in Status')

plt.tight_layout()
plt.show()
  1. 数据生成:使用 NumPy 和 Pandas 创建一个包含治疗前后状态的虚拟数据集。“Before”列表示治疗前的状态,“After”列表示治疗后的状态。

  2. McNemar 检验:使用 statsmodels 库中的 mcnemar 方法进行 McNemar 检验,输出统计量和 p 值,以判断治疗效果的显著性。

  • 堆叠条形图显示治疗前后状态的变化情况,便于直观了解各状态的人数分布。
  • 混淆矩阵用热图表示,帮助理解实际状态与预测状态之间的关系。
  • 比例饼图清晰展示状态变化的比例,增强结果的可读性。

通过 McNemar 检验,我们可以分析治疗前后状态的变化是否具有统计学显著性。图形化分析不仅可以直观呈现数据的分布情况,还可以帮助识别数据中的趋势和模式。

10. Cochran's Q 检验

Cochran's Q 检验是一种非参数检验,用于分析多个相关样本的二分类变量差异。它是 McNemar 检验的扩展,可以处理三个或更多相关样本的情况。

原理

Cochran's Q 检验基于多个相关样本的二分类数据,通过比较不同处理组的二分类结果是否一致,判断不同处理是否存在显著性差异。

核心公式

Cochran's Q 检验的统计量为:

  •  是处理组数
  •  是样本数
  •  是第  组的总响应数
  •  是第  组第  个样本的响应(0 或 1)

公式推导

1. 列联表构造:针对每个样本的多个处理结果,构造  的二分类矩阵,每个元素  代表样本  在处理  下的响应。

2. 总响应数计算:对于每个处理组,计算总响应数 

3. 统计量  计算:通过总响应数和样本的响应差异,计算 Cochran's Q 统计量:

4. 显著性检验:Q 统计量服从自由度为  的卡方分布,通过查表判断是否显著。如果  超过临界值,则不同处理之间存在显著性差异。

Python实现

假设我们有 10 个患者,他们接受了三种不同的治疗(药物A、药物B 和药物C)。对于每个患者,我们记录了每种治疗是否成功(成功用 1 表示,失败用 0 表示)。我们想知道这三种治疗方法的成功率是否有显著差异。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.stats.contingency_tables import cochrans_q

# 生成虚拟数据集
np.random.seed(42)
data = {
    'Patient': ['Patient {}'.format(i+1for i in range(10)],
    'Drug A': np.random.randint(02, size=10),
    'Drug B': np.random.randint(02, size=10),
    'Drug C': np.random.randint(02, size=10)
}

df = pd.DataFrame(data)
df.set_index('Patient', inplace=True)

# Cochran's Q 检验
# Reshape data for Cochran's Q test
data_for_cochrans_q = df.values.T  # Transpose the DataFrame to get treatments as rows
q_stat, p_value = cochrans_q(data_for_cochrans_q)
print(f"Cochran's Q 检验统计量: {q_stat:.4f}, p值: {p_value:.4f}")

# 可视化分析
fig, axs = plt.subplots(22, figsize=(1212))

# 1. 条形图:展示每种治疗的成功率
success_rate = df.mean().sort_values(ascending=False)
axs[00].bar(success_rate.index, success_rate, color=['blue''green''red'])
axs[00].set_title('Comparison of Success Rates for Treatments')
axs[00].set_ylabel('Success Rate')
axs[00].set_ylim(01)

# 2. 热力图:展示每个患者在不同治疗中的结果
sns.heatmap(df, cmap='coolwarm', linewidths=1, linecolor='black', cbar=False, ax=axs[01])
axs[01].set_title('Heatmap of Patient Treatment Outcomes')

# 3. 点图:展示患者在每种治疗中的成功与失败情况
for i, drug in enumerate(df.columns):
    axs[10].scatter(df.index, df[drug] + i * 0.1, label=drug, s=100, alpha=0.8)

axs[10].set_yticks([01])
axs[10].set_yticklabels(['Failure''Success'])
axs[10].legend()
axs[10].set_title('Patient Treatment Outcomes Scatter Plot')

# 4. 箱线图:展示不同治疗结果的分布
df_melt = df.melt(var_name='Drug', value_name='Outcome')
sns.boxplot(x='Drug', y='Outcome', data=df_melt, palette='Set2', ax=axs[11])
axs[11].set_title('Distribution of Treatment Outcomes')

plt.tight_layout()
plt.show()

  1. 条形图:展示了三种药物的成功率对比,能够快速识别哪个药物成功率较高或较低。条形图非常适合展示分类变量的频率或比例,因此我们使用它来比较药物成功率。

  2. 热力图:展示每个患者在不同药物下的成功或失败情况,颜色的变化使得成功与失败的模式更直观。热力图可以清晰地呈现每个患者的结果模式,通过颜色对比,易于观察每种治疗方法的差异。

  3. 点图(Dot Plot):每个患者的每次治疗结果用点表示,多个治疗之间有轻微位移,使得结果的层次更加明显。点图可以展示出每个个体在不同治疗中的详细结果,能够从个体层面上评估成功与失败情况。

  4. 箱线图:展示了不同治疗方法的结果分布。虽然数据是二值的,但仍然可以通过箱线图可视化各组的集中趋势和离散情况。箱线图适合展示数据的分布和离散情况,尽管我们的数据是二进制,但仍然可以用于展示治疗效果的波动。

通过这些图形和Cochran's Q检验结果,我们可以得出对三种药物的成功率差异的结论。如果p值小于显著性水平(通常是0.05),则说明三种药物的成功率有显著差异。

最后

以上就是今天所有的内容了。
如果对你来说比较有用,记得点赞、收藏,慢慢学习~
下期会有更多干货等着你!~


Python和机器学习初学者
Python和机器学习分享,只写干货,一起学习~
 最新文章