Random numbers in a column based on another column's value

Viewed 641

Good day all,

I'm struggling with creating a column which would populate the values using a random value generating value function which takes another column's value as an argument.

A bit of a context - I have a data table with lead time in a column:

library(data.table)

dt <- data.table(Item = rep(123456,each = 1000), LT = rnorm(1000,mean = 10, sd = 3))

and a function:

rand_ddlt_norm <- function(Lt,mean,sd){
  sign(Lt) * ( sum( rnorm( floor(abs(Lt)), mean, sd) ) +
                 rnorm(1, mean, sd) * ( abs(Lt)%%1) )
}

The above function is designed to calculate demand during the lead time for each row.

Unfortunately, I cannot do that:

dt[,ddlt := rand_ddlt_norm(LT, mean = 100, sd = 30)]

because all rows will be populated with the same number.

I could obviously put it into a loop, but for 10,000 iterations, 20,000+ products and numerous distribution types, the computation time is getting ridiculous.

I would graciously welcome any suggestions about how this code could be optimised without running a loop.

2 Answers

Use Vectorize() to vectorize your function.

# data
library(data.table)

set.seed(1)

dt <- data.table::data.table(Item = rep(123456,each = 1000), LT = rnorm(1000,mean = 10, sd = 3))

# def function
rand_ddlt_norm <- function(Lt,est11,est12){
  sign(Lt) * ( sum( rnorm( floor(abs(Lt)), est11, est12) ) +
                 rnorm(1, est11, est12) * ( abs(Lt)%%1) )
}

rand_ddlt_norm <- Vectorize(rand_ddlt_norm) # vectorize it

dt[,ddlt := rand_ddlt_norm(LT, 100,30)]

Result:

> head(dt)
     Item        LT      ddlt
1: 123456  8.120639  845.6967
2: 123456 10.550930 1112.5837
3: 123456  7.493114  733.3808
4: 123456 14.785842 1516.8916
5: 123456 10.988523 1101.0449
6: 123456  7.538595  898.3760

I would propse you vectorize your function directly :

rand_ddlt_norm_vec <- function(Lt,mean,sd){
  sign(Lt) * ( rowSums( t(sapply(1:length(Lt),function(x){rnorm(floor(abs(Lt)),mean,sd)})))  +
                 rnorm(length(Lt), mean, sd) * ( abs(Lt)%%1) )
}

Where Lt is now a vector. Here

t(sapply(1:length(Lt),function(x){rnorm(floor(abs(Lt)),mean,sd)}))

create a matrice that has the same number of row than Lt, and the same number of column than floor(abs(Lt)). You then use Rowsum to get a vector.

To compare with the solution of JdeMello:

rand_ddlt_norm_vec2 <- Vectorize(rand_ddlt_norm)

library(microbenchmark)
library(data.table)

dt <- data.table(Item = rep(123456,each = 10000), LT = rnorm(10000,mean = 10, sd = 3))

    microbenchmark(
      denis = function(){dt[,ddlt := rand_ddlt_norm_vec(LT, mean = 100, sd = 30)]},
      jdeMello = function(){dt[,ddlt := rand_ddlt_norm_vec2(LT, mean = 100, sd = 30)]}
    )

Unit: nanoseconds
     expr min lq  mean median uq  max neval cld
    denis   0  0  0.24      0  0    1   100   a
 jdeMello   0  0 25.88      0  0 2566   100   a

This solution is 100 time faster than JdeMello solution.

Related