R simulation data with grouped random normal values

Viewed 246

I have a data set with around 10k data points related to products and their prices. I would like to generate simulated sales data from normal distribution that follow specific pattern based on the price of the product. For example if the price is low, e.g., less than 40 sales (quantity) should be extracted from a normal distribution with mean 120 and sd=20. If the price is average, e.g., between 40 and 50, quantity should be extracted from a normal distribution with mean 80 and sd=15. If price is high, e.g., above 50, the quantity should be extracted from a normal with mean 20 and sd=3

I have created the following working example:

dt <- tibble(sample(x = c("cheap","mediumA", "mediumB", 
                      "mediumC", "expensive"),
   prob = c(.3, .2,.2,.2, .1), 
   size = 10000,
   replace = TRUE))
dt <- dt %>% 
  mutate(productName=`sample(...)`,
         Price=case_when(
           productName=="cheap"~10,
           productName=="mediumA"~40,
           productName=="mediumB"~45,
           productName=="mediumC"~50,
           TRUE~300)) %>% 
  select(productName, Price)

I have tried the following but it does not work.

dt %>% count(Price)
dt <- dt %>% 
  mutate(
    Quantity=case_when(
  Price<40 ~ 
    rnorm(dt %>%
            filter(Price<40) %>% 
            count(Price) %>% 
            pull(n), 120, 20),
  Price >=40 & Price <= 50 ~ 
    rnorm(dt %>%
            filter(Price >=40 & 
                     Price <= 50) %>% 
            count(Price) %>%
            pull(n) %>% 
            sum(), 80, 15),
  TRUE~ 
    rnorm(dt %>%
            filter(Price>50) %>% 
            count(Price) %>% 
            pull(n), 20, 3)))

The above R script produces the following error message:

Error: 
Price < 40 ~ rnorm(dt %>% filter(Price < 40) %>% count(Price) %>%      pull(n), 120, 20), 
Price >= 40 & Price <= 50 ~ rnorm(dt %>% filter(Price >= 40 &      
Price <= 50) %>% count(Price) %>% pull(n) %>% sum(), 80,      15), 
TRUE ~ rnorm(dt %>% filter(Price > 50) %>% count(Price) %>% pull(n),      20, 3) 
must be length 1000 or one, not 313, 597, 90 Call rlang::last_error() to see a backtrace

Any suggestions on how to make this work would be great.

2 Answers

Here is a way you can achieve your desired result, using map from the purrr package which is part of the tidyverse:

 fun <- function(p) {
   case_when(p < 40 ~ rnorm(1, 120, 20),
             p <= 50 ~ rnorm(1, 80, 15),
             p > 50 ~ rnorm(1, 20, 3))
 }
 
 dt %>%
   mutate(Quantity = map_dbl(Price, fun))

You can use nested ifelse, however, you need to count how many values are in the range (we do that using sum) and generate them together in one go in rnorm.

library(dplyr)

dt %>% 
  mutate(Quantity= ifelse(Price < 40, rnorm(sum(Price < 40), 120, 20),
              ifelse(Price <= 50, rnorm(sum(Price >= 40 & Price < 50), 80, 12), 
                                  rnorm(sum(Price > 50), 20, 3))))
Related