1 관찰 연구 설계: 코호트, 케이스-컨트롤, 단면 연구
이 파일은 20번 연구 설계 대분류의 관찰 연구 섹션을 확장한 것이다. 이전 파일: 32 — RCT와 A/B 테스트의 설계 원칙
1.1 1. 관찰 연구가 필요한 이유
RCT(A/B 테스트)는 인과 추론의 황금 기준이지만, 다음 상황에서는 실험이 불가능하다:
| 상황 | 의학 예시 | IT 예시 |
|---|---|---|
| 윤리적 제약 | 흡연을 무작위 배정할 수 없음 | 의도적으로 서비스 품질을 낮출 수 없음 |
| 현실적 제약 | 희귀 질환은 충분한 표본 확보 불가 | 소수 엔터프라이즈 고객 대상 실험 불가 |
| 비용/시간 제약 | 30년 추적 코호트 비용 막대 | 장기 효과는 A/B로 측정 어려움 |
| 이미 일어난 사건 | 체르노빌 방사선 노출 효과 | 과거 알고리즘 변경의 장기 영향 |
| 정책/규제 | 승인 전 약물 배포 불가 | 일부 국가에서 개인화 실험 제한 |
관찰 연구의 본질: 연구자가 처치를 배정하지 않고, 자연적으로 발생한 노출 차이를 이용하여 효과를 추정한다.
1.2 2. 코호트 연구 (Cohort Study)
1.2.1 구조
공통 특성을 가진 집단(코호트)을 시간에 따라 추적하여 노출 → 결과의 관계를 관찰한다.
시간 흐름 →
노출 여부 확인 결과 측정
↓ ↓
코호트 선정 → [노출군: 흡연자] ───→ 폐암 발생?
[비노출군: 비흡연자] ──→ 폐암 발생?
1.2.2 전향적 코호트 vs 후향적 코호트
| 특성 | 전향적 (Prospective) | 후향적 (Retrospective) |
|---|---|---|
| 시작점 | 현재: 노출 확인 → 미래 추적 | 현재: 과거 기록 검토 |
| 데이터 | 미래에 수집 (설계 통제 가능) | 기존 기록/DB 활용 |
| 시간/비용 | 수년~수십 년, 비용 높음 | 빠르고 저렴 |
| 편향 | 정보 편향 적음 | 기록 의존 → 측정 편향 |
| 인과 강도 | 상대적으로 강함 | 상대적으로 약함 |
| 유명 사례 | Framingham Heart Study (1948~) | Nurses’ Health Study |
| IT 대응 | 기능 출시 후 신규 사용자 추적 | 로그 기반 과거 행동 분석 |
1.2.3 상대 위험도 (Relative Risk, RR)
\[RR = \frac{P(\text{결과} \mid \text{노출})}{P(\text{결과} \mid \text{비노출})} = \frac{a / (a+b)}{c / (c+d)}\]
2x2 표:
| 결과 O | 결과 X | 합계 | |
|---|---|---|---|
| 노출 O | a | b | a+b |
| 노출 X | c | d | c+d |
해석:
- \(RR = 1\): 연관 없음
- \(RR > 1\): 노출이 위험 증가 (예: RR=2 → 위험 2배)
- \(RR < 1\): 노출이 보호 효과 (예: RR=0.5 → 위험 절반)
95% 신뢰구간:
\[\ln(RR) \pm z_{0.025} \sqrt{\frac{1}{a} - \frac{1}{a+b} + \frac{1}{c} - \frac{1}{c+d}}\]
1.2.4 인시던스 레이트 (Incidence Rate)
추적 시간이 개인마다 다를 때 (중도 절단 등):
\[IR = \frac{\text{사건 발생 수}}{\text{인-시간 (person-time)}}\]
\[IRR = \frac{IR_{\text{노출}}}{IR_{\text{비노출}}}\]
인-시간의 직관:
사용자 A: 1월~6월 관찰 (이탈) → 6인-월
사용자 B: 1월~12월 관찰 → 12인-월
사용자 C: 3월~5월 관찰 (이탈) → 3인-월
총 인-시간: 21인-월
이탈 2건 → IR = 2/21 = 0.095 이탈/인-월
1.2.5 IT 적용: 가입 월 기반 코호트 리텐션 분석
import pandas as pd
import numpy as np
# --- 시뮬레이션 데이터 ---
np.random.seed(42)
n = 5000
df = pd.DataFrame({
"user_id": range(n),
"signup_date": pd.date_range("2025-01-01", periods=365, freq="D")[
np.random.randint(0, 365, n)
],
"last_active": pd.date_range("2025-01-01", periods=365, freq="D")[
np.random.randint(0, 365, n)
]
})
df["last_active"] = df[["signup_date", "last_active"]].max(axis=1) + pd.to_timedelta(
np.random.exponential(60, n), unit="D"
)
# --- 코호트 구성 ---
df["cohort"] = df["signup_date"].dt.to_period("M")
df["tenure_months"] = (
(df["last_active"] - df["signup_date"]).dt.days / 30.44
).astype(int)
# --- 코호트별 리텐션 테이블 ---
cohort_sizes = df.groupby("cohort")["user_id"].nunique()
retention_data = []
for month_offset in range(13):
active = df[df["tenure_months"] >= month_offset].groupby("cohort")["user_id"].nunique()
retention = (active / cohort_sizes).rename(f"M+{month_offset}")
retention_data.append(retention)
retention_table = pd.concat(retention_data, axis=1)
print("=== 코호트 리텐션 테이블 ===")
print(retention_table.round(2))
# --- 코호트 간 비교: RR 계산 ---
# 노출군: 2025-01 코호트 (기능 X 미적용)
# 비노출군: 2025-04 코호트 (기능 X 적용)
# 결과: M+6 시점 이탈 여부
# RR = P(이탈|노출) / P(이탈|비노출)1.2.6 R 코드
library(dplyr)
library(tidyr)
library(lubridate)
# 코호트 리텐션
df <- df %>%
mutate(
cohort = floor_date(signup_date, "month"),
tenure_months = as.integer(difftime(last_active, signup_date, units = "days")) %/% 30
)
retention <- df %>%
group_by(cohort) %>%
summarise(
m0 = n(),
m1 = sum(tenure_months >= 1),
m3 = sum(tenure_months >= 3),
m6 = sum(tenure_months >= 6)
) %>%
mutate(across(m1:m6, ~ .x / m0))
# RR 계산
library(epitools)
tab <- matrix(c(a, b, c, d), nrow = 2, byrow = TRUE)
riskratio(tab)1.3 3. 케이스-컨트롤 연구 (Case-Control Study)
1.3.1 구조
결과에서 거꾸로 과거 노출을 탐색한다.
시간 흐름 (거꾸로 ←):
결과 확인 과거 노출 탐색
↓ ↓
케이스 선정 → [케이스: 이탈 사용자] → 과거 오류 경험 빈도?
→ [컨트롤: 유지 사용자] → 과거 오류 경험 빈도?
1.3.2 왜 케이스-컨트롤인가?
- 희귀 결과에 효율적: 결과 발생자를 직접 모집하므로, 코호트 연구 대비 비용이 크게 절감
- 빠름: 이미 발생한 결과를 기준으로 시작
- 의학 명작 예시: Doll & Hill (1950) — 폐암 환자 vs 비환자의 흡연 이력 비교
1.3.3 오즈비 (Odds Ratio, OR)
케이스-컨트롤에서는 발생률을 직접 계산할 수 없으므로 RR 대신 OR을 사용한다.
\[OR = \frac{\text{케이스의 노출 오즈}}{\text{컨트롤의 노출 오즈}} = \frac{a/b}{c/d} = \frac{ad}{bc}\]
| 노출 O | 노출 X | |
|---|---|---|
| 케이스 | a | b |
| 컨트롤 | c | d |
95% 신뢰구간:
\[\ln(OR) \pm z_{0.025} \sqrt{\frac{1}{a} + \frac{1}{b} + \frac{1}{c} + \frac{1}{d}}\]
1.3.4 희귀 질환 가정: OR \(\approx\) RR
결과가 희귀할 때 (\(P(\text{결과}) < 10\%\)):
\[RR = \frac{a/(a+b)}{c/(c+d)} \approx \frac{a/b}{c/d} = OR\]
왜냐하면 \(a \ll b\)이고 \(c \ll d\)이면 \(a+b \approx b\), \(c+d \approx d\)이기 때문이다.
IT에서의 적용:
- 이탈률이 5% 미만인 서비스 → OR \(\approx\) RR로 해석 가능
- 이탈률이 30% → OR과 RR 차이가 큼, 해석 주의
1.3.5 IT 적용: 이탈 사용자 vs 유지 사용자 과거 행동 비교
import pandas as pd
import numpy as np
from scipy.stats import fisher_exact
# --- 케이스: 최근 30일 이탈 사용자 ---
# --- 컨트롤: 같은 기간 활성 사용자 (매칭) ---
# 이탈 사용자 500명, 유지 사용자 500명 (1:1 매칭)
np.random.seed(42)
n_case = 500
n_control = 500
cases = pd.DataFrame({
"group": "case",
"user_id": range(n_case),
"had_error_last_30d": np.random.binomial(1, 0.45, n_case), # 45% 오류 경험
"sessions_last_30d": np.random.poisson(3, n_case),
"feature_X_used": np.random.binomial(1, 0.20, n_case), # 20% 기능X 사용
})
controls = pd.DataFrame({
"group": "control",
"user_id": range(n_case, n_case + n_control),
"had_error_last_30d": np.random.binomial(1, 0.25, n_control), # 25% 오류 경험
"sessions_last_30d": np.random.poisson(8, n_control),
"feature_X_used": np.random.binomial(1, 0.35, n_control), # 35% 기능X 사용
})
df = pd.concat([cases, controls], ignore_index=True)
# --- 2x2 표: 오류 경험과 이탈 ---
tab = pd.crosstab(df["group"], df["had_error_last_30d"])
print("=== 오류 경험 × 이탈 ===")
print(tab)
a = tab.loc["case", 1] # 케이스 & 노출
b = tab.loc["case", 0] # 케이스 & 비노출
c = tab.loc["control", 1] # 컨트롤 & 노출
d = tab.loc["control", 0] # 컨트롤 & 비노출
OR = (a * d) / (b * c)
se_ln_OR = np.sqrt(1/a + 1/b + 1/c + 1/d)
ci_lower = np.exp(np.log(OR) - 1.96 * se_ln_OR)
ci_upper = np.exp(np.log(OR) + 1.96 * se_ln_OR)
print(f"\nOR = {OR:.3f} (95% CI: {ci_lower:.3f} - {ci_upper:.3f})")
# Fisher's exact test
odds_ratio, p_value = fisher_exact([[a, b], [c, d]])
print(f"Fisher's exact p-value = {p_value:.4f}")
# --- 다변량: 로지스틱 회귀로 조정된 OR ---
import statsmodels.api as sm
df["is_case"] = (df["group"] == "case").astype(int)
X = df[["had_error_last_30d", "sessions_last_30d", "feature_X_used"]]
X = sm.add_constant(X)
y = df["is_case"]
model = sm.Logit(y, X).fit(disp=0)
print("\n=== 로지스틱 회귀 (조정된 OR) ===")
print(pd.DataFrame({
"OR": np.exp(model.params),
"95% CI lower": np.exp(model.conf_int()[0]),
"95% CI upper": np.exp(model.conf_int()[1]),
"p-value": model.pvalues
}).round(4))1.3.6 R 코드
library(epitools)
# 2x2 표
tab <- matrix(c(a, b, c, d), nrow = 2, byrow = TRUE,
dimnames = list(c("case", "control"), c("exposed", "unexposed")))
oddsratio(tab, method = "wald")
# 로지스틱 회귀
model <- glm(is_case ~ had_error + sessions + feature_X, data = df, family = binomial)
exp(cbind(OR = coef(model), confint(model)))1.4 4. 단면 연구 (Cross-sectional Study)
1.4.1 구조
특정 시점의 스냅샷으로 노출과 결과를 동시에 측정한다.
시점 T
↓
전체 모집단 → [조사: 노출 여부 + 현재 결과 여부]
인과 방향 불명확:
운동하는 사람이 건강한 건가?
건강한 사람이 운동하는 건가?
1.4.2 측정 지표: 유병률 (Prevalence)
\[\text{Prevalence} = \frac{\text{시점 T에 결과를 가진 사람 수}}{\text{시점 T의 전체 인원}}\]
유병률 vs 발생률:
| 지표 | 측정 | 시간 | 설계 |
|---|---|---|---|
| 유병률 (Prevalence) | 시점의 비율 | 스냅샷 | 단면 |
| 발생률 (Incidence) | 기간 중 신규 발생 | 추적 필요 | 코호트 |
1.4.3 장단점
| 장점 | 단점 |
|---|---|
| 빠르고 저렴 | 인과관계 불명확 |
| 유병률/현황 파악에 유용 | 시간적 선후 관계 알 수 없음 |
| 가설 생성에 적합 | 유병-발생 편향 (prevalence-incidence bias) |
| 다수 변수 동시 측정 가능 | 희귀 사건 탐지 어려움 |
1.4.4 IT 적용
import pandas as pd
# --- 단면 연구: 특정 날짜의 사용자 스냅샷 ---
snapshot_date = "2026-03-01"
# 해당 날짜 활성 사용자의 특성
snapshot = pd.read_sql(f"""
SELECT
user_id,
device_type,
subscription_tier,
feature_x_enabled,
satisfaction_score,
error_count_today,
session_duration_min
FROM user_activity
WHERE activity_date = '{snapshot_date}'
""", conn)
# 유병률: 불만족 비율
prevalence_dissatisfied = (snapshot["satisfaction_score"] <= 2).mean()
print(f"불만족 유병률: {prevalence_dissatisfied:.1%}")
# 노출(기능X)과 결과(불만족)의 교차 분석
cross_tab = pd.crosstab(
snapshot["feature_x_enabled"],
snapshot["satisfaction_score"] <= 2,
margins=True
)
print(cross_tab)
# 주의: 이것만으로는 기능X가 불만족을 '유발'하는지 알 수 없음
# (인과 방향 불명확)1.4.5 단면 → 코호트로의 확장
단면 연구에서 흥미로운 패턴을 발견하면 → 코호트 연구로 확장하여 인과를 탐색:
단면: "기능X 사용자의 만족도가 낮다" (관찰)
↓ 가설: 기능X → 불만족?
코호트: 기능X 도입 시점 전후로 사용자 추적
↓ 가설 지지?
RCT: 기능X를 무작위 배정하여 인과 확인
1.5 5. 연구 설계별 비교 표
| 특성 | 코호트 (전향적) | 코호트 (후향적) | 케이스-컨트롤 | 단면 |
|---|---|---|---|---|
| 시간 방향 | 노출 → 결과 (미래) | 노출 → 결과 (과거) | 결과 → 노출 (역추적) | 동시 측정 |
| 주요 지표 | RR, IR, HR | RR, IR | OR | 유병률, PR |
| 인과 강도 | 중-상 | 중 | 중-하 | 하 |
| 비용/시간 | 높음 | 중간 | 낮음 | 낮음 |
| 희귀 결과 | 비효율적 | 비효율적 | 효율적 | 비효율적 |
| 희귀 노출 | 효율적 | 효율적 | 비효율적 | 비효율적 |
| 주요 편향 | 이탈, 측정 | 기록 의존 | 회상 편향, 선택 편향 | 인과 불명확 |
| IT 적용 | 기능 출시 후 추적 | 로그 기반 분석 | 이탈 원인 탐색 | DAU 스냅샷 |
1.6 6. 효과 측정 지표 정리
1.6.1 전체 지표 체계
관찰 연구 실험 연구
───────── ─────────
비율 비교: RR (코호트) RR (RCT)
OR (케이스-컨트롤) OR (로지스틱)
시간 비교: HR (생존 분석) HR (RCT 생존)
절대 효과: ARR ARR
효율성: NNT NNT
상대 효과: RRR RRR
1.6.2 각 지표의 수식과 직관
1.6.2.1 RR (Relative Risk, 상대 위험도)
\[RR = \frac{P(\text{outcome} \mid \text{exposed})}{P(\text{outcome} \mid \text{unexposed})}\]
직관: “노출된 사람은 노출되지 않은 사람보다 몇 배 위험한가?”
IT: “처치군의 전환율은 대조군의 몇 배인가?”
1.6.2.2 OR (Odds Ratio, 오즈비)
\[OR = \frac{P(\text{outcome} \mid \text{exposed}) / [1 - P(\text{outcome} \mid \text{exposed})]}{P(\text{outcome} \mid \text{unexposed}) / [1 - P(\text{outcome} \mid \text{unexposed})]} = \frac{ad}{bc}\]
직관: “오즈(확률이 아닌 오즈)의 비율”
주의: OR은 항상 RR보다 1에서 더 멀다 (효과를 과장한다).
왜 OR이 RR보다 항상 1에서 더 먼가?
RR은 확률의 비율이고, OR은 오즈(odds)의 비율이다. 오즈는 \(p / (1-p)\)이므로 확률 \(p\)가 커질수록 오즈는 확률보다 빠르게 증가한다.
예시: 발생률 20%인 결과
노출군 발생률: 40% → 오즈 = 0.40 / 0.60 = 0.667
비노출군 발생률: 20% → 오즈 = 0.20 / 0.80 = 0.250
RR = 0.40 / 0.20 = 2.0
OR = 0.667 / 0.250 = 2.67
→ 같은 현상인데 OR로 보면 "2.67배 위험"으로 읽혀서 과장됨
직관: 오즈는 "성공 대비 실패"의 비율이므로,
분모(1-p)가 줄어드는 효과까지 포함하여 항상 RR보다 극단적임
| 기저 발생률 | RR | OR | 괴리 |
|---|---|---|---|
| 5% | 2.0 | 2.1 | 작음 — 희귀 질환이면 OR \(\approx\) RR |
| 20% | 2.0 | 2.7 | 중간 — 해석 주의 필요 |
| 50% | 2.0 | 4.0 | 큼 — OR을 RR로 오해하면 효과 2배 과장 |
실무적 함의: 이탈률이 30%를 넘는 서비스에서 로지스틱 회귀의 OR 계수를 “이탈 위험이 X배”로 해석하면 효과를 과대 평가하게 된다. 이 경우 Poisson 회귀로 RR을 직접 추정하거나, OR과 RR의 변환 공식을 사용해야 한다.
1.6.2.3 HR (Hazard Ratio, 위험비)
\[HR = \frac{h_{\text{exposed}}(t)}{h_{\text{unexposed}}(t)}\]
여기서 \(h(t)\)는 시점 \(t\)의 순간 위험률(hazard rate).
직관: “어느 시점에서든 노출군이 사건을 경험할 순간 위험이 몇 배인가?”
IT: “처치군 사용자가 이탈할 순간 위험이 대조군의 몇 배인가?” (Cox 모델)
1.6.2.4 ARR (Absolute Risk Reduction, 절대 위험 감소)
\[ARR = P_{\text{control}} - P_{\text{treatment}}\]
직관: “처치로 인해 절대적으로 몇 %p 위험이 줄었는가?”
1.6.2.5 NNT (Number Needed to Treat, 치료 필요 수)
\[NNT = \frac{1}{ARR}\]
직관: “몇 명을 처치해야 1명의 나쁜 결과를 방지하는가?”
예시:
대조군 이탈률: 40%
처치군 이탈률: 30%
ARR = 10%
NNT = 1/0.10 = 10
→ 10명에게 개인화를 적용해야 1명의 이탈을 방지
→ 비용: 10명 × 개인화 비용 < 1명의 고객 LTV?
1.6.2.6 RRR (Relative Risk Reduction, 상대 위험 감소)
\[RRR = 1 - RR = \frac{P_{\text{control}} - P_{\text{treatment}}}{P_{\text{control}}} = \frac{ARR}{P_{\text{control}}}\]
직관: “위험이 상대적으로 몇 % 줄었는가?”
1.6.3 Python: 전체 지표 계산
import numpy as np
def compute_effect_measures(a, b, c, d):
"""
2x2 표에서 모든 효과 지표 계산.
결과+ 결과-
노출+ a b
노출- c d
"""
p_exposed = a / (a + b)
p_unexposed = c / (c + d)
RR = p_exposed / p_unexposed
OR = (a * d) / (b * c)
ARR = p_unexposed - p_exposed # 보호 효과일 때 양수
RRR = 1 - RR
NNT = 1 / abs(ARR) if ARR != 0 else np.inf
# 신뢰구간
se_ln_RR = np.sqrt(1/a - 1/(a+b) + 1/c - 1/(c+d))
se_ln_OR = np.sqrt(1/a + 1/b + 1/c + 1/d)
results = {
"P(outcome|exposed)": p_exposed,
"P(outcome|unexposed)": p_unexposed,
"RR": RR,
"RR 95% CI": (np.exp(np.log(RR) - 1.96*se_ln_RR),
np.exp(np.log(RR) + 1.96*se_ln_RR)),
"OR": OR,
"OR 95% CI": (np.exp(np.log(OR) - 1.96*se_ln_OR),
np.exp(np.log(OR) + 1.96*se_ln_OR)),
"ARR": ARR,
"RRR": RRR,
"NNT": NNT,
}
return results
# 예시: AI Agent 개인화 효과
# 노출 = 개인화 적용, 결과 = 이탈
a, b = 30, 470 # 개인화 O → 이탈 30, 유지 470
c, d = 50, 450 # 개인화 X → 이탈 50, 유지 450
results = compute_effect_measures(a, b, c, d)
for k, v in results.items():
if isinstance(v, tuple):
print(f"{k}: ({v[0]:.4f}, {v[1]:.4f})")
else:
print(f"{k}: {v:.4f}")1.6.4 R 코드
library(epitools)
library(fmsb)
tab <- matrix(c(a, b, c, d), nrow = 2, byrow = TRUE)
# RR
riskratio(tab, method = "wald")
# OR
oddsratio(tab, method = "wald")
# NNT
NNT(CER = c/(c+d), EER = a/(a+b))1.7 7. 실무 예시: AI Agent 서비스
1.7.1 코호트 분석: 개인화 기능 도입 전후
import pandas as pd
import matplotlib.pyplot as plt
# 코호트 정의
# 2025-06: 개인화 기능 없는 코호트
# 2025-09: 개인화 기능 있는 코호트
# 두 코호트의 6개월 리텐션 비교
cohorts = {
"2025-06 (no personalization)": [1.00, 0.72, 0.58, 0.48, 0.41, 0.36, 0.32],
"2025-09 (personalization)": [1.00, 0.78, 0.67, 0.59, 0.53, 0.48, 0.45],
}
fig, ax = plt.subplots(figsize=(8, 5))
for name, retention in cohorts.items():
ax.plot(range(7), retention, marker="o", label=name)
ax.set_xlabel("Months since signup")
ax.set_ylabel("Retention Rate")
ax.set_title("Cohort Retention: Before vs After Personalization")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
# 주의: 코호트 간 시기 차이 → 계절성, 시장 변화 등 교란 변수 가능
# → DiD로 보완하거나, 매칭으로 조정 필요1.7.2 케이스-컨트롤: 이탈 원인 탐색
# 케이스: 최근 이탈한 사용자 200명
# 컨트롤: 같은 기간 활성 사용자 200명 (가입 시기, 세그먼트 매칭)
# 과거 60일간 행동 패턴 비교
factors = ["error_experienced", "used_help", "completed_onboarding",
"received_notification", "used_premium_feature"]
for factor in factors:
tab = pd.crosstab(df["is_case"], df[factor])
a, b = tab.loc[1, 1], tab.loc[1, 0]
c, d = tab.loc[0, 1], tab.loc[0, 0]
OR = (a * d) / (b * c)
print(f"{factor:30s}: OR = {OR:.2f}")
# 결과 예시:
# error_experienced : OR = 2.45 ← 가장 강한 연관
# used_help : OR = 0.68 ← 보호 요인
# completed_onboarding : OR = 0.52 ← 보호 요인
# received_notification : OR = 1.12 ← 약한 연관
# used_premium_feature : OR = 0.41 ← 강한 보호 요인1.8 8. 요약
| 설계 | 시간 방향 | 핵심 지표 | 인과 강도 | IT 적용 |
|---|---|---|---|---|
| 전향적 코호트 | 노출 → 미래 결과 | RR, IR | 중-상 | 기능 출시 후 추적 |
| 후향적 코호트 | 과거 노출 → 결과 | RR, IR | 중 | 로그 기반 코호트 분석 |
| 케이스-컨트롤 | 결과 → 과거 노출 | OR | 중-하 | 이탈 원인 역추적 |
| 단면 | 동시 측정 | 유병률, PR | 하 | DAU 스냅샷, 설문 |
핵심: 관찰 연구는 실험이 불가능할 때의 “차선의 방법”이 아니라, 가설 생성과 장기 효과 파악에 고유한 가치를 가진다. 다만 교란 변수 통제가 핵심 과제이며, 이를 위한 통계적 기법(매칭, 회귀 조정, 도구 변수)은 35번 인과 추론 프레임워크에서 다룬다.