1 정의
데이터의 일부 값이 비어 있는 (NaN, NULL, 결측) 상태. 분석가가 그 결측을 무시하거나 잘못 처리하면 분석 결과에 체계적 편향 (systematic bias) 이 발생한다 (Buisson, 2021, Ch.6).
흔한 잘못된 처리 방법:
- Listwise deletion (filter): 결측이 있는 행 전체 제거 → 표본 편향
- Mean imputation (replace): 결측을 변수의 평균으로 대체 → 분산 축소, 상관 왜곡
- Last observation carried forward (LOCF): 시계열에서 마지막 값으로 대체 → 인공 평탄화
- 무시: 분석 도구가 자동으로 결측 처리하는 대로 둠 → 통제 못 함
빅데이터 시대 분석가의 자연스러운 직감:
“12,000,000 행 중에서 결측 100만 행을 빼면 11,000,000 행이 남는다. 분석에 충분한 표본 크기. 결측을 신경 쓸 필요 있는가?”
이 직감의 함정:
분석에 사용된 11,000,000 행이 무작위 부분 표본 (random subsample) 이 아니라 체계적으로 편향된 부분 표본 일 가능성이 높다.
구체적 시나리오:
- 자동 결제 등록 안 한 고객은 결제 정보 결측 → 자동 결제 등록한 고객만 분석에 들어감
- 자동 결제 등록은 디지털 친숙도 + 신용 점수 + 나이와 강한 상관 → 분석 표본이 젊고 디지털 친숙한 고객으로 치우침
- 분석 결과 (예: “이메일 캠페인의 효과”) 가 그 부분 인구에만 적용. 전체 고객 적용하면 잘못
→ 결측의 위험은 표본 크기 감소 가 아니라 표본 대표성 상실.
11,000,000 행이라도 그 행들이 인구의 20% 만 대표하면, 1,000 행짜리 무작위 표본보다 더 잘못된 결론을 줌.
분석가의 두 번째 흔한 처리: “결측을 평균으로 대체하자.”
수식:
\[ x_i = \begin{cases} x_i & \text{if observed} \\ \bar{x} & \text{if missing} \end{cases} \]
문제점 1 — 분산 축소:
원본 분산: \(\sigma^2_X\) 대체 후 분산: \(\sigma^2_X \cdot (1 - p_{\text{missing}})\)
대체된 점들이 모두 평균이므로 변동에 기여 안 함 → 분산이 결측 비율만큼 줄어듦.
문제점 2 — 상관 왜곡:
다른 변수와의 상관도 약화. 대체 점들이 변수의 진짜 패턴을 반영 안 함.
문제점 3 — 회귀 계수 편향:
평균 대체된 변수가 종속 변수에 미치는 효과의 추정 계수가 0 으로 끌려감 (attenuation bias).
→ 평균 대체는 “안 한 것처럼 보이게” 만드는 도구이지, 실제로 결측의 편향을 처리하지 않음.
2 Ch.6 의 전체 흐름
Buisson Ch.6 의 결측 데이터 처리 절차:
1. 시각화 (Visualize)
├── md.pattern() 으로 결측 패턴 파악
├── 변수별 결측 비율
└── 변수 간 결측 상관 (한 변수 결측이 다른 변수 결측과 같이 발생?)
2. 진단 (Diagnose)
├── Rubin 의 3 분류: MCAR / MAR / MNAR
├── CD 로 결측 메커니즘 표현
└── 변수별 분류 결정
3. 처리 (Handle)
├── MCAR → Listwise deletion 가능 (편향 없음)
├── MAR → Multiple Imputation
└── MNAR → Sensitivity Analysis 또는 새 데이터 수집
4. 검증 (Validate)
├── 대체 결과의 분포 점검
├── 분석 결과의 robustness
└── 보고서에 가정 명시
각 단계가 다음 단계의 전제. 진단 없이 처리 안 함.
분석가의 흔한 패턴: “결측 발견 → 즉시 mice() 함수 호출 → 결과.”
이 패턴의 문제:
- 결측 메커니즘 (Rubin 분류) 을 모른 채 처리
- mice() 가 가정하는 MAR 조건이 위반된 데이터에 적용 → 잘못된 보정
- 처리 결과를 검증 없이 신뢰
올바른 순서:
- 시각화로 패턴 인식
- 도메인 지식 + 시각화로 메커니즘 가설
- 메커니즘에 맞는 도구 선택
- 처리 후 결과 검증
각 단계에 시간을 들이는 게 결국 빠른 길. 잘못된 처리는 분석 전체를 무효화.
3 AirCnC 데이터 — 사례
3.1 비즈니스 맥락
AirCnC 마케팅 부서가 고객 특성·동기를 이해하기 위해 이메일 설문 발송. 3 개 주 (A, B, C) 의 2000 명 고객 표본.
수집 정보:
| 카테고리 | 변수 | 의미 |
|---|---|---|
| Demographics | Age | 고객 나이 |
| Gender | 성별 (F/M) | |
| State | 거주 주 (A/B/C) | |
| Personality Traits | Openness | 개방성 (0~10) |
| Extraversion | 외향성 (0~10) | |
| Neuroticism | 신경증 (0~10) | |
| Outcome | Bkg_amt | 예약 금액 |
가정 (단순화 위해):
- 모든 demographic 변수와 trait 가 BookingAmount 의 원인
- 변수 간 서로 관계 없음 (현실은 더 복잡, 분석 단순화)
CD:
[Age] [Gender] [State] [Openness] [Extraversion] [Neuroticism]
↓ ↓ ↓ ↓ ↓ ↓
└─────┴────────┴─────────┴─────────────┴────────────────┘
↓
BookingAmount
Buisson 의 미묘한 주의:
“Gender, Extraversion 이 BookingAmount 의 원인이라고 하면 두 의미가 있다:
- 외생 변수 (exogenous): 우리 분석 목적에서는 1 차 원인 (다른 어떤 변수의 결과 아님)
- 다중 매개: 인과 효과가 사회적 현상으로 매개·조절됨”
예: Gender 의 BookingAmount 효과는 직접 인과가 아니라:
Gender → Income → BookingAmount
→ Occupation →
→ FamilyStatus →
→ ...
여러 mediator 를 거침. 그러나 우리 분석에서는 mediator 를 통제 못 하므로 Gender 를 직접 원인으로 다룸.
이게 confounding 은 아님 (Gender 가 다른 변수의 결과 아니므로). “Cause of causes” 라는 표현이 더 정확.
→ 비즈니스 분석에서 demographic 을 다룰 때 이 미묘함을 이해해야. 단순히 “Gender 가 차이를 만듦” 이 아니라 “Gender 가 일으킨 사회적 mediator 들의 종합 효과”.
3.2 데이터 구조
| 파일 | 내용 | 용도 |
|---|---|---|
chap6-complete_data.csv |
결측 없는 ground truth (시뮬레이션이라 가능) | 처리 결과 평가 |
chap6-available_data.csv |
일부 결측 발생한 데이터 | 실제 분석 |
chap6-available_data_supp.csv |
보조 변수 (Insurance, Active) | MAR 처리 보조 |
available_data 의 결측 패턴:
| 변수 | 결측 수 | 비율 |
|---|---|---|
| Age | 0 | 0% |
| Open | 0 | 0% |
| Gender | 0 | 0% |
| Bkg_amt | 175 | 8.75% |
| State | 711 | 35.5% |
| Extra | 793 | 39.7% |
| Neuro | 1,000 | 50% |
| 합계 결측 셀 | 2,679 | 19.1% |
→ Neuroticism 절반이 결측. 단순 listwise deletion 시 80% 표본 손실.
50% 결측은 극단적이지만 비즈니스 데이터에서 실제로 발생:
- 설문 응답 끝까지 안 한 응답자
- 민감한 질문 (성격, 소득) 회피
- 시스템 로깅 실패
- 외부 벤더 데이터의 부분 매칭
이 50% 가 무작위면 listwise deletion 후 표본 크기 절반 → 통계적 검정력 ↓ 하지만 편향 없음. 이 50% 가 체계적이면 (예: 신경증 높은 사람이 설문 회피) listwise deletion 결과는 신경증 낮은 인구만 → 편향.
→ 진단 (Rubin 분류) 이 처리 방법을 결정.
4 Rubin 의 결측 분류
4.1 MCAR — Missing Completely At Random
Missing Completely At Random: 결측 발생이 어떤 변수와도 독립.
수식:
\[ P(\text{Missing}_X = 1 \mid X, \text{other vars}) = P(\text{Missing}_X = 1) \]
결측 확률이 데이터의 어떤 값에도 의존 안 함. 완전 무작위.
CD 표현:
[Random_Noise]
↓
Missing_X (결측 indicator)
↓ (영향 없음)
X
비유: 설문 응답을 랜덤으로 분실하는 시스템.
- 응답자 1000 명
- 시스템이 무작위로 100 명의 응답을 분실 (랜덤 선택, 응답자 특성 무관)
이 경우:
- 분실된 100 명과 남은 900 명의 분포가 통계적으로 동일
- 900 명 분석 결과 = 1000 명 분석 결과 (잡음 작음)
- Listwise deletion 안전
비즈니스 사례:
- 데이터베이스 이전 중 무작위 행 손상
- 측정 장비의 무작위 오작동
- 설문 마지막 페이지를 우연히 건너뜀 (응답자가 신경 안 쓰는 부분)
→ MCAR 은 분석가에게 가장 친절한 결측. 그러나 비즈니스 데이터에서 MCAR 은 드물다.
MCAR 을 가정으로 받아들이는 게 안전한가?
검증:
- Little’s MCAR test (다변량 정규성 가정 하)
- 결측 indicator 를 종속 변수로, 다른 변수를 설명 변수로 회귀 → 모든 계수 0 인지 점검
- 그러나 검증의 power 는 약함
추천: MCAR 을 가정하기보다 데이터 생성 메커니즘을 도메인 지식으로 따져볼 것.
“이 변수의 결측이 정말 무작위인가? 어떤 사람이 응답 안 했을까?”
이 질문에 “정말 모르겠다, 무작위 같다” 답이 안전. 보통 어떤 패턴이 있다.
4.2 MAR — Missing At Random
Missing At Random: 결측 확률이 관측된 다른 변수 에 의존하지만, 결측 변수 자체에는 의존하지 않음.
수식:
\[ P(\text{Missing}_X = 1 \mid X, \text{other vars}) = P(\text{Missing}_X = 1 \mid \text{other observed vars}) \]
결측 확률이 관측 가능한 다른 변수로 설명됨.
CD 표현:
Z (관측됨)
↓
Missing_X
↓ (영향 없음)
X
여기서 Z 가 결측을 일으키는 변수. X 자체는 결측에 영향 안 줌.
이름에 “Random” 이 들어 있어 혼란스럽지만, 실제로는 “다른 관측 변수를 통해 결측 메커니즘 설명 가능” 이라는 뜻.
비유: 설문에서 “운동 빈도” 항목을 65세 이상 응답자가 응답 거부 경향.
- 결측 확률이 Age (관측됨) 에 의존
- 그러나 실제 운동 빈도 (결측 변수 자체) 에는 직접 의존 안 함
- Age 가 통제되면 결측이 무작위처럼 행동
비즈니스 사례:
- 이메일 응답률이 디바이스 (모바일 vs 데스크톱) 에 의존 — 디바이스는 관측됨
- 신용카드 한도 결측이 가입 시기 (관측됨) 에 의존 — 옛 가입자에게는 그 필드가 없었음
- CSAT 응답이 예약 채널 (관측됨) 에 의존 — 모바일 앱 채널은 설문 push 안 함
해결: Multiple Imputation 등 MAR 가정 하 도구 사용.
→ MAR 은 실제 비즈니스 분석에서 가장 흔한 시나리오. 이 가정 하의 도구가 표준.
MAR 가정의 핵심 한계:
“결측 변수 자체에 의존 안 한다” 는 결측된 값을 모르므로 검증 불가.
분석가가 할 수 있는 것:
- 충분한 보조 변수 포함: 결측을 설명할 만한 모든 관측 변수 회귀에
- 민감도 분석: MAR 위반 시 결과가 얼마나 바뀌는지 검토
- 도메인 직관: “이 변수의 결측이 그 변수 자체 값에 의존할 가능성?”
만약 도메인 직관이 “그렇다 (Yes)” 면 MAR 가정 깨짐 → MNAR.
4.3 MNAR — Missing Not At Random
Missing Not At Random: 결측 확률이 결측 변수 자체에 의존.
수식:
\[ P(\text{Missing}_X = 1 \mid X, \text{other vars}) \neq P(\text{Missing}_X = 1 \mid \text{other observed vars}) \]
결측 확률이 X 의 진짜 값에 의존 → X 의 다른 값을 가진 사람들이 다른 비율로 결측.
CD 표현:
X ──→ Missing_X
↓
X 의 결측은 X 의 값에 의존
비유: 설문에서 “월 소득” 항목을 고소득자가 회피.
- 결측 확률이 월 소득 자체 (결측 변수) 에 의존
- 다른 변수를 모두 통제해도 결측 메커니즘 설명 못함
- 결측된 사람들의 진짜 값이 시스템적으로 다름
비즈니스 사례:
- 가격 민감도가 강한 고객이 가격 관련 설문 회피 (가격 민감 자체가 결측 일으킴)
- 우울증 환자가 우울증 척도 설문 회피
- 노화에 민감한 고객이 나이 정보 회피
해결의 어려움:
- MAR 가정의 도구 (mice 등) 가 작동 안 함 → 잘못된 보정
- Sensitivity analysis (가정의 강도 변화) 또는 외부 데이터 수집 필요
- 가정 없이 식별 안 됨
→ MNAR 은 분석가에게 가장 어려운 결측. 보고서에 한계 명시 필수.
4.4 분류의 비교
| MCAR | MAR | MNAR | |
|---|---|---|---|
| 결측 의존성 | 없음 (완전 무작위) | 다른 관측 변수 | 결측 변수 자체 |
| CD | Missing_X 부모 없음 | Z (관측) → Missing_X | X → Missing_X |
| Listwise deletion | OK (편향 없음) | 편향 발생 | 편향 발생 |
| Mean imputation | OK (효율 손실만) | 편향 발생 | 편향 발생 |
| Multiple Imputation | OK | 권장 | 부분 작동 (가정 깸) |
| 검증 | Little’s test | 도메인 검증 | 검증 불가 (외부 데이터 필요) |
| 비즈니스 빈도 | 드뭄 | 가장 흔함 | 흔함 (어려운 케이스) |
→ 분석 절차: 1. 도메인 직관으로 분류 가설 2. 시각화로 패턴 점검 3. 필요시 listwise + multiple imputation 두 결과 비교 (sensitivity)
5 Multiple Imputation 도입
Multiple Imputation (MI): 결측 값을 단일 값으로 대체하지 않고, 결측의 불확실성을 반영해 여러 번 (m 번) 다른 대체 데이터셋 을 생성. 각 데이터셋에 분석 적용 후 결과 종합.
절차:
원본 데이터 (결측 있음)
↓
대체 m 번
↓
대체 1, 대체 2, ..., 대체 m (각각 완전한 데이터셋)
↓ (각각 분석)
분석 1, 분석 2, ..., 분석 m
↓ (Rubin's rules 로 종합)
최종 추정 + 신뢰구간
단일 대체의 문제:
- 결측에 한 값만 채우면 그 값의 불확실성이 분석에 반영 안 됨
- 분산 추정치가 진짜 분산보다 작음 → 신뢰구간이 좁아 보임 → “유의” 결과 가짜
다중 대체의 해결:
- 결측 값에 대한 사후 분포에서 m 번 sample
- 각 sample 의 분석 결과 사이 변동이 결측의 불확실성을 반영
- Rubin’s rules 가 이 변동을 최종 신뢰구간에 통합
비유:
- 단일 대체 = 결측 값을 “정답” 으로 가정 → 자만
- 다중 대체 = 결측 값을 여러 가능성 종합 → 정직
→ MI 가 통계적으로 옳고, 실용적 표준.
m 개 대체 데이터셋의 분석 결과 종합:
추정량 (point estimate):
\[ \hat{\theta} = \frac{1}{m} \sum_{i=1}^{m} \hat{\theta}_i \]
(평균)
분산:
\[ \text{Var}(\hat{\theta}) = \underbrace{\bar{U}}_{\text{within}} + \underbrace{(1 + \frac{1}{m}) \cdot B}_{\text{between}} \]
여기서:
- \(\bar{U} = \frac{1}{m} \sum \text{Var}(\hat{\theta}_i)\) — 각 대체 내 분산 평균
- \(B = \frac{1}{m-1} \sum (\hat{\theta}_i - \hat{\theta})^2\) — 대체 간 분산
→ within (각 대체 내) + between (대체 간) 분산 합. 결측 불확실성이 between 에 반영.
비유: 여론조사를 5 번 다른 표본으로.
- Within: 한 조사의 표본 잡음 (그 조사가 진짜 모수에서 얼마나 떨어졌는지)
- Between: 5 조사 결과 사이 차이 (조사 간 변동)
두 분산을 합쳐야 진짜 불확실성. Within 만 보고 “결과 정확” 하면 잘못.
MI 도 같은 논리:
- Within: 각 대체 데이터셋의 표본 잡음
- Between: 대체 간 차이 (결측 불확실성)
Multiple Imputation 의 핵심 통계 메커니즘.
6 시각화 — md.pattern() 의 해석
6.1 결측 패턴 행렬
출력 예시:
age open gender bkg_amt state extra neuro
368 1 1 1 1 1 1 1 0
358 1 1 1 1 1 1 0 1
249 1 1 1 1 1 0 1 1
228 1 1 1 1 1 0 0 2
163 1 1 1 0 1 1 1 1
214 1 1 1 0 1 1 0 2
125 1 1 1 0 1 0 1 2
120 1 1 1 0 1 0 0 3
33 1 1 1 1 0 1 1 1
23 1 1 1 1 0 1 0 2
15 1 1 1 1 0 0 1 2
15 1 1 1 1 0 0 0 3
24 1 1 1 0 0 1 1 2
24 1 1 1 0 0 1 0 3
23 1 1 1 0 0 0 1 3
18 1 1 1 0 0 0 0 4
0 0 0 175 711 793 1000 2679
해석:
- 첫 행 (368): 모든 변수 관측. 완전 데이터 368 명.
- 둘째 행 (358): Neuro 만 결측. 358 명.
- 셋째 행 (249): Extra 만 결측. 249 명.
- …
- 마지막 행 (18): Bkg_amt, State, Extra, Neuro 모두 결측. 18 명.
- 하단 합계 행: 변수별 결측 수. Neuro 1000 (50%) 가 가장 많음.
이 행렬에서 알 수 있는 것:
결측의 함께 발생 패턴: Extra·Neuro 둘 다 결측인 행이 많음 (228 + 120 + 24 + 18 = 390 명) → 두 trait 의 결측 메커니즘이 비슷할 가능성 (성격 검사 끝까지 안 함).
Bkg_amt 결측 비율: 175 / 2000 = 8.75%. 비교적 작음 → 결제 정보 결측이 한정적.
State 결측: 711 / 2000 = 35.5%. 의외로 큼 → “거주 주” 가 응답하기 부담스러운 정보일 수도, 또는 시스템적 누락 (신규 가입 폼에서 빠진 시기).
완전 데이터 비율: 368 / 2000 = 18.4%. Listwise deletion 시 표본 80% 손실 → 통계적 검정력 큰 손실.
이 패턴 분석이 MAR 가설로 이어짐:
- “Neuro 결측 = trait 검사를 끝까지 안 한 사람” → 응답 행동 자체와 연관 (신경증 높은 사람이 끝까지 안 함 가능성?)
- 그러나 Age, Gender 같은 관측 변수로 부분 설명 가능 → MAR
7 응용 — 다른 비즈니스 사례
7.1 SaaS 사용자 활성도 분석
분석 질문: “프리미엄 기능 사용이 retention 을 높이는가?”
데이터:
- 활성 사용자: 모든 변수 관측
- 비활성 사용자: 활성도 관련 metric 결측
가능한 메커니즘:
| 메커니즘 | 설명 | 분류 |
|---|---|---|
| 시스템 로깅 무작위 누락 | 1% 이벤트 손실 | MCAR |
| 모바일 사용자만 활성도 측정 안 됨 (데스크톱 vs 모바일 = 관측) | 디바이스에 의존 | MAR |
| 비활성 사용자 = 측정 자체가 안 됨 (활성도 자체가 결측 일으킴) | 결측 변수 자체에 의존 | MNAR |
각 분류에 따라 처리 방법 다름. 도메인 인터뷰 + 데이터 점검으로 결정.
7.2 헬스케어 — 환자 추적 손실
임상 시험에서 환자가 추적 기간 중 데이터 제공 중단:
| 메커니즘 | 사례 | 분류 |
|---|---|---|
| 환자가 이사로 연락 끊김 (이사 = 외부 사건, trial 무관) | MCAR/MAR | |
| 부작용 경험 환자가 응답 회피 | MNAR (부작용 자체가 결측 일으킴) | |
| 효과 없다고 느낀 환자가 응답 회피 | MNAR (treatment 효과가 결측 일으킴) |
→ 임상 시험에서 MNAR 가능성 항상 점검. CONSORT guideline 의 “loss to follow-up” 보고 필수.
8 코드 예시 — Python 으로 결측 처리
8.1 데이터 생성 + 시각화
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
np.random.seed(42)
n = 2000
# 완전 데이터 (ground truth)
age = np.random.normal(40, 12, n).clip(18, 80)
gender = np.random.choice(["F", "M"], n)
state = np.random.choice(["A", "B", "C"], n, p=[0.4, 0.35, 0.25])
openness = np.random.normal(5, 2, n).clip(0, 10)
extraversion = np.random.normal(5, 2, n).clip(0, 10)
neuroticism = np.random.normal(5, 2, n).clip(0, 10)
# Bkg_amt 는 모든 변수의 함수
bkg_amt = (
100
+ 5 * age
+ 30 * (gender == "F").astype(int)
+ 20 * (state == "A").astype(int) - 30 * (state == "C").astype(int)
+ 15 * openness
+ 10 * extraversion
- 10 * neuroticism
+ np.random.normal(0, 50, n)
).clip(0, 1000)
complete_df = pd.DataFrame({
"age": age, "gender": gender, "state": state,
"open": openness, "extra": extraversion, "neuro": neuroticism,
"bkg_amt": bkg_amt,
})
print("=== 완전 데이터 ===")
print(complete_df.describe())진짜 비즈니스 데이터는 ground truth 를 모름. 그러나 시뮬레이션에서는:
- 결측 발생 메커니즘을 우리가 설계
- 처리 결과가 진짜 값에 얼마나 가까운지 평가 가능
- 다양한 메커니즘 (MCAR/MAR/MNAR) 별로 도구 효과 비교
이게 시뮬레이션의 가치. 진짜 데이터 분석 전에 도구의 동작을 확인.
8.2 결측 발생 메커니즘 시뮬레이션
# MCAR: Bkg_amt 의 무작위 8.75% 결측
mcar_mask = np.random.uniform(0, 1, n) < 0.0875
# MAR: Neuroticism 결측이 Age 에 의존 (나이 든 사람이 응답 회피)
mar_prob = 1 / (1 + np.exp(-(0.05 * (age - 50)))) # 50세 이상에서 결측 증가
mar_mask = np.random.uniform(0, 1, n) < mar_prob
# MNAR: Extraversion 결측이 Extraversion 값 자체에 의존 (외향성 낮은 사람이 응답 회피)
mnar_prob = 1 / (1 + np.exp(-(-0.5 * (extraversion - 5)))) # 외향성 낮을수록 결측
mnar_mask = np.random.uniform(0, 1, n) < mnar_prob
# 결측 적용
available_df = complete_df.copy()
available_df.loc[mcar_mask, "bkg_amt"] = np.nan
available_df.loc[mar_mask, "neuro"] = np.nan
available_df.loc[mnar_mask, "extra"] = np.nan
# State 도 일부 결측 (가입 시기 시스템 변경 가정)
state_missing_mask = np.random.uniform(0, 1, n) < 0.355
available_df.loc[state_missing_mask, "state"] = np.nan
print("\n=== 결측 비율 ===")
print(available_df.isnull().mean() * 100)세 결측이 다른 메커니즘:
- bkg_amt (MCAR): 8.75% — 무작위. Listwise deletion 가능.
- neuro (MAR): ~25-50% — Age 의존. 다른 변수 통제 후 무작위처럼 작동.
- extra (MNAR): ~30-40% — Extra 자체 의존. 가장 어려움.
- state (MCAR-ish): 35.5% — 시스템 결측, 무작위.
이 시뮬레이션이 Buisson 의 데이터 패턴을 닮음.
8.3 md.pattern 시각화 (Python 버전)
def md_pattern(df):
"""결측 패턴 행렬 (mice::md.pattern Python 버전)."""
miss_cols = [c for c in df.columns if df[c].isnull().any()]
if not miss_cols:
print("결측 없음")
return
# 0/1 indicator
indicators = df[miss_cols].notna().astype(int)
pattern_count = indicators.value_counts().reset_index()
pattern_count.columns = miss_cols + ["count"]
pattern_count["n_missing"] = (1 - pattern_count[miss_cols]).sum(axis=1)
# 변수별 결측 수
var_missing = df[miss_cols].isnull().sum()
print("=== 결측 패턴 ===")
print(pattern_count.sort_values("count", ascending=False).to_string(index=False))
print("\n=== 변수별 결측 수 ===")
print(var_missing)
md_pattern(available_df)이 함수의 출력으로 분석가가 보는 것:
- 가장 흔한 결측 조합 (전체 관측? 한 변수만 결측? 여러 변수 결측?)
- 결측이 함께 발생하는 변수 (한 변수 결측이면 다른 변수도 결측 경향?)
- 변수별 결측 수 순위
이 정보를 기반으로 다음 단계 (진단) 의 가설 도출.
8.4 Listwise Deletion 의 편향 시연
# 가설: Bkg_amt ~ Age + Extra (단순화)
import statsmodels.api as sm
# 진실 (완전 데이터)
X_true = sm.add_constant(complete_df[["age", "extra"]])
m_true = sm.OLS(complete_df["bkg_amt"], X_true).fit()
print("=== 진실 (완전 데이터) ===")
print(f" beta_age = {m_true.params['age']:.3f}")
print(f" beta_extra = {m_true.params['extra']:.3f}")
# Listwise deletion (모든 결측 제거)
listwise_df = available_df.dropna()
print(f"\nListwise deletion 후 표본: {len(listwise_df)} (원본 {n})")
X_lw = sm.add_constant(listwise_df[["age", "extra"]])
m_lw = sm.OLS(listwise_df["bkg_amt"], X_lw).fit()
print("=== Listwise Deletion ===")
print(f" beta_age = {m_lw.params['age']:.3f}")
print(f" beta_extra = {m_lw.params['extra']:.3f}")예상 결과:
- 진실: \(\beta_{age} \approx 5\), \(\beta_{extra} \approx 10\)
- Listwise: \(\beta_{age} \approx 4.5\) (편향), \(\beta_{extra} \approx 11\) (편향)
표본이 80% 손실되고 (n = 2000 → ~370), 추정이 편향됨.
이유: Extra 가 MNAR 결측이므로, 살아남은 표본은 외향성 높은 사람 비율 ↑. 그 표본에서 Extra 의 효과 추정이 진짜와 다름.
→ 표본 크기 손실 + 편향. 두 가지 비용 모두.
8.5 Mean Imputation 의 분산 축소
# Mean imputation
mean_df = available_df.copy()
for col in ["bkg_amt", "neuro", "extra"]:
mean_df[col] = mean_df[col].fillna(mean_df[col].mean())
# State 는 mode imputation
mean_df["state"] = mean_df["state"].fillna(mean_df["state"].mode()[0])
# Extra 의 분산 비교
print("\n=== Mean Imputation 의 분산 효과 ===")
print(f"진짜 Extra 분산: {complete_df['extra'].var():.3f}")
print(f"Listwise Extra 분산: {listwise_df['extra'].var():.3f}")
print(f"Mean imputed Extra 분산: {mean_df['extra'].var():.3f}")예상:
- 진짜: ~4.0
- Listwise: ~4.0 (분산 거의 같음, 표본만 줄어듦)
- Mean imputed: ~2.5 (분산 줄어듦)
Mean imputation 이 분산을 약 30~40% 줄임 (결측 비율에 따라).
이 분산 축소가 회귀 계수 추정에 영향:
- Extra 의 분산이 작으면 → Extra 의 회귀 계수 추정의 표준 오차가 작음
- 그러나 이건 가짜 정밀도 — 진짜 분산은 더 큼
- 신뢰구간이 좁아 보임 → “유의” 결과의 false positive ↑
→ Mean imputation 은 절대 사용 금지. 분석가가 자만하게 만드는 도구.
8.6 Multiple Imputation 도입 (statsmodels.imputation.mice)
from statsmodels.imputation import mice
# Mice 객체 생성
imp = mice.MICEData(available_df[["age", "gender", "state", "open", "extra", "neuro", "bkg_amt"]])
# 여러 imputation 수행
m = 5
imputed_dfs = []
for i in range(m):
imp.update_all()
imputed_dfs.append(imp.data.copy())
print(f"\n{m} 개 대체 데이터셋 생성 완료")
print(f"\n첫 대체의 head:")
print(imputed_dfs[0].head())5 개 대체 데이터셋이 생성됨. 각 데이터셋의 결측은 다른 합리적 값으로 채워짐.
각 데이터셋에서:
- Age, Open, Gender 는 그대로 (결측 없음)
- Bkg_amt, State, Extra, Neuro 는 채워짐 (각 데이터셋마다 다른 값 가능)
다음 단계: 각 데이터셋에 회귀 적합 → Rubin’s rules 로 종합. (자세히는 E-BUI6-3 에서)
→ MI 의 마법: 결측의 불확실성을 m 개 대체 사이 변동으로 표현.
8.7 Bootstrap 으로 MI 결과 종합 (단순화)
# 각 대체 데이터셋의 회귀 결과
results = []
for i, df_imp in enumerate(imputed_dfs):
X = sm.add_constant(df_imp[["age", "extra", "neuro"]])
m = sm.OLS(df_imp["bkg_amt"], X).fit()
results.append({
"imputation": i,
"beta_age": m.params["age"],
"beta_extra": m.params["extra"],
"beta_neuro": m.params["neuro"],
"se_age": m.bse["age"],
"se_extra": m.bse["extra"],
"se_neuro": m.bse["neuro"],
})
results_df = pd.DataFrame(results)
print("\n=== 5 개 대체 결과 ===")
print(results_df)
# Rubin's rules 종합
def rubin_pooling(point_estimates, std_errors):
m = len(point_estimates)
theta_hat = np.mean(point_estimates)
U_bar = np.mean(np.array(std_errors) ** 2)
B = np.var(point_estimates, ddof=1)
total_var = U_bar + (1 + 1/m) * B
return theta_hat, np.sqrt(total_var)
print("\n=== Rubin's Pooled Results ===")
for var in ["age", "extra", "neuro"]:
point, se = rubin_pooling(
results_df[f"beta_{var}"].values,
results_df[f"se_{var}"].values,
)
print(f" beta_{var} = {point:.3f} (SE = {se:.3f})")예상: MI 의 추정량이 진짜 (완전 데이터) 에 가깝고, listwise/mean 보다 정확.
표준 오차도 within + between 분산 합으로 계산되어 결측의 불확실성 반영.
비교 (예상치):
| 방법 | \(\beta_{age}\) | SE | 진실과 차이 |
|---|---|---|---|
| 완전 데이터 | 5.00 | 0.25 | 0 |
| Listwise | 4.50 | 0.45 | 0.50 |
| Mean impute | 4.20 | 0.20 (가짜) | 0.80 |
| MI (5) | 4.95 | 0.30 | 0.05 |
MI 가 진실에 가장 가깝고, SE 도 적절히 보수적.
→ MI 가 표준 도구인 이유. 추가 비용 (m 번 분석) 대비 효과가 크다.
9 관련 주제
9.1 Ch.6 의 형제 글
- E-BUI6-1 결측 시각화 (mice md.pattern, 결측 상관) — 단계 1 자세히
- E-BUI6-2 결측 분류와 진단 (MCAR/MAR/MNAR) — 단계 2 자세히
- E-BUI6-3 다중 대체 (PMM, 정규) — 단계 3 자세히
- E-BUI6-4 보조 변수와 대체 수 결정 — 단계 4 + 보조 변수
9.2 이전 챕터
- E-BUI5-2 백도어 기준과 M-패턴 — Ch.5: Confounding
- E-BUI4-3 데이터 검증과 반복 정제 — Ch.4: CD 짓기
9.3 후속 챕터
- E-BUI8-0 Theory of Change 실험 설계 overview — Ch.8: 실험 설계
- Phase A-BUI7 — Bootstrap (자세히)
9.4 Statistics 카테고리 cross-link
- Statistics/missing-data — 통계학적 결측 데이터 처리
9.5 Hernan 정통 cross-link
- Causal_Inference/08 선택 편향 — Selection bias 와 결측
9.6 카테고리 진입점
- Experimentation 학습 로드맵 — 11 Phase × 7 교재 매핑