duoan / notes

Classtag's Notebooks
https://github.com/classtag/notebook/issues
9 stars 3 forks source link

R语言基础知识 #10

Open duoan opened 8 years ago

duoan commented 8 years ago

R语言作为统计学一门语言,一直在小众领域闪耀着光芒。直到大数据的爆发,R语言变成了一门炙手可热的数据分析的利器。随着越来越多的工程背景的人的加入,R语言的社区在迅速扩大成长。现在已不仅仅是统计领域,教育,银行,电商,互联网….都在使用R语言。

R语言是在大数据时代被工业界了解和认识的语言,R语言被时代赋予了,挖掘数据价值,发现数据规律,创造数据财富的任务。

R语言也是帮助人们发挥智慧和创造力的最好的生产工具,我们不仅要学好R语言,还要用好R语言,为社会注入更多的创新的生产力。

我认为“R是最值得学习的编程语言”。不论你还在读书,还是已经工作,掌握R语言这个工具,找最适合自己的位置,前途将无限量。

本文主要记录了笔者自学R的基础知识点,这里直介绍R的基础入门知识,全文使用RStudio操作

目录

如何通过R软件显示某一变量或表达式的值。

解决方案

在提示符后直接输入变量名或表达式,R软件便会直接在屏幕中输出其值。使用print函数能输出所有的变量和表达式值,使用cat函数则能选择以用户自定义的格式显示对象的值。

讨论

通过R软件显示结果的步骤很简单:只需在提示符后输入变量名或表达式:

> pi
[1] 3.141593
> sqrt(2)
[1] 1.414214

当输入如上表达式后,R软件会对表达式进行计算并自动调用print函数显示结果。因此上述命令等同于如下命令:

> print(pi)
[1] 3.141593
> print(sqrt(2))
[1] 1.414214

print函数的优点在于它知道该以何种格式显示结果,包括一些具有格式的变量,如矩阵和列表:

> print(matrix(c(1,2,3,4), 2, 2))
     [,1] [,2]
[1,]    1    3
[2,]    2    4
> print(list("a","b","c"))
[[1]]
[1] "a"
[[2]]
[1] "b"
[[3]]
[1] "c"

这一命令很常用,因为你可以随时通过print函数显示数据,而不必在意数据显示的顺序与逻辑,即使对如矩阵这样复杂的数据格式也是如此。

但print函数也有其局限性:print函数每次只能显示一个对象。同时显示多个变量会得到如下结果:

> print("The zero occurs at", 2*pi, "radians.")
Error in print.default("The zero occurs at", 2 * pi, "radians.") :  unimplemented type 'character' in 'asLogical'

只有通过多次使用print函数才能显示多个对象,但用户往往会认为这种方法过于繁琐:

> print("The zero occurs at"); print(2*pi); print("radians")
[1] "The zero occurs at"
[1] 6.283185
[1] "radians"

cat函数是另外一种可以替代print的显示方式。它可以将多个对象连接并以连续的方式显示:

> cat("The zero occurs at", 2*pi, "radians.", "\n")
The zero occurs at 6.283185 radians.

注意,cat函数默认在两个对象间加上空格。如果需要换行,则可以使用换行符(\n)来结束本行语句。

cat函数也能显示简单向量:

> fib <- c(0,1,1,2,3,5,8,13,21,34)
> cat("The first few Fibonacci numbers are:", fib, "...\n")
The first few Fibonacci numbers are: 0 1 1 2 3 5 8 13 21 34 ...

cat函数对变量的输出有更多的控制和选择,这在R程序中尤为重要。但它也有缺陷,即cat函数无法显示复合的数据结构,如矩阵和列表。用cat函数显示列表会得到以下结果:

> cat(list("a","b","c"))
Error in cat(list(...), file, sep, fill, labels, append) :
argument 1 (type list) cannot be handled by cat

设定变量

问题

如何将某个值赋值给一个变量。

解决方案

使用赋值运算符(<-)进行赋值。在赋值前无须对变量进行声明:

> x <- 3

讨论

R软件采用“计算器”模式,方便快捷。但是,有时候需要定义变量并保存变量值。这省去了重复输入的时间并使你的工作更为明晰。

在R软件中,不必对变量进行声明或者显式地创建变量,只需要将值赋予一个名称,R软件就会自动生成该名称的变量:

> x <- 3
> y <- 4
> z <- sqrt(x^2 + y^2)
> print(z)
[1] 5

注意,赋值操作由一个小于号(<)和连字符(-)构成,两个符号之间没有空格。

当使用此种方法定义变量时,该变量将存储到当前的工作空间中。此时工作空间仅存储在计算机的内存中,当退出R软件时可保存至本地硬盘。工作空间会永久保存该变量,直至用户删除或替代该变量。

R软件是动态的输入语言,即可随意改变变量的数据类型。我们可以先定义x为数值型变量,随后马上对其赋值一个字符串向量,在这一过程中R软件能完全理解用户的意图:

>  x <- 3
> print(x)
[1] 3
> x <- c("fee", "fie", "foe", "fum")
> print(x)
[1] "fee" "fie" "foe" "fum"

在某些R函数中,你会看到很特别的赋值符号<<-:

x <<- 3

这一操作能强制赋值给一个全局变量,而不是局部变量。

为了全面介绍,在此给出另外两种赋值形式。也可以在命令提示符中使用单个等号(=)对变量进行赋值。在所有可以应用向左赋值符号(<-)的地方都可以使用向右赋值符号(->),它对右侧变量进行赋值:

> foo = 3
> print(foo)
[1] 3
> 5 -> fum
> print(fum)
[1] 5

列出所有变量

问题

你希望知道目前工作空间中存在哪些已定义的变量和函数。

解决方案

使用ls函数,或者使用ls.str函数了解每个变量更详细的信息。

讨论

ls函数可以显示当前工作空间中所有对象的名称:

> x <- 10
> y <- 50
> z <- c("three", "blind", "mice")
> f <- function(n,p) sqrt(p*(1-p)/n)
> ls()
[1] "f" "x" "y" "z"

注意,ls函数输出的结果是一个字符串向量,其中向量的每个元素代表一个变量名。当工作空间中没有已定义的变量时,函数ls会返回一个空向量,它会产生如下令人迷惑的结果:

> ls()
character(0)

事实上,R软件采用这样的方式向用户说明,ls函数返回一个长度为0的字符串向量;即工作空间中不含有任何已定义变量。

如果你除了变量名称以外还想对变量有更多的了解,那么你可以使用ls.str函数,该函数会返回变量的一些其他信息:

> ls.str()
f : function (n, p)
x :  num 10
y :  num 50
z :  chr [1:3] "three" "blind" "mice"

ls.str函数之所以写为ls.str,原因在于其功能既显示了所有变量的名称,又对所有变量使用了str函数,方法12.15对此进行了详细的说明。

ls函数不会显示以点(.)开头的变量名,以点开头的变量一般作为隐藏变量不为用户所知(这一输出规定来源于UNIX系统)。在R软件中,可以通过将ls.str函数中的all.names参数设定为TRUE,强制列出所有变量:

> .hidvar <- 10
> ls()
[1] "f" "x" "y" "z"
> ls(all.names=TRUE)
[1] ".hidvar" "f"       "x"       "y"       "z"

删除变量

问题

你希望删除工作空间中不需要的变量和函数,或者完全删除它们的取值内容。

解决方案

使用rm函数。

讨论

在R软件的使用过程中,工作空间容易很快变得杂乱。rm函数能永久地从工作空间中删除一个或多个对象:

> x <- 2*pi
> x
[1] 6.283185
> rm(x)
> x
Error: object "x" not found

该命令无法“撤销”,即删除的变量无法找回。

你可以通过如下命令同时删除多个变量:

> rm(x,y,z)

你甚至可以同时删除工作空间中所有的内容。rm函数中有一个list参数,它包含所有需要删除的变量名称。前面章节介绍过ls函数能返回所有变量名称,因此你可以通过结合rm函数与ls函数,删除工作空间中的所有变量:

> ls()
[1] "f" "x" "y" "z"
> rm(list=ls())
> ls()
character(0)

R软件的道德规范在与他人共享代码时,绝不能将带有rm(list=ls())的恶意代码通过网络示例或者其他方法发送至他人。用此手段删除对方工作空间中所有的内容是如此粗鄙的行为,让你变得不受欢迎

生成向量

问题

如何生成一个向量。

解决方案

通过c(...)命令对给定的值构建一个向量。

讨论

向量不仅是R的一种数据结构,它还是贯通R软件的重要组成部分。向量中可以包含数值、字符串或者逻辑值,但不能由多种格式混合组成。

c(...)命令中添加元素对向量进行赋值:

> c(1,1,2,3,5,8,13,21)
[1]  1  1  2  3  5  8 13 21
> c(1*pi, 2*pi, 3*pi, 4*pi)
[1]  3.141593  6.283185  9.424778 12.566371
> c("Everyone", "loves", "stats.")
[1] "Everyone" "loves"    "stats."
> c(TRUE,TRUE,FALSE,TRUE)
[1]  TRUE  TRUE FALSE  TRUE

如果c(...) 中的参数自身是向量,那么c(...) 命令会将多个向量合为一个向量:

> v1 <- c(1,2,3)
> v2 <- c(4,5,6)
> c(v1,v2)
[1] 1 2 3 4 5 6

对于一个向量来说,其中的内容不能由多种数据格式混合组成,如在一个向量中同时包含数值和字符串。R软件对于混合型向量会进行如下的格式转换:

> v1 <- c(1,2,3)
> v3 <- c("A","B","C")
> c(v1,v3)
[1] "1" "2" "3" "A" "B" "C"

这里,用户希望将一组数值数据和一组字符串数据同时赋值给一个新的向量。对于这种情况,R软件会先将数值数据转换为字符串数据,使得两组数据的类型得以统一。

理论上来说,两组数据能同时赋值于一个向量的条件,在于两组数据具有相同的类型 (mode)。例如3.1415和"foo"分别为数值型和字符型:

> mode(3.1415)
[1] "numeric"
> mode("foo")
[1] "character"

上述两者的类型不同。为了生成新的向量,R软件将3.1415转换为字符类型,使得3.1415的类型与"foo"的类型一样:

> c(3.1415, "foo")
[1] "3.1415" "foo"
> mode(c(3.1415, "foo"))
[1] "character"

警告:c是一个通用的运算符,这意味着它不仅应用于向量,同时也应用于其他的数据类型。但是,它可能不是那么精确地与用户预期相一致。因此在将c命令用于其他数据类型和对象前,要查看它的效果。

计算基本统计量

问题

如何使用R软件计算下列统计量:均值、中位数、标准差、方差、协方差和相关系数。

解决方案

采用如下函数进行计算,其中x、y均为向量:

mean(x)
median(x)
sd(x)
var(x)
cor(x, y)
cov(x, y)

讨论

我初次阅读R软件帮助文件是为了寻找“标准差的计算过程”这一内容,原本认为帮助文件会以一整章篇幅介绍这一重要概念。

实际上没有那么复杂。

R软件中,用简单的函数便能完成标准差和其他基本统计量的计算。一般来说,函数参数是一个数值向量,而函数返回计算出的统计量:

> x <- c(0,1,1,2,3,5,8,13,21,34)
> mean(x)
[1] 8.8
> median(x)
[1] 4
> sd(x)
[1] 11.03328
> var(x)
[1] 121.7333

其中sd函数计算样本标准差,var函数计算样本方差。

cor函数以及cov函数分别计算两变量间的相关系数与协方差:

> x <- c(0,1,1,2,3,5,8,13,21,34)
> y <- log(x+1)
> cor(x,y)
[1] 0.9068053
> cov(x,y)
[1] 11.49988

上述函数对于是否存在缺失值(NA)很敏感。某个变量中的一个缺失值就有可能导致函数返回NA结果,甚至可能造成计算机在计算过程中报错:

> x <- c(0,1,1,2,3,NA)
> mean(x)
[1] NA
> sd(x)
[1] NA

虽然R软件对于缺失值的敏感程度有时会造成用户的不便,但这种处理方式也是合情合理的。对于R软件返回的结果你应该慎重地考虑:数据中的缺失值是否会严重影响统计结果?如果是,那么R软件返回错误结果是正确的;如果不是,则可以通过设置参数na.rm=TRUE, 告知R软件忽略缺失值:

> x <- c(0,1,1,2,3,NA)
> mean(x, na.rm=TRUE)
[1] 1.4
> sd(x, na.rm=TRUE)
[1] 1.140175

mean函数和sd函数能巧妙地处理数据框数据,自动将数据框中的每一列认为是不同的变量,并对每列数据分别进行计算。下面的例子展示了mean和sd函数对有三列的数据框的计算结果:

> print(dframe)
       small    mediumbig
1  0.6739635  10.526448   99.83624
2  1.5524619   9.205156  100.70852
3  0.3250562  11.427756   99.73202
4  1.2143595   8.533180   98.53608
5  1.3107692   9.763317  100.74444
6  2.1739663   9.806662   98.58961
7  1.6187899   9.150245  100.46707
8  0.8872657  10.058465   99.88068
9  1.9170283   9.182330  100.46724
10 0.7767406   7.949692  100.49814
> mean(dframe)
   small    medium       big
1.245040  9.560325 99.946003
> sd(dframe)
    small    medium       big
0.5844025 0.9920281 0.8135498

注意,mean和sd函数都会返回3个值,每个数值对应着对数据框中一列数据的计算结果(一般地,R软件会以一个包含三个元素的向量返回结果,其中每个元素的names属性由数据框中各个列的名称得来)。

var函数也能处理数据框数据,但处理方式与mean函数和sd函数有些许不同。var函数计算每两列变量间的协方差,并以协方差矩阵的形式返回结果:

> var(dframe)
             small      medium         big
small   0.34152627 -0.21516416 -0.04005275
medium -0.21516416  0.98411974 -0.09253855
big    -0.04005275 -0.09253855  0.66186326

同样,如果x是一个数据框或矩阵,则cor(x) 返回其相关系数矩阵;而cov(x) 返回其协方差矩阵:

> cor(dframe)
             small     medium         big
small   1.00000000 -0.3711367 -0.08424345
medium -0.37113670  1.0000000 -0.11466070
big    -0.08424345 -0.1146607  1.00000000
> cov(dframe)
             small      medium         big
small   0.34152627 -0.21516416 -0.04005275
medium -0.21516416  0.98411974 -0.09253855
big    -0.04005275 -0.09253855  0.66186326

median函数无法辨认数据框形式的数据。若需计算数据框数据的中位数,需要使用方法6.4对各列分别进行计算。

生成数列

问题

如何生成一个数列。

解决方案

使用表达式n:m生成简单数列n,n+1,n+2,...,m:

> 1:5
[1] 1 2 3 4 5

对于增量不为1的数列,可以使用seq函数:

> seq(from=1, to=5, by=2)
[1] 1 3 5

使用rep函数生成由一个数的重复所组成的数列:

> rep(1, times=5)
[1] 1 1 1 1 1

讨论

冒号运算符(n:m)会生成包含n,n+1,n+2,...,m的一个向量:

> 0:9
[1] 0 1 2 3 4 5 6 7 8 9
> 10:19
[1] 10 11 12 13 14 15 16 17 18 19
> 9:0
[1] 9 8 7 6 5 4 3 2 1 0

注意,上述最后一个表达式(9:0),R软件能自动识别9大于0并以递减的形式生成数列。

冒号运算符仅能生成增量为1的数列。而seq函数通过它的第三个参数来规定数列元素的增量:

> seq(from=0, to=20)
[1]  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
> seq(from=0, to=20, by=2)
[1]  0  2  4  6  8 10 12 14 16 18 20
> seq(from=0, to=20, by=5)
[1]  0  5 10 15 20

相应地,你可以在函数中规定输出数列的长度,R软件会自动识别并根据要求生成等增量数列:

> seq(from=0, to=20, length.out=5)
[1]  0  5 10 15 20
> seq(from=0, to=100, length.out=5)
[1]   0  25  50  75 100

函数seq的增量参数并非一定是整数。R软件也可以生成具有分数增量的数列:

> seq(from=1.0, to=2.0, length.out=5)
[1] 1.00 1.25 1.50 1.75 2.00

特殊情况下,若需要生成重复某个值的数列,则可以使用rep函数,生成的数列重复其第一个参数值:

> rep(pi, times=5)
[1] 3.141593 3.141593 3.141593 3.141593 3.141593

向量比较

问题

如何比较两个向量,或者将一个向量的所有元素与某一个常数进行比较。

解决方案

比较运算符(==、!=、<、>、<=、>=)能对两向量间的各个元素进行比较。这些运算符也能将向量中所有元素与一个常数进行比较。返回结果是每两个元素间比较结果的逻辑值向量。

讨论

R软件包含两个逻辑值,TRUE和FALSE。在其他编程语言中也称为布尔值(Boolean values)。

比较运算符通过比较两个值,并根据比较结果返回TRUE或FALSE:

> a <- 3
> a == pi     # 检验两者是否相等
[1] FALSE
> a != pi     # 检验两者是否不等
[1] TRUE
> a < pi
[1] TRUE
> a > pi
[1] FALSE
> a <= pi
[1] TRUE
> a >= pi
[1] FALSE

你可以使用R软件一次性地对两个向量进行比较,它会将两个向量中每两个对应的元素进行比较,并以逻辑值向量方式返回比较结果:

> v <- c( 3, pi,  4)
> w <- c(pi, pi, pi)
> v == w# 比较两个各自包含3个元素的向量
[1] FALSE  TRUE FALSE# 结果以包含3个逻辑值的向量形式输出
> v != w
[1]  TRUE FALSE  TRUE
> v < w
[1]  TRUE FALSE FALSE
> v <= w
[1]  TRUE  TRUE FALSE
> v > w
[1] FALSE FALSE  TRUE
> v >= w
[1] FALSE  TRUE  TRUE

也可以将一个向量与一个常数进行比较,R软件会将常数扩充为一组长度与所比较向量的长度相等,并由常数值重复组成的向量,再将新向量与它需要比较向量的对应元素进行比较。所以,之前的例子可以简化为:

> v <- c(3, pi, 4)
> v == pi# 将包含3个元素的向量与一个常数进行比较
[1] FALSE  TRUE FALSE
> v != pi
[1]  TRUE FALSE  TRUE

比较两个向量后,你通常会想知道比较结果中是否存在TRUE,或者比较结果是否全为TRUE。可以应用函数any和all来检验上述问题。两个函数都针对逻辑型变量进行检验,其中如果元素中含有至少一个TRUE,则any函数返回TRUE;如果元素全为TRUE,则all函数返回TRUE:

> v <- c(3, pi, 4)
> any(v == pi)# 若v向量中元素至少一个等于pi,则返回TRUE
[1] TRUE
> all(v == 0)# 若v向量中所有元素都为0,则返回TRUE
[1] FALSE

选取向量中的元素

问题:如何选取向量中一个或多个元素。

解决方案

从向量中选出某些元素是R的又一项强大功能。和其他编程语言一样,R选取向量元素的基本方法是使用一对方括号和简单索引(下标):

> fib <- c(0,1,1,2,3,5,8,13,21,34)
> fib
[1]  0  1  1  2  3  5  8 13 21 34
> fib[1]
[1] 0
> fib[2]
[1] 1
> fib[3]
[1] 1
> fib[4]
[1] 2
> fib[5]
[1] 3

注意,向量第一个元素的索引(或下标)为1,而非某些编程语言中的0。

另外也可以同时选择一个向量中的多个元素,向量的索引本身可以是一个向量,并且根据下标向量中所指定的位置选择原向量中的元素:

> fib[1:3]# 选择下标为1至3的元素
[1] 0 1 1
> fib[4:9]# 选择下标为4至9的元素
[1]  2  3  5  8 13 21

1:3这样的下标意味着选择第1、2、3个元素,如上例所示。索引向量可以不是简单数列,可以选择向量数据中的任何元素,如下例所示,它选择第1、2、4和第8个元素:

> fib[c(1,2,4,8)]
[1]  0  1  2 13

R将负索引看做是排除向量中相应索引的元素。如下标为-1,意味着选择除了向量的第一个元素外的所有其他元素:

> fib[-1]          # 忽略第一个元素
[1]  1  1  2  3  5  8 13 21 34

通过使用负索引的索引向量,该方法可以扩展为排除多个元素:

> fib[1:3]         # 前述向量
[1] 0 1 1
> fib[-(1:3)]      # 在索引前添加负号,排除相应的元素
[1]  2  3  5  8 13 21 34

还可以使用逻辑向量从数据向量中选择元素。与索引逻辑向量取值为TRUE的元素相对应的原始数据向量的元素将被选择:

> fib < 10# 仅当向量中元素值小于10时该表达式为TRUE
[1]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE
> fib[fib < 10]# 使用该表达式选择向量中小于10的值
[1] 0 1 1 2 3 5 8
> fib %% 2 == 0# 仅当向量fib中元素值为偶数时该表达式为TRUE
[1]  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE  TRUE
> fib[fib %% 2 == 0]# 使用该表达式选择向量中的偶数值
[1]  0  2  8  34

一般地,索引逻辑向量的长度应与原始数据向量的长度相同,这样才能清晰地选择或者排除每一个元素(若两者长度不同,则需要了解循环规则,具体请参见方法5.3)。

结合向量比较、逻辑运算符以及向量索引,可以用少量的R命令来完成强大的选择功能:

选择所有小于中位数的元素

v[ v > median(v) ]

选择分布于两端5%的元素

v[ (v < quantile(v,0.05)) | (v > quantile(v,0.95)) ]

选择所有处于均值的两倍标准差区间以外的元素

v[ abs(v-mean(v)) > 2*sd(v) ]

选择所有NA或NULL值的元素

v[ !is.na(v) & !is.null(v) ]

最后一个索引特征可以让你通过名称选择元素。它要求数据向量有name属性,即其中每个元素都定义各自的名称。可以指定一个字符串向量来完成该数据向量名称的定义:

> years <- c(1960, 1964, 1976, 1994)
> names(years) <- c("Kennedy", "Johnson", "Carter", "Clinton")
> years
Kennedy Johnson  Carter Clinton
   1960    1964    1976    1994

一旦元素的名称已定义,则可以使用名称引用向量中的元素:

> years["Carter"]
Carter
  1976
> years["Clinton"]
Clinton
   1994

可以推广到使用名称来索引向量元素:R软件会返回向量中对应名称的元素:

> years[c("Carter","Clinton")]
Carter Clinton
  1976    1994

向量的计算

问题

你希望对整个向量执行计算。

解决方案

基本的数学运算符可以对向量中的元素进行逐个计算。许多其他的函数也能对向量元素逐个进行运算,并以向量的形式输出结果。

讨论

向量计算是R软件的一大特色。所有的基本数学运算符都能应用于向量对中。这些运算符对两个向量中相应的每个元素对进行计算,即将两个向量中对应的元素进行基本运算:

> v <- c(11,12,13,14,15)
> w <- c(1,2,3,4,5)
> v + w
[1] 12 14 16 18 20
> v - w
[1] 10 10 10 10 10
> v * w
[1] 11 24 39 56 75
> v / w
[1] 11.000000  6.000000  4.333333  3.500000  3.000000
> w ^ v
[1]     1   4096   1594323   268435456  30517578125

注意,输出的结果向量的长度与原向量的长度相等。原因是结果向量中的每个元素都是由原向量对中对应的两个元素计算得来。

若使一个向量与一个常数进行运算,则会将该向量的每个元素与常数进行运算:

> w
[1] 1 2 3 4 5
> w + 2
[1] 3 4 5 6 7
> w - 2
[1] -1  0  1  2  3
> w * 2
[1]  2  4  6  8 10
> w / 2
[1] 0.5 1.0 1.5 2.0 2.5
> w ^ 2
[1]  1  4  9 16 25
> 2 ^ w
[1]  2  4  8 16 32

例如,可以在一个表达式中得到一个向量减去其元素均值后的向量:

> w
[1] 1 2 3 4 5
> mean(w)
[1] 3
> w - mean(w)
[1] -2 -1  0  1  2

同样,可以通过计算向量减去其均值并除以其标准差,来获得该向量数据的z分数(z-score):

> w
[1] 1 2 3 4 5
> sd(w)
[1] 1.581139
> (w - mean(w)) / sd(w)
[1] -1.2649111 -0.6324555  0.0000000  0.6324555  1.2649111

向量的运算功能远不止对元素的简单运算。还有许多函数对整个向量进行运算。如sqrt函数和log函数,都可以应用于整个向量中的每个元素,并以向量的形式输出结果:

> w
[1] 1 2 3 4 5
> sqrt(w)
[1] 1.000000 1.414214 1.732051 2.000000 2.236068
> log(w)
[1] 0.0000000 0.6931472 1.0986123 1.3862944 1.6094379
> sin(w)
[1]  0.8414710  0.9092974  0.1411200 -0.7568025 -0.9589243

R软件的向量运算有两大优点。第一个最明显的优点是操作的简便性,其他编程软件中需要通过循环才能完成的操作,在R软件中一行命令便可以实现。第二个优点是计算速度快。大多数向量化的运算直接由C语言代码来实现,它比你自己用R写的代码本质上快很多。

运算符优先级问题

问题

R软件输出结果有误,你希望了解问题是否由运算符的优先级所导致的。

解决方案

所有的运算符显示在表2-1中,并以最高优先级至最低优先级的顺序排列。相同优先级的运算符,除特指外皆由从左至右的顺序进行运算。

表2-1:运算符优先级

运算符含义参考

[ [[               索引方法2.9
:: :::             使用名称访问变量
$ @                元素提取、位置提取
^                  指数形式(从右到左)
- +                元素的负、正
:                  创建数列方法2.7,7.14
%any%              特殊运算符讨论
* /                乘、除讨论
+ -                加、减
== !=  < > <= >=   比较运算符方法2.8
!                 逻辑取反
&  &&              逻辑“与”、 短路“与”

表2-1:运算符优先级(续) 运算符含义参考

|  ||   逻辑 “或”、 短路“或”
~       公式方法11.1
->  ->> 向右赋值方法2.2
=       赋值(从右向左)方法2.2
<-  <<- 赋值(从右向左)方法2.2

讨论

用户在R中搞错运算符的优先级是经常遇到的问题。我经常会犯这样的错误,例如我会不假思索地认为表达式0:n-1会生成从0~n-1的数列,但事实并非如此:

> n <-10
> 0:n-1
[1] -1  0  1  2  3  4  5  6  7  8  9

该表达式生成-1~n-1的数列,因为R软件将上式理解为(0:n)-1。

你可能不熟悉表2-1中的符号%any%,R中用两个百分号夹带一个符号的形式(%...%)表示一个二元运算符。R中预定义的二元运算符的含义如下:

%%   取模
%/%  整除
%*%  矩阵乘积
%in% 右侧变量中包含左侧变量时,为TRUE;否则,为FALSE。

你可以通过%...%记号来定义新的二元运算符,参见方法12.19。此种运算符都具有相同的运算优先级。

定义函数

问题

如何定义一个R函数。

解决方案

使用关键字function,并在其后跟随函数参数列表和函数主体。其基本形式如下:

function(param1, ...., paramN) expr

函数主体可以是一系列表达式,这些表达式需要用大括号括起来:

function(param1, ..., paramN) {
  expr1
   .
   .
   .
  exprM
}

讨论

函数的定义告诉R软件“用何种方式进行计算”。例如,R软件没有内置计算变异系数的函数,因此你可以定义函数如下:

> cv <- function(x) sd(x)/mean(x)
> cv(1:10)
[1] 0.5504819

第一行定义了名为cv的函数,第二行引用该函数,以1∶10作为其参数x的值。函数对参数应用函数主体中的表达式sd(x)/mean(x)进行计算并返回结果。

定义函数后,我们可以在任何需要函数的地方应用它,例如可以作为lapply函数的第二个参数:

> cv <- function(x) sd(x)/mean(x)
> lapply(lst, cv)

函数主体如果包含多行表达式,则需要使用大括号来确定函数内容的起始和结束位置。下面这一函数采用了欧几里德算法计算两个整数的最大公约数:

> gcd <- function(a,b) {
+ if (b == 0) return(a)
+ else return(gcd(b, a %% b))
+ }

R软件也允许使用匿名函数,匿名函数是没有函数名称但在单行的语句中很实用的函数。先前的例子中我们提到将cv函数作为lapply函数的一个参数,而若使用匿名函数直接作为lapply函数的参数,则能将原先的命令简化至同一行中:

> lapply(lst, function(x) sd(x)/mean(x))

由于本书重点不在于介绍R的编程语言,这里不对R函数编程的细微之处进行解释。下面给出几个需要注意的地方:

返回值

所有函数都有一个返回值,即函数主体最后一个表达式值。你也可以通过return(expr)命令给出函数的返回值。

值调用

函数参数是“值调用”——如果你改变了函数中的参数值,改变只是局部的,并不会影响该参数所引用的变量值。

局部变量

你可以简单地通过赋值来创建一个局部变量,函数结束后该局部变量会消失。

条件执行

R语法中包含if语句,更多详情可以使用help(Control)命令查看。

循环语句

R语法中也包括for循环、while循环以及repeat循环语句。更多详情可以使用help(Control)命令查看。

全局变量

在函数中,你可以通过<<-操作符来改变全局变量的值,但此种方法不推荐使用。

另请参阅 有关如何定义函数,参见《An Introduction to R》(http://cran.r-project.org/doc/manuals/R-intro.pdf)和《R in a Nutshell》。2.12 定义函数