1 정의
Kohavi (2020) Ch.20.10 가 명시한 미해결 문제. 산업 표준의 정답이 없는 영역.
1.0.0.1 Question 1 — Triggering Unit
사용자 가 trigger 된 후, 어느 activity 부터 분석에 포함?
Option 1: trigger 시점 이후의 activity 만
Option 2: 전체 session
Option 3: 전체 day
Option 4: 실험 시작부터의 모든 activity
1.0.0.2 Question 2 — Plotting Metrics over Time
Triggered metric 의 time-series plot:
- 가짜 trend 의 자연 발생
- Decreasing Treatment effect over time pattern
- 단일-day 와 cross-day 의 mismatch
원문 인용 (Ch.20.10): “The following are issues that we face where we have no clear answer. Awareness is important, even when we have pros and cons and not the ‘answer.’”
핵심 통찰: 두 question 모두 trade-off 가 명확. Option 별 장단. Awareness 가 가장 중요. 표준 은 없지만 잘못된 선택의 결과를 알면 informed decision 가능.
2 개념 및 원리
2.1 Question 1 — Triggering Unit
저자 명시 (Ch.20.10).
2.1.1 4 가지 Option
2.1.1.1 Option 1 — Trigger 후 의 activity 만
Approach:
사용자 가 trigger 시점 t_T
Analysis: t > t_T 의 activity 만
Reasoning:
Trigger 전 의 data 는 Treatment 영향 안 받음
→ Pure Treatment effect 측정
Implementation:
사용자 별 trigger timestamp 추적
분석 시 t > t_T 만 filter
2.1.1.2 함정 — Partial Sessions
저자 강조: “those triggered sessions are now partial, and their metrics are abnormal (e.g., the metric clicks prior to checkout are zero).”
시나리오:
사용자 Alice 의 trigger:
Session 시작 t=0
Trigger event t=120s (checkout 시작)
Session 끝 t=300s
Trigger 후 activity:
t=120~300s 만
Total session: 180s 의 후반
Metric 의 abnormality:
Pre-checkout clicks: 0 (사실 trigger 후 의 사용자 가 이미 search 다 했음)
Page views: 매우 적음
Browse time: 0
분석:
Per-user metric 이 abnormal
Triggered user 의 baseline 이 wrong
Variance 큼
2.1.1.3 Statistical Power 의 손실
Partial session 의 effect:
- Pre-trigger activity 무시
- Per-user 의 활동 부족
- Metric 의 mean 이 small
- Variance 큼
Sensitivity:
Smaller effect detect 어려움
Sample size 더 큰 필요
2.1.1.4 Option 2 — 전체 Session
Approach:
사용자 가 trigger 한 session 의 모든 activity
Trigger 전후 모두 포함
Reasoning:
Session 의 일관성
사용자 의 전체 행동 측정
Pre-trigger 도 trigger 의 motivation 의 일부
함정:
Pre-trigger activity 가 Treatment effect 와 무관
Variance ↑ (irrelevant noise 추가)
2.1.1.5 Option 3 — 전체 Day
Approach:
사용자 가 trigger 한 day 의 모든 activity
Multi-session 포함
Reasoning:
Day 의 자연 단위
사용자 의 daily behavior 의 capture
Cross-session effect (예: trigger 후 다시 visit)
함정:
Day 의 boundary 가 arbitrary (timezone)
사용자 의 trigger 와 day boundary 의 mismatch
분석 의 noise
2.1.1.6 Option 4 — 실험 시작부터의 모든 activity
저자 권고 (computational simplicity): “Computationally, it is easier to take the user, including data from the beginning of the experiment, if they trigger at any point; however, this causes a small loss of statistical power.”
Approach:
사용자 가 실험 기간 동안 한 번이라도 trigger 시:
그 사용자 의 모든 activity (실험 시작부터) 분석에 포함
Reasoning:
Computationally simple:
- Per-user trigger flag 만 필요
- Activity 전체 sum
- 단순 query
Lifetime tracking 의 mental model:
- Trigger 한 사용자 = "affected"
- 전체 사용자 행동 분석
함정:
Pre-trigger activity 는 Treatment 영향 안 받음
포함 시 variance ↑ (small statistical power loss)
2.1.2 4 Option 의 비교
Statistical Power:
Option 1 (trigger 후 만): potentially highest (cleanest), but partial sessions 의 noise
Option 2 (전체 session): medium
Option 3 (전체 day): lower
Option 4 (실험 시작부터): lowest (가장 많은 irrelevant)
Computational Cost:
Option 1: 높음 (per-event filtering)
Option 2: 중간 (session boundary detect)
Option 3: 중간 (day aggregation)
Option 4: 낮음 (single trigger flag)
Implementation 복잡성:
Option 1: 매우 복잡
Option 2~3: 중간
Option 4: 매우 단순
2.1.3 산업 표준
대부분 platform: Option 4
Why:
- Computational efficiency 우선
- Sensitivity loss 가 marginal (대부분 case)
- Implementation 단순
예외:
- Per-event critical metric (page latency)
- 매우 niche feature (trigger 후 effect 만 critical)
- 이 경우 Option 1 또는 2 사용
2.1.3.1 Option 4 의 sensitivity loss 의 정량
사용자 Alice 의 모든 activity:
Pre-trigger: 100 events (Treatment 영향 0)
Post-trigger: 50 events (Treatment 영향 가능)
Total events: 150
Treatment effect 의 dilution:
Effect 가 50/150 = 33% 의 events 에 집중
→ Effect dilute factor 1/3
Sample variance:
Pre-trigger 의 variability 가 sample 에 추가
But Treatment effect 자체는 50 events 에만
Net effect:
Sensitivity 손실 ~30% (Option 1 대비)
But computational savings 큼
Trade-off 합리적
2.1.3.2 Time axis 의 분해
사용자 Alice 의 timeline:
t_0 = 실험 시작
t_1 = Alice 의 first visit
t_2 = Alice 의 trigger event (예: checkout 시작)
t_3 = Alice 의 trigger session 끝
t_4 = Alice 의 trigger day 끝
t_5 = 실험 끝
각 option 의 분석 범위:
Option 1: t_2 ~ t_5 (trigger 후 만)
Option 2: session boundary ~ session boundary 의 trigger session
Option 3: day boundary ~ day boundary 의 trigger day + 모든 future activity
Option 4: t_1 ~ t_5 (Alice 의 모든 activity)
2.1.3.3 Trade-off 의 graph
Option 1 ←─ pure ─────────────── inclusive ─→ Option 4
(Statistical purity) (Computational simplicity)
Trade-off:
Pure 쪽: sensitivity 의 theoretical maximum, but partial sessions
Inclusive 쪽: 단순, but small dilution
2.1.3.4 실무 선택 framework
선택 기준:
Option 1:
- Per-event critical metric
- Partial session OK
- Niche analysis
Option 2:
- Session-level metric (session duration, etc.)
- Whole session 의 의미
Option 3:
- Daily metric (DAU, daily revenue)
- Day boundary 의 명확성
Option 4:
- Computational efficiency 우선
- User-level metric (LTV, retention)
- Lifetime tracking
- 산업 표준
이 framework 이 결정의 input.
2.1.4 Implementation Detail — Sticky Flag
Standard implementation:
사용자 의 trigger flag (sticky):
Initial: False
First trigger event: True
이후: True 유지
분석:
Trigger flag = True 사용자
그 사용자 의 모든 events 분석
Storage:
Per-user 의 single boolean
Compact
Query:
SELECT user.* FROM events
WHERE user_id IN (SELECT user_id FROM triggers)
이 sticky flag 가 Option 4 의 운영 표준.
2.2 Question 2 — Plotting Metrics over Time
저자 명시 (Ch.20.10).
2.2.1 일반 plotting 의 함정
저자 인용 (Kohavi et al. 2012, Chen, Liu and Xu 2019): “Plotting a metric over time with increasing numbers of users usually leads to false trends.”
2.2.1.1 시나리오 — Cumulative
Standard (잘못된) plot:
X-axis: day
Y-axis: cumulative metric (실험 시작부터 누적)
Day 1: metric 의 day 1 만
Day 2: metric 의 day 1 + 2
Day 3: metric 의 day 1 + 2 + 3
...
False trend:
Cumulative metric 의 평균이 점진 변화
- 사용자 수 증가
- Heavy user 의 추가
- Fresh data 의 비중 변화
→ "Trend" 처럼 보이지만 noise
해결:
Daily plot:
X-axis: day
Y-axis: that day 의 metric
Each day 의 사용자 가 visit 한 metric 만
Cumulative 무시
2.2.1.2 Daily plot 의 issue
저자 강조: “It’s best to look at graphs over time where each day shows the user who visited that day.”
Daily plot:
Day 1 의 metric: Day 1 의 사용자 만
Day 2 의 metric: Day 2 의 사용자 만
...
각 day 가 independent
Trend 의 noise 회피
2.2.2 Triggered Metric 의 추가 함정
저자 강조: “When users are triggered, we have the same problem: 100% of users on the first day were triggered, but a smaller portion of users on day 2 were triggered, as some triggered on day 1 but just visited on day 2.”
2.2.2.1 시나리오
Day 1:
100 users visit
10 users trigger (10% trigger rate)
Triggered analysis: 10 users 의 metric
Day 2:
100 users visit
20 users 가 트리거 ever (Day 1 의 10 + Day 2 의 새 10)
But: Day 1 의 trigger 한 사용자 의 일부만 Day 2 visit (예: 5)
Triggered visit on Day 2:
10 (Day 2 의 새 trigger) + 5 (Day 1 의 returning) = 15
Triggered analysis 의 day 2 metric:
15 users (mixed: new triggers + returning)
Average effect? unclear
2.2.2.2 False Trend 의 메커니즘
Triggered effect 의 시간 변화:
Day 1: 100% 사용자 가 fresh trigger
- First exposure 의 novelty effect
- Effect: +10%
Day 2: mix
- 67% returning triggered (Day 1 의 trigger 한 사용자)
- 33% new triggered
- Returning 의 attenuated effect (novelty 사라짐)
- Effect: +7%
Day 3:
- 더 많은 returning
- Average effect: +5%
Day 7:
- 거의 모든 사용자 가 returning
- Average effect: +3% (long-term effect 만)
False trend:
"Treatment effect 가 decreasing over time"
→ 사용자 가 wear-out? 또는 단지 sample composition 변화?
이 trend 가 일반적이지만 false interpretation.
2.2.3 Chen, Liu, Xu (2019) 의 권고
저자 인용: “Perhaps it’s better to plot each day with the users who visited that day and triggered on that day.”
2.2.3.1 Approach
Plot:
X-axis: day
Y-axis: that day's triggered users 의 metric
Each day 의 사용자:
- That day visit
- That day triggered (다른 day 가 아닌)
각 day 의 fresh sample 만
Cross-day contamination 제거
2.2.3.2 한계
저자 강조: “The key issue is that the overall Treatment effect must include all days, so the single-day and overall (or cross-day) numbers do not match.”
Plot 의 limit:
Day 별 fresh trigger 만 사용
But overall analysis 는 모든 sample 포함:
- Cross-day returning user
- Lifetime tracking
Mismatch:
Daily plot 의 effect: +X%
Overall analysis 의 effect: +Y%
X ≠ Y (different sample composition)
Reason:
Daily plot: pure first-exposure effect
Overall: lifetime average effect (novelty + wear-out)
이 mismatch 가 reporting 의 confusion 원인.
2.2.3.3 Best Practice
Recommended:
- Daily plot (fresh trigger 만): trend visualization
- Overall analysis: launch decision
Both 표시:
Daily: "trend pattern"
Overall: "summary number"
Decision:
Overall metric 으로 launch 결정
Daily plot 으로 trend 의 understanding
이 dual presentation 이 산업 표준.
가정: 분석가 가 daily plot 의 decreasing trend 를 보고 “Treatment effect wearing out” 결론.
2.2.3.4 시나리오
Daily plot:
Day 1: +10% lift
Day 7: +5% lift
Day 14: +3% lift
Day 21: +2% lift
분석가 conclusion:
"Treatment effect wearing out over time"
"Long-term lift = 2%"
Decision:
"Long-term effect 가 launch ROI 에 부족"
Reject
Mistake:
Daily plot 의 sample composition 의 자연 변화
Treatment 의 진정 wear-out 가 아닌 (또는 일부 만)
Cross-day returning user 의 attenuated effect
2.2.3.5 Real Investigation
Daily plot 의 분해:
Day 7 의 triggered = 첫 trigger 사용자 + Day 1 trigger 사용자 의 returning
첫 trigger (Day 7):
Effect: +10% (novelty intact)
Day 1 trigger 의 returning:
Effect: +3% (attenuated)
Mix 의 average:
Day 7 의 fresh: 30%
Day 7 의 returning: 70%
Average: 0.30 × 10% + 0.70 × 3% = 5.1%
→ Daily 의 +5% 는 mix 의 결과
→ True wear-out: +10% (첫 exposure) → +3% (long-term)
2.2.3.6 Correct Decision
Two effects 의 분리:
Initial (novelty): +10%
Long-term: +3%
Decision:
Long-term +3% 가 sustainability 측정의 truth
Holdout 의 결과 (Ch.15) 와 일치
만약 +3% 가 launch ROI 에 충분:
Launch decision
만약 +3% 가 부족:
Investigate novelty effect
- Real value or trick?
- Generalize possible?
또는 reject
이 분석이 진정 mature platform 의 decision quality.
2.3 Multi-iteration Triggered 분석
2.3.0.1 시나리오
실험: 새 feature
Trigger: feature 사용
Iteration 1:
Trigger rate: 5%
Triggered effect: +20%
Diluted: +1%
Iteration 2 (improved version):
Trigger rate: 8%
Triggered effect: +15%
Diluted: +1.2%
Iteration 3 (final):
Trigger rate: 12%
Triggered effect: +12%
Diluted: +1.4%
2.3.0.2 Pattern
Each iteration:
Triggered effect 의 ↓ (effect 의 attenuation)
Trigger rate 의 ↑ (더 많은 사용자 노출)
Diluted impact 의 일부 ↑
Decision:
Final iteration 의 +1.4% 가 launch ROI?
Generalization 의 path 보임
Future iteration 가능?
이 multi-iteration 의 pattern 이 generalization 의 typical path.
3 왜 필요한가
2 Open Question 의 awareness 부재 시.
- Triggering unit 의 잘못된 선택: Sensitivity 손실 또는 computational cost
- Time-series misinterpretation: False trend 의 잘못된 결정
활성 시.
- 합리적 trigger unit: Trade-off 의 명시
- Time-series 의 정확한 해석: Daily vs overall 의 분리
이 awareness 가 advanced triggering 의 mature 단계.
4 응용 사례 — Chen, Liu, Xu (2019) 의 LinkedIn 사례
LinkedIn 의 메타분석 (Chen, Liu, Xu 2019):
Cross-experiment pattern:
Day 1: 강한 effect
Day 7: 약한 effect
Day 30: 매우 약한 effect
이 pattern 이 universal:
Sample composition 의 변화
Returning user 의 attenuated effect
Recommendation:
Daily plot 의 trend 를 wear-out 으로 해석 X
Sample composition 의 이해 필요
Best practice:
Each day 의 fresh trigger 만 plot
Overall lifetime metric 으로 decision
이 메타분석 의 lesson 이 산업 표준.
5 Ch.20 시리즈 마무리
6 편 완료:
- F20-0 — Triggering 의 본질, 5 사례, trustworthy, pitfalls 의 지도
- F20-1 — Examples 1~3 (Partial, Conditional, Coverage Increase)
- F20-2 — Examples 4~5 (Coverage Change, ML Counterfactual)
- F20-3 — Numerical example, optimal/conservative, diluted impact
- F20-4 — Trustworthy (SRM, complement), 3 pitfalls
- F20-5 — Open questions (triggering unit, time-series)
다음: Ch.21 (SRM, 4 편).
6 코드 예시 — Triggered Metric 의 Time-series Plot
False trend vs corrected plot.
import numpy as np
import pandas as pd
from datetime import timedelta
rng = np.random.default_rng(42)
# 시뮬레이션 setup
n_days = 21
n_users = 5000
# 사용자 의 daily visit 패턴
daily_visit_prob = rng.uniform(0.1, 0.8, n_users) # heterogeneous
# Trigger probability per visit (10% chance per visit)
trigger_per_visit = 0.10
# Treatment assignment
treatment = rng.choice([0, 1], n_users, p=[0.5, 0.5])
# Treatment effect 의 lifecycle
# - Initial (first day after trigger): +10%
# - Long-term (after day 7): +3%
# 각 user 의 trigger day 추적
user_trigger_day = np.full(n_users, -1, dtype=int)
# Daily activity 시뮬레이션
daily_data = []
for day in range(n_days):
for user in range(n_users):
if rng.uniform(0, 1) < daily_visit_prob[user]:
# Visit
if user_trigger_day[user] == -1:
# 아직 trigger 안 함, trigger 시도
if rng.uniform(0, 1) < trigger_per_visit:
user_trigger_day[user] = day
triggered = user_trigger_day[user] != -1
days_since_trigger = day - user_trigger_day[user] if triggered else 0
# Treatment effect (depending on time since trigger)
if treatment[user] == 1 and triggered:
if days_since_trigger <= 1:
effect_factor = 1.10 # +10% initial
elif days_since_trigger <= 7:
effect_factor = 1.06 # +6% middle
else:
effect_factor = 1.03 # +3% long-term
else:
effect_factor = 1.0
base_metric = rng.normal(50, 15)
metric = base_metric * effect_factor
daily_data.append({
"day": day,
"user": user,
"treatment": treatment[user],
"metric": metric,
"triggered": triggered,
"first_trigger_day": user_trigger_day[user] if triggered else -1,
})
df = pd.DataFrame(daily_data)
# === Plot 1: Naive cumulative ===
print("=== Naive Cumulative Plot (잘못) ===")
print(f"{'Day':>4} {'Cumulative T mean':>20} {'Cumulative C mean':>20} {'Cum lift':>10}")
for day in range(0, n_days, 3):
cum_data = df[df["day"] <= day]
triggered = cum_data[cum_data["triggered"]]
t_mean = triggered[triggered["treatment"] == 1]["metric"].mean()
c_mean = triggered[triggered["treatment"] == 0]["metric"].mean()
if not np.isnan(t_mean) and not np.isnan(c_mean):
lift = (t_mean - c_mean) / c_mean * 100
print(f"{day:>4} {t_mean:>20.2f} {c_mean:>20.2f} {lift:>9.2f}%")
# === Plot 2: Daily (mixed sample) ===
print("\n=== Daily Plot (mix of fresh + returning triggered) ===")
print(f"{'Day':>4} {'Daily T mean':>15} {'Daily C mean':>15} {'Daily lift':>12}")
for day in range(0, n_days, 3):
day_data = df[df["day"] == day]
triggered = day_data[day_data["triggered"]]
t_mean = triggered[triggered["treatment"] == 1]["metric"].mean()
c_mean = triggered[triggered["treatment"] == 0]["metric"].mean()
if not np.isnan(t_mean) and not np.isnan(c_mean):
lift = (t_mean - c_mean) / c_mean * 100
print(f"{day:>4} {t_mean:>15.2f} {c_mean:>15.2f} {lift:>11.2f}%")
# === Plot 3: Daily fresh trigger only ===
print("\n=== Daily Plot (only fresh trigger 사용자) ===")
print(f"{'Day':>4} {'Fresh T mean':>15} {'Fresh C mean':>15} {'Fresh lift':>12}")
for day in range(0, n_days, 3):
day_data = df[df["day"] == day]
fresh_trigger = day_data[day_data["first_trigger_day"] == day]
t_mean = fresh_trigger[fresh_trigger["treatment"] == 1]["metric"].mean()
c_mean = fresh_trigger[fresh_trigger["treatment"] == 0]["metric"].mean()
if not np.isnan(t_mean) and not np.isnan(c_mean):
lift = (t_mean - c_mean) / c_mean * 100
print(f"{day:>4} {t_mean:>15.2f} {c_mean:>15.2f} {lift:>11.2f}%")
# Overall analysis
print("\n=== Overall Lifetime Analysis ===")
all_triggered = df[df["triggered"]]
t_overall = all_triggered[all_triggered["treatment"] == 1]["metric"].mean()
c_overall = all_triggered[all_triggered["treatment"] == 0]["metric"].mean()
overall_lift = (t_overall - c_overall) / c_overall * 100
print(f"T overall: {t_overall:.2f}, C overall: {c_overall:.2f}")
print(f"Overall lift: {overall_lift:.2f}%")이 코드의 메시지:
6.0.0.1 Naive Cumulative
Day 별 cumulative lift:
Day 0: ~+10% (only first day)
Day 7: ~+7% (mix)
Day 14: ~+5%
Day 21: ~+4%
Pattern: decreasing
6.0.0.2 Daily Plot (mix)
Day 별 daily lift:
Day 0: ~+10%
Day 7: ~+6% (mix of fresh + returning)
Day 14: ~+5%
Day 21: ~+4%
Pattern: similar decreasing
But each day independent
6.0.0.3 Daily Fresh Trigger
Day 별 fresh trigger lift:
Day 0: ~+10% (all fresh)
Day 7: ~+10% (still fresh on day 7)
Day 14: ~+10%
Day 21: ~+10%
Pattern: stable!
True initial effect
6.0.0.4 통합 메시지
Cumulative 또는 daily mix:
False trend (decreasing)
Sample composition 의 변화
Fresh trigger only:
True initial effect (stable)
Pure first-exposure 측정
Overall lifetime:
Mix 의 lifetime average
Launch decision 의 input
→ 3 metric 모두 다른 question 에 답
→ Awareness 의 가치
7 관련 주제
선행
Ch.20 시리즈 마무리 — 6 편 완료. 다음 Ch.21 (SRM).
관련 챕터
- F23-* — Ch.23 장기 효과 — Novelty/wear-out 분석
- F8-* — Ch.8 제도적 기억 — 메타분석
다른 카테고리 연결