본문으로 건너뛰기
업무 일지 · No. 011

마케팅 테스트 결과를 효과적으로 검증하기 위한 시행착오

YoonRain읽는 시간 5분데이터
목차

광고 소재나 랜딩을 바꾸고 나면 늘 같은 질문이 남습니다. 이게 정말 나아진 건지, 그날 운이 좋았던 건지요. 표본은 늘 작았고, 그래서 8월부터 11월까지 판정 방법이 네 번 바뀌었어요.

8월, 합쳐서 볼 때와 날짜별로 짝지어 볼 때

처음 부딪힌 건 메타 소재 A/B였습니다. 7일치를 합쳐서 CTR을 검정했더니 p=0.21이 나왔어요. 아무 차이도 없다는 뜻이었습니다.

그런데 날짜별로 두 소재를 짝지어 보니 그림이 달랐습니다. 9일 중 9일 모두 이미지 소재가 이겼어요. 이긴 날만 세는 부호검정으로 보면 p=0.0039입니다.

from scipy.stats import binomtest

# 날마다 두 소재의 CTR을 비교해 이긴 날만 센다
wins, days = 9, 9
print(binomtest(wins, days, 0.5).pvalue)  # 0.0039

합쳐서 보면 날마다 다른 노출량과 요일 차이가 전부 오차로 섞입니다. 같은 날끼리 짝을 지으면 그 날짜 효과가 빠져요. 표본이 작아도 검정력이 올라간다는 걸 이때 처음 확인했고, 이후 메타 A/B는 이 방식을 기본 판정법으로 썼습니다.

하루치 CPA는 보지 않기로

8~9월에는 증액 판정이 이어졌습니다. 메타 일별 전환은 0건인 날과 두 자릿수인 날이 섞여 있었어요. 하루치 CPA로는 판단하지 않는다는 규칙을 이때 세웠습니다.

기존 소재와 신규 소재의 CPA를 비교할 때도 같은 문제가 나왔습니다. 양쪽 전환이 모두 50건 미만이었고 포아송 구간이 겹쳤어요. 이 상태로는 어느 쪽이 낫다고 확정할 수 없었습니다.

구글 증액 판정에서는 CPA 95% 신뢰구간을 같이 냈고, 비용당 전환은 정확 포아송 검정으로 비교했어요(p=0.748). 결론은 점추정으로 내되, 구간이 얼마나 넓은지는 한계로 함께 적었습니다.

전환 수로 CPA 구간을 내는 방법은 아래와 같습니다. 원리를 보여 주려고 다시 쓴 예시라 값은 실제와 달라요.

from scipy.stats import chi2

def poisson_ci(k, alpha=0.05):
    """전환 k건의 정확 포아송 95% 구간"""
    lo = chi2.ppf(alpha / 2, 2 * k) / 2 if k > 0 else 0.0
    hi = chi2.ppf(1 - alpha / 2, 2 * (k + 1)) / 2
    return lo, hi

cost, k = 1_000_000, 30  # 예시 값
lo, hi = poisson_ci(k)
print(cost / hi, cost / k, cost / lo)  # CPA 하한, 점추정, 상한

검정 단위는 날

9월에는 주말 예산을 다시 나눌지 판단해야 했습니다. 클릭이나 세션 단위로 비율 검정을 하면 유의성이 부풀려져요. 같은 사람, 같은 날의 관측이 여러 개로 잡히기 때문입니다. 통계에서는 의사복제라고 부르는 문제예요.

그래서 요일 효과는 날 단위로만 봤습니다. 주차마다 평일 하루 평균과 주말 하루 평균을 비교하는 부호검정을 했고, 8주 중 7주에서 방향이 같았어요(p=0.035).

일별 CPA는 Mann-Whitney로 비교했습니다. 8주 표본에서는 메타가 p=0.11로 유의하지 않았습니다. 6개월로 넓히자 p<0.001이 나왔고, 주말 CPA 중앙값은 평일의 약 2.4배였어요.

검정력도 계산해 봤습니다. CPA 25% 차이를 잡으려면 전환이 250건 필요했는데, 주말 전환은 그 6분의 1 정도였어요. 유의성을 기다리면 판정을 영영 못 합니다. 그래서 유의성 대신 방향이 일관되는지와 미리 정한 임계값을 넘는지로 판정했어요.

10~11월, 판정 규칙을 먼저 정해 두기

랜딩 개편 때는 데이터를 보기 전에 판정 규칙부터 고정했습니다. 판정 기준을 잠그는 장치는 앞 글에 적었고, 여기서는 그 안의 통계 설계를 정리합니다.

먼저 오차가 어디서 오는지 진단했어요. 홈 평일 가입은 분산/평균이 1.13, 전날과의 상관이 0.00이었습니다. 포아송과 거의 같다는 뜻이고, 일 단위 오차가 세션 단위 이항 오차와 비슷했어요(0.98배). 반면 다른 페이지 하나는 1.48배였습니다. 이 페이지를 세션 단위로 검정하면 유의가 과대하게 나와요.

from statistics import mean, variance

daily = [21, 24, 19, 23, 25, 20, 22]  # 평일 하루 가입 수 (예시 값)
print(variance(daily) / mean(daily))  # 1에 가까우면 포아송에 가깝다

다음으로 MDE를 페이지별, 기간별로 계산했습니다. 그 결과 1순위 지표를 MDE가 가장 작은 가입 화면 진입률(MDE 10%)로 골랐어요.

사후 창은 평일 20일로 잡았습니다. 4주를 채우면 요일 구성이 같아서 요일 보정이 필요 없어요. 판정은 일별 비율의 웰치 t로 하고, 주 단위 분산비가 1을 넘는 지표는 판정선을 √(분산비)배 넓혔습니다. 개편 전 기간끼리 비교하는 A/A도 넣었어요.

이상 감지 기준도 미리 정했습니다. 2일 누적 −50%(z 약 2.9), 5일 누적 −30%(z 약 2.6)이에요. 개편 직후에 숫자가 무너지면 판정일을 기다리지 않고 바로 봅니다.

정규분포 가정은 어디까지 버텼나

판정 설계 곳곳에 정규분포 가정이 들어가 있었습니다.

일별 가입이 하루 20명대라서 포아송을 정규로 근사해도 된다고 봤어요. 앞의 분산 진단이 이 전제를 받쳤습니다.

판정선에는 z 1.96 대신 t분포를 썼어요. 표본이 평일 26일 대 20일이라 t(0.975, df=44)가 2.0154입니다. 근사식 1.96 + 2.4/df로 계산하면 2.0145가 나와서 차이가 거의 없다는 것도 확인했어요.

결과는 유의, 비유의로 나누지 않고 개선 확률로 보고했습니다. 차이를 웰치 표준오차로 나눈 값의 정규 누적확률이에요. 평평한 사전분포와 정규 근사를 가정한 값이라는 점은 문서에 같이 적었습니다.

from math import sqrt
from statistics import NormalDist, mean, stdev

def judge(pre, post, var_ratio=1.0, min_days=10):
    """pre, post: 일별 비율 목록. 평일 10일 미만이면 판정하지 않는다"""
    if len(post) < min_days:
        return None
    diff = mean(post) - mean(pre)
    se = sqrt(stdev(pre) ** 2 / len(pre) + stdev(post) ** 2 / len(post))
    line = 2.0154 * se * sqrt(max(var_ratio, 1.0))  # t(0.975, df=44), 분산비만큼 넓힘
    p_improve = NormalDist().cdf(diff / se)  # 평평한 사전분포 + 정규 근사
    return diff, line, p_improve

위 코드도 원리를 보여 주려고 다시 쓴 예시입니다. min_days 줄은 11월 4일에 직접 고친 부분이에요. 사후 3일째에 일별 sd가 우연히 0.32pp로 작게 나와서 웰치 판정선이 좁아졌습니다. 2일째에는 반대로 ±15.68pp까지 벌어졌어요. 짧은 창에서는 정규 가정이 버티지 못했고, 그 뒤로는 평일 10일 미만이면 판정선과 개선 확률을 아예 내지 않습니다.

이 설계는 다른 서비스의 기준선을 잡을 때도 그대로 옮겨 썼어요.

CPA는 건수로 쪼개서

CPA는 한쪽으로 꼬리가 긴 값이라 같은 방식이 통하지 않았습니다. 9월 이후로는 CPA를 변환하지 않고, 비용 ÷ 전환 건수로 쪼갰어요. 그리고 분포가 비교적 얌전한 건수 쪽만 포아송으로 다뤘습니다.

앞에서 적은 구글 증액 판정이 첫 사례였어요. 전환이 13건일 때 CPA 95% 구간은 상한이 하한의 3배를 넘었습니다.

10월에는 같은 계산을 테스트 설계에 썼습니다. 닷새짜리 테스트로 가입이 16건 안팎 나온다고 가정하면 CPA 구간이 2.8배 폭으로 벌어져요. 짧은 테스트로는 판정이 안 난다는 근거가 됐습니다.

11월 소재 판정은 p값 대신 확률로 했어요. 한 소재가 비교 소재보다 비쌀 확률이 약 96%, 1.3배를 넘을 확률은 약 67%였습니다(포아송 근사). 비싼 건 거의 확실하지만 크게 비싸다고 보기는 어려웠어요. 그래서 소재를 끄지 않고 예산을 반으로 줄였습니다.

다른 퍼널 개선에서는 두 기간을 포아송으로 비교해서 가입과 세션 건수로 판정선을 미리 정해 두었어요.

아직 안 해 본 것

돌아보면 집계 검정에서 날짜별 짝 검정으로, 다시 날 단위 고정으로 옮겨 왔습니다. 마지막에는 분산 진단과 MDE를 먼저 계산하고 판정 규칙을 미리 고정하는 데까지 왔어요.

빠진 것도 있습니다. 일별 비율이 정말 정규를 따르는지 Shapiro 같은 정규성 검정으로 확인해 본 적은 없어요. 비율 지표는 분산비와 최소 일수로 버텼고, CPA는 로그로 펴는 대신 건수만 포아송으로 다뤄 피해 갔습니다.

글쓴이YoonRain

광고·검색·측정을 직접 설계하고, 판정 기준을 먼저 정해 성과를 검증하는 그로스 마케터입니다. 이력 보기