How to apply function over each matrix element's indices

I am wondering if there is a built-in function in R which applies a function to each element of the matrix (of course, the function should be computed based on matrix indices). The equivalent would be something like this:

matrix_apply <- function(m, f) {
  m2 <- m
  for (r in seq(nrow(m2)))
    for (c in seq(ncol(m2)))
      m2[[r, c]] <- f(r, c)
  return(m2)
}

If there is no such built-in function, what is the best way to initialize a matrix to contain values obtained by computing an arbitrary function which has matrix indices as parameters?


I suspect you want outer:

> mat <- matrix(NA, nrow=5, ncol=3)

> outer(1:nrow(mat), 1:ncol(mat) , FUN="*")
     [,1] [,2] [,3]
[1,]    1    2    3
[2,]    2    4    6
[3,]    3    6    9
[4,]    4    8   12
[5,]    5   10   15

> outer(1:nrow(mat), 1:ncol(mat) , FUN=function(r,c) log(r+c) )
          [,1]     [,2]     [,3]
[1,] 0.6931472 1.098612 1.386294
[2,] 1.0986123 1.386294 1.609438
[3,] 1.3862944 1.609438 1.791759
[4,] 1.6094379 1.791759 1.945910
[5,] 1.7917595 1.945910 2.079442

That yields a nice compact output. but it's possible that mapply would be useful in other situations. It is helpful to think of mapply as just another way to do the same operation that others on this page are using Vectorize for. mapply is more general because of the inability Vectorize to use "primitive" functions.

data.frame(mrow=c(row(mat)),   # straightens out the arguments
           mcol=c(col(mat)), 
           m.f.res= mapply(function(r,c) log(r+c), row(mat), col(mat)  ) )
#   mrow mcol   m.f.res
1     1    1 0.6931472
2     2    1 1.0986123
3     3    1 1.3862944
4     4    1 1.6094379
5     5    1 1.7917595
6     1    2 1.0986123
7     2    2 1.3862944
8     3    2 1.6094379
9     4    2 1.7917595
10    5    2 1.9459101
11    1    3 1.3862944
12    2    3 1.6094379
13    3    3 1.7917595
14    4    3 1.9459101
15    5    3 2.0794415

You probably didn't really mean to supply to the function what the row() and col() functions would have returned: This produces an array of 15 (somewhat redundant) 3 x 5 matrices:

> outer(row(mat), col(mat) , FUN=function(r,c) log(r+c) )

The simplest approach is just to use an f() that can be applied directly to the elements of the matrix. For example, using the matrix m from @adamleerich's Answer

m <- matrix(c(1,2,3,4,5,6,7,8), nrow = 2)

There is no reason to use apply() in the case of the as.character() example. Instead we can operate on the elements of m as if it were a vector (it really is one) and replace in-place:

> m[] <- as.character(m)
> m
     [,1] [,2] [,3] [,4]
[1,] "1"  "3"  "5"  "7" 
[2,] "2"  "4"  "6"  "8"

The first part of that block is the key here. m[] forces the elements of m to be replaced by the output from as.character(), rather than overwriting m with a vector of characters.

So that is the general solution to applying a function to each element of a matrix.

If one really needs to use an f() that works on row and column indices then I'd write a f() using row() and col():

> m <- matrix(c(1,2,3,4,5,6,7,8), nrow = 2)
> row(m)
     [,1] [,2] [,3] [,4]
[1,]    1    1    1    1
[2,]    2    2    2    2
> col(m)
     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    1    2    3    4
> row(m) * col(m) ## `*`(row(m), col(m)) to see this is just f()
     [,1] [,2] [,3] [,4]
[1,]    1    2    3    4
[2,]    2    4    6    8

or one that use outer() as other's have shown. If f() isn't vectorised, then I'd rethink my strategy as far as possible as there i) probably is a way to write a truly vectorised version, and ii) a function that isn't vectorised isn't going to scale very well.


You didn't tell us what kind of function you want to apply to each element, but I think that the only reason the examples in the other answers work is because the functions are already vectorized. If you really want to apply a function to each element, outer will not give you anything special that the function didn't already give you. You'll notice that the answers didn't even pass a matrix to outer!

How about following @Chase's comment and use apply.

For example, I have the matrix

m <- matrix(c(1,2,3,4,5,6,7,8), nrow = 2)

If I want to turn it into a character matrix, element by element (just as an example) I could do this

apply(m, c(1,2), as.character)

Of course, as.character is already vectorized, but my special function my.special.function isn't. It only takes one argument, an element. There is no straighforward way to get outer to work with it. But, this works

apply(m, c(1,2), my.special.function)

You may be thinking of outer:

rows <- 1:10
cols <- 1:10

outer(rows,cols,"+")

      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
 [1,]    2    3    4    5    6    7    8    9   10    11
 [2,]    3    4    5    6    7    8    9   10   11    12
 [3,]    4    5    6    7    8    9   10   11   12    13
 [4,]    5    6    7    8    9   10   11   12   13    14
 [5,]    6    7    8    9   10   11   12   13   14    15
 [6,]    7    8    9   10   11   12   13   14   15    16
 [7,]    8    9   10   11   12   13   14   15   16    17
 [8,]    9   10   11   12   13   14   15   16   17    18
 [9,]   10   11   12   13   14   15   16   17   18    19
[10,]   11   12   13   14   15   16   17   18   19    20

That's clearly a fairly trivial example function, but you can supply your own custom one as well. See ?outer.

Edit

Contrary to the comment below, you can also use outer with non-vectorized functions by....vectorizing them!

m <- matrix(1:16,4,4)

#A non-vectorized function 
myFun <- function(x,y,M){
     M[x,y] + (x*y)
}

#Oh noes! 
outer(1:4,1:4,myFun,m)
Error in dim(robj) <- c(dX, dY) : 
  dims [product 16] do not match the length of object [256]

#Oh ho! Vectorize()! 
myVecFun <- Vectorize(myFun,vectorize.args = c('x','y'))

#Voila! 
outer(1:4,1:4,myVecFun,m)
     [,1] [,2] [,3] [,4]
[1,]    2    7   12   17
[2,]    4   10   16   22
[3,]    6   13   20   27
[4,]    8   16   24   32