ANOVA Illustration — Bock 어휘 성장 데이터 수치 예시

SS 분해, 분산 성분 추정, ICC, 직교 다항식 추세 분해 단계별 계산

Bock(1975)의 어휘 성장 데이터(N=64, 4시점)를 이용하여 단일 표본 반복측정 ANOVA의 전체 계산 절차를 단계별로 수행한다. 피험자 SS · 시간 SS · 잔차 SS를 수기로 유도하고, 분산 성분(σ̂²_π, σ̂²_e)과 ICC(=0.80)를 추정한다. 직교 다항식 대비 행렬 C로 선형·이차·삼차 추세를 분리하고 각 추세의 F 검정을 수행하며, Mauchly 구형성 검정으로 전제 조건을 확인한다.

Statistics
Longitudinal Data Analysis
저자

Kwangmin Kim

공개

2026년 04월 10일

1 개요

반복측정 ANOVA의 이론(모형 구조, SS 분해 공식, 구형성 검정)은 앞선 포스트들에서 다뤘다.

  • [170] 반복측정 ANOVA — 단일·다중 표본 설계, 구형성
  • [171] 단일 표본 반복측정 ANOVA: SS 분해, F 검정 논리

이 포스트는 수치 Illustration 단계다. Hedeker & Gibbons(2006, Ch.2) §2.3 예제인 Bock(1975)의 어휘 성장 데이터를 이용하여 아래를 단계별로 계산한다.

  1. 요약 통계 확인 (Table 2.1) — 평균, 표준편차, 상관계수
  2. Mauchly 구형성 검정 결과 해석
  3. SS 분해: \(\text{SS}_S\), \(\text{SS}_T\), \(\text{SS}_R\) 수기 유도
  4. 분산 성분 추정: \(\hat{\sigma}^2_e\), \(\hat{\sigma}^2_\pi\)
  5. ICC = 0.80 계산 및 해석
  6. 직교 다항식 대비로 시간 효과 분해 (Table 2.3)
  7. 선형·이차·삼차 추세 F 검정
  8. 생물학적·교육적 해석

2 연구 설계와 데이터

2.1 Bock 어휘 성장 연구

Bock(1975)은 시카고 대학교 부속 실험학교의 학생 64명을 대상으로 8학년부터 11학년까지 어휘 점수를 반복 측정했다 (Hedeker & Gibbons, 2006, Ch.2).

변수
피험자 수 \(N\) 64
시점 수 \(n\) 4 (8학년, 9학년, 10학년, 11학년)
총 관측 수 \(Nn = 256\)
반응 변수 어휘 점수 (협동 독해 검사, Davis 1950)
설계 단일 집단 반복측정 (one-sample)

연구자의 가설: 신체 성장이 사춘기에 감속되듯, 어휘 습득도 청소년기에 유사한 감속 패턴을 보일 것이다. 즉 선형 증가 + 음의 이차 추세(감속)를 예상한다.

2.2 요약 통계 (Table 2.1)

학년 평균 \(\bar{y}_{.j}\) 표준편차 \(s_j\) 8학년 9학년 10학년 11학년
8학년 1.137 1.889 1.000
9학년 2.542 2.085 .810 1.000
10학년 2.988 2.169 .868 .785 1.000
11학년 3.472 1.925 .785 .757 .811 1.000

직관적 관찰:

  • 평균: 8→9학년 증가폭(1.41)이 9→10학년(0.45), 10→11학년(0.48)보다 훨씬 크다. 초기 가파른 성장 후 완만해지는 패턴이 보인다.
  • 표준편차: 8~10학년에서 약간 증가하다가 11학년에서 다소 감소 — 분산이 대체로 유사하다.
  • 상관: 0.757~0.868 범위로 모두 높다. 개인의 어휘 수준이 시간이 지나도 안정적으로 유지됨을 의미한다.

전반적 총 평균:

\[ \bar{y}_{..} = \frac{1.137 + 2.542 + 2.988 + 3.472}{4} = \frac{10.139}{4} = 2.535 \]


3 구형성(Sphericity) 검정

3.1 왜 구형성을 먼저 확인하는가?

반복측정 ANOVA는 구형성(sphericity) 가정 — 모든 시점 대비의 분산이 동일하다는 조건 — 을 만족할 때 F 검정이 정확하다. 위반되면 1종 오류가 증가하므로 분석 전에 반드시 확인해야 한다.

구형성과 복합대칭 관계

복합대칭(compound symmetry): 모든 분산이 동일하고 모든 공분산이 동일한 구조.

\[ \Sigma = \begin{bmatrix} \sigma^2_\pi + \sigma^2_e & \sigma^2_\pi & \sigma^2_\pi & \sigma^2_\pi \\ \sigma^2_\pi & \sigma^2_\pi + \sigma^2_e & \sigma^2_\pi & \sigma^2_\pi \\ \sigma^2_\pi & \sigma^2_\pi & \sigma^2_\pi + \sigma^2_e & \sigma^2_\pi \\ \sigma^2_\pi & \sigma^2_\pi & \sigma^2_\pi & \sigma^2_\pi + \sigma^2_e \end{bmatrix} \]

복합대칭은 구형성의 충분조건이다. 복합대칭이 성립하면 구형성도 반드시 성립한다.

3.2 Mauchly 검정 결과

\[ \chi^2 = 6.32, \quad df = 5, \quad p > 0.05 \quad \text{(기각하지 않음)} \]

해석: 구형성 가정이 이 데이터에서는 성립한다. 따라서 Greenhouse-Geisser 또는 Huynh-Feldt 보정 없이 표준 F 검정을 그대로 사용할 수 있다.

왜 df=5인가? \(n=4\) 시점에서 독립적인 대비의 수는 \(n-1=3\), 그 대비 쌍의 수는 \(\binom{3}{2}=3\), 자유도는 \(n(n-1)/2 - (n-1) = 6 - 1 = 5\). (Mauchly 검정 자유도 공식.)


4 반복측정 ANOVA: SS 분해

4.1 모형 구조

단일 표본 반복측정 ANOVA 모형 (식 2.1):

\[ y_{ij} = \mu + \pi_i + \tau_j + e_{ij} \]

  • \(\mu\): 전체 총 평균 (grand mean)
  • \(\pi_i \sim N(0, \sigma^2_\pi)\): 피험자 랜덤 효과 (개인 간 차이)
  • \(\tau_j\): 시점(학년) 고정 효과, \(\sum \tau_j = 0\)
  • \(e_{ij} \sim N(0, \sigma^2_e)\): 잔차 오차

핵심 아이디어: \(\pi_i\) 는 각 피험자가 전체 기간에 걸쳐 평균적으로 더 높거나 낮은 어휘 수준을 갖는다는 사실을 포착한다. \(\tau_j\) 는 학년이 올라감에 따라 모든 피험자에게 공통으로 나타나는 시간 효과다.

4.2 SS 분해 공식

총 편차 \(y_{ij} - \bar{y}_{..}\) 는 세 성분으로 분해된다:

\[ \underbrace{(y_{ij} - \bar{y}_{..})}_{\text{Total}} = \underbrace{(\bar{y}_{i.} - \bar{y}_{..})}_{\text{Subject}} + \underbrace{(\bar{y}_{.j} - \bar{y}_{..})}_{\text{Time}} + \underbrace{(y_{ij} - \bar{y}_{i.} - \bar{y}_{.j} + \bar{y}_{..})}_{\text{Residual}} \]

각 성분의 제곱합:

\[ \text{SS}_S = n \sum_{i=1}^{N} (\bar{y}_{i.} - \bar{y}_{..})^2 \]

\[ \text{SS}_T = N \sum_{j=1}^{n} (\bar{y}_{.j} - \bar{y}_{..})^2 \]

\[ \text{SS}_R = \sum_{i=1}^{N}\sum_{j=1}^{n} (y_{ij} - \bar{y}_{i.} - \bar{y}_{.j} + \bar{y}_{..})^2 \]

\[ \text{SS}_y = \text{SS}_S + \text{SS}_T + \text{SS}_R \]

4.3 수치 계산 (N=64, n=4)

\(\text{SS}_T\) 계산:

\[ \text{SS}_T = N \sum_{j=1}^{4} (\bar{y}_{.j} - \bar{y}_{..})^2 = 64 \times \sum_{j=1}^{4} (\bar{y}_{.j} - 2.535)^2 \]

학년 \(j\) \(\bar{y}_{.j}\) \(\bar{y}_{.j} - 2.535\) \((\bar{y}_{.j} - 2.535)^2\)
8 1.137 −1.398 1.9544
9 2.542 +0.007 0.0000
10 2.988 +0.453 0.2052
11 3.472 +0.937 0.8779
합계 3.0375

\[ \text{SS}_T = 64 \times 3.0375 = 194.40 \approx \mathbf{194.34} \]

(미세한 차이는 평균값의 반올림 때문이며, 교재 원값 194.34를 사용한다.)

4.4 ANOVA 표 (Table 2.2)

소스 df SS MS F p
피험자 (Subjects) \(N-1=63\) 873.60 13.87 16.91 <.0001
학년 (Grade/Time) \(n-1=3\) 194.34 64.78 79.02 <.0001
잔차 (Residual) \((N-1)(n-1)=189\) 154.94 0.82
전체 (Total) \(Nn-1=255\) 1222.88

자유도 직관: - 피험자 df = \(N-1 = 63\): 64명의 독립적인 개인 편차 - 시간 df = \(n-1 = 3\): 4시점의 독립적인 시간 편차 - 잔차 df = \((N-1)(n-1) = 63 \times 3 = 189\): 피험자×시간 교호작용

잔차 MS 계산:

\[ \hat{\sigma}^2_e = \text{MS}_R = \frac{\text{SS}_R}{(N-1)(n-1)} = \frac{154.94}{189} = 0.820 \]

F 검정:

\[ F_T = \frac{\text{MS}_T}{\text{MS}_R} = \frac{64.78}{0.820} = 79.00 \approx 79.02 \]

F(3, 189) = 79.02, p < .0001. 학년 간 어휘 점수에 통계적으로 매우 유의한 차이가 있다.


5 분산 성분 추정과 ICC

5.1 왜 E(MS)를 비교하는가?

ANOVA 표의 기댓값(Expected MS)를 이용하면 관측된 MS로부터 분산 성분을 역추정할 수 있다.

소스 \(E(\text{MS})\)
피험자 \(\sigma^2_e + n\sigma^2_\pi\)
잔차 \(\sigma^2_e\)

직관: MS_R 의 기댓값이 \(\sigma^2_e\) 이므로 직접 추정 가능하다. MS_S 의 기댓값에서 \(\sigma^2_e\) 를 빼고 \(n\) 으로 나누면 \(\sigma^2_\pi\) 를 얻는다.

5.2 분산 성분 추정

\[ \hat{\sigma}^2_e = \text{MS}_R = 0.820 \]

\[ \hat{\sigma}^2_\pi = \frac{\text{MS}_S - \text{MS}_R}{n} = \frac{13.87 - 0.82}{4} = \frac{13.05}{4} = 3.263 \]

5.3 급내상관계수 (ICC)

\[ \text{ICC} = \frac{\hat{\sigma}^2_\pi}{\hat{\sigma}^2_\pi + \hat{\sigma}^2_e} = \frac{3.263}{3.263 + 0.820} = \frac{3.263}{4.083} = \mathbf{0.80} \]

해석: 어휘 점수 변동의 80%는 피험자 간 개인차로 설명된다. 즉, 어떤 학생이 8학년 때 어휘 점수가 높으면 11학년까지도 계속 높은 경향이 있다. 이는 ICC = 0.80이라는 높은 수치로 확인된다.

ICC의 실무적 의미

ICC가 높다는 것은 반복측정의 상관이 강하다는 뜻이다. 이 경우:

  1. 독립 관측 가정 위반: 일반 OLS를 그대로 적용하면 표준오차가 과소 추정된다.
  2. 검정력 향상: 피험자 내 비교를 통해 개인 간 차이 \(\sigma^2_\pi\) 를 오차에서 제거하므로 검정력이 증가한다.
  3. 분산 분해: \(\text{SS}_S\)(873.60)이 \(\text{SS}_T\)(194.34)의 약 4.5배 — 학년 효과보다 개인 기저 수준 차이가 훨씬 크다.

6 직교 다항식 추세 분해

6.1 왜 추세 분해가 필요한가?

\(F_T = 79.02\) 는 “학년 간 어떤 차이가 있다”는 전반적 검정이다. 그러나 이것만으로는 “어떤 종류의 변화인가” — 단순한 선형 증가인가, 아니면 감속하는 성장인가 — 를 알 수 없다.

직교 다항식 대비(orthogonal polynomial contrast)를 이용하면 \(\text{SS}_T = 194.34\) 를 선형·이차·삼차 성분으로 완전 분해할 수 있다.

6.2 직교 다항식 대비 행렬 \(C\)

4시점 등간격 자료에 대한 직교 다항식 대비 행렬 (3×4):

\[ C = \begin{bmatrix} c_{11} & c_{12} & c_{13} & c_{14} \\ c_{21} & c_{22} & c_{23} & c_{24} \\ c_{31} & c_{32} & c_{33} & c_{34} \end{bmatrix} = \begin{bmatrix} -0.67082 & -0.22361 & 0.22361 & 0.67082 \\ \phantom{-}0.50000 & -0.50000 & -0.50000 & 0.50000 \\ -0.22361 & \phantom{-}0.67082 & -0.67082 & 0.22361 \end{bmatrix} \]

  • 1행 (선형): 시간에 따라 단조 증가하는 가중치 — 시간이 지날수록 더 큰 양의 가중치
  • 2행 (이차): 양 끝이 높고 가운데가 낮은 U자 혹은 역U자 — 가속·감속 패턴을 포착
  • 3행 (삼차): S자 패턴 — 변곡점이 있는 복잡한 곡선

직교성 확인: 임의의 두 행 \(c_{k}\)\(c_{l}\) 에 대해 \(\sum_j c_{kj} c_{lj} = 0\). 이 조건이 각 SS가 독립적으로 분리되도록 보장한다.

6.3 대비 추정값 \(L_{j'}\)

\[ L_k = \sum_{j=1}^{4} c_{kj} \bar{y}_{.j} = c_k \cdot \bar{y} \]

선형 추정값:

\[ L_1 = (-0.67082)(1.137) + (-0.22361)(2.542) + (0.22361)(2.988) + (0.67082)(3.472) \]

\[ = -0.763 - 0.568 + 0.668 + 2.329 = 1.666 \approx \mathbf{1.67} \]

이차 추정값:

\[ L_2 = (0.5)(1.137) + (-0.5)(2.542) + (-0.5)(2.988) + (0.5)(3.472) \]

\[ = 0.569 - 1.271 - 1.494 + 1.736 = -0.460 \approx \mathbf{-0.46} \]

삼차 추정값:

\[ L_3 = (-0.22361)(1.137) + (0.67082)(2.542) + (-0.67082)(2.988) + (0.22361)(3.472) \]

\[ = -0.254 + 1.705 - 2.005 + 0.776 = 0.222 \approx \mathbf{0.22} \]

직관: 선형(1.67) > 이차(−0.46) > 삼차(0.22) 순서로 크기가 줄어든다. 어휘 성장의 주된 패턴은 선형 증가이며, 그 위에 감속 성분(음의 이차)이 얹혀 있다.

6.4 추세별 제곱합 \(\text{SS}_{T_k}\)

대비 계수가 정규화(단위 벡터)되어 있으므로 \(\sum_j c_{kj}^2 = 1\), 따라서:

\[ \text{SS}_{T_k} = \frac{N L_k^2}{\sum_j c_{kj}^2} = N L_k^2 \]

추세 대비 추정값 \(L_k\) \(L_k^2\) \(\text{SS}_{T_k} = N \cdot L_k^2\)
선형 1.67 2.789 \(64 \times 2.789 = \mathbf{178.50} \approx 177.58\)
이차 −0.46 0.212 \(64 \times 0.212 = \mathbf{13.57} \approx 13.58\)
삼차 0.22 0.048 \(64 \times 0.048 = \mathbf{3.09} \approx 3.17\)
합계 \(\mathbf{194.34} = \text{SS}_T\)

세 추세 SS의 합이 정확히 \(\text{SS}_T = 194.34\) 와 일치한다. 직교 분해가 올바르게 수행되었음을 확인하는 검증이다.

6.5 추세별 F 검정 (Table 2.3)

\[ F_k = \frac{\text{SS}_{T_k}/1}{\text{MS}_R} = \frac{\text{SS}_{T_k}}{0.820}, \quad df = (1, \; 189) \]

추세 \(\text{SS}_{T_k}\) \(F_k\) \(p\)
선형 177.58 216.56 <.0001
이차 13.58 16.56 <.0001
삼차 3.17 3.86 .051

해석:

  • 선형 추세 (F=216.56, p<.0001): 압도적으로 유의. 학년이 높아질수록 어휘 점수가 증가한다.
  • 이차 추세 (F=16.56, p<.0001): 유의. 음수 추정값(−0.46)과 결합하면 “성장 속도가 감소”한다는 결론 — 감속형 성장.
  • 삼차 추세 (F=3.86, p=.051): 경계적으로 유의. 소규모 S자 변동이 있지만 선형·이차에 비해 미미하다.

7 성장 곡선 해석

7.1 추세 성분의 기여 비율

\[ \frac{\text{SS}_{T_1}}{\text{SS}_T} = \frac{177.58}{194.34} = 91.4\%, \quad \frac{\text{SS}_{T_2}}{\text{SS}_T} = \frac{13.58}{194.34} = 6.99\%, \quad \frac{\text{SS}_{T_3}}{\text{SS}_T} = \frac{3.17}{194.34} = 1.63\% \]

시간 효과의 91%는 선형 추세, 7%는 이차 감속, 2%는 삼차 변동으로 설명된다.

7.2 적합 성장 곡선

직교 다항식 계수를 원래 시간 척도로 재표현하면 성장 곡선을 직관적으로 그릴 수 있다. 이 데이터는 대략 다음 형태로 요약된다:

\[ \hat{\mu}(t) \approx \mu + \hat{L}_1 \cdot p_1(t) + \hat{L}_2 \cdot p_2(t) \]

여기서 \(\hat{L}_1 = 1.67 > 0\) (오름세), \(\hat{L}_2 = -0.46 < 0\) (오목형). 즉 “아래로 볼록한 증가 곡선”이다.

성장 감속의 생물학적 의미

Bock이 제시한 가설 — 사춘기 이후 신체 성장 감속이 어휘 습득에도 반영될 것이다 — 이 데이터로 지지된다. 8→9학년에서 가장 급격한 증가(+1.41점)가 일어나고, 이후 증가 속도가 현저히 줄어든다(9→10: +0.45, 10→11: +0.48). 이 패턴은 유의한 음의 이차 추세로 포착된다.


8 R 코드: 단계별 수치 재현

library(tidyverse)

# ----------------------------------------------------------------
# Step 1: 요약 통계 (Table 2.1)
# ----------------------------------------------------------------
N <- 64
n <- 4

# 시점별 평균 벡터 (8, 9, 10, 11학년)
y_bar <- c(1.137, 2.542, 2.988, 3.472)
sd_j  <- c(1.889, 2.085, 2.169, 1.925)

# 상관 행렬
cor_mat <- matrix(c(
  1.000, 0.810, 0.868, 0.785,
  0.810, 1.000, 0.785, 0.757,
  0.868, 0.785, 1.000, 0.811,
  0.785, 0.757, 0.811, 1.000
), nrow=4)

# 공분산 행렬
cov_mat <- diag(sd_j) %*% cor_mat %*% diag(sd_j)

grand_mean <- mean(y_bar)
cat("총 평균:", round(grand_mean, 3), "\n")

# ----------------------------------------------------------------
# Step 2: SS 분해 (요약 통계로부터)
# ----------------------------------------------------------------
# SS_T: 시점별 평균의 편차 제곱합
SS_T <- N * sum((y_bar - grand_mean)^2)
cat("SS_T:", round(SS_T, 2), "\n")   # 목표: 194.34

# SS_R = SS_y - SS_S - SS_T
# 공분산 행렬로부터 SS_y 계산
# Var(Y) = (1/N) * total SS → total SS ≈ (N-1) * total variance
# SS_R: pooled within-subject residual
# SS_R = sum_j (N-1)*s²_j - SS_T 는 정확하지 않음.
# 교재 수치를 직접 사용.
SS_S <- 873.60
SS_R <- 154.94
SS_y <- SS_S + SS_T + SS_R
cat("SS_S:", SS_S, "  SS_R:", SS_R, "  SS_y:", round(SS_y, 2), "\n")

# ----------------------------------------------------------------
# Step 3: ANOVA 표
# ----------------------------------------------------------------
df_S <- N - 1
df_T <- n - 1
df_R <- (N-1)*(n-1)

MS_S <- SS_S / df_S
MS_T <- SS_T / df_T
MS_R <- SS_R / df_R

F_S <- MS_S / MS_R
F_T <- MS_T / MS_R

cat("\n--- ANOVA 표 ---\n")
cat(sprintf("피험자: df=%d  SS=%.2f  MS=%.3f  F=%.2f\n", df_S, SS_S, MS_S, F_S))
cat(sprintf("학년:   df=%d  SS=%.2f  MS=%.3f  F=%.2f\n", df_T, SS_T, MS_T, F_T))
cat(sprintf("잔차:   df=%d  SS=%.2f  MS=%.3f\n", df_R, SS_R, MS_R))
cat(sprintf("전체:   df=%d  SS=%.2f\n", N*n-1, SS_y))

# ----------------------------------------------------------------
# Step 4: 분산 성분 추정 & ICC
# ----------------------------------------------------------------
sigma2_e <- MS_R
sigma2_pi <- (MS_S - MS_R) / n
ICC <- sigma2_pi / (sigma2_pi + sigma2_e)

cat("\n--- 분산 성분 ---\n")
cat(sprintf("σ²_e = %.3f\n", sigma2_e))
cat(sprintf("σ²_π = %.3f\n", sigma2_pi))
cat(sprintf("ICC  = %.3f\n", ICC))

# ----------------------------------------------------------------
# Step 5: 직교 다항식 대비
# ----------------------------------------------------------------
# 4시점 등간격 직교 다항식 계수 (단위 벡터)
C_linear    <- c(-0.67082, -0.22361,  0.22361,  0.67082)
C_quadratic <- c( 0.50000, -0.50000, -0.50000,  0.50000)
C_cubic     <- c(-0.22361,  0.67082, -0.67082,  0.22361)

L1 <- sum(C_linear    * y_bar)
L2 <- sum(C_quadratic * y_bar)
L3 <- sum(C_cubic     * y_bar)

cat("\n--- 직교 다항식 대비 추정값 ---\n")
cat(sprintf("선형 L1  = %.4f\n", L1))
cat(sprintf("이차 L2  = %.4f\n", L2))
cat(sprintf("삼차 L3  = %.4f\n", L3))

# 추세별 SS (정규화된 대비 계수이므로 Σc²=1)
SS_T1 <- N * L1^2
SS_T2 <- N * L2^2
SS_T3 <- N * L3^2

cat("\n--- 추세별 SS ---\n")
cat(sprintf("SS_선형 = %.2f  (SS_T의 %.1f%%)\n", SS_T1, 100*SS_T1/SS_T))
cat(sprintf("SS_이차 = %.2f  (SS_T의 %.1f%%)\n", SS_T2, 100*SS_T2/SS_T))
cat(sprintf("SS_삼차 = %.2f  (SS_T의 %.1f%%)\n", SS_T3, 100*SS_T3/SS_T))
cat(sprintf("합계    = %.2f  (목표: %.2f)\n", SS_T1+SS_T2+SS_T3, SS_T))

# ----------------------------------------------------------------
# Step 6: 추세별 F 검정
# ----------------------------------------------------------------
F1 <- SS_T1 / MS_R
F2 <- SS_T2 / MS_R
F3 <- SS_T3 / MS_R

p1 <- pf(F1, 1, df_R, lower.tail=FALSE)
p2 <- pf(F2, 1, df_R, lower.tail=FALSE)
p3 <- pf(F3, 1, df_R, lower.tail=FALSE)

cat("\n--- 추세별 F 검정 (df = 1, 189) ---\n")
cat(sprintf("선형: F=%.2f  p=%.4f\n", F1, p1))
cat(sprintf("이차: F=%.2f  p=%.4f\n", F2, p2))
cat(sprintf("삼차: F=%.2f  p=%.4f\n", F3, p3))

# ----------------------------------------------------------------
# Step 7: 실제 데이터로 검증 (시뮬레이션으로 근사)
# ----------------------------------------------------------------
set.seed(42)
# 요약 통계로 데이터 재현
sigma_pi_hat <- sqrt(sigma2_pi)
sigma_e_hat  <- sqrt(sigma2_e)

subject_effects <- rnorm(N, 0, sigma_pi_hat)
Y_sim <- outer(subject_effects, rep(1, n)) +
         outer(rep(1, N), y_bar) +
         matrix(rnorm(N*n, 0, sigma_e_hat), N, n)

# 반복측정 ANOVA (aov로 확인)
df_long <- data.frame(
  score   = as.vector(Y_sim),
  subject = rep(1:N, n),
  grade   = rep(1:n, each=N)
)
df_long$subject <- factor(df_long$subject)
df_long$grade   <- factor(df_long$grade)

fit <- aov(score ~ grade + Error(subject/grade), data=df_long)
cat("\n--- aov 결과 (시뮬레이션 데이터) ---\n")
print(summary(fit))

9 Python 코드: 단계별 수치 재현

import numpy as np
from scipy import stats

# ----------------------------------------------------------------
# Step 1: 요약 통계 설정
# ----------------------------------------------------------------
N = 64
n = 4

y_bar = np.array([1.137, 2.542, 2.988, 3.472])
sd_j  = np.array([1.889, 2.085, 2.169, 1.925])

grand_mean = y_bar.mean()
print(f"총 평균: {grand_mean:.3f}")

# ----------------------------------------------------------------
# Step 2: SS 분해
# ----------------------------------------------------------------
SS_T = N * np.sum((y_bar - grand_mean)**2)
SS_S = 873.60    # 교재 Table 2.2
SS_R = 154.94
SS_y = SS_S + SS_T + SS_R

print(f"\nSS_T = {SS_T:.2f}  (목표: 194.34)")
print(f"SS_S = {SS_S:.2f}  SS_R = {SS_R:.2f}  SS_y = {SS_y:.2f}")

# ----------------------------------------------------------------
# Step 3: ANOVA 표
# ----------------------------------------------------------------
df_S = N - 1
df_T = n - 1
df_R = (N-1) * (n-1)

MS_S = SS_S / df_S
MS_T = SS_T / df_T
MS_R = SS_R / df_R

F_S = MS_S / MS_R
F_T = MS_T / MS_R

print(f"\n--- ANOVA 표 ---")
print(f"피험자: df={df_S}  MS={MS_S:.3f}  F={F_S:.2f}")
print(f"학년:   df={df_T}  MS={MS_T:.3f}  F={F_T:.2f}")
print(f"잔차:   df={df_R}  MS={MS_R:.3f}")

# ----------------------------------------------------------------
# Step 4: 분산 성분 & ICC
# ----------------------------------------------------------------
sigma2_e  = MS_R
sigma2_pi = (MS_S - MS_R) / n
ICC = sigma2_pi / (sigma2_pi + sigma2_e)

print(f"\n--- 분산 성분 ---")
print(f"σ²_e  = {sigma2_e:.3f}")
print(f"σ²_π  = {sigma2_pi:.3f}")
print(f"ICC   = {ICC:.3f}")

# ----------------------------------------------------------------
# Step 5: 직교 다항식 대비
# ----------------------------------------------------------------
C = np.array([
    [-0.67082, -0.22361,  0.22361,  0.67082],   # 선형
    [ 0.50000, -0.50000, -0.50000,  0.50000],   # 이차
    [-0.22361,  0.67082, -0.67082,  0.22361]    # 삼차
])

L = C @ y_bar   # 대비 추정값 벡터 [L1, L2, L3]
print(f"\n직교 다항식 추정값: 선형={L[0]:.4f}  이차={L[1]:.4f}  삼차={L[2]:.4f}")

# 추세별 SS (Σc²=1 정규화)
SS_trends = N * L**2
print(f"\n추세별 SS: 선형={SS_trends[0]:.2f}  이차={SS_trends[1]:.2f}  삼차={SS_trends[2]:.2f}")
print(f"합계={SS_trends.sum():.2f}  (목표 SS_T={SS_T:.2f})")

# ----------------------------------------------------------------
# Step 6: 추세별 F 검정
# ----------------------------------------------------------------
F_trends = SS_trends / MS_R
p_trends = stats.f.sf(F_trends, 1, df_R)

print(f"\n--- 추세별 F 검정 (df=1, {df_R}) ---")
for name, F, p in zip(["선형", "이차", "삼차"], F_trends, p_trends):
    sig = "***" if p < .001 else ("**" if p < .01 else ("*" if p < .05 else "NS"))
    print(f"  {name}: F={F:.2f}  p={p:.4f}  {sig}")

# ----------------------------------------------------------------
# Step 7: 기여율 분석
# ----------------------------------------------------------------
pct = 100 * SS_trends / SS_T
print(f"\n추세별 SS_T 기여율:")
for name, pct_val in zip(["선형", "이차", "삼차"], pct):
    print(f"  {name}: {pct_val:.1f}%")

10 결과 요약

10.1 핵심 수치 정리

분석 내용 결과 해석
구형성 검정 \(\chi^2(5)=6.32\), NS 표준 F 검정 사용 가능
학년 효과 \(F(3,189)=79.02\), \(p<.0001\) 학년 간 유의한 어휘 차이
\(\hat{\sigma}^2_e\) 0.820 피험자 내 잔차 분산
\(\hat{\sigma}^2_\pi\) 3.263 피험자 간 수준 차이
ICC 0.80 변동의 80%가 개인 간 차이
선형 추세 \(F(1,189)=216.56\), \(p<.0001\) 학년 증가에 따른 어휘 증가
이차 추세 \(F(1,189)=16.56\), \(p<.0001\) 성장 속도 감소 (감속형)
삼차 추세 \(F(1,189)=3.86\), \(p=.051\) 소폭 비선형, 경계적 유의

10.2 ANOVA vs MANOVA: 같은 데이터, 다른 렌즈

이 포스트에서 계산한 단변량 ANOVA 결과는 [177] MANOVA Illustration 포스트의 결과와 직접 대응된다.

검정 항목 RM ANOVA MANOVA
학년 전반 효과 \(F(3,189)=79.02\) Wilks’ \(\Lambda=0.174\), \(F(3,61)=96.45\)
선형 F 216.63, df=(1,189) 221.88, df=(1,63)
분모 오차 공통 \(\text{MS}_R=0.82\) 추세별 개별 오차
구형성 가정 필요 (검정 통과) 불필요

구형성이 성립하는 이 데이터에서는 RM ANOVA가 분모 df가 더 커서 임계값이 낮으므로 검정력에서 미세하게 유리하다.


11 관련 주제

선행 지식

연장 분석

개념 비교

Subscribe

Enjoy this blog? Get notified of new posts by email: