2.2 数据对象

在正式开始学习R之前,我们需要明白,R是一种基于对象的语言:对象是指可以赋值给变量的任何事物,包括常量、数据结构、函数甚至是图形。R中有多种用于存储数据的对象类型,包括向量、矩阵、数组、数据框和列表。

2.2.1 向量

向量是用于存储数值型、字符型或者逻辑型数据的一维数组,是所有R进行数据分析的基础数据结构之一。在R中,没有对标量类型的正式定义,标量只是长度为1的向量,所以向量是最基础也是最常用的数据类型。由于组成元素的不同,向量可以分为数值向量、逻辑向量和字复向量。

我们可以使用函数c()可以用来创建不同类型的向量:

x <- c(1,2,3,4,5)
y <- c("a","b","c")
z <- c(TRUE,FALSE)

这里,x是数值向量,y是字符型,z是逻辑型。
上面这段代码有两点需要注意的:

  1. 在同一个向量中,只能包含相同的数据类型(数值型、字符型、逻辑型)

  2. 在R中,尽量使用“<-”进行赋值,而不是“=”(有些函数会将=解释为判断)

通过“[]”,我们可以访问向量中指定的元素,x[]表示对x向量中的元素进行操作。接下来我们通过一些代码示例对前面生成的向量来进行演示:

x[3]
## [1] 3
x[1:3]
## [1] 1 2 3
y[c(1,3)]
## [1] "a" "c"

第二个语句中的冒号用于生成一个数值序列,例如,x<-c(1:5)等价于x<-c(1,2,3,4,5)。

2.2.2 矩阵

矩阵是一个二维数组,也可以说是一个二维向量,所以在矩阵中同样只能保存相同类型的数据。在R中一般使用matrix()函数来创建矩阵,创建矩阵的方式有两种:

m1 <- matrix(1:16,nrow=4,ncol=4)
m1
##      [,1] [,2] [,3] [,4]
## [1,]    1    5    9   13
## [2,]    2    6   10   14
## [3,]    3    7   11   15
## [4,]    4    8   12   16
x<-c(1:16)
m2<-matrix(x,nrow = 4,byrow = TRUE)
m2
##      [,1] [,2] [,3] [,4]
## [1,]    1    2    3    4
## [2,]    5    6    7    8
## [3,]    9   10   11   12
## [4,]   13   14   15   16

我们使用两种方式创建了相同的两个4*4矩阵。在第二种方式中,byrow参数控制的是是否按列填充,当byrow=FALSE时,m2将成为m1的转置矩阵。有时,我们需要创建对角矩阵,R中提供diag()函数完成这个操作:

diag(1,nrow=4)
##      [,1] [,2] [,3] [,4]
## [1,]    1    0    0    0
## [2,]    0    1    0    0
## [3,]    0    0    1    0
## [4,]    0    0    0    1

我们可以使用“[]”来对矩阵中的元素进行访问。x[i,j]指矩阵中的第i行,第j列;x[i,]会访问第i行的所有元素;x[,j]则会访问矩阵中第j列的所有元素:

m1 <- matrix(1:16,nrow=4,ncol=4)
m1[2,2]
## [1] 6
m1[2,]
## [1]  2  6 10 14
m1[,2]
## [1] 5 6 7 8

首先,我们创建了4*4的矩阵,默认按列填充。然后我们访问了矩阵中第2行、第2列的元素;接着,我们又分别访问了第2行和第二列中的所有元素。

在默认情况下,创建矩阵时不会自动分配行列名。但是在使用矩阵时,我们有时会希望对行和列进行命名,以赋予行列不同的含义。我们可以在创建矩阵时通过matrix()中的dimnames参数为行列进行命名:

matrix(1:16,nrow=4,ncol=4,
       dimnames = list(c("r1","r2","r3","r4"),
                       c("n1","n2","n3","n4")))
##    n1 n2 n3 n4
## r1  1  5  9 13
## r2  2  6 10 14
## r3  3  7 11 15
## r4  4  8 12 16

我们也可以在矩阵创建完成后,在对其行列进行命名:

m1 <- matrix(1:16,nrow=4,ncol=4)
rownames(m1)<-c("r1","r2","r3","r4")
colnames(m1)<-c("n1","n2","n3","n4")
m1
##    n1 n2 n3 n4
## r1  1  5  9 13
## r2  2  6 10 14
## r3  3  7 11 15
## r4  4  8 12 16

矩阵运算符的使用

矩阵本质上来说还是向量,所以所有适用于向量的算术运算符也适用于矩阵。这些算术运算符在元素层面上进行运算。另外还有一些矩阵专用的运算符,例如矩阵乘法%*%。

m1 + m1
##    n1 n2 n3 n4
## r1  2 10 18 26
## r2  4 12 20 28
## r3  6 14 22 30
## r4  8 16 24 32
m1 - 2*m1
##    n1 n2  n3  n4
## r1 -1 -5  -9 -13
## r2 -2 -6 -10 -14
## r3 -3 -7 -11 -15
## r4 -4 -8 -12 -16
m1 * 2
##    n1 n2 n3 n4
## r1  2 10 18 26
## r2  4 12 20 28
## r3  6 14 22 30
## r4  8 16 24 32
m1 / m1
##    n1 n2 n3 n4
## r1  1  1  1  1
## r2  1  1  1  1
## r3  1  1  1  1
## r4  1  1  1  1
m1 ^ 2
##    n1 n2  n3  n4
## r1  1 25  81 169
## r2  4 36 100 196
## r3  9 49 121 225
## r4 16 64 144 256
m1 %*% m1
##     n1  n2  n3  n4
## r1  90 202 314 426
## r2 100 228 356 484
## r3 110 254 398 542
## r4 120 280 440 600

在R语言中可以通过cbind()和rbind()两个函数来实现两个矩阵的列、行合并;但需要注意的是,在进行行、列的合并时,需要确保两个矩阵的行、列的数量是相同的。比如:

m1 <- matrix(1:16,nrow=4,ncol=4)
m2 <- matrix(x,nrow = 4,byrow = TRUE)
cbind(m1,m2)
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
## [1,]    1    5    9   13    1    2    3    4
## [2,]    2    6   10   14    5    6    7    8
## [3,]    3    7   11   15    9   10   11   12
## [4,]    4    8   12   16   13   14   15   16

2.2.3 数组

数组是向量和矩阵的自然推广,是由三维或三维以上的数据构成的。本质上来说,数组仍然是一个向量,所以数组依然具有向量的性质,只能存储相同的数据类型。在R语言中,我们可以通过array()函数来创建数组:

myarray <- array(vector, dim, dimnames)

其中,vector包含了数组中的数据,dim是一个数值型向量,指定了数组的不同维度,dimnames是一个列表,可以指定不同维度的名称,例如:

arr1<-array(1:12,dim=c(2,3,2),
            dimnames=list(c("a","b"),
                          c("d","e","f"),
                          c("g","h")))
arr1
## , , g
## 
##   d e f
## a 1 3 5
## b 2 4 6
## 
## , , h
## 
##   d  e  f
## a 7  9 11
## b 8 10 12

对于已经存在的数组,可以使用dimnames()函数为数组的各个维度命名,例如:

dimnames(arr1)<-list(c("a1","a2"),
                     c("b1","b2","b3"),
                     c("c1","c2"))

同样的,我们也可以使用“[]”来对数组中的元素进行操作,例如arr1[1,2,2]为9。这里需要注意,在[1,2,2]这个坐标中:1为行坐标,第一个2为列坐标,第二个2则为第三维的坐标。

读到这里,细心的读者可以发现,向量、矩阵和数组的基本特征和基本操作几乎完全相同,而且它们都只能存储相同的数据类型,我们可以称它们为同质数据类。在R中,还存在异质数据类,即可以存储不同类型的数据。异质数据类存储更加灵活,但是在存储效率和运行效率上不如同质数据类。

2.2.4 数据框

在R语言中,数据框(dataframe)的数据结构与矩阵相似,但是其各列的数据类型可以不相同。一般情况,数据框的每列是一个变量,每行是一个观测样本。虽然,数据框内不同的列可以是不同的数据模式,但是数据框内每列的长度必须相同。

通常我们需要处理的数据集都会包含多种数据类型,如下表所示,此时此数据集无法写入矩阵或者数组,在这种情况下数据框是最佳选择。所以,数据框是R语言中最常处理的数据结构。

表 2.1: 经管学生信息
ID Male Name Birthdate Major
11 张三 12-29 工程管理
12 李四 5-6 工商管理
13 王五 8-8 国际贸易

在R语言中,我们可以调用data.frame()函数来创建数据框:

df <- data.frame(col1,col2,col3)

其中,列向量col可以为任何类型(字符型、数值型或者逻辑型),每一列的名称可由函数names()指定。或者,我们也可以在数据框创建完毕后使用colnames()和rownames()函数对其进行重命名:

student <- data.frame(ID=c(404,405,406),
                    Male=c("男","女","男"),
                    Name=c("张三","李四","王五"),
                    Birthdate=c("12-29","5-6","8-8"),
                    Major=c("工程管理","工商管理","国际贸易"))
student
##    ID Male Name Birthdate    Major
## 1 404   男 张三     12-29 工程管理
## 2 405   女 李四       5-6 工商管理
## 3 406   男 王五       8-8 国际贸易
rownames(student) <- letters[1:3]

数据框可以存储不同的数据类型,但是每一列的数据模式必须是唯一的。在进行数据分析时,我们可以以列为单位进行处理。最后一行代码的letters会自动生成abc的字母序列。

选取数据框中元素的方式有多种。我们任然可以使用”[]“对数据框中的数据进行选取,亦可以直接指定列名,还可以使用$符号,它的作用是选取给定数据框中的某个特定向量。

student[1:2]
##    ID Male
## a 404   男
## b 405   女
## c 406   男
student["Name"]
##   Name
## a 张三
## b 李四
## c 王五
student$ID
## [1] 404 405 406

上述操作默认提取的是数据框的列变量,即student[1:2]会提取数据框的第1和第2列。如果我们想要进行行操作,可以仿照对矩阵的操作形式,使用student[1,],这样我们提取出来的是student数据框的第一行,且返回值是一个数据框。 但是我们使用这种方式提取列变量时,则需要设置drop参数,写成student[,1,drop=FALSE]这种形式,否则将会返回一个向量。

赋值

我们可以使用$或者“[]”结合赋值符号“<-”对列表中的成分进行重新赋值,例如:

student$ID <- c("1","2","3")
student["Name"] <- c("a","b","c")

因子

数据框会默认的使用更有效率的利用内存的方式来存储数据,在存储的过程中,数据的类型会发生一些改变,我们可以使用str()函数进行查看:

str(student)
## 'data.frame':    3 obs. of  5 variables:
##  $ ID       : chr  "1" "2" "3"
##  $ Male     : chr  "男" "女" "男"
##  $ Name     : chr  "a" "b" "c"
##  $ Birthdate: chr  "12-29" "5-6" "8-8"
##  $ Major    : chr  "工程管理" "工商管理" "国际贸易"

如我们所看到的,字符向量会在数据框中转换为因子,相同的字符只存储一次,以节省内存。因子会带有水平(level)属性,水平指字符所有唯一取值的集合。例如,性别一列中,level为2,只有男和女两个水平。

这种存储方式提高了内存的利用效率,但是,也会产生一些问题:

student[1,"Name"] <- "d"
student
##   ID Male Name Birthdate    Major
## a  1   男    d     12-29 工程管理
## b  2   女    b       5-6 工商管理
## c  3   男    c       8-8 国际贸易

这里会产生一个警告,这是因为在最初设定Name这一列时,相应的水平集合中没有“d”这个水平。这时我们就无法赋予它一个水平集合中不存在的名称。R是一种十分占用内存的语言,所以这种设定在R语言中有其合理之处,但是有时也会产生一些问题。我们可以通过设定data.frame()中的stringsAsFactors参数来禁止它转换成因子:

student <- data.frame(ID=c(404,405,406),
                    Male=c("男","女","男"),
                    Name=c("张三","李四","王五"),
                    Birthdate=c("1994-12-29","1993-5-6","1996-8-8"),
                    Major=c("工程管理","工商管理","国际贸易"),
                    stringsAsFactors = FALSE)
str(student)
## 'data.frame':    3 obs. of  5 variables:
##  $ ID       : num  404 405 406
##  $ Male     : chr  "男" "女" "男"
##  $ Name     : chr  "张三" "李四" "王五"
##  $ Birthdate: chr  "1994-12-29" "1993-5-6" "1996-8-8"
##  $ Major    : chr  "工程管理" "工商管理" "国际贸易"

2.2.5 列表

列表(list)是R中最为复杂的一种数据结构。列表可以理解为广义的向量,是一些对象的有序集合,他可以包含各种类型的对象,甚至是其它列表。在R中,可以使用list()函数创建列表:

mylist <- list(object1,object2,...)

其中的对象可以是向量、矩阵、数组、数据框和列表。在创建列表的同时,你还可以为列表中的对象命名,当然你也可以在创建完之后使用names()函数对它进行重命名:

mylist <- list(name1 = object1,name2 = object2,...)

创建列表

a <- "my list"
b <- c(1:5)
c <- matrix(1:10,nrow = 5)
d <- c("x1","x2","x3")
list1 <- list(a,b,c,d)
names(list1) <- c("r1","r2","r3","r4")
list1
## $r1
## [1] "my list"
## 
## $r2
## [1] 1 2 3 4 5
## 
## $r3
##      [,1] [,2]
## [1,]    1    6
## [2,]    2    7
## [3,]    3    8
## [4,]    4    9
## [5,]    5   10
## 
## $r4
## [1] "x1" "x2" "x3"

提取列表中的元素有几种不同的方式,最常用的方法是使用美元符号$,通过成分(向量、矩阵等)的名称来提取列表中的成分。在提取出成分来之后,例如向量,还可以通过对向量的操作提取其中的元素:

list1$r1
## [1] "my list"
list1$r2[1]
## [1] 1

还可以使用“[]”来访问列表中的元素。但是需要注意list1[2]返回的仍然是一个列表,list[[2]]返回的是列表中第2个元素的成分,及向量b。另外,在列表中还可以使用名称直接访问列表中的成分,list1[[2]]和list1[[“r2”]]是等价的。

list1[2]
## $r2
## [1] 1 2 3 4 5
list1[[2]]
## [1] 1 2 3 4 5
list1[["r2"]]
## [1] 1 2 3 4 5

由于列表存储数据的灵活性,它成为了R语言中非常重要的数据结构。列表允许以一种简单的方式组织和重新调用不相干的信息;另外,在R语言中,许多函数的运行结果是以列表的形式返回的。列表中的数据可以灵活的访问和提取,所以,列表成为了R语言中非常重要的数据结构。