Bindiya12
Bindiya12

Reputation: 3491

Selecting a random number within a range with a discontinuity in R

I would like to generate a random number that is between 0 and 1 but that is NOT between .4 and .6. Is there any better way to do this in R than as follows?

sample(c(runif(1,0,.4), runif(1,.6,1)), 1, prob=c(.5,.5))

Upvotes: 2

Views: 260

Answers (4)

Axeman
Axeman

Reputation: 35377

Similar to TC Zhang, but about 3x faster (since ifelse is slow):

mysample_axe <- function(n = 1){
    tmp <- runif(n, max = 0.8)
    tmp + (tmp > 0.4) * 0.2
}

Comparison to TC Zhang (mysample) and Cath (samp_runif):

microbenchmark::microbenchmark(mysample(1e5), mysample_axe(1e5), samp_runif(1e5))
Unit: milliseconds
                expr       min        lq      mean    median        uq       max neval cld
     mysample(1e+05) 12.684764 13.193528 17.313560 13.420470 19.692859 130.23693   100   c
 mysample_axe(1e+05)  4.897770  5.159778  5.751177  5.187718  5.218367  14.60607   100 a  
   samp_runif(1e+05)  7.615363  8.101890  9.266797  8.139951  8.194121  25.44451   100  b
ggplot2::qplot(mysample_axe(1e5), breaks = I(seq(0, 1, 0.02)))

enter image description here

Upvotes: 3

Cath
Cath

Reputation: 24074

You can sample the uniform distribution on [0,1] until you have a number following your constraints with a recursive function (it is ~twice slower than @Axeman solution) :

samp_runif <- function(n){
    x <- runif(n, 0, 1)
    wh_pb <- which(x>0.4 & x<0.6)
    if(length(wh_pb)){x[wh_pb] <- samp_runif(length(wh_pb)) ; return(x)} else return(x)
}

samp_runif(5)
# [1] 0.3633319 0.9586853 0.6766313 0.6903275 0.8090996

Visualisation:

test <- samp_runif(1e6)
plot(density(test))

enter image description here


Generalisation:

You can adapt the above function to put the threshold values (0.4 and 0.6 in the example) as parameters, same for the lower and upper bound (0 and 1 respectively in the example):

samp_runif <- function(n, a=0.4, b=0.6){
    x <- runif(n, 0, 1)
    wh_pb <- which(x>a & x<b)
    if(length(wh_pb)){x[wh_pb] <- samp_runif(length(wh_pb), a=a, b=b) ; return(x)} else return(x)
}
samp_runif(5, 0.2, 0.8)
#[1] 0.80316178 0.99624724 0.89554995 0.05928052 0.17771131

Upvotes: 2

IanRiley
IanRiley

Reputation: 233

Yes, that idea was a completely dud, so I have deleted it. Thanks Axeman for the helpful feedback.

Here's another idea (this time better tested):

fx <- function() {y <- runif(1); if (y > 0.4 & y < 0.6) fx() else y}

Rather than correct the value between 0.4 and 0.6, just get another.

Upvotes: 0

TC Zhang
TC Zhang

Reputation: 2797

The basic idea to sample a random number from any distribution is inverse transform sampling.

mysample <- function(n = 1){
  tmp <- runif(n)
  ifelse (tmp > 0.5, 0.8 * tmp + 0.2, 0.8 * tmp)
}

library(ggplot2)
## A density plot for confirmation
df <- data.frame(x= mysample(1000000))

gg <- ggplot(df, aes(x=x)) +
  geom_density()

gg

Created on 2018-07-10 by the reprex package (v0.2.0.9000).

Upvotes: 2

Related Questions