층화 형성과 거리 척도 — Distance·Rescaling·One-Hot 의 깊이 (Buisson Ch.9.1)

Manhattan vs Euclidean, Min-Max vs Z-score, Ordered vs Unordered, 알고리즘 비교

Buisson (2021) Ch.9 의 층화 형성 절을 자세히 정리한다. 거리 척도 (Manhattan, Euclidean, Mahalanobis), Rescaling 방법 (Min-Max, Z-score, Quantile), Categorical 변수의 처리 (one-hot, ordered, target encoding), Pair Matching 알고리즘 (Optimal, OptGreedy, NaiveGreedy) 의 비교, 5,000 owner 규모의 실무 적용을 단계별로 시연한다.

Experimentation
Causal Inference
저자

Kwangmin Kim

공개

2026년 05월 08일

1 정의

정의: 층화 형성의 3 단계 절차
1. 거리 척도 (Distance Metric) 선택
   - 두 owner 의 "유사성" 정량화

2. 변수 정규화 (Rescaling)
   - 단위 차이 균등화

3. 매칭 알고리즘 (Pair Matching)
   - 비슷한 owner 묶기

각 단계의 결정이 stratum 의 품질 결정. 잘 형성된 stratum = 그룹별 perfect balance.

직관 — 3 단계가 모두 필요한 이유

분석가의 자연스러운 직감: “그냥 비슷한 사람끼리 묶으면 끝.”

각 단계의 함정:

  • 거리 없이: “비슷함” 의 정량화 없음 → 임의적 매칭
  • Rescaling 없이: 큰 단위 변수가 거리 dominate → 잘못된 매칭
  • 알고리즘 없이: 큰 데이터에서 unfeasible (수백만 가능 매칭)

세 단계 모두 통합되어야 정확한 stratum.

→ 각 단계가 다음 단계의 전제. Skip 불가.

2 거리 척도

2.1 Manhattan vs Euclidean

두 거리의 정의

Manhattan (L1 norm):

\[ d_{AB} = \sum_{j} |x_{A,j} - x_{B,j}| \]

Euclidean (L2 norm):

\[ d_{AB} = \sqrt{\sum_{j} (x_{A,j} - x_{B,j})^2} \]

예 (2 변수, A = (0, 0), B = (3, 4)):

  • Manhattan: |3-0| + |4-0| = 7
  • Euclidean: √(9 + 16) = 5

→ 같은 두 점이 다른 거리.

직관 — 두 거리의 비유

비유: 도시 안에서 A 지점에서 B 지점 가는 방법.

  • Manhattan: 격자형 도시 (NYC), 도로 따라 이동. “오른쪽 3 블럭, 위로 4 블럭” → 7 블럭.
  • Euclidean: 직선 거리 (까마귀가 나는 방향). √(9 + 16) = 5.

일반적으로:

  • Manhattan: outlier 영향 작음 (제곱 안 함)
  • Euclidean: 표준 통계 가정에 부합

비즈니스 분석 default = Euclidean. 단, outlier 많으면 Manhattan 고려.

2.2 Mahalanobis 거리

변수 간 상관 고려

Mahalanobis 거리:

\[ d_{AB} = \sqrt{(x_A - x_B)^T \Sigma^{-1} (x_A - x_B)} \]

여기서 \(\Sigma\) = 변수의 공분산 행렬.

특징:

  • 변수 간 상관 자동 조정
  • Rescaling 효과 자동 (분산 normalize)
  • 통계학에서 표준 거리

비용: 공분산 행렬 계산 + 역행렬 (큰 데이터에서 계산 비용).

직관 — Mahalanobis 의 강점

비유: 키와 몸무게 비교.

  • 일반인: 키 170, 몸무게 70 (정상)
  • 농구선수: 키 200, 몸무게 70 (가벼운 키)
  • 여성: 키 170, 몸무게 50 (마름)

Euclidean (rescaled):

  • 일반 vs 농구: 거리 큼 (키 차이)
  • 일반 vs 여성: 거리 작음 (몸무게만 차이)

Mahalanobis:

  • 키 200 + 몸무게 70 = “마름 (키 대비 가벼움)” 패턴
  • 키 170 + 몸무게 50 = “정상 (키 대비 가벼움)” 패턴
  • 두 경우 모두 “가벼움” 의 비슷함 인식 가능

→ Mahalanobis 는 “변수의 joint 분포” 를 고려. 더 정확하지만 복잡.

비즈니스 분석에서 default = Euclidean (단순). Mahalanobis 는 변수 간 강한 상관 + 정확도 우선 시.

2.3 Gower 거리

직관 — 혼합 변수의 거리

데이터가 numeric + categorical 혼합이면:

  • Euclidean: one-hot 후 사용
  • Gower: numeric/categorical 동시 처리

Gower 거리 (변수 j 의 거리):

  • Numeric: \(|x_{A,j} - x_{B,j}| / \text{range}(x_j)\)
  • Categorical: 0 (같음) 또는 1 (다름)

전체 거리:

\[ d_{AB} = \frac{1}{p} \sum_{j} d_j(A, B) \]

(p = 변수 수)

장점: One-hot 안 해도 됨. 단순.

단점: 모든 변수에 같은 weight. Euclidean 처럼 customize 어려움.

→ R 의 cluster::daisy() 가 Gower 지원. Python 은 별도 패키지 필요.

비즈니스 분석 default = Euclidean + one-hot. Gower 는 명시적 선택.

3 Rescaling

3.1 Min-Max vs Z-score

두 방법 비교

Min-Max (Range-based):

\[ x_{\text{scaled}} = \frac{x - x_{\min}}{x_{\max} - x_{\min}} \]

  • 출력 범위: [0, 1]
  • 분포 형태 보존
  • Outlier 에 민감 (max 가 outlier 면 다른 값들 모두 작아짐)

Z-score (Standard):

\[ x_{\text{scaled}} = \frac{x - \mu}{\sigma} \]

  • 출력 범위: 약 [-3, 3] (정규 분포)
  • 평균 0, 분산 1
  • Outlier 영향 분산
직관 — 어느 것을 사용할까

선택 가이드:

상황 권장
분포가 정규에 가까움 Z-score
Outlier 많음 Z-score (또는 robust scaling)
분포가 skewed Min-Max (단, outlier 점검)
모든 변수 [0, 1] 같은 단위 원함 Min-Max
통계학적 표준 Z-score

비즈니스 분석에서:

  • Default = Min-Max (단순, 직관)
  • Outlier 많으면 → Quantile rescaling 또는 winsorize 후 Min-Max

Buisson 의 default = Min-Max.

3.2 Quantile (Rank-Based) Rescaling

직관 — 분포 무관 변환

Outlier 가 많을 때 대안:

df["x_quantile"] = df["x"].rank(pct=True)

각 값을 percentile 로 변환:

  • x = 100 (top 1%) → 0.99
  • x = 50 (median) → 0.50
  • x = 1 (bottom 1%) → 0.01

특징:

  • 분포 형태 무시
  • Outlier 영향 0
  • Rank 정보만 보존

장점: 매우 robust. 단점: 분포 정보 손실.

→ 비즈니스에서 income, sale amount 같은 highly skewed 변수에 자주 사용.

3.3 Outlier 처리

Outlier 의 함정
sq_ft 데이터: 460~1120 (정상)
+ 한 개의 outlier: 5,000 (10 배 큰 property)

Min-Max:

  • 정상 값 범위가 [0, 0.13] 으로 압축
  • 정상 owner 들 사이 거리가 매우 작아짐
  • Outlier 가 “혼자 1.0”

대처:

  1. Winsorize: 99 percentile 로 cap (5,000 → 1,200)
  2. Log transform: \(\log(x)\) 후 rescale
  3. Quantile rescaling: rank 기반

Winsorize 코드:

df["sq_ft_winsor"] = df["sq_ft"].clip(
    lower=df["sq_ft"].quantile(0.01),
    upper=df["sq_ft"].quantile(0.99),
)

이 처리로 outlier 영향 제거하되 정보 일부 유지.

4 Categorical 변수의 처리

4.1 One-Hot Encoding 의 변형

표준 One-Hot
Property type: ("house", "townhouse", "apartment")
   ↓
type_house: 0/1
type_townhouse: 0/1
type_apartment: 0/1

Property A (house): (1, 0, 0) Property B (townhouse): (0, 1, 0) Property C (apartment): (0, 0, 1)

거리:

  • A vs B (Euclidean): \(\sqrt{(1-0)^2 + (0-1)^2 + (0-0)^2} = \sqrt{2} \approx 1.414\)
  • A vs C: \(\sqrt{2}\)
  • B vs C: \(\sqrt{2}\)

→ 모든 다른 카테고리 쌍이 같은 거리.

직관 — Drop-First 의 함정

흔한 실수: pandas 의 drop_first=True:

pd.get_dummies(df["type"], drop_first=True)

이 옵션이 첫 카테고리 (house) 를 drop. 결과:

type_townhouse: 0/1
type_apartment: 0/1

거리:

  • House (0, 0) vs Townhouse (1, 0): √1 = 1
  • House (0, 0) vs Apartment (0, 1): √1 = 1
  • Townhouse (1, 0) vs Apartment (0, 1): √2

→ House 가 다른 두 type 과 거리 1, Townhouse 와 Apartment 사이 거리 √2 ≈ 1.41.

함의: House 가 reference 처럼 작용. Townhouse 와 Apartment 가 House 보다 서로 더 멀음 (artifact).

→ Stratification 에서는 drop_first=False 권장. 모든 카테고리 동등 처리.

drop_first=True 는 회귀 (multicollinearity 회피) 용. Stratification 과 다름.

4.2 Ordered Categorical

Tier 의 ordering 보존

AirCnC 의 tier (1, 2, 3, descending):

  • Tier 1 (top) > Tier 2 > Tier 3 (bottom)

One-hot 사용 시 ordering 무시:

  • Tier 1 vs Tier 2 거리 = √2
  • Tier 1 vs Tier 3 거리 = √2 (잘못! 2 가 더 가까울텐데)

해결 1: Numeric 처리

df["tier_num"] = df["tier"].map({1: 1, 2: 2, 3: 3})
df["tier_scaled"] = (df["tier_num"] - 1) / 2  # [0, 1]

거리:

  • Tier 1 (0) vs Tier 2 (0.5): 0.5
  • Tier 1 (0) vs Tier 3 (1): 1.0

→ Tier 1 vs Tier 2 가 더 가까움 (직관 일치).

직관 — Ordered vs Unordered 의 결정

질문: “변수의 카테고리가 ordering 가능한가?”

Ordered:

  • Tier (1, 2, 3)
  • 등급 (A, B, C, D, F)
  • Likert (매우 동의 ~ 매우 비동의)
  • → Numeric 처리 (또는 ordered categorical with custom distance)

Unordered:

  • 색깔 (red, blue, green)
  • 국적 (US, JP, KR)
  • Property type (house, townhouse, apartment) — 약간 ordering 가능 (size?)
  • → One-hot

판단 어려운 경우:

  • 거리 명세 가능 (예: “house ↔︎ townhouse 가 house ↔︎ apartment 보다 가깝다”)
  • → Custom distance matrix 또는 ordering 정의

→ Domain 직관 + 데이터의 의미. 자동 결정 어려움.

4.3 Target Encoding

직관 — Outcome 의 평균으로 인코딩

대안: 카테고리를 outcome 의 평균으로:

국가별 booking_amount 평균:
   US: $80
   JP: $120
   KR: $95

각 owner 의 country 를 그 평균값으로 변환.

장점: Outcome 과 직접 연관 → distance 가 outcome 측면에서 의미 있음. 단점: Outcome 사용 → leakage 위험 (실험 후 분석 시 control vs treatment 비교가 영향)

비즈니스 분석에서 stratification 에는 권장 안 함 (outcome 사용으로 partial outcome leakage). 다른 ML 작업에서는 사용 가능.

→ Stratification default: one-hot 또는 ordered numeric. Target encoding 회피.

5 Pair Matching 알고리즘

5.1 Optimal Matching

Hungarian Algorithm

Optimal matching = 모든 가능 매칭 중 최적 (전역 최소 거리).

알고리즘: Hungarian Algorithm (Munkres, 1957).

복잡도: O(N³).

5,000 owner: ~10^11 연산 → 며칠.

작은 N (< 500) 에서만 실용적.

직관 — Optimal 의 비용

Optimal 의 약속:

  • 모든 stratum 의 거리 합이 최소

비용:

  • N = 100: 0.01 초
  • N = 1,000: 1 초
  • N = 10,000: 17 분
  • N = 100,000: 12 일

→ N > 1,000 에서 비실용적.

해결: Greedy approximation.

5.2 NaiveGreedy

알고리즘
1. 모든 pair 의 거리 계산 (O(N²))
2. 가장 가까운 pair 선택 → stratum 1
3. 사용된 owner 제외
4. 가장 가까운 다음 pair 선택 → stratum 2
5. 반복 until 모두 매칭

복잡도: O(N²) (거리 계산) + O(N² log N) (sorting).

5,000 owner: ~25M 연산 + sorting → 수 분.

NaiveGreedy 의 한계

전역 최적이 아님 — 첫 pair 가 좋아도 마지막 pair 가 매우 나쁠 수 있음.

예시:

4 owner: A, B, C, D
거리:
   A-B: 1
   A-C: 100
   A-D: 100
   B-C: 100
   B-D: 100
   C-D: 1

NaiveGreedy:

  • 가장 가까운 pair: A-B (거리 1)
  • 다음 pair: C-D (거리 1)
  • 총 거리: 2

Optimal:

  • A-B + C-D: 2 (같음)

이 경우는 OK. 그러나 다른 시나리오:

   A-B: 1
   A-C: 2
   A-D: 100
   B-C: 100
   B-D: 100
   C-D: 100

NaiveGreedy:

  • A-B (거리 1)
  • C-D (거리 100, 강제)
  • 총 = 101

Optimal:

  • A-C (거리 2) + B-D (거리 100)
  • 또는 A-D + B-C
  • 총 ≈ 102

거의 비슷. Greedy 가 일반적으로 충분.

5.3 OptGreedy

NaiveGreedy 의 개선

OptGreedy = “한 단계 더 보고 결정”.

1. 가장 가까운 pair X 선택
2. X 와 비슷한 다른 가까운 pair Y 가 있는가?
   - X 가 사라지면 Y 의 distance 가 어떻게?
3. X 와 Y 를 함께 고려해 더 좋은 결정

복잡도: 약 N² log N.

정확도: NaiveGreedy 보다 약간 좋음, Optimal 보다 약간 나쁨.

5,000 owner: 수 분 (NaiveGreedy 와 비슷).

→ 분석가의 default: NaiveGreedy 또는 OptGreedy (실용적 trade-off).

5.4 R 의 block() vs Python 구현

R 의 blockTools
library(blockTools)
strat <- block(
    data,
    id.vars = "ID",
    n.tr = 3,
    algorithm = "naiveGreedy",
    distance = "euclidean"
)

옵션:

  • algorithm: “optimal”, “optGreedy”, “naiveGreedy”
  • distance: “euclidean”, “mahalanobis”, “manhattan”
  • n.tr: 그룹 수

R 의 가장 성숙한 구현.

Python 의 한계

Python 에는 직접 대응 패키지가 부족. 옵션:

  1. scikit-learn 의 NearestNeighbors: nearest pair 찾기
  2. networkx 의 max_weight_matching: graph 기반 optimal
  3. scipy 의 linear_sum_assignment: Hungarian
  4. 수동 구현: NaiveGreedy 직접

R 사용자가 stratification 에 더 편리. Python 사용자는 직접 구현 또는 R 호출.

→ Buisson 권장 (재방문): “Categorical 변수가 중요하면 R 사용. Python 은 numeric only 데이터에 OK.”

6 5,000 Owner 의 매칭

6.1 시간 측정

Python NaiveGreedy 의 실제
import time
import numpy as np
from scipy.spatial.distance import pdist, squareform


def time_matching(n_owners=5000, n_features=10, n_groups=3):
    """매칭 시간 측정."""
    np.random.seed(42)
    features = np.random.normal(0, 1, (n_owners, n_features))

    start = time.time()

    # 거리 계산
    distances = squareform(pdist(features, metric="euclidean"))
    elapsed_dist = time.time() - start

    # 매칭 (단순화)
    np.fill_diagonal(distances, np.inf)
    used = np.zeros(n_owners, dtype=bool)
    n_strata = n_owners // n_groups

    for _ in range(n_strata):
        masked = distances.copy()
        masked[used, :] = np.inf
        masked[:, used] = np.inf
        i, j = np.unravel_index(np.argmin(masked), masked.shape)
        if masked[i, j] == np.inf:
            break
        used[i] = used[j] = True
        # 추가 stratum member (단순화)
        avail = np.where(~used)[0]
        if len(avail) > 0:
            k = avail[0]
            used[k] = True

    elapsed_total = time.time() - start
    return elapsed_dist, elapsed_total


dist_time, total_time = time_matching(5000)
print(f"거리 계산 시간: {dist_time:.2f} 초")
print(f"전체 매칭 시간: {total_time:.2f} 초")

예상 (laptop):

  • 거리 계산: 5 초
  • 매칭: ~30 초
  • 합계: ~35 초

5,000 owner 가 실용적 시간 안에 처리 가능.

직관 — 메모리 vs 시간

거리 행렬 = 5,000 × 5,000 = 25M 엔트리.

각 엔트리 8 bytes (double) → 200 MB 메모리.

큰 데이터 (50,000+) 에서:

  • 거리 행렬 = 50,000² × 8 = 20 GB → laptop 한계 초과
  • 해결: chunked computation, sparse distance, 또는 approximate nearest neighbor

비즈니스 분석에서 N < 10,000 가 일반적 → 메모리 OK.

10,000+ 는 분산 처리 또는 sampling.

6.2 매칭 품질 평가

::: {.callout-note} ## Stratum 내 거리 통계

def evaluate_matching(strata, distances):
    """Stratum 내 거리의 분포 평가."""
    intra_distances = []
    for stratum in strata:
        # Stratum 내 모든 pair 의 평균 거리
        for i in range(len(stratum)):
            for j in range(i + 1, len(stratum)):
                intra_distances.append(distances[stratum[i], stratum[j]])

    return {
        "mean": np.mean(intra_distances),
        "median": np.median(intra_distances),
        "max": np.max(intra_distances),
        "p95": np.percentile(intra_distances, 95),
    }


# 비교: 단순 무작위 vs 층화
random_strata = np.random.permutation(5000).reshape(-1, 3)  # 무작위 stratum
# 가정: stratified_strata 는 NaiveGreedy 결과

# 시뮬레이션 (간단화)
print("매칭 후 stratum 내 평균 거리:")
print("  단순 무작위: 1.5 (예상)")
print("  층화: 0.3 (예상)")
print("\n5 배 작은 거리 = stratum 안의 owner 가 매우 비슷")
직관 — 매칭 품질의 비즈니스 의미

Stratum 내 평균 거리가:

  • 작음 (0.3): owner 들이 서로 매우 비슷 → 그룹별 perfect balance
  • 큼 (1.5): owner 들이 서로 다름 → balance 효과 약함

품질이 안 좋으면:

  • 더 많은 변수 추가 (도메인 지식)
  • 알고리즘 변경 (NaiveGreedy → OptGreedy)
  • 더 많은 stratum (n_groups = 2 vs 3)

이 self-check 가 stratification 의 자기 검증.

7 응용 — 다양한 변수 형태

7.1 Numeric Only

시나리오

5,000 owner, 5 numeric 변수.

처리:

  1. Min-Max rescale 또는 Z-score
  2. Euclidean 거리
  3. NaiveGreedy 매칭

가장 단순. R/Python 둘 다 쉬움.

7.2 Mixed (Numeric + Categorical)

시나리오

5 numeric + 3 categorical (각 3~5 수준).

처리:

  1. Categorical: one-hot (drop_first=False)
  2. Numeric: rescale
  3. 결합 → Euclidean 거리 + NaiveGreedy

또는:

  1. Gower 거리 (categorical/numeric 동시 처리)
  2. 매칭 (Gower 호환 알고리즘)

R 의 cluster::daisy + blockTools 조합. Python 은 직접 구현.

7.3 Time Series Variables

직관 — 시계열 변수의 처리

5 변수가 시계열 (예: 6 개월 booking 추세):

처리 옵션:

  1. 요약 통계: 평균, 분산, trend (slope) 추출 → 5 numeric 변수로 환원
  2. DTW (Dynamic Time Warping) 거리: 시계열 직접 비교
  3. PCA: 주성분 추출 후 매칭

비즈니스 default = 요약 통계 (단순). DTW/PCA 는 정확도 우선 시.

8 코드 예시 — 종합

8.1 통합 함수

import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler, OneHotEncoder
from scipy.spatial.distance import pdist, squareform


def stratified_assignment(df, id_col, n_groups=3,
                           rescaling="minmax", distance="euclidean",
                           algorithm="naive_greedy", random_seed=42):
    """
    완전한 층화 무작위 배정 파이프라인.
    """
    np.random.seed(random_seed)

    # 1. 변수 분리
    ids = df[id_col].values
    features_df = df.drop(columns=[id_col])
    num_cols = features_df.select_dtypes(include=[np.number]).columns.tolist()
    cat_cols = features_df.select_dtypes(include=["object", "category"]).columns.tolist()

    # 2. Rescaling
    if rescaling == "minmax":
        scaler = MinMaxScaler()
    elif rescaling == "zscore":
        from sklearn.preprocessing import StandardScaler
        scaler = StandardScaler()
    else:
        raise ValueError(f"Unknown rescaling: {rescaling}")

    if num_cols:
        num_scaled = scaler.fit_transform(features_df[num_cols])
    else:
        num_scaled = np.empty((len(df), 0))

    # 3. One-hot encoding
    if cat_cols:
        enc = OneHotEncoder(handle_unknown="ignore", sparse_output=False)
        cat_array = enc.fit_transform(features_df[cat_cols])
    else:
        cat_array = np.empty((len(df), 0))

    # 4. 결합
    features = np.concatenate([num_scaled, cat_array], axis=1)

    # 5. 거리
    if distance == "euclidean":
        distances = squareform(pdist(features, metric="euclidean"))
    elif distance == "manhattan":
        distances = squareform(pdist(features, metric="cityblock"))
    elif distance == "mahalanobis":
        distances = squareform(pdist(features, metric="mahalanobis"))
    else:
        raise ValueError(f"Unknown distance: {distance}")

    np.fill_diagonal(distances, np.inf)

    # 6. 매칭 (NaiveGreedy 만 구현)
    n = len(ids)
    used = np.zeros(n, dtype=bool)
    strata = []

    while used.sum() + n_groups <= n:
        masked = distances.copy()
        masked[used, :] = np.inf
        masked[:, used] = np.inf
        i, j = np.unravel_index(np.argmin(masked), masked.shape)
        if masked[i, j] == np.inf:
            break

        stratum = [i, j]
        used[i] = used[j] = True

        for _ in range(n_groups - 2):
            avail = np.where(~used)[0]
            if len(avail) == 0:
                break
            avg_dist = distances[stratum, :][:, avail].mean(axis=0)
            next_idx = avail[np.argmin(avg_dist)]
            stratum.append(next_idx)
            used[next_idx] = True

        if len(stratum) == n_groups:
            strata.append(stratum)

    # 7. Stratum 안에서 무작위 그룹 배정
    group_labels = ["control"] + [f"treat{i+1}" for i in range(n_groups - 1)]
    assignments = np.empty(n, dtype=object)
    for stratum in strata:
        groups = np.random.permutation(group_labels)
        for k, idx in enumerate(stratum):
            assignments[idx] = groups[k]

    return pd.DataFrame({id_col: ids, "group": assignments})
직관 — 통합 함수의 가치

이 함수가 분석가에게 주는 것:

  • 데이터 + 옵션 입력
  • 7 단계 파이프라인 자동
  • 결과 즉시 사용 가능

옵션:

  • Rescaling: minmax / zscore
  • Distance: euclidean / manhattan / mahalanobis
  • Algorithm: naive_greedy (확장 가능)

→ 분석 파이프라인의 표준 도구. Production 사용 가능.

8.2 시뮬레이션 — Quality 비교

# 가상 AirCnC 데이터
np.random.seed(42)
n = 500
df = pd.DataFrame({
    "ID": range(n),
    "sq_ft": np.random.uniform(460, 1120, n),
    "tier": np.random.choice([1, 2, 3], n),
    "avg_review": np.random.uniform(0, 10, n),
    "type": np.random.choice(["house", "townhouse", "apartment"], n),
})

# 1. 단순 무작위
np.random.seed(42)
df["random_group"] = np.random.choice(
    ["control", "treat1", "treat2"],
    n,
)

# 2. 층화
strat = stratified_assignment(df, "ID", n_groups=3)
df_strat = df.merge(strat, on="ID")

# 그룹별 특성 비교
print("=== 단순 무작위 ===")
print(df.groupby("random_group").agg(
    sq_ft_mean=("sq_ft", "mean"),
    sq_ft_std=("sq_ft", "std"),
    avg_review_mean=("avg_review", "mean"),
))

print("\n=== 층화 ===")
print(df_strat.groupby("group").agg(
    sq_ft_mean=("sq_ft", "mean"),
    sq_ft_std=("sq_ft", "std"),
    avg_review_mean=("avg_review", "mean"),
))
직관 — 결과 비교

예상:

sq_ft 평균 (단순) sq_ft 평균 (층화)
Control 790 790
Treat1 815 790
Treat2 770 790

층화 후 그룹별 차이 거의 0. 단순 무작위에서 ±25 차이.

비즈니스 함의: 층화 후 효과 측정 정확. Sample size 줄이거나 power 향상 가능.

9 종합 — 분석가의 결정 트리

Stratification 의 결정
1. 표본 크기?
   < 1,000 → 거의 항상 층화
   > 10,000 → 단순 무작위 OK

2. Pre-experiment 데이터?
   있음 → 층화
   없음 → 단순

3. 변수 유형?
   Numeric only → Euclidean + Min-Max
   Mixed → One-hot + Euclidean (또는 Gower)
   Time series → 요약 통계 추출 후 Euclidean

4. 알고리즘?
   N < 500 → Optimal
   N < 10,000 → NaiveGreedy 또는 OptGreedy
   N > 10,000 → Sampling 또는 분산 처리

5. Quality 검증?
   - Stratum 내 거리 분포 점검
   - 그룹별 특성 비교
   - 원하는 quality 안 나오면 → 변수 추가, 알고리즘 변경

이 결정 트리가 stratification 의 표준 절차.

10 관련 주제

10.1 Ch.9 의 형제 글

10.2 이전 챕터

10.3 후속 챕터

10.4 카테고리 진입점

Subscribe

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