2025년 2학기 계량분석 강의자료

9주차: 이원분산분석 & 공분산분석

1. 이원분산분석 (Two-way ANOVA)


1) 개념


2) 기본 가정


3) 가설 설정


4) 직관적 이해

2. 공분산분석 (ANCOVA)


1) 개념


2) 공변량 (Covariate)


3) 공분산분석의 필요성

✅ 정리


1) 이원분산분석 (Two-way ANOVA) — 실습 확장판


A. 가정 점검 & 효과크기 포함한 전체 코드

# !pip install statsmodels pingouin  # 필요시
import numpy as np, pandas as pd
import statsmodels.api as sm
from statsmodels.formula.api import ols
from scipy import stats

# -----------------------------
# 1) 데이터 생성 (사용자 예제 기반)
# -----------------------------
np.random.seed(42)
N = 100
df = pd.DataFrame({
    'income': np.random.normal(loc=200, scale=50, size=N) + np.tile([20, -10], N//2),
    'gender': np.repeat(['male', 'female'], N//2),
    'major' : np.tile(np.repeat(['eng', 'biz'], N//4), 2)
})
# 상호작용 효과(예시)
df.loc[(df['gender']=='male') & (df['major']=='eng'), 'income'] += 30
df.loc[(df['gender']=='female') & (df['major']=='eng'), 'income'] -= 15

# -----------------------------
# 2) 이원분산분석(상호작용 포함)
# -----------------------------
# 균형 설계에 가깝다면 typ=2가 보편적. 불균형 심하면 typ=3 권장.
model = ols('income ~ C(gender) * C(major)', data=df).fit()
anova_table = sm.stats.anova_lm(model, typ=2)
print("=== Two-way ANOVA (Type II) ===")
print(anova_table, "\n")

# -----------------------------
# 3) 가정 점검
# -----------------------------
# (a) 잔차 정규성
w, p_shapiro = stats.shapiro(model.resid)
print(f"[정규성] Shapiro-Wilk p={p_shapiro:.4f}  (p>=.05면 정규성 위반 아님)")

# (b) 등분산성 (Levene: 두 요인의 조합 그룹 기준)
groups = df.groupby(['gender','major'])['income'].apply(list).tolist()
lev_stat, p_levene = stats.levene(*groups, center='median')
print(f"[등분산성] Levene p={p_levene:.4f}  (p>=.05면 등분산 위반 아님)")

# -----------------------------
# 4) 효과크기 (partial eta-squared)
# -----------------------------
# partial η² = SS_effect / (SS_effect + SS_error)
ss_error = anova_table.loc['Residual','sum_sq']
effects = ['C(gender)', 'C(major)', 'C(gender):C(major)']
for eff in effects:
    ss_eff = anova_table.loc[eff, 'sum_sq']
    pes = ss_eff / (ss_eff + ss_error)
    print(f"[효과크기] {eff} partial eta² = {pes:.3f}")

해석 팁

  • `PR(>F) < 0.05` 이면 해당 효과 유의.
  • `partial eta²` (대략): 0.01≈작음, 0.06≈중간, 0.14≈큼.
  • 가정 p값이 0.05 미만이면(정규성/등분산성 위반) → 변환(로그 등) 또는 견고한 방법/부트스트랩 고려.

공분산분석 (ANCOVA) 실습


1. 데이터 설정

import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.formula.api import ols
from scipy import stats

# 가상 데이터 생성
np.random.seed(42)
data_ancova = {
    'product_attitude': np.random.normal(loc=5, scale=1, size=90),
    'pre_attitude': np.random.uniform(1, 10, size=90),
    'ad_option': np.repeat(['A', 'B', 'C'], 30)
}
df_ancova = pd.DataFrame(data_ancova)

# 공변량 효과 추가 (사전태도가 높을수록 제품태도도 높음)
df_ancova['product_attitude'] += 0.5 * df_ancova['pre_attitude']

# 독립변수 효과 추가 (광고대안 B와 C)
df_ancova.loc[df_ancova['ad_option'] == 'B', 'product_attitude'] += 1.5
df_ancova.loc[df_ancova['ad_option'] == 'C', 'product_attitude'] -= 0.5

2. 가설 설정


3. 평행선 가정 검정 (공변량×독립변수 상호작용)

ANCOVA에서는 **공변량의 기울기가 집단별로 평행해야 함** (즉, 공변량×집단 상호작용 효과 없음).

# 평행선 가정 확인: 공변량*광고대안 상호작용 추가
model_check = ols('product_attitude ~ C(ad_option)*pre_attitude', data=df_ancova).fit()
anova_check = sm.stats.anova_lm(model_check, typ=2)
print("=== 평행선 가정 검정 ===")
print(anova_check[['F','PR(>F)']])

4. 공분산분석 수행

# ANCOVA: 공변량 통제 후 광고대안 효과 확인
model_ancova = ols('product_attitude ~ C(ad_option) + pre_attitude', data=df_ancova).fit()
anova_ancova_table = sm.stats.anova_lm(model_ancova, typ=2)

print("=== ANCOVA 결과 ===")
print(anova_ancova_table)

5. 효과크기 계산 (partial η²)

# partial eta squared 계산
ss_error = anova_ancova_table.loc['Residual','sum_sq']
for eff in ['C(ad_option)', 'pre_attitude']:
    ss_eff = anova_ancova_table.loc[eff,'sum_sq']
    pes = ss_eff / (ss_eff + ss_error)
    print(f"[효과크기] {eff} partial eta² = {pes:.3f}")

6. 정리