결측 데이터 처리 개관 — 빅데이터 시대의 함정과 Rubin 분류 (Buisson Ch.6 overview)

Filter·Replace 의 편향, MCAR·MAR·MNAR 의 의미, Multiple Imputation 의 도입

Buisson (2021) Ch.6 의 전체 흐름을 압축한 overview. 빅데이터 시대에 결측 데이터를 단순 filter 하면 발생하는 편향, Rubin 의 결측 분류 (MCAR/MAR/MNAR) 의 정의와 직관, Multiple Imputation 의 도입을 정리한다. AirCnC 가상 데이터 (2000 명, 7 변수) 로 단계별 시연.

Experimentation
Causal Inference
저자

Kwangmin Kim

공개

2026년 05월 08일

1 정의

정의: Missing Data 와 그 위험

데이터의 일부 값이 비어 있는 (NaN, NULL, 결측) 상태. 분석가가 그 결측을 무시하거나 잘못 처리하면 분석 결과에 체계적 편향 (systematic bias) 이 발생한다 (Buisson, 2021, Ch.6).

흔한 잘못된 처리 방법:

  • Listwise deletion (filter): 결측이 있는 행 전체 제거 → 표본 편향
  • Mean imputation (replace): 결측을 변수의 평균으로 대체 → 분산 축소, 상관 왜곡
  • Last observation carried forward (LOCF): 시계열에서 마지막 값으로 대체 → 인공 평탄화
  • 무시: 분석 도구가 자동으로 결측 처리하는 대로 둠 → 통제 못 함
직관 — “데이터 1200만 행에서 100만 빠지면 어때?” 의 함정

빅데이터 시대 분석가의 자연스러운 직감:

“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 의 전체 흐름

4 단계 워크플로

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 조건이 위반된 데이터에 적용 → 잘못된 보정
  • 처리 결과를 검증 없이 신뢰

올바른 순서:

  1. 시각화로 패턴 인식
  2. 도메인 지식 + 시각화로 메커니즘 가설
  3. 메커니즘에 맞는 도구 선택
  4. 처리 후 결과 검증

각 단계에 시간을 들이는 게 결국 빠른 길. 잘못된 처리는 분석 전체를 무효화.

3 AirCnC 데이터 — 사례

3.1 비즈니스 맥락

AirCnC 마케팅 설문

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
직관 — Demographic 이 cause 라는 의미

Buisson 의 미묘한 주의:

“Gender, Extraversion 이 BookingAmount 의 원인이라고 하면 두 의미가 있다:

  1. 외생 변수 (exogenous): 우리 분석 목적에서는 1 차 원인 (다른 어떤 변수의 결과 아님)
  2. 다중 매개: 인과 효과가 사회적 현상으로 매개·조절됨”

예: 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% 표본 손실.

직관 — Neuroticism 결측이 50% 라는 의미

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
직관 — MCAR 의 비유

비유: 설문 응답을 랜덤으로 분실하는 시스템.

  • 응답자 1000 명
  • 시스템이 무작위로 100 명의 응답을 분실 (랜덤 선택, 응답자 특성 무관)

이 경우:

  • 분실된 100 명과 남은 900 명의 분포가 통계적으로 동일
  • 900 명 분석 결과 = 1000 명 분석 결과 (잡음 작음)
  • Listwise deletion 안전

비즈니스 사례:

  • 데이터베이스 이전 중 무작위 행 손상
  • 측정 장비의 무작위 오작동
  • 설문 마지막 페이지를 우연히 건너뜀 (응답자가 신경 안 쓰는 부분)

→ MCAR 은 분석가에게 가장 친절한 결측. 그러나 비즈니스 데이터에서 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 자체는 결측에 영향 안 줌.

직관 — MAR 의 의미

이름에 “Random” 이 들어 있어 혼란스럽지만, 실제로는 “다른 관측 변수를 통해 결측 메커니즘 설명 가능” 이라는 뜻.

비유: 설문에서 “운동 빈도” 항목을 65세 이상 응답자가 응답 거부 경향.

  • 결측 확률이 Age (관측됨) 에 의존
  • 그러나 실제 운동 빈도 (결측 변수 자체) 에는 직접 의존 안 함
  • Age 가 통제되면 결측이 무작위처럼 행동

비즈니스 사례:

  • 이메일 응답률이 디바이스 (모바일 vs 데스크톱) 에 의존 — 디바이스는 관측됨
  • 신용카드 한도 결측이 가입 시기 (관측됨) 에 의존 — 옛 가입자에게는 그 필드가 없었음
  • CSAT 응답이 예약 채널 (관측됨) 에 의존 — 모바일 앱 채널은 설문 push 안 함

해결: Multiple Imputation 등 MAR 가정 하 도구 사용.

→ MAR 은 실제 비즈니스 분석에서 가장 흔한 시나리오. 이 가정 하의 도구가 표준.

MAR 의 가정 검증 불가능성

MAR 가정의 핵심 한계:

“결측 변수 자체에 의존 안 한다” 는 결측된 값을 모르므로 검증 불가.

분석가가 할 수 있는 것:

  1. 충분한 보조 변수 포함: 결측을 설명할 만한 모든 관측 변수 회귀에
  2. 민감도 분석: MAR 위반 시 결과가 얼마나 바뀌는지 검토
  3. 도메인 직관: “이 변수의 결측이 그 변수 자체 값에 의존할 가능성?”

만약 도메인 직관이 “그렇다 (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 의 값에 의존
직관 — MNAR 의 사례

비유: 설문에서 “월 소득” 항목을 고소득자가 회피.

  • 결측 확률이 월 소득 자체 (결측 변수) 에 의존
  • 다른 변수를 모두 통제해도 결측 메커니즘 설명 못함
  • 결측된 사람들의 진짜 값이 시스템적으로 다름

비즈니스 사례:

  • 가격 민감도가 강한 고객이 가격 관련 설문 회피 (가격 민감 자체가 결측 일으킴)
  • 우울증 환자가 우울증 척도 설문 회피
  • 노화에 민감한 고객이 나이 정보 회피

해결의 어려움:

  • MAR 가정의 도구 (mice 등) 가 작동 안 함 → 잘못된 보정
  • Sensitivity analysis (가정의 강도 변화) 또는 외부 데이터 수집 필요
  • 가정 없이 식별 안 됨

→ MNAR 은 분석가에게 가장 어려운 결측. 보고서에 한계 명시 필수.

4.4 분류의 비교

3 가지 결측 메커니즘 비교
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 로 종합)
   최종 추정 + 신뢰구간
직관 — MI 의 통찰

단일 대체의 문제:

  • 결측에 한 값만 채우면 그 값의 불확실성이 분석에 반영 안 됨
  • 분산 추정치가 진짜 분산보다 작음 → 신뢰구간이 좁아 보임 → “유의” 결과 가짜

다중 대체의 해결:

  • 결측 값에 대한 사후 분포에서 m 번 sample
  • 각 sample 의 분석 결과 사이 변동이 결측의 불확실성을 반영
  • Rubin’s rules 가 이 변동을 최종 신뢰구간에 통합

비유:

  • 단일 대체 = 결측 값을 “정답” 으로 가정 → 자만
  • 다중 대체 = 결측 값을 여러 가능성 종합 → 정직

→ MI 가 통계적으로 옳고, 실용적 표준.

Rubin’s Rules — 결과 종합

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 에 반영.

직관 — Within + Between 의 의미

비유: 여론조사를 5 번 다른 표본으로.

  • Within: 한 조사의 표본 잡음 (그 조사가 진짜 모수에서 얼마나 떨어졌는지)
  • Between: 5 조사 결과 사이 차이 (조사 간 변동)

두 분산을 합쳐야 진짜 불확실성. Within 만 보고 “결과 정확” 하면 잘못.

MI 도 같은 논리:

  • Within: 각 대체 데이터셋의 표본 잡음
  • Between: 대체 간 차이 (결측 불확실성)

Multiple Imputation 의 핵심 통계 메커니즘.

6 시각화 — md.pattern() 의 해석

6.1 결측 패턴 행렬

R 의 mice 패키지
library(mice)
md.pattern(available_data)

출력 예시:

     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%) 가 가장 많음.
직관 — 패턴 분석으로 도출되는 통찰

이 행렬에서 알 수 있는 것:

  1. 결측의 함께 발생 패턴: Extra·Neuro 둘 다 결측인 행이 많음 (228 + 120 + 24 + 18 = 390 명) → 두 trait 의 결측 메커니즘이 비슷할 가능성 (성격 검사 끝까지 안 함).

  2. Bkg_amt 결측 비율: 175 / 2000 = 8.75%. 비교적 작음 → 결제 정보 결측이 한정적.

  3. State 결측: 711 / 2000 = 35.5%. 의외로 큼 → “거주 주” 가 응답하기 부담스러운 정보일 수도, 또는 시스템적 누락 (신규 가입 폼에서 빠진 시기).

  4. 완전 데이터 비율: 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 헬스케어 — 환자 추적 손실

Loss to Follow-up

임상 시험에서 환자가 추적 기간 중 데이터 제공 중단:

메커니즘 사례 분류
환자가 이사로 연락 끊김 (이사 = 외부 사건, 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)
직관 — 패턴이 가르치는 것

이 함수의 출력으로 분석가가 보는 것:

  1. 가장 흔한 결측 조합 (전체 관측? 한 변수만 결측? 여러 변수 결측?)
  2. 결측이 함께 발생하는 변수 (한 변수 결측이면 다른 변수도 결측 경향?)
  3. 변수별 결측 수 순위

이 정보를 기반으로 다음 단계 (진단) 의 가설 도출.

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}")
직관 — Listwise 의 결과

예상 결과:

  • 진실: \(\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())
직관 — MI 의 첫 결과

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})")
직관 — Rubin Pooling 결과 해석

예상: 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 의 형제 글

9.2 이전 챕터

9.3 후속 챕터

9.6 카테고리 진입점

Subscribe

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