2.2 数据对象
R拥有许多用于存储数据的对象类型,包括向量、矩阵、数组、数据框和列表。它们在存储数据的类型、创建方式、结构复杂度,以及用于定位和访问其中个别元素的标记等方面均有所不同。多样化的数据对象赋予了R灵活处理数据的能力。
R中有许多数据类型用来存储各种各样的数据,包括数值型(numeric)、逻辑型(logical)、日期型(date)、字符型(character)、复数型(complex)、原味型(二进制形式保存数据raw)。此外,也可能是缺省值(NA)和空值(NULL)。其中最经常用到的4种类型是数值型、逻辑型、日期型和字符型。
对象中存储的数据类型可以用mode()函数查看。R中提供了一系列用来判断某个对象的数据类型和将其转换为另一种数据类型的函数,如表2-1所示。
R数据对象的另一个基本属性是长度和类型属性。可以用length()函数读取对象的长度,通过mode()函数读取对象的类型。
2.2.1 向量
1.向量的创建
R语言最基本的数据对象是向量(vector),向量是以一维数组的方法管理数据。在大多数情况下都会使用长度大于1的向量,可以在R中使用c()函数(代表合并combine)和相应的参数来创建一个向量。向量的数据类型可以是字符型、逻辑值型(TRUE/T、FALSE/F)、数值型和复数型。一个向量的长度是它含有元素的数量,可以用length()函数来获取。接下来,利用几个小例子帮助大家理解。
> (w<-c(1,3,4,5,6,7)) #创建数值型向量
[1] 1 3 4 5 6 7
> length(w) #查看向量的长度
[1] 6
> mode(w) #查看向量的数据类型
[1] "numeric"
> (w1<-c("张三","李四","王五")) #创建字符型向量
[1] "张三" "李四" "王五"
> length(w1) #查看向量的长度
[1] 3
> mode(w1) #查看向量的数据类型
[1] "character"
> (w2<-c(T,F,T)) #创建逻辑型向量
[1] TRUE FALSE TRUE
> length(w2) #查看向量的长度
[1] 3
> mode(w2) #查看向量的数据类型
[1] "logical"
一个向量的所有元素都必须属于相同的模式。如果不是,R将强制执行类型转换。例如:
> w3 <- c(w,w2) # 数值型+逻辑型=数值型
> w3
[1] 1 3 4 5 6 7 1 0 1
> mode(w3)
[1] "numeric"
> w4<-c(w,w1) # 数值型+字符型=字符型
> w4
[1] "1" "3" "4" "5" "6" "7" "张三" "李四" "王五"
> mode(w4)
[1] "character"
> w5<-c(w1,w2) # 字符型+逻辑型=字符型
> w5
[1] "张三" "李四" "王五" "TRUE" "FALSE" "TRUE"
> mode(w5)
[1] "character"
从以上例子可知,当数值型与逻辑型向量合并时,R会将逻辑型转化为数值型,TRUE转换成1,FALSE转换成0,此时的向量是数值型向量;当数值型或逻辑值与字符型合并时,R会将其值强制转换成字符类型(数字和逻辑值元素都加上双引号),变成字符型向量。
2.向量的运算
因为R语言是矢量化的语言,所以其最强大的方面之一就是函数的向量化。这意味着操作会自动应用于向量的每一个元素,而不需要遍历向量的每个元素。例如,创建一个向量w,元素由1~10的序列组成,要对向量w中的每个元素进行开方根,只需要进行如下操作:
> (w<-seq(1:10))
[1] 1 2 3 4 5 6 7 8 9 10
> (x<-round(sqrt(w),3))
[1] 1.000 1.414 1.732 2.000 2.236 2.449 2.646 2.828 3.000 3.162
sqrt函数直接作用于w中的每个元素进行开方根,不需要通过对w中的每个元素进行循环的方式实现。
也可以利用R的这个特性进行向量的四则运算。
> rm(list=ls())
> (w1<-c(2,3,4))
[1] 2 3 4
> (w2<-c(3.1,4.2,5.3))
[1] 3.1 4.2 5.3
> (w<-w1+w2)
[1] 5.1 7.2 9.3
向量w1和w2中相同位置的元素会进行相加。例如,w1的第一个元素是2,w2的第一个元素是3.1,相加后,w的第一个元素就是2+3.1=5.1。
如果两个向量的长度不同,R将利用循环规则,该规则重复较短的向量元素,直到得到的向量长度与较长的向量的长度相同。
# 例一
> rm(list=ls())
> (w1<-c(2,4,6,8))
[1] 2 4 6 8
> (w2<-c(10,12))
[1] 10 12
> (w<-w1+w2)
[1] 12 16 16 20
# 例二
> rm(list=ls())
> (w1<-c(2,4,6,8))
[1] 2 4 6 8
> (w2<-c(10,12,14))
[1] 10 12 14
> (w<-w1+w2)
[1] 12 16 20 18
Warning message:
In w1 + w2 : 长的对象长度不是短的对象长度的整倍数
在例一中,因为w1的长度是w2的两倍,所以w2先补长为c(10,12,10,12)再与w1求和;在例二中,由于w1的长度不是w2的整倍数,不会出错但是会有警告信息,所以w2会补长为(10,12,14,10)再与w1求和。
3.生成数列
冒号运算符(:)会生成增量为1或者-1的数列。如果想生成1~10,增量为1的等差数列,或者是10~1,增量为-1的等差数列,执行以下代码即可。
> 1:10
[1] 1 2 3 4 5 6 7 8 9 10
> 10:1
[1] 10 9 8 7 6 5 4 3 2 1
对于增量不为1的数列,可以使用seq函数。seq函数基本形式如下:
seq(from = 1, to = 1, by = ((to - from)/(length.out - 1)),
length.out = NULL, along.with = NULL, ...)
seq函数的主要参数如表2-2所示。
只给出首项和尾项数据,by自动匹配为1或-1。
> seq(1,9)
[1] 1 2 3 4 5 6 7 8 9
> seq(1,-9)
[1] 1 0 -1 -2 -3 -4 -5 -6 -7 -8 -9
给出首项和尾项数据以及长度,会自动计算等差的数值。
> seq(1,-9,length.out=5)
[1] 1.0 -1.5 -4.0 -6.5 -9.0
上例中,因为首项是1,尾项是-9,向量长度是5,所以等差的数值by=(-9-1)/(5-1)=-2.5。
给出首项和尾项数据以及等差的数值,会自动计算长度。
> seq(1,-9,by=-2)
[1] 1 -1 -3 -5 -7 -9
给出首项和等差的数值以及长度,会自动计算尾项。
> seq(1,by=2,length.out=10)
[1] 1 3 5 7 9 11 13 15 17 19
rep()是重复函数,它可以将某一向量重复若干次。其基本形式是rep(x,…)。其中x是预重复的序列,可以是任意数据类型的向量或数值。
> rep(1:4,times=2)
[1] 1 2 3 4 1 2 3 4
设置times参数为2时,会将序列1,2,3,4重复两次,如果希望得到向量中每个元素先重复两次的重复数列,可以设置参数each=2。
> rep(1:4,each=2)
[1] 1 1 2 2 3 3 4 4
也可以将向量赋予each参数。例如,想实现1,1,2,3,3,4这样的重复数列,可以将向量c(2,1,2,1)赋予参数each。
> rep(1:4, c(2,1,2,1))
[1] 1 1 2 3 3 4
与seq函数一样,也可以通过参数length.out(可简写为len)设置重复数列的长度。
> rep(1:4, times = 2, lenth.out = 6)
[1] 1 2 3 4 1 2
rep(1:4,times = 2)重复数列的长度是8,但是因为参数length.out限制了长度是6,所以输出结果是1 2 3 4 1 2。
> rep(1:4, times = 2, length.out = 10)
[1] 1 2 3 4 1 2 3 4 1 2
可见,如果设置的长度大于数列长度(10>8),则会以循环补齐的方式补全。
此外,还有letters和LETTERS函数,可以生成26个英文小写字母和大写字母。
> letters
[1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
> LETTERS
[1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" "T" "U" "V" "W" "X" "Y" "Z"
4.向量索引
通常,只需要访问向量中的部分或个别元素,可以通过在向量名后的方括号[ ]中加入向量索引来实现。
根据元素在向量中的位置选出元素,它的初始位置是1(而不像其他某些语言一样是0)。
索引前加负号(-),排除向量中对应位置的元素,返回其他位置的元素。
使用向量索引来选择多个元素值。
使用逻辑向量根据条件来选择元素。
使用表达式选择元素。
以下三种索引方式都将返回相同的值。
> set.seed(1234)
> x <- rnorm(5)
> # 以下三种方法返回相同的值:
> set.seed(1234)
> x <- rnorm(5)
> x[c(1,3,5)]
[1] -1.2070657 1.0844412 0.4291247
> x[c(-2,-4)]
[1] -1.2070657 1.0844412 0.4291247
> x[c(TRUE,FALSE,TRUE,FALSE,TRUE)]
[1] -1.2070657 1.0844412 0.4291247
> x[c(1,-2)] # 混合使用正负值是不允许的,会抛出一个错误
Error in x[c(1, -2)] : 只有负下标里才能有零
假如需要提取x中大于0的元素,只需要在中括号中增加表达式x>0即可。背后原理就是x>0得到一个逻辑向量,然后返回逻辑值为TRUE的元素。
> x>0
[1] FALSE TRUE TRUE FALSE TRUE
> x[x>0]
[1] 0.2774292 1.0844412 0.4291247
which函数将返回逻辑向量中为TRUE 的位置。例如,找出向量v中大于5的元素位置。
> set.seed(123)
> v <- sample(1:10,5)
> v
[1] 3 8 4 7 6
> which(v>5)
[1] 2 4 5
利用sample函数对序列1~10进行无放回随机抽取5个,得到向量v为c(3,8,4,7,6),因此大于5返回的位置是2,4,5。
可以利用函数which.min和which.max查找向量中最大值和最小值的下标。
> which.min(v)
[1] 1
> which.max(v)
[1] 2
2.2.2 矩阵与数组
利用矩阵matrix可以描述二维数据,与向量相似,其内部元素可以是实数、复数、字符、逻辑型数据。矩阵matrix使用两个下标来访问元素,A[i,j]表示矩阵A第i行、第j列的元素。
多维数组array可以描述多维数据。array有一个特征属性叫维数向量(dim属性),它的长度是多维数组的维数,dim内的元素则是对应维度的长度。矩阵是数组的特殊情况,它具有两个维度。
在R中,可以使用matrix函数并以向量形式输入矩阵中的全部元素,使用ncol和nrow可设置矩阵的行数和列数,从而创建一个矩阵。
> (w<-seq(1:10))
[1] 1 2 3 4 5 6 7 8 9 10
> (a<-matrix(w,nrow=5,ncol=2))
[,1] [,2]
[1,] 1 6
[2,] 2 7
[3,] 3 8
[4,] 4 9
[5,] 5 10
先创建一个1~10数列的向量w,然后利用matrix函数将矩阵的行数设置为5,列数设置为2,得到矩阵a。注意,矩阵a的数据是按照列填充的,而在实际工作中,数据可能更多地是按照行填充,这时只需要将参数byrow设置为TRUE即可。
> (a<-matrix(w,nrow=5,ncol=2,byrow=T)) #按行填充
[,1] [,2]
[1,] 1 2
[2,] 3 4
[3,] 5 6
[4,] 7 8
[5,] 9 10
给矩阵的行列命名有助于提高数据的可读性,可通过参数dimnames实现。
> (a<-matrix(w,nrow=5,ncol=2,byrow=T,
+ dimnames=list(paste0("r",1:5),paste0("l",1:2)))) #给行列设置名称
l1 l2
r1 1 2
r2 3 4
r3 5 6
r4 7 8
r5 9 10
函数cbind()把其自变量横向拼成一个大矩阵,可以想象为水平地将矩阵拼在一起,rbind()把其自变量纵向拼成一个大矩阵,可以将其想象为垂直地将矩阵拼在一起。
当函数cbind()的自变量是矩阵或看作列向量的向量时,自变量的行数应该相等。当rbind()的自变量是矩阵或看作行向量的向量时,自变量的列数应该相等。如果参与合并的自变量比其变量短,则循环不足后合并。例如:
> (x1<-rbind(c(1,2),c(3,4))) # 创建向量x1
[,1] [,2]
[1,] 1 2
[2,] 3 4
> (x2<-10+x1) # 创建向量x2
[,1] [,2]
[1,] 11 12
[2,] 13 14
> (x3<-cbind(x1,x2)) # 将向量x1和x2水平(列)合并
[,1] [,2] [,3] [,4]
[1,] 1 2 11 12
[2,] 3 4 13 14
> (x4<-rbind(x1,x2)) # 将向量x1和x2垂直(行)合并
[,1] [,2]
[1,] 1 2
[2,] 3 4
[3,] 11 12
[4,] 13 14
> cbind(1,x1) # 当1长度小于x1时,会先自动补全为c(1,1),再与x1进行列合并
[,1] [,2] [,3]
[1,] 1 1 2
[2,] 1 3 4
通过函数as.vector(A)可以将矩阵转化为向量。例如:
> (A<-matrix(1:6,nrow=2))
[,1] [,2] [,3]
[1,] 1 3 5
[2,] 2 4 6
> as.vector(A)
[1] 1 2 3 4 5 6
可以使用colSums(列求和)、colMeans(列求平均)、rowSums(行求和)、rowMeans(行求平均)函数对矩阵的行、列求和或者求平均值,也可使用apply函数实现。
> (A <- matrix(1:16,4,4))
[,1] [,2] [,3] [,4]
[1,] 1 5 9 13
[2,] 2 6 10 14
[3,] 3 7 11 15
[4,] 4 8 12 16
> colSums(A) # 等价于 apply(A,2,sum)
[1] 10 26 42 58
> colMeans(A) # 等价于 apply(A,2,mean)
[1] 2.5 6.5 10.5 14.5
> rowSums(A) # 等价于 apply(A,1,sum)
[1] 28 32 36 40
> rowMeans(A) # 等价于 apply(A,1,mean)
[1] 7 8 9 10
矩阵A第一列的元素是1、2、3、4,故第一列的和是1+2+3+4=10,第一列的平均值是(1+2+3+4)/4=2.5。
数组是矩阵的扩展,它把数据的维度扩展到两个以上。这意味着数组中的元素需要两个以上的索引。除此之外,数组与矩阵类似,可以使用相同的方法。与函数matrix( )类似,可以通过函数array( )方便地创建数组。
> (w<-array(1:30,dim=c(3,5,2)))
, , 1
[,1] [,2] [,3] [,4] [,5]
[1,] 1 4 7 10 13
[2,] 2 5 8 11 14
[3,] 3 6 9 12 15
, , 2
[,1] [,2] [,3] [,4] [,5]
[1,] 16 19 22 25 28
[2,] 17 20 23 26 29
[3,] 18 21 24 27 30
上面表示建立一个三维数据的数组,其维度是3×5×2。在结果中会依次展示2个3行5列的矩阵。
2.2.3 列表和数据框
列表list和数据框data.frame也是一个二维数据,其中向量vector、多维数组array以及矩阵matrix存储的元素,其数据类型是唯一的。列表和数据框内每列元素的数据类型可以不同,列表内的长度也可以不同。一般在使用R语言进行数据分析和挖掘的过程中,向量和数据框的使用频率是最高的,列表则在存储较复杂的数据时作为数据对象类型。
1.列表
列表可以使用list函数创建,函数的每一个参数变成列表的元素。
> user.list<-list(user.id=34453,
+ user.name="张三",
+ user.games=c('地铁跑酷','神庙逃亡2','水果忍者','苍穹变'))
> user.list
$user.id
[1] 34453
$user.name
[1] "张三"
$user.games
[1] "地铁跑酷" "神庙逃亡2" "水果忍者" "苍穹变"
对象user.list由三个成分组成:第一个是名称为user.id的数值,第二个是名称为user.name的字符串,第三个是名称为user.games的字符型向量。
可以使用length函数来检查列表成分的个数。
> length(user.list) #检查列表成分个数
[1] 3
可以通过函数unlist()把列表中的所有元素转换为向量元素,转换后向量元素的个数和列表中所有数据对象的个数相同。
> unlist(user.list) #转换成向量元素
user.id user.nameuser.games1 user.games2 user.games3 user.games4
"34453" "张三" "地铁跑酷" "神庙逃亡2" "水果忍者" "苍穹变"
2.数据框
数据框是仅次于向量的最重要的数据对象类型。在R语言中,很多数据分析算法函数的输入对象都是数据框对象。而且,在使用读取excel/csv/txt等格式数据集的函数时,也是以数据框对象输入的。类似于list,数据框也可以由不同的向量作为列来合成,并且不同列之间的元素可以是不同的数据类型。但是数据框并没有list那么灵活,数据框内每个列的长度必须相同。在实际生产环境中,通常会用数据框的一列代表某一变量属性的所有取值,用一行代表某一个样本数据。
data.frame()函数可以直接将多个向量建立为一个数据框,并为列设置名称。
> my.dataset<-data.frame(userid=c("S001","S002","S003","S004","S005"),
+ gamename=c('地铁跑酷','神庙逃亡2','水果忍者','水果忍者','机战王'),
+ iamount=c(100,50,30,60,70))
> my.dataset
userid gamename iamount
1 S001 地铁跑酷 100
2 S002 神庙逃亡2 50
3 S003 水果忍者 30
4 S004 水果忍者 60
5 S005 机战王 70
我们创建了一个名为my.dataset的数据框,包含三列数据,其中第一列和第二列是字符型的数据类型,第三列是数值型的数据对象。
可以通过names函数查看数据框的变量名称。
> names(my.dataset)
[1] "userid" "gamename" "iamount"
也可以通过colnames函数查看变量名称,rownames函数查看记录名称。以mtcars数据集为例:
> head(mtcars) #查看前六行数据
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21.0 6 160 1103.90 2.620 16.46 0 1 4 4
Mazda RX4 Wag 21.0 6 160110 3.90 2.875 17.02 0 1 4 4
Datsun 710 22.8 4 108 93 3.85 2.320 18.61 1 1 4 1
Hornet 4 Drive 21.4 6 258 110 3.08 3.215 19.44 1 0 3 1
Hornet Sportabout 18.7 8 360 175 3.15 3.440 17.02 0 0 3 2
Valiant 18.1 6 225 105 2.763.460 20.22 1 0 3 1
> colnames(mtcars) # 查看变量名称
[1] "mpg" "cyl" "disp" "hp" "drat" "wt" "qsec" "vs" "am" "gear"
[11] "carb"
> rownames(mtcars) # 查看记录名称
[1] "Mazda RX4" "Mazda RX4 Wag" "Datsun 710"
[4] "Hornet 4 Drive" "Hornet Sportabout" "Valiant"
[7] "Duster 360" "Merc 240D" "Merc 230"
[10] "Merc 280" "Merc 280C" "Merc 450SE"
[13] "Merc 450SL" "Merc 450SLC" "Cadillac Fleetwood"
[16] "Lincoln Continental" "Chrysler Imperial" "Fiat 128"
[19] "Honda Civic" "Toyota Corolla" "Toyota Corona"
[22] "Dodge Challenger" "AMC Javelin" "Camaro Z28"
[25] "Pontiac Firebird" "Fiat X1-9" "Porsche 914-2"
[28] "Lotus Europa" "Ford Pantera L" "Ferrari Dino"
[31] "Maserati Bora" "Volvo 142E"
names函数也可以直接修改变量名称。例如,将my.dataset中的用户id变量名称改成vopenid,只需执行以下代码:
> names(my.dataset)
[1] "userid" "gamename" "iamount"
> names(my.dataset)[1] <- "vopenid"
> names(my.dataset)
[1] "vopenid" "gamename" "iamount"
也可以利用reshape包中的rename函数批量修改变量名。
> library(reshape)
> newdata <- rename(my.dataset,c("vopenid" = "用户ID",
+ "gamename" = "游戏名称","iamount" = "付费金额"))
> names(newdata)
[1] "用户ID" "游戏名称" "付费金额"
数据框的索引和矩阵类似,由于都是二维数据,所以它也有两个维度的下标,同时数据框的列名称也可以方便地索引数据框的列数据。
例如,提取my.dataset数据集中的第二列,有以下几种方式实现:
> my.dataset$gamename
[1] 地铁跑酷神庙逃亡2 水果忍者水果忍者机战王
Levels: 地铁跑酷机战王神庙逃亡2 水果忍者
> my.dataset[["gamename"]]
[1] 地铁跑酷神庙逃亡2 水果忍者水果忍者机战王
Levels: 地铁跑酷机战王神庙逃亡2 水果忍者
> my.dataset[[2]]
[1] 地铁跑酷神庙逃亡2 水果忍者水果忍者机战王
Levels: 地铁跑酷机战王神庙逃亡2 水果忍者
> my.dataset[,2]
[1] 地铁跑酷神庙逃亡2 水果忍者水果忍者机战王
Levels: 地铁跑酷机战王神庙逃亡2 水果忍者
如果想提取前三行、前两列的数据,可以执行以下代码:
> my.dataset[1:3,1:2]
vopenid gamename
1 S001 地铁跑酷
2 S002 神庙逃亡2
3 S003 水果忍者
> my.dataset[1:3,c("vopenid","gamename")]
vopenid gamename
1 S001 地铁跑酷
2 S002 神庙逃亡2
3 S003 水果忍者