Skip to content

方差分析

单因素方差分析,检验各组均值是否存在显著差异。

核心代码

py
    def _fit_anova_one_way(self, df: pd.DataFrame, decimals: int, title: str) -> None:
        """
        执行单因素方差分析

        参数:
            df: 数据框
            decimals: 小数位数
            title: 表格标题
        """
        from scipy import stats
        from statsmodels.stats.multicomp import pairwise_tukeyhsd
        from statsmodels.stats.multicomp import MultiComparison

        # 获取检验参数
        alpha = self.anova_options.get('alpha', 0.05)
        levene_test = self.anova_options.get('levene_test', True)
        posthoc_test = self.anova_options.get('posthoc_test', True)
        posthoc_method = self.anova_options.get('posthoc_method', 'tukey')

        # 检查变量
        if len(self.x_vars) < 2:
            raise ValueError(_('单因素方差分析需要至少2个变量(1个因素 + 1个因变量)。'))

        # 第一个变量作为因素(分组变量),其余变量作为因变量
        factor_var = self.x_vars[0]
        dep_vars = self.x_vars[1:]

        if factor_var not in df.columns:
            raise ValueError(_('因素变量 %(var)s 不存在于数据中。') % {'var': factor_var})

        # 构建HTML结果
        html_parts = []

        for dep_var in dep_vars:
            if dep_var not in df.columns:
                continue

            # 准备数据
            data_clean = df[[factor_var, dep_var]].dropna()

            if len(data_clean) < 3:
                continue

            # 获取各组数据
            groups = data_clean.groupby(factor_var)[dep_var].apply(list)

            if len(groups) < 2:
                continue

            # 1. 描述性统计
            desc_stats = []
            for group_name, group_data in groups.items():
                n = len(group_data)
                mean = np.mean(group_data)
                std = np.std(group_data, ddof=1)
                se = std / np.sqrt(n)
                ci_lower = mean - 1.96 * se
                ci_upper = mean + 1.96 * se

                desc_stats.append({
                    'Group': str(group_name),
                    'N': int(n),
                    'Mean': f"{mean:.{decimals}f}",
                    'Std.Dev': f"{std:.{decimals}f}",
                    'Std.Err': f"{se:.{decimals}f}",
                    '95% CI Lower': f"{ci_lower:.{decimals}f}",
                    '95% CI Upper': f"{ci_upper:.{decimals}f}"
                })

            desc_df = pd.DataFrame(desc_stats)

            # 2. 执行单因素方差分析
            f_stat, p_value = stats.f_oneway(*groups.values)

            # 计算效应量 (eta squared)
            grand_mean = data_clean[dep_var].mean()
            ss_between = sum([len(g) * (np.mean(g) - grand_mean)**2 for g in groups.values])
            ss_total = sum([(x - grand_mean)**2 for g in groups.values for x in g])
            eta_squared = ss_between / ss_total if ss_total > 0 else 0

            # 计算自由度和均方
            df_between = len(groups) - 1
            df_within = len(data_clean) - len(groups)
            df_total = len(data_clean) - 1

            ms_between = ss_between / df_between
            ss_within = ss_total - ss_between
            ms_within = ss_within / df_within

            # 判断显著性
            if p_value < 0.01:
                stars = "***"
            elif p_value < 0.05:
                stars = "**"
            elif p_value < 0.1:
                stars = "*"
            else:
                stars = ""

            # 3. Levene方差齐性检验
            levene_result = None
            if levene_test:
                levene_stat, levene_p = stats.levene(*groups.values)
                levene_result = {
                    'statistic': levene_stat,
                    'p_value': levene_p
                }

            # 4. 事后检验 (Tukey HSD 或 Bonferroni)
            posthoc_result = None
            if posthoc_test and p_value < alpha:
                if posthoc_method == 'tukey':
                    mc = MultiComparison(data_clean[dep_var], data_clean[factor_var])
                    posthoc_result = mc.tukeyhsd(alpha=alpha)
                elif posthoc_method == 'bonferroni':
                    mc = MultiComparison(data_clean[dep_var], data_clean[factor_var])
                    # Bonferroni校正
                    n_comparisons = len(groups) * (len(groups) - 1) / 2
                    bonferroni_alpha = alpha / n_comparisons
                    posthoc_result = mc.tukeyhsd(alpha=bonferroni_alpha)

            # 生成HTML
            html = self._generate_anova_one_way_html(
                dep_var, factor_var, desc_df, f_stat, p_value, stars,
                df_between, df_within, df_total,
                ss_between, ss_within, ss_total,
                ms_between, ms_within,
                eta_squared, levene_result, posthoc_result,
                alpha, decimals
            )
            html_parts.append(html)

        if not html_parts:
            raise ValueError(_('没有可用的数据进行单因素方差分析。'))

        # 合并所有结果
        title = title if title else _('单因素方差分析')
        self.custom_html = f'<div class="table-editable-container"><h5 style="text-align: center; margin-bottom: 20px;">{title}</h5>' + '<br><br>'.join(html_parts) + '</div>'

Released under the AGPL-3.0 License.