R 简介

这是 R(“GNU S”)的简介,R 是一种用于统计计算和图形的语言和环境。R 与屡获殊荣的1 S 系统类似,该系统由贝尔实验室的 John Chambers 等人开发。它提供了各种统计和图形技术(线性与非线性建模、统计检验、时间序列分析、分类、聚类,...)。

本手册提供了有关数据类型、编程元素、统计建模和图形的信息。

本手册适用于 R 4.3.3 版(2024-02-29)。

版权所有 © 1990 W. N. Venables
版权所有 © 1992 W. N. Venables 和 D. M. Smith
版权所有 © 1997 R. Gentleman 和 R. Ihaka
版权所有 © 1997、1998 M. Maechler
版权所有 © 1999–2023 R 核心团队

在所有副本上保留版权声明和此许可声明的前提下,允许制作和分发本手册的逐字副本。

在逐字复制的条件下,允许复制和分发本手册的修改版本,前提是整个派生作品在与本声明相同的许可声明的条款下进行分发。

在上述修改版本条件下,允许复制和分发本手册的翻译版本,但此许可声明可以由 R 核心团队批准的翻译中陈述。

目录


序言

这份 R 简介源自比尔·韦纳布尔斯和大卫·M·史密斯在 1990-2 年在阿德莱德大学时编写的描述 S 和 S-PLUS 环境的原始笔记。我们做了一些小的更改以反映 R 和 S 程序之间的差异,并扩展了一些内容。

我们要向比尔·韦纳布尔斯(和大卫·史密斯)表示衷心的感谢,感谢他们允许以这种方式分发此修改版的笔记,并一直支持 R。

我们始终欢迎评论和更正。请将电子邮件发送至

给读者的建议

大多数 R 新手都会从附录 A 中的介绍性会话开始。这应该有助于熟悉 R 会话的风格,更重要的是,可以立即反馈实际发生的情况。

许多用户主要会使用 R 的图形功能。请参见 图形过程,几乎可以在任何时候阅读,不必等到消化完所有前面的部分。


1 简介和准备工作


1.1 R 环境

R 是一套用于数据处理、计算和图形显示的集成软件工具。除其他功能外,它还具有

  • 有效的数据处理和存储工具,
  • 一组用于对数组(尤其是矩阵)进行计算的运算符,
  • 大量连贯的集成数据分析中间工具,
  • 用于数据分析和显示的图形工具,可直接在计算机上或以硬拷贝形式显示,以及
  • 一种经过完善的、简单有效的编程语言(称为“S”),其中包括条件、循环、用户定义的递归函数以及输入和输出工具。(实际上,系统提供的大多数函数都是用 S 语言编写的。)

术语“环境”旨在将其描述为一个经过全面规划和连贯的系统,而不是像其他数据分析软件那样,是逐渐积累的非常具体且不灵活的工具。

R 在很大程度上是用于开发交互式数据分析新方法的工具。它发展迅速,并已通过大量程序包进行了扩展。但是,大多数用 R 编写的程序本质上都是短暂的,是为单一数据分析编写的。


1.3 R 和统计

我们对 R 环境的介绍并未提及统计,但许多人将 R 用作统计系统。我们更愿意将它视为一个环境,其中已实现许多经典和现代统计技术。其中一些内置于 R 基本环境中,但许多以的形式提供。R 附带约 25 个包(称为“标准”和“推荐”包),还有更多包可通过CRAN系列互联网网站(通过https://CRAN.R-project.org)和其他地方获得。稍后将提供有关包的更多详细信息(请参见)。

大多数经典统计和许多最新方法都可以与 R 一起使用,但用户可能需要做好一些准备工作来查找它们。

S(因此还有 R)与其他主要统计系统在理念上存在着重要的差异。在 S 中,统计分析通常作为一系列步骤来完成,中间结果存储在对象中。因此,虽然 SAS 和 SPSS 会从回归或判别分析中给出大量输出,但 R 会给出最少量的输出,并将结果存储在拟合对象中,以便通过其他 R 函数进行后续查询。


1.4 R 和窗口系统

使用 R 最方便的方式是在运行窗口系统的图形工作站上。本指南针对拥有此类设施的用户。特别是,我们偶尔会提到在 X 窗口系统上使用 R,尽管所述内容的大部分通常适用于 R 环境的任何实现。

大多数用户会发现,有时有必要直接与计算机上的操作系统进行交互。在本指南中,我们主要讨论与 UNIX 机器上的操作系统的交互。如果你在 Windows 或 macOS 下运行 R,则需要进行一些小的调整。

设置工作站以充分利用 R 的可自定义特性是一个直接但有些繁琐的过程,此处将不再进一步考虑。遇到困难的用户应寻求当地专家的帮助。


1.5 交互使用 R

当你使用 R 程序时,它会在需要输入命令时发出提示。默认提示为“>”,在 UNIX 中它可能与 shell 提示相同,因此可能看起来什么都没有发生。但是,正如我们所看到的,如果你愿意,可以轻松更改为不同的 R 提示。我们将假设 UNIX shell 提示为“$”。

在 UNIX 下使用 R 时,建议首次执行以下过程

  1. 创建一个单独的子目录,例如 work,用于保存你将在此问题上使用 R 的数据文件。每当你针对此特定问题使用 R 时,这都将是工作目录。
    $ mkdir work
    $ cd work
    
  2. 使用以下命令启动 R 程序
    $ R
    
  3. 此时可以发出 R 命令(稍后参见)。
  4. 退出 R 程序的命令是
    > q()
    

    此时系统会询问您是否要保存 R 会话中的数据。在某些系统上,这会弹出一个对话框,而在其他系统上,您将收到一个文本提示,您可以回复 yesnocancel(使用单个字母缩写即可)来在退出前保存数据、不保存而退出或返回 R 会话。保存的数据将在将来的 R 会话中可用。

后续 R 会话很简单。

  1. work 设置为工作目录,然后像以前一样启动程序
    $ cd work
    $ R
    
  2. 使用 R 程序,在会话结束时以 q() 命令终止。

要在 Windows 下使用 R,遵循的过程基本上是相同的。创建一个文件夹作为工作目录,并在 R 快捷方式的 开始于 字段中设置该文件夹。然后双击该图标启动 R。

1.6 入门会话

强烈建议希望在继续之前在计算机上体验 R 的读者完成 示例会话 中给出的入门会话。


1.7 获取函数和功能的帮助

R 具有类似于 UNIX 的 man 功能的内置帮助功能。要获取有关任何特定命名函数(例如 solve)的更多信息,命令是

> help(solve)

另一种方法是

> ?solve

对于由特殊字符指定的特性,参数必须用双引号或单引号括起来,使其成为“字符字符串”:对于一些具有语法含义的单词,包括 ifforfunction,这也是必需的。

> help("[[")

两种形式的引号都可以用来转义对方,就像字符串 "It's important" 中一样。我们的惯例是优先使用双引号。

在大多数 R 安装中,可以通过运行以下命令以 HTML 格式获得帮助

> help.start()

这将启动一个 Web 浏览器,允许使用超链接浏览帮助页面。在 UNIX 上,后续帮助请求将发送到基于 HTML 的帮助系统。由 help.start() 加载的页面中的“搜索引擎和关键字”链接特别有用,因为它包含一个高级概念列表,用于搜索可用函数。这可能是一种快速了解情况并理解 R 提供的广度的绝佳方式。

help.search 命令(或 ??)允许以多种方式搜索帮助。例如,

> ??solve

尝试 ?help.search 以获取详细信息和更多示例。

通常可以通过以下方式运行帮助主题中的示例

> example(topic)

R 的 Windows 版本有其他可选的帮助系统:使用

> ?help

了解更多详细信息。


1.8 R 命令、大小写敏感性等。

从技术角度来看,R 是一种表达式语言,语法非常简单。它区分大小写,就像大多数基于 UNIX 的软件包一样,因此Aa是不同的符号,它们表示不同的变量。可以在 R 名称中使用的符号集取决于 R 运行所在的操作系统和国家/地区(从技术角度上讲,取决于所使用的区域设置)。通常允许使用所有字母数字符号2(在某些国家/地区,还包括带重音符号的字母)以及“.”和“_”,但有一个限制,即名称必须以“.”或字母开头,如果以“.”开头,则第二个字符不能是数字。名称的长度实际上不受限制。

基本命令由表达式赋值组成。如果表达式作为命令给出,则会对其求值、打印(除非专门将其设为不可见),并且会丢失该值。赋值也会对表达式求值,并将该值传递给变量,但不会自动打印结果。

命令由分号(“;”)或换行符分隔。可以通过大括号(“{”和“}”)将基本命令组合成一个复合表达式。注释几乎可以放在3任何地方,以井号(“#”)开头,该行末尾的所有内容都是注释。

如果一行末尾的命令不完整,R 会给出不同的提示,默认情况下

+

在第二行及后续行,并继续读取输入,直到命令在语法上完整为止。用户可以更改此提示。我们通常会省略续行提示,并通过简单的缩进来表示续行。

在控制台中输入的命令行限制4为大约 4095 个字节(不是字符)。


1.9 召回和更正之前的命令

在许多版本的 UNIX 和 Windows 上,R 提供了一种机制,用于召回和重新执行之前的命令。可以使用键盘上的垂直箭头键向前和向后滚动命令历史记录。通过这种方式找到命令后,可以使用水平箭头键在命令中移动光标,并可以使用DEL键删除字符,或使用其他键添加字符。稍后会提供更多详细信息:请参阅命令行编辑器

UNIX 下的召回和编辑功能具有高度可定制性。你可以通过阅读 readline 库的手册条目来了解如何执行此操作。

或者,Emacs 文本编辑器通过 ESSEmacs Speaks Statistics)提供了更通用的支持机制,以便与 R 交互式地工作。请参阅 R 统计系统常见问题解答 中的 R 和 Emacs


1.10 从文件执行命令或将输出转至文件

如果命令5存储在外部文件中,比如工作目录 work 中的 commands.R,则可以使用以下命令在 R 会话中随时执行这些命令

> source("commands.R")

对于 Windows,Source 也可在 File 菜单中找到。函数 sink,

> sink("record.lis")

会将控制台的所有后续输出转至外部文件 record.lis。命令

> sink()

会将其再次还原到控制台。


1.11 数据持久性和删除对象

R 创建和处理的实体称为对象。这些对象可以是变量、数字数组、字符字符串、函数或由这些组件构建的更通用的结构。

在 R 会话期间,对象将按名称创建并存储(我们将在下一部分讨论此过程)。R 命令

> objects()

(或者 ls()) 可用于显示当前存储在 R 中的(大多数)对象的名称。当前存储的对象集合称为工作空间

要移除对象,可以使用函数 rm

> rm(x, y, z, ink, junk, temp, foo, bar)

在 R 会话期间创建的所有对象都可以永久存储在文件中,以便在将来的 R 会话中使用。在每个 R 会话结束时,您都有机会保存当前所有可用的对象。如果您表示要执行此操作,则对象将写入名为 .RData6 的文件(位于当前目录中),并且会话中使用的命令行将保存到名为 .Rhistory 的文件中。

当稍后从同一目录启动 R 时,它会从该文件重新加载工作空间。同时,将重新加载关联的命令历史记录。

建议您为使用 R 进行的分析使用单独的工作目录。在分析期间,通常会创建名称为 xy 的对象。此类名称在单个分析的上下文中通常有意义,但在同一目录中进行多次分析时,很难确定它们可能是什么。


2 简单操作;数字和向量


2.1 向量和赋值

R 对命名的数据结构进行操作。最简单的此类结构是数字向量,它是一个由有序数字集合组成的单一实体。要设置一个名为 x 的向量(比如包含五个数字 10.4、5.6、3.1、6.4 和 21.7),请使用 R 命令

> x <- c(10.4, 5.6, 3.1, 6.4, 21.7)

这是一个赋值语句,使用函数 c(),在此上下文中,它可以采用任意数量的向量参数,其值是通过将参数首尾相连而获得的向量。7

表达式中单独出现的数字被视为长度为一的向量。

请注意赋值运算符(‘<-’),它由两个字符‘<’(“小于”)和‘-’(“减号”)严格并排组成,它“指向”接收表达式值的 对象。在大多数情况下,‘=’运算符可用作替代项。

还可以使用函数 assign() 进行赋值。与上述相同的赋值的等效方式如下

> assign("x", c(10.4, 5.6, 3.1, 6.4, 21.7))

通常的运算符 <- 可以被认为是此语法的一个快捷方式。

还可以使用赋值运算符的明显更改向其他方向进行赋值。因此,可以使用以下方式进行相同的赋值

> c(10.4, 5.6, 3.1, 6.4, 21.7) -> x

如果表达式用作完整命令,则该值将被打印并丢失8。因此,如果现在我们使用命令

> 1/x

五个值的倒数将打印在终端(当然,x 的值保持不变)。

进一步的赋值

> y <- c(x, 0, x)

将创建一个向量 y,其中包含 11 个条目,由两个 x 副本和中间位置的零组成。


2.2 向量算术

向量可用于算术表达式中,在这种情况下,逐个元素执行操作。出现在同一表达式中的向量不必全部具有相同的长度。如果它们不同,则表达式的值为与表达式中出现的最长向量具有相同长度的向量。表达式中较短的向量会根据需要(可能按分数)被循环利用,直到它们与最长向量的长度匹配。特别是常量只是被简单地重复。因此,对于以上赋值,命令

> v <- 2*x + y + 1

通过逐个元素相加生成一个新的长度为 11 的向量 v,其中包括重复 2.2 次的 2*x、重复一次的 y 和重复 11 次的 1

基本算术运算符是通常的 +-*/^(用于求幂)。 此外,所有常见的算术函数都可用。logexpsincostansqrt 等都具有其通常的含义。 maxmin 分别选择向量的最大元素和最小元素。 range 是一个函数,其值是一个长度为 2 的向量,即 c(min(x), max(x)) length(x)x 中的元素数, sum(x) 给出 x 中元素的总和, prod(x) 给出它们的乘积。

两个统计函数是 mean(x),它计算样本均值,与 sum(x)/length(x) 相同,var(x),它给出

sum((x-mean(x))^2)/(length(x)-1)

或样本方差。如果 var() 的参数是一个 n 乘以 p 的矩阵,则该值是一个 p 乘以 p 的样本协方差矩阵,通过将行视为独立的 p 元样本向量得到。

sort(x) 返回一个与 x 大小相同的向量,其中元素按递增顺序排列;但是,还有其他更灵活的排序工具可用(请参见 order()sort.list(),它们生成一个用于排序的排列)。

请注意,maxmin 选择其参数中的最大值和最小值,即使给出了多个向量。并行最大值和最小值函数 pmaxpmin 返回一个向量(长度等于其最长的参数),其中每个元素包含任何输入向量中该位置的最大(最小)元素。

对于大多数目的,用户不必关心数字向量中的“数字”是整数、实数还是复数。在内部,计算以双精度实数或(如果输入数据为复数)双精度复数进行。

要处理复数,请提供一个明确的复数部分。因此

sqrt(-17)

将给出 NaN 和一个警告,但

sqrt(-17+0i)

将以复数形式进行计算。


2.3 生成规则序列

R 有许多用于生成常用数字序列的工具。例如 1:30 是向量 c(1, 2, …, 29, 30) 冒号运算符在表达式中具有较高的优先级,因此,例如 2*1:15 是向量 c(2, 4, …, 28, 30)。将 n <- 10,然后比较序列 1:n-11:(n-1)

构造 30:1 可用于反向生成序列。

函数 seq() 是用于生成序列的更通用的工具。它有五个参数,在任何一次调用中只能指定其中一些参数。如果给出了前两个参数,则指定序列的开头和结尾,如果这两个参数是给出的唯一两个参数,则结果与冒号运算符相同。即 seq(2,10) 与向量 2:10 相同。

传递给 seq() 和许多其他 R 函数的参数也可以以命名形式给出,在这种情况下,它们出现的顺序无关紧要。前两个参数可以命名为 from=valueto=value;因此 seq(1,30)seq(from=1, to=30)seq(to=30, from=1) 都与 1:30 相同。传递给 seq() 的后两个参数可以命名为 by=valuelength=value,它们分别指定序列的步长和长度。如果未给出这两个参数,则假定默认 by=1

例如

> seq(-5, 5, by=.2) -> s3

s3 中生成向量 c(-5.0, -4.8, -4.6, …, 4.6, 4.8, 5.0)。类似地

> s4 <- seq(length=51, from=-5, by=.2)

s4 中生成相同的向量。

第五个参数可以命名为 along=vector,通常用作创建序列 1, 2, …, length(vector) 的唯一参数,如果向量为空(它可以为空),则创建空序列。

一个相关的函数是 rep() ,它可以用于以各种复杂的方式复制对象。最简单的形式是

> s5 <- rep(x, times=5)

它将在 s5 中将 x 的五个副本首尾相连地放置。另一个有用的版本是

> s6 <- rep(x, each=5)

它在继续下一个元素之前重复 x 的每个元素五次。


2.4 逻辑向量

除了数字向量,R 还允许操作逻辑量。逻辑向量的元素可以具有值 TRUEFALSENA(表示“不可用”,见下文)。前两个通常分别缩写为 TF。但请注意,TF 只是默认设置为 TRUEFALSE 的变量,但不是保留字,因此可以被用户覆盖。因此,您应始终使用 TRUEFALSE

逻辑向量由条件生成。例如

> temp <- x > 13

temp 设置为与 x 长度相同的向量,其中 FALSE 值对应于 x 中不满足条件的元素,TRUE 值对应于满足条件的元素。

逻辑运算符为 <<=>>===(表示精确相等)和 !=(表示不相等)。 此外,如果 c1c2 是逻辑表达式,则 c1 & c2 是它们的交集(“与”),c1 | c2 是它们的并集(“或”),!c1c1 的否定。

逻辑向量可用于普通算术中,在这种情况下,它们会强制转换为数字向量,FALSE 变成 0TRUE 变成 1。但是,在某些情况下,逻辑向量及其强制转换的数字对应项并不等效,例如,请参阅下一小节。


2.5 缺失值

在某些情况下,向量的分量可能不完全已知。当一个元素或值“不可用”或在统计意义上为“缺失值”时,可以通过将特殊值 NA 分配给它来为其在向量中保留一个位置。 通常,对 NA 的任何操作都会变成 NA。此规则的动机很简单,即如果操作的规范不完整,则无法知道结果,因此不可用。

函数 is.na(x) 会生成一个与 x 大小相同的逻辑向量,当且仅当 x 中的相应元素为 NA 时,该向量的值为 TRUE

> z <- c(1:3,NA);  ind <- is.na(z)

请注意,逻辑表达式 x == NAis.na(x) 有很大不同,因为 NA 实际上不是一个值,而是一个标记,表示一个不可用的量。因此,x == NA 是一个与 x 长度相同的向量,所有 值都是 NA,因为逻辑表达式本身是不完整的,因此无法确定。

请注意,还有第二种“缺失”值,是由数值计算产生的,即所谓的 非数字NaN 值。示例如下:

> 0/0

> Inf - Inf

由于结果无法合理定义,因此两者都给出 NaN

总之,is.na(xx)NANaN 值都为 TRUE both。为了区分它们,is.nan(xx) 仅对 NaNTRUE

在不带引号打印字符向量时,缺失值有时会打印为 <NA>


2.6 字符向量

字符量和字符向量在 R 中经常使用,例如作为绘图标签。在需要的地方,它们由双引号字符分隔的一系列字符表示,例如 "x-values""New iteration results"

字符字符串使用匹配的双 (") 或单 (') 引号输入,但使用双引号(或有时不带引号)打印。它们使用 C 样式转义序列,使用 \ 作为转义字符,因此 \ 输入并打印为 \\,在双引号内 " 输入为 \"。其他有用的转义序列有 \n,换行符,\t,制表符和 \b,退格符——有关完整列表,请参见 ?Quotes

字符向量可以通过 c() 函数连接成一个向量;其用法示例将经常出现。

paste() 函数接受任意数量的参数,并将它们逐个连接成字符串。参数中给出的任何数字都会以明显的方式强制转换为字符串,也就是说,转换方式与打印它们的方式相同。默认情况下,结果中的参数由单个空格分隔,但可以通过命名参数 sep=string 更改分隔符,将其更改为 string,甚至可以为空。

例如

> labs <- paste(c("X","Y"), 1:10, sep="")

labs 变成字符向量

c("X1", "Y2", "X3", "Y4", "X5", "Y6", "X7", "Y8", "X9", "Y10")

特别注意,短列表的回收也在这里发生;因此 c("X", "Y") 重复 5 次以匹配序列 1:109


2.7 索引向量;选择和修改数据集的子集

可以通过在向量名称后附加方括号中的索引向量来选择向量的元素子集。更一般地,任何求值为向量的表达式都可以通过在表达式后立即附加方括号中的索引向量来选择其元素的子集。

此类索引向量可以是四种不同类型中的任何一种。

  1. 逻辑向量。在这种情况下,索引向量将被回收至与要从中选择元素的向量长度相同。选择与索引向量中 TRUE 对应的值,而省略与 FALSE 对应的值。例如
    > y <- x[!is.na(x)]
    

    创建(或重新创建)一个对象 y,其中将按相同顺序包含 x 的非缺失值。请注意,如果 x 有缺失值,则 y 将比 x 短。此外

    > (x+1)[(!is.na(x)) & x>0] -> z
    

    创建一个对象 z,并将向量 x+1 的值放入其中,其中 x 中的对应值既非缺失值,又为正值。

  2. 正整数向量的向量。在这种情况下,索引向量中的值必须位于集合 {1, 2, …, length(x)} 中。向量的相应元素将按该顺序选择并在结果中连接。索引向量的长度可以是任意长度,结果的长度与索引向量的长度相同。例如,x[6]x 的第六个分量,而
    > x[1:10]
    

    选择 x 的前 10 个元素(假设 length(x) 不小于 10)。此外

    > c("x","y")[rep(c(1,2,2,1), times=4)]
    

    (这件不太可能的事情)会生成一个长度为 16 的字符向量,其中包含重复四次的 "x", "y", "y", "x"

  3. 负整数数量向量。此类索引向量指定要 排除 而不是包含的值。因此
    > y <- x[-(1:5)]
    

    给予 y 所有元素,但 x 的前五个元素除外。

  4. 字符字符串向量。此可能性仅适用于对象具有 names 属性以识别其组件的情况。在这种情况下,名称向量的子向量可以与上面第 2 项中的正整数标签以相同的方式使用。
    > fruit <- c(5, 10, 1, 20)
    > names(fruit) <- c("orange", "banana", "apple", "peach")
    > lunch <- fruit[c("apple","orange")]
    

    优点是字母数字 名称 通常比 数字索引 更容易记住。此选项与数据框特别有用,我们稍后会看到。

索引表达式也可以出现在赋值的接收端,在这种情况下,赋值操作仅对向量的 那些元素 执行。表达式必须采用 vector[index_vector] 的形式,因为在此处用任意表达式代替向量名称没有多大意义。

例如

> x[is.na(x)] <- 0

用零替换 x 中的任何缺失值,并且

> y[y < 0] <- -y[y < 0]

具有与

> y <- abs(y)

2.8 其他类型的对象

向量是 R 中最重要的对象类型,但还有其他几种类型,我们将在后面的章节中正式了解它们。

  • 矩阵 或更一般的 数组 是向量的多维概括。事实上,它们 可以用两个或更多索引进行索引并以特殊方式打印的向量。请参阅 数组和矩阵
  • 因子 提供了处理分类数据的方法。请参阅 有序和无序因子
  • 列表是向量的一种通用形式,其中各个元素不必是同种类型,并且它们本身通常是向量或列表。列表提供了一种返回统计计算结果的便捷方式。参见 列表
  • 数据框是类矩阵结构,其中各列可以是不同类型。将数据框视为每观测单位一行,但(可能)同时包含数值变量和分类变量的“数据矩阵”。许多实验最好用数据框描述:处理是分类的,但响应是数字的。参见 数据框
  • 函数本身是 R 中的对象,可以存储在项目的 workspace 中。这提供了一种简单便捷的方式来扩展 R。参见 编写你自己的函数

3 对象及其模式和属性


3.1 内在属性:模式和长度

R 操作的实体在技术上称为对象。示例包括数值(实数)或复数值向量、逻辑值向量和字符串向量。这些被称为“原子”结构,因为它们的组件都是同种类型或模式,即数值10复数逻辑字符原始

向量必须具有所有相同模式的值。因此,任何给定的向量都必须明确地是逻辑数值复数字符原始。(此规则唯一的明显例外是将不可用数量列为特殊“值”NA,但实际上有几种类型的NA)。请注意,向量可以为空,但仍然具有模式。例如,空字符字符串向量列为character(0),空数值向量列为numeric(0)

R 还会操作称为 列表 的对象,其模式为 列表。这些是有序的对象序列,每个对象可以是任何模式。列表 被称为“递归”而不是原子结构,因为它们的组件本身也可以是列表。

其他递归结构是模式为 函数表达式 的结构。函数是构成 R 系统一部分的对象,还有类似的用户编写函数,我们将在后面详细讨论。作为对象的表达式构成了 R 的高级部分,本指南不会讨论,除非在讨论 R 中用于建模的 公式 时间接讨论。

通过对象的 模式,我们指的是其基本组成部分的基本类型。这是对象“属性”的一个特例。每个对象的另一个属性是其 长度。函数 mode(object)length(object) 可用于找出任何已定义结构的模式和长度 11

对象的进一步属性通常由 attributes(object) 提供,请参阅 获取和设置属性。因此,模式长度 也称为对象的“内在属性”。

例如,如果 z 是长度为 100 的复数向量,那么在表达式 mode(z) 中,字符字符串为 "complex"length(z)100

R 几乎在任何可以被认为合理的地方处理模式更改(以及一些可能不合理的地方)。例如,使用

> z <- 0:9

我们可以输入

> digits <- as.character(z)

之后,digits 是字符向量 c("0", "1", "2", …, "9")。进一步的 强制转换 或模式更改会再次重建数值向量

> d <- as.integer(digits)

现在 dz 相同。12 有大量形式为 as.something() 的函数,用于从一种模式强制转换为另一种模式,或用于赋予对象它可能尚未拥有的其他一些属性。读者应查阅不同的帮助文件以熟悉它们。


3.2 更改对象长度

“空”对象仍可能具有模式。例如

> e <- numeric()

使 e 成为模式为数字的空向量结构。类似地,character() 是一个空字符向量,依此类推。一旦创建了任何大小的对象,只需为其提供超出其先前范围的索引值,即可向其中添加新组件。因此

> e[3] <- 17

现在将 e 设为长度为 3 的向量(其前两个分量此时均为 NA)。这适用于任何结构,前提是附加分量的模式与对象在第一位置的模式一致。

这种自动调整对象长度的方法经常使用,例如在输入的 scan() 函数中。(参见 The scan() function。)

相反,截断对象的大小只需进行赋值即可。因此,如果 alpha 是长度为 10 的对象,则

> alpha <- alpha[2 * 1:5]

将其设为长度为 5 的对象,仅包含索引为偶数的前分量。(当然,旧索引不会保留。)然后,我们可以通过以下方式仅保留前三个值

> length(alpha) <- 3

并且可以以相同的方式扩展向量(通过缺失值)。


3.3 获取和设置属性

函数 attributes(object) 返回当前为该对象定义的所有非固有属性的列表。函数 attr(object, name) 可用于选择特定属性。这些函数很少使用,除非在某些特殊情况下为某些特定目的创建新属性时,例如将创建日期或操作员与 R 对象关联起来。但是,这个概念非常重要。

分配或删除属性时应小心,因为它们是 R 中使用的对象系统的一个组成部分。

当它用在赋值的左侧时,它可以用来将新属性与 object 关联起来,或更改现有属性。例如

> attr(z, "dim") <- c(10,10)

允许 R 将 z 视为 10x10 矩阵。


3.4 对象的类

R 中的所有对象都有一个 ,由函数 class 报告。对于简单的向量,这只是模式,例如 "numeric""logical""character""list",但 "matrix""array""factor""data.frame" 是其他可能的值。

一个称为对象的特殊属性class 用于允许面向对象样式13的 R 编程。例如,如果一个对象具有 class "data.frame",它将以某种方式打印,plot() 函数将以某种方式以图形方式显示它,而其他所谓的通用函数,如 summary() 将以对其 class 敏感的方式对其作为参数做出反应。

要暂时移除 class 的影响,请使用函数 unclass() 例如,如果 winter 具有 class "data.frame",则

> winter

将以数据框形式打印它,这有点像矩阵,而

> unclass(winter)

将以普通列表的形式打印它。只有在非常特殊的情况下,您才需要使用此功能,但其中一种情况是当您正在学习了解 class 和通用函数的概念时。

通用函数和 class 将在 Class、通用函数和对象面向 中进一步讨论,但只是简要讨论。


4 有序和无序因子

一个因子是一个向量对象,用于指定相同长度的其他向量的分量的离散分类(分组)。R 提供有序无序因子。虽然因子的“实际”应用是模型公式(参见对比),但我们在此查看一个具体示例。

4.1 一个具体示例

例如,假设我们有一个来自澳大利亚所有州和领地的 30 名税务会计师样本14,并且他们的原籍州由一个字符向量指定,如下所示,其中州助记符为

> state <- c("tas", "sa",  "qld", "nsw", "nsw", "nt",  "wa",  "wa",
             "qld", "vic", "nsw", "vic", "qld", "qld", "sa",  "tas",
             "sa",  "nt",  "wa",  "vic", "qld", "nsw", "nsw", "wa",
             "sa",  "act", "nsw", "vic", "vic", "act")

请注意,对于字符向量,“已排序”表示按字母顺序排序。

同样,使用 factor() 函数创建 因子

> statef <- factor(state)

print() 函数处理因子的方式与处理其他对象略有不同

> statef
 [1] tas sa  qld nsw nsw nt  wa  wa  qld vic nsw vic qld qld sa
[16] tas sa  nt  wa  vic qld nsw nsw wa  sa  act nsw vic vic act
Levels:  act nsw nt qld sa tas vic wa

要找出因子的级别,可以使用 levels() 函数。

> levels(statef)
[1] "act" "nsw" "nt"  "qld" "sa"  "tas" "vic" "wa"

4.2 函数 tapply() 和参差不齐的数组

继续前面的示例,假设我们在另一个向量中拥有相同税务会计师的收入(以适当的大货币单位表示)

> incomes <- c(60, 49, 40, 61, 64, 60, 59, 54, 62, 69, 70, 42, 56,
               61, 61, 61, 58, 51, 48, 65, 49, 49, 41, 48, 52, 46,
               59, 46, 58, 43)

现在,我们可以使用特殊函数 tapply() 来计算每个州的样本平均收入

> incmeans <- tapply(incomes, statef, mean)

给出带有由级别标记的组件的均值向量

   act    nsw     nt    qld     sa    tas    vic     wa
44.500 57.333 55.500 53.600 55.000 60.500 56.000 52.250

函数 tapply() 用于将函数(此处为 mean())应用于第一个参数的每个组件组(此处为 incomes),该组由第二个组件的级别(此处为 statef15)定义,就好像它们是单独的向量结构一样。结果是一个结构,其长度与包含结果的因子的级别属性相同。读者应查阅帮助文档以了解更多详细信息。

进一步假设我们需要计算州收入均值的标准误差。为此,我们需要编写一个 R 函数来计算任何给定向量的标准误差。由于有一个内置函数 var() 来计算样本方差,因此此类函数是一个非常简单的单行函数,由赋值指定

> stdError <- function(x) sqrt(var(x)/length(x))

(稍后将在 编写自己的函数 中考虑编写函数。请注意,R 的内置函数 sd() 是不同的。) 在此赋值之后,通过以下方式计算标准误差

> incster <- tapply(incomes, statef, stdError)

然后计算的值为

> incster
act    nsw  nt    qld     sa tas   vic     wa
1.5 4.3102 4.5 4.1061 2.7386 0.5 5.244 2.6575

作为练习,您可能需要找出州平均收入的通常 95% 置信区间。为此,您可以再次使用 tapply()length() 函数来找出样本量,并使用 qt() 函数来找出适当 t 分布的百分位数。(您还可以调查 R 对 t 检验的工具。)

函数 tapply() 还可以用于处理通过多个类别对向量进行更复杂的索引。例如,我们可能希望按州和性别对税务会计师进行划分。然而,在这个简单的实例中(只有一个因子),可以将发生的事情视为如下。向量中的值被收集到与因子中的不同条目对应的组中。然后将该函数分别应用于这些组中的每一个。该值是一个函数结果向量,由因子的 levels 属性标记。

向量和标记因子的组合是一个有时称为交错数组的示例,因为子类大小可能不规则。当子类大小都相同时,索引可以隐式完成,并且效率更高,正如我们在下一节中看到的那样。


4.3 有序因子

因子的级别按字母顺序存储,或按显式指定给 factor 的顺序存储(如果它们是显式指定的)。

有时,级别将具有我们想要记录并希望我们的统计分析利用的自然顺序。 ordered() 函数创建这样的有序因子,但其他方面与 factor 相同。对于大多数目的,有序因子和无序因子之间的唯一区别在于,前者在打印时显示级别的顺序,但为它们在拟合线性模型中生成的对比是不同的。


5 数组和矩阵


5.1 数组

数组可以看作是多重标引的数据项集合,例如数字。R 提供了创建和处理数组的简单工具,尤其是矩阵这种特殊情况。

维度向量是非负整数向量。如果其长度为k,则数组为k维,例如矩阵是2维数组。维度从一索引到维度向量中给出的值。

只有当向量的dim属性为维度向量时,R 才能将该向量用作数组。例如,假设z是包含 1500 个元素的向量。赋值

> dim(z) <- c(3,5,100)

为其提供了dim属性,使其可以被视为3 x 5 x 100 数组。

其他函数(例如 matrix()array())可用于更简单、更直观的赋值,如我们在array() 函数中所看到的。

数据向量中的值按其在 FORTRAN 中出现的顺序给出数组中的值,即“列主序”,第一个下标移动最快,最后一个下标移动最慢。

例如,如果数组(例如 a)的维度向量为 c(3,4,2),则 a 中有 3 * 4 * 2 = 24 个条目,数据向量按 a[1,1,1], a[2,1,1], …, a[2,4,2], a[3,4,2] 的顺序保存这些条目。

数组可以是一维的:此类数组通常与向量以相同的方式处理(包括打印时),但例外情况可能会引起混淆。


5.2 数组索引。数组的子部分

数组的各个元素可以通过给出数组名称,后跟方括号中的下标(用逗号分隔)来引用。

更一般地说,可以通过给出 索引向量 序列来指定数组的子部分,以代替下标;但是,如果任何索引位置给出了一个空索引向量,则取该下标的全部范围

继续前面的示例,a[2,,] 是一个 4 * 2 数组,维度向量为 c(4,2),数据向量包含按以下顺序排列的值

c(a[2,1,1], a[2,2,1], a[2,3,1], a[2,4,1],
  a[2,1,2], a[2,2,2], a[2,3,2], a[2,4,2])

a[,,] 表示整个数组,这与完全省略下标并单独使用 a 相同。

对于任何数组(例如 Z),维度向量可以明确地引用为 dim(Z)(在赋值的任一侧)。

此外,如果数组名称仅给出 一个下标或索引向量,则仅使用数据向量的相应值;在这种情况下,维度向量将被忽略。但是,如果单个索引不是向量而是数组本身,则情况并非如此,我们接下来将对此进行讨论。


5.3 索引矩阵

除了在任何下标位置使用索引向量外,还可以将矩阵与单个 索引矩阵 一起使用,以便将数量向量分配给数组中元素的不规则集合,或将不规则集合作为向量提取出来。

矩阵示例让这个过程变得清晰。对于双重索引数组,可以给出一个由两列和任意行数组成的索引矩阵。索引矩阵中的条目是双重索引数组的行索引和列索引。例如,假设我们有一个 45 数组 X,我们希望执行以下操作

  • 将元素 X[1,3]X[2,2]X[3,1] 作为矢量结构提取出来,并且
  • 用零替换数组 X 中的这些条目。

在这种情况下,我们需要一个 32 下标数组,如下例所示。

> x <- array(1:20, dim=c(4,5))   # Generate a 4 by 5 array.
> x
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    5    9   13   17
[2,]    2    6   10   14   18
[3,]    3    7   11   15   19
[4,]    4    8   12   16   20
> i <- array(c(1:3,3:1), dim=c(3,2))
> i                             # i is a 3 by 2 index array.
     [,1] [,2]
[1,]    1    3
[2,]    2    2
[3,]    3    1
> x[i]                          # Extract those elements
[1] 9 6 3
> x[i] <- 0                     # Replace those elements by zeros.
> x
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    5    0   13   17
[2,]    2    0   10   14   18
[3,]    0    7   11   15   19
[4,]    4    8   12   16   20
>

索引矩阵中不允许出现负索引。允许出现 NA 和零值:包含零的行在索引矩阵中会被忽略,包含 NA 的行在结果中会产生一个 NA

作为一个不太简单的示例,假设我们希望为由因子 blocksb 个级别)和 varietiesv 个级别)定义的块设计生成一个(未简化的)设计矩阵。此外,假设实验中有 n 个小区。我们可以按以下方式进行

> Xb <- matrix(0, n, b)
> Xv <- matrix(0, n, v)
> ib <- cbind(1:n, blocks)
> iv <- cbind(1:n, varieties)
> Xb[ib] <- 1
> Xv[iv] <- 1
> X <- cbind(Xb, Xv)

要构造关联矩阵(假设为 N),我们可以使用

> N <- crossprod(Xb, Xv)

但是,生成此矩阵的一种更简单的直接方法是使用 table()

> N <- table(blocks, varieties)

索引矩阵必须是数值的:作为矩阵提供的任何其他形式的矩阵(例如逻辑矩阵或字符矩阵)都将被视为索引矢量。


5.4 array() 函数

除了给矢量结构一个 dim 属性之外,还可以通过 array 函数从矢量构造数组,该函数的形式为

> Z <- array(data_vector, dim_vector)

例如,如果矢量 h 包含 24 个或更少的数字,则命令

> Z <- array(h, dim=c(3,4,2))

将使用 hZ 中设置一个 342 数组。如果 h 的大小恰好为 24,则结果与

> Z <- h ; dim(Z) <- c(3,4,2)

相同。但是,如果 h 短于 24,则其值将从头开始循环使用以使其达到 24 的大小(参见 混合矢量和数组算术。循环使用规则),但 dim(h) <- c(3,4,2) 会发出关于长度不匹配的错误。作为一个极端的但常见的示例

> Z <- array(0, c(3,4,2))

使 Z 成为一个全零数组。

此时dim(Z)表示维度向量c(3,4,2)Z[1:24]表示数据向量,就像在h中一样,Z[]带空下标或Z不带下标表示整个数组作为数组。

数组可用于算术表达式,结果是通过对数据向量进行逐元素运算形成的数组。dim运算符的属性通常需要相同,这将成为结果的维度向量。因此,如果ABC都是类似的数组,则

> D <- 2*A*B + C + 1

使D成为类似的数组,其数据向量是给定逐元素运算的结果。但是,关于混合数组和向量计算的精确规则必须更仔细地考虑一下。


5.4.1 混合向量和数组算术。循环利用规则

影响向量和数组逐元素混合计算的精确规则有些古怪,在参考中很难找到。根据经验,我们发现以下内容是一个可靠的指南。

  • 从左到右扫描表达式。
  • 任何短向量运算符都通过循环利用其值进行扩展,直到它们与任何其他运算符的大小匹配。
  • 只要遇到短向量和数组,数组必须具有相同的dim属性或产生错误。
  • 任何比矩阵或数组运算符长的向量运算符都会产生错误。
  • 如果存在数组结构且没有错误或强制转换为向量,则结果是一个数组结构,其数组操作数具有公共 dim 属性。

5.5 两个数组的外积

数组上的一个重要操作是外积。如果 ab 是两个数值数组,它们的外积是一个数组,其维度向量是通过连接它们的两个维度向量(顺序很重要)获得的,其数据向量是通过形成 a 的数据向量的元素与 b 的数据向量的元素的所有可能乘积而获得的。外积由特殊运算符 %o% 形成:

> ab <- a %o% b

另一种方法是

> ab <- outer(a, b, "*")

乘法函数可以用两个变量的任意函数替换。例如,如果我们希望使用 R 向量 xy 分别定义的 xy 坐标的规则值网格对函数 f(x; y) = cos(y)/(1 + x^2) 进行求值,我们可以按如下方式进行

> f <- function(x, y) cos(y)/(1 + x^2)
> z <- outer(x, y, f)

特别是,两个普通向量的外积是一个双下标数组(即一个秩最多为 1 的矩阵)。请注意,外积运算符当然是非交换的。在编写自己的函数中将进一步考虑定义您自己的 R 函数。

一个示例:2 x 2 单数字矩阵的行列式

作为一个简单但可爱的示例,考虑行列式为 22 的矩阵 [a, b; c, d],其中每个条目都是范围 0, 1, ..., 9 中的非负整数,即数字。

问题是找到此形式的所有可能矩阵的行列式 ad - bc,并以 高密度 图的形式表示每个值出现的频率。这相当于找到行列式的概率分布,如果每个数字都是独立且均匀随机选择的。

一种巧妙的方法是使用 outer() 函数两次

> d <- outer(0:9, 0:9)
> fr <- table(outer(d, d, "-"))
> plot(fr, xlab="Determinant", ylab="Frequency")

请注意,此处 plot() 使用直方图之类的绘图方法,因为它“看到” fr"table" 类的。使用 for 循环解决此问题的“明显”方法将在 分组、循环和条件执行 中讨论,效率太低,不切实际。

大约 20 个此类矩阵中有一个是奇异矩阵,这也许令人惊讶。


5.6 数组的广义转置

函数 aperm(a, perm) 可用于排列数组 a。参数 perm 必须是整数 {1, ..., k} 的排列,其中 ka 中下标的数量。该函数的结果是一个与 a 大小相同的数组,但旧维度由 perm[j] 给出,成为新的第 j 维。考虑此操作的最简单方法是将其视为矩阵转置的概括。实际上,如果 A 是一个矩阵(即双下标数组),那么由下式给出的 B

> B <- aperm(A, c(2,1))

只是 A 的转置。对于此特殊情况,可以使用更简单的函数 t() ,因此我们可以使用 B <- t(A)


5.7 矩阵工具

如上所述,矩阵只是一个具有两个下标的数组。然而,它是一个非常重要的特例,需要单独讨论。R 包含许多仅可用于矩阵的运算符和函数。例如,如上所述,t(X) 是矩阵转置函数。函数 nrow(A)ncol(A) 分别给出矩阵 A 中的行数和列数。


5.7.1 矩阵乘法

运算符 %*% 用于矩阵乘法。 当然,n11n 矩阵可以用作 n 向量,如果在上下文中合适的话。相反,出现在矩阵乘法表达式中的向量会自动提升为行向量或列向量,无论哪种方式在乘法上是一致的,如果可能的话(尽管这并不总是明确可能的,正如我们稍后看到的)。

例如,如果 AB 是相同大小的方阵,那么

> A * B

是元素乘积矩阵,

> A %*% B

是矩阵乘积。如果 x 是一个向量,那么

> x %*% A %*% x

是一个二次型。16

函数 crossprod() 形成“交叉乘积”,这意味着 crossprod(X, y)t(X) %*% y 相同,但操作效率更高。如果省略 crossprod() 的第二个参数,则将其视为与第一个参数相同。

diag() 的含义取决于其参数。diag(v),其中 v 是一个向量,给出一个对角矩阵,其中向量的元素作为对角线条目。另一方面,diag(M),其中 M 是一个矩阵,给出了 M 的主对角线条目的向量。这与 MATLABdiag() 使用的约定相同。此外,有点令人困惑的是,如果 k 是一个单一的数值,那么 diag(k) 就是 kk 的单位矩阵!


5.7.2 线性方程和求逆

求解线性方程是矩阵乘法的逆运算。当在

> b <- A %*% x

仅提供 Ab 时,向量 x 是该线性方程组的解。在 R 中,

> solve(A,b)

求解该方程组,返回 x(可能会损失一些精度)。请注意,在线性代数中,形式上 x = A^{-1} %*% b,其中 A^{-1} 表示 A,可通过以下方式计算:

solve(A)

但很少需要。从数值上讲,计算 x <- solve(A) %*% b 而不是 solve(A,b) 既效率低下,又可能不稳定。

多元计算中使用的二次形式  x %*% A^{-1} %*% x   应通过类似于17 x %*% solve(A,x) 的方式计算,而不是计算 A 的逆。


5.7.3 特征值和特征向量

函数 eigen(Sm) 计算对称矩阵 Sm 的特征值和特征向量。此函数的结果是一个包含两个组件的列表,分别名为 valuesvectors。分配

> ev <- eigen(Sm)

将把此列表分配给 ev。然后,ev$valSm 的特征值向量,ev$vec 是相应特征向量的矩阵。如果我们只需要特征值,则可以使用分配

> evals <- eigen(Sm)$values

evals 现在保存特征值向量,第二个组件被丢弃。如果表达式

> eigen(Sm)

本身用作命令,则会打印这两个组件及其名称。对于大型矩阵,最好避免计算不需要的特征向量,方法是使用表达式

> evals <- eigen(Sm, only.values = TRUE)$values

5.7.4 奇异值分解和行列式

函数 svd(M) 采用任意矩阵参数 M,并计算 M 的奇异值分解。这包括一个正交列矩阵 U,它与 M 具有相同的列空间,一个正交列矩阵 V,其列空间是 M 的行空间,以及一个正项对角矩阵 D,使得 M = U %*% D %*% t(V)D 实际上作为对角元素的向量返回。 svd(M) 的结果实际上是一个包含三个组件的列表,分别命名为 duv,含义显而易见。

如果 M 实际上是方阵,那么很容易看出

> absdetM <- prod(svd(M)$d)

计算 M 的行列式的绝对值。如果需要使用各种矩阵经常进行此计算,则可以将其定义为 R 函数

> absdet <- function(M) prod(svd(M)$d)

之后,我们可以将 absdet() 作为另一个 R 函数使用。作为一个进一步的简单但可能很有用的示例,您可能需要考虑编写一个函数,比如 tr(),来计算方阵的迹。[提示:您不需要使用显式循环。请再次查看 diag() 函数。]

R 有一个内置函数 det 来计算行列式,包括符号,另一个函数 determinant 来给出符号和模数(可以选择对数刻度),


5.7.5 最小二乘拟合和 QR 分解

函数 lsfit() 返回一个列表,给出最小二乘拟合过程的结果。这样的赋值

> ans <- lsfit(X, y)

给出最小二乘拟合的结果,其中 y 是观测值向量,X 是设计矩阵。有关更多详细信息,请参阅帮助工具,以及用于回归诊断的后续函数 ls.diag()。请注意,自动包含总均值项,无需将其明确包含为 X 的一列。此外,请注意,您几乎总是更愿意使用 lm(.)(请参阅 线性模型)而不是 lsfit() 进行回归建模。

另一个密切相关的函数是 qr() 及其相关函数。考虑以下赋值

> Xplus <- qr(X)
> b <- qr.coef(Xplus, y)
> fit <- qr.fitted(Xplus, y)
> res <- qr.resid(Xplus, y)

这些计算 yfit 中对 X 范围的正交投影,在 res 中对正交补的投影以及在 b 中的投影系数向量,即 b 基本上是 MATLAB “反斜杠”运算符的结果。

不假设 X 具有满列秩。冗余将被发现并删除,因为它们被发现。

此替代方法是执行最小二乘计算的较旧、低级方法。虽然在某些情况下仍然有用,但现在通常会被统计模型功能所取代,如 R 中的统计模型 中所讨论的。


5.8 形成分区矩阵,cbind()rbind()

正如我们已经非正式地看到的那样,可以通过函数 cbind()rbind() 从其他向量和矩阵构建矩阵。大致上 cbind() 通过水平或按列方式将矩阵绑定在一起来形成矩阵,而 rbind() 则通过垂直或按行方式绑定在一起来形成矩阵。

在赋值中

> X <- cbind(arg_1, arg_2, arg_3, ...)

cbind() 的参数必须是任意长度的向量或具有相同列大小的矩阵,即相同数量的行。结果是一个矩阵,其中连接的参数 arg_1arg_2、… 形成列。

如果对 cbind() 的某些参数是向量,则它们可能比任何现有矩阵的列大小短,在这种情况下,它们将被循环扩展以匹配矩阵列大小(如果没有给出矩阵,则匹配最长向量的长度)。

函数 rbind() 对行执行相应的操作。在这种情况下,任何向量参数(可能循环扩展)当然都被视为行向量。

假设 X1X2 具有相同数量的行。为了通过列将它们组合到矩阵 X 中,连同 1 的初始列,我们可以使用

> X <- cbind(1, X1, X2)

rbind()cbind() 的结果始终具有矩阵状态。因此,cbind(x)rbind(x) 可能分别允许向量 x 被视为列或行矩阵的最简单方法。


5.9 连接函数 c() 与数组

值得注意的是,虽然 cbind()rbind() 是尊重 dim 属性的连接函数,但基本 c() 函数则不是,而是清除所有 dimdimnames 属性的数字对象。这偶尔会很有用。

将数组强制转换回简单向量对象的官方方法是使用 as.vector()

> vec <- as.vector(X)

然而,可以通过仅使用一个参数来使用 c() 来实现类似的结果,仅仅是为了这个副作用

> vec <- c(X)

两者之间有细微的差别,但最终它们之间的选择在很大程度上取决于风格(前者更可取)。


5.10 因子的频率表

回想一下,因子定义了分组划分。类似地,一对因子定义了一个双向交叉分类,依此类推。 函数 table() 允许从等长因子计算频率表。如果有 k 个因子参数,结果是一个 k 维频率数组。

例如,假设 statef 是一个因子,它为数据向量中的每个条目提供状态代码。赋值

> statefr <- table(statef)

statefr 中给出了样本中每个状态的频率表。频率按因子的 levels 属性排序并标记。这个简单的情况等同于以下情况,但更方便:

> statefr <- tapply(statef, statef, length)

进一步假设 incomef 是一个因子,它为数据向量中的每个条目提供了一个适当定义的“收入等级”,例如使用 cut() 函数

> factor(cut(incomes, breaks = 35+10*(0:7))) -> incomef

然后计算频率的双向表

> table(incomef,statef)
         statef
incomef   act nsw nt qld sa tas vic wa
  (35,45]   1   1  0   1  0   0   1  0
  (45,55]   1   1  1   1  2   0   1  3
  (55,65]   0   3  1   3  2   2   2  1
  (65,75]   0   1  0   0  0   0   1  0

立即扩展到更高阶的频率表。


6 列表和数据框


6.1 列表

R 中的列表是一个对象,它由一个有序的对象集合组成,称为其组件

组件没有特别的需要具有相同的模式或类型,例如,一个列表可以由一个数字向量、一个逻辑值、一个矩阵、一个复数向量、一个字符数组、一个函数等组成。下面是一个创建列表的简单示例

> Lst <- list(name="Fred", wife="Mary", no.children=3,
              child.ages=c(4,7,9))

组件始终编号,并且始终可以这样引用。因此,如果Lst是一个有四个组件的列表的名称,则可以单独引用它们为Lst[[1]]Lst[[2]]Lst[[3]]Lst[[4]]。此外,如果Lst[[4]]是一个向量下标数组,则Lst[[4]][1]是它的第一个条目。

如果Lst是一个列表,则函数length(Lst)给出它具有的(顶级)组件数。

列表的组件也可以命名,在这种情况下,组件可以通过在双方括号中用字符字符串代替数字来引用,或者更方便地通过给出以下形式的表达式来引用

> name$component_name

表示相同的事物。

这是一个非常有用的约定,因为它可以让你在忘记数字时更容易获得正确的组件。

因此,在上面给出的简单示例中

Lst$nameLst[[1]]相同,是字符串"Fred"

Lst$wifeLst[[2]]相同,是字符串"Mary"

Lst$child.ages[1]Lst[[4]][1]相同,是数字4

此外,还可以使用双重方括号中的列表组件名称,即 Lst[["name"]]Lst$name 相同。当要提取的组件名称存储在另一个变量中时,这特别有用,如

> x <- "name"; Lst[[x]]

区分 Lst[[1]]Lst[1] 非常重要。‘[[]]’ 是用于选择单个元素的操作符,而 ‘[]’ 是一个通用下标操作符。因此前者是 列表 Lst 中的第一个对象,如果它是一个命名列表,则 包含名称。后者是 列表 Lst 的一个子列表,仅包含第一个条目。如果它是一个命名列表,则名称将被转移到子列表中。

组件的名称可以缩写为唯一标识它们所需的最小字母数。因此 Lst$coefficients 可以最小指定为 Lst$coeLst$covariance 可以最小指定为 Lst$cov

名称向量实际上只是列表的一个属性,就像其他任何属性一样,可以作为这样的属性进行处理。当然,除了列表之外的其他结构也可以赋予一个 名称 属性。


6.2 构造和修改列表

可以通过函数 list() 从现有对象中形成新列表。形式为

> Lst <- list(name_1=object_1, ..., name_m=object_m)

使用 object_1、…、object_m 为组件设置列表 Lst,并根据参数名称(可以自由选择)为这些组件指定名称。如果省略这些名称,则组件仅编号。用于形成列表的组件在形成新列表时会复制,而不会影响原始组件。

列表与任何带下标的对象一样,可以通过指定附加组件进行扩展。例如

> Lst[5] <- list(matrix=Mat)

6.2.1 连接列表

当连接函数 c() 给出列表参数时,结果也是模式列表的对象,其组件是按顺序连接在一起的参数列表的组件。

> list.ABC <- c(list.A, list.B, list.C)

回想一下,当使用矢量对象作为参数时,连接函数同样将所有参数连接到单个矢量结构中。在这种情况下,所有其他属性(例如 dim 属性)都将被丢弃。


6.3 数据框

数据框是具有类 "data.frame" 的列表。对于可以制成数据框的列表有一些限制,即

  • 组件必须是矢量(数字、字符或逻辑)、因子、数字矩阵、列表或其他数据框。
  • 矩阵、列表和数据框分别向新数据框提供与它们具有相同数量的列、元素或变量的变量。
  • 作为数据框变量出现的矢量结构必须长度相同,矩阵结构必须行数相同

对于许多目的,数据框可以看作是列可能具有不同模式和属性的矩阵。它可以以矩阵形式显示,并使用矩阵索引约定提取其行和列。


6.3.1 创建数据框

满足数据框列(组件)限制的对象可以使用 data.frame 函数来形成一个数据框:

> accountants <- data.frame(home=statef, loot=incomes, shot=incomef)

组件符合数据框限制的列表可以使用 as.data.frame() 函数强制转换为数据框

从头开始构建数据框的最简单方法是使用 read.table() 函数从外部文件读取整个数据框。这将在 从文件读取数据 中进一步讨论。


6.3.2 attach()detach()

列表组件的 $ 符号,如 accountants$home,并不总是很方便。一个有用的功能是,以某种方式使列表或数据框的组件在不需要每次都明确引用列表名称的情况下,以其组件名称的形式暂时显示为变量。

attach() 函数以列表或数据框等“数据库”作为其参数。因此,假设 lentils 是一个具有三个变量 lentils$ulentils$vlentils$w 的数据框。attach

> attach(lentils)

将数据框置于搜索路径的第 2 位,并且如果在第 1 位没有变量 uvw,则 uvw 可作为数据框中的变量本身使用。此时,诸如

> u <- v+w

这样的赋值不会替换数据框的组件 u,而是使用搜索路径上第 1 位的工作区中的另一个变量 u 对其进行屏蔽。要对数据框本身进行永久更改,最简单的方法是再次使用 $ 符号

> lentils$u <- v+w

但是,组件 u 的新值在数据框分离并重新附加之前不可见。

要分离数据框,请使用函数

> detach()

更准确地说,此语句从搜索路径中分离当前位于第 2 个位置的实体。因此,在当前上下文中,变量 uvw 将不再可见,除非使用列表表示法,如 lentils$u 等。搜索路径中位置大于 2 的实体可以通过将它们的编号提供给 detach 来分离,但始终使用名称要安全得多,例如 detach(lentils)detach("lentils")

注意:在 R 中,列表和数据框只能附加在第 2 个位置或更高位置,并且附加的是原始对象的副本。你可以通过 assign 更改附加的值via,但原始列表或数据框保持不变。


6.3.3 使用数据框

一个有用的约定,它允许你在同一个工作区中舒适地处理许多不同的问题,那就是

  • 将任何明确定义且独立的问题的所有变量收集到一个数据框中,并使用适当的信息名称;
  • 在处理问题时,将适当的数据框附加在第 2 个位置,并将第 1 级的工作区用于操作量和临时变量;
  • 在离开问题之前,使用 $ 形式的赋值将你希望保留以供将来参考的任何变量添加到数据框中,然后 detach()
  • 最后,从工作区中删除所有不需要的变量,并尽可能保持工作区不包含任何剩余的临时变量。

通过这种方式,在同一目录中处理许多问题非常简单,例如,所有问题都具有名为 xyz 的变量。


6.3.4 附加任意列表

attach() 是一个通用函数,它不仅允许将目录和数据框附加到搜索路径,还允许附加其他类对象。特别是,任何模式为 "list" 的对象都可以以相同的方式附加

> attach(any.old.list)

任何已附加的内容都可以通过 detach 按位置编号或(最好)按名称分离。


6.3.5 管理搜索路径

函数 search 显示当前搜索路径,因此是一种非常有用的方式来跟踪已附加和分离的数据框和列表(以及包)。最初它给出

> search()
[1] ".GlobalEnv"   "Autoloads"    "package:base"

其中 .GlobalEnv 是工作区。18

附加 lentils 后,我们有

> search()
[1] ".GlobalEnv"   "lentils"      "Autoloads"    "package:base"
> ls(2)
[1] "u" "v" "w"

正如我们所见,ls(或 objects)可用于检查搜索路径上任何位置的内容。

最后,我们分离数据框并确认它已从搜索路径中删除。

> detach("lentils")
> search()
[1] ".GlobalEnv"   "Autoloads"    "package:base"

7 从文件中读取数据

大型数据对象通常会从外部文件读取值,而不是在 R 会话期间通过键盘输入。R 输入工具很简单,其要求相当严格,甚至相当不灵活。R 的设计者明确地认为,您将能够使用其他工具(例如文件编辑器或 Perl19)修改您的输入文件,以满足 R 的要求。通常,这非常简单。

如果变量主要保存在数据框中(我们强烈建议这样做),则可以使用 read.table() 函数直接读取整个数据框。还有一个更原始的输入函数 scan(),可以直接调用它。

有关将数据导入 R 以及导出数据的更多详细信息,请参阅R 数据导入/导出手册。


7.1 read.table() 函数

要直接读取整个数据框,外部文件通常具有特殊形式。

  • 文件的第一行应为数据框中每个变量的名称
  • 文件中的每一行都将第一个项目作为行标签和每个变量的值。

如果文件的第一行比第二行少一个项目,则假定此排列有效。因此,要作为数据框读取的文件的前几行可能如下所示。

Input file form with names and row labels:

     Price    Floor     Area   Rooms     Age  Cent.heat
01   52.00    111.0      830     5       6.2      no
02   54.75    128.0      710     5       7.5      no
03   57.50    101.0     1000     5       4.2      no
04   57.50    131.0      690     6       8.8      no
05   59.75     93.0      900     5       1.9     yes
...

默认情况下,数字项(行标签除外)将作为数字变量读取,而非数字变量(如示例中的 Cent.heat)将作为字符变量读取。如有必要,可以更改此设置。

然后可以使用函数 read.table() 直接读取数据框

> HousePrice <- read.table("houses.data")

通常,您希望直接省略包括行标签并使用默认标签。在这种情况下,文件可以省略行标签列,如下所示。

Input file form without row labels:

Price    Floor     Area   Rooms     Age  Cent.heat
52.00    111.0      830     5       6.2      no
54.75    128.0      710     5       7.5      no
57.50    101.0     1000     5       4.2      no
57.50    131.0      690     6       8.8      no
59.75     93.0      900     5       1.9     yes
...

然后可以读取数据框,如下所示

> HousePrice <- read.table("houses.data", header=TRUE)

其中,header=TRUE 选项指定第一行是标题行,因此,从文件形式中可以推断出没有给出明确的行标签。


7.2 scan() 函数

假设数据向量长度相等,并且要并行读取。此外,假设有三个向量,第一个模式为字符,其余两个模式为数字,并且文件为 input.dat。第一步是使用 scan() 将三个向量作为列表读入,如下所示

> inp <- scan("input.dat", list("",0,0))

第二个参数是一个虚拟列表结构,用于建立要读取的三个向量的模式。存储在 inp 中的结果是一个列表,其组件是读入的三个向量。要将数据项分隔为三个单独的向量,请使用类似这样的赋值

> label <- inp[[1]]; x <- inp[[2]]; y <- inp[[3]]

更方便的是,虚拟列表可以具有命名组件,在这种情况下,可以使用名称来访问读入的向量。例如

> inp <- scan("input.dat", list(id="", x=0, y=0))

如果您希望分别访问变量,则可以将它们重新分配给工作框架中的变量

> label <- inp$id; x <- inp$x; y <- inp$y

或者列表可以附加到搜索路径的第 2 个位置(请参阅附加任意列表)。

如果第二个参数是单个值而不是列表,则读入单个向量,其所有组件的模式必须与虚拟值相同。

> X <- matrix(scan("light.dat", 0), ncol=5, byrow=TRUE)

有更多精细的输入工具,这些工具在手册中进行了详细说明。


7.3 访问内置数据集

R 提供了大约 100 个数据集(在 datasets 包中),其他数据集可在包中获得(包括 R 提供的推荐包)。要查看当前可用的数据集列表,请使用

data()

R 提供的所有数据集都可直接按名称获取。但是,许多包仍使用已过时的惯例,其中 data 也用于将数据集加载到 R 中,例如

data(infert)

这仍可与标准包一起使用(如本示例中所示)。在大多数情况下,这将加载同名 R 对象。但是,在少数情况下,它会加载多个对象,因此请参阅对象的在线帮助以了解预期结果。

7.3.1 从其他 R 包加载数据

要访问特定包中的数据,请使用 package 参数,例如

data(package="rpart")
data(Puromycin, package="datasets")

如果已通过 library 附加包,则其数据集会自动包含在搜索中。

用户贡献的包可以是数据集的丰富来源。


7.4 编辑数据

当在数据框或矩阵上调用 edit 时,它会调出一个类似电子表格的单独环境用于编辑。这对于在读取数据集后进行小幅更改非常有用。命令

> xnew <- edit(xold)

将允许你编辑数据集 xold,并且完成后,更改后的对象将分配给 xnew。如果你想更改原始数据集 xold,最简单的方法是使用 fix(xold),它等效于 xold <- edit(xold)

使用

> xnew <- edit(data.frame())

通过电子表格界面输入新数据。


8 概率分布


8.1 R 作为一组统计表

R 的一个便利用途是提供一组全面的统计表。提供函数来评估累积分布函数 P(X <= x)、概率密度函数和分位数函数(给定q,最小的x,使得 P(X <= x) > q),并模拟分布。

分布R 名称其他参数
betabetashape1、shape2、ncp
binomialbinomsize、prob
Cauchycauchylocation、scale
chi-squaredchisqdf、ncp
exponentialexprate
Ffdf1、df2、ncp
gammagammashape、scale
geometricgeomprob
hypergeometrichyperm、n、k
log-normallnormmeanlog、sdlog
logisticlogislocation、scale
negative binomialnbinomsize、prob
normalnormmean、sd
Poissonpoislambda
signed ranksignrankn
Student’s ttdf、ncp
uniformunifmin、max
Weibullweibullshape、scale
Wilcoxonwilcoxm、n

在此给出的名称前加上“d”表示密度,“p”表示 CDF,“q”表示分位数函数,“r”表示模拟(random deviates)。第一个参数对于 dxxxx,对于 pxxxq,对于 qxxxp,对于 rxxxnrhyperrsignrankrwilcox 除外,对于它们是 nn)。并非在所有情况下都提供非中心参数 ncp:有关详细信息,请参见在线帮助。

所有 pxxxqxxx 函数都具有逻辑参数 lower.taillog.p,而 dxxx 函数具有 log。这允许通过以下方式获取累积(或“积分”)风险函数 H(t) = - log(1 - F(t)):

 - pxxx(t, ..., lower.tail = FALSE, log.p = TRUE)

或更准确的对数似然(通过 dxxx(..., log = TRUE)),直接。

此外,还有用于正态分布样本的学生化范围分布的函数 ptukeyqtukey,以及用于多项分布的函数 dmultinomrmultinom。其他分布可在贡献的软件包中获得,尤其是 SuppDists

以下是一些示例

> ## 2-tailed p-value for t distribution
> 2*pt(-2.43, df = 13)
> ## upper 1% point for an F(2, 7) distribution
> qf(0.01, 2, 7, lower.tail = FALSE)

请参阅 RNG 的在线帮助,了解如何在 R 中进行随机数生成。


8.2 检查数据集的分布

给定一组(单变量)数据,我们可以通过多种方式检查其分布。最简单的方法是检查数字。summaryfivenum 给出了两个略有不同的摘要 ,而 stem(“茎叶”图)显示了数字。

> attach(faithful)
> summary(eruptions)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.
  1.600   2.163   4.000   3.488   4.454   5.100
> fivenum(eruptions)
[1] 1.6000 2.1585 4.0000 4.4585 5.1000
> stem(eruptions)

  The decimal point is 1 digit(s) to the left of the |

  16 | 070355555588
  18 | 000022233333335577777777888822335777888
  20 | 00002223378800035778
  22 | 0002335578023578
  24 | 00228
  26 | 23
  28 | 080
  30 | 7
  32 | 2337
  34 | 250077
  36 | 0000823577
  38 | 2333335582225577
  40 | 0000003357788888002233555577778
  42 | 03335555778800233333555577778
  44 | 02222335557780000000023333357778888
  46 | 0000233357700000023578
  48 | 00000022335800333
  50 | 0370

茎叶图类似于直方图,R 有一个函数 hist 来绘制直方图。

> hist(eruptions)
## make the bins smaller, make a plot of density
> hist(eruptions, seq(1.6, 5.2, 0.2), prob=TRUE)
> lines(density(eruptions, bw=0.1))
> rug(eruptions) # show the actual data points

可以使用 density 绘制更精美的密度图,并且在此示例中,我们添加了 density 生成的线。带宽 bw 是通过反复试验选择的,因为默认值会产生过多的平滑(对于“有趣”的密度通常如此)。(可以使用更好的自动带宽选择方法,在此示例中,bw = "SJ" 给出了一个好的结果。)

images/hist

我们可以使用函数 ecdf 绘制经验累积分布函数。

> plot(ecdf(eruptions), do.points=FALSE, verticals=TRUE)

显然,此分布与任何标准分布都相差甚远。较右侧的模式怎么样,比如持续时间超过 3 分钟的喷发?让我们拟合一个正态分布并叠加拟合的 CDF。

> long <- eruptions[eruptions > 3]
> plot(ecdf(long), do.points=FALSE, verticals=TRUE)
> x <- seq(3, 5.4, 0.01)
> lines(x, pnorm(x, mean=mean(long), sd=sqrt(var(long))), lty=3)
images/ecdf

分位数-分位数(Q-Q)图可以帮助我们更仔细地检查这一点。

par(pty="s")       # arrange for a square figure region
qqnorm(long); qqline(long)

这显示了合理的拟合,但右尾比正态分布的预期要短。让我们将此与 t 分布的模拟数据进行比较

images/QQ
x <- rt(250, df = 5)
qqnorm(x); qqline(x)

如果它是随机样本,通常会显示比正态分布预期更长的尾部。我们可以通过以下方式针对生成分布绘制 Q-Q 图

qqplot(qt(ppoints(250), df = 5), x, xlab = "Q-Q plot for t dsn")
qqline(x)

最后,我们可能需要对正态性(或非正态性)进行更正式的检验。R 提供 Shapiro-Wilk 检验

> shapiro.test(long)

         Shapiro-Wilk normality test

data:  long
W = 0.9793, p-value = 0.01052

和 Kolmogorov-Smirnov 检验

> ks.test(long, "pnorm", mean = mean(long), sd = sqrt(var(long)))

         One-sample Kolmogorov-Smirnov test

data:  long
D = 0.0661, p-value = 0.4284
alternative hypothesis: two.sided

(请注意,由于我们从同一样本中估计了正态分布的参数,因此分布理论在此处无效。)


8.3 单样本和双样本检验

到目前为止,我们已经将单个样本与正态分布进行了比较。更常见的操作是比较两个样本的各个方面。请注意,在 R 中,所有“经典”检验(包括下面使用的检验)都在包 stats 中,该包通常已加载。

考虑 Rice (1995, p.490) 中关于冰的熔化潜热(cal/gm)的以下数据集

Method A: 79.98 80.04 80.02 80.04 80.03 80.03 80.04 79.97
          80.05 80.03 80.02 80.00 80.02
Method B: 80.02 79.94 79.98 79.97 79.97 80.03 79.95 79.97

箱形图提供了两个样本的简单图形比较。

A <- scan()
79.98 80.04 80.02 80.04 80.03 80.03 80.04 79.97
80.05 80.03 80.02 80.00 80.02

B <- scan()
80.02 79.94 79.98 79.97 79.97 80.03 79.95 79.97

boxplot(A, B)

这表明第一组倾向于给出比第二组更高的结果。

images/ice

要检验两个样本均值的相等性,我们可以使用 未配对 t 检验,方法是

> t.test(A, B)

         Welch Two Sample t-test

data:  A and B
t = 3.2499, df = 12.027, p-value = 0.00694
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 0.01385526 0.07018320
sample estimates:
mean of x mean of y
 80.02077  79.97875

如果假设正态分布,则确实表明存在显着差异。默认情况下,R 函数不假设两个样本中方差相等。我们可以使用 F 检验来检验方差是否相等,前提是两个样本来自正态总体。

> var.test(A, B)

         F test to compare two variances

data:  A and B
F = 0.5837, num df = 12, denom df =  7, p-value = 0.3938
alternative hypothesis: true ratio of variances is not equal to 1
95 percent confidence interval:
 0.1251097 2.1052687
sample estimates:
ratio of variances
         0.5837405

这表明没有显着差异的证据,因此我们可以使用假设方差相等的经典 t 检验。

> t.test(A, B, var.equal=TRUE)

         Two Sample t-test

data:  A and B
t = 3.4722, df = 19, p-value = 0.002551
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 0.01669058 0.06734788
sample estimates:
mean of x mean of y
 80.02077  79.97875

所有这些测试都假设两个样本的正态性。双样本 Wilcoxon(或 Mann-Whitney)检验仅假设零假设下的公共连续分布。

> wilcox.test(A, B)

         Wilcoxon rank sum test with continuity correction

data:  A and B
W = 89, p-value = 0.007497
alternative hypothesis: true location shift is not equal to 0

Warning message:
Cannot compute exact p-value with ties in: wilcox.test(A, B)

注意警告:每个样本中都有多个平局,这强烈表明这些数据来自离散分布(可能是由于舍入)。

有几种方法可以直观地比较两个样本。我们已经看到了一对箱线图。以下

> plot(ecdf(A), do.points=FALSE, verticals=TRUE, xlim=range(A, B))
> plot(ecdf(B), do.points=FALSE, verticals=TRUE, add=TRUE)

将显示两个经验 CDF,而 qqplot 将执行两个样本的 Q-Q 图。假设公共连续分布,Kolmogorov-Smirnov 检验是两个 ecdf 之间最大垂直距离

> ks.test(A, B)

         Two-sample Kolmogorov-Smirnov test

data:  A and B
D = 0.5962, p-value = 0.05919
alternative hypothesis: two-sided

Warning message:
cannot compute correct p-values with ties in: ks.test(A, B)

9 分组、循环和条件执行


9.1 分组表达式

R 是一种表达式语言,其唯一命令类型是返回结果的函数或表达式。即使是赋值也是一个表达式,其结果是赋值的值,并且可以在任何可以使用表达式的任何地方使用它;特别是,可以进行多个赋值。

命令可以分组在大括号中,{expr_1; ; expr_m},在这种情况下,组的值是组中最后一个表达式的结果评估。由于这样的组也是一个表达式,因此它可以(例如)本身包含在括号中并用作更大表达式的部分,依此类推。


9.2 控制语句


9.2.1 条件执行:if 语句

语言提供了一个形式为条件结构

> if (expr_1) expr_2 else expr_3

其中 expr_1 必须计算为单个逻辑值,然后整个表达式的结果显而易见。

“短路”运算符 &&|| 通常用作 if 语句中条件的一部分。虽然 &| 按元素应用于向量,但 &&|| 应用于长度为一的向量,并且仅在必要时计算其第二个参数。

有一个 if/else 结构的向量化版本,即 ifelse 函数。它的形式为 ifelse(condition, a, b),并返回一个与 condition 长度相同的向量,其中元素为 a[i](如果 condition[i] 为真),否则为 b[i](其中 ab 根据需要进行回收)。


9.2.2 重复执行:for 循环、repeatwhile

还有一个 for 循环结构,形式为

> for (name in expr_1) expr_2

其中 name 是循环变量。 expr_1 是一个向量表达式(通常是类似 1:20 的序列),而 expr_2 通常是一个分组表达式,其子表达式用虚拟 name 编写。 expr_2 会重复计算,因为 name 遍历 expr_1 的向量结果中的值。

例如,假设 ind 是类指示符的向量,我们希望生成 y 相对于 x 的各个类别的单独绘图。这里的一种可能性是使用 coplot(),20 它将生成与因子的每个级别相对应的绘图数组。另一种方法是将所有绘图放在一个显示中,如下所示

> xc <- split(x, ind)
> yc <- split(y, ind)
> for (i in 1:length(yc)) {
    plot(xc[[i]], yc[[i]])
    abline(lsfit(xc[[i]], yc[[i]]))
  }

(请注意函数 split(),它生成一个向量列表,该列表是通过根据因子指定的类别拆分一个较大的向量而获得的。这是一个有用的函数,主要用于与箱形图相关联。有关更多详细信息,请参阅 help 工具。)

警告:在 R 代码中,for() 循环的使用频率远低于编译语言。采用“整个对象”视图的代码在 R 中可能既更清晰又更快。

其他循环工具包括

> repeat expr

语句和

> while (condition) expr

语句。

break 语句可用于终止任何循环,可能是异常终止。这是终止 repeat 循环的唯一方法。

next 语句可用于中止一个特定循环并跳到“下一个”。

控制语句最常与函数结合使用,这些函数在 编写您自己的函数 中讨论,并且会出现更多示例。


10 编写您自己的函数

正如我们在沿途非正式地看到的那样,R 语言允许用户创建模式为函数的对象。这些是存储在特殊内部形式中的真正 R 函数,可以在进一步的表达式等中使用。在这个过程中,该语言在功能、便利性和优雅性方面获得了极大的提升,学习编写有用的函数是让您使用 R 变得舒适和高效的主要方法之一。

值得强调的是,R 系统中提供的大多数函数,例如 mean()var()postscript() 等,都是用 R 编写的,因此与用户编写的函数在实质上没有区别。

函数通过以下形式的赋值来定义

> name <- function(arg_1, arg_2, ...) expression

expression 是一个 R 表达式(通常是一个分组表达式),它使用参数 arg_i 来计算一个值。表达式的值是函数返回的值。

对函数的调用通常采用以下形式 name(expr_1, expr_2, …),并且可以在函数调用合法的地方出现。


10.1 简单示例

作为一个第一个示例,考虑一个函数来计算两个样本 t 统计量,显示“所有步骤”。当然,这是一个人工示例,因为还有其他更简单的方法来实现相同目的。

函数定义如下

> twosam <- function(y1, y2) {
    n1  <- length(y1); n2  <- length(y2)
    yb1 <- mean(y1);   yb2 <- mean(y2)
    s1  <- var(y1);    s2  <- var(y2)
    s <- ((n1-1)*s1 + (n2-1)*s2)/(n1+n2-2)
    tst <- (yb1 - yb2)/sqrt(s*(1/n1 + 1/n2))
    tst
  }

在此函数定义后,您可以使用如下调用执行两个样本 t 检验

> tstat <- twosam(data$male, data$female); tstat

作为一个第二个示例,考虑一个函数来直接模拟 MATLAB 反斜杠命令,该命令返回向量 y 在矩阵 X 的列空间上的正交投影的系数。(这通常称为回归系数的最小二乘估计。)通常使用 qr() 函数来完成此操作;但是,直接使用它有时有点棘手,并且值得拥有一个简单的函数(例如以下函数)来安全地使用它。

因此,给定一个 n1 向量 y 和一个 np 矩阵 X,则 X \ y 定义为 (X’X)^{-}X’y,其中 (X’X)^{-} 是 X'X 的广义逆。

> bslash <- function(X, y) {
  X <- qr(X)
  qr.coef(X, y)
}

创建此对象后,可以在语句中使用它,例如

> regcoeff <- bslash(Xmat, yvar)

等等。

经典 R 函数 lsfit() 可以很好地完成此工作,还有更多21。它反过来以略微违反直觉的方式使用函数 qr()qr.coef() 来完成计算的这一部分。因此,如果经常使用,那么将这一部分单独隔离在一个易于使用的函数中可能具有一定价值。如果是这样,我们可能希望将其设为矩阵二元运算符,以便更方便地使用。


10.2 定义新的二元运算符

如果我们给 bslash() 函数一个不同的名称,即以下形式之一

%anything%

它可以用作表达式中的二元运算符,而不是函数形式。例如,假设我们选择 ! 作为内部字符。然后,函数定义将以以下形式开始

> "%!%" <- function(X, y) { ... }

(注意引号的使用方式。)然后,该函数可以用作 X %!% y。(反斜杠符号本身不是一个方便的选择,因为它在此上下文中会出现特殊问题。)

矩阵乘法运算符 %*% 和外积矩阵运算符 %o% 是以这种方式定义的其他二元运算符示例。


10.3 命名参数和默认值

正如 生成常规序列 中首先指出的,如果以“name=object”的形式给出对被调用函数的参数,则可以按任何顺序给出它们。此外,参数序列可以从未命名的位置形式开始,并在位置参数之后指定命名参数。

因此,如果有一个由以下内容定义的函数 fun1

> fun1 <- function(data, data.frame, graph, limit) {
    [function body omitted]
  }

那么可以以多种方式调用该函数,例如

> ans <- fun1(d, df, TRUE, 20)
> ans <- fun1(d, df, graph=TRUE, limit=20)
> ans <- fun1(data=d, limit=20, graph=TRUE, data.frame=df)

都是等效的。

在很多情况下,参数可以赋予通常合适的默认值,在这种情况下,当默认值合适时,可以完全从调用中省略它们。例如,如果将 fun1 定义为

> fun1 <- function(data, data.frame, graph=TRUE, limit=20) { ... }

可以将其调用为

> ans <- fun1(d, df)

这现在等效于上述三个情况,或者作为

> ans <- fun1(d, df, limit=10)

这会更改其中一个默认值。

需要注意的是,默认值可以是任意表达式,甚至涉及同一函数的其他参数;它们不必像我们这里简单的示例那样被限制为常量。


10.4 ‘’ 参数

另一个常见要求是允许一个函数将参数设置传递给另一个函数。例如,许多图形函数使用函数 par(),而 plot() 等函数允许用户将图形参数传递给 par() 以控制图形输出。(有关 par() 函数的更多详细信息,请参见永久更改:par() 函数。)这可以通过在函数中包含一个额外的参数(从字面上讲为 ‘’)来完成,然后可以传递该参数。下面给出了一个概要示例。

fun1 <- function(data, data.frame, graph=TRUE, limit=20, ...) {
  [omitted statements]
  if (graph)
    par(pch="*", ...)
  [more omissions]
}

不太常见的是,函数需要引用 ‘’ 的组件。表达式 list(...) 会计算所有此类参数,并将它们以命名列表的形式返回,而 ..1..2 等会一次计算一个参数,其中 ‘..n’ 返回第 n 个未匹配的参数。


10.5 函数内的赋值

请注意,在函数中执行的任何普通赋值都是局部且临时的,在退出函数后将丢失。因此,赋值 X <- qr(X) 不会影响调用程序中参数的值。

要完全理解控制 R 赋值范围的规则,读者需要熟悉评估框架的概念。这是一个有点高级但并不难的主题,这里不再赘述。

如果在函数中打算进行全局且永久的赋值,则可以使用“超赋值”运算符 <<- 或函数 assign()。有关详细信息,请参阅 help 文档。


10.6 更高级的示例


10.6.1 块设计中的效率因子

作为一个更完整(尽管有点平淡)的函数示例,考虑查找块设计的效率因子。(此问题的某些方面已在 索引矩阵 中讨论。)

块设计由两个因子定义,例如 blocksb 级)和 varietiesv 级)。如果 RK 分别是 vvbb重复块大小矩阵,而 Nbv 的关联矩阵,则效率因子定义为矩阵 E 的特征值,其中 E = I_v - R^{-1/2}N’K^{-1}NR^{-1/2} = I_v - A’A,其中 A = K^{-1/2}NR^{-1/2}。下面给出了编写函数的一种方法。

> bdeff <- function(blocks, varieties) {
    blocks <- as.factor(blocks)             # minor safety move
    b <- length(levels(blocks))
    varieties <- as.factor(varieties)       # minor safety move
    v <- length(levels(varieties))
    K <- as.vector(table(blocks))           # remove dim attr
    R <- as.vector(table(varieties))        # remove dim attr
    N <- table(blocks, varieties)
    A <- 1/sqrt(K) * N * rep(1/sqrt(R), rep(b, v))
    sv <- svd(A)
    list(eff=1 - sv$d^2, blockcv=sv$u, varietycv=sv$v)
}

在此情况下,使用奇异值分解比使用特征值例程在数值上稍微好一些。

该函数的结果是一个列表,不仅提供效率因子作为第一个分量,还提供块和品种典型对比,因为有时这些会提供额外的有用定性信息。


10.6.2 删除打印数组中的所有名称

对于大型矩阵或数组的打印目的,通常很有用以紧凑的块形式打印它们,而无需数组名称或数字。删除dimnames属性不会产生此效果,而是必须为数组指定一个由空字符串组成的dimnames属性。例如,要打印一个矩阵X

> temp <- X
> dimnames(temp) <- list(rep("", nrow(X)), rep("", ncol(X)))
> temp; rm(temp)

使用函数no.dimnames()可以更方便地完成此操作,如下所示,作为“包装”以实现相同的结果。它还说明了一些有效且有用的用户函数可以非常短。

no.dimnames <- function(a) {
  ## Remove all dimension names from an array for compact printing.
  d <- list()
  l <- 0
  for(i in dim(a)) {
    d[[l <- l + 1]] <- rep("", i)
  }
  dimnames(a) <- d
  a
}

定义此函数后,可以使用以下方法以紧凑格式打印数组

> no.dimnames(X)

这对于大型整数数组特别有用,其中模式是真正的兴趣,而不是值。


10.6.3 递归数值积分

函数可以是递归的,并且可以在自身内定义函数。但是,请注意,此类函数或变量不会像在搜索路径上一样被更高评估框架中的调用函数继承。

下面的示例展示了一种执行一维数值积分的简单方法。被积函数在范围的端点和中间进行评估。如果单面板梯形规则答案足够接近双面板,则后者将作为值返回。否则,将对每个面板递归应用相同的过程。结果是一个自适应积分过程,它将函数评估集中在被积函数最远离线性的区域。但是,开销很大,并且仅当被积函数既平滑又非常难以评估时,该函数才与其他算法具有竞争力。

该示例也部分作为 R 编程中的一个小难题给出。

area <- function(f, a, b, eps = 1.0e-06, lim = 10) {
  fun1 <- function(f, a, b, fa, fb, a0, eps, lim, fun) {
    ## function ‘fun1’ is only visible inside ‘area’
    d <- (a + b)/2
    h <- (b - a)/4
    fd <- f(d)
    a1 <- h * (fa + fd)
    a2 <- h * (fd + fb)
    if(abs(a0 - a1 - a2) < eps || lim == 0)
      return(a1 + a2)
    else {
      return(fun(f, a, d, fa, fd, a1, eps, lim - 1, fun) +
             fun(f, d, b, fd, fb, a2, eps, lim - 1, fun))
    }
  }
  fa <- f(a)
  fb <- f(b)
  a0 <- ((fa + fb) * (b - a))/2
  fun1(f, a, b, fa, fb, a0, eps, lim, fun1)
}

10.7 作用域

本节中的讨论比本文档其他部分的技术性更强。但是,它详细说明了 S-PLUS 和 R 之间的一个主要区别。

函数体中出现的符号可分为三类;形式参数、局部变量和自由变量。函数的形式参数是出现在函数参数列表中的那些。它们的值由将实际函数参数绑定到形式参数的过程确定。局部变量是其值由函数体中表达式的求值确定的那些。不是形式参数或局部变量的变量称为自由变量。如果自由变量被赋值,则它们将变为局部变量。考虑以下函数定义。

f <- function(x) {
  y <- 2*x
  print(x)
  print(y)
  print(z)
}

在此函数中,x 是形式参数,y 是局部变量,z 是自由变量。

在 R 中,首先在创建函数的环境中查找来解析自由变量绑定。这称为词法作用域。首先,我们定义一个名为 cube 的函数。

cube <- function(n) {
  sq <- function() n*n
  n*sq()
}

函数 sq 中的变量 n 不是该函数的参数。因此,它是一个自由变量,并且必须使用作用域规则来确定与它关联的值。在静态作用域 (S-PLUS) 下,该值与名为 n 的全局变量相关联。在词法作用域 (R) 下,它是函数 cube 的参数,因为这是定义函数 sq 时变量 n 的活动绑定。在 R 中求值和在 S-PLUS 中求值之间的区别在于,S-PLUS 查找名为 n 的全局变量,而 R 首先在调用 cube 时创建的环境中查找名为 n 的变量。

## first evaluation in S
S> cube(2)
Error in sq(): Object "n" not found
Dumped
S> n <- 3
S> cube(2)
[1] 18
## then the same function evaluated in R
R> cube(2)
[1] 8

词法作用域还可以用于为函数赋予可变状态。在以下示例中,我们将展示如何使用 R 模拟银行账户。一个有效的银行账户需要有余额或总额、一个用于取款的函数、一个用于存款的函数以及一个用于说明当前余额的函数。我们通过在 account 中创建这三个函数,然后返回包含它们的列表来实现这一点。当调用 account 时,它会采用一个数字参数 total,并返回一个包含这三个函数的列表。由于这些函数是在包含 total 的环境中定义的,因此它们将能够访问其值。

特殊赋值运算符 <<- 用于更改与 total 关联的值。此运算符会在封闭环境中向后查找包含符号 total 的环境,并且当它找到这样的环境时,它会用右侧的值替换该环境中的值。如果在未找到符号 total 的情况下到达全局或顶级环境,那么将创建该变量并将其分配到那里。对于大多数用户,<<- 会创建一个全局变量,并将右侧的值分配给它22。只有当在作为另一个函数的值返回的函数中使用了 <<- 时,才会发生此处描述的特殊行为。

open.account <- function(total) {
  list(
    deposit = function(amount) {
      if(amount <= 0)
        stop("Deposits must be positive!\n")
      total <<- total + amount
      cat(amount, "deposited.  Your balance is", total, "\n\n")
    },
    withdraw = function(amount) {
      if(amount > total)
        stop("You don't have that much money!\n")
      total <<- total - amount
      cat(amount, "withdrawn.  Your balance is", total, "\n\n")
    },
    balance = function() {
      cat("Your balance is", total, "\n\n")
    }
  )
}

ross <- open.account(100)
robert <- open.account(200)

ross$withdraw(30)
ross$balance()
robert$balance()

ross$deposit(50)
ross$balance()
ross$withdraw(500)

10.8 自定义环境

用户可以通过多种不同的方式自定义其环境。有一个站点初始化文件,并且每个目录都可以有自己的特殊初始化文件。最后,可以使用特殊函数 .First.Last

网站初始化文件的位置取自 R_PROFILE 环境变量的值。如果该变量未设置,则使用 R 主目录 etc 中的文件 Rprofile.site。此文件应包含每次在您的系统下启动 R 时要执行的命令。第二个个人配置文件名为 .Rprofile23 可以放置在任何目录中。如果在该目录中调用 R,则会引用该文件。此文件让各个用户可以控制其工作区,并允许在不同的工作目录中使用不同的启动过程。如果在启动目录中找不到 .Rprofile 文件,则 R 会在用户的个人目录中查找 .Rprofile 文件并使用该文件(如果存在)。如果设置了环境变量 R_PROFILE_USER,则会使用它指向的文件,而不是 .Rprofile 文件。

在两个配置文件或 .RData 映像中命名的任何函数 .First() 都有特殊状态。它会在 R 会话开始时自动执行,可用于初始化环境。例如,以下示例中的定义将提示符更改为 $,并设置会话其余部分可以视为理所当然的其他各种有用内容。

因此,执行文件的顺序为 Rprofile.site、用户配置文件、.RData,然后是 .First()。后面文件中的定义将屏蔽前面文件中的定义。

> .First <- function() {
  options(prompt="$ ", continue="+\t")  # $ is the prompt
  options(digits=5, length=999)         # custom numbers and printout
  x11()                                 # for graphics
  par(pch = "+")                        # plotting character
  source(file.path(Sys.getenv("HOME"), "R", "mystuff.R"))
                                        # my personal functions
  library(MASS)                         # attach a package
}

类似地,如果定义了函数 .Last(),则通常会在会话的最后执行该函数。下面给出了一个示例。

> .Last <- function() {
  graphics.off()                        # a small safety measure.
  cat(paste(date(),"\nAdios\n"))        # Is it time for lunch?
}

10.9 类、泛型函数和面向对象

对象的类决定了它将如何被所谓的泛型函数处理。换句话说,泛型函数对其参数执行特定于参数本身类的任务或操作。如果参数没有任何 class 属性,或者没有针对该泛型函数专门提供的类,则始终提供默认操作

一个示例可以更清楚地说明。类机制为用户提供了设计和编写特殊用途泛型函数的功能。其他泛型函数包括用于以图形方式显示对象的 plot()、用于汇总各种类型分析的 summary(),以及用于比较统计模型的 anova()

可以以特定方式处理类的泛型函数的数量可能很大。例如,可以适应类 "data.frame" 对象的函数包括

[     [[<-    any    as.matrix
[<-   mean    plot   summary

可以使用 methods() 函数获取当前完整列表

> methods(class="data.frame")

相反,泛型函数可以处理的类数量也可以非常大。例如,plot() 函数具有默认方法和针对 "data.frame""density""factor" 等类的对象的变体。可以通过使用 methods() 函数再次获取完整列表

> methods(plot)

对于许多泛型函数,函数主体非常短,例如

> coef
function (object, ...)
UseMethod("coef")

UseMethod 的存在表示这是一个泛型函数。要查看可用的方法,我们可以使用 methods()

> methods(coef)
[1] coef.aov*         coef.Arima*       coef.default*     coef.listof*
[5] coef.nls*         coef.summary.nls*

   Non-visible functions are asterisked

在此示例中,有六种方法,通过键入其名称无法看到任何一种方法。我们可以通过以下任一种方法来读取它们

> getAnywhere("coef.aov")
A single object matching ‘coef.aov’ was found
It was found in the following places
  registered S3 method for coef from namespace stats
  namespace:stats
with value

function (object, ...)
{
    z <- object$coef
    z[!is.na(z)]
}

> getS3method("coef", "aov")
function (object, ...)
{
    z <- object$coef
    z[!is.na(z)]
}

泛型 gen 将调用名为 gen.cl 的函数以获取类 cl,因此不要以这种样式命名函数,除非它们打算成为方法。

建议读者参阅 R 语言定义 以更全面地讨论此机制。


11 R 中的统计模型

本节假定读者对统计方法有一定程度的熟悉,特别是对回归分析和方差分析。稍后,我们做出了一些更大胆的假设,即对广义线性模型和非线性回归有所了解。

对统计模型进行拟合的要求定义得足够好,从而可以构建适用于广泛问题的通用工具。

R 提供了一套相互关联的工具,使得拟合统计模型非常简单。正如我们在引言中提到的,基本输出是最小的,需要通过调用提取器函数来询问详细信息。


11.1 定义统计模型;公式

统计模型的模板是具有独立同方差误差的线性回归模型

y_i = sum_{j=0}^p beta_j x_{ij} + e_i,     i = 1, ..., n,

其中 e_i 是 NID(0, sigma^2)。用矩阵形式表示如下

y = X  beta + e

其中 y 是响应向量,X模型矩阵设计矩阵,具有列 x_0, x_1, ..., x_p,即确定变量。通常 x_0 将是一列定义 截距 项的 1。

示例

在给出正式规范之前,一些示例可能有助于设置图片。

假设 yxx0x1x2 等是数值变量,X 是一个矩阵,ABC 等是因子。下面左侧的公式指定了右侧描述的统计模型。

y ~ x
y ~ 1 + x

这两个公式都隐含了 yx 的相同简单线性回归模型。第一个具有隐式截距项,第二个具有显式截距项。

y ~ 0 + x
y ~ -1 + x
y ~ x - 1

yx 的简单线性回归通过原点(即没有截距项)。

log(y) ~ x1 + x2

对转换变量 log(y) 的多元回归,对 x1x2(具有隐式截距项)。

y ~ poly(x,2)
y ~ 1 + x + I(x^2)

yx 的 2 次多项式回归。第一种形式使用正交多项式,第二种形式使用显式幂作为基础。

y ~ X + poly(x,2)

多元回归 y,其模型矩阵由矩阵 X 以及 x 中的 2 次多项式项组成。

y ~ A

A 确定的类别的 y 的单分类方差分析模型。

y ~ A + x

A 确定的类别的 y 的单分类协方差分析模型,其中协变量为 x

y ~ A*B
y ~ A + B + A:B
y ~ B %in% A
y ~ A/B

yAB 上的二因子非加性模型。前两个指定相同的交叉分类,后两个指定相同的嵌套分类。从抽象意义上讲,所有四个都指定相同的模型子空间。

y ~ (A + B + C)^2
y ~ A*B*C - A:B:C

三因子实验,但模型仅包含主效应和二因子交互作用。这两个公式指定相同的模型。

y ~ A * x
y ~ A/x
y ~ A/(1 + x) - 1

yA 的级别内在 x 上的独立简单线性回归模型,具有不同的编码。最后一种形式生成与 A 中的级别一样多的不同截距和斜率的明确估计值。

y ~ A*B + Error(C)

一个具有两个处理因子(AB)的实验,以及由因子 C 确定的误差层。例如,一个裂区实验,其中全区(因此还有子区)由因子 C 确定。

运算符 ~ 用于在 R 中定义一个 模型公式。对于普通线性模型,形式为

response ~ op_1 term_1 op_2 term_2 op_3 term_3 ...

其中

响应

是一个向量或矩阵(或求值为向量或矩阵的表达式),定义响应变量。

op_i

是一个运算符,即 +-,表示在模型中包含或排除一个项(第一个是可选的)。

term_i

要么是

  • 一个向量或矩阵表达式,或 1
  • 一个因子,或
  • 一个 公式表达式,其中因子、向量或矩阵通过 公式运算符 连接。

在所有情况下,每个项都定义了一组要添加到模型矩阵或从中删除的列。一个 1 代表一个截距列,并且默认情况下包含在模型矩阵中,除非明确删除。

这些 公式运算符 的效果类似于 Glim 和 Genstat 等程序使用的 Wilkinson 和 Rogers 符号。一个不可避免的更改是运算符 ‘.’ 变为 ‘:’,因为句点是 R 中有效的名称字符。

符号总结如下(基于 Chambers & Hastie,1992 年,第 29 页)

Y ~ M

Y 被建模为 M

M_1 + M_2

包含 M_1M_2

M_1 - M_2

包含 M_1,省略 M_2 的项。

M_1 : M_2

M_1M_2 的张量积。如果两个项都是因子,则“子类”因子。

M_1 %in% M_2

类似于 M_1:M_2,但编码不同。

M_1 * M_2

M_1 + M_2 + M_1:M_2.

M_1 / M_2

M_1 + M_2 %in% M_1.

M^n

M 中的所有项以及与 n 阶“交互”项

I(M)

隔离 M。在 M 内部,所有运算符都有其常规算术含义,并且该项出现在模型矩阵中。

请注意,在通常括起函数参数的括号内,所有运算符都有其常规算术含义。函数 I() 是一个恒等函数,用于允许使用算术运算符定义模型公式中的项。

特别注意,模型公式指定模型矩阵的列,参数的指定是隐式的。在其他情况下并非如此,例如在指定非线性模型时。


11.1.1 对比

我们需要至少了解一些有关模型公式如何指定模型矩阵列的想法。如果我们有连续变量,这很容易,因为每个变量提供模型矩阵的一列(如果模型中包含截距,则截距将提供一列)。

关于k级因子A怎么样?对于无序因子和有序因子,答案不同。对于无序因子,为因子的第二、…、k级指示符生成了k - 1列。(因此,隐式参数化是将每个级别的响应与第一级的响应进行对比。)对于有序因子,k - 1列是1, ..., k上的正交多项式,省略了常数项。

尽管答案已经很复杂,但这并不是全部。首先,如果在包含因子项的模型中省略截距,则第一个此类项将编码为k列,给出所有级别的指示符。其次,contrastsoptions设置可以更改整个行为。R 中的默认设置是

options(contrasts = c("contr.treatment", "contr.poly"))

提及此原因的主要原因是 R 和 S 对无序因子有不同的默认值,S 使用 Helmert 对比。因此,如果您需要将您的结果与使用S-PLUS的教科书或论文的结果进行比较,您将需要设置

options(contrasts = c("contr.helmert", "contr.poly"))

这是一个故意的差异,因为处理对比(R 的默认值)被认为更容易让新手理解。

我们还没有完成,因为可以使用函数contrastsC为模型中的每个项设置要使用的对比方案。

我们尚未考虑交互项:这些项生成为其组成项引入的列的乘积。

尽管细节很复杂,但 R 中的模型公式通常会生成专家统计学家期望的模型,前提是保留边际性。例如,拟合具有交互作用但没有相应主效应的模型通常会导致令人惊讶的结果,并且仅适用于专家。


11.2 线性模型

用于拟合普通多元模型的基本函数是lm(),并且调用的精简版本如下:

> fitted.model <- lm(formula, data = data.frame)

例如

> fm2 <- lm(y ~ x1 + x2, data = production)

将拟合yx1x2上的多元回归模型(具有隐式截距项)。

重要的(但在技术上是可选的)参数data = production指定用于构建模型的任何变量都应首先来自production数据框无论数据框production是否已附加到搜索路径上,情况都是如此


11.3 用于提取模型信息的通用函数

lm() 的值是一个拟合模型对象;从技术上讲,它是 "lm" 类的结果列表。然后,可以使用面向 "lm" 类的对象的通用函数来显示、提取、绘制拟合模型的信息,依此类推。这些函数包括

add1    deviance   formula      predict  step
alias   drop1      kappa        print    summary
anova   effects    labels       proj     vcov
coef    family     plot         residuals

下面简要介绍最常用的函数。

anova(object_1, object_2)

将子模型与外部模型进行比较,并生成方差分析表。

coef(object)

提取回归系数(矩阵)。

长格式:coefficients(object)

deviance(object)

残差平方和,如果适用,则加权。

formula(object)

提取模型公式。

plot(object)

生成四个图,显示残差、拟合值和一些诊断信息。

predict(object, newdata=data.frame)

提供的数据框必须具有与原始数据框具有相同标签的变量。该值是与 data.frame 中的决定变量值对应的预测值向量或矩阵。

print(object)

打印对象的简洁版本。最常隐式使用。

residuals(object)

提取(残差的)矩阵,并根据需要加权。

简短格式:resid(object)

step(object)

通过添加或删除项并保留层次结构来选择合适的模型。返回在逐步搜索中发现的 AIC(赤池信息准则)值最小的模型。

summary(object)

打印回归分析结果的综合摘要。

vcov(对象)

返回已拟合模型对象的各个主要参数的协方差矩阵。


11.4 方差分析和模型比较

模型拟合函数 aov(公式, data=数据框) 以非常类似于函数 lm() 的方式在最简单的层面上操作,并且 用于提取模型信息的通用函数 表格中列出的大多数通用函数都适用。

需要注意的是,除了 aov() 之外,还可以分析具有多个误差层级的模型,例如分割小区实验或具有块间信息恢复的平衡不完全区组设计。模型公式

response ~ mean.formula + Error(strata.formula)

指定了一个多层级实验,其误差层级由 层级公式 定义。在最简单的情况下,层级公式 只是一个因子,当它定义一个两层级实验时,即在因子的层级之间和内部。

例如,对于所有决定变量因子,类似于

> fm <- aov(yield ~ v + n*p*k + Error(farms/blocks), data=farm.data)

中的模型公式通常用于描述具有均值模型 v + n*p*k 和三个误差层级的实验,即“农场间”、“农场内,区组间”和“区组内”。


11.4.1 ANOVA 表格

还要注意,方差分析表(或表格)是针对一系列拟合模型的。显示的平方和是残差平方和的减少,这是由于在序列中该项在模型中该位置包含而导致的。因此,只有对于正交实验,包含顺序才会无关紧要。

对于多层实验,该过程首先是将响应投影到误差层,同样是按顺序,并将均值模型拟合到每个投影。有关更多详细信息,请参阅 Chambers & Hastie (1992)。

与默认完整 ANOVA 表相比,一种更灵活的替代方法是使用 anova() 函数直接比较两个或更多模型。

> anova(fitted.model.1, fitted.model.2, ...)

然后,显示一个 ANOVA 表,显示按顺序拟合时拟合模型之间的差异。当然,正在比较的拟合模型通常是分层序列。这并不会提供与默认不同的信息,而是使其更容易理解和控制。


11.5 更新拟合模型

update() 函数在很大程度上是一个便利函数,它允许拟合一个与之前拟合的模型不同的模型,通常只是增加或删除一些项。它的形式是

> new.model <- update(old.model, new.formula)

new.formula 中,由一个句点组成的特殊名称“.”, 只能用于表示“旧模型公式的对应部分”。例如,

> fm05 <- lm(y ~ x1 + x2 + x3 + x4 + x5, data = production)
> fm6  <- update(fm05, . ~ . + x6)
> smf6 <- update(fm6, sqrt(.) ~ .)

将拟合一个五变量多元回归,其中变量(可能)来自数据框 production,拟合一个包含第六个回归变量的附加模型,并拟合一个响应应用了平方根变换的模型的变体。

特别注意,如果在对模型拟合函数的原始调用中指定了 data= 参数,则此信息将通过拟合模型对象传递给 update() 及其关联函数。

名称“.”还可以在其他上下文中使用,但含义略有不同。例如

> fmfull <- lm(y ~ . , data = production)

将拟合一个响应为 y 和回归变量为数据框 production 中所有其他变量的模型。

用于探索模型增量序列的其他函数是 add1()drop1()step() 这些名称很好地提示了它们的目的,但有关完整详细信息,请参阅在线帮助。


11.6 广义线性模型

广义线性建模是线性模型的开发,它以简洁直接的方式适应非正态响应分布和线性变换。广义线性模型可以通过以下一系列假设来描述

  • 存在感兴趣的响应 y 和刺激变量 x_1、x_2、…,其值影响响应的分布。
  • 刺激变量通过 一个线性函数(仅此一个)影响 y 的分布。此线性函数称为 线性预测器,通常写为
    eta = beta_1 x_1 + beta_2 x_2 + ... + beta_p x_p,
    

    因此,当且仅当 beta_i 为零时,x_i 对 y 的分布没有影响。

  • y 的分布形式为
    f_Y(y; mu, phi)
      = exp((A/phi) * (y lambda(mu) - gamma(lambda(mu))) + tau(y, phi))
    

    其中 phi 是 尺度参数(可能已知),并且对于所有观测值都是常数,A 表示先验权重,假设已知但可能随观测值而变化,并且 $\mu$ 是 y 的均值。因此,假设 y 的分布由其均值和可能的尺度参数决定。

  • 均值 mu 是线性预测器的平滑可逆函数
    mu = m(eta),    eta = m^{-1}(mu) = ell(mu)
    

    并且此逆函数 ell() 称为 链接函数

这些假设足够宽松,可以涵盖统计实践中有用的广泛模型,但又足够严格,可以开发出一种统一的估计和推理方法,至少是近似的。读者可以参阅任何当前关于该主题的参考著作以获取完整详细信息,例如 McCullagh & Nelder (1989) 或 Dobson (1990)。


11.6.1 族

R 中提供的工具处理的广义线性模型类别包括 gaussianbinomialpoissoninverse gaussiangamma 响应分布,还包括响应分布未明确指定的 quasi-likelihood 模型。在后一种情况下,方差函数 必须指定为均值的函数,但在其他情况下,此函数由响应分布隐含。

每个响应分布都允许使用各种链接函数将均值与线性预测器联系起来。自动可用的链接函数显示在以下表格中

族名称链接函数
binomiallogitprobitlogcloglog
gaussianidentityloginverse
Gammaidentityinverselog
inverse.gaussian1/mu^2identityinverselog
poissonidentitylogsqrt
quasilogitprobitcloglogidentityinverselog1/mu^2sqrt

响应分布、链接函数以及执行建模练习所需的其他各种信息的组合称为广义线性模型的


11.6.2 glm() 函数

由于响应的分布仅通过单个线性函数依赖于刺激变量,因此,用于线性模型的机制仍然可用于指定广义模型的线性部分。族必须以不同的方式指定。

用于拟合广义线性模型的 R 函数是 glm(),它使用以下形式

> fitted.model <- glm(formula, family=family.generator, data=data.frame)

唯一的新特性是 family.generator,它是描述族的工具。它是生成函数和表达式的列表的函数的名称,这些函数和表达式共同定义和控制模型和估计过程。尽管乍看之下这似乎有点复杂,但它的用法非常简单。

标准、提供的族生成器的名称在 表中的“族名称”下给出。在存在链接选择的情况下,链接名称也可以作为参数用括号与族名称一起提供。对于 quasi 族,方差函数也可以用这种方式指定。

一些示例可以使流程清晰明了。

gaussian

如下调用

> fm <- glm(y ~ x1 + x2, family = gaussian, data = sales)

与如下调用实现相同的结果

> fm <- lm(y ~ x1+x2, data=sales)

但效率低得多。请注意,gaussian 族不会自动提供链接选择,因此不允许参数。如果问题需要具有非标准链接的 gaussian 族,通常可以通过 quasi 族实现,我们稍后会看到。

binomial

考虑 Silvey (1970) 中的一个小型人工示例。

在爱琴海的卡利索斯岛上,男性居民患有先天性眼疾,其影响会随着年龄的增长而加剧。对不同年龄的岛民男性进行了失明测试并记录了结果。数据如下所示

年龄2035455570
受试人数5050505050
失明人数 617263744

我们考虑的问题是为这些数据拟合逻辑回归和概率模型,并为每个模型估算 LD50,即男性居民失明几率为 50% 的年龄。

如果 yx 岁的失明人数,n 是受试人数,则这两个模型的形式均为 y ~ B(n, F(beta_0 + beta_1 x)),其中对于概率情况,F(z) = Phi(z) 是标准正态分布函数,而在 logit 情况下(默认),F(z) = e^z/(1+e^z)。在这两种情况下,LD50 为 LD50 = - beta_0/beta_1,即分布函数参数为零的点。

第一步是将数据设置为数据框

> kalythos <- data.frame(x = c(20,35,45,55,70), n = rep(50,5),
                         y = c(6,17,26,37,44))

要使用 glm() 拟合二项模型,响应有三种可能性

  • 如果响应是 向量,则假定它保存 二进制 数据,因此必须是 0/1 向量。
  • 如果响应是 两列矩阵,则假定第一列保存试验的成功次数,第二列保存失败次数。
  • 如果响应是 因子,则其第一级被视为失败 (0),所有其他级别被视为“成功” (1)。

这里我们需要第二个约定,因此我们向数据帧添加一个矩阵

> kalythos$Ymat <- cbind(kalythos$y, kalythos$n - kalythos$y)

为了拟合模型,我们使用

> fmp <- glm(Ymat ~ x, family = binomial(link=probit), data = kalythos)
> fml <- glm(Ymat ~ x, family = binomial, data = kalythos)

由于 logit 链接是默认值,因此可以在第二次调用时省略参数。要查看每个拟合的结果,我们可以使用

> summary(fmp)
> summary(fml)

两个模型都非常适合(甚至太适合了)。要查找 LD50 估计值,我们可以使用一个简单函数

> ld50 <- function(b) -b[1]/b[2]
> ldp <- ld50(coef(fmp)); ldl <- ld50(coef(fml)); c(ldp, ldl)

此数据中的实际估计值分别为 43.663 年和 43.601 年。

泊松模型

对于泊松族,默认链接是 log,实际上,此族的用途主要在于拟合频率数据的替代泊松对数线性模型,其实际分布通常是多项式分布。这是一个庞大而重要的主题,我们在此不做进一步讨论。它甚至构成了非高斯广义模型整体使用的一个主要部分。

偶尔,泊松数据会在实践中真正出现,并且在过去,它通常在对数或平方根转换后作为高斯数据进行分析。作为后者的优雅替代方案,可以拟合泊松广义线性模型,如下例所示

> fmod <- glm(y ~ A + B + x, family = poisson(link=sqrt),
              data = worm.counts)

拟似似然模型

对于所有族,响应的方差将取决于均值,并将具有比例参数作为乘数。方差对均值的依赖形式是响应分布的特征;例如,对于泊松分布,Var(y) = mu。

对于拟似似然估计和推理,没有指定精确的响应分布,而只指定了链接函数和方差函数的形式,因为它取决于均值。由于拟似似然估计使用与高斯分布形式相同的技术,因此顺便说一句,此族提供了一种拟合具有非标准链接函数或方差函数的高斯模型的方法。

例如,考虑拟合非线性回归 y = theta_1 z_1 / (z_2 - theta_2) + e,也可以将其另写为 y = 1 / (beta_1 x_1 + beta_2 x_2) + e,其中 x_1 = z_2/z_1,x_2 = -1/z_1,beta_1 = 1/theta_1,beta_2 = theta_2/theta_1。假设设置了合适的 data 帧,我们可以拟合此非线性回归,如下所示

> nlfit <- glm(y ~ x1 + x2 - 1,
               family = quasi(link=inverse, variance=constant),
               data = biochem)

读者可参阅手册和帮助文档以获取更多信息(视需要而定)。


11.7 非线性最小二乘法和最大似然模型

某些形式的非线性模型可以通过广义线性模型 (glm()) 进行拟合。但在大多数情况下,我们必须将非线性曲线拟合问题当作非线性优化问题来处理。R 的非线性优化例程是 optim()nlm()nlminb() 我们寻求最小化某些拟合度不足指标的参数值,它们通过迭代尝试各种参数值来实现这一点。例如,与线性回归不同,无法保证该过程会收敛于令人满意的估计值。所有方法都需要关于要尝试的参数值的初始猜测,并且收敛可能极大地取决于起始值的质量。


11.7.1 最小二乘法

拟合非线性模型的一种方法是通过最小化平方误差和 (SSE) 或残差。如果观测误差可能合理地源自正态分布,则此方法有意义。

以下是 Bates & Watts (1988) 第 51 页中的一个示例。数据是

> x <- c(0.02, 0.02, 0.06, 0.06, 0.11, 0.11, 0.22, 0.22, 0.56, 0.56,
         1.10, 1.10)
> y <- c(76, 47, 97, 107, 123, 139, 159, 152, 191, 201, 207, 200)

要最小化的拟合准则是

> fn <- function(p) sum((y - (p[1] * x)/(p[2] + x))^2)

为了进行拟合,我们需要参数的初始估计值。找到合理起始值的一种方法是绘制数据,猜测一些参数值,并使用这些值叠加模型曲线。

> plot(x, y)
> xfit <- seq(.02, 1.1, .05)
> yfit <- 200 * xfit/(0.1 + xfit)
> lines(spline(xfit, yfit))

我们可以做得更好,但 200 和 0.1 的起始值似乎足够了。现在进行拟合

> out <- nlm(fn, p = c(200, 0.1), hessian = TRUE)

拟合后,out$minimum 是 SSE,out$estimate 是参数的最小二乘估计值。要获得估计值的近似标准误差 (SE),我们执行

> sqrt(diag(2*out$minimum/(length(y) - 2) * solve(out$hessian)))

上面一行中减去的 2 表示参数的数量。95% 置信区间将是参数估计值 +/- 1.96 SE。我们可以将最小二乘拟合叠加到新绘图中

> plot(x, y)
> xfit <- seq(.02, 1.1, .05)
> yfit <- 212.68384222 * xfit/(0.06412146 + xfit)
> lines(spline(xfit, yfit))

标准包 stats 为通过最小二乘法拟合非线性模型提供了更广泛的工具。我们刚刚拟合的模型是 Michaelis-Menten 模型,因此我们可以使用

> df <- data.frame(x=x, y=y)
> fit <- nls(y ~ SSmicmen(x, Vm, K), df)
> fit
Nonlinear regression model
  model:  y ~ SSmicmen(x, Vm, K)
   data:  df
          Vm            K
212.68370711   0.06412123
 residual sum-of-squares:  1195.449
> summary(fit)

Formula: y ~ SSmicmen(x, Vm, K)

Parameters:
    Estimate Std. Error t value Pr(>|t|)
Vm 2.127e+02  6.947e+00  30.615 3.24e-11
K  6.412e-02  8.281e-03   7.743 1.57e-05

Residual standard error: 10.93 on 10 degrees of freedom

Correlation of Parameter Estimates:
      Vm
K 0.7651

11.7.2 最大似然

最大似然是一种非线性模型拟合方法,即使误差不呈正态分布也可以应用。该方法找到使对数似然函数最大化(或等效地使负对数似然函数最小化)的参数值。以下是 Dobson (1990) 第 108-111 页的一个示例。此示例将逻辑模型拟合到剂量反应数据,显然也可以通过 glm() 进行拟合。数据为

> x <- c(1.6907, 1.7242, 1.7552, 1.7842, 1.8113,
         1.8369, 1.8610, 1.8839)
> y <- c( 6, 13, 18, 28, 52, 53, 61, 60)
> n <- c(59, 60, 62, 56, 63, 59, 62, 60)

要最小化的负对数似然函数为

> fn <- function(p)
   sum( - (y*(p[1]+p[2]*x) - n*log(1+exp(p[1]+p[2]*x))
           + log(choose(n, y)) ))

我们选择合理的起始值并进行拟合

> out <- nlm(fn, p = c(-50,20), hessian = TRUE)

拟合后,out$minimum 是负对数似然函数,out$estimate 是参数的最大似然估计值。要获得估计值的近似 SE,我们执行

> sqrt(diag(solve(out$hessian)))

95% 置信区间为参数估计值 +/- 1.96 SE。


11.8 一些非标准模型

我们用简要介绍 R 中可用于特殊回归和数据分析问题的其他一些工具来结束本章。

  • 混合模型。推荐的 nlme 包提供了函数 lme()nlme() ,用于线性混合效应模型和非线性混合效应模型,即某些系数对应于随机效应的线性和非线性回归。这些函数大量使用公式来指定模型。
  • 局部逼近回归。 loess() 函数通过使用局部加权回归来拟合非参数回归。此类回归对于突出杂乱数据中的趋势或对数据进行简化以深入了解大型数据集非常有用。

    函数 loess 在标准包 stats 中,与投影追踪回归的代码一起使用。

  • 稳健回归。 有几个函数可用于拟合回归模型,以抵抗数据中极端异常值的影响。推荐包 MASS 中的函数 lqs 为高抗性拟合提供了最先进的算法。在包中提供了不太抗性但统计上更有效的方法,例如包 MASS 中的函数 rlm
  • 加性模型。 此技术旨在从确定变量的平滑加性函数(通常每个确定变量一个)构建回归函数。包 acepack 中的函数 avasace 以及包 mda 中的函数 brutomars 提供了 R 中用户贡献包中这些技术的示例。扩展是 广义加性模型,在用户贡献包 gammgcv 中实现。
  • 基于树的模型。 与为预测或解释寻求明确的全局线性模型不同,基于树的模型试图在确定变量的关键点递归地对数据进行二分,以便最终将数据划分为组,这些组在内部尽可能同质,在组之间尽可能异质。结果通常会导致其他数据分析方法无法产生的见解。

    模型再次以普通线性模型形式指定。模型拟合函数是 tree() 但许多其他通用函数(如 plot()text())非常适合以图形方式显示基于树的模型拟合的结果。

    树模型可通过用户贡献包 rparttree 在 R 获得。


12 图形程序

图形工具是 R 环境中一个重要且极其通用的组件。可以使用该工具显示各种统计图形,还可以创建完全新类型的图形。

图形工具可在交互模式和批处理模式下使用,但在大多数情况下,交互使用更有效。交互使用也很容易,因为在启动时,R 会启动一个图形设备驱动程序,该驱动程序会打开一个特殊的图形窗口以显示交互式图形。虽然这是自动完成的,但了解所使用的命令在 UNIX 下是 X11()、在 Windows 下是 windows()、在 macOS 下是 quartz() 可能很有用。始终可以通过 dev.new() 打开新设备。

设备驱动程序运行后,可以使用 R 绘图命令生成各种图形显示并创建完全新类型的显示。

绘图命令分为三类基本组

此外,R 维护了一个图形参数列表,可以对其进行操作以自定义绘图。

本手册仅描述所谓的“基本”图形。程序包 grid 中的单独图形子系统与基本图形共存——它更强大,但更难使用。有一个推荐的程序包 lattice,它建立在 grid 的基础上,并提供生成类似于 S 中Trellis 系统中的多面板绘图的方法。


12.1 高级绘图命令

高级绘图函数旨在生成作为函数参数传递的数据的完整绘图。在适当的情况下,会自动生成坐标轴、标签和标题(除非您另有要求)。高级绘图命令始终会启动一个新绘图,必要时会擦除当前绘图。


12.1.1 plot() 函数

R 中使用最频繁的绘图函数之一是 plot() 函数。这是一个泛型函数:所生成绘图的类型取决于第一个参数的类型或

plot(x, y)
plot(xy)

如果 xy 是向量,plot(x, y) 会生成 y 针对 x 的散点图。通过将一个参数(第二种形式)作为包含两个元素 xy 的列表或两列矩阵,也可以产生相同的效果。

plot(x)

如果 x 是时间序列,这会生成时间序列绘图。如果 x 是数值向量,它会生成向量中值针对其在向量中的索引的绘图。如果 x 是复数向量,它会生成向量元素的虚部针对实部的绘图。

plot(f)
plot(f, y)

f 是一个因子对象,y 是一个数字向量。第一种形式生成 f 的条形图;第二种形式生成 f 的每个级别的 y 的箱线图。

plot(df)
plot(~ expr)
plot(y ~ expr)

df 是一个数据框,y 是任何对象,expr 是一个由“+”分隔的对象名称列表(例如 a + b + c)。前两种形式生成数据框中的变量的分布图(第一种形式)或多个命名对象的分布图(第二种形式)。第三种形式将 yexpr 中命名的每个对象进行比较。


12.1.2 显示多变量数据

R 提供了两个非常有用的函数来表示多变量数据。如果 X 是一个数字矩阵或数据框,则命令

> pairs(X)

生成由 X 的列定义的变量的对成散点图矩阵,即 X 的每一列都与 X 的每一列绘制,生成的 n(n-1) 个图以矩阵的形式排列,矩阵的行和列上的图例保持不变。

当涉及三个或四个变量时,coplot 可能会更直观。如果 ab 是数字向量,c 是数字向量或因子对象(所有长度相同),则命令

> coplot(a ~ b | c)

生成多个散点图,其中 a 相对于 b,针对 c 的给定值。如果 c 是一个因子,这仅仅意味着 a 相对于 b 绘制 c 的每个级别。当 c 是数字时,它会被分成多个条件间隔,并且对于每个间隔,a 相对于 b 绘制间隔内 c 的值。间隔的数量和位置可以通过 given.values= 参数控制 coplot()—函数 co.intervals() 对于选择间隔很有用。您还可以使用两个给定变量,使用类似这样的命令

> coplot(a ~ b | c + d)

生成 a 相对于 b 的散点图,针对 cd 的每个联合条件间隔。

函数 coplot()pairs() 都采用参数 panel=,该参数可用于自定义每个面板中显示的绘图类型。默认值为 points(),用于生成散点图,但是通过提供一些其他低级图形函数(两个向量 xy)作为 panel= 的值,您可以生成您希望的任何类型的绘图。一个对共绘图有用的示例面板函数是 panel.smooth()


12.1.3 显示图形

其他高级图形函数生成不同类型的绘图。一些示例是

qqnorm(x)
qqline(x)
qqplot(x, y)

分布比较绘图。第一种形式将数字向量 x 相对于预期的正态顺序分数(正态分数绘图)进行绘图,第二种形式通过绘制一条穿过分布和数据四分位的直线,向这种绘图中添加一条直线。第三种形式将 x 的分位数相对于 y 的分位数进行绘图,以比较它们各自的分布。

hist(x)
hist(x, nclass=n)
hist(x, breaks=b, …)

生成数字向量 x 的直方图。通常会选择合理的类别数,但可以通过 nclass= 参数提供建议。或者,可以使用 breaks= 参数准确指定断点。如果给出了 probability=TRUE 参数,则条形图表示按箱宽除以的相对频率,而不是计数。

dotchart(x, …)

构建 x 中数据的点图。在点图中,y 轴给出了 x 中数据的标记,x 轴给出了其值。例如,它允许轻松直观地选择值位于指定范围内的所有数据条目。

image(x, y, z, …)
contour(x, y, z, …)
persp(x, y, z, …)

三个变量的绘图。image 绘图使用不同的颜色绘制矩形网格来表示 z 的值,contour 绘图绘制轮廓线来表示 z 的值,persp 绘图绘制 3D 曲面。


12.1.4 高级绘图函数的参数

可以将许多参数传递给高级图形函数,如下所示

add=TRUE

强制函数充当低级图形函数,将绘图叠加在当前绘图上(仅限某些函数)。

axes=FALSE

禁止生成坐标轴,这对于使用 axis() 函数添加自己的自定义坐标轴很有用。默认值 axes=TRUE 表示包含坐标轴。

log="x"
log="y"
log="xy"

导致 xy 或两个坐标轴变为对数。这适用于许多类型的绘图,但并非所有类型。

type=

type= 参数控制生成的绘图类型,如下所示

type="p"

绘制各个点(默认)

type="l"

绘制线

type="b"

绘制由线连接的点(两者

type="o"

绘制被线覆盖的点

type="h"

从点到零轴绘制垂直线(高密度

type="s"
type="S"

阶跃函数绘图。在第一种形式中,垂直线的顶部定义点;在第二种形式中,底部定义点。

type="n"

完全不绘图。但是,仍然绘制轴(默认),并且根据数据设置坐标系。非常适合使用后续低级图形函数创建绘图。

xlab=string
ylab=string

xy 轴的轴标签。使用这些参数来更改默认标签,通常是高级绘图函数调用中使用的对象的名称。

main=string

图形标题,以大字体置于绘图顶部。

sub=string

副标题,以较小的字体置于 x 轴正下方。


12.2 低级绘图命令

有时,高级绘图函数无法生成您想要的绘图类型。在这种情况下,可以使用低级绘图命令向当前绘图添加额外信息(例如点、线或文本)。

一些更有用的低级绘图函数是

points(x, y)
lines(x, y)

向当前绘图添加点或连接线。plot()type= 参数也可以传递给这些函数(并且对于 points() 默认为 "p",对于 lines() 默认为 "l")。

text(x, y, labels, …)

在由 x, y 给出的点处向绘图添加文本。通常,labels 是一个整数或字符向量,在这种情况下,labels[i] 绘制在点 (x[i], y[i]) 处。默认值为 1:length(x)

注意:此函数通常在以下序列中使用

> plot(x, y, type="n"); text(x, y, names)

图形参数 type="n" 会禁止点,但会设置轴,而 text() 函数会提供特殊字符,如字符向量 names 为点指定的特殊字符。

abline(a, b)
abline(h=y)
abline(v=x)
abline(lm.obj)

向当前绘图添加一条斜率为 b、截距为 a 的线。h=y 可用于指定水平线穿过绘图的 y 坐标,v=x 同理用于指定垂直线的 x 坐标。此外,lm.obj 可以是长度为 2 的 coefficients 组件的列表(例如模型拟合函数的结果),按顺序将其视为截距和斜率。

polygon(x, y, …)

绘制由 (x, y) 中的有序顶点定义的多边形,并(可选)用阴影线填充它,或者如果图形设备允许填充图形,则填充它。

legend(x, y, legend, …)

在指定位置向当前绘图添加图例。绘图字符、线型、颜色等通过字符向量 legend 中的标签进行识别。还必须给出至少一个其他参数 v(一个与 legend 长度相同的向量),其中包含绘图单元的相应值,如下所示

legend( , fill=v)

填充框的颜色

legend( , col=v)

绘制点或线的颜色

legend( , lty=v)

线条样式

legend( , lwd=v)

线条宽度

legend( , pch=v)

绘制字符(字符向量)

title(main, sub)

在当前绘图顶部添加大字体标题 main,并在底部添加小字体副标题 sub(可选)。

axis(side, …)

在当前绘图中添加一个轴,轴位于第一个参数指定的侧边(从底部顺时针计数,为 1 到 4)。其他参数控制轴在绘图中或旁边的位置,以及刻度位置和标签。在使用 axes=FALSE 参数调用 plot() 之后,此功能可用于添加自定义轴。

低级绘图函数通常需要一些定位信息(例如,xy 坐标)来确定新绘图元素的位置。坐标以用户坐标给出,由先前的顶级图形命令定义,并根据提供的数据进行选择。

在需要 xy 参数的地方,也可以只提供一个参数,该参数是一个列表,其中包含名为 xy 的元素。类似地,具有两列的矩阵也是有效的输入。通过这种方式,可以使用诸如 locator()(见下文)之类的函数来交互式地指定绘图中的位置。


12.2.1 数学注释

在某些情况下,向绘图中添加数学符号和公式很有用。这可以通过在 textmtextaxistitle 中指定表达式而不是字符串来实现。例如,以下代码绘制了二项概率函数的公式

> text(x, y, expression(paste(bgroup("(", atop(n, x), ")"), p^x, q^{n-x})))

更多信息,包括可用功能的完整列表,可以使用以下命令从 R 中获取

> help(plotmath)
> example(plotmath)
> demo(plotmath)

12.2.2 Hershey 矢量字体

使用 textcontour 函数渲染文本时,可以指定 Hershey 矢量字体。使用 Hershey 字体有三个原因

  • Hershey 字体可以产生更好的输出,尤其是在计算机屏幕上,对于旋转和/或小文本。
  • Hershey 字体提供某些标准字体中可能没有的符号。特别是,有十二生肖、制图符号和天文符号。
  • Hershey 字体提供西里尔文和日文(假名和汉字)字符。

更多信息,包括 Hershey 字符表,可以使用以下命令从 R 中获取

> help(Hershey)
> demo(Hershey)
> help(Japanese)
> demo(Japanese)

12.3 与图形交互

R 还提供了一些函数,允许用户使用鼠标从绘图中提取或向绘图中添加信息。其中最简单的是 locator() 函数

locator(n, type)

等待用户使用鼠标左键选择当前绘图上的位置。这将持续到选择 n(默认 512)个点,或按下另一个鼠标按钮。 type 参数允许在所选点处绘图,并且与高级图形命令的效果相同;默认是不绘图。 locator() 返回所选点的坐标,作为具有两个分量 xy 的列表。

locator() 通常不带参数调用。当难以预先计算图形应该放置的位置时,它对于交互式选择诸如图例或标签等图形元素的位置特别有用。例如,要在远离点的附近放置一些信息性文本,可以使用以下命令

> text(locator(1), "Outlier", adj=0)

(如果当前设备(如 postscript)不支持交互式指向,则将忽略 locator()。)

identify(x, y, labels)

允许用户通过绘制 labels 的相应分量(或如果 labels 不存在,则绘制点的索引号)来突出显示 xy 定义的任何点(使用鼠标左键)。在按下另一个按钮时返回所选点的索引。

有时我们希望识别绘图上的特定,而不是它们的位置。例如,我们可能希望用户从图形显示中选择一些感兴趣的观察结果,然后以某种方式处理该观察结果。给定两个数字向量 xy 中的若干(x, y) 坐标,我们可以按如下方式使用 identify() 函数

> plot(x, y)
> identify(x, y)

identify() 函数本身不执行绘图,而只是允许用户移动鼠标指针并在某个点附近单击鼠标左键。如果鼠标指针附近有某个点,它将用其索引号(即它在 x/y 向量中的位置)标记在附近。或者,你可以使用一些信息字符串(例如案例名称)作为高亮,方法是将 labels 参数用于 identify(),或使用 plot = FALSE 参数完全禁用标记。当该进程终止(见上文)时,identify() 返回所选点的索引;你可以使用这些索引从原始向量 xy 中提取所选点。


12.4 使用图形参数

在创建图形时,特别是出于演示或发布目的,R 的默认设置并不总是能完全产生所需内容。但是,你可以使用图形参数来自定义显示的几乎每个方面。R 维护了一个包含大量图形参数的列表,这些参数控制着线条样式、颜色、图形排列和文本对齐等许多内容。每个图形参数都有一个名称(例如“col”,它控制颜色)和一个值(例如颜色编号)。

为每个活动设备维护一个单独的图形参数列表,并且每个设备在初始化时都有一组默认参数。图形参数可以通过两种方式设置:永久设置,影响访问当前设备的所有图形函数;或临时设置,仅影响单个图形函数调用。


12.4.1 永久更改:par() 函数

par() 函数用于访问和修改当前图形设备的图形参数列表。

par()

如果没有参数,则返回当前设备的所有图形参数及其值的列表。

par(c("col", "lty"))

使用字符向量参数,仅返回命名的图形参数(同样,作为列表)。

par(col=4, lty=2)

使用命名参数(或单个列表参数),设置命名图形参数的值,并将参数的原始值作为列表返回。

使用 par() 函数设置图形参数会更改参数的值,从某种意义上说,永久地,因为对图形函数(在当前设备上)的所有未来调用都将受到新值的影响。您可以将以这种方式设置图形参数视为设置参数的“默认”值,除非给出了替代值,否则所有图形函数都将使用该值。

请注意,对 par() 的调用始终影响图形参数的全局值,即使从函数内调用 par() 也是如此。这通常是不希望的行为——通常我们希望设置一些图形参数,进行一些绘图,然后恢复原始值,以免影响用户的 R 会话。您可以通过在进行更改时保存 par() 的结果,并在绘图完成后恢复初始值来恢复初始值。

> oldpar <- par(col=4, lty=2)
  ... plotting commands ...
> par(oldpar)

要保存和恢复所有可设置24 图形参数,请使用

> oldpar <- par(no.readonly=TRUE)
  ... plotting commands ...
> par(oldpar)

12.4.2 临时更改:图形函数的参数

图形参数也可以作为命名参数传递给(几乎)任何图形函数。这与将参数传递给 par() 函数的效果相同,除了更改仅持续函数调用的时间。例如

> plot(x, y, pch="+")

使用加号作为绘图字符生成散点图,而不会更改未来绘图的默认绘图字符。

遗憾的是,这并未完全一致地实现,有时有必要使用 par() 设置和重置图形参数。


12.5 图形参数列表

以下部分详细介绍了许多常用的图形参数。par() 函数的 R 帮助文档提供了一个更简洁的摘要;这作为一种更详细的替代方案提供。

图形参数将以以下形式呈现

name=value

参数效果的描述。 name 是参数的名称,即在调用 par() 或图形函数时使用的参数名称。 value 是设置参数时可能使用的典型值。

请注意,axes 不是图形参数,而是几个 plot 方法的参数:请参阅 xaxtyaxt


12.5.1 图形元素

R 图形由点、线、文本和多边形(填充区域)组成。存在控制如何绘制这些图形元素的图形参数,如下所示

pch="+"

用于绘制点的字符。默认值因图形驱动程序而异,但通常为圆形。绘制的点往往会略高于或低于适当的位置,除非您使用 "." 作为绘制字符,它会产生居中的点。

pch=4

pch 被指定为 0 到 25(包括)之间的整数时,将生成一个专门的绘制符号。要查看符号是什么,请使用命令

> legend(locator(1), as.character(0:25), pch = 0:25)

21 到 25 的符号可能看起来与前面的符号重复,但可以用不同的方式着色:请参阅 points 及其示例的帮助。

此外,pch 可以是字符或介于 32:255 范围内的数字,代表当前字体中的一个字符。

lty=2

线条类型。并非所有图形设备都支持备用线条样式(并且在支持备用线条样式的设备上有所不同),但线条类型 1 始终为实线,线条类型 0 始终为不可见,线条类型 2 及更高为点线或虚线,或两者的某种组合。

lwd=2

线条宽度。以“标准”线条宽度的倍数表示的所需线条宽度。影响坐标轴线以及使用 lines() 等绘制的线条。并非所有设备都支持此功能,有些设备对可使用的宽度有限制。

col=2

用于点、线、文本、填充区域和图像的颜色。当前调色板中的一个数字(请参阅 ?palette)或一个已命名的颜色。

col.axis
col.lab
col.main
col.sub

分别用于坐标轴注释、xy 标签、主标题和副标题的颜色。

font=2

一个整数,用于指定用于文本的字体。如果可能,设备驱动程序会安排 1 对应于纯文本、2 对应于粗体、3 对应于斜体、4 对应于粗斜体和 5 对应于符号字体(包括希腊字母)。

font.axis
font.lab
font.main
font.sub

分别用于坐标轴注释、xy 标签、主标题和副标题的字体。

adj=-0.1

文本相对于绘图位置的对齐方式。0 表示左对齐,1 表示右对齐,0.5 表示在绘图位置水平居中。实际值是文本中出现在绘图位置左侧的部分,因此值为 -0.1 会在文本和绘图位置之间留出 10% 的文本宽度间隙。

cex=1.5

字符扩展。该值是文本字符(包括绘图字符)相对于默认文本大小的所需大小。

cex.axis
cex.lab
cex.main
cex.sub

分别用于坐标轴注释、xy 标签、主标题和副标题的字符扩展。


12.5.2 坐标轴和刻度标记

R 的许多高级绘图都有坐标轴,你也可以使用低级 axis() 图形函数自己构建坐标轴。坐标轴有三个主要组成部分:坐标轴线(线条样式由 lty 图形参数控制)、刻度标记(沿坐标轴线标记单位划分)和刻度标签(标记单位)。可以使用以下图形参数自定义这些组件。

lab=c(5, 7, 12)

前两个数字分别是 xy 坐标轴上所需的刻度间隔数。第三个数字是坐标轴标签所需的长度,以字符为单位(包括小数点)。为此参数选择过小的值可能会导致所有刻度标签都舍入到相同的数字!

las=1

坐标轴标签的方向。 0 表示始终平行于坐标轴,1 表示始终水平,2 表示始终垂直于坐标轴。

mgp=c(3, 1, 0)

坐标轴组件的位置。第一个组件是从坐标轴标签到坐标轴位置的距离,以文本行表示。第二个组件是到刻度标签的距离,最后一个组件是从坐标轴位置到坐标轴线(通常为零)的距离。正数测量绘图区域外部,负数测量内部。

tck=0.01

刻度标记的长度,作为绘图区域大小的几分之一。当 tck 较小时(小于 0.5),xy 坐标轴上的刻度标记被迫具有相同的大小。值为 1 会生成网格线。负值会在绘图区域外部生成刻度标记。对于内部刻度标记,请使用 tck=0.01mgp=c(1,-1.5,0)

xaxs="r"
yaxs="i"

分别针对 xy 轴的轴样式。对于样式 "i"(内部)和 "r"(默认),刻度标记始终落在数据范围内,但样式 "r" 在边缘处留出少量空间。


12.5.3 图形边距

R 中的单个绘图称为 figure,由一个 绘图区域 组成,该区域周围有边距(可能包含轴标签、标题等),并且(通常)由轴本身限定。

一个典型的图形是

images/fig11

控制图形布局的图形参数包括

mai=c(1, 0.5, 0.5, 0)

分别以英寸为单位测量的底部、左侧、顶部和右侧边距的宽度。

mar=c(4, 2, 2, 1)

类似于 mai,但测量单位是文本行。

marmai 是等效的,因为设置其中一个会更改另一个的值。为此参数选择的默认值通常过大;很少需要右侧边距,如果未使用标题,则也不需要顶部边距。底部和左侧边距必须足够大,以容纳轴和刻度标签。此外,默认值的选择不考虑设备表面的大小:例如,将 postscript() 驱动程序与 height=4 参数一起使用,将导致一个绘图,除非明确设置 marmai,否则该绘图大约有 50% 的边距。当使用多幅图形时(见下文),边距会减小,但当许多图形共享同一页面时,这可能不够。


12.5.4 多个图形环境

R 允许您在单页上创建 nm 个图形的阵列。每个图形都有自己的边距,并且图形阵列可以选择被 外边距 包围,如下面的图形所示。

images/fig12

与多个图形相关的图形参数如下

mfcol=c(3, 2)
mfrow=c(2, 4)

设置多个图形阵列的大小。第一个值是行数;第二个值是列数。这两个参数之间的唯一区别在于设置 mfcol 会按列填充图形;mfrow 按行填充。

图中的布局可以通过设置 mfrow=c(3,2) 创建;该图形显示在绘制了四个图后的页面。

设置其中任何一个都可以减小符号和文本的基本大小(由 par("cex") 和设备的点大小控制)。在恰好有两行两列的布局中,基本大小减小了 0.83 倍:如果行或列有三个或更多,则减小因子为 0.66。

mfg=c(2, 2, 3, 2)

在多个图形环境中当前图形的位置。前两个数字是当前图形的行和列;后两个数字是多个图形阵列中的行数和列数。设置此参数可在阵列中的图形之间跳转。您甚至可以使用与同一页面上大小不等的图形的 真实 值不同的值作为后两个数字。

fig=c(4, 9, 1, 4)/10

当前图形在页面上的位置。值分别是页面左、右、下和上边缘的位置,以从左下角测量的页面百分比表示。示例值将用于页面右下角的图形。设置此参数可在页面内任意放置图形。如果您想将图形添加到当前页面,也请使用 new=TRUE(与 S 不同)。

oma=c(2, 0, 3, 0)
omi=c(0, 0, 0.8, 0)

外边距的大小。与 marmai 类似,第一个测量文本行,第二个测量英寸,从下边距开始,顺时针进行。

外边距对于页面标题等特别有用。可以使用 mtext() 函数将文本添加到外边距,其参数 outer=TRUE。但是,默认情况下没有外边距,因此必须使用 omaomi 显式创建。

可以使用 split.screen()layout() 函数以及 gridlattice 包生成更复杂的多个图形排列。


12.6 设备驱动程序

R 可以在几乎任何类型的显示器或打印设备上生成图形(质量等级不同)。但在开始之前,R 需要知道它处理的是哪种类型的设备。这可以通过启动设备驱动程序来完成。设备驱动程序的目的是将 R 中的图形指令(例如“绘制一条线”)转换为特定设备可以理解的形式。

通过调用设备驱动程序函数来启动设备驱动程序。每个设备驱动程序都有一个这样的函数:键入 help(Devices) 以获取它们的完整列表。例如,发出命令

> postscript()

将导致所有未来的图形输出以 PostScript 格式发送到打印机。一些常用的设备驱动程序是

X11()

用于类 Unix 系统上的 X11 窗口系统

windows()

用于 Windows

quartz()

用于 macOS

postscript()

用于在 PostScript 打印机上打印或创建 PostScript 图形文件。

pdf()

生成 PDF 文件,也可以将其包含在 PDF 文件中。

png()

生成位图 PNG 文件。(并非始终可用:请参阅其帮助页面。)

jpeg()

生成位图 JPEG 文件,最适合用于 image 图表。(并非始终可用:请参阅其帮助页面。)

使用完设备后,务必通过发出以下命令终止设备驱动程序

> dev.off()

这可确保设备干净地完成;例如,在硬拷贝设备的情况下,这可确保完成每页并将其发送到打印机。(这将在会话正常结束时自动发生。)


12.6.1 排版文档的 PostScript 图表

通过将 file 参数传递给 postscript() 设备驱动程序函数,您可以在您选择的文件中以 PostScript 格式存储图形。除非给出了 horizontal=FALSE 参数,否则图表将采用横向,您可以使用 widthheight 参数控制图形的大小(图表将按适当比例缩放以适应这些尺寸)。例如,命令

> postscript("file.ps", horizontal=FALSE, height=5, pointsize=10)

将生成包含用于图表的 PostScript 代码的文件,该文件可能用于包含在文档中。需要注意的是,如果命令中命名的文件已存在,则该文件将被覆盖。即使该文件只是在同一 R 会话中创建的,情况也是如此。

PostScript 输出的许多用法是将图形合并到另一个文档中。当生成封装的 PostScript 时,效果最佳:R 始终生成符合规范的输出,但仅在提供 onefile=FALSE 参数时将输出标记为符合规范。这种不寻常的表示法源自 S 兼容性:它实际上意味着输出将是单页(这是 EPSF 规范的一部分)。因此,要生成用于包含的绘图,请使用类似于

> postscript("plot1.eps", horizontal=FALSE, onefile=FALSE,
             height=8, width=6, pointsize=10)

12.6.2 多个图形设备

在高级使用 R 时,同时使用多个图形设备通常很有用。当然,任何时候只有一个图形设备可以接受图形命令,这称为当前设备。当多个设备处于打开状态时,它们会形成一个编号序列,名称会给出任何位置的设备类型。

用于操作多个设备的主要命令及其含义如下

X11()

[UNIX]

windows()
win.printer()
win.metafile()

[Windows]

quartz()

[macOS]

postscript()
pdf()
png()
jpeg()
tiff()
bitmap()

每次对设备驱动程序函数的新调用都会打开一个新的图形设备,从而将设备列表扩展一个。此设备成为当前设备,图形输出将发送到该设备。

dev.list()

返回所有活动设备的数量和名称。列表中位置 1 上的设备始终是根本不接受图形命令的空设备

dev.next()
dev.prev()

分别返回当前设备的下一个或上一个图形设备的数量和名称。

dev.set(which=k)

可用于将当前图形设备更改为设备列表中位置 k 上的设备。返回设备的数量和标签。

dev.off(k)

终止设备列表中点 k 处的图形设备。对于某些设备(例如 postscript 设备),这将立即打印文件或正确完成文件以供以后打印,具体取决于设备的启动方式。

dev.copy(设备, …, which=k)
dev.print(设备, …, which=k)

复制设备 k。此处 设备 是一个设备函数,如 postscript,如有需要,可通过“”指定额外参数。 dev.print 类似,但复制的设备会立即关闭,以便立即执行结束操作,如打印硬拷贝。

graphics.off()

终止列表中的所有图形设备,空设备除外。


12.7 动态图形

R 没有动态或交互图形的内置功能,例如旋转点云或“刷选”(交互式突出显示)点。但是,Swayne、Cook 和 Buja 提供的系统 GGobi 中提供了广泛的动态图形功能,可从以下位置获取:

http://ggobi.org/

并且可以通过包 rggobi 从 R 中访问,该包在 http://ggobi.org/rggobi.html 中进行了描述。

此外,包 rgl 提供了与 3D 图形(例如曲面)交互的方法。


13 包

所有 R 函数和数据集都存储在中。只有在加载包时,其内容才可用。这样做既是为了提高效率(完整列表会占用更多内存,并且搜索时间比子集更长),也是为了帮助包开发人员,使他们免受与其他代码的名称冲突。包的开发过程在 编写 R 扩展创建 R 包 中进行了描述。在此,我们将从用户的角度对其进行描述。

要查看在您的网站上安装了哪些软件包,请发布命令

> library()

不带任何参数。要加载特定软件包(例如,boot软件包,其中包含 Davison & Hinkley (1997) 的函数),请使用类似于以下内容的命令

> library(boot)

连接到互联网的用户可以使用 install.packages()update.packages() 函数(可通过 Windows 和 macOS GUI 中的 Packages 菜单获得,请参阅 R 安装和管理 中的 安装软件包)来安装和更新软件包。

要查看当前加载了哪些软件包,请使用

> search()

以显示搜索列表。某些软件包可能已加载但不在搜索列表中(请参阅 命名空间):这些软件包将包含在

> loadedNamespaces()

中给出的列表中。要查看已安装软件包中的所有可用帮助主题的列表,请使用

> help.start()

以启动 HTML 帮助系统,然后导航到 Reference 部分中的软件包列表。


13.1 标准软件包

标准(或 base)软件包被认为是 R 源代码的一部分。它们包含使 R 能够工作的基本函数,以及本手册中描述的数据集和标准统计和图形函数。它们应在任何 R 安装中自动可用。请参阅 R 常见问题解答 中的 R 软件包,以获取完整列表。


13.2 贡献的软件包和 CRAN

R 有成千上万个由不同作者编写的贡献包。其中一些包实现了专门的统计方法,另一些包提供了对数据或硬件的访问,还有一些包旨在补充教科书。一些(推荐包)随 R 的每个二进制发行版一起分发。大多数包可从 CRANhttps://CRAN.R-project.org/ 及其镜像)和其他存储库(如 Bioconductor (https://www.bioconductor.org/))下载。R 常见问题解答包含 CRAN 包在发布时的列表,但可用包的集合经常变化。


13.3 命名空间

包有命名空间,它可以做三件事:允许包编写者隐藏仅供内部使用的函数和数据,防止当用户(或其他包编写者)选择与包中名称冲突的名称时函数中断,并提供一种在特定包中引用对象的方法。

例如,t() 是 R 中的转置函数,但用户可以定义自己的名为 t 的函数。命名空间可以防止用户的定义优先,并破坏尝试转置矩阵的每个函数。

有两个运算符可用于命名空间。双冒号运算符 :: 从特定命名空间中选择定义。在上面的示例中,转置函数始终可用作 base::t,因为它是在 base 包中定义的。只有从包中导出的函数才能通过这种方式检索。

三冒号运算符 ::: 可能出现在 R 代码中的几个地方:它就像双冒号运算符,但还可以访问隐藏的对象。用户更有可能使用 getAnywhere() 函数,该函数搜索多个包。

包通常是相互依赖的,加载一个包可能会导致自动加载其他包。上面描述的冒号运算符还将导致自动加载关联的包。当自动加载具有命名空间的包时,它们不会添加到搜索列表中。


14 操作系统工具

R 拥有相当广泛的设施来访问其运行所在的操作系统:这允许将其用作脚本语言,并且 R 本身大量使用了这种能力,例如安装包。

由于 R 自身的脚本需要在所有平台上运行,因此已投入大量精力使脚本设施尽可能地与平台无关。


14.1 文件和目录

有许多函数可以操作文件和目录。以下是其中一些常用函数的指针。

要创建(空)文件或目录,请使用 file.createdir.create。(它们是 POSIX 实用程序 touchmkdir 的类似物。)有关 R 会话目录中的临时文件和目录,请参见 tempfile

可以通过 file.removeunlink 删除文件:后者可以删除目录树。

对于目录列表,请使用 list.files(也可作为 dir)或 list.dirs。这些可以使用正则表达式选择文件:要通过通配符选择,请使用 Sys.glob

可以通过 file.info 查找有关文件路径的许多类型的信息(例如,它是否是文件或目录)。

有几种方法可以找出文件是否“存在”(文件可以存在于文件系统中,但当前用户不可见)。有函数 file.existsfile.accessfile_test,其中包含此测试的各种版本:file_test 是 POSIX test 命令的版本,适用于熟悉 shell 脚本的人员。

函数 file.copy 是 POSIX 命令 cp 的 R 类似物。

可以通过 file.choose 交互式地选择文件:Windows 端口具有更通用的函数 choose.fileschoose.dir,并且 tcltk 包中也有类似的函数:tk_choose.filestk_choose.dir

函数 file.showfile.edit 将以适合 R 端口的方式显示和编辑一个或多个文件,如果正在使用控制台(例如 Windows 上的 RGui 或 macOS 上的 R.app),则使用控制台的功能。

文件系统中对 链接 有一定的支持:请参阅函数 file.linkSys.readlink


14.2 文件路径

除了少数例外情况外,R 依赖于底层操作系统函数来处理文件路径。这方面的一些内容允许依赖于操作系统,甚至依赖于操作系统的版本。有关操作系统应如何解释文件路径的 POSIX 标准,许多 R 用户都假定符合 POSIX:但 Windows 并未声称符合该标准,而其他操作系统可能不太完全符合该标准。

以下是使用文件路径时遇到的一些问题。

  • POSIX 文件系统区分大小写,因此 foo.pngFoo.PNG 是不同的文件。但是,Windows 和 macOS 的默认设置是不区分大小写的,而 FAT 文件系统(通常用于可移动存储)通常不区分大小写(并且所有文件路径都可以映射为小写)。
  • 几乎所有 Windows 操作系统服务都支持使用斜杠或反斜杠作为文件路径分隔符,并且 R 将已知的例外转换为 Windows 所需的形式。
  • 带有尾部斜杠的文件路径的行为取决于操作系统。此类路径在 Windows 上无效,不应指望它们起作用。POSIX-2008 要求此类路径仅匹配目录,但早期版本允许它们也匹配文件。因此,最好避免使用它们。
  • 文件路径中出现多个斜杠(例如 /abc//def)在 POSIX 文件系统中是有效的,并且被视为只有一个斜杠。它们通常被 Windows 操作系统函数接受。但是,前导双斜杠可能具有不同的含义。
  • Windows 的 UNC 文件路径(例如 \\server\dir1\dir2\file\\?\UNC\server\dir1\dir2\file)不受支持,但它们可能在某些 R 函数中起作用。POSIX 文件系统被允许特殊地处理前导双斜杠。
  • Windows 允许包含驱动器且相对于驱动器上当前目录的文件路径,例如 d:foo/bar 指的是 d:/a/b/c/foo/bar,如果 驱动器 d: 上的 当前目录是 /a/b/c。这些路径应能起作用,但使用绝对路径更安全。

函数 basenamedirname 选择文件路径的各个部分:从组件组装文件路径的推荐方法是 file.path。函数 pathexpand 执行“波浪线扩展”,用主目录(当前用户的主目录,可能还有其他用户的主目录)的值进行替换。

在具有链接的文件系统上,单个文件可以通过许多文件路径进行引用。函数 normalizePath 将找到一个规范文件路径。

Windows 有短(“8.3”)和长文件名概念:normalizePath 将返回一个使用长文件名的绝对路径,shortPathName 将返回一个使用短文件名的版本。后者不包含空格,并使用反斜杠作为分隔符,因此有时可用于从 R 导出名称。

文件 权限 是一个相关主题。R 支持所有者/组/所有人的读/写/执行权限的 POSIX 概念,但这可能仅在文件系统上得到部分支持,例如在 Windows 上仅识别只读文件(对于运行 R 会话的帐户)。访问控制列表 (ACL) 用于多个文件系统,但没有商定的标准,R 也没有控制它们的工具。使用 Sys.chmod 更改权限。


14.3 系统命令

函数 systemsystem2 用于调用系统命令并选择性地收集其输出。 system2 更通用一些,但其主要优势在于使用它可以更轻松地编写跨平台代码。

system 在 Windows 上的行为与其他操作系统不同(因为同名的 API C 调用也是如此)。在其他地方,它调用 shell 来运行命令:R 的 Windows 端口有一个函数 shell 来执行此操作。

要找出操作系统是否包含某个命令,请使用 Sys.which,它尝试以跨平台的方式执行此操作(不幸的是,它不是标准操作系统服务)。

函数 shQuote 会根据当前操作系统对命令所需的文件路径进行引用。


14.4 压缩和存档

最近版本的 R 具有广泛的工具来读取和写入压缩文件,通常是透明的。在 R 中读取文件在很大程度上是由连接完成的,而 file 函数用于打开与文件(或 URL)的连接,并且能够从文件的“魔术”头识别所使用的压缩。

支持时间最长的压缩类型是 gzip 压缩,它仍然是一个很好的通用折衷方案。也可以读取由早期 Unix compress 实用程序压缩的文件,但这些文件变得越来越少见。另外两种形式的压缩,即 bzip2xz 实用程序的压缩,也可用。这些通常会以较慢的解压缩速度和更慢的压缩速度为代价实现更高的压缩率(取决于文件,高得多)。

xzlzma 压缩之间存在一些混淆(请参阅 https://en.wikipedia.org/wiki/Xzhttps://en.wikipedia.org/wiki/LZMA):R 可以读取大多数版本压缩的文件两者。

文件存档是包含文件集合的单个文件,最常见的是“tarball”和 zip 文件,用于分发 R 包。R 可以列出和解包两者(请参阅函数 untarunzip),并创建两者(对于 zip,借助于外部程序)。


附录 A 示例会话

以下会话旨在通过使用 R 环境的某些功能向您介绍这些功能。系统中的许多功能最初会让人感到陌生和困惑,但这种困惑很快就会消失。

根据您的平台适当地启动 R(请参阅调用 R)。

R 程序以一个横幅开始。

(在 R 代码中,左侧的提示符不会显示,以避免混淆。)

help.start()

启动在线帮助的HTML界面(使用机器上可用的 Web 浏览器)。您应该使用鼠标简要浏览此功能的特性。

最小化帮助窗口并转到下一部分。

x <- rnorm(50)
y <- rnorm(x)

生成两个xy坐标的伪随机正态向量。

plot(x, y)

在平面上绘制点。图形窗口将自动出现。

ls()

查看 R 工作空间中现在有哪些 R 对象。

rm(x, y)

删除不再需要的对象。(清理)。

x <- 1:20

使x = (1, 2, ..., 20)

w <- 1 + sqrt(x)/2

标准差的“权重”向量。

dummy <- data.frame(x=x, y= x + rnorm(x)*w)
dummy

制作一个包含两列(xy)的数据框,并查看它。

fm <- lm(y ~ x, data=dummy)
summary(fm)

拟合一个简单的线性回归并查看分析。使用波浪号左侧的y,我们正在对x依赖的y进行建模。

fm1 <- lm(y ~ x, data=dummy, weight=1/w^2)
summary(fm1)

由于我们知道标准差,因此我们可以进行加权回归。

attach(dummy)

使数据框中的列显示为变量。

lrf <- lowess(x, y)

生成非参数局部回归函数。

plot(x, y)

标准点图。

lines(x, lrf$y)

添加局部回归。

abline(0, 1, lty=3)

真回归线:(截距 0,斜率 1)。

abline(coef(fm))

未加权回归线。

abline(coef(fm1), col = "red")

加权回归线。

detach()

从搜索路径中移除数据框。

plot(fitted(fm), resid(fm),
     xlab="Fitted values",
     ylab="Residuals",
     main="Residuals vs Fitted")

标准回归诊断图,用于检查异方差性。你能看到吗?

qqnorm(resid(fm), main="Residuals Rankit Plot")

正态分数图,用于检查偏度、峰度和异常值。(此处不太有用。)

rm(fm, fm1, lrf, x, dummy)

再次清理。

下一部分将研究 Michelson 的经典实验数据,以测量光速。此数据集可在 morley 对象中获得,但我们将读取它以说明 read.table 函数。

filepath <- system.file("data", "morley.tab" , package="datasets")
filepath

获取数据文件的路径。

file.show(filepath)

可选。查看文件。

mm <- read.table(filepath)
mm

将 Michelson 数据读入数据框并查看。有五次实验(列 Expt),每项实验有 20 次运行(列 Run),sl 是记录的光速,经过适当编码。

mm$Expt <- factor(mm$Expt)
mm$Run <- factor(mm$Run)

ExptRun 更改为因子。

attach(mm)

使数据框在位置 2(默认)处可见。

plot(Expt, Speed, main="Speed of Light Data", xlab="Experiment No.")

使用简单的箱线图比较五次实验。

fm <- aov(Speed ~ Run + Expt, data=mm)
summary(fm)

作为随机区组分析,其中“运行”和“实验”作为因子。

fm0 <- update(fm, . ~ . - Run)
anova(fm0, fm)

拟合省略“运行”的子模型,并使用正式方差分析进行比较。

detach()
rm(fm, fm0)

继续之前先清理干净。

现在我们来看看一些更图形化的特性:等高线图和图像图。

x <- seq(-pi, pi, len=50)
y <- x

x 是 [-pi\, pi] 区间中 50 个等间隔值的向量。y 相同。

f <- outer(x, y, function(x, y) cos(y)/(1 + x^2))

f 是一个方阵,行和列分别由 xy 索引,值为函数 cos(y)/(1 + x^2)。

oldpar <- par(no.readonly = TRUE)
par(pty="s")

保存绘图参数并将绘图区域设置为“正方形”。

contour(x, y, f)
contour(x, y, f, nlevels=15, add=TRUE)

绘制 f 的等高线图;添加更多线条以获取更多详细信息。

fa <- (f-t(f))/2

faf 的“非对称部分”。(t() 是转置)。

contour(x, y, fa, nlevels=15)

绘制等高线图,…

par(oldpar)

… 并恢复旧的图形参数。

image(x, y, f)
image(x, y, fa)

绘制一些高密度图像图(如果你愿意,可以获得它们的硬拷贝),…

objects(); rm(x, y, f, fa)

… 并继续之前先清理干净。

R 还可以进行复数运算。

th <- seq(-pi, pi, len=100)
z <- exp(1i*th)

1i 用于复数 i

par(pty="s")
plot(z, type="l")

绘制复数参数意味着绘制虚部与实部的关系。这应该是一个圆。

w <- rnorm(100) + rnorm(100)*1i

假设我们想要在单位圆内抽样点。一种方法是采用实部和虚部为标准正态分布的复数…

w <- ifelse(Mod(w) > 1, 1/w, w)

…并将圆外的任何点映射到它们的倒数。

plot(w, xlim=c(-1,1), ylim=c(-1,1), pch="+",xlab="x", ylab="y")
lines(z)

所有点都在单位圆内,但分布不均匀。

w <- sqrt(runif(100))*exp(2*pi*runif(100)*1i)
plot(w, xlim=c(-1,1), ylim=c(-1,1), pch="+", xlab="x", ylab="y")
lines(z)

第二种方法使用均匀分布。现在这些点在圆盘上看起来应该分布得更均匀。

rm(th, w, z)

再次清理。

q()

退出 R 程序。系统会询问你是否要保存 R 工作空间,对于这样的探索性会话,你可能不想保存。


附录 B 调用 R

Windows 或 macOS 上的 R 用户应首先阅读特定于操作系统的部分,但命令行使用也受支持。


B.1 从命令行调用 R

在 UNIX 或 Windows 上使用命令行时,命令“R”既可用于以如下形式启动 R 主程序

R [options] [<infile] [>outfile],

也可通过 R CMD 接口用作各种 R 工具(例如,用于处理 R 文档格式中的文件或操作加载项包)的包装器,这些工具不打算被“直接”调用。

在 Windows 命令行中,Rterm.exe 优于 R

你需要确保环境变量 TMPDIR 未设置,或者它指向一个可用于创建临时文件和目录的有效位置。

大多数选项控制 R 会话开始和结束时发生的情况。启动机制如下(有关更多信息,另请参阅主题“Startup”的在线帮助,以及以下部分中的一些特定于 Windows 的详细信息)。

  • 除非给出了 --no-environ,否则 R 将搜索用户和站点文件以处理环境变量设置。站点文件名称由环境变量 R_ENVIRON 指向;如果未设置,则使用 R_HOME/etc/Renviron.site(如果存在)。用户文件由环境变量 R_ENVIRON_USER 指向(如果已设置);否则,按顺序搜索当前目录或用户主目录中的 .Renviron 文件。这些文件应包含“name=value”格式的行。(有关精确说明,请参见 help("Startup")。)您可能想要设置的变量包括 R_PAPERSIZE(默认纸张大小)、R_PRINTCMD(默认打印命令)和 R_LIBS(指定搜索附加包的 R 库树列表)。
  • 然后,R 将搜索全站启动配置文件,除非给出了命令行选项 --no-site-file。此文件名称取自 R_PROFILE 环境变量的值。如果该变量未设置,则使用默认 R_HOME/etc/Rprofile.site(如果存在)。
  • 然后,除非给出了 --no-init-file,否则 R 将搜索用户配置文件并对其进行来源。此文件名称取自环境变量 R_PROFILE_USER;如果未设置,则按顺序搜索当前目录或用户主目录中的名为 .Rprofile 的文件。
  • 如果存在,它还将从当前目录中的 .RData 文件加载已保存的工作空间(除非指定了 --no-restore--no-restore-data)。
  • 最后,如果存在 .First() 函数,则执行该函数。此函数(以及在 R 会话结束时执行的 .Last())可以在相应的启动配置文件中定义,或驻留在 .RData 中。

此外,还有用于控制 R 进程可用内存的选项(有关更多信息,请参见主题“Memory”的在线帮助)。除非用户尝试限制 R 使用的内存量,否则通常不需要使用这些选项。

R 接受以下命令行选项。

--help
-h

将简短的帮助消息打印到标准输出并成功退出。

--version

将版本信息打印到标准输出并成功退出。

--encoding=enc

指定从控制台或 stdin 输入时要采用的编码。这需要是 iconv 已知的编码:请参见其帮助页面。(--encoding enc 也被接受。)输入将重新编码为 R 正在运行的区域设置,并且需要在后者的编码中表示(因此,例如,除非区域设置使用 UTF-8 编码,否则无法在法语区域设置中重新编码希腊文本)。

RHOME

将 R“主目录”的路径打印到标准输出并成功退出。除了前端 shell 脚本和手册页,R 安装会将所有内容(可执行文件、包等)放入此目录。

--save
--no-save

控制在 R 会话结束时是否保存数据集。如果在交互会话中未给出任何选项,则在使用 q() 结束会话时会询问用户所需行为;在非交互使用中,必须指定其中一个选项或由其他选项暗示(见下文)。

--no-environ

不读取任何用户文件来设置环境变量。

--no-site-file

在启动时不读取全站配置文件。

--no-init-file

在启动时不读取用户配置文件。

--restore
--no-restore
--no-restore-data

控制在启动时是否恢复已保存的映像(R 启动所在的目录中的文件 .RData)。默认情况下为恢复。(--no-restore 暗含所有特定的 --no-restore-* 选项。)

--no-restore-history

控制在启动时是否恢复历史文件(通常为 R 启动所在的目录中的文件 .Rhistory,但可以通过环境变量 R_HISTFILE 设置)。默认情况下为恢复。

--no-Rconsole

(仅限 Windows)防止在启动时加载 Rconsole 文件。

--vanilla

组合 --no-save--no-environ--no-site-file--no-init-file--no-restore。在 Windows 中,这也包括 --no-Rconsole

-f file
--file=file

(不是 Rgui.exe)从 file 获取输入:‘-’表示 stdin。暗示 --no-save,除非已设置 --save。在类似 Unix 的系统上,应在 file 中避免使用 shell 元字符(但允许使用空格)。

-e expression

(不是 Rgui.exe)将 expression 用作输入行。可以使用一个或多个 -e 选项,但不能与 -f--file 一起使用。暗示 --no-save,除非已设置 --save。(以这种方式使用的表达式的总长度限制为 10,000 个字节。包含空格或 shell 元字符的表达式需要加引号。)

--no-readline

(仅限 UNIX)通过 readline 关闭命令行编辑。当使用 ESS(“Emacs Speaks Statistics”)包在 Emacs 中运行 R 时,这很有用。有关更多信息,请参见 命令行编辑器。命令行编辑已启用以供默认交互使用(参见 --interactive)。此选项还影响波浪号展开:请参见 path.expand 的帮助。

--min-vsize=N
--min-nsize=N

仅供专家使用:分别设置向量堆(以字节为单位)和cons 单元格(数字)的垃圾回收的初始触发器大小。后缀“M”分别指定兆字节或数百万个单元格。默认值分别为 6Mb 和 350k,也可以通过环境变量 R_NSIZER_VSIZE 设置。

--max-ppsize=N

将指针保护堆的最大大小指定为 N 个位置。默认值为 10000,但可以增加以允许进行大型和复杂的计算。目前接受的最大值为 100000。

--quiet
--silent
-q

不打印初始版权和欢迎消息。

--no-echo

让 R 尽可能安静地运行。此选项旨在支持使用 R 为其计算结果的程序。它暗示 --quiet--no-save

--interactive

(仅限 UNIX)即使输入已重定向,也断言 R 确实正在交互式运行:如果输入来自 FIFO 或管道并由交互式程序提供,则使用。 (默认情况下,仅当 stdin 连接到终端或 pty 时,才推断出 R 正在交互式运行。)使用 -e-f--file 断言非交互式使用,即使给出了 --interactive

请注意,这不会打开命令行编辑。

--ess

(仅限 Windows)设置 Rterm 以供 ESS 中的 R-inferior-mode 使用,包括断言交互式使用(没有命令行编辑器)和不缓冲 stdout

--verbose

打印更多有关进度的信息,特别是将 R 的选项 verbose 设置为 TRUE。R 代码使用此选项来控制诊断消息的打印。

--debugger=name
-d name

(仅限 UNIX)通过调试器 name 运行 R。对于大多数调试器(例外是 valgrindgdb 的最新版本),将忽略进一步的命令行选项,而应在从调试器内部启动 R 可执行文件时提供这些选项。

--gui=type
-g type

(仅限 UNIX)使用 type 作为图形用户界面(请注意,这也包括交互式图形)。目前,type 的可能值为“X11”(默认值),并且只要有“Tcl/Tk”支持,则为“Tk”。(为了向后兼容,“x11”和“tk”是可以接受的。)

--arch=name

(仅限 UNIX)运行指定的子架构。

--args

此标志除了导致跳过命令行的其余部分外,什么也不做:这可用于使用 commandArgs(TRUE) 从中检索值。

请注意,输入和输出可以用通常的方式重定向(使用“<”和“>”),但仍适用 4095 字节的行长限制。警告和错误消息将发送到错误通道(stderr)。

命令 R CMD 允许调用各种工具,这些工具与 R 结合使用时很有用,但并不打算“直接”调用。一般形式为

R CMD command args

其中 command 是工具的名称,args 是传递给它的参数。

目前,可以使用以下工具。

BATCH

以批处理模式运行 R。使用可能的进一步选项(参见 ?BATCH)运行 R --restore --save

COMPILE

(仅限 UNIX)编译 C、C++、Fortran … 文件以与 R 一起使用。

SHLIB

构建共享库以进行动态加载。

INSTALL

安装附加包。

REMOVE

移除附加包。

build

构建(即打包)附加包。

check

检查附加包。

LINK

(仅限 UNIX)创建可执行程序的前端。

Rprof

后处理 R 配置文件。

Rdconv
Rd2txt

将 Rd 格式转换为各种其他格式,包括 HTML、LaTeX、纯文本以及提取示例。 Rd2txt 可用作 Rd2conv -t txt 的速记。

Rd2pdf

将 Rd 格式转换为 PDF。

Stangle

从 Sweave 或其他小插图文档中提取 S/R 代码

Sweave

处理 Sweave 或其他小插图文档

Rdiff

忽略标题等内容的 Diff R 输出

config

获取配置信息

javareconf

(仅限 Unix)更新 Java 配置变量

rtags

(仅限 Unix)从 C、R 和 Rd 文件创建 Emacs 样式标记文件

open

(仅限 Windows)通过 Windows 的文件关联打开文件

texify

(仅限 Windows)使用 R 的样式文件处理 (La)TeX 文件

使用

R CMD command --help

要获取通过 R CMD 接口访问的每个工具的使用信息。

此外,您可以在 RCMD 之间使用选项 --arch=--no-environ--no-init-file--no-site-file--vanilla:这些选项会影响工具运行的任何 R 进程。(此处 --vanilla 等效于 --no-environ --no-site-file --no-init-file。)但请注意,R CMD 本身不会使用任何 R 启动文件(特别是,不会使用用户或站点 Renviron 文件),并且这些工具运行的所有 R 进程(BATCH 除外)都会使用 --no-restore。大多数都会使用 --vanilla,因此不会调用任何 R 启动文件:当前的例外是 INSTALLREMOVESweaveSHLIB(使用 --no-site-file --no-init-file)。

R CMD cmd args

对于路径中或通过绝对文件路径给出的任何其他可执行文件 cmd:这对于拥有与 R 或在其下运行的特定命令相同的环境非常有用,例如运行 lddpdflatex。在 Windows 下,cmd 可以是可执行文件或批处理文件,或者如果它具有扩展名 .sh.pl,则会调用相应的解释器(如果可用)来运行它。


B.2 在 Windows 下调用 R

在 Windows 下运行 R 有两种方法。在终端窗口(例如 cmd.exe 或功能更强大的 shell)中,可以使用上一部分中描述的方法,通过 R.exe 或更直接地通过 Rterm.exe 进行调用。对于交互式使用,有一个基于控制台的 GUI(Rgui.exe)。

Windows 下的启动过程与 UNIX 下的非常相似,但需要澄清对“主目录”的引用,因为这在 Windows 上并不总是被定义。如果定义了环境变量 R_USER,则它会给出主目录。接下来,如果定义了环境变量 HOME,则它会给出主目录。在这些两个用户可控设置之后,R 会尝试查找系统定义的主目录。它首先尝试使用 Windows 的“个人”目录(在 Windows 的最新版本中通常是 My Documents)。如果失败,并且定义了环境变量 HOMEDRIVEHOMEPATH(它们通常被定义),则它们会定义主目录。如果所有这些都失败,则主目录将被视为起始目录。

您需要确保环境变量 TMPDIRTMPTEMP 未设置,或者其中一个指向创建临时文件和目录的有效位置。

环境变量可以在命令行上作为“name=value”对提供。

如果有以 .RData(不区分大小写)结尾的参数,则将其解释为要还原的工作空间的路径:它意味着 --restore,并将工作目录设置为命名文件父目录。(此机制用于 RGui.exe 的拖放和文件关联,但也可用于 Rterm.exe。如果命名文件不存在,则在父目录存在时设置工作目录。)

调用 RGui.exe 时,可以使用以下其他命令行选项。

--mdi
--sdi
--no-mdi

控制 Rgui 是以 MDI 程序(在一个主窗口内有多个子窗口)还是 SDI 应用程序(控制台、图形和寻呼机有多个顶级窗口)的形式运行。命令行设置会覆盖用户 Rconsole 文件中的设置。

--debug

启用 Rgui 中的“中断到调试器”菜单项,并在命令行处理期间触发中断到调试器。

在使用 R CMD 的 Windows 中,您还可以指定自己的 .bat.exe.sh.pl 文件。它将在适当的解释器(.pl 的 Perl)下运行,并设置多个环境变量,包括 R_HOMER_OSTYPEPATHBSTINPUTSTEXINPUTS。例如,如果您已在路径中拥有 latex.exe,则

R CMD latex.exe mydoc

将在 mydoc.tex 上运行 LaTeX,并将 R 的 share/texmf 宏的路径附加到 TEXINPUTS。(不幸的是,这对于 MiKTeX 构建的 LaTeX 没有帮助,但 R CMD texify mydoc 在这种情况下将起作用。)


B.3 在 macOS 下调用 R

在 macOS 中运行 R 有两种方法。在 Terminal.app 窗口中调用 R,应用第一个小节中描述的方法。此外,还有一个基于控制台的 GUI (R.app),默认安装在系统中的 Applications 文件夹中。它是一个标准的 macOS 双击应用程序。

macOS 中的启动过程与 UNIX 中的非常相似,但 R.app 不使用命令行参数。“主目录”是 R.framework 中的目录,但除非在 GUI 中可访问的“首选项”窗口中给出了不同的启动目录,否则启动和当前工作目录将设置为用户的“主目录”。


B.4 使用 R 编写脚本

如果你只想运行一个 R 命令文件 foo.R,建议使用 R CMD BATCH foo.R。如果你想在后台或作为批处理作业运行此文件,请使用特定于操作系统的工具来执行此操作:例如,在类似 Unix 的操作系统上的大多数 shell 中,R CMD BATCH foo.R & 会运行一个后台作业。

你可以通过命令行上的其他参数将参数传递给脚本:例如(所需的精确引号将取决于所使用的 shell)

R CMD BATCH "--args arg1 arg2" foo.R &

将参数传递给脚本,可以通过以下方式将其作为字符向量检索:

args <- commandArgs(TRUE)

替代前端 Rscript 简化了此操作,可以通过以下方式调用此前端:

Rscript foo.R arg1 arg2

还可以使用此前端编写可执行脚本文件,如下所示(至少在类 Unix 系统和某些 Windows shell 中):

#! /path/to/Rscript
args <- commandArgs(TRUE)
...
q(status=<exit status code>)

如果将此内容输入文本文件 runfoo 并使其可执行(通过 chmod 755 runfoo),则可以通过以下方式针对不同的参数调用此文件:

runfoo arg1 arg2

有关更多选项,请参阅 help("Rscript")。这会将 R 输出写入 stdoutstderr,并且可以通过运行该命令的 shell 以通常的方式进行重定向。

如果您不希望硬编码到 Rscript 的路径,但将其放在您的路径中(对于已安装的 R 来说通常如此,除了 Windows,但例如 macOS 用户可能需要将 /usr/local/bin 添加到其路径中),请使用

#! /usr/bin/env Rscript
...

至少在 Bourne 和 bash shell 中,#! 机制 不允许 额外的参数,例如 #! /usr/bin/env Rscript --vanilla

需要考虑的一件事是 stdin() 指的是什么。用类似以下片段编写 R 脚本很常见

chem <- scan(n=24)
2.90 3.10 3.40 3.40 3.70 3.70 2.80 2.50 2.40 2.40 2.70 2.20
5.28 3.37 3.03 3.03 28.95 3.77 3.40 2.20 3.50 3.60 3.70 3.70

并且 stdin() 指的是脚本文件,以允许此类传统用法。如果您想引用进程的 stdin,请使用 "stdin" 作为 file 连接,例如 scan("stdin", ...)

编写可执行脚本文件(由 François Pinard 建议)的另一种方法是使用类似以下内容的 here 文档

#!/bin/sh
[environment variables can be set here]
R --no-echo [other options] <<EOF

   R program goes here...

EOF

但此处 stdin() 指的是程序源,并且 "stdin" 将不可用。

可以通过 -e 标志在命令行上将简短脚本传递给 Rscript via。(不接受空脚本。)

请注意,在类似 Unix 的系统上,输入文件名(例如 foo.R)不应包含空格或 shell 元字符。


附录 C 命令行编辑器

C.1 准备工作

当在 UNIX 下为编译配置 R 时,如果 GNU readline 库可用,将使用一个内置命令行编辑器,允许召回、编辑和重新提交之前的命令。请注意,readline 的其他版本存在,并且可能被内置命令行编辑器使用:这在 macOS 上最常见。您可以在 R 会话中运行 extSoftVersion() 来找出哪个版本(如果有)可用。

可以使用启动选项 --no-readline 禁用它(对于与 ESS 一起使用很有用 25)。

R 的 Windows 版本具有更简单的命令行编辑:请参阅 GUI 的“帮助”菜单下的“控制台”,以及 README.Rterm 文件,了解 Rterm.exe 下的命令行编辑。

在将 R 与 GNU26 readline 功能一起使用时,可以使用下面描述的函数,以及系统上的 man readlineinfo readline 中(可能)记录的其他函数。

其中许多函数使用 Control 或 Meta 字符。Control 字符(如 Control-m)是通过在按 m 键的同时按住 CTRL 获得的,在下面写为 C-m。Meta 字符(如 Meta-b)是通过按住 META27 并按 b 获得的,在下面写为 M-b。如果您的终端未启用 META 键,您仍可以使用以 ESC 开头的两个字符序列来输入 Meta 字符。因此,要输入 M-b,您可以键入 ESCbESC 字符序列在具有真实 Meta 键的终端上也是允许的。请注意,Meta 字符区分大小写。

某些但并非所有版本28readline 将识别终端窗口的大小调整,因此最好避免这种情况。

C.2 编辑操作

R 程序会保留您键入的命令行的历史记录,包括错误行,并且历史记录中的命令可以被重新调用、必要时更改,并作为新命令重新提交。在 Emacs 样式的命令行编辑中,在该编辑阶段中进行的任何直接键入操作都会导致字符插入到您正在编辑的命令中,从而取代光标右侧的任何字符。在 vi 模式中,字符插入模式由 M-iM-a 启动,键入字符后,通过键入另一个 ESC 来结束插入模式。(默认是 Emacs 样式,这里仅对此进行描述:有关 vi 模式,请参阅 readline 文档。)

随时按下 RET 命令都会导致重新提交该命令。

其他编辑操作总结在以下表格中。

C.3 命令行编辑器摘要

命令召回和垂直移动

C-p

转到上一个命令(在历史记录中向后)。

C-n

转到下一个命令(在历史记录中向前)。

C-r text

查找包含 text 字符串的最后一个命令。这可以通过 C-g(在某些 R 版本中通过 C-c)取消。

在大多数终端上,您还可以使用向上和向下箭头键分别代替 C-pC-n

光标的水平移动

C-a

转到命令的开头。

C-e

转到行的末尾。

M-b

后退一个单词。

M-f

前进一个单词。

C-b

后退一个字符。

C-f

前进一个字符。

在大多数终端上,您还可以使用向左和向右箭头键分别代替 C-bC-f

编辑和重新提交

文本

在光标处插入 文本

C-f 文本

在光标后追加 文本

DEL

删除前一个字符(光标左侧)。

C-d

删除光标下的字符。

M-d

删除光标下单词的其余部分,并“保存”它。

C-k

从光标删除到命令末尾,并“保存”它。

C-y

在此处插入(yank)最后“保存”的文本。

C-t

将光标下的字符与下一个字符互换。

M-l

将单词的其余部分更改为小写。

M-c

将单词的其余部分更改为大写。

RET

重新向 R 提交命令。

最后的 RET 终止命令行编辑序列。

readline 键绑定可以通过通常的方式 via ~/.inputrc 文件进行自定义。这些自定义可以有条件地应用于应用程序 R,即通过包含类似于以下内容的部分

$if R
  "\C-xd": "q('no')\n"
$endif

附录 D 函数和变量索引

跳转到:  -   :   !   ?   .   *   /   &   %   ^   +   <   =   >   |   ~  
A   B   C   D   E   F   G   H   I   J   K   L   M   N   O   P   Q   R   S   T   U   V   W   X  
索引条目章节

-
-向量算术

:
:生成常规序列
::命名空间
:::命名空间

!
!逻辑向量
!=逻辑向量

?
?获取帮助
??获取帮助

.
.更新拟合模型
.First自定义环境
.Last自定义环境

*
*向量算术

/
/向量算术

&
&逻辑向量
&&条件执行

%
%*%乘法
%o%两个数组的外积

^
^向量算术

+
+向量算术

<
<逻辑向量
<<-范围
<=逻辑向量

=
==逻辑向量

>
>逻辑向量
>=逻辑向量

|
|逻辑向量
||条件执行

~
~统计模型的公式

A
abline低级绘图命令
ace一些非标准模型
add1更新拟合模型
anova用于提取模型信息的泛型函数
anovaANOVA 表格
aov方差分析和模型比较
aperm数组的广义转置
arrayarray() 函数
as.data.frame制作数据框
as.vector使用数组的连接函数 c()
attachattach() 和 detach()
attr获取和设置属性
attr获取和设置属性
属性获取和设置属性
属性获取和设置属性
avas一些非标准模型
axis低级绘图命令

B
boxplot单样本和双样本检验
break重复执行
bruto一些非标准模型

C
c向量和赋值
c字符向量
c使用数组的连接函数 c()
c连接列表
C对比
cbind形成分区矩阵
coef用于提取模型信息的泛型函数
系数用于提取模型信息的泛型函数
contour显示图形
contrasts对比
coplot显示多元数据
cos向量算术
crossprod索引矩阵
crossprod乘法
cut因子的频率表

D
data访问内置数据集
data.frame制作数据框
density检查数据集的分布
det奇异值分解和行列式
detachattach() 和 detach()
行列式奇异值分解和行列式
dev.list多个图形设备
dev.next多个图形设备
dev.off多个图形设备
dev.prev多个图形设备
dev.set多个图形设备
deviance用于提取模型信息的泛型函数
diag乘法
dim数组
dotchart显示图形
drop1更新拟合模型

E
ecdf检查数据集的分布
edit编辑数据
eigen特征值和特征向量
else条件执行
Error方差分析和模型比较
example获取帮助
exp向量算术

F
F逻辑向量
factor因子
FALSE逻辑向量
fivenum检查数据集的分布
for重复执行
formula用于提取模型信息的泛型函数
function编写自己的函数

G
getAnywhere对象定向
getS3method对象定向
glmglm() 函数

H
help获取帮助
help获取帮助
help.search获取帮助
help.start获取帮助
hist检查数据集的分布
hist显示图形

I
identify与图形交互
if条件执行
if条件执行
ifelse条件执行
image显示图形
is.na缺失值
is.nan缺失值

J
jpeg设备驱动程序

K
ks.test检查数据集的分布

L
legend低级绘图命令
length向量算术
length内在属性 mode 和 length
levels因子
lines低级绘图命令
list列表
lm线性模型
lme一些非标准模型
locator与图形交互
loess一些非标准模型
loess一些非标准模型
log向量算术
lqs一些非标准模型
lsfit最小二乘拟合和 QR 分解

M
mars一些非标准模型
max向量算术
mean向量算术
methods对象定向
min向量算术
mode内在属性 mode 和 length

N
NA缺失值
NaN缺失值
ncol矩阵工具
next重复执行
nlm非线性最小二乘和最大似然模型
nlm最小二乘法
nlm最大似然
nlme一些非标准模型
nlminb非线性最小二乘和最大似然模型
nrow矩阵工具

O
optim非线性最小二乘和最大似然模型
order向量算术
ordered有序因子
ordered有序因子
outer两个数组的外积

P
pairs显示多元数据
parpar() 函数
paste字符向量
pdf设备驱动程序
persp显示图形
plot用于提取模型信息的泛型函数
plotplot() 函数
pmax向量算术
pmin向量算术
png设备驱动程序
低级绘图命令
多边形低级绘图命令
PostScript设备驱动程序
预测用于提取模型信息的泛型函数
打印用于提取模型信息的泛型函数
乘积向量算术

Q
qqline检查数据集的分布
qqline显示图形
qqnorm检查数据集的分布
qqnorm显示图形
qqplot显示图形
qr最小二乘拟合和 QR 分解
quartz设备驱动程序

R
范围向量算术
rbind形成分区矩阵
read.tableread.table() 函数
rep生成常规序列
重复重复执行
残差用于提取模型信息的泛型函数
残差用于提取模型信息的泛型函数
rlm一些非标准模型
rm数据持久性和删除对象

S
扫描scan() 函数
sdtapply() 函数和参差不齐的数组
搜索管理搜索路径
seq生成常规序列
shapiro.test检查数据集的分布
sin向量算术
sink从文件执行命令或将输出重定向到文件
求解线性方程和求逆
排序向量算术
从文件执行命令或将输出重定向到文件
拆分重复执行
sqrt向量算术
stem检查数据集的分布
step用于提取模型信息的泛型函数
step更新拟合模型
求和向量算术
摘要检查数据集的分布
摘要用于提取模型信息的泛型函数
svd奇异值分解和行列式

T
T逻辑向量
t数组的广义转置
t.test单样本和双样本检验
表格索引矩阵
表格因子的频率表
tan向量算术
tapplytapply() 函数和参差不齐的数组
文本低级绘图命令
标题低级绘图命令
一些非标准模型
TRUE逻辑向量

U
取消类对象的类
更新更新拟合模型

V
var向量算术
vartapply() 函数和参差不齐的数组
var.test单样本和双样本检验
vcov用于提取模型信息的泛型函数
向量向量和赋值

W
while重复执行
wilcox.test单样本和双样本检验
windows设备驱动程序

X
X11设备驱动程序


附录 E 概念索引

跳转到:  A   B   C   D   E   F   G   I   K   L   M   N   O   P   Q   R   S   T   U   V   W  
索引条目章节

A
访问内置数据集访问内置数据集
加法模型一些非标准模型
方差分析方差分析和模型比较
算术函数和运算符向量算术
数组数组
赋值向量和赋值
属性对象

B
二元运算符定义新的二元运算符
箱形图单样本和双样本检验

C
字符向量字符向量
对象的类
对象定向
连接列表连接列表
对比对比
控制语句控制语句
CRAN贡献的包和 CRAN
自定义环境自定义环境

D
数据框数据框
默认值命名参数和默认值
密度估计检查数据集的分布
行列式奇异值分解和行列式
转移输入和输出从文件执行命令或将输出重定向到文件
动态图形动态图形

E
特征值和特征向量特征值和特征向量
经验 CDF检查数据集的分布

F
因子因子
因子对比
公式统计模型的公式

G
广义线性模型广义线性模型
数组的广义转置数组的广义转置
通用函数对象定向
图形设备驱动程序设备驱动程序
图形参数par() 函数
分组表达式分组表达式

I
数组的索引和按数组索引数组索引
索引向量索引向量

K
Kolmogorov-Smirnov 检验检查数据集的分布

L
最小二乘拟合最小二乘拟合和 QR 分解
线性方程线性方程和求逆
线性模型线性模型
列表列表
局部逼近回归一些非标准模型
循环和条件执行循环和条件执行

M
矩阵数组
矩阵乘法乘法
最大似然最大似然
缺失值缺失值
混合模型一些非标准模型

N
命名参数命名参数和默认值
命名空间命名空间
非线性最小二乘非线性最小二乘和最大似然模型

O
对象定向对象定向
对象对象
单样本和双样本检验单样本和双样本检验
有序因子因子
有序因子对比
数组的外积两个数组的外积

P
R 和统计
概率分布概率分布

Q
QR 分解最小二乘拟合和 QR 分解
分位数-分位数图检查数据集的分布

R
从文件中读取数据从文件中读取数据
循环利用规则向量算术
循环利用规则循环利用规则
正则序列生成常规序列
删除对象数据持久性和删除对象
稳健回归一些非标准模型

S
范围范围
搜索路径管理搜索路径
Shapiro-Wilk 检验检查数据集的分布
奇异值分解奇异值分解和行列式
统计模型R 中的统计模型
Student’s t 检验单样本和双样本检验

T
制表因子的频率表
基于树的模型一些非标准模型

U
更新拟合模型更新拟合模型

V
向量简单的数字和向量操作

W
Wilcoxon 检验单样本和双样本检验
工作区数据持久性和删除对象
编写函数编写自己的函数


附录 F 参考文献

D. M. Bates 和 D. G. Watts (1988),非线性回归分析及其应用。 John Wiley & Sons,纽约。

Richard A. Becker、John M. Chambers 和 Allan R. Wilks (1988),新 S 语言。 Chapman & Hall,纽约。这本书通常被称为“蓝皮书”。

John M. Chambers 和 Trevor J. Hastie 编辑。(1992),S 中的统计模型。 Chapman & Hall,纽约。这也称为“白皮书”。

John M. Chambers (1998) 用数据编程。施普林格,纽约。这也称为“绿皮书”。

A. C. Davison 和 D. V. Hinkley (1997),自举法及其应用,剑桥大学出版社。

Annette J. Dobson (1990),广义线性模型简介,Chapman and Hall,伦敦。

Peter McCullagh 和 John A. Nelder (1989),广义线性模型。第二版,Chapman and Hall,伦敦。

John A. Rice (1995),数理统计和数据分析。第二版。Duxbury Press,贝尔蒙特,加利福尼亚州。

S. D. Silvey (1970),统计推断。企鹅,伦敦。


脚注

(1)

ACM 软件系统奖,1998 年:https://awards.acm.org/award_winners/chambers_6640862.cfm

(2)

对于可移植的 R 代码(包括在 R 包中使用的代码),只应使用 A–Za–z0–9。

(3)

在字符串中,也不在函数定义的参数列表中

(4)

有些控制台不允许您输入更多内容,而允许输入的控制台中,有些会静默地丢弃多余内容,有些会将其用作下一行的开头。

(5)

长度不受限制。

(6)

此文件名中的前导“点”使其在 UNIX 中的普通文件列表中和 macOS 和 Windows 上的默认 GUI 文件列表中不可见

(7)

对于非向量类型的参数,例如 list 模式参数,c() 的操作相当不同。请参阅 连接列表

(8)

实际上,在执行任何其他语句之前,它仍然可用作 .Last.value

(9)

paste(..., collapse=ss) 将参数连接成一个单一的字符字符串,在它们之间放置 ss,例如,ss <- "|"。还有更多用于字符操作的工具,请参阅 subsubstring 的帮助。

(10)

numeric 模式实际上是两种不同模式的混合,即 integerdouble 精度,如手册中所述。

(11)

但请注意,length(object) 并不总是包含有用的内在信息,例如,当 object 是一个函数时。

(12)

通常,从数字到字符再返回的转换不会完全可逆,因为字符表示中存在舍入误差。

(13)

methods 中提供了使用“形式”或“S4”类的不同样式。

(14)

读者应注意,澳大利亚有八个州和领地,即澳大利亚首都领地、新南威尔士州、北领地、昆士兰州、南澳大利亚州、塔斯马尼亚州、维多利亚州和西澳大利亚州。

(15)

请注意,当 tapply() 的第二个参数不是因子时,它也能工作,例如,“tapply(incomes, state)”,对于很多其他函数来说也是如此,因为必要时参数会强制转换为因子(使用 as.factor())。

(16)

请注意,x %*% x 是模棱两可的,因为它可能表示 x’x 或 x x’,其中 x 是列形式。在这种情况下,较小的矩阵似乎是隐式采用的解释,因此标量 x’x 在这种情况下是结果。矩阵 x x’ 可以通过 cbind(x) %*% xx %*% rbind(x) 计算,因为 rbind()cbind() 的结果始终是矩阵。但是,计算 x’x 或 x x’ 的最佳方法分别是 crossprod(x)x %o% x

(17)

更好的方法是形成一个矩阵平方根 B,其中 A = BB’,并找出 By = x 的解的平方长度,也许可以使用 A 的 Cholesky 或特征值分解。

(18)

有关第二个术语的含义,请参阅 autoload 的在线帮助。

(19)

在 UNIX 下,可以使用实用程序 sedawk

(20)

稍后讨论,或从包 lattice 中使用 xyplot

(21)

另请参阅 R 中的统计模型 中描述的方法

(22)

从某种意义上来说,这模仿了 S-PLUS 中的行为,因为在 S-PLUS 中,此运算符始终创建或赋值给全局变量。

(23)

因此它在 UNIX 下是隐藏的。

(24)

某些图形参数(例如当前设备的大小)仅供参考。

(25)

“Emacs Speaks Statistics” 包;请参阅 URL https://ESS.R-project.org/

(26)

可以使用 GNU readline 的仿真来构建 R,例如基于 NetBSD 的 editline(也称为 libedit),在这种情况下,可能只提供部分功能。

(27)

在 PC 键盘上,这通常是 Alt 键,有时是“Windows”键。在 Mac 键盘上,通常没有元键可用。

(28)

特别是,不是 6.3 或更高版本:这是从 R 3.4.0 开始解决的。