AB테스트 유의성 계산, AI 판정을 z검정으로 다시 하기
방문·전환 건수만 주고 검정 이름·공식·중간값을 모두 적게 한 뒤, statsmodels 로 z 와 p 를 다시 계산해 AI 판정과 맞춰 봅니다. 표본 크기에 따라 결론이 어떻게 갈리는지도 확인합니다.
방문 100명씩, 전환 8건과 14건. 이 숫자를 AI 에 그대로 물으면 「유의하지 않다」는 답은 맞게 나옵니다. 걸리는 것은 답 옆에 붙어 나오는 숫자들입니다.
짧게 물었을 때
랜딩 페이지 A/B 테스트 결과입니다.A안: 방문 100, 전환 8B안: 방문 100, 전환 14B안이 유의하게 더 낫나요? 짧게 답해 주세요.아니요, 통계적으로 유의하지 않습니다. - 전환율은 A 8%, B 14%입니다. 차이는 6%p이고, 두 비율 z-검정에서 z ≈ 1.36, p ≈ 0.18입니다. 보통 기준인 0.05보다 훨씬 큽니다.- 표본이 각 100명이라 이 정도 차이는 우연으로도 충분히 나올 수 있습니다.- 6%p 차이(8%에서 14%)를 80% 검정력으로 확인하려면 안마다 대략 400명 이상이 필요합니다.…판정은 맞습니다. 그러나 필요 표본 수는 네 번(한국어 2·영어 2) 돌리는 동안 「400명 이상」 · 「400~450명」 · 「about 420」 · 「350 to 400」 으로 바뀌었고, 계산 과정은 한 번도 나오지 않았습니다.
건수와 규칙을 주는 프롬프트
{ } 자리에 원시 건수를 넣습니다. 전환율(%)이 아니라 방문 수와 전환 수를 줘야 검산할 수 있습니다.
아래 <data> 는 랜딩 페이지 A/B 테스트의 원시 건수입니다. B안이 A안보다 전환율이 통계적으로 유의하게 높은지 판정해 주세요. 규칙:- 사용할 검정 이름과 공식을 먼저 적습니다. 단측인지 양측인지도 밝힙니다. 유의수준은 0.05(양측)입니다.- 계산은 중간값을 모두 적습니다: 각 안의 전환율, 합동 비율, 표준오차, z, p. 소수 넷째 자리까지.- 결론은 계산을 모두 적은 뒤 맨 끝에 「유의함」 또는 「유의하지 않음」으로 쓰고, 이유를 두 문장 이내로. 계산하기 전에는 z·p 값을 적지 않습니다.- 표본 크기·기대 빈도로 보아 이 검정을 쓰기 어려우면 그렇다고 말합니다.- 데이터만으로 알 수 없는 것(방문이 고유 사용자인지, 테스트 기간·중간 확인 여부 등)은 추측하지 말고 「확인 필요」 목록으로 따로 적습니다. <data>A안: 방문 {A 방문}, 전환 {A 전환}B안: 방문 {B 방문}, 전환 {B 전환}</data>마지막 규칙은 Anthropic 의 환각 줄이기 안내가 권하는 대로, 모르는 것은 모른다고 말해도 된다고 미리 허락하는 줄입니다. 「결론은 맨 끝에」 는 처음엔 「결론부터」 였는데, 그 판으로 6번 돌려도 모델은 매번 계산을 먼저 썼습니다. 그래서 지시를 그 순서에 맞췄습니다.
실행
--setting-sources "" 는 개인 설정과 CLAUDE.md 를 읽지 않게 합니다(평소 지침이 섞이면 답의 언어·말투가 바뀝니다).
$ claude -p --model claude-sonnet-5-5 --max-turns 1 --tools "" --setting-sources "" --strict-mcp-config < prompt.big.txt > out.big.txt큰 표본(안마다 12,000명) 결과에서 계산표와 결론만 옮깁니다.
| A 전환율 = 540/12000 | 0.0450 || B 전환율 = 612/12000 | 0.0510 |…| 표준오차 = √(0.045696 × 0.00016667) = √(7.616×10⁻⁶) | 0.0028 (정확값 0.002760) || z = 0.0060 / 0.002760 | 2.1741 || p (양측) = 2 × (1 − Φ(2.1741)) | 0.0297 |…## 결론**유의함** — 양측 p = 0.0297로 α = 0.05보다 작아 H0(두 전환율이 같다)를 기각합니다. …작은 표본(100명씩)은 z = 1.3560, p = 0.1751, 「유의하지 않음」 이었습니다. 「확인 필요」 에는 두 세트 모두 고유 사용자 여부·무작위 배정·중간 확인(peeking)이 들어 있었습니다.
statsmodels 로 다시 계산
원시 건수에서 다시 시작합니다(가상환경에 statsmodels·scipy 설치). proportions_ztest 의 기본값이 합동 비율·양측이라 프롬프트 조건과 같습니다.
#!/usr/bin/env python3"""usage: ztest.py conv_a n_a conv_b n_b"""import sysfrom statsmodels.stats.proportion import proportions_ztestfrom scipy.stats import fisher_exactxa, na, xb, nb = map(int, sys.argv[1:5])z, p = proportions_ztest([xb, xa], [nb, na]) # pooled, two-sidedpool = (xa + xb) / (na + nb)se = (pool * (1 - pool) * (1 / na + 1 / nb)) ** 0.5_, pf = fisher_exact([[xb, nb - xb], [xa, na - xa]])print(f"rate A {xa/na:.4f} rate B {xb/nb:.4f} pooled {pool:.4f} se {se:.6f}")print(f"z {z:.4f} p {p:.4f} fisher p {pf:.4f}")세 번째 실행은 AI 에 묻지 않은 세트로, 전환율은 4.5% 대 5.1% 그대로 두고 방문만 2,000명으로 줄였습니다.
$ v/bin/python ztest.py 540 12000 612 12000rate A 0.0450 rate B 0.0510 pooled 0.0480 se 0.002760z 2.1741 p 0.0297 fisher p 0.0320$ v/bin/python ztest.py 8 100 14 100rate A 0.0800 rate B 0.1400 pooled 0.1100 se 0.044249z 1.3560 p 0.1751 fisher p 0.2582$ v/bin/python ztest.py 90 2000 102 2000rate A 0.0450 rate B 0.0510 pooled 0.0480 se 0.006760z 0.8876 p 0.3748 fisher p 0.4159AI 가 낸 z·p 는 두 세트 모두 넷째 자리까지 같았습니다. 같은 13% 개선이라도 2,000명씩이면 p 가 0.3748 입니다. 「유의함」 은 개선 폭이 아니라 표본 크기에서 갈립니다.
빗나가는 곳
- 반올림한 중간값으로 손 계산하기. 결과표의 표준오차 0.0028 로 0.0060 을 나누면 z 가 2.1429 가 됩니다. 한 번은 AI 도 답 안에 그렇게 경고했습니다. 검산은 늘 원시 건수부터 합니다.
- 필요 표본 수. 짧은 답의 400~450명 류 숫자는 근거가 없습니다.
NormalIndPower().solve_power(proportion_effectsize(0.14, 0.08), alpha=0.05, power=0.8)는 419.3 을 냅니다. 이 값이 필요하면 직접 계산합니다. - 믿으면 안 되는 경우. 방문 수가 세션 수이거나, 결과를 여러 번 들여다보다 좋은 날 멈췄거나, 변형안을 여럿 비교했다면 p 자체가 맞지 않습니다. 계산과 검산이 일치해도 드러나지 않는 문제라서 「확인 필요」 목록을 실제로 채운 뒤에 판정을 씁니다.