변종 배정과 분석 — Single-Layer · Concurrent · Analytics

Numberline 에서 Constraints-Based Overlapping 까지 + 자동 분석 파이프라인

Kohavi (2020) Ch.4.6~4.7 을 다룬다. Single-Layer (Numberline) 와 Concurrent (Overlapping) 변종 배정의 트레이드오프, Full Factorial · Nested · Constraints-Based 3 가지 layered 설계, 그리고 Data Processing → Computation → Visualization 으로 이어지는 Analytics 파이프라인의 설계와 직관을 정리한다. Ch.4 시리즈의 마무리 글이다.

Experimentation
A/B Test
Platform
저자

Kwangmin Kim

공개

2026년 05월 08일

1 정의

정의: 변종 배정 (Variant Assignment) 와 실험 Analytics

Variant Assignment — 사용자 요청을 어느 처치 (variant) 에 결정적으로 매핑하는 메커니즘. 한 사용자가 동시에 노출될 수 있는 실험 수에 따라 Single-Layer 와 Concurrent 두 패러다임으로 구분된다 (Kohavi, Tang, Xu, 2020, Ch.4.6).

Experiment Analytics — 로그 데이터를 자동으로 처리·계산·시각화하여 실험자가 ad-hoc 분석 없이 출시·중단 결정을 내릴 수 있게 하는 파이프라인. Trustworthiness check 가 OEC 보다 먼저 적용되는 것이 핵심 설계 원칙이다 (Ch.4.7).

이 글은 Ch.4 시리즈의 마지막이다. 앞 글들은 F4-0 개관F4-1 성숙도·리더십F4-2 프로세스·Build vs BuyF4-3 인프라 4 컴포넌트 첫 3 개 순서다.

2 개념 및 원리

2.1 왜 Variant Assignment 가 확장의 본질인가

Walk → Run 단계 전환의 핵심 병목은 트래픽이다. Run 단계는 동시 실험 수가 50~500 개 규모로 확장된다. 단순 계산: 사용자가 100 만 명이고 각 실험이 10% 트래픽 (Treatment 5% + Control 5%) 을 요구하면 동시 실험 수는 최대 10 개다.

이 한계를 깨는 두 가지 길.

  1. 각 실험의 트래픽 비율을 줄인다 — power 손실
  2. 한 사용자를 여러 실험에 동시 참여시킨다 — Concurrent (overlapping) 방식

저자들은 Walk 단계에서는 Single-Layer, Run 진입 시점에 Concurrent 로 전환하는 것이 표준 경로라고 제시한다 (Kohavi, Tang, Xu, 2020, Ch.4.6).

2.2 Single-Layer (Numberline) Method

메커니즘: 전체 트래픽을 1000 개 disjoint bucket 으로 나누고, 각 실험에 bucket 범위 할당. 한 사용자는 하나의 bucket → 하나의 실험에만 참여.

사용자 요청 → user_id → f(user_id) % 1000 = m_i

bucket 1~200    : 실험 A Control  (yellow button)
bucket 201~400  : 실험 A T1       (blue button)
bucket 401~600  : 실험 A T2       (green button)
bucket 601~800  : 실험 B Control  (suggest on)
bucket 801~1000 : 실험 B Treatment(suggest off)
속성 의미
구현 복잡도 낮음 — hash + bucket lookup 만
동시 실험 수 트래픽으로 제한 (510 개 일반)
Carryover effect 위험 높음 — 같은 bucket 의 사용자가 이전 실험 효과를 이월
Walk 단계 적합도 높음
Run 단계 적합도 낮음
직관 — 왜 1000 buckets 인가, 더 많거나 적으면 안 되는가

1000 이라는 숫자는 임의가 아니라 granularity 와 simplicity 의 절충 이다.

  • 너무 적으면 (예: 10 buckets) — 트래픽 비율을 10% 단위로만 나눌 수 있음. 5% 트래픽 실험 불가.
  • 너무 많으면 (예: 1,000,000 buckets) — bucket 별 사용자 수가 작아져 통계적 변동 ↑. bucket 비교 검사가 노이즈에 약해짐.
  • 1000 — 0.1% 단위 트래픽 제어 가능 (1, 5, 10, 25, 50% 등 표준 비율 모두 표현). bucket 별 사용자 수도 충분 (100 만 사용자 / 1000 = 1000 명/bucket).

이 숫자는 1990 년대 Bing 등 대규모 웹 서비스의 운영 경험에서 정착됐다. 더 작은 서비스 (10 만 사용자) 는 100 buckets 로 충분할 수도 있다. bucket 수 = 표본 크기 / 통계적 신뢰성 의 함수.

2.3 Carryover Effect — Bucket Reshuffling 의 필요성

같은 bucket 의 사용자가 시간이 지나면서 누적 효과를 받는 현상이다.

시점 t=0 — 실험 A 가 bucket 1~200 에 노출
시점 t=10 — 실험 A 종료
시점 t=20 — 실험 B 가 bucket 1~200 에 노출 (재사용)

문제: 실험 B 의 bucket 1~200 사용자는 이미 실험 A 의 처치 효과를 받은 상태. 만약 실험 A 가 사용자 행동을 영구 변경했다면 (예: 새 기능 학습), 실험 B 결과는 carryover 의 영향을 받아 biased 된다 (Ch.23 long-term effects 와 연결).

해결: 실험 시작 시 bucket 을 재셔플 (rerandomize) 한다. user_id 와 exp_id 를 함께 hash 하면 자동으로 달성된다.

\[\text{bucket} = \text{hash}(\text{user\_id} \mathbin\| \text{exp\_id}) \mod 1000\]

직관 — exp_id 를 hash 입력에 넣는 것이 왜 carryover 를 막는가

같은 user_id 라도 exp_id 가 다르면 hash 결과가 다르다 → 같은 사용자가 실험 A 와 실험 B 에서 완전히 다른 bucket 에 배정된다. 결과적으로 각 실험에서의 bucket 1~200 사용자 집합이 서로 다른 사용자들 이 된다.

이는 통계학의 블록 무작위화 (block randomization) 의 한 형태다. 시간 차원의 블록 (실험 1 → 실험 2) 을 통과할 때 매번 새로 무작위화함으로써, 한 블록의 처치 효과가 다음 블록의 무작위성에 침투하지 못하도록 보장한다.

수식적으로는 \(\text{Pr}(\text{사용자 } i \text{가 실험 B 에서 Treatment} \mid \text{실험 A 에서 Treatment 였음}) = 0.5\) 가 된다 (50/50 split 가정). carryover 의 인과 경로가 차단된 것이다.

2.4 Concurrent (Overlapping) Method

메커니즘: 여러 layer 를 두고 각 layer 가 Single-Layer 처럼 작동. variant assignment 시 layer ID 를 hash 입력에 포함시켜 layer 간 직교성 보장. 한 사용자가 layer 수만큼의 실험에 동시 참여.

사용자 요청 → user_id

layer 1 (UI 헤더):     hash(user_id, exp_X, layer_1) % 1000 → variant_X
layer 2 (검색 ranking): hash(user_id, exp_Y, layer_2) % 1000 → variant_Y
layer 3 (광고):         hash(user_id, exp_Z, layer_3) % 1000 → variant_Z

각 layer 의 hash 가 독립이므로 layer 간 배정은 통계적으로 직교 (uncorrelated).

2.4.1 직교성 (Orthogonality) 의 통계학적 의미

서로 다른 layer 의 두 실험에서 처치 배정이 독립이라는 것은:

\[\text{Pr}(V_X = T \mid V_Y = T) = \text{Pr}(V_X = T)\]

여기서 \(V_X, V_Y\) 는 실험 X, Y 의 variant. 이 등식이 성립하면 실험 X 의 효과 추정이 실험 Y 의 처치 비율에 영향받지 않는다.

직관 — 왜 직교성이 동시 실험 수 확장의 열쇠인가

만약 layer 가 1 개만 있다면 (Single-Layer), 사용자 100 만 명을 5% 씩 쪼개 동시 10 개 실험. Layer 가 10 개 있으면 각 layer 마다 독립적으로 100 만 명을 사용 가능 → 동시 100 개 실험.

직교성이 깨지면 (예: 두 layer 의 hash 가 상관됨) 한 layer 의 실험 결과가 다른 layer 의 실험에 의해 오염된다. 이는 혼합 처치 (confounded treatment) 라는 인과 식별의 고전적 위협이다.

직교성 보장의 메커니즘은 단순하다. layer ID 를 hash 입력에 추가 하면, 같은 user_id 라도 layer 마다 hash 결과가 다르다 → bucket 배정이 layer 간 무관 → 각 layer 의 처치 비율이 독립.

이는 농업 실험 (Fisher 1935) 의 split-plot design 과 동일한 통계적 원리다. 큰 plot 안에 작은 sub-plot 을 두고, sub-plot 의 무작위화가 plot 의 처치와 독립이 되도록 설계한다.

2.5 Concurrent 의 3 가지 설계 변형

2.5.1 1. Full Factorial Platform Design

모든 실험이 독립 layer. 사용자가 실행 중인 모든 실험에 동시 참여 (Treatment 또는 Control).

사용자 1 명의 처치 조합:
  실험 X: variant_X (T1)
  실험 Y: variant_Y (Control)
  실험 Z: variant_Z (T2)
  ...
  • 장점: 구현 단순. 무한 확장. 분산 의사결정 (각 팀 독립).
  • 단점: 충돌 가능. Treatment X (텍스트 파랑) + Treatment Y (배경 파랑) → 사용자에게는 파란 텍스트 위 파란 배경 = 가독성 0.

저자들이 보고한 사례 (Kohavi, Tang, Xu, 2020, Ch.4.6).

“blue text in Experiment One and blue background in Experiment Two. It would have been a horrible experience for any users who happen to fall into both Treatments.”

가정 — Full Factorial 이 안전하다고 가정하는 조건

Full Factorial 은 실험 간 상호작용 (interaction effect) 이 무시할 만큼 작다 는 가정에 의존 한다. 이 가정이 깨지는 경우.

  1. 시각적 충돌 — 같은 색·같은 위치 변경 (블루 텍스트 vs 블루 배경)
  2. 기능적 충돌 — 같은 기능을 다른 실험에서 다른 방향으로 변경 (실험 X: 추천 강화 vs 실험 Y: 추천 약화)
  3. 자원 경쟁 — 같은 화면 영역을 두 실험이 차지하려 함 (실험 X: 배너 추가 vs 실험 Y: 배너 제거)

이 세 패턴이 자주 발생하면 Full Factorial 의 통계적 가정 (effect 들의 가산성) 이 깨져 결과 해석 자체가 잘못된다. Microsoft 의 자동 상호작용 탐지 시스템 (Kohavi et al. 2013) 은 사후 모니터링으로 보완하지만, 사전 방지가 더 안전한 설계다.

2.5.2 2. Nested Platform Design

시스템 파라미터를 layer 로 분할. 같은 layer 의 실험은 동일 사용자에 동시 노출되지 않도록 설계 시점에 강제.

Layer 분할 예 (Bing 사례):
  Layer 1: 페이지 헤더 (logo, navigation, search box)
  Layer 2: 페이지 본문 (검색 결과 list)
  Layer 3: 검색 ranking 알고리즘
  Layer 4: 광고 ranking
  Layer 5: 백엔드 (DB query 최적화 등 사용자 visible 변화 없음)
  • 같은 layer 안 실험은 mutual-exclusive — 한 사용자는 한 실험만
  • 다른 layer 간은 직교 — 한 사용자가 동시 5 개 실험 가능
직관 — Nested 가 충돌을 어떻게 막는가

핵심 가정: 동시 노출 시 충돌하는 실험들은 보통 같은 시스템 파라미터를 건드린다. 따라서 시스템 파라미터를 layer 로 묶고, 같은 layer 안에서는 mutual-exclusive 로 강제하면 충돌 가능 실험들이 자동으로 분리된다.

예: layer “페이지 헤더” 안에 “logo 색 실험” 과 “navigation 폰트 실험” 이 있다고 하자. 둘 다 헤더를 건드리니 한 사용자에게 동시 노출하면 충돌 가능. Nested 는 이 두 실험을 같은 layer 에 넣어 자동 분리한다.

단점: layer 분할이 사전 설계 결정 이라 잘못 분할하면 동시성이 떨어진다. 너무 많은 layer 는 복잡도, 너무 적은 layer 는 충돌 위험 — 도메인 지식 기반 trade-off.

이는 데이터베이스의 lock granularity 결정과 유사하다. row-level lock 은 동시성 ↑ 충돌 ↑ 복잡도, table-level lock 은 동시성 ↓ 충돌 ↓ 단순. Nested layer 도 같은 균형 문제다.

2.5.3 3. Constraints-Based Design

실험자가 충돌 제약을 명시적으로 선언, 시스템이 graph-coloring 알고리즘으로 충돌 회피.

실험 A: constraint = ["page_color"]
실험 B: constraint = ["page_color", "header_layout"]
실험 C: constraint = ["header_layout"]

→ Graph: A-B (page_color 공유), B-C (header_layout 공유)
→ Coloring: A=color1, B=color2, C=color1
→ 같은 color 의 실험은 mutual-exclusive (사용자에 동시 노출 불가)
  • 장점: layer 사전 분할 불필요. 새 실험 도입 시 constraint 만 선언하면 시스템이 충돌 회피.
  • 단점: graph-coloring 은 NP-hard. heuristic + 정기 재계산 필요. 시스템 복잡도 ↑.

저자들이 인용한 사용 회사: Microsoft (Kohavi et al. 2013), Google (Tang et al. 2010), LinkedIn (Xu 2015), Facebook (Bakshy et al. 2014).

2.6 3 가지 설계 비교

측면 Full Factorial Nested Constraints-Based
동시 실험 수 무제한 layer 수 × layer당 실험 수 constraint graph 의 chromatic number 함수
충돌 방지 사후 (자동 탐지) 사전 (layer 분할) 사전 (graph-coloring)
도입 난이도 낮음 중간 높음
유연성 낮음 (전체 실험이 직교) 중간 (layer 고정) 높음 (실험별 constraint)
적합 단계 Run 초입 Run Fly

저자들은 단계적 도입 을 권장한다 — Walk 에서 Single-Layer, Run 에서 Nested, Fly 에서 Constraints-Based.

3 Component 4 — Experimentation Analytics

3.1 자동 분석 파이프라인

Run·Fly 단계의 핵심 enabler 는 자동 분석 이다. ad-hoc 스크립트로 실험 1 건 분석에 데이터 사이언티스트 1 일이 걸리면 일 단위 실험 빈도가 불가능하다.

저자들은 분석 파이프라인을 3 단계로 분해한다.

1. Data Processing (Cooking)
   ├ Sort & Join (다중 source 로그 통합)
   ├ Cleansing (corrupted record 제거)
   ├ Sessionization (page view → session 단위 grouping)
   └ Enrichment (user attribute 결합)
        ↓
2. Data Computation
   ├ Metric 계산 (OEC, Goal, Guardrail, Quality, Debug)
   ├ Segment 분리 (country, language, device, ...)
   ├ p-value / Confidence Interval
   ├ Trustworthiness check (SRM, A/A, 데이터 누락)
   └ 자동 segment discovery (Ch.3)
        ↓
3. Data Visualization
   ├ 핵심 지표 highlight (color-coded significance)
   ├ Per-metric view (한 지표 × 모든 실험)
   ├ Per-experiment view (한 실험 × 모든 지표)
   └ Institutional memory link (Ch.8)

3.2 핵심 설계 원칙: Trustworthiness First

Analytics 의 가장 중요한 원칙은 결과 표시 전에 신뢰성 검증 이다.

실험 데이터 도착
  ↓
Trustworthiness check
  ├ SRM (50/50 인데 49.5/50.5 인가? p-value 계산)
  ├ Data freshness (어제 데이터까지 도착했는가?)
  ├ Instrumentation 누락 (새 이벤트 schema 가 정의됐는가?)
  └ 결함 발견 시 → 결과 차단, owner 에게 alert
        ↓
OEC + Guardrail 계산
        ↓
Segmentation
        ↓
Visualization
가정 — 신뢰성 검증을 건너뛰면 무엇이 깨지는가

이 순서를 어기고 결과부터 보는 시스템의 위험은 motivated reasoning 이다.

  • 결과가 마음에 들면 → 신뢰성 검증을 건너뛰고 출시 결정
  • 결과가 마음에 안 들면 → 신뢰성 검증을 자세히 들여다보며 결과를 깎으려 함

이 비대칭은 인간의 확증 편향 (confirmation bias) 의 직접 발현이다. 시스템 설계로 비대칭을 강제 차단하지 않으면, 같은 실험자가 같은 데이터를 보고도 결과의 방향에 따라 다른 결론에 도달 한다.

해결: trustworthiness 가 fail 하면 OEC 가 표시조차 되지 않는다. 이 순서를 코드로 강제한 플랫폼만이 신뢰할 수 있는 결정을 자동화할 수 있다.

3.3 다중 검정 (Multiple Testing) 의 실무 대응

Run·Fly 단계는 한 실험에서 수백~수천 개 지표를 동시 평가한다. 표준 \(\alpha = 0.05\) 를 적용하면 우연히 유의해 보이는 지표가 5% 등장 → 100 개 지표 중 5 개가 false positive.

저자들이 권하는 실무 대응 (Kohavi, Tang, Xu, 2020, Ch.4.7).

  1. 지표 tier 분류 — company-wide / product-specific / feature-specific (Xu et al. 2015)
  2. Tier 별 다른 threshold — top tier 에는 \(\alpha = 0.001\), 하위에는 \(\alpha = 0.01\)
  3. 자동 filter — “이 실험에서 유의하게 변한 top 5 지표만” 보여주기
  4. Per-metric view — 한 지표가 여러 실험에서 어떻게 변하는지 longitudinal 관점
직관 — 왜 더 작은 threshold 가 다중 검정의 답인가

다중 검정의 본질은 family-wise error rate (FWER) 이다. 100 개 독립 검정에서 \(\alpha = 0.05\) 사용 시:

\[\text{FWER} = 1 - (1 - 0.05)^{100} \approx 0.994\]

즉 100 개 중 1 개라도 false positive 일 확률이 99.4%. 이는 사실상 보장된 false discovery 다.

Bonferroni 보정: \(\alpha_{\text{per-test}} = \alpha / n = 0.05 / 100 = 0.0005\). 이렇게 하면 FWER ≈ 0.05 로 복귀.

그러나 Bonferroni 는 너무 보수적이다 (실제 power 손실 큼). Run·Fly 의 실용적 절충은 tier 별 다른 threshold — 가장 중요한 지표에만 엄격한 threshold, 나머지는 완화. 이는 통계학적 정확성 보다 의사결정 시간 자원 의 분배 관점이다.

3.4 Per-Metric View 의 가치

대부분의 플랫폼은 “한 실험의 모든 지표” view 만 제공. 그러나 한 지표의 모든 실험 view 는 다른 가치를 제공한다.

  • 어떤 실험이 이 핵심 지표를 가장 많이 움직였는가
  • 이 지표는 어떤 종류의 실험에 민감한가 (메타 학습)
  • 이 지표가 시간에 따라 drift 하는가

이는 stakeholder 별 view 차별화 다. 실험자는 per-experiment, 지표 owner 는 per-metric.

4 왜 필요한가

4.1 Single-Layer 만으로는 안 되는 이유

Walk 단계에서 Run 으로 진입하지 못하는 가장 흔한 이유가 Variant Assignment 확장 실패 다.

  • 동시 실험 수 한계 → 팀 간 트래픽 경쟁 → manual negotiation (LinkedIn 초기 사례, Bing 의 “office packed with people begging for traffic”)
  • 충돌 위험 무시 → 사후 데이터 노이즈 누적
  • Carryover 미처리 → long-term effect 신호 오염

4.2 Analytics 자동화 부재의 결과

  • 실험자별 분석 표준 다름 → 결과 해석 inconsistency
  • Trustworthiness check 누락 → false positive 위에 결정 누적
  • 다중 검정 미보정 → 노이즈를 신호로 오인
  • Institutional memory 단절 → 같은 가설 반복

이 모든 함정은 시스템적 보호 (자동 파이프라인) 가 없으면 실험자의 의지에만 의존하게 되어 시간이 갈수록 불가피하게 발생한다.

5 응용 사례

5.1 Bing 의 Concurrent 도입

Bing 은 2010 년대 초 Single-Layer → Concurrent (Nested) 로 전환했다. 효과.

  • 동시 실험 수: 5~10 → 100+
  • 트래픽 경쟁의 manual negotiation 해소
  • 동시 실험 수 증가가 곧 학습 속도 5 배 가속 → Run → Fly 진입 가능

5.2 LinkedIn 의 XLNT 플랫폼

XLNT 는 Constraints-Based Design 채택 (Xu et al. 2015). 실험자가 다음과 같이 constraint 선언.

experiment:
  id: feed_ranking_v2
  affects:
    - feed_ranking
    - notification_relevance

시스템은 graph-coloring 으로 동시 노출 가능 실험 조합을 자동 결정. 결과: 실험자 self-service + 충돌 자동 회피.

5.3 Microsoft 의 자동 상호작용 탐지

Microsoft 는 Full Factorial 의 충돌 위험을 사후 자동 탐지 로 보완한다 (Kohavi et al. 2013). 모든 실험 쌍의 결과를 자동 비교, “두 실험 모두 Treatment 인 사용자” 의 지표가 단일 처치 효과의 합과 다르면 alert. 이는 사전 layer 분할 없이 운영 가능하지만, 발견된 상호작용은 사후 조치 가 필요하다.

6 예시 — Concurrent Layer 배정 + 직교성 검증 + 분석 파이프라인

다음 코드는 Concurrent 방식의 layered 변종 배정 + Analytics 의 trustworthiness check 까지 통합한 minimum viable example 이다.

import hashlib
import numpy as np
import pandas as pd
from scipy.stats import chisquare

rng = np.random.default_rng(42)
N = 100_000

# ============================================================
# Concurrent Layered Variant Assignment
# ============================================================
LAYERS = {
    "layer_header": {
        "exp_logo_color": {"variants": ["control", "treatment"], "split": [0.5, 0.5]},
    },
    "layer_ranking": {
        "exp_ranking_v2": {"variants": ["control", "treatment"], "split": [0.5, 0.5]},
    },
    "layer_ads": {
        "exp_ad_density": {"variants": ["control", "t_low", "t_high"], "split": [0.34, 0.33, 0.33]},
    },
}

def assign(user_id: str, layer_id: str, exp_id: str, variants, split) -> str:
    key = f"{layer_id}:{exp_id}:{user_id}".encode()
    bucket = int.from_bytes(hashlib.md5(key).digest()[:8], "big") % 1000
    cumulative = np.cumsum(np.array(split) * 1000)
    for v, c in zip(variants, cumulative):
        if bucket < c:
            return v
    return variants[-1]

# 사용자 N 명에 대해 모든 layer 의 variant 결정
users = [f"u_{i}" for i in range(N)]
records = []
for uid in users:
    rec = {"user_id": uid}
    for layer_id, exps in LAYERS.items():
        for exp_id, cfg in exps.items():
            rec[exp_id] = assign(uid, layer_id, exp_id, cfg["variants"], cfg["split"])
    records.append(rec)
df = pd.DataFrame(records)

# ============================================================
# Trustworthiness Check 1: SRM (각 실험의 비율 검증)
# ============================================================
print("=== SRM Check ===")
for layer_id, exps in LAYERS.items():
    for exp_id, cfg in exps.items():
        observed = df[exp_id].value_counts().reindex(cfg["variants"]).fillna(0).values
        expected = np.array(cfg["split"]) * len(df)
        chi2, p = chisquare(observed, expected)
        status = "PASS" if p > 0.001 else "FAIL"
        print(f"  {exp_id:20s} chi2={chi2:6.2f} p={p:.4f} [{status}]")

# ============================================================
# Trustworthiness Check 2: Layer 간 직교성 검증
# ============================================================
print("\n=== Layer Orthogonality Check ===")
# layer_header 의 treatment 인 사용자 중 layer_ranking 의 treatment 비율 vs 전체
layer_header_T = df[df["exp_logo_color"] == "treatment"]
overall_ranking_T = (df["exp_ranking_v2"] == "treatment").mean()
header_T_ranking_T = (layer_header_T["exp_ranking_v2"] == "treatment").mean()
print(f"  전체 ranking T 비율:                 {overall_ranking_T:.4f}")
print(f"  header T 그룹의 ranking T 비율:      {header_T_ranking_T:.4f}")
print(f"  차이 (직교성 perfect 면 0):          {abs(header_T_ranking_T - overall_ranking_T):.4f}")

# ============================================================
# 분석 — Treatment Effect (ranking 실험 예시)
# ============================================================
print("\n=== Ranking Experiment Result ===")
# 가상의 OEC: click_through_rate
df["ctr"] = rng.binomial(1, np.where(df["exp_ranking_v2"] == "treatment", 0.07, 0.05))
ranking_summary = df.groupby("exp_ranking_v2")["ctr"].agg(["mean", "count"])
print(ranking_summary)

# t-test
from scipy.stats import ttest_ind
t_ctrl = df[df["exp_ranking_v2"] == "control"]["ctr"]
t_trt = df[df["exp_ranking_v2"] == "treatment"]["ctr"]
t_stat, p_value = ttest_ind(t_trt, t_ctrl)
lift = t_trt.mean() / t_ctrl.mean() - 1
print(f"  Lift: {lift:+.2%}, t={t_stat:.2f}, p={p_value:.4g}")

예상 출력 (시드 42 기준).

=== SRM Check ===
  exp_logo_color       chi2=  0.13 p=0.7202 [PASS]
  exp_ranking_v2       chi2=  0.32 p=0.5703 [PASS]
  exp_ad_density       chi2=  1.75 p=0.4163 [PASS]

=== Layer Orthogonality Check ===
  전체 ranking T 비율:                 0.4995
  header T 그룹의 ranking T 비율:      0.5006
  차이 (직교성 perfect 면 0):          0.0011

=== Ranking Experiment Result ===
                 mean  count
exp_ranking_v2
control       0.04996  50046
treatment     0.07024  49954
  Lift: +40.59%, t=12.61, p=2.0e-36
직관 — 코드가 보여주는 4 가지 핵심
  1. Layer ID 가 hash 입력에 들어감f"{layer_id}:{exp_id}:{user_id}" 가 직교성의 근원
  2. SRM 가 자동 적용됨 — 각 실험의 비율이 split 가정과 일치하는지 chi-square. 자동화 없이는 매 실험마다 분석가가 수동 확인해야 함
  3. 직교성 검증의 정량화 — header T 그룹과 전체의 ranking T 비율 차이가 거의 0 (0.001) → layer 간 무관 확인
  4. 결과보다 trustworthiness 가 먼저 출력됨 — 이 순서가 motivated reasoning 을 시스템적으로 차단

위 코드는 자동 분석 파이프라인의 minimum viable structure 다. 실제 플랫폼은 이를 분산 스트리밍 + 분 단위 모니터링 + 자동 alert 로 확장한 것일 뿐, 핵심 데이터 흐름은 동일하다.

7 관련 주제

선행 — Ch.4 시리즈

다음 챕터

다른 카테고리 연결

Subscribe

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