R语言基础

本节概述R语言的基本知识。用稍微简单的方式讲述,而将一些细节问题轻轻带过是有必要的。虽然包含一节关于编程的简单介绍,但是这部分内容的重点是介绍交互界面使用中的一些有用的东西,而不是具体编程。

表达式和对象

R的一个基本交互模式是表达式求值。用户输入一个表达式,系统计算它并输出结果。一些表达式并不是为计算结果而是有一些其他用处,比如生成一个图形窗口或写入一个文件。所有的R表达式返回一个值(可以是NULL),但有时它是不可见的,而且不被输出。

表达式通常包含变量引用、运算符(比如+)、函数调用,以及其他尚未介绍的项目。

表达式作用于对象。对象是一个抽象的术语,可以针对任何可以给变量赋值的事物。R包含几种不同类型的对象。到目前为止,我们基本上只看到向量,但几种其他类型的变量也将在本章介绍。

虽然对象可以抽象地讨论,但如果没有如何生成它们以及可以用它们来做什么的例子来说明的话,读起来可能会十分枯燥。另一方面,许多表达式结构如果没有它们所针对的工作对象的知识的话,介绍起来也没有太大意义。因此,后续几节将在介绍新的对象和新的语言元素之间交替进行。

函数和参数

关于函数,你已经有过R是如何工作的印象了,我们也已经在讨论作图函数及其他函数时使用过一些特殊的术语。有一点是关键:R中的许多工作都是通过函数调用来完成的,函数调用看起来像是一个或几个变量的数学函数的应用,比如log(x)或plot(height,weight)。

调用函数的格式是在函数名后由圆括号包含起来的一个或几个参数。比如在plot(height,weight)中,plot是函数名,参数是height和weight。这些称为实参,仅适用于当前调用。函数也有形参,它们在调用中与实参相联系。

当你输入plot(height,weight)时,R假设第一参数对应x,第二参数对应y。这称为“位置匹配”。如果一个函数有很多参数,你不得不将每一个都输入而且记住它们在序列中的位置,所以使得函数调用起来很麻烦。幸运的是,R中有方法避免这个问题:大多数参数有合理的默认值,在标准情形下可以省略,当你不希望是默认值时,也有非位置的方法指定它们。

plot函数事实上就是一个有大量可选输入参数的例子,这些参数可以用来修改绘图符号、线宽、标题、坐标轴等。当用plot(height, weight, pch=2)把画图符号变成三角形时,我们使用了指定参数的另外一种形式。

形式pch=2称为指定实际参数,它的名字可以用函数的形式参数匹配,从而允许参数的关键字匹配。关键字pch用来说明参数的作用是指定作图特征。这种类型的函数参数可以用任一顺序指定。因此,你可以输入plot(y=weight,x=height),也可以用plot(x=height,y=weight)得到相同的图形。

参数设定的两种方式——位置和指定——可以在同一调用中混合使用。

即使一个函数调用中没有参数,你也必须写圆括号,比如ls()用来显示工作区的内容。一个容易犯的错误就是略去了(),这将显示一段R代码,因为输入ls本身意味着你想看到函数的定义而不是去执行它。

函数的形参是函数定义的一部分。一个函数的形式参数可以通过如下方式看到,以plot.default为例(这个函数是当把变量x传递给plot时不指定特定的作图方法时调用的):

请注意,大部分参数都有默认值,这意味着假如你没有指定type参数,函数将认为你设定了type="p"。对许多参数来说,NULL被默认为参数未被具体指定的标志,允许函数内部定义一些具体的性能。比如,如果没有被具体指定,则xlab和ylab将由x和y传递的实际值来构造(具体细节在此不作过多解释)。

三联点(…)参数意味着还可以有未指定名称和数字的其他参数。这通常表示转化为其他功能,尽管有些函数对其有特殊的含义。比如,在data.frame和 c中,…参数的名称就变成结果元素的名称。

 向量

前面我们已经看到数值向量。还有其他两种向量,即字符向量和逻辑向量。

字符向量是一个字符串的向量,它的元素用引号来指定和输出:

用单引号或双引号都可以,只要保持引号左右两端一致则可。

然而,应该注意避免误用某些键盘上的撇号重音键(’)。为避免错误,本书将全部使用双引号。逻辑向量可以取值TRUE或FALSE(或NA;见1.2.5小节)。在输入的时候,你可以用简写T和F(如果你足够仔细没有重新定义它们的话)。跟其他向量类型一样,逻辑向量也用c函数来定义:

实际上,你不必经常指定如上形式的逻辑向量。更常用的是在函数调用中,使用一个单个的逻辑值决定打开还是关闭一个选项。多值向量通常会产生关系表达式:

我们将在1.2.12小节条件选择情形下再回到关系表达式和逻辑运算上来。

引用和转义序列

对带引号的字符串需要特殊的考虑,比如,怎样在字符串中插入引号,怎样对待特殊符号,如换行符。这时需要使用转义序列。接下来我们将看到这些,但之前我们先要观察如下内容。

一个文本字符串本身和它的输出形式之间是有差别的。比如,给一个字符串"Huey",它是一个有4个字符的字符串,不是6个。引号不是字符串的一部分,它们出现在这里只是系统要告诉你字符串和变量名称的区别。

如果要输出一个字符向量,通常引号将被加到每一个元素上。使用cat函数可以避免这个问题。例如:

这就输出一个不带引号的字符串,而仅用空格字符分隔。字符串后也没有换行符,所以下一行输入的提示符(>)直接跟在这一行的末尾。(注意,当字符向量通过cat输出时,没有办法与单一字符串"Huey Dewey Louie"区分。)

为将系统提示符转到下一行,必须用一个换行符:

在这里,\ n是转义序列的一个例子。实际上,虽然它表示一个单一的字符:换行(LF),但被表达成两个。反斜杠(\)被称为转移字符。同样,你可以用\"方式插入引用字符,如

也有其他方式插入其他控制字符或特殊字形,但太过详细的讨论反而会使我们迷茫。当然,还有一个重要的问题,即转义字符本身怎么处理。它也必须被转义,所以要在一个字符串中插入反斜杠,就必须输入两次。在Windows中指定文件路径时,这一点是很重要的,见2.4.1小节。

 缺失值

在实际数据分析中,数据点很多时候是无法得到的(病人没来,试验失败等原因),统计软件需要有办法来处理这种情况。R允许向量包含特殊的NA值。这个值在计算中可以执行,从而对NA的操作也产生NA作为结果。还有一些与处理缺失值有关的特殊问题,我们将在后面碰到的时候讨论(参考索引中的“缺失值”)。

生成向量的函数

这里我们介绍3个函数:c、seq和rep,用于不同情形下向量的创建。

第一个函数c,前面已经做过介绍了。它是"concatenate"(连结)的简写,含义是把各分项首尾连接,这正是这个函数要做的:

你也可以如下连接多于一个元素的向量:

不过,你不需要用c来创建长度为1的向量。人们有时会打出比如c(1)来,但实际上这跟直接打出1是一样的。

另外也有可能给某些元素命名。这改变了变量输出的方式,这样做通常是出于显示的目的。

(在这个例子中,用c确实是有意义的,即使针对只有一个元素的向量。)

名称可以被提取或使用names设置:

一个向量的所有元素类型都相同。如果你连接不同类型的向量,它们将被转化为最少“限制”的类型:

也就是说,逻辑值被转换为0/1或"False"/"True",数字转换为它们的输出形式的字符串。

第二个函数seq(“sequence”),用来建立数字等差序列。如

产生如上所示的从4到9的整数。如果你希望序列的间距是2,写

这种类型的序列经常会被用到,特别是作图的时候。比如,我们之前用c(1.65,1.70,1.75,1.80,1.85,1.90)来定义曲线的x坐标,这个也可以写成seq(1.65,1.90,0.05)(如果身高的步长是1 cm而不是5 cm的话,那么用seq的好处就更明显了)。

步长大小等于1的情形还可以用一种特殊的句法写出来:

上述结果完全等同于seq(4,9),只是更易于阅读。

第三个函数rep(“replicate”),用来产生重复值。使用时有两个参数,依赖于第二个参数是一个向量还是一个数字,产生的结果会有不同:

调用的第一个rep函数对整个向量oops重复三次。第二次调用用一个有3个值(1,2,3)的向量代替了数字3;这些值相应于oops向量的每一个元素,意味着7重复1次,9两次,13重复3次。rep函数通常用于生成编码的情形:如果已知前10个观测者是男性,后15个观测者是女性,可以使用:

生成一个向量,给每一个观测标记它是来自于一个男性还是女性。

一个特例是当每一个分量有相同的重复时,可以使用each参数来获取。比如,rep(1:2,each=10)与rep(1:2,c(10,10))完全等同。

矩阵和数组

在数学上,矩阵就是一个二维的数值数组。矩阵在理论和实践统计学中有很多用途,但这里不假设读者对矩阵代数很熟悉。很多矩阵运算,包括矩阵乘法等,我们略过不讲(安装时自带的文档“R语言介绍”很好地概述了这些内容)。然而,矩阵和更高维的数组确实也用于更简单的场景,主要是用于表格,故这里对其进行一些基本的介绍。

在R中,“矩阵”被拓展成任意类型的元素,所以,你也可以建立一个字符串矩阵。矩阵和数组被表达成带维数的向量:

dim函数设置或更改x的维度,使R将一个12个数字的向量作为一个3×4矩阵处理。注意,矩阵的存储是以列为主的,即第二列元素跟在第一列元素的后面,以此类推。

创建矩阵的一个方便的方法是使用matrix函数:

请注意,这里byrow=T将矩阵改变成以按行而不是按列形式填充。

对矩阵进行操作的有用函数包括rownames、colnames、转置函数t(注意小写t与代表"TRUE"的大写T对应),该函数对矩阵进行转置计算。

特征向量LETTERS是一个包含大写字母A—Z的内置变量。其他有用的相似变量是分别表示小写字母、月份名称、月份名称缩写的letters、month.name和month.abb。

可以按行或者按列分别使用cbind和rbind函数将向量“粘”到一起。

我们将在4.5节返回表格操作上来,讨论数据集中变量的表格化操作。

因子

分类变量在统计数据中是常见的,表明数据的某些细分属性,如社会阶层、主要诊断、肿瘤分期、青春期阶段等。通常它们用数字代码输入。

在R中,这些变量被指定为因子。这种数据结构使得不同的分类类别被赋予有意义的名称成为可能。

对R来说,区分类别编码和取值有直接数字含义的变量是最基本的(参考第7章)。

术语说一个因子有一系列水平——比方说4个水平。一个四水平因子包含两项含义:(a)1到4之间整数的一个向量;(b)一个长度为4的包含字符串的特征向量。我们看一个例子:

第一个命令创建了一个数值向量pain,对5个病人的疼痛水平编码。我们希望把它作为一个分类变量处理,所以我们通过它利用函数factor创建一个因子fpain。它的调用除了pain以外,还跟着另一个参数,即levels=0:3,这意味着输入的编码使用了3个值0~3。后者在理论上可以省略,因为R默认使用pain中合理排序的值,但保留它是一个很好的习惯,如下所示。最后一行的作用是将水平名称转换为特定的4个字符串。

下面的结果是显而易见的:

函数as.numeric提取数字编码为1~4,levels提取水平的名称。注意根据数字0~3的原始输入编码不显示了,一个因子的内置表达方式始终是从1开始的数字。

R允许创建一个特殊类型的因子,其水平是有顺序的。这通过ordered函数来完成,它类似于factor。这可能是有用的,因为它们区分名义变量和顺序变量(可以认为text.pain应该是一个有序因子)。不幸的是,在R中,默认因子水平是等距离的(通过生成多项式对照),所以这个阶段最好忽略有序因子。

列表

把一系列对象组合成一个复合对象有时候是很有用的。这可以通过lists实现。

你可以通过函数list创建一个列表。

作为示例,考虑一组来自Altman (1991, p. 183)的关于一群妇女月经前后的能量摄入数据。我们可以把这些数据按如下方式放在两个向量中:

注意输入行可以中断并在下一行继续。当表达式在语句结构上不完整时,按下Enter键,R认为表达式将会在下一行继续并改变正常的>提示符成为接续符+。这通常发生在不经意间忘了一个括号或其他类似的问题。这种情形下,可以在下一行完成表达式或者按ESC键(在Windows和Macintosh下)或Ctrc-C(在UNIX下)。在Windows下也可以按这种组合键来停止。

把一些单独的向量组合成一个列表,可以用:

列表各部分通过list中使用的参数名称来命名。命名的部分可以如下提取:

许多R的内置函数计算结果不仅仅是一个向量,因此以列表的形式返回结果。

数据框

在其他统计软件包中,数据框被称为“数据矩阵”或“数据集”。它是一系列等长度的向量和/或因子,它们交叉相关,使得同一位置的数据来自同一试验单元(对象、动物等)。除此之外,它具有唯一的一组行名称。

你可以从一组业已存在的向量中创建数据框。

请注意,这些数据是成对的,即同一个妇女在经前摄入5 260 kJ,在经后摄入3 910 kJ。

像lists一样,组分(单个变量)可以通过$符号获得:

索引

如果你需要向量中一个具体的元素,比如第5个妇女的经前摄入能量,你可以用:

方括号用来选择数据,也称为索引(indexing)或子集选择(subsetting)。如果你希望修改向量中元素的值的话,这也适用于左侧赋值(比如,你可以用intake.pre[5] <- 6390)。

如果你希望一个包含多于一个妇女的数据的子向量,比如序号为3、5、7、可以用一个向量来索引:

注意使用c(…)构造来定义包含数字3、5、7的向量是必要的。intake.pre[3,5,7]则意味着完全不同的功能。它将指定索引一个三维阵列。

当然,如果一个索引向量储存在一个变量中,用这个向量进行索引也适用。当你需要用同样方式索引几个变量时,这种方式是很有用的。

值得一提的是,为了得到一列元素,比如前5个,你可以用a:b形式:

R的一个巧妙功能是可以使用负索引。你可以通过下式得到除了位置为3、5、7之外的所有其他元素:

正的和负的索引不能混合,否则将造成很大的混淆。

条件选择

我们在1.2.11小节看到如何使用一个或几个索引提取数据。实际中,你经常需要提取一些满足某种标准的数据,比如来自于男性的,或者青春期前的,或是患有慢性病的人数等。这可以通过插入一个关系表达式而不是用指标索引来完成:

这产生了经后能量摄入超过7 000 kJ的4个妇女的经后摄入能量值。

当然,仅当进入关系表达式的向量与被检索的变量有相同长度时,这种表达式才有意义。

比较操作符有<(小于)、>(大于)、==(等于)、<=(小于或等于)、> =(大于或等于)、!=(不等于)。注意,双等号用来判断是否相等。这是为了避免与=混淆,=通常用来对函数参数进行赋值。此外,!=运算符也是新的符号,!符号表示否定。相同的符号也用于C编程语言中。

要结合几个表达式,你可以使用逻辑运算符&(逻辑“和”),|(逻辑“或”),!(逻辑“不是”)。例如,我们用下式找到经前摄入7 000—8 000 kJ的妇女停经后的摄入量值:

还有&&和||符号,用于R编程中的流程控制。然而,它们超出了我们此刻的讨论范围。

值得仔细看一下的是,当你用逻辑表达式作为索引的时候到底发生了什么。逻辑表达式的结果是如1.2.3小节所描述的逻辑向量:

用逻辑向量进行索引意味着你选出来的是那些逻辑值为TRUE的值。所以在上一个例子中我们得到的是intake.post中的第8个和第9个值。

如果缺失值(NA,见1.2.5小节)出现在索引向量中,那么R将在结果创建一个元素,但它的值是NA。

除了关系和逻辑运算符外,还有许多函数返回逻辑值。特别重要的一个是is.na(x),它用来寻找x中的哪些元素被记录成缺失(NA)。

注意,is.na(x)的一个必需之处在于你不能直接通过x==NA比较。否则将对任何x的值直接给出NA作为结果,因为与一个未知值比较的结果仍然是未知的!

数据框的索引

我们已经看到如何能从一个数据框中提取向量,比如通过输入d$intake.post语句。然而,也可以使用另外一种表示法,它直接使用类似于矩阵的结构:

给出第5行,第1列(第5位妇女的“前”测量数据),以及

给出第5位妇女的所有测量数据。注意,d[5,]中的“,”是必要的;没有“,”,比如d[2],你得到的是由d的第2列(更像d[,2],它是列本身)构成的数据框。

其他索引技术也可应用。特别地,对满足某些标准的个体,提取其所有的数据是有用的,比如那些停经前能量输入超过7 000 kJ的妇女:

这里我们提取的是数据框中的某些行,它们满足intake.pre>7 000。注意,行的名称与它们在原始数据结构中的一致。

如果想理解细节的话,把它分解成一些小的步骤看上去会更容易。作法应该如下:

sel(select简写)变成一个逻辑向量,对4个满足条件的妇女,它的值是TRUE。d[sel,]的索引生成了那些来自于sel是TRUE的行的数据,因为逗号后是空的,所以列取的是相应行的全部列数据。

看一下数据集中的前几个观测通常是一种方便的作法。这可以通过索引来完成:

这是经常会遇到的问题,为此有一个方便的函数head。它默认显示前六行:

类似地,tail函数显示最后面的六行。

分组数据和数据框

在数据框中存储分组数据的自然方法是在一个向量中存储数据本身,另一个与之平行的因子(factor)记录哪些数据来自哪些组。例如,考虑下述消瘦和肥胖妇女的能量消耗的数据集:

这是一种方便的格式,因为它很容易推广到有多个标准的数据分类中去。然而,有时候希望对每一组有一个独立的向量存储数据。幸运的是,从数据框中可以很容易地提取这些数据:

或者你也可以使用split函数,它根据分组生成一系列向量:

隐式循环

2.3.1小节将描述R中的循环结构。就本书而言,读者可以略过这些内容。然而,下面一组R函数是很有用并值得学习的。

循环的一个常用功能是把一个函数应用到一组值或向量中的每一个元素,并将结果返回一个单式结构中。在R中,这被抽象成lapply和sapply两个函数。前者总是返回一个列表(故用'l'标识),而后者则尽可能将结果简化(用's'标识)成矢量或矩阵。因此,计算数值向量组成的数据框中每个变量的均值可以如下操作:

注意,两种情形下对结果附加具有一定含义名称的方式,这是人们倾向于使用这两个函数而不是显式循环的另一个原因。lapply/sapply第二个参数是所应用的函数,此处是mean。其他参数被传递到该函数中,这里我们用na.rm=T要求移除缺失值(见4.1节)。

有时候你只想多次重复某个过程,而仍将结果归结为一个向量。显然,这只在多次重复的计算结果不完全相同时才有意义,比如通常的模拟研究。这时可用sapply执行,但有一个更简化的版本,即replicate,用它你只需给定一个数值和表达式就可以计算:

还有一个类似的函数apply,用它可以针对矩阵的行或列(或广义下多维数组的下标)进行操作,如

第二个参数表明将函数应用到哪个维度(或下标向量),上面操作执行的是逐列取最小值。

此外,还有一个函数tapply用来创建表格(用t标识),该表由函数关于第二个参数定义的子组上的返回值构成,其中参数可以是一个因子或一列因子。后一种情形将生成一个交叉分类表。(分组也可以通过普通向量定义,它们将在内部被转换为因子。)

排序

对向量排序是常见的工作,只需使用sort函数即可。(我们这里使用内置数据集intake;它包含在1.2.9小节使用的相同的数据。)

(由于已经排过序,故intake$pre就不用在这里了。)

然而,仅对一个单一的向量排序并不总能满足需求。你经常需要根据某些其他变量的值对一系列变量排序——如根据性别和年龄对血压排序。为此目的,有一个结构看上去可能很抽象,但确实功能强大。首先计算一个变量的次序。

结果是数字1至11(或者其他的向量长度),根据order参数(这里是intake$post)的大小排序。对order结果的解释有点棘手——应该是如下所述:通过将其值依次置于3、1、2、6等对intake$post排序。

要点在于,通过对这个向量的索引,其他变量可以依据同样的标准排序。注意,一个包含从数字1到元素个数的索引向量准确地相应于元素重新排序的序号。

这里intake$post已经被排序了——正如sort(intake$post)——同时intake$pre也已经被按照相应于intake$post的大小进行了排序。

当然,对整个数据框intake排序也是可以的。

有几个标准的排序可以通过设置order的几个参数来完成。比如order(sex,age),给出男性和女性的优先划分,然后在每一个性别中按年龄排序。当排序不能根据第一个变量完成时,将使用第二个变量。按逆序排列也可以处理,比如通过改变变量的符号来完成。

本文摘自《R语言统计入门(第2版)》

推荐阅读更多精彩内容