Log-linear Models — 예제

결핵균 생물학적 검정(라틴 정방)과 Lloyd’s 선박 손상 데이터 (McCullagh & Nelder §6.3)

로그선형 모형의 실제 적용을 두 고전 예제로 전개한다. (1) Fisher (1949) 결핵균 검정 — 라틴 정방 설계와 곱셈적 처리효과, (2) Lloyd’s 선박 파도 손상 데이터 — offset 을 통한 rate 회귀, 과산포 진단, 주효과 vs 교호작용 해석, 조건부 자유도 계산까지 수식과 직관으로 풀어낸다.

Statistics
GLM
저자

Kwangmin Kim

공개

2026년 04월 15일

1 두 예제가 보여주는 것

§6.3 의 두 예제는 로그선형 모형의 두 가지 전형을 담고 있다.

예제 반응의 본질 모형 포인트
결핵균 검정(§6.3.1) 측정값(mm) — 본래 카운트가 아님 곱셈적 평균 + 평균비례 분산 → 로그선형으로 접근, 라틴 정방 설계
선박 손상(§6.3.2) 사고 건수 offset \(\log(\text{노출})\) 을 통한 rate 회귀, 과산포

두 예제 모두 “이 데이터가 왜 로그선형 구조에 맞는가” 를 엔지니어링적으로 설명한다는 공통점이 있다. 카운트라고 자동으로 포아송을 쓰지 않고, 카운트가 아니어도 곱셈적 구조와 linear variance 가 맞으면 로그선형 프레임을 쓴다.


2 예제 1: 결핵균 생물학적 검정 (§6.3.1)

2.1 실험 설계 — 라틴 정방 (Latin Square)

Fisher (1949) 의 고전 실험. 120 마리의 소를 네 반(I, II, III, IV) × 30 마리 로 나누고, 각 소의 목 네 부위(site 1–4) 에 네 결핵균 처리를 적용.

처리 제제 용량
A Standard double (0.10 mg)
B Standard single (0.05 mg)
C Weybridge single (0.05 mg)
D Weybridge half (0.025 mg)

라틴 정방 배치:

Site I II III IV
1 A B C D
2 B A D C
3 C D A B
4 D C B A

왜 라틴 정방인가: 소 개체 간 변동(between-animal) 이 부위 간 변동(between-site) 과 처리 간 변동을 가리지 않도록 세 요인을 모두 직교 하게 배치. 각 처리가 모든 반에 한 번씩, 모든 부위에 한 번씩 등장하므로 세 주효과가 교락되지 않는다.

2.2 반응과 모형

반응 \(y_{ij}\) = site \(i\), class \(j\) 에서 30마리의 피부 비후(skin thickening) 합계 (mm). 카운트가 아니지만 Fisher 는 예비 분석에서 다음 두 사실을 확인했다.

  1. 처리와 부위의 효과는 곱셈적 (로그 척도에서 더하기)
  2. \(\mathrm{Var}(Y) \propto \mathrm{E}(Y)\) — 평균에 비례하는 분산

이 두 조건이 맞으면 측정값이라도 로그선형 GLM 이 자연스럽다. 포아송이 아니어도 quasi-Poisson (\(\mathrm{Var}(Y) = \sigma^2 \mu\)) 로 적합.

2.3 모형식

\[ \text{site} + \text{class} + \text{volume} + \text{tuberculin} \]

  • site, class: 4-수준 factor
  • tuberculin: 2-수준 factor (Standard vs Weybridge)
  • volume: 2-수준 factor (low vs high) 또는 정량 공변량 (−1, 0, 1)

\(2 \times 2\) factorial (volume × tuberculin) 로 네 처리를 재구성 — 교호작용 없이. 이것이 “Standard 와 Weybridge 의 용량 효과가 로그 척도에서 동일하다” 는 생물학적 가정의 수학적 표현.

2.4 적합 결과

\[ \hat{\beta}_{\text{high-low}} = 0.2095, \quad \text{SE} = 0.0124 \]

\[ \hat{\beta}_{\text{Weybridge-Standard}} = 0.0026, \quad \text{SE} = 0.0123 \]

과산포 추정: \(\hat\sigma^2 = 1.410/7 = 0.2014\) — Fisher 의 비반복적 추정치 0.2018 과 거의 일치.

2.5 해석 — 곱셈적 효과의 직관

용량 2배 → 반응 \(\exp(0.2095) = 1.233\). 즉 23.3% 증가. 이것이 Standard 와 Weybridge 모두에 적용된다 (교호작용 없음 가정 하).

Weybridge vs Standard 의 상대 효능(relative potency):

\[ 2^{\hat\beta_C / \hat\beta_A} = 2^{0.2121/0.2095} = 2.017. \]

“같은 반응을 내기 위해 Standard 는 Weybridge 의 약 2배 용량이 필요” — 즉 Weybridge 의 효능이 Standard 의 약 2배. Fisher 의 값 2.009 (또는 2.013) 와 본질적으로 같음.

\(0.2121/0.2095\) 인가: 용량이 \(\exp(\beta)\) 로 반응에 영향을 주므로 “동일 반응을 위한 용량비” 는 로그 척도에서 \(\beta\) 의 비율. \(2^{\text{ratio}}\) 로 변환하는 것은 volume 을 \(\log_2\) 로 코딩했기 때문 (half = \(-1\), single = \(0\), double = \(+1\)).

2.6 곱셈성 가정의 검정

volume × tuberculin 교호작용을 포함한 포화 \(2 \times 2\) 모형(즉 4-수준 factor 로 취급)과 비교해 \(F\)-검정 (1, 6 자유도, \(\sigma^2\) 미지이므로 \(\chi^2\) 아님). 유의하지 않으면 곱셈성 가정 유지.

2.7 잔차 설계의 중요성

라틴 정방 덕분에 처리 간 대조는 같은 동물 내 부위 간 차이로 계산되어 동물 간 변동이 제거된다. 만약 다른 설계(랜덤 완전 블록 등) 였다면 동물 간 변동성이 처리 대조의 SE 에 추가로 기여했을 것. 교재 지적: “class 간 대조는 동물 간 변동을 포함하므로 여기 보고된 SE 가 적절하지 않다”는 미묘한 뉘앙스가 있다 — 관심 대조(처리·용량)는 동물 내이므로 SE 가 유효.

현대적 관점: 이 구조는 반복측정 혼합효과 모형 (소를 랜덤효과) 으로 더 명시적으로 다룰 수 있다. 교재 §14.3 에서 상세히 전개된다.


3 예제 2: Lloyd’s 선박 파도 손상 (§6.3.2)

3.1 데이터 구조

Crilley & Heminway, Lloyd’s Register of Shipping. 카고선의 전방부에 파도가 주는 손상 건수와 노출 기간을 세 요인으로 분류.

요인 수준
Ship type (선종) A, B, C, D, E
Year of construction (건조 연도) 1960–64, 65–69, 70–74, 75–79
Period of operation (운항 시기) 1960–74, 1975–79

\(5 \times 4 \times 2 = 40\) 셀. 각 셀에 (사고 건수, 총 운항 개월) 이 기록됨. 1975 이후 건조된 배가 1974 이전에 운항할 수 없으므로 6 셀이 구조적으로 빈 셀(structurally empty).

핵심: 반응은 한 배당 손상 건수가 아니라 셀 전체의 손상 건수. 같은 배가 두 번 손상될 수 있고, 두 기간 모두 운항할 수도 있다.

3.2 모형 — offset 을 통한 rate 회귀

\[ \log(\text{기대 손상 건수}) = \beta_0 + \log(\text{총 운항 개월}) + \text{선종} + \text{건조 연도} + \text{운항 시기} \tag{6.5} \]

offset 의 정체: \(\log(\text{운항 개월})\) 앞의 계수가 1로 고정 된 항. 이것을 다시 쓰면

\[ \log\!\left(\frac{\text{기대 손상 건수}}{\text{운항 개월}}\right) = \beta_0 + \text{선종} + \cdots \]

“단위 시간당 사고율(rate)” 의 로그가 요인들에 선형. 이것이 로그선형 모형의 가장 흔한 응용 형태이다.

왜 offset 인가: “운항 기간이 두 배면 사고도 두 배” 라는 선험적 지식 을 모형에 강제로 반영. 이 계수를 자유 모수로 두면 데이터에서 추정되지만, 여기서는 물리적으로 \(\beta = 1\) 이 자명하다. (나중에 이 가정을 점검함 — 3번 포인트 참조.)

3.3 과산포 가정

배마다 “사고 성향(accident-proneness)” 이 다르므로 inter-ship variability 가 존재. 이는 클러스터 포아송 메커니즘(§6.2.3) 의 전형 — 과산포가 예상된다. 따라서

\[ \mathrm{E}(Y) = \mu, \quad \mathrm{Var}(Y) = \sigma^2 \mu, \quad \sigma^2 > 1 \]

을 가정하되 점추정은 포아송 우도로 진행.

3.4 적합 결과 — 주효과 모형

과산포 추정: \(\hat\sigma^2 = 1.69\). 예상대로 \(> 1\), 즉 실제 분산이 포아송보다 약 70% 더 크다.

주요 결과:

  1. 모든 주효과 유의
  2. offset 의 계수를 자유롭게 두면 \(\hat\beta_{\log(\text{service})} = 0.903\), SE \(= 0.13\) → 1과 유의하게 다르지 않음 ← offset 가정 확인
  3. service period × 다른 요인 2-요인 교호작용 없음
  4. ship type × year of construction 교호작용은 유의할 듯 하지만 과산포 보정 후 \(F\)-비가 \(0.92\)유의하지 않음
  5. 관측 21 (C 선종, 1970–74, 1960–74) 의 표준화 잔차 2.87 — 단일 이상치

3.5 과산포가 결론을 바꾸는 장면

포아송 가정 하에서는 type × year 교호작용의 이탈도 감소 \(38.7 - 14.6 = 24.1\) on 15 df 가 \(\chi^2_{15}\) 에서 유의해 보인다. 그러나 \(\sigma^2 = 1.74\) 로 보정한 \(F\)-비

\[ F = \frac{(38.7 - 14.6)/15}{1.74} = \frac{24.1}{15 \times 1.74} = 0.92 \]

\(F_{15, \text{...}}\) 의 1%도 안 되는 분위수. “유의” 가 “비유의” 로 완전히 뒤집힌다.

교훈: 과산포를 무시하면 교호작용을 부풀려 감지한다. “효과가 시간에 따라 달라진다”는 잘못된 결론이 내려질 수 있다. 과산포 보정은 단순히 SE 를 키우는 기술이 아니라 질적 결론 자체 를 뒤집을 수 있다.

3.6 해석 — 주효과의 물리적 의미

운항 시기 효과 (1974 오일 쇼크 이후 vs 이전):

\[ \hat\beta = 0.385, \quad \text{SE} = 0.154 \]

\(\exp(0.385) = 1.47\). 1974년 이후 사고율이 47% 증가, 95% CI 약 \((8\%, 100\%)\). 선종에 관계없이 균일하게 적용 (교호작용 없음).

왜 1974년 이후 사고가 늘었는가: 오일 쇼크로 에너지 가격이 오르자 선박 연료 절약을 위해 항로·속도 최적화가 요구되고 유지보수가 지연된 결과로 추측된다. 경영 환경의 변화가 유지보수 품질에 영향을 준 거시경제 → 미시 안전의 고리.

선종별 위험: B, C 가 가장 안전, E 가 가장 위험.

건조 연도별 위험 (Table 6.3):

연도 계수 상대 위험
1960–64 0 (기준) 1.00
1965–69 0.70 2.01
1970–74 0.82 2.27
1975–79 0.45 1.57

1965–74년 건조된 배가 가장 위험. 가장 오래된 1960–64 선박이 오히려 더 안전 — 생존 편향 (survivor bias) 가능성. 위험한 배는 일찍 사라지고 살아남은 오래된 배들이 본질적으로 견고할 수 있다.

3.7 조건부 자유도 계산의 미묘함

교호작용 포함 모형에서 잔차 자유도를 계산할 때 표준 공식 은 13을 주지만, 적절한 조건부 참조 분포 에서는 10이 맞다.

이유: 교호작용 모형의 충분통계량type × year 주변 합 (Table 6.4)을 고정. 이 표의 4×4 에서 (4개의 0셀 + 1개 오일쇼크 이후 단일 관측 열) 을 제외하면 실질 자유도는 11, 그리고 service period 주효과가 1 을 더 빼서 10.

실무적 함의: GLM 소프트웨어가 자동으로 주는 자유도가 희소 테이블 / 구조적 결측 / 조건부 충분통계량 상황에서는 잘못될 수 있다. 분할표의 정확 추론에서는 직접 계산이 필요.

3.8 이상치 (관측 21) 논의

선종 C × 1970–74 × 1960–74 (6 사고 / 783 개월). 기대치 1.47 대비 잔차 2.87. 모형을 확장해도 잔차가 2.48 정도로 남음. 단일 관측의 설명되지 않은 높은 사고율.

가능한 설명: (i) C형 선박의 특정 설계 결함이 이 건조 연도 일부 배에 집중, (ii) 특정 운항 지역·날씨 노출, (iii) 기록 오류. 이 관측을 제외하면 해석이 일부 달라지지만, 제외 없이도 주결론은 견고.


4 두 예제의 대비 — 무엇이 다른가

항목 결핵균 선박 손상
반응 측정값 (mm) 카운트
분포 가정 quasi-Poisson (\(\hat\sigma^2 = 0.20\)) quasi-Poisson (\(\hat\sigma^2 = 1.69\))
offset 없음 \(\log(\text{운항 개월})\)
설계 라틴 정방 (균형) 관측 연구 (불균형)
주 관심 곱셈적 처리 효과 rate 변화 + 요인별 위험
과산포 방향 under (\(\sigma^2 < 1\)) over (\(\sigma^2 > 1\))
교호작용 판단 곱셈성 가정 검정 과산포 보정 후 비유의

특히 주목: 결핵균은 \(\hat\sigma^2 < 1\)과소산포. 실험실에서 철저히 통제된 설계이므로 자연 변동이 포아송보다 작다. 선박은 현실 관찰이라 과산포. 두 방향 모두 quasi 프레임으로 수용된다 (§6.2.3 의 “\(0 \le \sigma^2\)” 확장).


5 코드 예시

5.1 Step 1: 결핵균 검정 — 라틴 정방 quasi-Poisson

import numpy as np
import pandas as pd
import statsmodels.api as sm
import statsmodels.formula.api as smf

# Table 6.1b — 사이트 × 클래스 반응 (30마리 합)
# 라틴 정방 배치: Site×Class → 처리 A/B/C/D
y = np.array([
    [454, 249, 349, 249],  # site 1: A B C D
    [408, 322, 312, 347],  # site 2: B A D C
    [523, 268, 411, 285],  # site 3: C D A B
    [364, 283, 266, 290],  # site 4: D C B A
])
site = np.repeat(np.arange(1, 5), 4)
cls  = np.tile(np.arange(1, 5), 4)
y_flat = y.ravel()

treat_map = [
    ["A", "B", "C", "D"],
    ["B", "A", "D", "C"],
    ["C", "D", "A", "B"],
    ["D", "C", "B", "A"],
]
treat = np.array([treat_map[s-1][c-1] for s, c in zip(site, cls)])
# volume: A=+1, B=0, C=0, D=-1 (Standard double, single / Weybridge single, half)
volume = np.array([{"A":1, "B":0, "C":0, "D":-1}[t] for t in treat])
# tuberculin: Standard (A,B) = 0, Weybridge (C,D) = 1
tuber = np.array([{"A":0, "B":0, "C":1, "D":1}[t] for t in treat])

df = pd.DataFrame({
    "y": y_flat, "site": site.astype(str), "cls": cls.astype(str),
    "volume": volume, "tuber": tuber,
})

# quasi-Poisson (scale 자동 추정)
fit = smf.glm("y ~ C(site) + C(cls) + volume + tuber", data=df,
              family=sm.families.Poisson()).fit(scale="X2")
print(fit.summary().tables[1])
print(f"\nScale = {fit.scale:.4f}   (교재: 0.2014)")

# 상대 효능
beta_volume = fit.params["volume"]
beta_tuber  = fit.params["tuber"]
print(f"\n고용량 효과  = {beta_volume:.4f}  → 용량 2배 시 {np.exp(beta_volume):.3f}배")
print(f"Weybridge 효과 = {beta_tuber:.4f}")
print(f"상대 효능 (Weybridge/Standard) = 2^{(beta_tuber + beta_volume)/beta_volume:.4f}")

5.2 Step 2: 선박 사고 — offset rate 회귀

import numpy as np
import pandas as pd
import statsmodels.api as sm
import statsmodels.formula.api as smf

# Table 6.2 의 축약 — 구조적 0 셀 제외
records = [
    # ship, year, period, service, incidents
    ("A", "1960-64", "1960-74",  127,  0),
    ("A", "1960-64", "1975-79",   63,  0),
    ("A", "1965-69", "1960-74", 1095,  3),
    ("A", "1965-69", "1975-79", 1095,  4),
    ("A", "1970-74", "1960-74", 1512,  6),
    ("A", "1970-74", "1975-79", 3353, 18),
    ("A", "1975-79", "1975-79", 2244, 11),
    ("B", "1960-64", "1960-74", 44882, 39),
    ("B", "1960-64", "1975-79", 17176, 29),
    ("B", "1965-69", "1960-74", 28609, 58),
    ("B", "1965-69", "1975-79", 20370, 53),
    ("B", "1970-74", "1960-74",  7064, 12),
    ("B", "1970-74", "1975-79", 13099, 44),
    ("B", "1975-79", "1975-79",  7117, 18),
    ("C", "1960-64", "1960-74",  1179,  1),
    ("C", "1960-64", "1975-79",   552,  1),
    ("C", "1965-69", "1960-74",   781,  0),
    ("C", "1965-69", "1975-79",   676,  1),
    ("C", "1970-74", "1960-74",   783,  6),  # 관측 21 — 이상치
    ("C", "1970-74", "1975-79",  1948,  2),
    ("C", "1975-79", "1975-79",   274,  1),
    ("D", "1960-64", "1960-74",   251,  0),
    ("D", "1960-64", "1975-79",   105,  0),
    ("D", "1965-69", "1960-74",   288,  0),
    ("D", "1965-69", "1975-79",   192,  0),
    ("D", "1970-74", "1960-74",   349,  2),
    ("D", "1970-74", "1975-79",  1208, 11),
    ("D", "1975-79", "1975-79",  2051,  4),
    ("E", "1960-64", "1960-74",    45,  0),
    ("E", "1965-69", "1960-74",   789,  7),
    ("E", "1965-69", "1975-79",   437,  7),
    ("E", "1970-74", "1960-74",  1157,  5),
    ("E", "1970-74", "1975-79",  2161, 12),
    ("E", "1975-79", "1975-79",   542,  1),
]
df = pd.DataFrame(records, columns=["ship", "year", "period", "service", "y"])
df["log_service"] = np.log(df["service"])

# 주효과 모형 (offset 고정)
fit_main = smf.glm(
    "y ~ C(ship) + C(year) + C(period)",
    data=df,
    family=sm.families.Poisson(),
    offset=df["log_service"],
).fit(scale="X2")
print("=== Main effects (quasi-Poisson) ===")
print(fit_main.summary().tables[1])
print(f"Scale sigma^2 = {fit_main.scale:.3f}  (교재: 1.69)")

# offset 계수를 자유롭게 두어 1인지 확인
fit_free = smf.glm(
    "y ~ C(ship) + C(year) + C(period) + log_service",
    data=df, family=sm.families.Poisson(),
).fit(scale="X2")
print(f"\n자유 offset 계수 = {fit_free.params['log_service']:.3f} "
      f"(SE = {fit_free.bse['log_service']:.3f})  — 1과 유의차 없으면 offset 가정 확인")

# 운항 시기 효과 해석
beta_period = fit_main.params["C(period)[T.1975-79]"]
print(f"\n1975-79 vs 1960-74 효과 = {beta_period:.3f}")
print(f"rate 증가 배수 = exp({beta_period:.3f}) = {np.exp(beta_period):.3f}")
print(f"95% CI (점근 정규): "
      f"({np.exp(beta_period - 1.96 * fit_main.bse['C(period)[T.1975-79]']):.3f}, "
      f"{np.exp(beta_period + 1.96 * fit_main.bse['C(period)[T.1975-79]']):.3f})")

# type × year 교호작용 F-test (과산포 보정)
fit_inter = smf.glm(
    "y ~ C(ship) * C(year) + C(period)",
    data=df, family=sm.families.Poisson(),
    offset=df["log_service"],
).fit(scale="X2")
dD = fit_main.deviance - fit_inter.deviance
df_diff = fit_main.df_resid - fit_inter.df_resid
F = (dD / df_diff) / fit_inter.scale
print(f"\n=== type × year 교호작용 검정 (과산포 보정) ===")
print(f"Delta D = {dD:.2f} on {df_diff} df")
print(f"scale = {fit_inter.scale:.3f}")
print(f"F = {F:.3f}  (교재: 0.92 — 비유의)")

5.3 R 대응

# 결핵균
df_fisher <- # ... as above
fit <- glm(y ~ site + cls + volume + tuber, data = df_fisher,
           family = quasipoisson())

# 선박
fit_ship <- glm(y ~ ship + year + period, data = df_ship,
                offset = log(service),
                family = quasipoisson())
summary(fit_ship)
anova(fit_ship, update(fit_ship, . ~ . + ship:year), test = "F")

6 자주 걸리는 함정

함정 증상 처방
카운트 아니라고 로그선형 포기 OLS 로 돌리고 SE 편향 평균-분산 관계·곱셈성 확인 후 quasi-Poisson
offset 대신 비율을 반응으로 분산 구조 깨짐 원본 카운트 + offset(log(exposure))
과산포 무시하고 \(\chi^2\) LRT 교호작용 과잉 감지 \(F\)-검정 + \(\hat\sigma^2\) 보정
offset 계수 고정 가정 없이 사용 계수가 1 아닐 때 편향 먼저 자유 계수로 추정해 1 인지 확인
희소 분할표 자동 자유도 신뢰 p-value 부정확 충분통계량 기반 수동 계산
이상치 하나로 결론 좌우 잔차 3 근처 관측 방임 sensitivity analysis (제거 전후 비교)
생존 편향 간과 오래된 배가 안전해 보임 시간 효과 해석 시 맥락 제시
under-dispersion 이 비정상이라고 오해 \(\sigma^2 < 1\) 버림 통제된 설계에서 정상적 현상

7 관련 주제

선행 지식

후속 주제 (placeholder)

관련 개념


8 참고문헌

  • McCullagh, P. & Nelder, J. A. (1989). Generalized Linear Models (2nd ed.), §6.3. Chapman & Hall.
  • Fisher, R. A. (1949). A biological assay of tuberculins. Biometrics, 5, 300–316.
  • Crilley, J. & Heminway, L. N. — Lloyd’s Register of Shipping, wave damage data (1985).
  • Aitkin, M. & Francis, B. (1995). Fitting overdispersed generalized linear models by nonparametric maximum likelihood. GLIM Newsletter.
  • Cameron, A. C. & Trivedi, P. K. (2013). Regression Analysis of Count Data (2nd ed.). Cambridge.

Subscribe

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