未记录事实核验 · 状态:维护中

记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。

warning 适用边界与维护说明

本文适合学习卡方分布及其概率计算、模拟、拟合优度检验、独立性检验和总体方差区间估计,示例数据不构成真实药物疗效或人群研究结论。代码宜按正文顺序运行,以复用前文导入的库。卡方可加性要求变量独立;频数检验的卡方近似依赖独立观测和足够的期望频数,拟合优度自由度还须考虑样本估计的参数;总体方差的卡方区间限于独立同分布正态样本。效应量分级仅供示例解释,Pearson残差和样本方差比不能单独判定显著性,不拒绝原假设不等于证明独立或方差相等。正文将班级A的Q-Q图称为“正态性检验”,应理解为图形诊断而非正式检验;可加性示例末尾的结论为固定输出,未随KS检验结果判断,模拟也不能证明分布相同或代替理论证明,两组独立样本的逐项差值不代表生成方法的误差。

🟧 卡方分布(Chi-square Distribution)

abstract 📋 本节概览

  • 🎯 学习目标:掌握卡方分布的定义、性质和应用场景
  • 📚 前置知识:正态分布 | 常见连续分布
  • ⏱️ 预计用时:15分钟

🤔 什么是卡方分布?

note 📖 **卡方分布的定义**

卡方分布是由多个独立标准正态分布的平方和构成的连续概率分布。如果X₁, X₂, ..., Xₖ 是独立的标准正态随机变量,那么它们的平方和 χ² = X₁² + X₂² + ... + Xₖ² 服从自由度为k的卡方分布。

数学表达式:

  • 概率密度函数:$$f(x) = \frac{1}{2^{k/2}\Gamma(k/2)} x^{k/2-1} e^{-x/2}, \quad x > 0$$
  • 记号:$\chi^2 \sim \chi^2(k)$,其中 $k$ 是自由度

重要特性:

  • 只取非负值($x \geq 0$)
  • 右偏分布,随自由度增大趋于对称
  • 均值 = 自由度 $k$
  • 方差 = $2k$

🏗️ 生活中的卡方分布

想象你在做一个"投掷色子"的实验:

example 🎲 **色子公平性检验**

你怀疑一个六面色子是否公平。投掷600次,每个面理论上应该出现100次。实际结果:

  • 面1:95次,面2:110次,面3:85次
  • 面4:120次,面5:90次,面6:100次

卡方统计量 = Σ(观察值-期望值)²/期望值;在公平色子的原假设成立、各次投掷独立且期望频数足够大时,该统计量的分布近似为自由度5的卡方分布。

其他应用场景:

  • 检验药物疗效是否与性别独立
  • 分析不同地区销售数据是否存在显著差异
  • 验证抽样调查结果是否符合总体分布
  • 质量控制中的方差检验

📊 卡方分布的性质和特征

1. 不同自由度的分布形状

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

def plot_chi2_properties():
    """展示卡方分布的基本性质"""
    
    # 设置中文字体
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False
    
    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
    
    # 1. 不同自由度的PDF
    x = np.linspace(0, 25, 1000)
    degrees_of_freedom = [1, 2, 3, 5, 10, 15]
    colors = ['red', 'blue', 'green', 'orange', 'purple', 'brown']
    
    for df, color in zip(degrees_of_freedom, colors):
        pdf = stats.chi2.pdf(x, df)
        ax1.plot(x, pdf, color=color, linewidth=2, label=f'df = {df}')
    
    ax1.set_xlabel('x')
    ax1.set_ylabel('概率密度')
    ax1.set_title('不同自由度的卡方分布')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    ax1.set_xlim(0, 25)
    
    # 2. 均值和方差的变化
    df_range = np.arange(1, 21)
    means = df_range  # 均值 = df
    variances = 2 * df_range  # 方差 = 2*df
    std_devs = np.sqrt(variances)
    
    ax2.plot(df_range, means, 'b-o', label='均值 = df', linewidth=2, markersize=6)
    ax2.plot(df_range, std_devs, 'r-s', label='标准差 = √(2df)', linewidth=2, markersize=6)
    ax2.set_xlabel('自由度 (df)')
    ax2.set_ylabel('数值')
    ax2.set_title('卡方分布的均值与标准差')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    # 3. 累积分布函数
    x_cdf = np.linspace(0, 20, 1000)
    for df, color in zip([1, 3, 5, 10], colors[:4]):
        cdf = stats.chi2.cdf(x_cdf, df)
        ax3.plot(x_cdf, cdf, color=color, linewidth=2, label=f'df = {df}')
    
    ax3.set_xlabel('x')
    ax3.set_ylabel('累积概率')
    ax3.set_title('卡方分布的累积分布函数')
    ax3.legend()
    ax3.grid(True, alpha=0.3)
    ax3.set_xlim(0, 20)
    
    # 4. 分位数图
    percentiles = np.array([0.9, 0.95, 0.975, 0.99, 0.999])
    df_values = [1, 2, 3, 5, 10]
    
    for df in df_values:
        quantiles = stats.chi2.ppf(percentiles, df)
        ax4.plot(percentiles * 100, quantiles, 'o-', label=f'df = {df}', linewidth=2, markersize=6)
    
    ax4.set_xlabel('百分位数 (%)')
    ax4.set_ylabel('分位数值')
    ax4.set_title('卡方分布的分位数')
    ax4.legend()
    ax4.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    # 输出重要性质
    print("卡方分布的重要性质:")
    print("=" * 40)
    print("1. 定义域: x ≥ 0 (只取非负值)")
    print("2. 右偏分布,df增大时趋于对称")
    print("3. 均值 E(X) = df")
    print("4. 方差 Var(X) = 2·df")
    print("5. 标准差 SD(X) = √(2·df)")
    print("6. 模式 Mode = max(0, df-2)")
    print("7. 可加性: 若X₁ ~ χ²(df₁)、X₂ ~ χ²(df₂)且相互独立,则X₁ + X₂ ~ χ²(df₁+df₂)")

plot_chi2_properties()

2. 数值特征验证

def verify_chi2_properties():
    """验证卡方分布的数值特征"""
    
    df_values = [1, 3, 5, 10, 20]
    n_samples = 100000
    
    print("卡方分布数值特征验证:")
    print("=" * 50)
    print("自由度\t理论均值\t模拟均值\t理论方差\t模拟方差\t理论标准差\t模拟标准差")
    print("-" * 80)
    
    np.random.seed(42)
    
    for df in df_values:
        # 生成随机样本
        samples = stats.chi2.rvs(df, size=n_samples)
        
        # 理论值
        theory_mean = df
        theory_var = 2 * df
        theory_std = np.sqrt(2 * df)
        
        # 模拟值
        sim_mean = np.mean(samples)
        sim_var = np.var(samples, ddof=1)
        sim_std = np.std(samples, ddof=1)
        
        print(f"{df}\t{theory_mean:.2f}\t\t{sim_mean:.2f}\t\t{theory_var:.2f}\t\t{sim_var:.2f}\t\t{theory_std:.2f}\t\t{sim_std:.2f}")
    
    # 可加性验证
    print(f"\n可加性验证:")
    print("-" * 30)
    
    df1, df2 = 3, 5
    df_sum = df1 + df2
    
    # 独立生成两个卡方分布
    chi2_1 = stats.chi2.rvs(df1, size=n_samples)
    chi2_2 = stats.chi2.rvs(df2, size=n_samples)
    chi2_sum = chi2_1 + chi2_2
    
    # 直接生成df1+df2的卡方分布
    chi2_direct = stats.chi2.rvs(df_sum, size=n_samples)
    
    print(f"χ²({df1}) + χ²({df2}) 的均值: {np.mean(chi2_sum):.3f}")
    print(f"χ²({df_sum}) 的均值: {np.mean(chi2_direct):.3f}")
    print(f"理论均值: {df_sum}")
    
    print(f"\nχ²({df1}) + χ²({df2}) 的方差: {np.var(chi2_sum, ddof=1):.3f}")
    print(f"χ²({df_sum}) 的方差: {np.var(chi2_direct, ddof=1):.3f}")
    print(f"理论方差: {2 * df_sum}")

verify_chi2_properties()

💻 Python实现详解

1. 基本计算

from scipy import stats
import numpy as np

def chi2_calculations():
    """卡方分布的各种计算"""
    
    df = 5  # 自由度为5
    
    print(f"卡方分布 χ²({df}) 的计算:")
    print("=" * 30)
    
    # 1. 概率密度函数值
    x_values = [1, 3, 5, 7, 10, 15]
    print("概率密度函数值:")
    for x in x_values:
        pdf_value = stats.chi2.pdf(x, df)
        print(f"f({x}) = {pdf_value:.4f}")
    
    # 2. 累积分布函数值
    print(f"\n累积分布函数值:")
    for x in [2, 5, 7, 10, 15]:
        cdf_value = stats.chi2.cdf(x, df)
        print(f"P(X ≤ {x}) = {cdf_value:.4f}")
    
    # 3. 上尾概率(生存函数)
    print(f"\n上尾概率:")
    for x in [5, 7, 10, 15]:
        sf_value = stats.chi2.sf(x, df)
        print(f"P(X > {x}) = {sf_value:.4f}")
    
    # 4. 分位数
    print(f"\n重要分位数:")
    percentiles = [0.9, 0.95, 0.975, 0.99, 0.995, 0.999]
    for p in percentiles:
        quantile = stats.chi2.ppf(p, df)
        print(f"{p:.1%}分位数: {quantile:.4f}")
    
    # 5. 区间概率
    print(f"\n区间概率:")
    intervals = [(2, 8), (3, 10), (5, 15)]
    for a, b in intervals:
        prob = stats.chi2.cdf(b, df) - stats.chi2.cdf(a, df)
        print(f"P({a} ≤ X ≤ {b}) = {prob:.4f}")
    
    # 6. 统计特征
    print(f"\n统计特征:")
    mean = stats.chi2.mean(df)
    var = stats.chi2.var(df)
    std = stats.chi2.std(df)
    
    print(f"均值: {mean:.2f}")
    print(f"方差: {var:.2f}")
    print(f"标准差: {std:.2f}")
    
    return df

df = chi2_calculations()

2. 随机数生成和可视化

def chi2_simulation_and_visualization():
    """卡方分布模拟和可视化"""
    
    df = 8
    n_samples = 10000
    
    print(f"卡方分布 χ²({df}) 模拟分析:")
    print("=" * 35)
    
    # 生成随机数
    np.random.seed(42)
    samples = stats.chi2.rvs(df, size=n_samples)
    
    # 基本统计
    print(f"样本量: {n_samples:,}")
    print(f"样本均值: {np.mean(samples):.3f} (理论值: {df})")
    print(f"样本方差: {np.var(samples, ddof=1):.3f} (理论值: {2*df})")
    print(f"样本标准差: {np.std(samples, ddof=1):.3f} (理论值: {np.sqrt(2*df):.3f})")
    print(f"样本最小值: {np.min(samples):.3f}")
    print(f"样本最大值: {np.max(samples):.3f}")
    
    # 分位数比较
    print(f"\n分位数比较:")
    print("百分位\t样本分位数\t理论分位数\t误差")
    print("-" * 40)
    
    percentiles_check = [0.25, 0.5, 0.75, 0.9, 0.95]
    for p in percentiles_check:
        sample_quantile = np.percentile(samples, p * 100)
        theory_quantile = stats.chi2.ppf(p, df)
        error = abs(sample_quantile - theory_quantile)
        
        print(f"{p:.0%}\t{sample_quantile:.3f}\t\t{theory_quantile:.3f}\t\t{error:.3f}")
    
    # 可视化
    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
    
    # 1. 直方图vs理论PDF
    ax1.hist(samples, bins=50, density=True, alpha=0.7, color='skyblue', edgecolor='black')
    
    x_theory = np.linspace(0, max(samples), 1000)
    pdf_theory = stats.chi2.pdf(x_theory, df)
    ax1.plot(x_theory, pdf_theory, 'r-', linewidth=2, label=f'理论PDF χ²({df})')
    
    ax1.set_xlabel('x')
    ax1.set_ylabel('概率密度')
    ax1.set_title('样本分布 vs 理论分布')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 2. Q-Q图
    from scipy.stats import probplot
    probplot(samples, dist=stats.chi2(df), plot=ax2)
    ax2.set_title('Q-Q图: 样本 vs 理论χ²分布')
    ax2.grid(True, alpha=0.3)
    
    # 3. 经验CDF vs 理论CDF
    x_sorted = np.sort(samples)
    empirical_cdf = np.arange(1, len(x_sorted) + 1) / len(x_sorted)
    theoretical_cdf = stats.chi2.cdf(x_sorted, df)
    
    ax3.plot(x_sorted, empirical_cdf, label='经验CDF', linewidth=1)
    ax3.plot(x_sorted, theoretical_cdf, 'r--', label='理论CDF', linewidth=2)
    ax3.set_xlabel('x')
    ax3.set_ylabel('累积概率')
    ax3.set_title('经验CDF vs 理论CDF')
    ax3.legend()
    ax3.grid(True, alpha=0.3)
    
    # 4. 箱线图分析
    quartiles = np.percentile(samples, [25, 50, 75])
    iqr = quartiles[2] - quartiles[0]
    
    ax4.boxplot(samples, vert=True, patch_artist=True, 
                boxprops=dict(facecolor='lightblue', alpha=0.7))
    ax4.set_ylabel('值')
    ax4.set_title('样本箱线图')
    ax4.grid(True, alpha=0.3)
    
    # 添加统计信息
    ax4.text(1.1, quartiles[1], f'中位数: {quartiles[1]:.2f}', fontsize=10)
    ax4.text(1.1, quartiles[1] - iqr*0.3, f'IQR: {iqr:.2f}', fontsize=10)
    
    plt.tight_layout()
    plt.show()
    
    return samples

samples = chi2_simulation_and_visualization()

🎯 卡方分布的实际应用

1. 拟合优度检验

def goodness_of_fit_test():
    """拟合优度检验案例"""
    
    print("案例1: 色子公平性检验")
    print("=" * 30)
    
    # 投掷600次色子的观察结果
    observed = np.array([95, 110, 85, 120, 90, 100])  # 各面出现次数
    expected = np.array([100, 100, 100, 100, 100, 100])  # 期望次数(公平色子)
    faces = ['面1', '面2', '面3', '面4', '面5', '面6']
    
    print("观察结果:")
    for face, obs, exp in zip(faces, observed, expected):
        print(f"{face}: 观察{obs}次, 期望{exp}次, 偏差{obs-exp:+d}")
    
    # 手动计算卡方统计量
    chi2_manual = np.sum((observed - expected)**2 / expected)
    df = len(observed) - 1  # 本例概率预先指定且未估计额外参数,自由度 = 类别数 - 1
    
    # 使用scipy计算
    chi2_stat, p_value = stats.chisquare(observed, expected)
    
    print(f"\n检验结果:")
    print(f"卡方统计量 χ² = {chi2_stat:.4f}")
    print(f"自由度 df = {df}")
    print(f"p值 = {p_value:.4f}")
    
    # 临界值
    alpha = 0.05
    critical_value = stats.chi2.ppf(1 - alpha, df)
    
    print(f"\n决策 (α = {alpha}):")
    print(f"临界值 χ²_{alpha}({df}) = {critical_value:.4f}")
    
    if chi2_stat > critical_value:
        print(f"拒绝原假设: 色子不公平 (χ² = {chi2_stat:.4f} > {critical_value:.4f})")
    else:
        print(f"不拒绝原假设: 没有足够证据表明色子不公平")
    
    if p_value < alpha:
        print(f"p值判断: 拒绝原假设 (p = {p_value:.4f} < {alpha})")
    else:
        print(f"p值判断: 不拒绝原假设 (p = {p_value:.4f} ≥ {alpha})")
    
    # 可视化
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))
    
    # 观察值vs期望值
    x_pos = np.arange(len(faces))
    width = 0.35
    
    bars1 = ax1.bar(x_pos - width/2, observed, width, label='观察值', alpha=0.7, color='skyblue')
    bars2 = ax1.bar(x_pos + width/2, expected, width, label='期望值', alpha=0.7, color='lightcoral')
    
    ax1.set_xlabel('色子面')
    ax1.set_ylabel('频次')
    ax1.set_title('观察频次 vs 期望频次')
    ax1.set_xticks(x_pos)
    ax1.set_xticklabels(faces)
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 在柱子上标注数值
    for bar1, bar2, obs, exp in zip(bars1, bars2, observed, expected):
        ax1.text(bar1.get_x() + bar1.get_width()/2, bar1.get_height() + 2, 
                str(obs), ha='center', va='bottom', fontsize=10)
        ax1.text(bar2.get_x() + bar2.get_width()/2, bar2.get_height() + 2, 
                str(exp), ha='center', va='bottom', fontsize=10)
    
    # 卡方分布和检验统计量
    x_chi2 = np.linspace(0, 20, 1000)
    pdf_chi2 = stats.chi2.pdf(x_chi2, df)
    
    ax2.plot(x_chi2, pdf_chi2, 'b-', linewidth=2, label=f'χ²({df})分布')
    ax2.axvline(chi2_stat, color='red', linestyle='--', linewidth=2, 
                label=f'检验统计量 = {chi2_stat:.3f}')
    ax2.axvline(critical_value, color='green', linestyle='--', linewidth=2, 
                label=f'临界值 = {critical_value:.3f}')
    
    # 填充拒绝域
    x_reject = x_chi2[x_chi2 >= critical_value]
    pdf_reject = stats.chi2.pdf(x_reject, df)
    ax2.fill_between(x_reject, 0, pdf_reject, alpha=0.3, color='red', label='拒绝域')
    
    ax2.set_xlabel('χ² 值')
    ax2.set_ylabel('概率密度')
    ax2.set_title('卡方检验图示')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    ax2.set_xlim(0, 20)
    
    plt.tight_layout()
    plt.show()

goodness_of_fit_test()

2. 独立性检验

def independence_test():
    """独立性检验案例"""
    
    print("\n\n案例2: 药物疗效与性别独立性检验")
    print("=" * 40)
    
    # 2x2列联表:药物疗效 vs 性别
    # 行:疗效(有效/无效),列:性别(男/女)
    observed_table = np.array([
        [60, 40],   # 有效:男60人,女40人
        [30, 70]    # 无效:男30人,女70人
    ])
    
    # 标签
    effect_labels = ['有效', '无效']
    gender_labels = ['男性', '女性']
    
    print("观察数据 (列联表):")
    print("\t\t男性\t女性\t总计")
    
    row_totals = observed_table.sum(axis=1)
    col_totals = observed_table.sum(axis=0)
    grand_total = observed_table.sum()
    
    for i, effect in enumerate(effect_labels):
        print(f"{effect}\t\t{observed_table[i,0]}\t{observed_table[i,1]}\t{row_totals[i]}")
    
    print(f"总计\t\t{col_totals[0]}\t{col_totals[1]}\t{grand_total}")
    
    # 计算期望频数
    expected_table = np.outer(row_totals, col_totals) / grand_total
    
    print(f"\n期望频数:")
    print("\t\t男性\t女性")
    for i, effect in enumerate(effect_labels):
        print(f"{effect}\t\t{expected_table[i,0]:.1f}\t{expected_table[i,1]:.1f}")
    
    # 卡方检验
    chi2_stat, p_value, dof, expected = stats.chi2_contingency(observed_table, correction=False)  # 使用未作连续性校正的Pearson统计量,与下方标准Cramér's V计算一致
    
    print(f"\n独立性检验结果:")
    print(f"卡方统计量 χ² = {chi2_stat:.4f}")
    print(f"自由度 df = {dof}")
    print(f"p值 = {p_value:.4f}")
    
    # 效应量 - Cramér's V
    n = grand_total
    cramers_v = np.sqrt(chi2_stat / (n * (min(observed_table.shape) - 1)))
    print(f"Cramér's V = {cramers_v:.4f}")
    
    # 判断效应大小
    if cramers_v < 0.1:
        effect_size = "微弱"
    elif cramers_v < 0.3:
        effect_size = "小"
    elif cramers_v < 0.5:
        effect_size = "中等"
    else:
        effect_size = "大"
    
    print(f"效应大小: {effect_size}")
    
    # 决策
    alpha = 0.05
    critical_value = stats.chi2.ppf(1 - alpha, dof)
    
    print(f"\n决策 (α = {alpha}):")
    if p_value < alpha:
        print("拒绝原假设: 药物疗效与性别存在关联")
        print("结论: 药物疗效可能因性别而异")
    else:
        print("不拒绝原假设: 没有足够证据表明药物疗效与性别有关,但不能据此证明二者独立")
        print("结论: 没有足够证据表明疗效与性别有关")
    
    # 可视化
    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
    
    # 1. 堆叠柱状图
    bottom_values = [0, 0]
    colors = ['lightblue', 'lightcoral']
    
    for i, effect in enumerate(effect_labels):
        ax1.bar(gender_labels, observed_table[i], bottom=bottom_values, 
                label=effect, alpha=0.7, color=colors[i])
        # 更新底部值用于堆叠
        if i == 0:
            bottom_values = observed_table[i]
    
    ax1.set_ylabel('人数')
    ax1.set_title('药物疗效按性别分布')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 2. 热图显示观察值
    import matplotlib.colors as mcolors
    
    im1 = ax2.imshow(observed_table, cmap='Blues', aspect='auto')
    ax2.set_xticks(range(len(gender_labels)))
    ax2.set_xticklabels(gender_labels)
    ax2.set_yticks(range(len(effect_labels)))
    ax2.set_yticklabels(effect_labels)
    ax2.set_title('观察频数热图')
    
    # 在格子中添加数值
    for i in range(len(effect_labels)):
        for j in range(len(gender_labels)):
            ax2.text(j, i, f'{observed_table[i, j]}', 
                    ha='center', va='center', fontsize=14, fontweight='bold')
    
    # 3. 残差分析
    residuals = (observed_table - expected_table) / np.sqrt(expected_table)
    
    im2 = ax3.imshow(residuals, cmap='RdBu_r', aspect='auto', vmin=-3, vmax=3)
    ax3.set_xticks(range(len(gender_labels)))
    ax3.set_xticklabels(gender_labels)
    ax3.set_yticks(range(len(effect_labels)))
    ax3.set_yticklabels(effect_labels)
    ax3.set_title('Pearson残差(用于探索偏离,不直接判定单元格显著性)')
    
    # 添加颜色条
    plt.colorbar(im2, ax=ax3, shrink=0.8)
    
    # 在格子中添加残差值
    for i in range(len(effect_labels)):
        for j in range(len(gender_labels)):
            color = 'white' if abs(residuals[i, j]) > 1.5 else 'black'
            ax3.text(j, i, f'{residuals[i, j]:.2f}', 
                    ha='center', va='center', fontsize=12, color=color, fontweight='bold')
    
    # 4. 比例图
    # 计算行百分比(每种疗效中的性别比例)
    row_props = observed_table / row_totals[:, np.newaxis] * 100
    
    x_pos = np.arange(len(effect_labels))
    width = 0.35
    
    bars1 = ax4.bar(x_pos - width/2, row_props[:, 0], width, 
                    label='男性%', alpha=0.7, color='lightblue')
    bars2 = ax4.bar(x_pos + width/2, row_props[:, 1], width, 
                    label='女性%', alpha=0.7, color='lightcoral')
    
    ax4.set_xlabel('疗效')
    ax4.set_ylabel('百分比 (%)')
    ax4.set_title('各疗效组中的性别比例')
    ax4.set_xticks(x_pos)
    ax4.set_xticklabels(effect_labels)
    ax4.legend()
    ax4.grid(True, alpha=0.3)
    
    # 在柱子上标注百分比
    for bar1, bar2, male_pct, female_pct in zip(bars1, bars2, row_props[:, 0], row_props[:, 1]):
        ax4.text(bar1.get_x() + bar1.get_width()/2, bar1.get_height() + 1, 
                f'{male_pct:.1f}%', ha='center', va='bottom', fontsize=10)
        ax4.text(bar2.get_x() + bar2.get_width()/2, bar2.get_height() + 1, 
                f'{female_pct:.1f}%', ha='center', va='bottom', fontsize=10)
    
    plt.tight_layout()
    plt.show()
    
    # 详细解读
    print(f"\n详细解读:")
    print("1. 观察与期望的对比表明治疗效果可能存在性别差异")
    print("2. 男性有效率: {:.1f}%".format(observed_table[0,0]/col_totals[0]*100))
    print("3. 女性有效率: {:.1f}%".format(observed_table[0,1]/col_totals[1]*100))
    
    if abs(residuals[0,0]) > 2:
        print("4. 男性有效人数的Pearson残差绝对值超过2,提示值得进一步检查的偏离,不单独构成显著性结论")
    if abs(residuals[0,1]) > 2:
        print("5. 女性有效人数的Pearson残差绝对值超过2,提示值得进一步检查的偏离,不单独构成显著性结论")

independence_test()

3. 方差齐性检验

def variance_homogeneity_test():
    """方差齐性检验案例"""
    
    print("\n\n案例3: 不同班级成绩方差齐性检验")
    print("=" * 40)
    
    # 模拟三个班级的考试成绩
    np.random.seed(42)
    
    # 假设班级A方差较小,班级B方差适中,班级C方差较大
    class_A = np.random.normal(85, 8, 30)   # 均值85,标准差8
    class_B = np.random.normal(82, 12, 30)  # 均值82,标准差12
    class_C = np.random.normal(80, 15, 30)  # 均值80,标准差15
    
    classes = {'班级A': class_A, '班级B': class_B, '班级C': class_C}
    
    print("各班级成绩统计:")
    print("-" * 30)
    print("班级\t样本量\t均值\t标准差\t方差")
    
    class_stats = {}
    for name, scores in classes.items():
        n = len(scores)
        mean_score = np.mean(scores)
        std_score = np.std(scores, ddof=1)
        var_score = np.var(scores, ddof=1)
        class_stats[name] = {'n': n, 'mean': mean_score, 'std': std_score, 'var': var_score}
        
        print(f"{name}\t{n}\t{mean_score:.1f}\t{std_score:.2f}\t\t{var_score:.2f}")
    
    # Bartlett检验:用于独立正态总体样本,原假设下统计量渐近服从自由度为组数减一的卡方分布
    from scipy.stats import bartlett
    
    bartlett_stat, bartlett_p = bartlett(class_A, class_B, class_C)
    
    print(f"\nBartlett方差齐性检验:")
    print(f"检验统计量: {bartlett_stat:.4f}")
    print(f"p值: {bartlett_p:.4f}")
    
    # Levene检验:对非正态数据通常更稳健,此处使用中位数中心;其统计量采用F分布参照,不是卡方分布参照
    from scipy.stats import levene
    
    levene_stat, levene_p = levene(class_A, class_B, class_C, center='median')
    
    print(f"\nLevene方差齐性检验:")
    print(f"检验统计量: {levene_stat:.4f}")
    print(f"p值: {levene_p:.4f}")
    
    # 决策
    alpha = 0.05
    print(f"\n决策 (α = {alpha}):")
    
    if bartlett_p < alpha:
        print(f"Bartlett检验: 拒绝方差齐性假设 (p = {bartlett_p:.4f} < {alpha})")
    else:
        print(f"Bartlett检验: 不拒绝方差齐性假设 (p = {bartlett_p:.4f} ≥ {alpha})")
    
    if levene_p < alpha:
        print(f"Levene检验: 拒绝方差齐性假设 (p = {levene_p:.4f} < {alpha})")
    else:
        print(f"Levene检验: 不拒绝方差齐性假设 (p = {levene_p:.4f} ≥ {alpha})")
    
    # 可视化
    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
    
    # 1. 箱线图比较
    data_for_boxplot = [class_A, class_B, class_C]
    labels = list(classes.keys())
    
    bp = ax1.boxplot(data_for_boxplot, labels=labels, patch_artist=True)
    colors = ['lightblue', 'lightgreen', 'lightcoral']
    
    for patch, color in zip(bp['boxes'], colors):
        patch.set_facecolor(color)
        patch.set_alpha(0.7)
    
    ax1.set_ylabel('成绩')
    ax1.set_title('各班级成绩分布比较')
    ax1.grid(True, alpha=0.3)
    
    # 2. 方差比较柱状图
    class_names = list(class_stats.keys())
    variances = [class_stats[name]['var'] for name in class_names]
    
    bars = ax2.bar(class_names, variances, alpha=0.7, color=colors)
    ax2.set_ylabel('方差')
    ax2.set_title('各班级成绩方差比较')
    ax2.grid(True, alpha=0.3)
    
    # 在柱子上标注数值
    for bar, var in zip(bars, variances):
        ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5,
                f'{var:.1f}', ha='center', va='bottom', fontsize=12, fontweight='bold')
    
    # 3. 残差图(用于检查正态性假设)
    # 计算标准化残差
    all_residuals = []
    class_labels_expanded = []
    
    for i, (name, scores) in enumerate(classes.items()):
        mean_score = class_stats[name]['mean']
        std_score = class_stats[name]['std']
        residuals = (scores - mean_score) / std_score
        all_residuals.extend(residuals)
        class_labels_expanded.extend([i] * len(scores))
    
    # 按班级绘制残差
    for i, (name, color) in enumerate(zip(class_names, colors)):
        class_residuals = [res for j, res in enumerate(all_residuals) if class_labels_expanded[j] == i]
        ax3.scatter([i] * len(class_residuals), class_residuals, 
                   alpha=0.6, color=color, s=30, label=name)
    
    ax3.set_xticks(range(len(class_names)))
    ax3.set_xticklabels(class_names)
    ax3.set_ylabel('标准化残差')
    ax3.set_title('标准化残差分布')
    ax3.axhline(y=0, color='black', linestyle='-', alpha=0.5)
    ax3.axhline(y=2, color='red', linestyle='--', alpha=0.7)
    ax3.axhline(y=-2, color='red', linestyle='--', alpha=0.7)
    ax3.grid(True, alpha=0.3)
    ax3.legend()
    
    # 4. Q-Q图检验正态性(以班级A为例)
    from scipy import stats as scipy_stats
    scipy_stats.probplot(class_A, dist="norm", plot=ax4)
    ax4.set_title('班级A成绩的Q-Q图 (正态性检验)')
    ax4.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    # 进一步分析
    print(f"\n进一步分析:")
    print("1. 方差差异程度:")
    max_var = max(variances)
    min_var = min(variances)
    variance_ratio = max_var / min_var
    print(f"   最大方差/最小方差 = {variance_ratio:.2f}")
    
    if variance_ratio > 4:
        print("   样本方差比超过4,差异较大;是否拒绝方差齐性假设需结合检验结果及其适用条件")
    elif variance_ratio > 2:
        print("   方差差异中等,需要谨慎解释")
    else:
        print("   样本方差比不超过2,差异相对较小;该比值不能单独证明方差齐性")
    
    print(f"\n2. 实际意义:")
    print("   - 如果方差不齐,说明不同班级学生成绩的离散程度不同")
    print("   - 这可能反映了教学方法、学生基础等因素的影响")
    print("   - 在进行方差分析前需要考虑方差齐性假设是否满足")

variance_homogeneity_test()

🧮 卡方分布的理论应用

1. 样本方差的分布

def sample_variance_distribution():
    """演示样本方差的卡方分布性质"""
    
    print("样本方差的卡方分布性质:")
    print("=" * 35)
    
    # 从正态总体N(μ, σ²)独立同分布抽样;下述样本方差卡方分布及总体方差置信区间依赖这一前提
    true_mean = 10
    true_std = 3
    true_var = true_std**2
    
    sample_sizes = [5, 10, 20, 30]
    n_experiments = 10000
    
    print(f"总体: N({true_mean}, {true_var})")
    print(f"实验次数: {n_experiments:,}")
    
    fig, axes = plt.subplots(2, 2, figsize=(15, 12))
    axes = axes.flatten()
    
    for idx, n in enumerate(sample_sizes):
        print(f"\n样本量 n = {n}:")
        
        # 进行多次抽样实验
        np.random.seed(42)
        sample_variances = []
        
        for _ in range(n_experiments):
            sample = np.random.normal(true_mean, true_std, n)
            sample_var = np.var(sample, ddof=1)  # 使用无偏估计
            sample_variances.append(sample_var)
        
        sample_variances = np.array(sample_variances)
        
        # 理论:(n-1)S²/σ² ~ χ²(n-1)
        chi2_statistics = (n - 1) * sample_variances / true_var
        
        # 统计结果
        print(f"  样本方差均值: {np.mean(sample_variances):.3f} (理论: {true_var:.3f})")
        print(f"  (n-1)S²/σ²的均值: {np.mean(chi2_statistics):.3f} (理论: {n-1})")
        print(f"  (n-1)S²/σ²的方差: {np.var(chi2_statistics, ddof=1):.3f} (理论: {2*(n-1)})")
        
        # 可视化
        ax = axes[idx]
        
        # 直方图
        ax.hist(chi2_statistics, bins=50, density=True, alpha=0.7, 
                color='skyblue', edgecolor='black', label='样本统计量')
        
        # 理论卡方分布
        x_theory = np.linspace(0, max(chi2_statistics), 1000)
        pdf_theory = stats.chi2.pdf(x_theory, n-1)
        ax.plot(x_theory, pdf_theory, 'r-', linewidth=2, 
                label=f'χ²({n-1})理论分布')
        
        ax.set_title(f'n = {n}, (n-1)S²/σ² ~ χ²({n-1})')
        ax.set_xlabel('(n-1)S²/σ²')
        ax.set_ylabel('概率密度')
        ax.legend()
        ax.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    # 置信区间应用
    print(f"\n置信区间应用:")
    print("-" * 20)
    
    n = 20
    confidence_levels = [0.90, 0.95, 0.99]
    
    print(f"当样本量 n = {n} 时,总体方差σ²的置信区间:")
    
    for conf_level in confidence_levels:
        alpha = 1 - conf_level
        
        # 卡方分布的临界值
        chi2_lower = stats.chi2.ppf(alpha/2, n-1)
        chi2_upper = stats.chi2.ppf(1-alpha/2, n-1)
        
        print(f"\n{conf_level:.0%}置信区间:")
        print(f"  χ²_{alpha/2:.3f}({n-1}) = {chi2_lower:.4f}")
        print(f"  χ²_{1-alpha/2:.3f}({n-1}) = {chi2_upper:.4f}")
        print(f"  σ²的置信区间: [(n-1)S²/{chi2_upper:.3f}, (n-1)S²/{chi2_lower:.3f}]")
        
        # 用一个具体的样本方差来计算
        sample_var_example = 10.5
        ci_lower = (n-1) * sample_var_example / chi2_upper
        ci_upper = (n-1) * sample_var_example / chi2_lower
        
        print(f"  若S² = {sample_var_example}, 则σ²的{conf_level:.0%}置信区间: [{ci_lower:.3f}, {ci_upper:.3f}]")

sample_variance_distribution()

2. 卡方分布的可加性

def chi2_additivity():
    """验证卡方分布的可加性"""
    
    print("\n\n卡方分布的可加性验证:")
    print("=" * 30)
    print("定理: 若 X₁ ~ χ²(df₁), X₂ ~ χ²(df₂) 且独立")
    print("则 X₁ + X₂ ~ χ²(df₁ + df₂)")
    
    # 设定参数
    df1, df2 = 3, 7
    df_sum = df1 + df2
    n_samples = 50000
    
    print(f"\n验证: χ²({df1}) + χ²({df2}) = χ²({df_sum})")
    print(f"样本量: {n_samples:,}")
    
    np.random.seed(42)
    
    # 方法1: 分别生成两个卡方分布然后相加
    chi2_1 = stats.chi2.rvs(df1, size=n_samples)
    chi2_2 = stats.chi2.rvs(df2, size=n_samples)
    chi2_sum = chi2_1 + chi2_2
    
    # 方法2: 直接生成df1+df2的卡方分布
    chi2_direct = stats.chi2.rvs(df_sum, size=n_samples)
    
    # 方法3: 通过正态分布构造
    # χ²(k) = Z₁² + Z₂² + ... + Zₖ²,其中 Zᵢ ~ N(0,1)
    normals_sum = np.sum(np.random.standard_normal((n_samples, df_sum))**2, axis=1)
    
    # 统计比较
    methods = {
        'χ²(3) + χ²(7)': chi2_sum,
        'χ²(10)直接生成': chi2_direct,
        '10个N(0,1)²之和': normals_sum
    }
    
    print("\n统计特征比较:")
    print("方法\t\t\t均值\t方差\t标准差\tQ1\tQ2\tQ3")
    print("-" * 80)
    
    theoretical_mean = df_sum
    theoretical_var = 2 * df_sum
    theoretical_std = np.sqrt(theoretical_var)
    
    print(f"理论值\t\t\t{theoretical_mean:.2f}\t{theoretical_var:.2f}\t{theoretical_std:.2f}\t-\t-\t-")
    
    for method_name, data in methods.items():
        mean_val = np.mean(data)
        var_val = np.var(data, ddof=1)
        std_val = np.std(data, ddof=1)
        q1, q2, q3 = np.percentile(data, [25, 50, 75])
        
        print(f"{method_name}\t{mean_val:.2f}\t{var_val:.2f}\t{std_val:.2f}\t{q1:.2f}\t{q2:.2f}\t{q3:.2f}")
    
    # KS检验比较分布相似性
    from scipy.stats import kstest, ks_2samp
    
    print(f"\n分布相似性检验 (KS检验):")
    print("-" * 35)
    
    # 与理论分布比较
    for method_name, data in methods.items():
        ks_stat, p_value = kstest(data, lambda x: stats.chi2.cdf(x, df_sum))
        print(f"{method_name} vs χ²({df_sum})理论分布:")
        print(f"  KS统计量: {ks_stat:.6f}, p值: {p_value:.6f}")
    
    # 两两比较
    print(f"\n方法间比较:")
    method_pairs = [
        ('χ²(3) + χ²(7)', 'χ²(10)直接生成'),
        ('χ²(3) + χ²(7)', '10个N(0,1)²之和'),
        ('χ²(10)直接生成', '10个N(0,1)²之和')
    ]
    
    for method1, method2 in method_pairs:
        ks_stat, p_value = ks_2samp(methods[method1], methods[method2])
        print(f"{method1} vs {method2}:")
        print(f"  KS统计量: {ks_stat:.6f}, p值: {p_value:.6f}")
    
    # 可视化
    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12))
    
    # 1. PDF比较
    x = np.linspace(0, 30, 1000)
    theoretical_pdf = stats.chi2.pdf(x, df_sum)
    
    ax1.plot(x, theoretical_pdf, 'k-', linewidth=3, label=f'理论χ²({df_sum})')
    
    colors = ['red', 'blue', 'green']
    alphas = [0.7, 0.7, 0.7]
    
    for i, (method_name, data) in enumerate(methods.items()):
        ax1.hist(data, bins=60, density=True, alpha=alphas[i], 
                color=colors[i], label=method_name, edgecolor='white', linewidth=0.5)
    
    ax1.set_xlabel('x')
    ax1.set_ylabel('概率密度')
    ax1.set_title('卡方分布可加性验证 - PDF比较')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    ax1.set_xlim(0, 30)
    
    # 2. CDF比较
    x_cdf = np.sort(chi2_sum)
    theoretical_cdf = stats.chi2.cdf(x_cdf, df_sum)
    empirical_cdf = np.arange(1, len(x_cdf) + 1) / len(x_cdf)
    
    ax2.plot(x_cdf, theoretical_cdf, 'k-', linewidth=2, label='理论CDF')
    ax2.plot(x_cdf, empirical_cdf, 'r--', alpha=0.7, linewidth=2, label='经验CDF')
    
    ax2.set_xlabel('x')
    ax2.set_ylabel('累积概率')
    ax2.set_title('累积分布函数比较')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    # 3. Q-Q图
    from scipy.stats import probplot
    probplot(chi2_sum, dist=stats.chi2(df_sum), plot=ax3)
    ax3.set_title('Q-Q图: χ²(3) + χ²(7) vs χ²(10)')
    ax3.grid(True, alpha=0.3)
    
    # 4. 残差分析
    # 比较χ²(3) + χ²(7)和χ²(10)直接生成的差异
    differences = chi2_sum - chi2_direct
    
    ax4.hist(differences, bins=50, alpha=0.7, color='purple', edgecolor='black')
    ax4.axvline(np.mean(differences), color='red', linestyle='--', linewidth=2, 
                label=f'均值 = {np.mean(differences):.4f}')
    ax4.set_xlabel('χ²(3) + χ²(7) - χ²(10)')
    ax4.set_ylabel('频数')
    ax4.set_title('两种方法的差异分布')
    ax4.legend()
    ax4.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()
    
    print(f"\n结论:")
    print("1. 三种方法生成的分布在统计上几乎相同")
    print("2. 验证了卡方分布的可加性: χ²(df₁) + χ²(df₂) = χ²(df₁+df₂)")
    print("3. 也验证了卡方分布的定义: k个独立标准正态分布的平方和")

chi2_additivity()

🔗 相关知识

#统计学 #卡方分布 #概率分布 #连续分布 #基础