purrr; sample from multiple columns with probability list

Viewed 702

Say I want to take a sample of values of variable length from an arbitrary number of different probability distributions, and with a weighted probability of sampling from each distribution.

Seems like I should be able to do this using purrr's map functions, but am struggling...

library(tidyverse)

set.seed(20171127)

# sample from 5 different probability distributions
dists <- tibble(
  samp_distA = round(rnorm(n=1000, mean=17, sd=4)),
  samp_distB = round(rnorm(n=1000, mean=13, sd=4)),
  samp_distC = round(rnorm(n=1000, mean=13, sd=4)),
  samp_distD = round(rbeta(n=1000, 2,8)*10),
  samp_distE = round(rnorm(n=1000, mean=8, sd=3))
  )

# define number of samples to be drawn for each group
n.times <- c(20,15,35,8,6) 

# define weights to be used for sampling from dists 
probs <- tibble(A = c(0.80, 0.05, 0.05, 0.05, 0.05),
                B = c(0.05, 0.80, 0.05, 0.05, 0.05),
                C = c(0.05, 0.05, 0.80, 0.05, 0.05),
                D = c(0.05, 0.05, 0.05, 0.80, 0.80),
                E = c(0.05, 0.05, 0.05, 0.05, 0.80)
                )

# sample from dists, n.times, and using probs as weights...
output <- map2(sample, size=n.times, weight=probs, tbl=dists)

#...doesn't work

Any suggestions gratefully received.

2 Answers

This seems doable with purrr, but it takes a bit of set up, particularly because there's not a sample2 function (that I'm aware of) that samples a distribution based on a vector of probabilities, and then grabs a random sample from that subset.

To do that with purrr, we have to loop twice: the outside loops through each person using a simple numerical index; inside that loop, we loop through the n.times to get random samples from the appropriate distribution.

# prep data ---------------------------------------------------------------

# pull all the controls into a single data frame 
controldf  <- tibble(
  cols = c(1:5), n.times
  ) %>% 
  bind_cols(probs %>% 
              t %>% 
              as.tibble %>% 
              setNames(c("distA", "distB", "distC", "distD", "distE")) 
  )

# turn the distrubtions into long form
longdists  <- dists %>% 
  gather(dist, val)

distnames  <- c("A", "B", "C", "D", "E")


# function to do the work  ---------------------------------------------------------------

getdist  <- function(i) {

  # get the probabilities as a numeric vector
  myprobs  <- controldf[i,3:7] %>% as.numeric

  # how many samples do we need
  myn      <- controldf[[i,2]]

  # use our probabilties to decide what distribution to grab from
  samplestoget  <- sample(distnames, myn, prob = myprobs, replace = T) %>% 
    paste0("samp_dist", .)

  # loop through our list of distributions to grab from 
  map_dbl(samplestoget, ~filter(
    # filter on distribution key
    longdists, dist == .x
    ) %>% 
    # from that distribution, select a single value at random
    sample_n(1) %>% 
    # extract the numeric value
    pluck('val') )

}

# get the values by running the function over our indexes  -------------------------

results  <- map(controldf$cols, ~ getdist(.x))
Related