目录
1. attach(), detach() 和 with()
R拥有许多用于存储数据的对象(objects)类型,包括标量(scalars), 向量(vectors), 矩阵(matrices), 数组(arrays), 数据框(data frames) 和列表(lists). 它们存在存储数据的类型、创建方式、结构复杂度,以及用于定位和访问其中个别元素的标记等方面均有所不同。下图给出了这些数据结构的一个示意图:
让我们从向量开始,逐个探究每一种数据结构。
一些定义
R 中有一些术语较为独特,可能会对用户造成困扰。
在 R 中,对象(object) 是指可以赋值给变量(variable) 的任何事物,包括常量、数据结构、函数,甚至图形。对象都拥有某种模式(mode), 模式描述了此对象是如何存储的;以及某个类(class), 类指示像 print 这样的泛型函数(generic functions) 如何处理此对象。
与其他标准统计软件(如 SAS, SPSS 和 Stata)中的数据集类似,数据框(data frame) 是 R 中用于存储数据的一种结构:列表示变量(variables), 行表示观测(observations). 在同一个数据框中可以存储不同类型(如数值型 numeric 或字符型 character)的变量。数据框将是你用来存储数据集的主要数据结构。
因子(factors) 是名义型变量或有序型变量。它们在 R 中被特殊地存储和处理,之后的博文中会详细介绍因子的处理。
其他多数术语你应该比较熟悉了,它们基本都遵循统计和计算中术语的定义。
1. 向量(Vectors)
向量(Vectors) 是用于存储数值型(numeric), 字符型(character) 或逻辑型(logical) 数据的一维数组。执行组合功能函数(combine function) c() 可用来创建向量。各类向量如下例所示:
a<-c(1,2,5,3,6,-2,4)
b<-c("one","two","three")
c<-c(TRUE,TRUE,TRUE,FALSE,TRUE,FALSE)
这里,a 是数值型向量,b 是字符型向量,而 c 是逻辑型向量。注意,单个向量中的数据必须拥有相同的类型或模式 mode(数值型 numeric, 字符型 character, or 逻辑型 logical). 同一向量中无法混杂不同模式的数据。
注意:标量(Scalars) 是只含一个元素的向量,例如
f<-3
g<-"US"
h<-TRUE
它们用于保存常量。
通过在方括号中给定元素所处位置的数值,我们可以访问向量中的元素。例如,a[c(2,4)] 用于访问向量 a 中的第二个和第四个元素:
> a<-c(1,2,5,3,6,-2,4)
> a[c(2,4)]
[1] 2 3
更多示例如下:
> a<-c("k","j","h","a","c","m")
> a[3]
[1] "h"
>
> a[c(1,3,5)]
[1] "k" "h" "c"
>
> a[2:6]
[1] "j" "h" "a" "c" "m"
最后一个语句 a[2:6] 中使用的冒号用于生成一个数值序列。例如,a<-c(2:6) 等价于 a<-c(2,3,4,5,6)
> a<-c(2:6)
> a
[1] 2 3 4 5 6
>
> a<-c(2,3,4,5,6)
> a
[1] 2 3 4 5 6
2. 矩阵(Matrices)
矩阵(matrix) 是一个二维数组(array), 只是每个元素都拥有相同的模式 mode(数值型 numeric, 字符型 character 或 逻辑型 logical). 可通过函数 matrix() 创建矩阵。一般使用格式为:
myymatrix <- matrix(vector, nrow=number_of_rows, ncol=number_of_columns,
byrow=logical_value, dimnames=list(
char_vector_rownames, char_vector_colnames))
其中 vector 包含了矩阵的元素,nrow 和 ncol 用以指定行和列的维数,dimnames 包含了可选的、以字符型向量表示的行名和列名。 选项 byrow 则表明矩阵应当按行填充(byrow=TRUE) 还是按列填充(byrow=FALSE), 默认情况下按列填充。代码清单 2-1 中的代码演示了 matrix() 函数的用法。
> #-------------------------------------------------------------------------
> # 代码清单2-1 创建矩阵
> #-------------------------------------------------------------------------
> y<-matrix(1:20,nrow=5,ncol=4) # 1. 创建一个5*4的矩阵
> y
[,1] [,2] [,3] [,4]
[1,] 1 6 11 16
[2,] 2 7 12 17
[3,] 3 8 13 18
[4,] 4 9 14 19
[5,] 5 10 15 20
>
> cells<-c(1,26,24,68)
> rnames<-c("R1", "R2")
> cnames<-c("C1", "C2")
>
> mymatrix<-matrix(cells,nrow=2,ncol=2,byrow=TRUE, # 2. 按行填充的2*2矩阵
+ dimnames=list(rnames,cnames))
> mymatrix
C1 C2
R1 1 26
R2 24 68
>
> mymatrix<-matrix(cells,nrow=2,ncol=2,byrow=FALSE, # 3. 按列填充的2*2矩阵
+ dimnames=list(rnames,cnames))
> mymatrix
C1 C2
R1 1 24
R2 26 68
> #-------------------------------------------------------------------------
- 我们首先创建了一个 的矩阵;
- 接着创建了一个 的含行、列名标签的矩阵,并按行进行填充;
- 接着创建了一个 的含行、列名标签的矩阵,并按列进行填充;
我们可以使用下标和方括号来选择矩阵中的行、列或元素。 x[1,] 指矩阵 x 中的第 i 行,x[,j] 指第 j 列,x[i,j] 指第 i 行第 j 个元素。选择多行或多列时,下标 i 和 j 可为数值型向量,如代码清单 2-2 所示:
> #----------------------------------------------------------------------------------------
> # 代码清单2-2 矩阵下标的使用
> #----------------------------------------------------------------------------------------
> x<-matrix(1:10,nrow=2) # 创建一个内容为数字1到10的2*5矩阵,默认情况下,矩阵按列填充
> x
[,1] [,2] [,3] [,4] [,5]
[1,] 1 3 5 7 9
[2,] 2 4 6 8 10
> x[2,] # 选择第2行的元素
[1] 2 4 6 8 10
> x[,2] # 选择第2列的元素
[1] 3 4
> x[1,4] # 选择第1行第4列的元素
[1] 7
> x[1,c(4,5)] # 选择第1行第4、5列的元素
[1] 7 9
> #----------------------------------------------------------------------------------------
矩阵都是二维的,和向量类似,矩阵中也仅能包含一种数据类型。当维度超过 2 时,不妨使用数组(arrays). 当有多种模式的数据时,可以使用数据框(data frames).
3. 数组(Arrays)
数组(arrays) 与矩阵类似,但是数组的维度可以大于 2. 数组可通过 array 函数创建,形式如下:
myarray <- array(vector, dimensions, dimnames)
其中 vector 包含了数组中的数据,dimensions 是一个数值型向量,给出了各个维度下标的最大值,而 dimnames 是可选的(optional)、各维度名称标签的列表。代码清单 2-3 给出了一个创建三维( ) 数值型数组的示例。
> #----------------------------------------------------------
> # 代码清单2-3 创建一个数组
> #----------------------------------------------------------
> dim1<-c("A1","A2")
> dim2<-c("B1","B2","B3")
> dim3<-c("C1","C2","C3","C4")
> z<-array(1:24,c(2,3,4),dimnames=list(dim1,dim2,dim3))
> z
, , C1
B1 B2 B3
A1 1 3 5
A2 2 4 6
, , C2
B1 B2 B3
A1 7 9 11
A2 8 10 12
, , C3
B1 B2 B3
A1 13 15 17
A2 14 16 18
, , C4
B1 B2 B3
A1 19 21 23
A2 20 22 24
> #----------------------------------------------------------
4. 数据框(Data frames)
由于不同的列可以包含不同模式(数值型、字符型等)的数据,数据框(Data frames) 比矩阵更为常见。它与你通常在 SAS, SPSS 和 Stata 中看到的数据集类似。数据框将是你在 R 中最常处理的数据结构。
之前介绍过的 mpg 数据集包含了数值型和字符型数据。由于数据有多种模式,无法将此数据集放入一个矩阵。在这种情况下,使用数据框是最佳选择。
> library(tidyverse)
> library(ggplot2)
> dim(mpg)
[1] 234 11
> mpg[1:10,]
# A tibble: 10 x 11
manufacturer model displ year cyl trans drv cty hwy fl class
<chr> <chr> <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
1 audi a4 1.8 1999 4 auto(l5) f 18 29 p compact
2 audi a4 1.8 1999 4 manual(m5) f 21 29 p compact
3 audi a4 2 2008 4 manual(m6) f 20 31 p compact
4 audi a4 2 2008 4 auto(av) f 21 30 p compact
5 audi a4 2.8 1999 6 auto(l5) f 16 26 p compact
6 audi a4 2.8 1999 6 manual(m5) f 18 26 p compact
7 audi a4 3.1 2008 6 auto(av) f 18 27 p compact
8 audi a4 quattro 1.8 1999 4 manual(m5) 4 18 26 p compact
9 audi a4 quattro 1.8 1999 4 auto(l5) 4 16 25 p compact
10 audi a4 quattro 2 2008 4 manual(m6) 4 20 28 p compact
数据框可通过函数 data.frame() 创建:
mydata <- data.frame(col1, col2, col3,...)
其中列向量 col1, col2, col3 等可为任何类型(如字符型、数值型或逻辑型)。每一列的名称可由函数 names 指定。代码清单 2-4 清晰地展示了相应用法。
> #----------------------------------------------------------
> # 代码清单2-4 创建一个数据框
> #----------------------------------------------------------
> patientID<-c(1,2,3,4)
> age<-c(25,34,28,52)
> diabetes<-c("Type1","Type2","Type1","Type1")
> status<-c("Poor","Improved","Excellent","Poor")
> patientdata<-data.frame(patientID,age,diabetes,status)
> patientdata
patientID age diabetes status
1 1 25 Type1 Poor
2 2 34 Type2 Improved
3 3 28 Type1 Excellent
4 4 52 Type1 Poor
> #----------------------------------------------------------
每一列数据的模式必须唯一,不过你却可以将多个模式的不同列放到一起组成数据框。由于数据框与分析人员通常设想的数据集的形态较为接近,我们在讨论数据框时将交替使用术语列(columns) 和变量(variables).
选取数据框中元素的方式有若干种。你可以使用前述(如矩阵中的)下标记号,亦可直接指定列名。代码清单 2-5 使用之前创建的 patientdata 数据框演示了这些方式。
> #----------------------------------------------------------
> # 代码清单2-5 选取数据框中的元素
> #----------------------------------------------------------
> patientdata[1:2]
patientID age
1 1 25
2 2 34
3 3 28
4 4 52
> patientdata[c("diabetes","status")]
diabetes status
1 Type1 Poor
2 Type2 Improved
3 Type1 Excellent
4 Type1 Poor
> patientdata$age # 表示 patientdata 数据框中的变量 age
[1] 25 34 28 52
> #----------------------------------------------------------
patientdata$age 中的记号 $ 是新出现的。它被用来选取一个给定数据框中的某个特定变量。例如,如果你想生成糖尿病类型变量 diabetes 和病情变量 status 的列联表(cross-tabulate diabetes type by status),使用以下代码即可:
> table(patientdata$diabetes, patientdata$status)
Excellent Improved Poor
Type1 1 0 2
Type2 0 1 0
在每个变量名前都键入一次 patientdata$ 可能会让人生厌,所以不妨走一些捷径。可以联合使用函数 attach() 和 detach() 或者单独使用函数 with() 来简化代码。
1. attach(), detach() 和 with()
函数 attach() 可将数据框添加到 R 的搜索路径中。R 在遇到一个变量名以后,将检查搜索路径中的数据框。以 R 的内置的汽车数据 mtcars(和之前分析过的名为 mpg 的数据集不同,在 mtcars 数据集中 mpg 是一个变量)为例,可以使用以下代码获取英里 / 加仑,即每加仑燃料所行英里数(mpg) 变量的描述性统计量,并分别绘制此变量与发动机排量(disp) 和车身重量(wt) 的散点图:
summary(mtcars$mpg)
plot(mtcars$mpg, mtcars$disp)
plot(mtcars$mpg, mtcars$wt)
# 以上代码也可写成
attach(mtcars)
summary(mpg)
plot(mpg, disp)
plot(mpg, wt)
detach(mtcars)
函数 detach() 将数据框从搜索路径中移除。值得注意的是,detach() 并不会对数据框本身做任何处理。这句是可以省略的,但它其实应当被例行地放入代码中,因为这是一个好的编程习惯。
当名称相同的对象不止一个时,这种方法的局限性就很明显了。考虑以下代码:
> mpg<-c(25,36,47)
> attach(mtcars)
The following object is masked _by_ .GlobalEnv:
mpg
> plot(mpg,wt)
Error in xy.coords(x, y, xlabel, ylabel, log) :
'x' and 'y' lengths differ
> mpg
[1] 25 36 47
这里,在数据框 mtcars 被绑定(attach) 之前,你们的环境中已经有了一个名为 mpg 的对象。在这种情况下,原始对象将取得优先权,这与你们想要的结果有所出入。由于 mpg 中有 3 个元素而 disp 中有 32 个元素,
> dim(mtcars)
[1] 32 11
> mtcars$disp
[1] 160.0 160.0 108.0 258.0 360.0 225.0 360.0 146.7 140.8 167.6 167.6 275.8
[13] 275.8 275.8 472.0 460.0 440.0 78.7 75.7 71.1 120.1 318.0 304.0 350.0
[25] 400.0 79.0 120.3 95.1 351.0 145.0 301.0 121.0
故 plot 语句出错。函数 attach() 和 detach() 最好在你分析一个单独的数据框,并且不太可能有多个同名对象时使用。任何情况下,都要当心那些告知某个对象已被屏蔽(masked) 的警告。
除此之外,另一种方式是使用函数 with(). 可以这样重写上例:
with(mtcars,{
print(summary(mpg))
plot(mpg,disp)
plot(mpg,wt)
})
在这种情况下,花括号{ } 之间的语句都针对数据框 mtcars 执行,这样就无须担心名称冲突了。如果仅有一条语句(例如 summary(mpg)),那么花括号{ } 可以省略。
函数 with() 的局限性在于,赋值仅在此函数的括号内生效。考虑以下代码:
> with(mtcars,{
+ stats <- summary(mpg)
+ stats
+ })
Min. 1st Qu. Median Mean 3rd Qu. Max.
10.40 15.43 19.20 20.09 22.80 33.90
> stats
错误: 找不到对象'stats'
如果你需要创建在 with() 结构以外存在的对象,使用特殊赋值符 <<- 替代标准赋值符(<-) 即可,它可将对象保存到 with() 之外的全局环境中。这一点可通过以下代码阐明:
> with(mtcars,{
+ nokeepstats<-summary(mpg)
+ keepstats<<-summary(mpg)
+ })
> nokeepstats
错误: 找不到对象'nokeepstats'
> keepstats
Min. 1st Qu. Median Mean 3rd Qu. Max.
10.40 15.43 19.20 20.09 22.80 33.90
相对于 attach(), 多数的 R 书籍更推荐使用 with(). 个人认为从根本上说,选择哪一个是自己的偏好问题,并且应当根据你的目的和这两个函数含义的理解而定。我们会交替使用这两个函数。
2. 实例标识符(Case Identifiers)
> patientID<-c(1,2,3,4)
> age<-c(25,34,28,52)
> diabetes<-c("Type1","Type2","Type1","Type1")
> status<-c("Poor","Improved","Excellent","Poor")
> patientdata<-data.frame(patientID,age,diabetes,status)
> patientdata
patientID age diabetes status
1 1 25 Type1 Poor
2 2 34 Type2 Improved
3 3 28 Type1 Excellent
4 4 52 Type1 Poor
在病例数据中,病人编号(patientID) 用于区分数据集中不同的个体。在 R 中,实例标识符(case identifiers) 可通过数据框操作函数中的 rowname 选项指定。例如,语句:
patientdata<-data.frame(patientID,age,diabetes,status,
row.names=patientID)
将 patientID 指定为 R 中标记各类打印输出和图形中实例名称所用的变量。