1.4 R语言中一些重要的数据结构
R有多种数据结构。本节将简单介绍几种常用的数据结构,使读者在深入细节之前先对R语言有个大概的认识。这样,读者至少可以开始尝试一些很有意义的例子,即使这些例子背后更多的细节还需要过一段时间才能揭晓。
1.4.1 向量,R语言中的战斗机
向量类型是R语言的核心。很难想象R语言代码或者R交互式会话可以一点都不涉及向量。
向量的元素必须属于某种“模式”(mode),或者说是数据类型。一个向量可以由三个字符串组成(字符模式),或者由三个整数元素组成(整数模式),但不可以由一个整数元素和两个字符串元素组成。
第2章将详细介绍向量。
标量
标量,或单个的数,其实在R中并不存在。正如前面提到的,单个的数实际上是一元向量。请看下面的命令:
前面提到过,符号[1]表示后面这行的开头是向量的第一个元素,本例中为x[1]。所以可以看出,R语言确实把x当做向量来看,也就是只有一个元素的向量。
1.4.2 字符串
字符串实际上是字符模式(而不是数值模式)的单元素向量。
第一个例子创建了数值向量x,也就是数值模式的。然后创建了两个字符模式的向量:y是单元素(也就是一个字符串)的向量,z由两个字符串组成。
R语言有很多种字符串操作函数。其中有些函数可以把字符串连接到一起或者把它们拆开,比如下面的两个函数:
第11章将会介绍字符串的细节。
1.4.3 矩阵
R中矩阵的概念与数学中一样:矩形的数值数组。从技术层面说,矩阵是向量,不过矩阵还有两个附加的属性:行数和列数。下面是一些例子:
首先,使用函数rbind()(rbind是row bind的缩写,意思是按行绑定)把两个向量结合成一个矩阵,这两个向量是矩阵的行,并把矩阵保存在m中(另一个函数cbind()把若干列结合成矩阵)。然后键入变量名,我们知道这样可以打印出变量,以此确认生成了我们想要的矩阵。最后,计算向量(1,1)和m的矩阵积。你也许已经在线性代数课程中学过矩阵乘法运算,在R语言中它的运算符是*。
矩阵使用双下标作为索引,这点与C/C++非常相似,只不过下标是从1开始,而不是0。
R语言的一个非常有用的特性是,可以从矩阵中提取出子矩阵,这与从向量中提取子向量非常相似。例子如下:
第3章将会详细介绍矩阵。
1.4.4 列表
和R语言的向量类似,R语言中的列表也是值的容器,不过其内容中的各项可以属于不同的数据类型(C/C++程序员可以把它与C语言的结构体做类比)。可以通过两部分组成的名称来访问列表的元素,其中用到了美元符号$。下面是个简单的例子:
表达式x$u指的是列表x中的组件u。列表x还包含另一个组件v。
列表的一种常见用法是把多个值打包组合到一起,然后从函数中返回。这对统计函数特别有用,因为统计函数有时可能有复杂的结果。例如,考虑1.2节提到的R语言中基础直方图函数hist(),为R中内置的尼罗河数据集调用该函数。
hn里面是什么?我们来看看:
现在不要试图去理解上面的所有东西。现在需要知道的是,除了绘制直方图之外,hist()还会返回包含若干个组件的列表。在这里,这些组件描述了直方图的特征。例如,组件breaks告诉我们直方图里的直条从哪里开始到哪里结束,组件counts是每个直条里观测值的个数。
R语言的设计者把hist()返回的信息打包到一个R列表中,这样可以通过美元符号$来访问,并用其他R语言命令进行操作。
要打印hn,也可以直接键入它的变量名:
另一种打印列表的较为简洁方式是使用str()函数:
这里的str代表structure(结构)。这个函数可以显示任何R对象的内部结构,不只限于列表。
1.4.5 数据框
一个典型的数据集包含多种不同类型的数据。例如在一个员工数据集里,可能有字符串数据(比如员工姓名),也可能有数值数据(比如工资)。因此,如有一个50个员工的数据集,其中每个员工有4个变量,虽然这样的数据集看起来像是50行4列的矩阵,但是这在R语言中并不符合矩阵的定义,因为它混合了多种数据类型。
此时应该用数据框,而不是矩阵。R语言中的数据框其实是列表,只不过列表中每个组件是由前面提到的“矩阵”数据的一列所构成的向量。实际上,可以用下面的方式创建数据框:
不过,通常数据框是通过读取文件或数据库来创建的。
本书第5章将详细介绍数据框。
1.4.6 类
R语言是一门面向对象的编程语言。对象是类的实例。类要比你目前见过的数据类型更加抽象。本节将简单介绍S3类的使用。(名称来源于第三代S语言,这是R语言的灵感来源。)大多数R对象都是基于这些类,并且它们非常简单。它们的实例仅仅是R列表,不过还附带一个属性:类名。
例如,前面提到的直方图函数hist()的(非图形)输出是一个包含多个组件的列表,而break和count都是它的组件。它还有一个“属性”(attribute),用来指定列表的类,即histogram类。
读到这里,你可能会产生疑问:“如果S3类的对象都是列表,那为什么还需要类的概念?”答案是,类需要用在泛型函数中。泛型函数代表一个函数族,其中每个函数都有相似的功能,但是适用于某个特定的类。
一个常用的泛型函数是summary()。如果R用户想使用统计函数,如hist(),但是不确定怎样处理它的输出结果(可能会输出很多内容),输出结果不仅仅是个列表,还是个S3类,这时可以对输出结果简单地调用summary()函数。
反过来说,summary()函数实际上是生成摘要的函数族,其中每个函数处理某个特定的类。当你在某个输出结果上调用summary()函数,R会为要处理的类寻找合适的摘要函数,并使用列表的更加友好的方式来展示。因此,对hist()的输出结果调用summary()函数会生成与之相适应的摘要,而对回归函数lm()调用summary()时也会生成与之相适应的摘要。
plot()函数是另一个泛型函数。你可以对任何一个R对象使用plot()函数,R会根据对象的类寻找合适的画图函数。
类也可以用来组织对象。类与泛型函数结合使用,可以开发出灵活的代码,以处理各种不同的但是相关联的任务。第9章将讨论关于类的更深层次的话题。