• R语言已解决
  • 关于 apply 函数中 MARGIN 参数设置不当的一个小问题

本楼主最近沉迷学习 R base 中的 apply 函数,产生了一个不算奇怪的问题。

如下所示,对一个矩阵按行来应用函数,为撒应用后的每一行是应用前的每一列,无意中成了转置的效果呢?

```{r}
my_matrix <- matrix(1:6,
                    nrow = 2,
                    ncol = 3,
                    byrow = FALSE)

dimnames(my_matrix)[[1]] <- c('第1行', '第2行')
dimnames(my_matrix)[[2]] <- c('第1列', '第2列', '第3列')

apply(
  my_matrix,
  MARGIN = 1,
  FUN = function(x)
    x
)
```
      第1行 第2行
第1列     1     2
第2列     3     4
第3列     5     6

如果是对一个3维数组瞎设置 MARGIN 参数,也会得到一些不算奇怪的结果。比如下面这些例子……整体上来看,我倒是知道应用前和应用后的数据分别长撒样,但是没明白为撒会变成这样

```{r}
# 创建一个维度为(2, 3, 4)的三维数组
my_array <- array(c(1:24), dim = c(2, 3, 4))
print(my_array)
```
, , 1

     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6

, , 2

     [,1] [,2] [,3]
[1,]    7    9   11
[2,]    8   10   12

, , 3

     [,1] [,2] [,3]
[1,]   13   15   17
[2,]   14   16   18

, , 4

     [,1] [,2] [,3]
[1,]   19   21   23
[2,]   20   22   24
```{r}
apply(my_array, MARGIN = 1, FUN = function(x) x)
```
     [,1] [,2]
 [1,]    1    2
 [2,]    3    4
 [3,]    5    6
 [4,]    7    8
 [5,]    9   10
 [6,]   11   12
 [7,]   13   14
 [8,]   15   16
 [9,]   17   18
[10,]   19   20
[11,]   21   22
[12,]   23   24
```{r}
apply(my_array, MARGIN = 2, FUN = function(x) x)
```
     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6
[3,]    7    9   11
[4,]    8   10   12
[5,]   13   15   17
[6,]   14   16   18
[7,]   19   21   23
[8,]   20   22   24
```{r}
apply(my_array, MARGIN = 3, FUN = function(x) x)
```
     [,1] [,2] [,3] [,4]
[1,]    1    7   13   19
[2,]    2    8   14   20
[3,]    3    9   15   21
[4,]    4   10   16   22
[5,]    5   11   17   23
[6,]    6   12   18   24
```{r}
apply(my_array, MARGIN = 1:2, FUN = function(x) x)
```
, , 1

     [,1] [,2]
[1,]    1    2
[2,]    7    8
[3,]   13   14
[4,]   19   20

, , 2

     [,1] [,2]
[1,]    3    4
[2,]    9   10
[3,]   15   16
[4,]   21   22

, , 3

     [,1] [,2]
[1,]    5    6
[2,]   11   12
[3,]   17   18
[4,]   23   24
```{r}
apply(my_array, MARGIN = 1:3, FUN = function(x) x)
```
, , 1

     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6

, , 2

     [,1] [,2] [,3]
[1,]    7    9   11
[2,]    8   10   12

, , 3

     [,1] [,2] [,3]
[1,]   13   15   17
[2,]   14   16   18

, , 4

     [,1] [,2] [,3]
[1,]   19   21   23
[2,]   20   22   24

第一个和第二个问题的答案都在apply的文档里:

If each call to FUN returns a vector of length n, and simplify is TRUE, then apply returns an array of dimension c(n, dim(X)[MARGIN]) if n > 1.

第一个问题,你的每次的返回值是一个长度大于1的向量,于是在简化生成最终的结果的时候,把它们作为列向量然后一列一列的拼接了起来。在你现在的例子里恰好看起来就是个转置。

第二个问题,每次的返回结果是个矩阵,但是会被as.vector()拉成列向量,因为文档里还有这句话

In all cases the result is coerced by as.vector to one of the basic vector types before the dimensions are set,

    fenguoerbian
    谢谢你。你写的汉字我没看懂,引用的文档中的文字我也还糊涂着。我的理解是 apply 函数中一共有三个主要参数,分别是 MARGIN 用来选择一个 N 维数组中 1:N 之中的一个或多个数字,FUN 用来写应用于 MARGIN 选择的维度的函数,simplified 用于简化输出结果比如原本输出的矩阵全都是整型数字的话会简化成一个向量。

    If each call to FUN returns a vector of length n, and simplify is TRUE, then apply returns an array of dimension c(n, dim(X)[MARGIN]) if n > 1. If n equals 1, apply returns a vector if MARGIN has length 1 and an array of dimension dim(X)[MARGIN] otherwise. If n is 0, the result has length 0 but not necessarily the ‘correct’ dimension.

    上面引用的文档写道 if ……FUN returns ……,then apply returns ……你说这是 FUN 返回一个向量,最终结果是简化后生成的数组,数组的维度是c(n, dim(X)[MARGIN]),在第一个问题中,我设置的是MARGIN = 1,那么对一个2行3列的矩阵按行应用函数会得到n=3的向量,dim(my_matrix)(1)会得到2,最终会得到一个c(3,2)的矩阵,你的解释是按行得到的向量按列拼了起来,额,可是我就是还是没理解为撒是这样,为撒这种情况下最终输出的矩阵维度是c(n, dim(X)[MARGIN])。ps 虽然不理解,但是以后用可以把这点死记硬背下来。

    If the calls to FUN return vectors of different lengths, or if simplify is FALSE, apply returns a list of length prod(dim(X)[MARGIN]) with dim set to MARGIN if this has length greater than one.

    上面这段我的理解是当 FUN 返回的是向量长度不等或者不简化结果时,如果 MARGIN 参数设置的是多于1个数字,整体返回一个prod(dim(X)[MARGIN])的列表,执行 apply(my_array, MARGIN = 1:2, FUN = function(x) x, simplify = FALSE)会得到长度为 prod(c(2,3,4)[1:2]) = 6 的列表,可是却是下面这样的,俺更糊涂了。

         [,1]      [,2]      [,3]     
    [1,] integer,4 integer,4 integer,4
    [2,] integer,4 integer,4 integer,4

    In all cases the result is coerced by as.vector to one of the basic vector types before the dimensions are set, so that (for example) factor results will be coerced to a character array.

    这段我理解说的是结果中的数据会被转换成向量来处理,由于向量只能存储同一数据类型,所以比如遇到这种情况c(1, 'a'),就会把1也转换成字符型,但是是怎么解读成拉成列向量的我是真的没懂,于是第二个问题就还依然是,啊,难得糊涂呀。

      yuanfan 在第一个问题中,我设置的是MARGIN = 1,那么对一个2行3列的矩阵按行应用函数会得到n=3的向量,dim(my_matrix)(1)会得到2,最终会得到一个c(3,2)的矩阵

      如果你看不懂我写的汉字,那么在这里你其实已经自己解释了为什么第一个问题会是这样的结果了。

      如果你想问的其实是这个东西是怎么实现的,那不妨dubug(apply)之后再去跑你的这个apply的例子,可以一步步看里面是怎么做的。后面不想再这么细致的时候undebug(apply)即可。其中的具体过程简而言之:一开始得到的结果是一个有两个元素的list,其中第一个元素是向量c(1, 3, 5),第二个元素是向量c(2, 4, 6)。它的simplify过程,到最终就是先unlist()这个结果,得到c(1, 3, 5, 2, 4, 6),然后用array()设置这个结果的维数为c(3, 2),其中3来自于每次function(x) x在你这个apply的例子里都是返回的一样长度的3维向量,2来自于这个apply其实是应用在了原数据的共计两行上。

      本身这个事情是符合直觉的,你的apply中的function(x) x在当前的这个MARGIN设定下每次都返回一样长度的3维向量,于是再简化最终结果的时候就把这些向量直接拼成了矩阵。而文档里告诉你的除了这个事情之外,也暗示了它是把每次的结果作为列向量进行拼接的。所以最终的矩阵里每一列就是每一次function(x) x的运行结果。

      至于第二个问题,as.vector(matrix(1 : 6, nrow = 2))你就会发现矩阵被拉成了向量。当然apply源码里用的unlist(),在第二个问题里,原始的结果是一个list,其中每个元素都是矩阵,像这样的数据unlist之后,那些矩阵也都变成了向量。

      至于你回复里的额外问题,你只是被打印出来的表象骗了。

      tmp <- apply(my_array, MARGIN = 1:2, FUN = function(x) x, simplify = FALSE)
      str(tmp)

      你会发现结果确实是个列表,只是它有额外的dim属性,所以在终端打印出来变成了你看到的这样子。这个结果列表的维数属性,实际上是dim(X)[MARGIN],(所以文档对这里可能表述有点偏差?)。有这个属性了,你可以用tmp[1, 2]或者tmp[[1, 2]]这样的形式找到对原先的数据对应MARGIN的指标分别取1和2时的apply结果到底是啥。

        3 个月 后