Home > Mobile >  Vectorizing a function with mapply
Vectorizing a function with mapply

Time:02-17

I have a function in R which takes strings which represent commerce discount structures and converts them into numerical multipliers. For example, the string "50/20/15" just means that the original price gets 50% off, then another 20% off, then another 15% off. This will be equivalent to multiplying the original price by (1 - 0.50)*(1 - 0.20)(1 - 0.15), which equals 0.34.

My function is as follows.

disc_str_to_num <- function(string){
  numstrings <- strsplit(string,"/")
  nums <- mapply(as.numeric, numstrings)
  prod(1 - nums/100)
}

which works as desired on single strings.

> disc_str_to_num("50/20/15")
[1] 0.34

Unfortunately this fails for string vectors.

discount_vec <- c("50/10",
                  "50/10/10",
                  "50/10/15",
                  "50/10/20")

> disc_str_to_num(discount_vec)
Error in nums/100 : non-numeric argument to binary operator

I have tried various applications of the Vectorize function, with no success.

How may I modify my function to work for string vectors? The expected output is a numeric vector of the component-wise values of the string vector.

CodePudding user response:

The prod should be also within the loop

sapply(strsplit(discount_vec, "/"), \(x) prod(1 - as.numeric(x)/100))
[1] 0.4500 0.4050 0.3825 0.3600

For a single element, mapply use SIMPLIFY = TRUE and thus we don't have any issue, however with more than one element, it returns a list and thus the last step doesn't work

> list(c(1, 3), c(2, 4))/100
Error in list(c(1, 3), c(2, 4))/100 : 
  non-numeric argument to binary operator

If we use the OP's code, then wrap with another mapply (or could do this in the same mapply as showed with sapply)

> disc_str_to_num <- function(string){
    numstrings <- strsplit(string,"/")
    nums <- mapply(as.numeric, numstrings)
    mapply(\(x) prod(1 - x/100), nums)
  }
> 
> disc_str_to_num(discount_vec)
[1] 0.4500 0.4050 0.3825 0.3600

Another option would be to read as data.frame with read.table, and then use rowProds from matrixStats

library(matrixStats)
rowProds(as.matrix(1- read.table(text = discount_vec,
  header = FALSE, sep = "/", fill = TRUE)/100), na.rm = TRUE)
[1] 0.4500 0.4050 0.3825 0.3600

CodePudding user response:

Use Vectorize like this:

Vectorize(disc_str_to_num)(discount_vec)
##    50/10 50/10/10 50/10/15 50/10/20 
##   0.4500   0.4050   0.3825   0.3600 

or create a new function name and then invoke it:

disc_str_to_num_vec <- Vectorize(disc_str_to_num)

disc_str_to_num_vec(discount_vec)
##    50/10 50/10/10 50/10/15 50/10/20 
##   0.4500   0.4050   0.3825   0.3600 

or rewrite the function as shown. Note that strsplit creates a one element list in this function and [[1]] takes the contents.

disc_str_to_num_vec2 <- 
  Vectorize(function(x) prod(1 - as.numeric(strsplit(x, "/")[[1]]) / 100))

disc_str_to_num_vec2(discount_vec)
##    50/10 50/10/10 50/10/15 50/10/20 
##   0.4500   0.4050   0.3825   0.3600 
  • Related