4
$\begingroup$

I'm trying to understand if there is any difference to the following approaches in permutating values based on their frequencies.

Here is an example of the raw data, which gets binned by hist:

# generate original data
set.seed(1)
n <- 100
x <- 20 * rlnorm(n, mean=0, sd=0.2)
h <- hist(x)

enter image description here

Now, I am sampling from these binned mid values given their frequencies. The first approach extends a vector first, and then samples with replacement. The second approach used the frequency information in the prob argument:

# approach 1 - extend value vector by frequencies and then sample with replacement
xs <- rep(h$mids, h$counts)
x1 <- sample(xs, replace = TRUE)

# approach 2 - sample values based on weighted probability with replacement
x2 <- sample(h$mids, size = sum(h$counts), prob = h$counts, replace = TRUE)

When I test both approaches in replication, the output looks similar, as I would expect it should be (here I look at the mean and sd of the replicate means):

fun1 <- function(x){
  xs <- rep(h$mids, h$counts)
  s <- sample(xs, replace = TRUE)
  mean(s)
}

fun2 <- function(x){
  s <- sample(h$mids, size = sum(h$counts), prob = h$counts, replace = TRUE)
  mean(s)
}

set.seed(1)
F1 <- unlist(lapply(vector("list", 1000), FUN = fun1))
F2 <- unlist(lapply(vector("list", 1000), FUN = fun2))

mean(F1); mean(F2)
# [1] 20.65432
# [1] 20.63858

sd(F1); sd(F2)
# [1] 0.3861233
# [1] 0.3750731

So, are these approaches equivalent? I would think that using probabilities is more computationally efficient since one need not first build the vector of values first.

$\endgroup$

migrated from stackoverflow.com Apr 21 '18 at 11:07

This question came from our site for professional and enthusiast programmers.

3
$\begingroup$

Looking at the code of the sample function, the first approach will end using:

size <- length(x)
x[sample.int(length(x), size, replace, prob)]

whilst the second will use:

sample.int(x, size, replace, prob)

sample.int internally calls .Internal(sample(n, size, replace, prob)). Note that the first approaches prob is NULL and the seconds one 's is h$counts.

To find the definition of the internal function I used the package pryr:

pryr::show_c_source(.Internal(sample(n, size, replace, prob)))

The function do_sample can be find here and also here.

As the first approach does not contain probabilities, its sampling will be performed using the following part of the c code:

if (replace || k < 2) {
  for (int i = 0; i < k; i++) iy[i] = (int)(R_unif_index(dn) + 1);
}

The second approach has probabilities, and hence, it uses this part of the c code for the sampling:

if (replace) {
  int i, nc = 0;
  for (i = 0; i < n; i++) if(n * p[i] > 0.1) nc++;
    if (nc > 200)
      walker_ProbSampleReplace(n, p, INTEGER(x), k, INTEGER(y));
    else
      ProbSampleReplace(n, p, INTEGER(x), k, INTEGER(y));
}

So the sampling is not exactly the same, but I assume they must be similar. In my opinion, the first one is more straightforward.

Regarding the performance the first one is slightly faster in my computer in this case. I have not tested it for bigger samples, so this can vary. The first approach though has a higher memory allocation, so the use of one or the other should be dependant on the case.

Below I write the code for microbenchmarking in case somebody want to test further:

microbenchmark::microbenchmark(
  F1 <- unlist(lapply(vector("list", 1000), FUN = fun1)),
  F2 <- unlist(lapply(vector("list", 1000), FUN = fun2))
)
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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