2.1.3 矩阵
前面介绍了R中重要的数据结构——向量。 R中的向量与数学中的向量非常相似,都只有一个维度。但实际上,信息丰富的数据通常需要多个向量来描述。比如,在狗熊会微信公众号上,熊大的男粉丝和女粉丝数目分别是100人、200人,那么在R中可以用向量c(100,200) 来代表熊大的粉丝统计;狗熊会不断壮大,加入了水妈、政委、段子手,他们的男女粉丝数目分别用向量c(100, 0),c(0, 100),c(50, 100)表示。这样,需要用4个向量才能表示狗熊会中熊大、水妈、政委、段子手的粉丝数目。而且随着狗熊会队伍的不断壮大,就需要用越来越多的向量代表每个人的粉丝数目。那么,能不能把这些向量放在一起表示呢?它们统计的不都是“男粉丝”“女粉丝”吗?没错,当然可以。将上述变量叠加起来,就是我们要讲到的矩阵(见图2-6)。
在R中,矩阵其实就是一个二维数组,外表类似Excel中的表格,但重点是其中的每个元素都必须具有相同的数据类型。矩阵也是在各种数值模拟运算中使用最多的数据结构。下面介绍对矩阵的典型操作。
1.创建及引用
在R中创建矩阵基本分成两种情形:
(1)生成一个矩阵。生成一个矩阵很简单,使用matrix( )函数即可,其语法是:matrix vector nrow=number_of_rows ncol=number_of_ columns byrow=T F ,即把要组成矩阵的元素、矩阵的行列数以及排列模式设置好。如果想生成对角矩阵,那么直接用diag( )函数。下面举两个典型例子:生成一个全部取同样值(例如,全部取0)的矩阵,以及生成一个对角线元素全是1(也可以是其他同样的取值)的矩阵。
(2)把已有数据转换成矩阵类型。比如把向量转换成矩阵,通过以下代码就可以将向量1:12转换成3行4列的矩阵。
需要注意的是,这里转换时矩阵元素的排列方式是将向量按列排列的(也可以通过设置byrow=T将其改变成按行排列)。
另外,也可以用diag vector 函数转换成以vector为对角线的对角矩阵。
2.基本的矩阵操作
通常拿到一个矩阵后,会按什么步骤处理呢?首先,要了解这个矩阵的概貌,比如用dim( )查看矩阵的行列数,或采用nrow( )提取矩阵的行数,ncol( )提取矩阵的列数。其次,如果引用矩阵中的某些元素,与向量类似,将目标元素的位置用方括号括住即可,只不过由于矩阵是二维,因此引用时常常需要具体定义其行列号。若只是提取或者更改矩阵的行列名,则采用rownames( )和colnames( )即可。由此可见,给一个矩阵的行(列)批量命名实际就是在给一个向量赋值,那么前面提到的paste( )函数就可以派上用场了。
此外,还可能需要将多个矩阵合并扩充信息量,cbind( ),rbind( )就可以实现最简单的矩阵之间的合并:前者代表按列合并,后者代表按行合并。
3.对矩阵的数学操作
矩阵作为高等数学的“宠儿”,一直作为重要的数学工具出现在我们的视野,因此R中自然少不了对矩阵数学方面的操作,最简单的加减乘除、求逆的运算等都有对应的函数来实现。值得注意的是,在R中,矩阵的加法和减法使用的符号是“+”“-”,但乘法有所不同,使用的符号是“%∗%”,而不是“∗”。下面的代码给出了矩阵A%∗% B以及A∗B的结果,你能找出它们的区别吗?
从以上代码可以发现,如果采用A∗B这种写法,只会计算出A中每个元素与B中每个元素对应相乘的结果,并不是线性代数中用到的乘法。
另外,矩阵的逆在R中需要用函数solve(M)计算(而不能直接用M^{-1} 计算得到)。还有更复杂的,比如对一个矩阵做特征值分解或者奇异值分解等操作,手工计算过程非常烦琐,而R语言可以立即给出结果。常见的使用矩阵的数学操作如表2-6所示。
R中自带的矩阵处理函数基本可以解决大部分的矩阵运算问题。但是,当矩阵规模增大时,某些矩阵的运算效率就会显得捉襟见肘,例如,矩阵求逆,特征值、奇异值求解等。为了提高大规模矩阵的运算效率,向大家推荐一个R包:r ARPACK。此包主要针对大规模矩阵运算,包内函数eigs( )可用来进行特征值分解,svds( )用来进行SVD分解。实际上,它们提高分解效率的关键在于,仅对矩阵计算一部分有代表性的特征值(奇异值)来近似分解,类似于主成分分析中只选取前几个主成分来概括原始信息的思想。下面通过一个简单的例子进行展示。
从以上例子可以看出,当分解一个1 000维的矩阵时,r ARPACK包就比基本包里的分解函数效率提升了十几倍;当维数进一步增加时,它们的差距会更大。
4.稀疏矩阵
下面介绍一种特殊矩阵——稀疏矩阵。这是一个与时俱进的新名词,近年来越来越多地被提到。
所谓稀疏矩阵,指的是这样一种矩阵:它所包含的元素中,数值为0的元素远远多于非0元素。随着现代数据采集设备越来越多,在很多领域收集到的数据都有可能带有稀疏的特征。最典型的比如电商网站的用户购买记录、社交网络中的关注关系矩阵。假如把淘宝所有商品的用户购买记录做成一个矩阵,每一列是一个商品,每一行是一个用户,其中的数值代表这个用户是否购买过这个商品,那么就可以用表2-7表示出来。
表2-7只是一个小示例,如果这是一张囊括天猫商城的购物列表,它起码会有数十万的商品列,那么每个用户所购买的商品一定只有极少的一部分,表现在矩阵里就是大量的0,这就形成了一个高度稀疏的矩阵。
再比如,把微博用户之间的关注数据抽象成邻接矩阵A,行和列都代表微博的所有用户(见表2-8)。
表2-8中,数值为1代表第i个用户关注了第j个用户,那么这必然是巨大的稀疏矩阵,因为茫茫人海中,我们关注的只能是寥寥无几(见图2-7)。
了解到稀疏矩阵的确真实存在,下面介绍R中擅长处理这类矩阵的包——Matrix。 Matrix包提供了很多独特的存储、处理稀疏矩阵的方法。比如生成一个稀疏矩阵有两个函数可用:Matrix( )函数和spMatrix( )函数。 Matrix( )函数使用的参数与普通matrix( )函数类似,通过输入数值以及行列数字来定义,区别在于需要设定参数Sparse=T或F来定义是不是稀疏矩阵。需要注意的是,虽然参数设置类似,但Matrix( )函数生成的矩阵对象却是与matrix( )完全不同的类型。如果Sparse设置为T,它会生成dgCMatrix矩阵类型,也就是以一种先将列按顺序排好再存储起来的方式存储。 spMatrix( )函数则是通过定义非0元素的行列位置来生成dgTMatrix稀疏矩阵类型,其存储方式是将非0元素所在的行、列以及它的值构成一个三元组 i,j,v ,然后再按某种规律把它们存储起来。下面通过几个例子来介绍稀疏矩阵的生成方法。
首先是Matrix( )函数。通过设定参数sparse=T就可以生成一个稀疏矩阵;如果不设定该参数,则会自动数矩阵中0的个数,超过一半就会设置为稀疏模式。下面来看一个稀疏矩阵。
其次是spMatrix( )函数。它的使用语法是spMatrix(nrow, ncol, i=integer( ), j=integer( ), x=numeric( )) ,其中前两个参数设定矩阵的行列数,i设定需要填补数字的行号,j为列号,x就是需要填补的元素,再对矩阵进行summary( )就可以统一看到它填补元素情况了。
理论上讲,这种方式更适合存储大规模的稀疏矩阵,规模越大,优势越明显;同时,矩阵中0的比例不能太低,否则不如直接存储成一般矩阵。当矩阵维数较低时,稀疏矩阵占用的内存反而比普通矩阵更大,生成时间也可能更长,但是随着矩阵维数的增加,稀疏矩阵在内存大小和生成时间方面的优势会越来越明显。
除了在存储上的优势以外,稀疏矩阵在运算上也有巨大威力,仔细观察下面的结果。
从以上命令可以发现,在做乘积运算时,存储为稀疏矩阵模式会大大提高运算效率,矩阵维数进一步增大,它们的差距就会更加明显。所以常做大规模矩阵运算的读者需要注意,如果面对的矩阵很稀疏,就可以考虑使用Matrix包。