I believe that you are sampling the wrong distribution to calculate a p-value. Your sampling approach could be used to calculate the confidence interval (if you sample the same number of observations as the data, rather than a subset).
To calculate a p-value, you need to sample from the null distribution. In this case, the null hypothesis is no difference between the treatments, so you need to sample from that distribution. To do so, simply combine the two sets of observations, sample from that, and compare the outcome metric of interest against the observed data.
Here are some sample data (note that I modified the distribution of the non-zero data a bit):
set.seed(8675309)
A <-
c(rep(0, 3020)
, sample(20:100, 700
, replace = TRUE
, prob = 10000 - 20:100
))
B <-
c(rep(0, 2811)
, sample(20:100, 700
, replace = TRUE
, prob = 10000 + 20:100
))
We can calculate the observed difference in the means:
obs_diff <-
mean(B) - mean(A)
For my sample, the difference is 0.95.
Then, we combine the A and B vectors to create the null distribution (that the treatment had no effect) and sample with replacement from that. Note that the sample sizes must match the data sample sizes for this to be valid.
all_values <- c(A, B)
n_rep <- 10000
null_outcomes <-
sapply(1:n_rep, function(idx){
mean(sample(all_values, length(B), TRUE)) -
mean(sample(all_values, length(A), TRUE))
})
Then, we see how often the observed data are as extreme as (or more extreme than) the null-distribution results.
mean(obs_diff >= null_outcomes)
For mine, it was as (or more) extreme than 93.97% of the null results. We can convert this to a two-tailed p-value by subtracting from 1 and doubling (skip the doubling if you are justifying a one-tailed test).
2 * (1 - mean(obs_diff >= null_outcomes) )
So, my sample data have a p-value of 0.12.
An important note: this p-value is very, very close to that calculated in a t-test of the raw data (t.test(A, B)$p.value
gives 0.121). Even with skewed data, the t-test is reasonably robust when the sample size is this large. Simulation is still slightly better (it will catch the occasional weird cases), but it should give pretty similar results when the sample size is this large. More importantly, simulation allows you to use metrics other than the mean, if you need to (e.g., if the median is more important, for some reason).
t = -4.8958, df = 1986.8, p-value = 1.058e-06
mean of x mean of y 12.40257 12.72635
$\endgroup$pwr::pwr.t.test
and the results have enough power and significance $\endgroup$