1 정의
CD 에 변수를 추가할 때 분석가가 결정해야 할 두 가지 (Buisson, 2021, Ch.4):
- 인과 역할 (causal role):
- Confounder (공통 원인) — Treatment 와 Outcome 양쪽으로 화살표
- Mediator (매개) — Treatment 의 영향을 Outcome 으로 전달
- Effect Modifier (효과 수정) — Treatment 효과의 크기를 변경
- Direct Cause of Outcome only — Outcome 에만 화살표 (Treatment 무관)
- 시점 (temporal precedence): Treatment 결정 전·후 인지
이 두 결정으로 변수의 CD 내 위치와 회귀 처리 방식이 정해진다.
PreviousCancellation 이라는 변수가:
- 호텔의 NRD 정책 분석에서 → Confounder (NRD 와 Cancel 양쪽 영향)
- 고객 충성도 분석에서 → Outcome (충성 고객이 안 취소함)
- 가격 인상 효과 분석에서 → Effect Modifier (과거 취소자가 가격 인상에 더 민감)
→ 변수의 본질은 분석 질문에 따라 바뀜. 변수마다 “고정 역할” 이 있는 게 아님.
분석 질문이 결정되어야 변수의 역할이 정해짐. 이게 Starter CD 가 분석의 출발점인 이유.
2 Personal Characteristics — Traits 와 Demographics 의 분리
2.1 Demographics 의 함정
분석가의 자연스러운 직감: “Age, Gender, Country 같은 demographic 변수를 넣자.”
함정:
- Demographic 변수 자체는 미관측 personal trait (성격, 가치관, 습관) 의 proxy 일 뿐
- Demographic 만 넣으면 진짜 인과 변수의 부분 통제만 됨
- 분석가가 “demographic 통제했음” 이라고 착각하지만 잔여 confounding 큼
Buisson 의 권장: Trait 부터 brainstorm, 그 다음 그 trait 의 proxy 인 demographic 을 매핑.
호텔 사례:
[Personality Trait] [Demographic proxy]
- Conscientiousness (성실성) ← Age, IsRepeatedGuest
- Neuroticism (신경증) ← (proxy 어려움)
- Risk Tolerance ← CustomerType, ADR
- Time Pressure ← TripReason, Quarter
이 순서가 분석가의 사고를 깊게 만든다. 단순히 “Age 가 confounder 일 것” 보다, “성실한 사람이 NRD 를 더 많이 받고 덜 취소할 것이다 → 성실성의 proxy 로 IsRepeatedGuest 사용” 이 더 정확.
2.2 호텔 사례의 Trait 후보
심리학의 Big Five (OCEAN):
- Openness (개방성)
- Conscientiousness (성실성)
- Extraversion (외향성)
- Agreeableness (친화성)
- Neuroticism (신경증)
호텔 취소 분석에 관련된 trait:
| Trait | NRD 부과 | Cancel 가능성 | 가설 |
|---|---|---|---|
| Conscientiousness ↑ | ↓ | ↓ | 성실한 사람은 약속을 지킴 → 호텔이 NRD 면제, 본인도 안 취소 |
| Neuroticism ↑ | (관계 없음) | ↑ | 불안한 사람은 일정 변경 자주 → 취소율 ↑ |
| Risk Tolerance ↑ | (관계 없음) | ↓ | 위험 감수형은 NRD 를 sunk cost 로 받아들이고 가서 묵음 |
| Openness ↑ | (관계 없음) | ↑ | 새 경험 추구 → 다른 호텔로 옮길 가능성 |
이 trait 들은 호텔 데이터에 없음. 그러나 CD 에 그려둠:
[Conscientiousness] [Neuroticism]
↓ ↓ ↓
NRDeposit ──→ IsCanceled
(미관측은 음영 또는 대괄호 [ ] 로 표기)
미관측을 그리는 이유:
- 분석가가 잔여 confounding 을 인식
- 보고서에 명시 (“이 미관측 변수는 통제 안 됨”)
- 추후 데이터 수집 우선순위 (성격 검사 추가?)
- Proxy 식별 (어떤 관측 변수가 이 trait 를 부분 노출?)
2.3 Demographic 변수의 Proxy 역할
| Trait (미관측) | Demographic Proxy (관측) | 강도 |
|---|---|---|
| Conscientiousness | IsRepeatedGuest, PreviousCancellation | 중간 |
| Risk Tolerance | ADR (가격 수용도), CustomerType (Group vs Individual) | 약함 |
| Time Pressure | TripReason ↔︎ MarketSegment | 중간 |
| 가족 책임감 | Children | 강함 (가족 동반 = 책임감) |
| Income / 가격 민감도 | ADR, Country | 중간 |
→ Demographic 을 회귀에 포함하면 trait 의 부분 통제. 완벽 안 함.
Buisson 의 표현: “Financial characteristics” 라는 묶음 (CustomerType + MarketSegment + DistributionChannel + Children + ADR + Country) 으로 통합 가능.
Demographic 을 한 묶음 (“Financial Characteristics”) 으로 aggregate 하면 CD 단순화. 그러나:
- 회귀에서는 분리해서 사용 (각 변수의 효과 추정)
- 한 묶음으로 합치면 정보 손실
→ CD 는 통합으로 가독성, 회귀는 분리로 정밀도. 두 표현을 함께 사용. (E-BUI3-2 의 Aggregating Variables 참조.)
2.4 호텔 사례의 갱신된 CD
[TripReason] [CancellationReason] [UnderstandsNRD] [TreatsNRDasSunkCost]
[Conscientiousness] [Neuroticism]
↓ ↓ ↓
│ │ │
↓ ↓ ↓
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ PreviousCancellation, IsRepeatedGuest │
│ CustomerType, MarketSegment, DistributionChannel │
│ Children, ADR, Country │
│ ↓ │
│ Financial Characteristics (묶음) │
│ ↓ │
│ NRDeposit ──→ IsCanceled │
│ ↗ │
└──────────────────────────────────────────────────────────────────────┘
이 CD 가 단계 1 (후보 식별) 의 중간 산출물.
3 Business Behaviors — 비즈니스 룰의 표현
3.1 Business Rules 의 인과적 의미
Business Rule: 회사가 정한 결정 규칙. 일반적으로 변수 → 변수 형태.
호텔 예시:
- “Country 가 X 면 NRD 부과” — Country → NRD 직접 화살표
- “Christmas 휴가철 예약은 NRD 부과” — ChristmasHolidays → NRD
- “PreviousCancellation = 1 이면 NRD 부과” — PrevCancel → NRD
이 룰들은 결정론적 화살표. 통계적 연관이 아닌 규칙에 의한 강제.
분석가의 흔한 실수: “PreviousCancellation 의 NRD 효과를 통계로 추정.” → 의미 없음. 이게 회사 정책이면 추정 결과는 정책의 100% 반영일 뿐, 인과 효과 추정 아님.
대신 권장:
- 비즈니스 파트너에게 모든 NRD 부과 룰 인터뷰
- 룰에 명시된 변수는 NRD 의 결정론적 원인 으로 표기 (인과 효과 추정 불필요)
- 룰 외 변수들의 NRD 효과를 통계로 추정
→ Business Rule 인터뷰가 분석의 시간 절약. 안 해보면 통계로 룰을 재발견하느라 시간 낭비.
3.2 Business Rule 의 두 표현 방식
룰: “PreviousCancellation = 1 이면 NRD = 1”
PreviousCancellation ──→ NRDeposit
직접 화살표. 다른 변수의 영향 없이 단일 룰.
룰: “Christmas 휴가 예약 (Q4 + 어린이 동반 + 7일 이상) 은 NRD 부과”
여러 변수의 조합. CD 표현:
Quarter, Children, StayLength
↓
ChristmasHolidayFlag (생성된 매개 변수)
↓
NRDeposit
새 변수 ChristmasHolidayFlag 를 생성. 데이터에 없어도 룰에서 도출 가능.
데이터에는 Quarter, Children 같은 raw 변수만 있을 수 있다. 그러나 비즈니스 룰을 알면:
- Raw 변수의 조합이 의미 있는 단위 (
ChristmasHoliday) 로 묶임 - 회귀에 raw 변수 대신 묶음 변수 사용 → 모형 단순
- 비즈니스 의사결정 (예: “휴가철 NRD 정책 변경”) 에 직접 매핑
→ Feature Engineering 의 인과추론 버전. 도메인 지식이 변수 생성을 가이드.
3.3 Business Rule 이 미관측 변수를 노출
“어떤 기준이 비즈니스 룰의 일부면, 그 기준은 정의상 관측 가능하다 (그렇지 않으면 룰을 어떻게 적용하겠는가).”
— Buisson
따라서:
- 룰 인터뷰가 숨겨진 관측 변수 를 노출시킴
- 데이터베이스에 컬럼이 없어도, 직원의 결정 절차에 있으면 캐낼 수 있음
- 분석가의 데이터 보강 우선순위 결정
호텔 사례: “직원이 신용카드 한도를 보고 NRD 부과 결정” → 카드 한도 데이터를 DB 에 추가하면 강력한 confounder 통제 가능.
4 Time Trends — 추세, 주기, 사건
4.1 세 종류의 시간 효과
- 추세 (Trend): 시간 따라 단조 증가/감소
- 예: NRD 정책 점진 강화, 취소율 점진 증가
- 변수: Year (연속)
- 주기 (Cycle): 정기적으로 반복
- 예: 계절성 (여름 휴가철), 요일 효과 (주말)
- 변수: Quarter, Month, DayOfWeek
- 일회성 사건 (Event): 특정 시점의 충격
- 예: COVID-19 (2020 Q1), 정책 변경 (2019 Q3)
- 변수: 이진 dummy (Pre/Post Covid)
각각 다른 표현 방법.
분석가가 시간을 단순 통제 변수로 넣으면 끝나지 않는다. 시간 효과의 본질을 이해해야:
- 추세: 무엇이 추세를 만드는가? — 정책 변경? 시장 경쟁? 인구통계 변화?
- 주기: 어느 계절이 가장 영향이 큰가? — 운영 계획에 활용
- 사건: 사건의 시작·끝은? — 데이터를 사건 전·후로 분리해야 할 수도
→ 단순 “Year, Quarter dummy 추가” 가 아니라 시간 효과의 분해. 더 풍부한 통찰.
4.2 호텔 사례의 시간 변수
Year (추세) ──→ NRDeposit, IsCanceled
Quarter (주기) ──→ NRDeposit, IsCanceled
[ChristmasHolidays] ──→ NRDeposit (룰)
[Covid_Period] ──→ IsCanceled (예외 사건)
세 종류 모두 CD 에 표기.
만약 데이터가 2017~2022 년이면:
- Year: 추세 변수 (선형 또는 dummy)
- Quarter: 계절성
- Covid_Period: 2020 Q1 ~ 2021 Q4 dummy
4.3 시간 변수의 인과 방향
“호텔이 시간을 통제할 수 있는가?” — 명백히 No.
→ 시간 변수는 다른 변수의 원인 일 수밖에 없음. 결과 아님.
Year ──→ {NRDeposit, IsCanceled, ADR, IsRepeatedGuest, ...}
이 자명함이 분석의 단순화에 도움. 시간 변수의 시점 결정에 고민 없음.
5 검증 절차 — 단계 2 의 통계 도구
5.1 수치 변수 — Pearson 상관
Buisson 의 휴리스틱:
“Treatment ↔︎ Outcome 의 상관이 0.16 이면, 어떤 변수가 그것들과 0.1 이상 상관을 가지면 포함.”
이 임계값이 상대적. Treatment-Outcome 상관이 큰 분석에서는 임계값도 큼, 작은 분석에서는 작음.
이유: 작은 상관 (0.05 정도) 의 변수가 많으면 중요한 변수의 효과가 가려짐. 큰 상관과 같은 자릿수만 포함하면 분석이 깔끔.
비과학적 임계값임. 변수가 적으면 0.05 도 포함, 많으면 0.2 이상만 포함.
이론적: A가 confounder 라도 marginal 상관이 작을 수 있음. 다른 변수와 결합 시 강한 영향.
따라서: 상관 행렬은 첫 필터. 강한 도메인 직관이 있으면 작은 상관의 변수도 포함.
→ 통계 + 직관의 조합. 통계만으로 결정 안 함.
5.2 범주 변수 — Cramer’s V
수치 변수의 Pearson 상관에 대응하는 범주 변수의 연관 척도.
\[ V = \sqrt{\frac{\chi^2}{n \cdot \min(r-1, k-1)}} \]
- \(\chi^2\): 카이 제곱 통계량 (분할표에서)
- \(n\): 표본 크기
- \(r, k\): 분할표의 행·열 수
해석: \(V \in [0, 1]\). 0 = 독립, 1 = 완전 연관.
| V | 강도 | 회귀 포함? |
|---|---|---|
| 0 ~ 0.1 | 약함 | 보통 제외 |
| 0.1 ~ 0.3 | 중간 | 도메인 직관 따라 결정 |
| 0.3+ | 강함 | 거의 확실히 포함 |
호텔 사례 (Buisson 데이터):
- NRDeposit ↔︎ IsCanceled: 0.165 (중간) — 분석 대상
- CustomerType ↔︎ MarketSegment: > 0.5 (강함) — 다중공선성 위험
- Quarter ↔︎ 다른 변수: 모두 < 0.1 — 제거 후보 (계절성 부재)
5.3 다중공선성 — VIF
VIF (Variance Inflation Factor): 한 변수가 다른 변수들로부터 얼마나 예측 가능한지의 척도.
\[ \text{VIF}_j = \frac{1}{1 - R_j^2} \]
여기서 \(R_j^2\) 는 변수 \(j\) 를 다른 모든 변수로 회귀한 결정계수.
해석:
- VIF = 1: 다른 변수와 무관
- VIF = 5: 다른 변수의 5 배 잡음으로 추정
- VIF > 10: 심각한 다중공선성 — 변수 선별 필요
CustomerType, MarketSegment, DistributionChannel 이 강하게 상관 (Cramer’s V > 0.5):
회귀 결과:
- 세 변수 모두 포함 → 각 계수 추정의 표준오차 폭증, 신뢰구간 매우 넓음
- “어느 변수가 진짜 confounder 인지 결정 불가”
해결:
- 선별: 하나만 포함 (가장 의미 있는 것)
- 합성: 새 변수 생성 (예: PCA 의 첫 주성분)
- 계층화: 한 변수로 stratify, 다른 변수는 stratum 내 회귀
→ 다중공선성을 무시하면 분석이 통계적으로는 돌아가지만 결과가 무의미.
5.4 검증 후 갱신된 CD
Customer Type, IsRepeatedGuest, Year
↓ ↓ ↓
┌────────────────────────────────────┐
│ │
│ Children PrevCancel │
│ ↓ ↓ │
│ Market Segment ADR │
│ Distribution Ch Country │
│ ↓ ↓ │
│ └→ NRDeposit ──→ IsCanceled │
│ │
│ (Quarter 제거 — 작은 V 로 인해) │
└────────────────────────────────────┘
검증 결과:
- 포함: PreviousCancellation, ADR, Children, IsRepeatedGuest, Year (Pearson > 0.1 또는 Cramer’s V > 0.1)
- 제거: Quarter (모든 변수와 V < 0.1, 계절성 영향 없음)
- 선별 필요: CustomerType, MarketSegment, DistributionChannel 중 하나 (다중공선성)
6 응용 — 다른 비즈니스 사례의 6 카테고리
6.1 SaaS 사례 — Onboarding 효과
분석 질문: “Onboarding 튜토리얼이 30일 retention 을 높이는가?”
| 카테고리 | 후보 변수 | 역할 가설 |
|---|---|---|
| Past Actions | 이전 SaaS 사용 횟수 | Confounder (경험자가 튜토리얼 완료 ↑, retention ↑) |
| Intentions | 사용 목적 (장기 vs 단기) | Confounder (장기 사용자가 튜토리얼 완료 + retention ↑) |
| Cognition | 기술 자신감 | Mediator (튜토리얼 → 자신감 → retention) |
| Personal | 직무 (Engineer vs Marketing) | Confounder (Engineer 가 튜토리얼 완료 ↑) |
| Business | 가입 경로 (광고 vs 추천) | Confounder (추천이 의도 ↑) |
| Time Trends | 가입 코호트 (월별) | Confounder (시기별 사용자 다름) |
6.2 이커머스 사례 — 추천 효과
분석 질문: “추천이 구매 전환을 높이는가?”
| 카테고리 | 후보 변수 | 역할 가설 |
|---|---|---|
| Past Actions | 과거 구매 빈도 | Confounder (자주 사는 사람이 추천도 더 받고 더 사고) |
| Intentions | 검색 의도 (browsing vs buy) | Confounder (의도 강할수록 추천도 받고 사고) |
| Cognition | 가격 비교 인지 | Effect Modifier (가격 비교한 사람은 추천 효과 ↓) |
| Personal | 인구통계 | Confounder (proxy for 선호) |
| Business | 디바이스, 시간대 | Confounder (모바일 / 데스크톱 다름) |
| Time Trends | 계절 (Black Friday) | Confounder + Effect Modifier |
7 코드 예시 — Python 으로 변수 검증
7.1 Pearson 상관 + Cramer’s V 통합
import numpy as np
import pandas as pd
from scipy.stats import chi2_contingency
def cramers_v(x, y):
"""범주 변수의 연관 강도."""
confusion = pd.crosstab(x, y)
chi2, _, _, _ = chi2_contingency(confusion)
n = confusion.values.sum()
r, k = confusion.shape
return np.sqrt(chi2 / (n * (min(r, k) - 1)))
def association_matrix(df, target_col):
"""
target 과 다른 변수들의 연관 점수.
수치-수치: Pearson, 범주-수치: 점bisearial (Pearson 으로 근사),
범주-범주: Cramer's V.
"""
results = []
for col in df.columns:
if col == target_col:
continue
if pd.api.types.is_numeric_dtype(df[col]) and pd.api.types.is_numeric_dtype(df[target_col]):
score = df[col].corr(df[target_col])
method = "Pearson"
else:
score = cramers_v(df[col], df[target_col])
method = "Cramer's V"
results.append({"variable": col, "score": score, "method": method})
return pd.DataFrame(results).sort_values("score", key=abs, ascending=False)
# 가상 호텔 데이터
np.random.seed(42)
n = 5000
df_hotel = pd.DataFrame({
"NRDeposit": np.random.binomial(1, 0.05, n),
"IsCanceled": np.random.binomial(1, 0.3, n),
"PrevCancel": np.random.binomial(1, 0.1, n),
"RepeatedGuest": np.random.binomial(1, 0.2, n),
"Children": np.random.poisson(0.3, n),
"ADR": np.random.lognormal(4.5, 0.5, n),
"CustomerType": np.random.choice(["Transient", "Group", "Contract"], n),
"MarketSegment": np.random.choice(["Direct", "OnlineTA", "Group"], n),
"Quarter": np.random.choice([1, 2, 3, 4], n),
})
# 합성된 confounding (PrevCancel 이 두 변수에 영향)
df_hotel["NRDeposit"] = (
df_hotel["PrevCancel"] * 0.5
+ np.random.binomial(1, 0.05, n)
).clip(0, 1).astype(int)
df_hotel["IsCanceled"] = (
df_hotel["PrevCancel"] * 0.3
+ np.random.binomial(1, 0.3, n)
).clip(0, 1).astype(int)
# 연관 점수
print("=== NRDeposit 과 다른 변수 연관 ===")
result_nrd = association_matrix(df_hotel, "NRDeposit")
print(result_nrd.to_string(index=False))
print("\n=== IsCanceled 과 다른 변수 연관 ===")
result_cancel = association_matrix(df_hotel, "IsCanceled")
print(result_cancel.to_string(index=False))이 함수는 수치-수치, 수치-범주, 범주-범주 모두 처리. 분석가가 변수 유형마다 다른 도구를 신경 쓸 필요 없음.
→ 후보 변수 100 개여도 한 번에 점수 매김. 임계값 (V > 0.1) 으로 자동 필터.
이 자동화는 첫 필터 일 뿐. 결과를 본 후 도메인 직관으로 보강.
7.2 VIF 진단
from statsmodels.stats.outliers_influence import variance_inflation_factor
def calculate_vif(df, columns):
"""수치 변수의 VIF 계산."""
X = df[columns].values
vif_data = pd.DataFrame()
vif_data["Variable"] = columns
vif_data["VIF"] = [
variance_inflation_factor(X, i) for i in range(len(columns))
]
return vif_data.sort_values("VIF", ascending=False)
# 수치형 변수만 추려 VIF
numeric_cols = ["PrevCancel", "RepeatedGuest", "Children", "ADR"]
vif_result = calculate_vif(df_hotel, numeric_cols)
print("\n=== VIF 진단 ===")
print(vif_result.to_string(index=False))
print("\n해석:")
print(" VIF < 5: OK")
print(" VIF 5~10: 주의")
print(" VIF > 10: 변수 선별 필요")VIF > 10 인 변수가 있으면:
- 가장 VIF 높은 변수 제거 후 재계산
- 남은 변수의 VIF 가 모두 < 5 가 되도록 반복
- 제거된 변수의 정보가 다른 변수에 흡수됨
호텔 사례에서 CustomerType + MarketSegment 둘 다 포함 시 VIF > 10 예상. 하나 제거 → 분석 안정.
7.3 시간 변수 분해
# 추세 + 주기 + 사건 분해
np.random.seed(42)
T = 60 # 60 개월
base_trend = 50 + 0.5 * np.arange(T) # 선형 추세
seasonality = 10 * np.sin(2 * np.pi * np.arange(T) / 12) # 12 개월 주기
covid_dummy = ((np.arange(T) >= 28) & (np.arange(T) < 40)).astype(int)
covid_effect = -30 * covid_dummy # COVID 충격
reservations = base_trend + seasonality + covid_effect + np.random.normal(0, 5, T)
# 회귀로 분해
df_time = pd.DataFrame({
"month_idx": np.arange(T),
"month_in_year": np.arange(T) % 12,
"covid": covid_dummy,
"reservations": reservations,
})
import statsmodels.api as sm
X = sm.add_constant(df_time[["month_idx", "covid"]])
# 계절 dummy
month_dummies = pd.get_dummies(df_time["month_in_year"], prefix="m", drop_first=True).astype(int)
X = pd.concat([X, month_dummies], axis=1)
m = sm.OLS(df_time["reservations"], X).fit()
print("\n=== 시간 분해 회귀 ===")
print(f" 추세 (month_idx): {m.params['month_idx']:.3f}")
print(f" COVID 충격: {m.params['covid']:.2f}")
print(f" R² = {m.rsquared:.3f}")추정 결과:
- 추세: 0.5 (월별 0.5 건 증가) — 시장 성장
- COVID 충격: -30 (3년치 성장 후퇴)
- R² > 0.9 (시간만으로 대부분 설명)
→ 분석에서 시간 효과를 분리하면 “진짜 NRD 효과” 만 추출 가능. 시간 통제 안 한 분석은 시간 트렌드를 NRD 효과로 오인.
8 관련 주제
8.1 Ch.4 의 형제 글
- E-BUI4-0 DAG 0부터 짓기 overview — 4 단계 레시피 전체
- E-BUI4-1 비즈니스 문제와 후보 변수 식별 — 단계 1
- E-BUI4-3 데이터 검증과 반복 정제 — 단계 3~4
8.2 이전 챕터
- E-BUI3-2 체인과 포크 구조 — Mediator·Confounder 구분
- E-BUI3-3 충돌 변수와 경로 — Collider 식별
8.3 후속 챕터
- E-BUI5-0 Deconfounding overview — Ch.5: confounder 통제 형식 절차
8.4 Hernan 정통 cross-link
- Causal_Inference/07 교란 — Confounder 의 학술적 처리
- Causal_Inference/05 효과 수정 — Effect Modifier 처리
8.5 카테고리 진입점
- Experimentation 학습 로드맵 — 11 Phase × 7 교재 매핑