A/Btest:组间的差异性检验,统计功效以及反选样本量,附python底层实现代码
先概括一下:本文主要闡述了A/Btest中組間差異的比率檢驗(單比率檢驗,雙比率檢驗),統計功效,以及何通過顯著性水平還有統計功效反實驗所需選樣本量。使用python對著三個功能進行實現,并封裝成類,方便直接調用。如果A/B test中包含多組人群,可以兩兩進行比較,也可以直接利用方差分析判斷不同組間是否存在差異(方差分析建立在樣本獨立,正態分布和方差齊性假設上,但實際上隨機抽樣時,樣本獨立,方差分析中F檢驗對正態分布不敏感,且方差不差太多(2倍以上)方差分析的結果基本都可以認為是有效的)。
一、A/B test
在產品發布,運營等場景我們都會遇到A/B test。A/B test通常為同一個目標,設計兩種方案,將兩種方案隨機投放市場中。
A/B test讓組成成分相同(相似)用戶去隨機體驗兩種方案之一,根據觀測結果,判斷哪個方案效果更好,結果可以通過CTR或者下單率來衡量。最終我們選擇CTR或者下單率更優的版本作為線上應用的版本。
現實場景中我們避不開幾個問題:
上面3個問題對順利進行A/B test至關重要,在網上找到了一個很好的課件,還有一些具體的例子,非常容易理解。下面結合課件對這三個問題進行闡述。
關于三者的求解,本文利用python從底層進行了實現。其實網上有很多統計軟件。但是共感覺有點雜亂,于是照著原理從底層編寫,按照自己的方式編寫后感覺清爽了很多。
一、單比率檢驗
對于A/B test中兩組人群的對比中,我們需要對比的是ctr,轉化率等指標。而ctr,用戶轉化率等指標,都是01分布,即二項分布。因此可以使用比率檢驗的方法進行假設檢驗。如果A/B test中包含多組人群,可以兩兩進行比較,也可以直接利用方差分析組間差異的判斷。
1.1.單比率檢驗
現在有這樣一種情景,我們新發布了一個版本或新上了一個活動,并選了一批人進行試驗,我們想要知道發布了這個版本或新上活動后新的樣本是否和原來有明顯差異。我們可以使用單比率檢驗。(與平常假設檢驗無差別。構造統計量,看統計量是否在拒絕域內。正常是T統計量,這里由于是二項分布,n*p>5時可以認為是正態分布,即Z統計量)
接下來是單比率檢驗的一個簡單例子:
1.2.單比率檢驗的統計功效
在上面我們闡述了在顯著性水平α\alphaα下一組樣本的統計指標是否與原來存在差異的方法。在概率統計中,我們知道,假設檢驗中有兩類錯誤,第一類錯誤是“棄真”,即當零假設正確時,我們拒絕的概率,記為α\alphaα;第二類錯誤是“納偽”,即零假設錯誤時,我們卻沒有拒絕的概率記為β\betaβ。
由定義可知上面的α\alphaα和β\betaβ都是關于零假設的條件概率,實際上我們所說的顯著性水平對應的就是第一類錯誤概率α\alphaα。
現在假設我們再比較一組樣本與另一組是否存在差異時,我們拒絕了零假設,即認為兩組有差異,我們需要進一步知道我們正確拒絕了零假設的概率powerpowerpower,我們把這個概率叫做統計功效。
實際上統計功效powerpowerpower就是1-零假設錯誤時,我們卻沒有拒絕的概率(第二類錯誤概率)。即power=1?βpower=1-\betapower=1?β。
1.3. 反選樣本量
在實驗室,我們總會預先設定一個顯著性水平α\alphaα和統計功效β\betaβ。根據上面統計功效的公式,實際上我們可以反推出我們需要的樣本量:
二、雙比率檢驗
在A/B test是,我們同上的做法是在同一層試驗中,選用兩個或多個版本(活動)進行同時實現,此時我們需要比較兩組樣本的差異性。于是我們就需要用到雙比率檢驗
2.1.雙比率檢驗
下面是雙比率檢驗一個簡單的例子:
2.2.雙比例檢驗的統計功效
與前面所說的統計功效一樣,這里我們比較兩組樣本時的統計功效:
2.3. 雙比例檢驗反選樣本數
注意:下面例子的反求樣本數量的計算出現錯誤,讀者可以自行計算。我這里重新求出來下面兩個例子的結果是1782和15022。
實際上根據樣本的分布、是否相互獨立,方差是否已知等條件,反求樣本量的方法如下:
(上圖來自https://www.datasciencecentral.com/profiles/blogs/determining-sample-size-in-one-picture)
三、比率檢驗、統計功效以及反選樣本量的python實現
本文利用python從底層進行了實現,并針對課件上的例子進行了求解。
其實網上有很多統計軟件。但是共感覺有點雜亂,于是照著公式從底層編寫,按照自己的方式編寫后感覺清爽了很多,可以直接對接其他程序聯合使用。
# -*- coding: utf-8 -*- """ Created on Tue Mar 31 13:53:28 2019@author: nbszg """ import math as m import numpy as np import scipy.stats as stclass AB_test_ratio_test(object):def single_Z_test(self, p_theta, p_real, sample_n, alpha=0.05, method='two sides'):'''輸入參數,輸出樣本指標是否與原來(總體)相同p_theta:新樣本組(實驗組)的轉化率,點擊率等p_real:原來的的轉化率,點擊率等sample_n:新樣本組(實驗組)的樣本容量alpha:顯著性水平method:檢測區間,有'two sides'、'one sides larger'、'one sides smaller'三種,代表雙側,右側和左側檢驗return 是否可以拒絕原假設和統計量Z值'''# 構造Z統計量Z = (p_theta - p_real) / m.sqrt(p_real * (1 - p_real) / sample_n)# 我們總是希望拒絕H0!!!# H0:p_theta=p_real, H1:p_theta!=p_realif method == 'two sides':if abs(Z) > st.norm.ppf(1 - alpha / 2):return "Z is: {} , refuse H0, there's difference between p_theta and p_real".format(Z)else:return "Z is: {} ,can't refuse H0".format(Z)# H0:p_theta<=p_real, H1:p_theta>p_realelif method == 'one sides larger':if Z > st.norm.ppf(1 - alpha):return "Z is: {} , refuse H0, p_theta is larger than p_real".format(Z)else:return "Z is: {} , can't refuse H0".format(Z)# H0:p_theta>=p_real, H1:p_theta<p_realelif method == 'one sides smaller':if Z < st.norm.ppf(alpha):return "Z is: {} , refuse H0, p_theta is smaller than p_real".format(Z)else:return "Z is: {} , can't refuse H0".format(Z)else:raise ValueError("there's no method named: {0}".format(method))def single_power_cul(self, p_theta, p_real, sample_n, alpha=0.05, method='two sides'):'''輸入參數,輸出統計功效p_theta:新樣本組(實驗組)的轉化率,點擊率等p_real:原來的的轉化率,點擊率等sample_n:新樣本組(實驗組)的樣本容量alpha:顯著性水平method:檢測區間,有'two sides'、'one sides larger'、'one sides smaller'三種,代表雙側,右側和左側檢驗return 檢驗的統計功效power'''# 求總體sigmasigma_p = m.sqrt(p_real * (1 - p_real) / sample_n)# 求B組s_p = m.sqrt(p_theta * (1 - p_theta) / sample_n)#power of p_theta!=p_realif method == 'two sides':fai_right = 1 - st.norm.cdf((p_real - p_theta + st.norm.ppf(1 - alpha / 2) * sigma_p) / s_p)fai_left = st.norm.cdf((p_real - p_theta - st.norm.ppf(1 - alpha / 2) * sigma_p) / s_p)power = fai_right + fai_left#power of p_theta>p_realelif method == 'one sides larger':fai_right = 1 - st.norm.cdf((p_real - p_theta + st.norm.ppf(1 - alpha) * sigma_p) / s_p)power = fai_right#power of p_theta<p_realelif method == 'one sides smaller':fai_left = st.norm.cdf((p_real - p_theta - st.norm.ppf(1 - alpha) * sigma_p) / s_p)power = fai_leftelse:raise ValueError("there's no method named: {0}".format(method))return powerdef single_sample_n(self, p_theta, p_real, alpha=0.05, beta=0.9, method='two sides'):'''輸入參數,輸出統計功效p_theta:新樣本組(實驗組)的轉化率,點擊率等p_real:原來的的轉化率,點擊率等alpha:顯著性水平beta:想要到達的功效powerreturn 達到檢驗功效所需要的最小樣本量'''# 先求分母denominator = pow(2 * m.asin(m.sqrt(p_real)) - 2 * m.asin(m.sqrt(p_theta)), 2)# H0:p_theta=p_real, H1:p_theta!=p_realif method == 'two sides':numerator = pow(st.norm.ppf(1 - alpha / 2) + st.norm.ppf(beta), 2)sample_n = numerator / denominatorelif method == 'one sides larger' or method == 'one sides smaller':numerator = pow(st.norm.ppf(1 - alpha) + st.norm.ppf(beta), 2)sample_n = numerator / denominatorelse:raise ValueError("there's no method named: {0}".format(method))return sample_ndef two_Z_test(self, p_theta, p_gamma, sample_theta, sample_gamma, alpha=0.05, method='two sides', d=0):'''輸入參數,輸出兩個總體的轉化率,點擊率是否可以認為不同p_theta:樣本組1(對照組A)的轉化率,點擊率等p_gamma:樣本組2(實驗組B)的轉化率,點擊率等sample_theta:樣本組1(對照組A)樣本容量sample_gamma:樣本組2(實驗組B)樣本容量alpha:顯著性水平method:檢測區間,有'two sides'、'one sides larger'、'one sides smaller'三種,代表雙側,右側和左側檢驗d:樣本組1(對照組A)和樣本組2(實驗組B)的設定差異return 樣本組1(對照組A)和樣本組2(實驗組B)的轉化率,點擊率是否存在差異和統計量Z'''# 構造Z統計量Z = (p_theta-p_gamma-d) / m.sqrt((p_theta*(1-p_theta)/sample_theta) + (p_gamma*(1-p_gamma)/sample_gamma))# 我們總是希望拒絕H0!!!# H0:p_theta=p_gamma, H1:p_theta!=p_gammaif method == 'two sides':if abs(Z) > st.norm.ppf(1 - alpha / 2):return "Z is: {} , refuse H0, there's difference between p_theta and p_gamma".format(Z)else:return "Z is: {} , can't refuse H0".format(Z)# H0:p_theta<=p_real, H1:p_theta>p_gammaelif method == 'one sides larger':if Z > st.norm.ppf(1-alpha):return "Z is: {} , refuse H0, p_theta is larger than p_gamma".format(Z)else:return "Z is: {} , can't refuse H0".format(Z)# H0:p_theta>=p_real, H1:p_theta<p_gammaelif method == 'one sides smaller':if Z < st.norm.ppf(alpha):return "Z is: {} , refuse H0, p_theta is smaller than p_gamma".format(Z)else:return "Z is: {} , can't refuse H0".format(Z)else:raise ValueError("there's no method named: {0}".format(method))def two_power_cul(self, p_theta, p_gamma, sample_theta, sample_gamma, alpha=0.05, method='two sides', d=0):'''輸入參數,輸出兩個總體的差異檢驗的統計功效p_theta:樣本組1(對照組A)的轉化率,點擊率等p_gamma:樣本組2(實驗組B)的轉化率,點擊率等sample_theta:樣本組1(對照組A)樣本容量sample_gamma:樣本組2(實驗組B)樣本容量alpha:顯著性水平method:檢測區間,有'two sides'、'one sides larger'、'one sides smaller'三種,代表雙側,右側和左側檢驗d:樣本組1(對照組A)和樣本組2(實驗組B)的設定差異return 樣本組1(對照組A)和樣本組2(實驗組B)的差異檢驗的統計功效power'''sigma_denominator = m.sqrt((p_theta*(1-p_theta)/sample_theta) + (p_gamma*(1-p_gamma)/sample_gamma))p_avg = (p_theta+p_gamma)/2sigma_numerator = m.sqrt(2*p_avg*(1-p_avg)/((sample_theta+sample_gamma)/2))#power of p_theta!=p_gamma + dif method == 'two sides':fai_right = 1 - st.norm.cdf((p_gamma + d - p_theta + st.norm.ppf(1-alpha/2) * sigma_numerator) / sigma_denominator)fai_left = st.norm.cdf((p_gamma + d - p_theta - st.norm.ppf(1-alpha/2) * sigma_numerator) / sigma_denominator)power = fai_right + fai_left#power of p_theta>p_gamma + delif method == 'one sides larger':fai_right = 1 - st.norm.cdf((p_gamma + d - p_theta + st.norm.ppf(1-alpha) * sigma_numerator) / sigma_denominator)power = fai_right#power of p_theta<p_gamma + delif method == 'one sides smaller':fai_left = st.norm.cdf((p_gamma + d - p_theta - st.norm.ppf(1-alpha) * sigma_numerator) / sigma_denominator)power = fai_leftelse:raise ValueError("there's no method named: {0}".format(method))return powerdef two_sample_n(self, p_theta, p_gamma, alpha=0.05, beta=0.9, method='two sides'):'''輸入參數,輸出兩個總體的差異檢驗的統計功效p_theta:樣本組1(對照組A)的轉化率,點擊率等p_gamma:樣本組2(實驗組B)的轉化率,點擊率等alpha:顯著性水平beta:想要到達的功效powermethod:檢測區間,有'two sides'、'one sides larger'、'one sides smaller'三種,代表雙側,右側和左側檢驗d:樣本組1(對照組A)和樣本組2(實驗組B)的設定差異return 每個樣本組的所需的最小樣本數'''# 先求分母denominator = pow(2 * m.asin(m.sqrt(p_gamma)) - 2 * m.asin(m.sqrt(p_theta)), 2)# H0:p_theta=p_real, H1:p_theta!=p_realif method == 'two sides':numerator = 2 * pow(st.norm.ppf(1 - alpha/2) + st.norm.ppf(beta), 2)sample_n = numerator / denominatorelif method == 'one sides larger' or method == 'one sides smaller':numerator = 2 * pow(st.norm.ppf(1 - alpha) + st.norm.ppf(beta), 2)sample_n = numerator / denominatorelse:raise ValueError("there's no method named: {0}".format(method))return sample_nif __name__=='__main__':ratio_test = AB_test_ratio_test()print('example1 Z test is:', ratio_test.single_Z_test(0.018, 0.02,sample_n=500, method='two sides'),'\n')print('example1 power is:', ratio_test.single_power_cul(0.018, 0.02,sample_n=500, method='two sides'),'\n')print('example2 need sample is:', ratio_test.single_sample_n(0.018, 0.02, method='two sides'),'\n')print('example3 Z test is:', ratio_test.two_Z_test(0.18, 0.2,sample_theta=1600,sample_gamma=2000, method='two sides'),'\n')print('example4 Z test is:', ratio_test.two_Z_test(0.18, 0.2,sample_theta=1600,sample_gamma=2000, method='two sides'),'\n')print('example5 Z test is:', ratio_test.two_Z_test(0.025, 0.007,sample_theta=3000,sample_gamma=3000, method='one sides larger', d=0.005),'\n')print('example5 power is:', ratio_test.two_power_cul(0.025, 0.007,sample_theta=3000,sample_gamma=3000, method='one sides larger'),'\n')print('example6 power is:', ratio_test.two_power_cul(0.025, 0.007,sample_theta=3000,sample_gamma=3000, method='one sides larger', d=0.005),'\n')print('example7 need sample is:', ratio_test.two_sample_n(0.025, 0.012, method='one sides larger'),'\n')print('example8 need sample is:', ratio_test.two_sample_n(0.025, 0.02, method='one sides larger'),'\n')程序運行結果如下:
對比課件中的結果,除去計算精度誤差,結果可以認為與課件中是相等的。
參考文獻:
假設檢驗與樣本數量分析④——單比率檢驗、雙比率檢驗
總結
以上是生活随笔為你收集整理的A/Btest:组间的差异性检验,统计功效以及反选样本量,附python底层实现代码的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于DAC的原理
- 下一篇: 图像常用的插值算法:最近邻插值、双线性插