外观
方差分析
单因素方差分析,检验各组均值是否存在显著差异。
核心代码
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>'