用R语言实现科研数据高效预处理(二)

文摘   2024-12-22 08:30   荷兰  

我们在第一期讲了R语言中与向量相关的一些知识,如果你还没有学习,建议先学习第一期:

用R语言实现科研数据高效预处理(一)

用R语言实现科研数据高效预处理(一)练习题

不幸的是,在实践中我们大多数情况下都在与二维的矩阵/数据框打交道,所以今天我们就来讲讲矩阵相关的一些特性。


1 矩阵的索引

在详细讲解矩阵的索引前,我们要先介绍矩阵的一种特性:矩阵是一种特殊的向量。如何理解这句话呢?我们首先看到如下矩阵:

> mat1 <- matrix(1:9, nrow=3, ncol=3)> mat1   [,1] [,2] [,3][1,] 1    4    7[2,] 2    5    8[3,] 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   [,1] [,2] [,3] [,4][1,] 1   16   49  100[2,] 4   25   64  121[3,] 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]))+ }[1] 2[1] 5[1] 8[1] 11

循环的方法好是好,但是在所有中级以上的R语言编程课中,老师都会告诫学生尽量避免写forwhile循环。比如在这个例子中,我们用一行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的本质和我们用“最笨”的办法编程的本质是一样的!


这个系列在后续也会以相同的逻辑向大家介绍其他的高级函数编程,希望本期教程对你有帮助!如果你喜欢这个系列,欢迎关注本公众号,持续锁定更多有用教程!


PsychoStatisticia
一个统计学研究者的个人天地
 最新文章