Search code examples
rmatrixapply

R apply custom function without for loop, expected output is matrix


How can I apply a custom function, with multiple arguments, to return an N x M dataframe without a for loop?

For example:

mat1 <- data.frame(a = 1:5, b = -1)
vec1 <- 100:107

myfcn <- function(x, vals){
  ans <- (x + vals[1]) * vals[2]
  return(ans)
}

df <- data.frame(matrix(nrow = length(mat1), ncol = length(vec1))) # pre-allocate

for (i in 1:length(vec1)){
  for (j in 1:nrow(mat1)){
    result <- myfcn(vec1[i], vals = c(mat1$a[j], mat1$b[j]))
    df[j,i] <- result
    }
  }
  
print(df)

This returns the desired output matrix:

enter image description here

How can I skip the for-loop and use some kind of apply function to get the above output matrix?

I tried sapply(vec1, myfcn, vals = c(mat1$a, mat1$b)), but it returns this:

enter image description here

And I tried outer(vec1, c(mat1$a, mat1$b), myfcn), but it returns this:

enter image description here


Solution

  • First of all, you do not need a double for loop to populate df, R is vectorized and you can pass the entire vec1 to the function each time through the loop.

    mat1 <- data.frame(a = 1:5, b = -1)
    vec1 <- 100:107
    
    myfcn <- function(x, vals){
      ans <- (x + vals[1]) * vals[2]
      return(ans)
    }
    
    df <- as.data.frame(matrix(nrow = length(mat1), ncol = length(vec1))) # pre-allocate
    
    for (j in 1:nrow(mat1)){
      result <- myfcn(vec1, vals = c(mat1$a[j], mat1$b[j]))
      df[j,] <- result
    }
    df
    #>     V1   V2   V3   V4   V5   V6   V7   V8
    #> 1 -101 -102 -103 -104 -105 -106 -107 -108
    #> 2 -102 -103 -104 -105 -106 -107 -108 -109
    #> 3 -103 -104 -105 -106 -107 -108 -109 -110
    #> 4 -104 -105 -106 -107 -108 -109 -110 -111
    #> 5 -105 -106 -107 -108 -109 -110 -111 -112
    

    Created on 2022-10-10 with reprex v2.0.2

    Now with a sapply loop. You don't even have to pre-allocate space, the loop will do it on its own.

    df2 <- t(sapply(1:nrow(mat1), \(j) myfcn(vec1, vals = c(mat1$a[j], mat1$b[j]))))
    df2 <- as.data.frame(df2)
    
    identical(df, df2)
    #> [1] TRUE
    

    Created on 2022-10-10 with reprex v2.0.2

    Note also that if you need a tabular data structure but not specifically a data.frame, then the last as.data.frame is not needed.