@evilking
2017-10-15T10:38:46.000000Z
字数 6888
阅读 1956
R基础篇
R语言最基础的数据类型是向量(vector),在R中没有标量,其他数据结构实际存储时也都是按向量来存储
本文主要会讲述向量的创建,取值,以及数学运算
笔者不会用专门的篇幅来讲太细的语法,会尽量在实例程序中使用这些语法知识,并附上注解,希望读者能认真阅读这些实例代码和注释,从而体会其中各种语法的使用方式
> x <- c(1,2,3,4,5)
> x
[1] 1 2 3 4 5
>
此段代码表示在R Console中输入x <- c(1,2,3,4,5)
,按回车后即可创建向量x,直接输入x
后回车即可查看该向量的值
c取“连接”(concatenate)之意;R中没有标量,任何数字都可以看成是一元向量,所以这里可以看成是把向量1、2、3等连接成一个更大的向量
<-
符号表示赋值,R中也可以如其他编程语言一样使用=
来赋值随你喜欢,不过我们一般采用<-
来做赋值操作,因为在某些情况下=
会无效;
结果显示[1] 1 2 3 4 5
,前面的[1]
表示结果只有一行,后面会学到矩阵或数据框时,结果数据有多行的情况下此标记可以很方便的查看结果
R中一个比较常用的技巧是用()
将语句括起来,可以在创建数据结构的同时将结果打印出来
> (x <- c(1,2,3,4,5))
[1] 1 2 3 4 5
>
可以通过mode(x)
、typeof(x)
或者class(x)
函数查看向量x的模式,即数据类型。如:
> x <- c(1,2,3,4,5)
> y <- 6
> z <- c(6)
> mode(x)
[1] "numeric"
> typeof(y)
[1] "numeric"
> class(z)
[1] "numeric"
>
这里创建了三个变量,结果显示的数据类型都是[1] "numeric"
,这表明这三种创建变量的方式产生的变量没有什么区别,进一步说明了在R中没有标量,任何数字都可以看成是一元向量
既然x <- c(1,2,3,4,5)
本质上是连接了五个向量来赋值给变量x,那么我们用下面的方式创建向量也就很自然了,如:
> (t <- c(x,y,z,10,11))
[1] 1 2 3 4 5 6 6 10 11
> mode(t)
[1] "numeric"
>
同一向量中的所有元素必须是相同的模式,可以是整型、数值型(浮点型)、字符型(字符串)、逻辑型(布尔逻辑)、复数型等等;如果在创建向量时使用混合类型,则结果变量的类型取能满足所有元素的最小类型,如:
> (x <- c(1,2,3,"a",c(4,5)))
[1] "1" "2" "3" "a" "4" "5"
> mode(x)
[1] "character"
>
向量x是由整型和字符型的向量连接而成,从打印的结果来看,向量x的每个元素都转换为了字符型。字符串和对象类型之间的转换顺序我们在后面再细讲。
R语言为函数式语言,不需要如其他编程语言一般需要事先声明变量,可以直接给变量赋值后使用,在赋值的过程中由程序自动判断变量的类型。事实上在R中任何实体都是对象,比如数据结构、函数、类等;赋值的过程可以看成是变量绑定到了对象上,比如x <- c(1,2,3,4,5)
可以看成是先由c(1,2,3,4,5)
创建了一个对象,然后由x
通过赋值符号<-
绑定到了该对象上,变量x的类型由该对象的类型决定
> (x <- c(1,2,3)) #变量x绑定到向量上
[1] 1 2 3
> mode(x)
[1] "numeric"
> (x <- "123") #变量x绑定到了字符串上
[1] "123"
> mode(x)
[1] "character"
>
这段代码可知,变量x先绑定到了一个向量上,然后绑定到了字符串上;其中在R中用#
表示注释
为了方便,我们这里使用的都是数值型向量
> 1:5 # ":"符合可以生成向量
[1] 1 2 3 4 5
> -2:-4
[1] -2 -3 -4
> (x <- c(1:5)) #创建向量x
[1] 1 2 3 4 5
> x[1] #取第一个元素
[1] 1
> x[0] #索引为0时取值为空
numeric(0)
> x[6] #超出索引范围时值为NA
[1] NA
> x[-1] #去掉索引为1的值
[1] 2 3 4 5
> x[-6] #去掉索引为6的值
[1] 1 2 3 4 5
> x[1:4] #取第一到第四个元素
[1] 1 2 3 4
> x[-1:-2] #去掉索引1~2的值
[1] 3 4 5
> x[-2:-4] #去掉索引2~4的值
[1] 1 5
> x #重新查看向量x的值
[1] 1 2 3 4 5
> (y <- c(1.2,3.4,4.5,6.7,8.9,9.0))
[1] 1.2 3.4 4.5 6.7 8.9 9.0
> y[c(1,3)] # 取索引为1和3的值
[1] 1.2 4.5
> y[c(1,1,3)] #元素重复是允许的
[1] 1.2 1.2 4.5
> (v <- 1:3)
[1] 1 2 3
> y[v]
[1] 1.2 3.4 4.5
> y[y > 5] #筛选出y中元素大于5的元素
[1] 6.7 8.9 9.0
>
:
符合可以生成向量,比如1:5
生成1 2 3 4 5
,-2:-4
可以生成-2 -3 -4
,取子集可借助:
符号
向量的取值用[]
,括号中的数值为索引,R中的索引都是从1开始,不像其他编程语言一样从0开始
所以当索引为0时取值为空,表示为numeric(0)
;
当索引超出向量长度时,取值为NA,NA表示“not a number”;
当索引为负数时,表示要去掉对应索引处的值;
在x[-6]
的例子中,索引为6的元素不存在,去掉索引为6的元素对结果没什么影响,所以结果任然是1 2 3 4 5
;
向量取子集操作,x[1:4]
表示生成1到4的索引"1 2 3 4",然后取向量x对应索引的值连接成新的向量1 2 3 4
;
而x[-2:-4]
表示取向量x去掉索引2到4后的元素连接而成的向量1 5
最后重新查看向量x的值可以看成,上面的取值操作不会对原对象有任何影响,取值只是取的向量的副本
y[y > 5]
是筛选语句,先执行y > 5
得到向量FALSE,FALSE,FALSE,TRUE,TRUE,TRUE
,然后从向量y中选出筛选索引为TRUE的元素
> (x <- c(1,2,3,4,5))
[1] 1 2 3 4 5
> x[2] <- 7 #修改索引为2处的元素值
> x
[1] 1 7 3 4 5
> x[1:3] <- 0 #修改索引从1到3处的元素值
> x
[1] 0 0 0 4 5
> x <- c(x[1:4],111,x[5]) #插入元素111
> x
[1] 0 0 0 4 111 5
> x <- c(x[1],x[4:6]) #删除索引2到3处的元素
> x
[1] 0 4 111 5
>
向量修改值是通过赋值语句<-
去修改值,如x[2] <- 7
是修改x向量中索引为2的值,而x[1:3] <- 0
是一元向量0
先按照1:3
生成的索引序列循环补齐成向量0 0 0
,再由这个三元向量去修改向量x中对应索引的值。关于循环补齐后面再讲
R中向量是连续存储的,向量的大小在创建时就已经确定,因此不能插入或删除元素,如果想要添加或删除元素,需要重新给向量赋值。以上面插入元素为例,是先取向量x的子集x[1:4]
和x[5]
,与向量111
连接,在内存中生成新的向量,这时候原来的x向量并没有发生变化,然后变量x通过<-
函数绑定到新生成的向量,我们看到的结果好像是直接插入到原来的x向量中了,但是事实上此时的向量与插入之前的向量在内存中的地址已经发生了改变。
循环补齐
在对两个向量使用运算符时,如果要求这两个向量具有相同的长度,R会自动循环补齐(recycle),即重复较短的向量,直到它与另一个向量长度相匹配,例如:
> c(1,2,4) + c(6,0,9,20,22)
[1] 7 2 13 21 24
Warning message:
In c(1, 2, 4) + c(6, 0, 9, 20, 22) : 长的对象长度不是短的对象长度的整倍数
> c(1,2,4) + 5
[1] 6 7 9
>
第一个例子中,第一个向量只有3个元素,与第二个向量的5个元素长度不一致,这时候R会自动将向量1 2 4
循环补齐成1 2 4 1 2
,这就与第二个向量长度一致了,然后两向量按对应元素相加,从而得到向量7 2 13 21 24
第二个例子中,R自动将一元向量5
循环补齐成5 5 5
,然后与第一个向量对应位置相加,得到向量6 7 9
循环补齐的概念非常重要,后面矩阵的运算会再遇到循环补齐,理解了它对后面去深入理解其他语句的运算非常有帮助
> 2+3 #一元向量相加
[1] 5
> "+"(2,3) # "+"操作事实上也是函数调用
[1] 5
> (x <- c(2,4))
[1] 2 4
> (x <- x + c(5,0,-1)) #向量循环补齐后对应相加
[1] 7 4 1
Warning message:
In x + c(5, 0, -1) : 长的对象长度不是短的对象长度的整倍数
> (x*c(5,0,-1)) # 对应元素相乘
[1] 35 0 -1
> (x*c(5,0,-1,4,3)) # 循环补齐后对应元素相乘
[1] 35 0 -1 28 12
Warning message:
In x * c(5, 0, -1, 4, 3) : 长的对象长度不是短的对象长度的整倍数
> (x/c(5,4,-1)) #对应元素相除
[1] 1.4 1.0 -1.0
>
> (x %% c(5,4,-1)) #对应元素做取余操作
[1] 2 0 0
> x
[1] 7 4 1
> (x %*% c(1,2,3)) # 向量做矩阵乘法
[,1]
[1,] 18
>
上面的例子中常见的算术运算符的操作都是按对应元素来操作,如果想两个向量做线性代数中的矩阵乘法,需要用%*%
矩阵乘法符合,如最后一个例子
> "["(x,-2:-4)
[1] 1 5
其实R中的任何符号操作本质上都是函数操作,比如上面的[
函数,"["为函数名,()中的变量为函数参数,其他符号如"+"、"-"、"*"等等本质上都是函数,但是为了好看以及与其他语言的风格一致,我们一般不会用这种方式来书写,不过理解这种符号运算的本质对我们理解R中运算表达式是如何工作的有非常大的好处
如果读者对矩阵运算、概率论或统计学方面的知识不是很熟悉,也不用过多担心,在基础篇介绍完后笔者会用专门的篇幅来补充所需要的基本数学知识
常用的向量操作函数
> (x <- 3:11) #用":"生成向量
[1] 3 4 5 6 7 8 9 10 11
> length(x) #length()函数可以查看向量的长度
[1] 9
> 1:length(x) #可以生成向量x的索引序列
[1] 1 2 3 4 5 6 7 8 9
> seq(x) #用seq()函数生成向量x的索引序列
[1] 1 2 3 4 5 6 7 8 9
> (x <- NULL) #将x绑定到空向量上,此时向量x没有元素
NULL
> 1:length(x) #这时再用length(x)函数生成向量x的索引序列就会出错
[1] 1 0
> seq(x) #而用seq()函数生成索引序列依然正确
integer(0)
> seq(from=3,to=11,by=1) #seq()可生成等差数列
[1] 3 4 5 6 7 8 9 10 11
> seq(from=3,to=15,by=0.3) #等差距离也可为小数
[1] 3.0 3.3 3.6 3.9 4.2 4.5 4.8 5.1 5.4 5.7 6.0 6.3 6.6 6.9
[15] 7.2 7.5 7.8 8.1 8.4 8.7 9.0 9.3 9.6 9.9 10.2 10.5 10.8 11.1
[29] 11.4 11.7 12.0 12.3 12.6 12.9 13.2 13.5 13.8 14.1 14.4 14.7 15.0
>
比:
运算符更为一般的函数是seq(from,to,by) (由sequence得来),可用来生成等差序列,从from到to,等差距离按by的值来生成,等差距离也可不为整数。在需要生成向量的索引序列时,用seq()函数比length()函数更好一些,可以避免参数向量为NULL时出错,例如在使用循环for(i in 1:length(x))
和for(i in seq(x))
时
> (x <- rep(8,4)) #将一元向量8重复4次
[1] 8 8 8 8
> rep(c(2,3,1,2),3) #将向量重复3次
[1] 2 3 1 2 2 3 1 2 2 3 1 2
> rep(1:3,3) #将向量重复3次
[1] 1 2 3 1 2 3 1 2 3
> rep(c(2,3,1),each=3) #将向量中的每个元素依次重复3次
[1] 2 2 2 3 3 3 1 1 1
>
rep(x,times) (由repeat得来)函数让我们可以方便地把同一常数放在长向量中,创建一个由x重复times次生成的向量;该函数还有一个参数each,与times参数重复方式不同的是,它指定x中每个元素依次重复的次数
> (x <- 1:10)
[1] 1 2 3 4 5 6 7 8 9 10
> any(x > 8) #至少有一个元素大于8即为TRUE
[1] TRUE
> any(x > 80)
[1] FALSE
> all(x > 8) #所有元素都大于8即为TRUE
[1] FALSE
> all(x > 0)
[1] TRUE
>
any()函数判断其参数是否至少有一个为TRUE
all()函数判断其参数是否所有的都为TRUE
以any(x > 8)
为例说明,x > 8
先循环补齐然后计算的结果为"FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,FALSE,TRUE,TRUE",可知该向量中的元素至少有一个为TRUE,所以any()函数的结果即为TRUE
> (x <- c(6,1:3,NA,12))
[1] 6 1 2 3 NA 12
> x[x > 5] #通过筛选索引来完成筛选
[1] 6 NA 12
> subset(x, x > 5) #使用subset()函数筛选
[1] 6 12
>
函数subset(x,condition)可以对向量x用condition条件做筛选,与普通筛选方式不同之处是在处理NA值的方式上;普通筛选方式中,R认为NA值未知,所以是否大于5也是未知的,也会输出到结果集中;而subset()函数会剔除掉NA值
> (x <- c(6,1:3,NA,12))
[1] 6 1 2 3 NA 12
> x > 5
[1] TRUE FALSE FALSE FALSE NA TRUE
> x[x > 5]
[1] 6 NA 12
> which(x > 5)
[1] 1 6
>
which()函数是列出x中满足x > 5条件的元素所在的位置,它会简单的报告在后面的表达式中哪些元素为TRUE,并且会忽略NA值
> (x <- 1:10)
[1] 1 2 3 4 5 6 7 8 9 10
> y <- ifelse(x %% 2 == 0, 5, 12) #向量化的ifelse()函数
> y
[1] 12 5 12 5 12 5 12 5 12 5
>
除了如其他编程语言常见的if-then-else结构外,R还有一个向量化的版本:ifelse(b,u,v)函数,其中b是一个boolean值向量,而u和v是向量,该函数的返回值也是向量,如果b[i]为真,则返回值的第i个元素为u[i],如果b[i]为假,则返回值的第i个元素为v[i]
在上述例子中,我们希望产生一个向量,向量对应元素为偶数的位置上取值为5,为奇数的位置上取值为12;x %% 2 == 0
的结果为FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE
,一元向量5
和12
分别循环补齐为向量5 5 5 5 5 5 5 5 5 5
和12 12 12 12 12 12 12 12 12 12
,按照ifesle()函数的结果向量的生成规则,结果即为12 5 12 5 12 5 12 5 12 5
NA与NULL值
> (x <- c(88,NA,12,168,13)) #向量中包含NA
[1] 88 NA 12 168 13
> length(x) # NA项作为实体被计算在内
[1] 5
> mean(x) # NA项被计算在内,结果为NA
[1] NA
> mean(x,na.rm = T) # 剔除掉NA项后求均值
[1] 70.25
> (y <- c(88,NULL,12,168,13)) #向量中包含NULL
[1] 88 12 168 13
> length(y) # NULL项不被计数
[1] 4
> mean(y) # NULL被忽略
[1] 70.25
> x[2] # NA项作为实体元素在向量中存在
[1] NA
> y[1]
[1] 88
> y[2] # NULL项不存在向量中
[1] 12
> mode(x[2]) # NA项具有模式
[1] "numeric"
>
在计算向量长度和均值时,NA项都作为实体元素被计算在内,且具有模式,而NULL项却被忽略;NULL是R中的一种特殊对象,表示不存在,它没有模式
向量的名称
> x <- c(1,2,3,4,5)
> names(x) #此时向量x没有名称
NULL
> names(x) <- c("a","b","c","d") #名称向量的长度可以小于向量x的长度
> names(x) #未设置名称的元素的名称为NA
[1] "a" "b" "c" "d" NA
> names(x) <- c("a","b","c","d","e","f") #名称向量的长度不能大于向量x的长度
Error in names(x) <- c("a", "b", "c", "d", "e", "f") :
'names'属性的长度[6]必需和矢量的长度[5]一样
>
> names(x) <- c("a","b","c","d","e") #为向量设置名称
> names(x)
[1] "a" "b" "c" "d" "e"
> x
a b c d e
1 2 3 4 5
> x[2]
b
2
> x["b"] #可以通过名称去访问元素
b
2
> names(x) <- NULL #去掉向量的名称
> x
[1] 1 2 3 4 5
>
names()函数可以给向量中的元素命名,也可以查询向量元素的名称
在进行向量运算时,尽量使用向量化运算,少用循环;向量化操作不仅简化代码,还能将代码运行效率显著提高到数百倍甚至更多,提高R代码执行速度的有效方法之一是向量化(vectorize)