0
$\begingroup$

I'm testing impact of a new flow on website sales performance. It is pretty straightforward to test impact on

  • conversion rate (number of sessions ending with transaction divided by total number of sessions) - binomial approximated by z-test
  • and average order value (total revenue divided by the number of sessions ending with transaction) - t-test.

The tricky part is testing average revenue per session as we end up with zero-inflated distribution. Problem is not at all unique, so I found many approaches: hurdle models, zero inflated poisson/negbinomial, two-part model, bootstrapping and bayesian angle.

Do you have any suggestions which method will be reliable in practice? To ground the problem, let's assume that conversion rate is 60%, and order value is uniformly distributed between \$40 and \$80.

$\endgroup$
4
  • $\begingroup$ Hurdle models would be good if you want to estimate the hurdle probability. The same is true of zero inflated models. What is your hesitation towards just taking revenue per session data, imputing 0 for those sessions without a purchase, and testing your statistical hypothesis on those data? $\endgroup$ Commented Aug 28 at 19:52
  • $\begingroup$ Adding a spike ad zero increases the variance a lot and makes experiment insensitive. $\endgroup$
    – mikowai
    Commented Aug 28 at 20:08
  • $\begingroup$ Is this an A/B test on website design? Does everyone have the same number of sessions? Does the business ultimately just want to know if they get more revenue no matter what the intermediate steps, or do they want to know the session frequency, conversion rate, & order value separately? $\endgroup$ Commented Aug 28 at 20:28
  • $\begingroup$ Session is the randomization unit in this case. Both conversion rate and average transaction value are important, but when impact on them has an opposite sign, revenue per session must be looked at. And building confidence intervals aground that metric is non-trivial, hence the question here. $\endgroup$
    – mikowai
    Commented Aug 28 at 21:04

1 Answer 1

0
$\begingroup$

In what follows, please remember that I am showing an approach, not the approach. The approach depends on assumptions, much of which I can not validate (nor do I have the wish to validate thoroughly since doing so might require I border on consulting, which I do not wish to do frankly).

Do you have any suggestions which method will be reliable in practice?

Assuming for now that there is no correlation between sessions from the same user (almost certainly and demonstrably false, but we can return to this in a moment), and that number of sessions per user is independent of exposure, and that average revenue per converted session is independent of conversion rate in the prior model, I think a Bayesian hurdle model is a good first start.

Why hurdle? The revenue observation is only observed once the user "jumps the hurdle" of intending to purchase. So there are two distinct processes to model: the conversion probability, and the amount of revenue obtained from the session. As you allude to in one of your comments, a decrease in conversion rate can be made up for by a suitable increase in revenue, and vice versa.

Why Bayesian? Two reasons: i) Model checks are going to be very important here because revenue data can behave in pathological ways, and ii) We can use the model based approach as described in Imbens and Rubin's "Causal Inference for statistics, social, and medical sciences".

Model

Suppose that the total revenue (we'll get to revenue per session) in arm $k$ is given by $y_k$, and that in arm $k$ a total $n_k$ of $N_k$ sessions result in a purchase. Then we can model $y_k$ as

$$ y_k \sim \operatorname{Binomial}(N_k, \theta_k) \times \operatorname{Gamma}(n_k, 1/\lambda_k) $$

with appropriate priors. Here:

  • $\theta_k$ is the rate at which sessions result in a purchase.
  • The gamma distribution is parameterized in terms of shape and rate, so that the expected total revenue from sessions resulting in a purchase is $n_k \lambda_k$.
  • This means $\lambda_k$ is interpreted as the expected revenue from a single converted session.

How can we use this to determine the average revenue per session? As mentioned, we can use the model based approach from Imbens and Rubin. In short, we can

  • Assume we observe some amount of new sessions.
  • Use the model to estimate counterfactual revenue for each session
  • Estimate the mean revenue in each session, and
  • Average our estimates over many MCMC draws from the posterior.

Simulation

Let's simulate some simple data in R. I'll generate 1 million sessions from two hypothetical populations -- one where all are treated, and one where all are control. Then, I will sample 100, 000 sessions from these populations. The conversion rate $\theta_k$ is the same in both populations, but the expected revenue from a converted session is higher by \$2 (so uniform between 42 and 82) in the treated population. I'm going to summarize the data so that we know: How many users did not convert, how many did convert, and what the total revenue from the converted sessions was.

library(tidyverse)
library(cmdstanr)
#> This is cmdstanr version 0.7.1
#> - CmdStanR documentation and vignettes: mc-stan.org/cmdstanr
#> - CmdStan path: /Users/demetripananos/.cmdstan/cmdstan-2.34.1
#> - CmdStan version: 2.34.1
#> 
#> A newer version of CmdStan is available. See ?install_cmdstan() to install it.
#> To disable this check set option or environment variable CMDSTANR_NO_VER_CHECK=TRUE.
library(tidybayes)

set.seed(0)
N <- 1000000
pop1 <- runif(N, 40, 80) * rbinom(N, 1, 0.6)
pop2 <- runif(N, 42, 82) * rbinom(N, 1, 0.6)

n <- 100000
x1 <- tibble(arm = "A", y = sample(pop1, size=n))
x2 <- tibble(arm = "B", y = sample(pop2, size=n))
d <- bind_rows(x1, x2)

md <- d %>% 
     mutate(paid = if_else(y==0, 0, 1),
            arm = factor(arm)) %>% 
     group_by(arm) %>% 
     summarise(
       Nn = sum(y>0),
       y = sum(y),
       N = n(), 
     )



md
#> # A tibble: 2 × 4
#>   arm      Nn        y      N
#>   <fct> <int>    <dbl>  <int>
#> 1 A     60074 3607691. 100000
#> 2 B     59768 3707771. 100000

Created on 2024-08-29 with reprex v2.0.2

You can verify that B has more revenue per session at about \$37 per session (which happens to be the average revenue per converting session -- \$60 -- multiplied by the conversion probability -- 60%).

Now, we will write a stan model to estimate the parameters from the data and do the simulation. The model looks like


stan_code <- '
data{
  int n;
  int n_arm;
  array[n] int Nn;
  array[n] int arm;
  array[n] real y;
  array[n] int N;
  int ng;
  
}
parameters{
  // Spending rate per session
  array[n_arm] real <lower=0> lambda;
  
  // Probability of spending
  array[n_arm] real <lower=0, upper=1> theta;
}
model{
  target += cauchy_lpdf(lambda|0, 1);
  target += beta_lpdf(theta | 1, 1);
  for(i in 1:n){
  target += binomial_lpmf( Nn[i]| N[i], theta[arm[i]]);
  target += gamma_lpdf(y[i]| Nn[i], lambda[arm[i]]);
  }
    
}

generated quantities{
  
  array[n_arm] real rev_per_session;
  
  {
  
  array[n_arm, ng] real yppc;
    
    for(i in 1:n_arm){
      for(j in 1:ng){
        yppc[i, j] = gamma_rng(1.0, lambda[i]) * binomial_rng(1, theta[i]);
      }
      rev_per_session[i] = sum(yppc[i, ]);
    }
  }

}

'

We'll now fit the model to the data

model <- stan_code %>% 
         write_stan_file() %>% 
         cmdstan_model()


stan_data <- compose_data(d) 
stan_data$ng <- n


fit <- model$sample(stan_data, parallel_chains = 4)

We can check that the model produces reasonable estimates with some other posterior predictive checks. Here, I will plot the estimated total revenue from each of the 100,000 sessions for B. The vertical line indicates the observed value, while the histogram are the model predictions. I will leave you to determine if (and in what way) the model predictions are deficient, but for now they predict that the observed revenue per session of 37 is among the most predicted quantities.


yppc <- posterior::as_draws_matrix(fit$draws('rev_per_session'))



bayesplot::ppc_stat(
  filter(md, arm=='B')$y/n, 
  yppc[, 2]/n
  )

enter image description here

Decisions

How do we decide which arm is better? There is a whole literature on how do make decisions with Bayesian models. Rather than repeat myself, I will point you to a previous answer here.

Limitations of this model

I began this answer by saying that this is "an" approach, not "the" approach. This model makes a lot of assumptions, including:

  • Treatment does not affect how many sessions a user has (perhaps a harmless assumption)
  • Sessions are independent of one another (dubious), and
  • Revenue and conversion rate are independent of one another in the prior model.

Each of these could rightfully be argued against -- especially the correlation. Your question was about "which method [would] be most reliable in practice" and my short answer is that "each method is as reliable as the assumptions it makes". If you can make all three assumptions above, you could ostensibly bootstrap your data -- which is a hell of a lot easier than all this.

However, if you want to relax any of these then a model is probably the best way to go. For example, relaxing the correlation assumption could be handled through a multilevel model approach, where each user gets a random effect in $\theta_k$ and $\lambda_k$. However, the determination of which arm is "better" also gets more complicated (but doable if you're really committed to it).

Why Such a Long Answer

My intent here with such a long answer is to give you some options. If the three assumptions I list are defensible, then you basically have a Bayesian model ready to go (although you could just as easily take another option). If any one of the three are not defensible -- and two of them should not be -- then you have a very good base from which to start, and I trust you could potentially extend this answer to suit your needs.

$\endgroup$
1
  • $\begingroup$ Wow, that's a super in-depth answer, thanks a lot! I must admit, I was considering MCMC as an option of last resort (I'm the only one of my team with background in MCMC) and had hoped that someone came up with a miraculous trick for a similar scenario. I'll give this question two more days, and if nothing better appears, I'll accept the answer - I think it's a very nice example of bayesian approach. $\endgroup$
    – mikowai
    Commented Aug 29 at 13:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.