A Formula for Bayesian A/B Testing
evanmiller.org
evanmiller.org
A pretty good shortcut (if you don't have a log-beta function, for example) is to approximate both A and B with the normal distribution. Then their difference is also normal, so you can just check the probability that a normal random variable is greater than zero.
Specifically, the mean μ of a beta distribution is α/(α+β) and the variance σ^2 is αβ/((α+β)^2(α+β+1)). Use these as the parameters for your normal approximation, and we have the difference D ~ N(μ_A-μ_B, σ_A^2+σ_B^2). The probability that B beats A is just the CDF of D evaluated at 0.
In Python:
from scipy.stats import norm as norm
def beta_mean(a, b):
return a/(a+b)
def beta_var(a, b):
return a*b/((a+b)**2*(a+b+1))
def probability_B_beats_A(α_A, β_A, α_B, β_B):
mu = beta_mean(α_A, β_A) - beta_mean(α_B, β_B)
sigma = (beta_var(α_A, β_A) + beta_var(α_B, β_B))**.5
return norm.cdf(0, mu, sigma)http://www.mdanderson.org/education-and-research/departments... [pdf]
But he doesn't really say what that closed form is, so I think his version must have been pretty hairy. (My version requires all four parameters to be integers, so I doubt we were talking about the same thing.)
Sadly I couldn't get the math to work out for producing a confidence interval on |p_B - p_A| so for now you're stuck with Monte Carlo for confidence bounds.
Thanks to prodding from Steven Noble over at Stripe, I'll have another formula up soon for asking the same question using count data instead of binary data. Stay tuned!
I'm not sure what you mean by Monte Carlo for confidence bounds - is there some reading I can do to brush up on that?
edit: here is your solution implemented in Ruby as I implemented it https://gist.github.com/nickelser/6dfa0f2737c502dae267
For example, given you know probabilities for A and B (say 0.5 and 0.4) and that number of trials has been 100, you can simply run thousands of simulations, know what the conversion rates come out to be in respective simulations and aggregate the difference in conversion rates at the end of each simulation. That would be a confidence bound.
This must be coming from the likelihood function, so you may want to be more specific about the exact experiment you have in mind. I'm not sure off the top of my head whether imposing independence is going to overstate or understate the precision of the estimate...
The 'non-informative' distribution can be interpreted as having no special information about the prior information of theta, so you just take theta uniform over [0, 1]. This is identical to the Beta(1, 1) distribution. Thus, you obtain the update equations @EvanMiller derived, while making it more clear how to generalize to the informative prior case.
In general though, I would have thought one would care more about distributional properties of the posterior p_B - p_A, which are more easily computed by MC methods - the closed form solution for P(p_B > p_A) is nice and fast, but it's not like hypothesis testing is in the critical path of a program, so I wouldn't shy away from MC methods here for a more nuanced view of your posteriors.
Something about the right side of the equation immediately preceding this quote seems to indicate that many of the terms in the numerator would cancel with equivalents in the denominator. I'm not really familiar with CAS systems, but is this the sort of thing they could do? Doing this simplification once when one writes the code seems to be a win over calculating the original expression every time the code runs.
Not really worth it unless this is in an inner-loop.
Edit: I did some Googling and found this http://www.stat.columbia.edu/~gelman/research/unpublished/mu...
If you haven't read it, Gelman and Hill's book is excellent: http://www.stat.columbia.edu/~gelman/arm/ and so is Gelman's blog.
That section should provide two or three lists of example input values, and the expected output value (up to some accuracy).
As the author notes, although this formula looks pretty simple, you can make a lot of numerical mistakes when implementing it. A test suite would help implementors to catch those mistakes early.
5.778 evaluation.py:20(evaluate) # Sampling version
0.043 evaluation.py:64(evaluate) # Closed formula
(See my A/B testing library https://github.com/bogdan-kulynych/trials)PrBbeatsA[\[Alpha]a_, \[Beta]a_, \[Alpha]b_, \[Beta]b_] := \!\( \UnderoverscriptBox[\(\[Sum]\), \(i = 0\), \(\[Alpha]b\)] \FractionBox[\(Beta[\[Alpha]a + i, \[Beta]b + \[Beta]a]\), \(\((\[Beta]b + i)\) Beta[ 1 + i, \[Beta]b] Beta[\[Alpha]a, \[Beta]a]\)]\)