如何对每个矩阵元素的索引应用函数

49

我想知道在R中是否有一个内置函数可以将一个函数应用于矩阵的每个元素(当然,该函数应基于矩阵索引进行计算)。相当于以下操作:

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)
}

如果没有这样的内置函数,那么初始化一个矩阵以包含通过计算一个具有矩阵索引作为参数的任意函数获得的值的最佳方法是什么?


3
你是否熟悉被称为apply()函数族的函数?MARGIN参数可以接受行、列和行&列的数值。此外,不少R函数都是向量化的,可以避免这种类型的编程。 - Chase
3
@leden,你能给出一个f()的例子吗?据我所知,任何向量化函数都可以在矩阵上运行,因为它只是具有dim属性的向量。您不需要将其拆分为行和列索引。目前,在您的问题中存在一定的歧义;看起来您想要一个通用解决方案,但规定它应基于索引,这是次优的。 - Gavin Simpson
我的意思是,为什么不能编写f(),以便你实际上只需要m [] <- f(m)?我会添加一个例子... - Gavin Simpson
1
我认为OP需要回复我们所有人,而不仅仅是因为这是礼貌的 :-)。根据他的示例,m2矩阵是通过一个函数'f(r,c)'生成的,该函数纯粹是指数的函数,与原始矩阵的内容无关。由于这可能不是他想要的,而是'g(r,c,m[r,c])'或'g(m[r,c])',到目前为止提供的答案非常好,但不一定回答了他(模棱两可的)问题。 - Carl Witthoft
我只需要能够应用一个至少需要每个矩阵列的索引的函数。其中一个应用是,比如说我想创建一个乘法表,或者仅仅评估两个参数的某些函数并将结果存储到矩阵中。 - eold
5个回答

32

我猜你想要的是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

这产生了一个漂亮紧凑的输出。但在其他情况下,mapply可能会很有用。将mapply视为执行与此页面上其他人使用Vectorize相同操作的另一种方法是很有帮助的。mapply更通用,因为无法使用"原始"函数。

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

你可能并不是真的想要将row()和col()函数返回的内容传递给函数:这会产生一个15个(有点冗余的)3 x 5矩阵数组:

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

19

最简单的方法是直接使用可以直接应用于矩阵元素的f()。例如,使用@adamleerich答案中的矩阵m

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

as.character() 示例的情况下,没有理由使用 apply()。相反,我们可以像操作向量一样操作 m 的元素(它实际上就是一个向量),并进行原地替换

> m[] <- as.character(m)
> m
     [,1] [,2] [,3] [,4]
[1,] "1"  "3"  "5"  "7" 
[2,] "2"  "4"  "6"  "8"
那个代码块的第一部分是关键。 m[] 强制对 m 的元素进行替换为 as.character() 的输出,而不是使用字符向量覆盖 m
这就是将函数应用于矩阵每个元素的通用解决方案。
如果确实需要使用一个作用于行和列索引的 f() 函数,那么我会编写一个使用 row()col()f() 函数:
> 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

或者使用其他人展示的outer()。如果f()不是矢量化的,则需要重新考虑我的策略,因为i)可能有一种方法编写真正矢量化的版本,和ii)一个不是矢量化的函数将无法很好地扩展。


14
你没有告诉我们你想对每个元素应用什么样的函数,但我认为其他答案中的示例之所以有效是因为这些函数已经被向量化了。如果你真的想对每个元素应用一个函数,outer 不会给你任何特殊的东西,而那个函数本身已经给你了。你会注意到,这些答案甚至没有将矩阵传递给 outer
不妨考虑一下遵循 @Chase 的评论并使用 apply
例如,我有以下矩阵:
m <- matrix(c(1,2,3,4,5,6,7,8), nrow = 2)

如果我想将其逐个元素地转换为字符矩阵(只是一个例子),我可以这样做:

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

当然,as.character 已经是矢量化的了,但我的特殊函数 my.special.function 不是。它只接受一个参数,一个元素。没有直接的方法让 outer 与它一起工作。但是,这个方法可以解决问题。

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

你可以让 outer 与非向量化函数配合使用,但只能通过以与 apply() 相同的方式伪造向量化来实现。 - Gavin Simpson
此外,我在我的回答中展示了如何在不使用apply()的情况下执行as.character()。因此,我们应该倡导正确的做法,而不是伪造所需的结果 - 您的apply()只是隐藏了一个更长的单循环,其中OP有两个嵌套的循环。 - Gavin Simpson

9
您可能正在思考“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

这显然是一个相当简单的示例函数,但您也可以提供自己的自定义函数。请参见?outer

编辑

与下面的评论相反,您还可以通过......对它们进行矢量化,使用非矢量化的函数来使用outer

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

我认为outer不是他要找的。仅仅因为他的双重循环示例看起来像对outer的调用,并不意味着outer会奏效。你能给出一个使用未矢量化函数的示例吗? - adamleerich
@adamleerich 请看我的编辑。非向量化函数可以向量化。 - joran

0

这并不完全回答你的问题,但我在尝试解决类似问题时找到了它,所以我会给你展示一些东西。

假设你有一个函数,想要将其应用于矩阵的每个元素,该函数只需要一个部分。

mydouble <- function(x) {
   return(x+x)
}

假设你有一个矩阵X,

> x=c(1,-2,-3,4)
> X=matrix(x,2,2)
> X
     [,1] [,2]
[1,]    1   -3
[2,]   -2    4

然后你这样做:
res=mydouble(X)

然后它将对每个值进行逐元素的双倍运算。

然而,如果您在函数中执行以下逻辑,则会收到警告,指出它未经参数化且不会按预期行事。

myabs <- function(x) {
  if (x<0) {
      return (-x)
  } else {
      return (x)
  }
}

> myabs(X)
     [,1] [,2]
[1,]    1   -3
[2,]   -2    4
Warning message:
In if (x < 0) { :
  the condition has length > 1 and only the first element will be used

但是如果你使用apply()函数,你就可以使用它。

例如:

> apply(X,c(1,2),myabs)
     [,1] [,2]
[1,]    1    3
[2,]    2    4

所以这很棒,对吧?但是,如果您有一个带有两个或更多参数的函数,它就会出问题。例如,假设您有以下内容:

mymath <- function(x,y) {
    if(x<0) {
        return(-x*y)
    } else {
        return(x*y)
    }
}

在这种情况下,您可以使用apply()函数。但是,它会丢失矩阵,但结果计算正确。如果您愿意,它们可以重新格式化。
> mapply(mymath,X,X)
[1]  1 -4 -9 16
> mapply(mymath,X,2)
[1] 2 4 6 8
> matrix(mapply(mymath,X,2),c(2,2))
     [,1] [,2]
[1,]    2    6
[2,]    4    8

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接