حاسبة أهمية اختبار A/B
تحقق من أن نتيجة اختبار A/B الخاص بك ذات دلالة إحصائية. أدخل الزوار والتحويلات للحصول على قيمة p، درجة z، فترة الثقة، والرفع النسبي.
حاسبة أهمية اختبار A/B
أدخل الزوار والتحويلات لكل متغير للتحقق من وجود فرق حقيقي أم مجرد ضوضاء.
النتائج
ذات دلالة إحصائية
من غير المرجح أن يكون الفرق بسبب الصدفة بمستوى الثقة المختار.
Chance this gap is just luck — lower is stronger.
How far apart the two rates are, in standard errors.
The likely range for the true difference.
التوثيق
اختبار أ/ب في دقيقة واحدة
غيّرت زرًا أو عنوانًا أو سير الدفع. الإصدار أ هو القديم، والإصدار ب هو الجديد. يبدو أن ب أفضل — حوّل 15% من الزوار مقابل 10% للإصدار أ. لكن هل يمكن أن يكون هذا الفرق مجرد صدفة، مثل رمي عملة عشر مرات والحصول على سبع وجوه؟
توفر هذه الحاسبة الإجابة بالضبط. تدخل عدد الأشخاص الذين رأوا كل إصدار وعدد من قاموا بالتحويل، وتخبرك بالاحتمالية بأن الفرق الذي قسته حقيقي بدلاً من ضوضاء عشوائية. إذا كان هذا الاحتمال مرتفعًا بما يكفي، فلديك فائز. إذا لم يكن كذلك، فتابع الاختبار.
الرقم الوحيد الذي يجب الانتباه له هو قيمة p: فرصة رؤية فجوة على الأقل بهذا الحجم إذا كان الإصدارتان متطابقتين فعلاً. تعني قيمة p الصغيرة (على سبيل المثال، أقل من 0.05) "هذا نادراً ما يحدث بالصدفة، لذا الفرق محتمل أن يكون حقيقياً."
كيفية الاستخدام
- الزوار — عدد الأشخاص الفريدين الذين تم عرض كل إصدار عليهم.
- التحويلات — كم عدد الأشخاص الذين فعلوا الشيء الذي تهتم به (اشتروا أو اشتركوا أو نقروا).
- مستوى الثقة — ما مدى تأكدك قبل إعلان الفائز. 95% هو المعيار الصناعي.
- الفرضية — ثنائية الطرف تسأل "هل ب مختلف عن أ؟" (أكثر أماناً، الافتراضي)؛ أحادية الطرف تسأل فقط "هل ب أفضل من أ؟" (أكثر حساسية، لكن يجب أن تقرر الاتجاه قبل تشغيل الاختبار).
تتحدث النتيجة مباشرة ويتم تخزين المدخلات في عنوان URL للصفحة، لذا يمكنك حفظ النتيجة أو مشاركتها. إعادة تعيين مسح النموذج؛ تحميل نموذج البيانات يملأ مثال عملي.
مثال عملي
| الزوار | التحويلات | المعدل | |
|---|---|---|---|
| أ (التحكم) | 1,000 | 100 | 10.0% |
| ب (المتغير) | 1,000 | 150 | 15.0% |
هذا رفع مطلق بنسبة 5 نقاط مئوية وأ 50% رفع نسبي. تحسب الحاسبة قيمة p حوالي 0.0007 — أقل من 0.05 — لذا بثقة 95% النتيجة ذات دلالة إحصائية. تقريباً، إذا كان الإصدارتان متطابقتان فعلاً، كنت ستشهد فجوة بهذا الحجم أقل من مرة واحدة من كل ألف اختبار.
الرياضيات (اختبار z ثنائي النسبة)
تحت الغطاء، هذا هو اختبار z ثنائي النسبة القياسي، الاختبار نفسه الذي يتم تدريسه في الإحصائيات التمهيدية وتستخدمه أدوات A/B السائدة.
-
معدل التحويل لكل مجموعة:
حيث هو التحويلات و هو الزوار.
-
المعدل المجمع، بافتراض (بالنسبة للفرضية الصفرية) أن كلا المجموعتين تتشاركان معدل حقيقي واحد:
-
الخطأ المعياري للفرق:
-
درجة z — عدد الأخطاء المعيارية التي تفصل بين المعدلين:
-
تأتي قيمة p من التوزيع الطبيعي المعياري . ثنائية الطرف: . أحادية الطرف: .
-
ذات دلالة عندما ، حيث (إذن 0.05 بنسبة 95%).
تحسب الحاسبة أيضًا فترة الثقة للفرق، المحسوبة بالخطأ المعياري غير المجمع : النطاق المعقول للفجوة الحقيقية. إذا استبعدت هذه الفترة 0، فالنتيجة ذات دلالة.
بثقة 95%، تعتبر درجة z التي تتجاوز ±1.96 (الذيول المظللة، 5% من المساحة) ذات دلالة إحصائية.
قراءة النتيجة بصراحة
- الدلالة الإحصائية ليست دلالة تجارية. يمكن أن يكون رفع 0.1% حقيقياً إحصائياً لكن لا يستحق النشر.
- لا تراقب وتتوقف مبكراً. التحقق من قيمة p يومياً والتوقف اللحظة التي تنخفض أقل من 0.05 يضخم معدل إيجابياتك الكاذبة. حدد حجم العينة مقدماً واترك الاختبار ينتهي.
- راقب قاعدة حجم العينة. التقريب الطبيعي يكون موثوقاً عندما تمتلك كل مجموعة ما لا يقل عن ~10 تحويلات و ~10 عدم تحويلات. مع الأرقام الصغيرة، تعامل مع النتيجة كاتجاهية فقط.
- الثقة ≠ احتمال أن ب أفضل. مستوى ثقة 95% يعني أن الإجراء مخطئ في الحد الأقصى 5% من الوقت على المدى الطويل — إنها بيان تكراري، وليس "95% فرصة أن ب يفوز."
- اختبار واحد، مقياس واحد. اختبار عدة مقاييس أو متغيرات في نفس الوقت يضاعف فرصة خطأ؛ صحح له (على سبيل المثال Bonferroni) أو سجل المقياس الوحيد الذي يقرر الاختبار.
احسبها بنفسك
يحسب كل مقطع قيمة p ثنائية الطرف للمثال العملي أعلاه.
1=2*(1-NORM.S.DIST(
2 (B2/A2 - B1/A1) /
3 SQRT(((B1+B2)/(A1+A2))*(1-(B1+B2)/(A1+A2))*(1/A1+1/A2)),
4 TRUE))
5' A1,B1 = control visitors, conversions ; A2,B2 = variation visitors, conversions
61from math import sqrt, erf
2
3def ab_pvalue(n1, x1, n2, x2, two_sided=True):
4 p1, p2 = x1 / n1, x2 / n2
5 p = (x1 + x2) / (n1 + n2)
6 se = sqrt(p * (1 - p) * (1 / n1 + 1 / n2))
7 z = (p2 - p1) / se
8 phi = lambda v: 0.5 * (1 + erf(v / sqrt(2))) # standard normal CDF
9 return 2 * (1 - phi(abs(z))) if two_sided else 1 - phi(z)
10
11print(ab_pvalue(1000, 100, 1000, 150)) # ~0.00072
121// erf via Abramowitz & Stegun 7.1.26
2function erf(x) {
3 const s = x < 0 ? -1 : 1;
4 x = Math.abs(x);
5 const t = 1 / (1 + 0.3275911 * x);
6 const y = 1 - ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t
7 - 0.284496736) * t + 0.254829592) * t * Math.exp(-x * x);
8 return s * y;
9}
10const phi = z => 0.5 * (1 + erf(z / Math.SQRT2));
11
12function abPValue(n1, x1, n2, x2, twoSided = true) {
13 const p1 = x1 / n1, p2 = x2 / n2, p = (x1 + x2) / (n1 + n2);
14 const se = Math.sqrt(p * (1 - p) * (1 / n1 + 1 / n2));
15 const z = (p2 - p1) / se;
16 return twoSided ? 2 * (1 - phi(Math.abs(z))) : 1 - phi(z);
17}
18console.log(abPValue(1000, 100, 1000, 150)); // ~0.00072
191public static double abPValue(int n1, int x1, int n2, int x2) {
2 double p1 = (double) x1 / n1, p2 = (double) x2 / n2;
3 double p = (double) (x1 + x2) / (n1 + n2);
4 double se = Math.sqrt(p * (1 - p) * (1.0 / n1 + 1.0 / n2));
5 double z = (p2 - p1) / se;
6 // Phi via the error function; erf approximated (A&S 7.1.26) separately.
7 double phi = 0.5 * (1 + erf(Math.abs(z) / Math.sqrt(2)));
8 return 2 * (1 - phi);
9}
101static double AbPValue(int n1, int x1, int n2, int x2) {
2 double p1 = (double)x1 / n1, p2 = (double)x2 / n2;
3 double p = (double)(x1 + x2) / (n1 + n2);
4 double se = Math.Sqrt(p * (1 - p) * (1.0 / n1 + 1.0 / n2));
5 double z = (p2 - p1) / se;
6 double phi = 0.5 * (1 + Erf(Math.Abs(z) / Math.Sqrt(2)));
7 return 2 * (1 - phi);
8}
91#include <cmath>
2
3double ab_pvalue(int n1, int x1, int n2, int x2) {
4 double p1 = (double)x1 / n1, p2 = (double)x2 / n2;
5 double p = (double)(x1 + x2) / (n1 + n2);
6 double se = std::sqrt(p * (1 - p) * (1.0 / n1 + 1.0 / n2));
7 double z = (p2 - p1) / se;
8 double phi = 0.5 * (1 + std::erf(std::fabs(z) / std::sqrt(2.0)));
9 return 2 * (1 - phi);
10}
111import "math"
2
3func abPValue(n1, x1, n2, x2 float64) float64 {
4 p1, p2 := x1/n1, x2/n2
5 p := (x1 + x2) / (n1 + n2)
6 se := math.Sqrt(p * (1 - p) * (1/n1 + 1/n2))
7 z := (p2 - p1) / se
8 phi := 0.5 * (1 + math.Erf(math.Abs(z)/math.Sqrt2))
9 return 2 * (1 - phi)
10}
111// erf from the `libm` crate; std has no erf.
2fn ab_pvalue(n1: f64, x1: f64, n2: f64, x2: f64) -> f64 {
3 let (p1, p2) = (x1 / n1, x2 / n2);
4 let p = (x1 + x2) / (n1 + n2);
5 let se = (p * (1.0 - p) * (1.0 / n1 + 1.0 / n2)).sqrt();
6 let z = (p2 - p1) / se;
7 let phi = 0.5 * (1.0 + libm::erf(z.abs() / 2f64.sqrt()));
8 2.0 * (1.0 - phi)
9}
101function ab_pvalue($n1, $x1, $n2, $x2) {
2 $p1 = $x1 / $n1; $p2 = $x2 / $n2;
3 $p = ($x1 + $x2) / ($n1 + $n2);
4 $se = sqrt($p * (1 - $p) * (1 / $n1 + 1 / $n2));
5 $z = ($p2 - $p1) / $se;
6 $phi = 0.5 * (1 + erf(abs($z) / sqrt(2)));
7 return 2 * (1 - $phi);
8}
91def ab_pvalue(n1, x1, n2, x2)
2 p1, p2 = x1.to_f / n1, x2.to_f / n2
3 p = (x1 + x2).to_f / (n1 + n2)
4 se = Math.sqrt(p * (1 - p) * (1.0 / n1 + 1.0 / n2))
5 z = (p2 - p1) / se
6 phi = 0.5 * (1 + Math.erf(z.abs / Math.sqrt(2)))
7 2 * (1 - phi)
8end
91# R has this built in: prop.test does the whole thing.
2prop.test(c(100, 150), c(1000, 1000), correct = FALSE)$p.value # ~0.00072
3الأسئلة الشائعة
ما حجم العينة الذي أحتاجه؟ ما يكفي بحيث يكون الرفع المعني قابلاً للكشف. كأساس، اهدف إلى ما لا يقل عن بضع مئات التحويلات لكل متغير؛ استخدم حاسبة حجم عينة مخصصة مع معدل الأساس والرفع الأصغر الذي يستحق الكشف.
ثنائي الطرف أم أحادي الطرف؟ استخدم ثنائي الطرف ما لم يكن لديك سبب قوي مقدم للعناية فقط بالتحسين. يضاعف الطرف الواحد حساسيتك لكنه يحظر الرد على أن ب أسوأ.
لماذا تعرض الحاسبة فترة ثقة؟ تعرض النطاق المعقول للفرق الحقيقي، ليس فقط حكم نعم/لا. فترة واسعة تمس 0 تعني "لا توجد بيانات كافية بعد."
هل هذا بايزي؟ لا — إنه اختبار z تكراري، الطريقة الأكثر تدريسًا واستخداماً. تقارير أدوات A/B بايزية "احتمالية أن ب يتغلب على أ" بدلاً من قيمة p؛ كلاهما صحيح، يجيب على أسئلة مختلفة قليلاً.