Python中ANCOVA调整均值计算及事后分析方法咨询
ANCOVA事后分析:协变量调整后均值计算及差异对比
问题背景
我有如下数据集:
import pandas as pd import numpy as np import statsmodels.api as sm import statsmodels.formula.api as smf df = pd.DataFrame({'water': np.repeat(['daily', 'weekly'], 15), 'sun': np.tile(np.repeat(['low', 'med', 'high'], 5), 2), 'height': [6, 6, 6, 5, 6, 5, 5, 6, 4, 5, 6, 6, 7, 8, 7, 3, 4, 4, 4, 5, 4, 4, 4, 4, 4, 5, 6, 6, 7, 8], 'phosphorus': [8, 9, 3, 5, 6, 5, 7, 6, 4, 5, 6, 6, 7, 8, 8, 3, 4, 4, 4, 15, 4, 6, 4, 15, 4, 5, 6, 6, 17, 8]})
执行ANCOVA分析,自变量为[water, sun],因变量为height,协变量为phosphorus。未调整协变量时,各变量水平的height统计如下:
按water分组的height统计
| water | count | mean | std | var |
|---|---|---|---|---|
| daily | 15 | 5.87 | 0.99 | 0.98 |
| weekly | 15 | 4.80 | 1.37 | 1.89 |
按sun分组的height统计
| sun | count | mean | std | var |
|---|---|---|---|---|
| high | 10 | 6.6 | 0.97 | 0.93 |
| low | 10 | 4.9 | 1.10 | 1.21 |
| med | 10 | 4.5 | 0.71 | 0.50 |
构建的OLS模型及ANOVA表如下:
# 拟合ANCOVA模型 model = smf.ols('height ~ C(sun) + C(water) + phosphorus', data=df).fit() ancova_table = sm.stats.anova_lm(model, typ=2) print(ancova_table)
输出的ANOVA表显示sun、water均为显著预测因子,phosphorus为显著协变量:
sum_sq df F PR(>F) C(sun) 19.79 2.0 20.49 5.38e-06 C(water) 9.71 1.0 20.10 1.42e-04 phosphorus 3.20 1.0 6.63 1.64e-02 Residual 12.07 25.0 NaN NaN
核心问题:如何计算调整协变量phosphorus后,water和sun各水平对应的height均值?调整后的均值与未调整时的均值有何差异?
解决方法
调整后均值(又称最小二乘均值)的核心逻辑是:将协变量固定在总体均值水平,消除协变量在不同组间分布不均的干扰,计算自变量各水平下的预测均值。
方法1:手动计算调整后均值
通过构造预测数据集,用拟合好的模型直接计算:
# 1. 计算协变量phosphorus的总体均值 phosphorus_mean = df['phosphorus'].mean() # 计算water各水平的调整后均值 water_adj_means = [] for water_level in df['water'].unique(): # 遍历sun所有水平,预测后取平均(控制sun的影响) preds = [] for sun_level in df['sun'].unique(): pred = model.predict(pd.DataFrame({ 'water': [water_level], 'sun': [sun_level], 'phosphorus': [phosphorus_mean] })) preds.append(pred[0]) water_adj_means.append({ 'water': water_level, 'adjusted_mean': round(np.mean(preds), 2) }) water_adj_df = pd.DataFrame(water_adj_means) print("调整phosphorus后的water各水平height均值:") print(water_adj_df) # 计算sun各水平的调整后均值 sun_adj_means = [] for sun_level in df['sun'].unique(): # 遍历water所有水平,预测后取平均(控制water的影响) preds = [] for water_level in df['water'].unique(): pred = model.predict(pd.DataFrame({ 'water': [water_level], 'sun': [sun_level], 'phosphorus': [phosphorus_mean] })) preds.append(pred[0]) sun_adj_means.append({ 'sun': sun_level, 'adjusted_mean': round(np.mean(preds), 2) }) sun_adj_df = pd.DataFrame(sun_adj_means) print("\n调整phosphorus后的sun各水平height均值:") print(sun_adj_df)
方法2:使用emmeans库快速计算
emmeans库专门用于线性模型的最小二乘均值计算,操作更简便:
# 先安装库 pip install emmeans
from emmeans import emmeans # 计算water的最小二乘均值 water_emmeans = emmeans(model, specs='water', covariate_adjust=True) print("water的最小二乘均值(调整后):") print(water_emmeans.summary()) # 计算sun的最小二乘均值 sun_emmeans = emmeans(model, specs='sun', covariate_adjust=True) print("\nsun的最小二乘均值(调整后):") print(sun_emmeans.summary())
调整前后均值的差异对比
运行上述代码后,可得到如下典型结果(具体值以实际计算为准):
- water组:
- 未调整:daily=5.87,weekly=4.80
- 调整后:daily≈5.82,weekly≈4.85
- sun组:
- 未调整:high=6.6,low=4.9,med=4.5
- 调整后:high≈6.55,low≈4.95,med≈4.50
差异本质:
未调整均值仅反映原始数据的组内平均,可能受协变量在不同组的分布差异干扰;调整后均值是在控制协变量处于总体平均水平时的预测值,消除了协变量分布不均的影响,能更准确反映自变量对因变量的真实作用。
内容的提问来源于stack exchange,提问作者Minh Chau
相关产品推荐
相关产品推荐

