1 GLMM (2): 로지스틱 혼합 모델
1.1 언제 사용하는가
결과 변수가 이진(0/1)이고, 같은 개체를 반복 측정하거나 군집 구조가 있을 때.
| 상황 | 결과 변수 | 구조 |
|---|---|---|
| AI Agent 개인화 실험 | 프리미엄 전환 여부 | 사용자 반복 측정 |
| 임상 시험 | 치료 반응 여부 (1=반응) | 환자 × 병원 군집 |
| 교육 연구 | 문제 정답 여부 | 학생 × 학급 군집 |
| 마케팅 | 이메일 클릭 여부 | 고객 반복 발송 |
1.2 모델 수식
\[\text{logit}(P(Y_{ij}=1 \mid u_i)) = \beta_0 + \beta_1 X_{ij} + u_i\]
\[u_i \sim N(0, \sigma^2_u)\]
- \(Y_{ij}\): 개체 \(i\)의 \(j\)번째 측정값 (0 또는 1)
- \(\beta_0 + \beta_1 X_{ij}\): 고정 효과
- \(u_i\): 개체 \(i\)의 랜덤 절편 — “이 사람의 기본 전환 성향”
조건부 전환 확률:
\[P(Y_{ij}=1 \mid u_i) = \frac{1}{1 + \exp(-(\beta_0 + \beta_1 X_{ij} + u_i))}\]
1.3 추정 방법: 왜 어려운가
LMM은 랜덤 효과를 closed-form으로 적분 제거할 수 있지만, 로지스틱 링크에서는 그것이 불가능하다.
\[L(\beta, \sigma^2_u) = \prod_i \int \prod_j P(Y_{ij} \mid u_i) \cdot \phi(u_i; 0, \sigma^2_u) \, du_i\]
이 적분에는 해석적 해가 없다. 근사 방법:
| 방법 | 특징 | 권장 상황 |
|---|---|---|
| Laplace Approximation | 빠름, 적당히 정확 | 일반적 사용 (기본값) |
| Gauss-Hermite Quadrature (GHQ) | 정확하지만 느림 | 랜덤 효과 작을 때, 정확도 중요 |
| PQL (Penalized Quasi-Likelihood) | 매우 빠름, 이분분산 과소추정 | 탐색적 분석 |
library(lme4)
# Laplace (기본값, nAGQ=1)
m_lap <- glmer(converted ~ personalized + week + (1 | user_id),
data=df, family=binomial, nAGQ=1)
# 7-point GHQ (더 정확)
m_ghq <- glmer(converted ~ personalized + week + (1 | user_id),
data=df, family=binomial, nAGQ=7)
# 비교
cat("Laplace β₁:", fixef(m_lap)["personalized"], "\n")
cat("7-GHQ β₁:", fixef(m_ghq)["personalized"], "\n")1.4 실무 예시: AI Agent 프리미엄 전환 분석
1.4.1 데이터 생성
import numpy as np
import pandas as pd
np.random.seed(42)
n_users, n_weeks = 500, 8
user_ids = np.repeat(range(n_users), n_weeks)
weeks = np.tile(range(1, n_weeks + 1), n_users)
personalized = (weeks >= 5).astype(int)
# 사용자별 기본 전환 성향 (분산 = 1.5)
u_i = np.repeat(np.random.normal(0, 1.22, n_users), n_weeks)
# 전환 확률 → 이진 결과
log_odds = -3.0 + 0.65 * personalized + 0.04 * weeks + u_i
prob = 1 / (1 + np.exp(-log_odds))
converted = np.random.binomial(1, prob)
segment = np.repeat(
np.random.choice(["SI","MIEP","N"], n_users, p=[0.6,0.3,0.1]),
n_weeks
)
df = pd.DataFrame({
"user_id": user_ids, "week": weeks,
"personalized": personalized, "converted": converted,
"segment": segment
})
print(f"전체 전환율: {df['converted'].mean():.3f}")
print(df.groupby("personalized")["converted"].mean())전체 전환율: 0.063
personalized
0 0.042 ← 개인화 전
1 0.087 ← 개인화 후 (단순 비교: +4.5%p)
1.4.2 모델 적합
library(lme4)
library(broom.mixed)
# Null model (ICC 계산용)
m0 <- glmer(converted ~ 1 + (1 | user_id), data=df, family=binomial)
# Fixed effects 추가
m1 <- glmer(converted ~ week + (1 | user_id), data=df, family=binomial)
m2 <- glmer(converted ~ week + personalized + (1 | user_id), data=df, family=binomial)
m3 <- glmer(converted ~ week + personalized + segment + (1 | user_id), data=df, family=binomial)
# 모델 비교
anova(m0, m1, m2, m3) Df AIC BIC logLik deviance Chisq Chi Df Pr(>Chisq)
m0 2 2341.2 2353.3 -1168.6 2337.2
m1 3 2335.4 2353.6 -1164.7 2329.4 7.8 1 0.005 **
m2 4 2286.1 2310.4 -1139.1 2278.1 51.2 1 <0.001 ***
m3 6 2269.8 2306.3 -1128.9 2257.8 20.4 2 <0.001 ***
→ m3 선택 (모든 추가 항 유의)
1.4.3 최종 모델 결과
Random effects:
Groups Name Variance Std.Dev.
user_id (Intercept) 1.48 1.22 ← 사용자 간 전환 성향 차이 큼
Fixed effects:
Estimate Std.Error z-value Pr(>|z|)
(Intercept) -3.42 0.22 -15.5 <0.001
week 0.04 0.02 2.0 0.046
personalized 0.68 0.13 5.2 <0.001 ← 관심 계수
segmentMIEP 0.82 0.16 5.1 <0.001
segmentN -0.49 0.21 -2.3 0.020
1.4.4 결과 해석: Conditional OR
1.4.5 Conditional vs Marginal 전환율
library(emmeans)
# 조건부 예측 (u_i = 0인 평균적 사용자 기준)
emmeans(m3, ~personalized, type="response")
# 주변 예측 (전체 모집단 평균, u_i 분포로 적분)
# → GEE 또는 margins 패키지 사용Conditional (u_i = 0인 사람):
개인화 전: 1 / (1 + exp(3.42)) = 3.2%
개인화 후: 1 / (1 + exp(3.42 - 0.68)) = 5.9%
Conditional OR = 1.97
Marginal (전체 모집단 평균):
개인화 전: ~4.2% (관측값과 일치)
개인화 후: ~8.7% (관측값과 일치)
Marginal OR ≈ 1.51 (GLMM OR보다 작음)
→ 리포트 목적에 따라 선택:
"이 사용자에게 효과?" → Conditional OR (GLMM)
"서비스 전체에 도입 시 전환율 변화?" → Marginal (GEE 또는 margins)
1.5 ICC in GLMM
로지스틱 모델에서 ICC 계산은 LMM과 다르다.
방법 1: 잠재 변수 접근법
로지스틱 모델의 잠재 변수는 표준 로지스틱 분포 → 분산 = \(\pi^2/3 \approx 3.29\)
\[\text{ICC} = \frac{\sigma^2_u}{\sigma^2_u + \pi^2/3}\]
sigma2_u <- VarCorr(m0)$user_id[1] # 1.48
icc_logit <- sigma2_u / (sigma2_u + pi^2/3)
cat("ICC (logit scale) =", round(icc_logit, 3))
# ICC = 1.48 / (1.48 + 3.29) = 0.310방법 2: performance 패키지 (권장)
1.6 모델 진단
library(DHARMa)
# Residual 시뮬레이션 기반 진단
simulated_residuals <- simulateResiduals(fittedModel=m3, n=500)
plot(simulated_residuals)
# 과산포 검정
testDispersion(simulated_residuals)
# → 유의하면 과산포 → Negative Binomial 또는 Beta-Binomial 고려
# 이상치 탐지
testOutliers(simulated_residuals)DHARMa 진단 결과 해석:
- QQ plot: 점들이 대각선에 가까우면 분포 가정 적절
- Residual vs Fitted: 패턴 없어야 함
- Dispersion test: p > 0.05 이면 과산포 없음
1.7 랜덤 기울기 포함 (Random Slope)
개인화에 대한 반응이 사람마다 다를 경우:
m_rs <- glmer(
converted ~ week + personalized + segment + (1 + personalized | user_id),
data=df, family=binomial, nAGQ=1
)
# 랜덤 기울기 분산 확인
VarCorr(m_rs)
# → personalized의 분산 > 0: 사람마다 개인화 효과 다름1.8 요약
| 항목 | 값/설명 |
|---|---|
| 모델 | Logistic GLMM |
| 개인화 효과 | OR = 1.97 (Conditional) |
| 사용자 간 분산 | \(\sigma^2_u = 1.48\) |
| ICC | 0.31 (전환 성향의 31%가 개인차) |
| 추정 방법 | Laplace Approximation |
| 소프트웨어 | R: lme4::glmer / Python: 제한적 (statsmodels 미지원, PyMC 사용) |
Python에서 GLMM: statsmodels는 LMM만 지원. 이진 GLMM은 R(lme4) 또는 Python의 PyMC(베이지안), bambi 패키지 사용.
다음: 07-mixed-model-glmm-count.qmd — 카운트 결과 (포아송, 음이항)