我们在第一期讲了R语言中与向量相关的一些知识,如果你还没有学习,建议先学习第一期:
不幸的是,在实践中我们大多数情况下都在与二维的矩阵/数据框打交道,所以今天我们就来讲讲矩阵相关的一些特性。
1 矩阵的索引
在详细讲解矩阵的索引前,我们要先介绍矩阵的一种特性:矩阵是一种特殊的向量。如何理解这句话呢?我们首先看到如下矩阵:
> mat1 <- matrix(1:9, nrow=3, ncol=3)
> mat1
[2] [,3] ] [,
[1 4 7 ]
[2 5 8 ]
[3 6 9 ]
这是一个二维的矩阵,但是R语言会将其作为一条向量进行存储,如下图:
也就是说,它的本质不过是一条c(1,2,3,4,5,6,7,8,9)的向量,再加上两个维度信息(3*3)。所以,向量索引的特性,在矩阵上都可以照常使用(相当于在对c(1,2,3,4,5,6,7,8,9)进行索引),比如:
> mat1[c(3,5)]
[1] 3 5
> mat1[3:5]
[1] 3 4 5
> mat1[mat1>3]
[1] 4 5 6 7 8 9
在这一基础上,我们还可以使用矩阵独有的索引方式:按维度索引。这种索引方式是最直观的:
> mat1[3,1]
[1] 3
在使用这种索引方式时,我们需要在逗号前输入第一个维度的索引位置,在逗号后输入第二个维度的索引位置。讲解向量时,我们提到我们总是用一个向量去索引目标向量(记得标量也是向量),矩阵索引也是如此:我们可以在逗号前后都输入一个向量去代表索引位置,这个向量既可以是数值型的,也可以是布尔型的,也可以为空(代表该维度所有位置都选取),如下:
> mat1[1:2,2:3]
[,1] [,2]
[1,] 4 7
[2,] 5 8
> mat1[c(TRUE,TRUE,FALSE),c(FALSE,TRUE,TRUE)]
[,1] [,2]
[1,] 4 7
[2,] 5 8
> mat1[,1]
[1] 1 2 3
在上述最后一个例子中,我们索引的元素构成了一个向量。有时候,我们并不希望我们从矩阵中索引的元素“降级”为向量,我们希望它仍保有矩阵的性质,可以做如下操作:
> mat1[,1,drop=F]
[,1]
[1,] 1
[2,] 2
[3,] 3
我们最后再提一点,矩阵也可以由矩阵进行索引,如下:
> mat2 <- rbind(c(1,1),c(2,2),c(3,3))
> mat1[mat2]
[1] 1 5 9
2 矩阵的运算
R语言中的矩阵的运算和向量很相似(因为矩阵也是一种向量),首先来看矩阵的乘法,它也是按元素相乘的:
> mat3 <- matrix(1:12, nrow=3, ncol=4)
> mat3*mat3
[2] [,3] [,4] ] [,
[1 16 49 100 ]
[4 25 64 121 ]
[9 36 81 144 ]
注意,如果是数学上,两个矩阵相乘并不是按元素相乘的,如果想要计算线性代数中定义的矩阵乘积,我们需要进行如下操作:
> t(mat3)%*%mat3
[,1] [,2] [,3] [,4]
[1,] 14 32 50 68
[2,] 32 77 122 167
[3,] 50 122 194 266
[4,] 68 167 266 365
矩阵的加法和向量加法一样,也是按元素相加的,我们在此不再演示。我们在向量部分聊过回收特性,它同样也适用于矩阵的运算,如下:
> mat3+3
[,1] [,2] [,3] [,4]
[1,] 4 7 10 13
[2,] 5 8 11 14
[3,] 6 9 12 15
> mat3*3
[,1] [,2] [,3] [,4]
[1,] 3 12 21 30
[2,] 6 15 24 33
[3,] 9 18 27 36
> mat3+c(1,2,3)
[,1] [,2] [,3] [,4]
[1,] 2 5 8 11
[2,] 4 7 10 13
[3,] 6 9 12 15
值得注意的是,R并不支持回收矩阵,比如下述代码会报错:
> mat3+cbind(c(1,2,3),c(1,2,3))
Error in mat3 + cbind(c(1, 2, 3), c(1, 2, 3)) : non-conformable arrays
3 apply()函数
apply类函数是数据预处理中最重要的函数类别,我们在这一期中先带大家学习最简单的apply()的使用。
假如我们想要获取上述mat3每一列的均值,可以如何操作呢?最直观的操作方法时逐个计算:
> mean(mat3[,1])
[1] 2
> mean(mat3[,2])
[1] 5
> mean(mat3[,3])
[1] 8
> mean(mat3[,4])
[1] 11
这个方法十分容易想到,也是很多R语言新手最先学习的方法,但是这个方法的缺陷也很明显:代码十分冗余,并且当数据框存在多列时,代码量会异常大!
为了解决这个问题,另一个直观的办法就是利用循环:
> for (i in 1:ncol(mat3)){
+ print(mean(mat3[,i]))
+ }
[2 ]
[5 ]
[8 ]
[11 ]
循环的方法好是好,但是在所有中级以上的R语言编程课中,老师都会告诫学生尽量避免写for和while循环。比如在这个例子中,我们用一行apply()函数代码就可以实现:
apply(mat3,2,mean)
[1] 2 5 8 11
上述代码的意思很容易读取:将mean函数按列(我们通过数字2告诉apply函数要按列)apply到目标矩阵上。大家可以看到,相比for函数,代码变得显著简洁了!如果我们现在要计算行均数,也十分简单,只需要修改一下数字就行了:
> apply(mat3,1,mean)
[1] 5.5 6.5 7.5
如上就是有关apply()函数的内容,你可能会有疑问:既然这个函数的使用那么简单,为什么还要在前面做那么多的铺垫呢?因为R语言是一种函数化编程(functional programming)语言,什么是函数化编程?就是R语言内嵌了很多能够减轻我们代码负担的函数,比如apply()。但对于大多数人来说,直接学习使用这些高级的函数并不简单,所以我们需要先学习负担更重,但更直观的方法,当我们熟练掌握这些方法后,才能够更好地掌握更加高级的函数化编程。毕竟,函数化编程背后的workflow的本质和我们用“最笨”的办法编程的本质是一样的!
这个系列在后续也会以相同的逻辑向大家介绍其他的高级函数编程,希望本期教程对你有帮助!如果你喜欢这个系列,欢迎关注本公众号,持续锁定更多有用教程!