全部版块 我的主页
论坛 提问 悬赏 求职 新闻 读书 功能一区 藏经阁
15507 61
2013-06-24
R.jpg      





【活动方式】
试读本书章节,写下你对这本书的读后感(不少于50字)
【活动时间】
2013年6月24日-7月9日
【本期奖品】

前5名会员赠送价值69.00元《R语言编程艺术》

后5名赠送价值50元言商书局电子阅读卡(以卡号、密码形式赠送)

【评奖单位】

       由人大经济论坛与机械工业出版社华章分社共同评选,结果将于710日以后于本帖公布


【内容简介】本书将带领你踏上R语言软件开发之旅,从最基本的数据类型和数据结构开始,到闭包、递归和匿名函数等高级主题,由浅入深,讲解细腻,读者完全不需要统计学的知识,甚至不需要编程基础。书中提到的很多高级编程技巧,是作者多年编程经验的总结,对有经验的开发者也大有裨益。本书精选了44个扩展案例,这些案例都源自于作者亲身参与过的咨询项目,都是与数据分析相关的,生动展示了R语言在统计学中的高效应用。 本书核心内容:

l   R语言的完整语法以及R语言的编程技巧。

l   创建精美图形来展示复杂数据和函数。

l   使用并行计算和向量化的方法编写更高效的代码。

l   使用R语言对C/C++Python的接口来提高计算速度或增加功能。

l   文本分析、图像处理等领域新的R包。

l   使用高级调试技巧清除代码里恼人的错误。

l   包含许多扩展案例,展示完整的、特定用途的函数,并针对同一个问题讨论了不同的设计方案。

l   在恰当的时候介绍R语言与其他语言的差异,为那些了解其他语言的开发人员提供参考。 这本书涵盖了R语言编程的诸多方面,尤其在面向对象编程、程序调试、提升程序运行速度以及并行计算等方面,填补了同类图书的空白。关于程序调试的章节更是作者多年经验的总结。不管是初学者还是有一定编程经验的读者,阅读这本书都会有所收获。——统计之都


中奖名单:

1.赠送书籍:美髯客fawziya

2.赠送电子阅读卡:ahterry,食神扶夏,sunqinggang

请中奖者尽快通过站内短信告知收件人、收件地址、邮编、联系电话、邮箱,我们将安排邮寄。谢谢!

PS:请于7月25日前联系,否则视为放弃领奖。



二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

全部回复
2013-6-24 09:21:29
译 者 序
    R是一种用于统计计算与做图的开源软件,同时也是一种编程语言,它广泛应用于企业和学术界的数据分析领域,正在成为最通用的语言之一。由于近几年数据挖掘、大数据等概念的走红,R也越来越多地被人关注。截至本文完成之日,CRAN(http://cran.r-project.org/)上共有4383个包,涉及统计、化学、经济、生物、医学、心理、社会学等各个学科。不同类型的公司,比如Google、辉瑞、默克、美国银行、洲际酒店集团和壳牌公司都在使用它,同时以S语言环境为基础的R语言由于其鲜明的特色,一出现就受到了统计专业人士的青睐,成为国外大学里相当标准的统计软件。
    一直以来,国内外关于R语言的著作都是以统计学专业的视角来介绍R语言的,对R语言本身的特性讲解得并不详尽,而软件自带的官方文档又显得过于技术,不那么亲民。另一方面,很多接触R的朋友都来自非计算机专业,没有接受过编程训练,他们使用R的时候,编写出来的代码通常只能算是一条条命令的集合,面对更复杂的问题,常常束手无策。记得在某届R语言大会上,有位SAS阵营的朋友说,他看到演讲者所展示的代码里只有函数调用,没有编程的东西,所以他觉得R不能算一种编程语言。其实,他错了,此时你手里这本书,覆盖了其他大部分R语言图书没有涉及的编程主题。这本书就如同R语言的九阳神功秘籍,当神功练成,任督二脉一旦打通,再学习针对某一领域应用的函数或包就如庖丁解牛一般。顺便提一下,据微博上的小道消息,前面提到的那位朋友最近也开始学R了。
    本书的特点表现在以下几个方面:
    第一,对读者的统计学知识和编程水平要求并不高。与很多R语言书籍不同,这本书并不需要很深的统计学功底,它从纯语言的角度入手来讲解R。对于有一定编程经验却没什么统计学背景的人来说,读这本书会比较顺畅,读者就可以重点关注R语言的特性在数据分析方面的应用。在有的地方,作者也会提醒那些有其他语言编程经验的人应该注意R语言有什么不同之处。而对于没有编程经验又想使用R做数据分析的人来说,这本书也是学习编程的绝佳教材。
    第二,专注于R语言编程。作者没有把这本书定位为菜谱式的手册,也不像有些R语言图书那样介绍完统计学某方面应用之后简单地把R语言代码摆出来。翻开这本书的目录,你几乎看不到统计学的术语。本书系统介绍了R语言的各种数据结构和编程结构、面向对象编程方法、socket网络编程、并行计算、代码调试、程序性能提升以及R语言与其他语言的接口等主题。书中也提到了不少编程的小技巧,这都是作者多年编程经验的总结。
    第三,丰富的案例分析。作者Matloff教授是位计算机科学家,同时也是位统计学家,有多年的教学经验,也做过统计学方法论的顾问。除了正文中的例子之外,本书还有44个扩展案例,很多案例源自作者亲身参与过的咨询项目。虽然本书没有讲解任何统计模型,但是扩展案例都是和数据分析相关的,比如对鲍鱼数据的重新编码(第2章)、寻找异常值(第3章)、文本词汇索引(第4章)、学习中文方言的辅助工具(第5章)等。通过学习这些案例,读者不仅能学到R语言的每种概念如何运作,也会学到如何把这些概念组合到一起成为有用的程序。比如第10章介绍了socket网络编程之后,就用一个扩展案例讲解如何用socket实现并行计算,这为第16章详细讲解并行计算做好了铺垫。在很多案例里,作者讨论了好几种设计方案,并比较了这几种方案的不同之处,以回答“为什么这样做”,这对于缺少编程经验的人来说,是非常好的安排。
    本书第1章简要介绍了R语言的几种数据结构和编程基础,其余章节可分为三大部分。
    第一部分(第2~6章)详细介绍R的几种主要的数据结构:向量、矩阵、列表、数据框和因子等。对很多人来说,R复杂多变的数据结构真的是一只拦路虎。而本书从最简单的向量开始,一步一步引导读者认识并掌握各种数据结构。
    第二部分(第7~13章)涉及编程方面: 编程结构和面向对象特性、输入/输出、字符串处理以及绘图。值得一提的是第13章,这章主要讲解的是R语言的调试。很多朋友在实际工作中有这样的经历,你可能用了一个小时就写好代码,却用了一天的时间来调试。可是到目前为止还没有在其他图书上看到与R语言调试相关的内容,甚至也很少见到关于其他编程语言调试的图书。本书刚好填补了这方面的空白。如果读者仔细读完第13章,并实践其中的调试技巧,一定能事半功倍,也就能少熬点儿夜,有延长寿命的功效。本书的作者同时也著有《调试的艺术》(The Art of Debugging),相信他在R语言调试方面的功力也是相当深厚的。
    第三部分(第14~16章)介绍的是更高级的内容,比如执行速度和性能的提升(第14章)、R语言与C/C++或Python混合编程(第15章)以及R语言并行计算(第16章) ,虽然最后一部分属于编程的高级内容,但如果读者从前往后一直学下来,随着能力的提高,也是可以读懂的。
    本人从2007年开始接触R语言,那时候市面上几乎没有R语言方面的书籍。当时我关于R语言的所有信息几乎都是来自统计之都(http://cos.name)和谢益辉的博客(http://yihui.name)。2008年冬天,统计之都成功举办了“第一届中国R语言会议”,来自各地的R语言用户们齐聚一堂,交流心得。从那以后,每年的R语言会议都会在北京和上海举办。这几年来,统计之都的队伍也逐渐壮大,比如本书的其他三位主要译者:邱怡轩、潘岚锋和熊熹,当年他们参加R语言会议的时候还是人大统计学院大一、大二的学生,后来也成为R语言社区的领军人物。去年我们接到本书的翻译任务时,他们三人分别收到了美国普度大学、爱荷华州立大学以及明尼苏达大学的录取通知,现在已经在美国留学深造。希望有越来越多的人加入统计之都的大家庭,和大家一起成长,为中国统计事业的发展尽自己的一份力。
    在翻译过程中,几位译者力求忠实于原文,但纠正了原书的几处错误,同时也兼顾中文表达的流畅,不过译文中可能仍有不当之处,欢迎读者予以指正。
    除了本人以及前面提到的三位译者之外,统计之都的三位老朋友林宇、严紫丹和程豪也参与了本书部分章节的校审和初稿翻译,在此表示感谢。全书译文最后由本人统稿,如有错误之处,均由本人承担。
    也感谢机械工业出版社的吴怡编辑,她给予了我们细心的帮助。
    统计之都的图书出版栏目(网址是http://cos.name/books/ )有本书的页面,读者可以在这里下载本书的数据和代码,也可以留言提问。

译者简介
    本书三位主要译者都是统计之都(http://cos.name)的管理员、中国R语言会议理事会成员。
    陈堰平毕业于中国人民大学统计学院,现任国家金融信息中心数据中心研发部负责人,从事指数编制、指数化投资、金融衍生品方面的工作,对金融数据分析有多年的研究,博客网址为http://yanping.me。邱怡轩是普度大学统计系在读博士研究生,开发过rarpark、R2SWF、Layer等R语言程序包,博客网址为http://yixuan.cos.name/。潘岚峰是爱荷华州立大学统计系在读博士研究生,开发过R语言程序包bignmf。

陈堰平
2013年3月于新华通讯社第三工作区




前  言
    R是一种用于数据处理和统计分析的脚本语言,它受到由AT&T实验室开发的统计语言S的启发,且基本上兼容于S语言。S语言的名称代表统计学(statistics),用来纪念AT&T开发的另一门以一个字母命名的编程语言,这就是著名的C语言。后来一家小公司买下了S,给它添加了图形用户界面并命名为S-Plus。
    由于R是免费的,而且有更多的人贡献自己的代码,R语言变得比S和S-Plus更受欢迎。R有时亦称为GNU S,以反映它的开源属性。(GNU项目是开源软件的一个重要集合。)
为什么在统计工作中用R
    粤语有个词“又便又靓”,意思是“物美价廉”,R语言就是这样一种工具,为什么还要用别的呢?
    R语言有许多优点:
    它是广受关注的统计语言S在公众领域的实现,而且R/S已经是专业统计学家的实际标准语言。
    在绝大多数情况下,它的功能不亚于甚至优于商业软件,比如它有大量的函数、良好的可编程性、强大的绘图功能,等等。
    在Windows、Mac、Linux等操作系统上都有相应的版本。
    除了提供统计操作以外,R还是门通用编程语言,所以你可以用它做自动分析、创建新的函数来拓展语言的现有功能。
    它结合了面向对象语言和函数式编程语言的特性。
    系统在两次会话之间可以保存数据集,所以不需要每次重新加载数据集。R还可以保存历史命令。
    因为R是开源软件,所以很容易从用户社区获得帮助。另外,用户们贡献了大量的新函数,其中很多用户都是杰出的统计学家。
    我必须事先提醒你,最好直接在终端窗口输入命令并提交给R,而不是在GUI里用鼠标点击菜单,并且大多数R用户都不用GUI。这并不是说R不能图形化操作。相反,它有很多工具可以生成实用、美观的图形,不过这些工具是用在系统输出方面,比如画图,而不是用在输入方面。
    如果你离不开GUI,则可以选用一种免费的GUI,它们是为R开发的,比如下面几种开源的或免费的工具:
    RStudio,http://www.rstudio.org/
    StatET,http://www.walware.de/goto/statet/
    ESS (Emacs Speaks Statistics),http://ess.r-project.org/
    R Commander:John Fox,“The R Commander: A Basic-Statistics Graphical Interface to R,” Journal of     Statistical Software 14, no. 9 (2005):1–42.
    JGR (Java GUI for R),http://cran.r-project.org/web/packages/JGR/index.html
    前三种软件,RStudio、StatET和 ESS属于集成开发环境(Integrated Development Environments, IDE),更多地是为编程设计的。StatET和ESS则为R程序员分别提供了针对著名的Eclipse 和Emacs环境的IDE。
    在商业软件中,另一种IDE出自Revolution Analytics公司,一家提供R语言服务的公司(http://www.revolutionanalytics.com/)。
    因为R是一种编程语言而不是各种不相关联的命令汇总,你可以把几个命令组合起来使用,每条命令用前一条命令的输出作为输入。(Linux用户可能会认出:这类似于用管道将shell命令串联起来。)这种组合R函数的能力带来了巨大的灵活性,如果使用恰当,功能会非常强大。
    下面是个简单的例子,请看这条命令:
    nrow(subset(x03,z == 1))
    首先,subset()函数针对数据框x03提取出变量z(取值为1)的所有记录,得到一个新的数据框,再把这个新数据框代入nrow()函数。这个函数计算数据框的行数。这行命令的最终效果是给出原数据框中z=1的记录的个数。
    之前提到过面向对象编程和函数式编程这两个术语。这两个主题会激起计算机科学家的兴趣,尽管它们对大多数读者来说可能有点陌生,但是它们跟任何使用R做统计编程的人都有关。下面概述这两个主题。
面向对象编程
    面向对象的优点可以用例子来解释,例如回归模型。当你用SAS、SPSS等其他统计软件做回归分析时,你会在屏幕上看到一大堆的输出结果。与之相反,如果在R里调用回归函数lm(),函数会返回一个包含所有结果的对象,对象里含有回归系数的估计、估计值的标准差、残差等。接下来你可以用编程的方式挑选对象里需要的部分并提取出来。
    你会看到通过R的方式可使编程变得更容易,部分因为它提供了访问数据的一致性。这种一致性源于R是多态的,即一个函数可以应用于不同类型的输入,函数在运行过程中会选择适当的方式来处理。这样的函数称为泛型函数。(如果你是C++程序员,肯定见过类似的概念虚函数。)
    例如plot()函数,如果你把它应用到一列数上,会得到一幅简单的图。但是如果把它应用到某个回归分析的输出结果中,会得到关于回归分析多个方面的一整套图形。当然,你只能在R生成的对象上使用plot()函数。这样也好,这意味着用户需要记的命令更少了!
函数式编程
    避免显式迭代是R语言的一个常见话题,这对于函数式编程语言来说是很典型的问题。你可以利用R的函数特性把迭代行为表达成隐式的,而不是用循环语句。这可以让代码执行起来更有效率,当R运行在大数据集上时运行时间会相差很大。
    正如你看到的,R语言函数式编程的属性有许多优点:
    更清晰,更紧凑的代码。
    有潜力达到更快的执行速度。
    减少了调试的工作量,因为代码更简单。
    容易转化为并行编程。
本书的读者对象
    许多人以特定的方式使用R——直方图、回归分析或者其他涉及统计运算的任务。不过本书针对的是那些希望用R开发软件的读者。本书的目标读者从专业软件开发人员,到只在大学修过编程课、为了完成特定任务而写R代码的人。(统计学知识一般不是必需的。)
    以下几类人可能会从本书受益:
    受雇于某个需要定期制作统计报告的机构,比如医院、ZF机关等,为此需要开发专用程序。
    开发统计学方法论的学术研究人员,所研究的方法论要么是全新的,要么就是结合了现有的方法并将其整合到一起,这些需要编程来实现,让学术界里更多的人能够使用。
    在市场营销、诉讼支持、新闻、出版等领域工作,需要通过编码来制作复杂图形以实现数据可视化的专家。
    有软件开发经验,参与的项目涉及统计分析的专业程序员。
    学习统计计算课程的学生。
    因此,本书不是R包中各种统计方法的纲要,其实本书更侧重于编程,覆盖了大部分R语言图书没有涉及的与编程相关的主题,我甚至是围绕编程主题展开论述的。下面是一些具体的例子:
    “扩展案例”展示完整的、特定用途的函数,而不是针对某个数据集的独立代码片段。你可能发现其中有些函数对你平常用R语言进行工作很有帮助。通过学习这些案例,你不仅能学到R语言的基本功能如何运作,也会学到如何把它们组合成有用的程序。在很多案例里,我讨论了其他设计方案,以回答“为什么我们这样做?”。
    内容上符合程序员的思维习惯。例如,在讨论数据框的时候,我不仅表明数据框是一种R的列表,也指出这一事实对编程的潜在影响。我在适当的时候加入R语言与其他语言的比较,为那些刚好了解其他语言的人提供参考。
    在任何语言里,调试对于编程都非常关键,而在其他大多数R语言书籍中却鲜有涉及。本书用了一章的篇幅来介绍调试技巧,用“扩展案例”方法真刀真枪地展示如何调试实际工作中的程序。
    如今,多核计算机甚至普及到普通家庭,图形处理单元(GPU)已经悄然在科学计算界引发了一场革命。越来越多的R应用涉及非常大量的计算,并行处理已经成为R程序员面临的主要课题。所以本书用一章的篇幅讨论这个主题,同样给出技术细节和扩展案例。
    本书单独用一章介绍如何利用R内部行为以及其他工具的优势来加速R代码。
    用一章来讨论R语言与其他语言(如C和Python)的接口,用扩展案例展示了应用方法,同时也介绍了调试的技巧。
我的学术背景
    我绕了一个圈才走进R的世界。
    我完成了抽象概率论领域的博士论文之后,在担任统计学教授的前几年,我教过学生、做过研究、也做过统计方法论的顾问。我是加州大学戴维斯分校统计系的创建者之一。
    后来我去了这个学校的计算机科学系,在那里我度过了职业生涯的大部分时间。我研究并行编程、网络流量、数据挖掘、磁盘系统性能以及其他领域。我在计算机科学领域的教学和研究大多都涉及统计学。
    所以我既有纯粹的计算机科学家的视角,也有作为统计学家和统计研究者的视角。我希望这样的综合视角能够使我更好地解读R语言,使本书更具实用价值。




致  谢
    本书很大程度上得益于很多人的帮助和支持。
    首先,也是最重要的,我必须感谢技术审稿人Hadley Wickham先生,他的成名作是ggplot2和plyr这两个包。我曾向No Starch出版社推荐过Hadley,因为除了这两个包之外,他开发的其他包在CRAN(R用户贡献的代码库)上也备受欢迎,可说是经验丰富。正如我期待的那样,Hadley的很多评论为本书增色不少,尤其是他对某些代码示例的评论,通常他都这样开头:“我在想,如果你这么写会怎么样……”。有时这些评论会导致原本只带有一两个版本代码的例子变得要用两三种甚至更多种不同方式来实现编程目的,这样可以比较不同方法的优点和缺点,我相信读者会因此受到启发。
非常感谢Jim Porzak,他是湾区R用户小组(Bay Area useR Group, BARUG,网址http://www.bay-r.org/)的联合创始人,在我写这本书时他曾多次鼓励我。说起BARUG,我必须感谢Jim和另一位联合创始人Mike Driscoll,感谢他们创建了这个充满活力而又富有启发性的论坛。在BARUG,介绍R语言精妙应用的演讲者们经常让我感觉写这本书是个很有价值的项目。BARUG也得益于Revolution Analytics公司的资助以及该公司员工David Smith和Joe Rickert付出的时间、精力,以及奇妙的想法。
Jay Emerson和Mike Kane,CRAN上备受赞誉的bigmemory包的作者,他们通读了第16章的早期文稿,并给出了极富价值的评论。
John Chambers(S语言的缔造者,而S语言是R语言的前身)和Martin Morgan提供了关于R内核的建议,这对我在第14章讨论R的性能问题有很大帮助。
7.8.4节涉及了一个在编程社区很有争议的主题——全局变量的使用。为了有一个更广阔的视角,我征求了几位专家的意见,特别是R核心小组的成员Thomas Lumley和加州大学戴维斯分校计算机科学学院的Sean Davis。当然,这并不意味着他们认可了我在这一节的观点,不过他们的评论非常有用。
在本项目的前期,我写了份非常粗糙的(也是非常不完整的)草稿以供公众评论,后来Ramon Diaz-Uriarte、Barbara F. La Scala、Jason Liao以及我的老朋友Mike Hannon给了我很有帮助的反馈。我的女儿Laura,一名工科学生,阅读了前面部分章节并给出了一些建议,使得本书得以完善。
我自己的CRAN项目以及与R相关的研究(有些成为了本书的示例)得益于许多人的建议、反馈和(或)鼓励,特别是Mark Bravington、Stephen Eglen、Dirk Eddelbuett、Jay Emerson、Mike Kane、Gary King、Duncan Murdoch和Joe Rickert。
R核心小组成员Duncan Temple Lang和我在同一个机构——加州大学戴维斯分校(UCD)。尽管我们在不同的系,以前也没有太多接触,但是这本书也得益于他在这个校园。他帮助UCD创造了一种广泛认可R的文化氛围,这让我能够很容易地向系里证明我用大量的时间写这本书是有价值的。
这本书是我跟No Starch出版社合作的第二个项目。当我决定写这本书的时候,很自然地想到去找No Starch出版社,因为我喜欢他们产品的这种不拘形式的风格、高度实用性和可接受的价格。感谢Bill Pollock同意这个项目,感谢编辑人员Keith Fancher和Alison Law以及自由编辑Marilyn Smith。
最后,但非常重要的是,我要感谢两位美丽、聪明、有趣的女人——我的妻子Gamis和前面提到的Laura,每次她们问我为什么如此埋头工作,我说“我正在写这本R书”,她们都会欣然接受。




目  录
译者序
前 言
致 谢
第1章 快速入门        1
1.1 怎样运行R        1
1.1.1 交互模式        1
1.1.2 批处理模式        2
1.2 第一个R会话        3
1.3 函数入门        5
1.3.1 变量的作用域        7
1.3.2 默认参数        8
1.4 R语言中一些重要的数据结构        8
1.4.1 向量,R语言中的战斗机        8
1.4.2 字符串        9
1.4.3 矩阵        9
1.4.4 列表        10
1.4.5 数据框        12
1.4.6 类        12
1.5 扩展案例:考试成绩的回归分析        13
1.6 启动和关闭R        16
1.7 获取帮助        17
1.7.1 help()函数        18
1.7.2 example()函数        18
1.7.3 如果你不太清楚要查找什么        19
1.7.4 其他主题的帮助        20
1.7.5 批处理模式的帮助        21
1.7.6 互联网资源        21
第2章 向量        22
2.1 标量、向量、数组与矩阵        22
2.1.1 添加或删除向量元素        22
2.1.2 获取向量长度        23
2.1.3 作为向量的矩阵和数组        24
2.2 声明        24
2.3 循环补齐        25
2.4 常用的向量运算        26
2.4.1 向量运算和逻辑运算        26
2.4.2 向量索引        27
2.4.3 用:运算符创建向量        28
2.4.4 使用seq()创建向量        28
2.4.5 使用rep()重复向量常数        29
2.5 使用all()和any()        30
2.5.1 扩展案例:寻找连续出现1的游程        30
2.5.2 扩展案例:预测离散值时间序列        31
2.6 向量化运算符        34
2.6.1 向量输入,向量输出        34
2.6.2 向量输入,矩阵输出        36
2.7 NA与NULL值        37
2.7.1 NA的使用        37
2.7.2 NULL的使用        37
2.8 筛选        38
2.8.1 生成筛选索引        38
2.8.2 使用subset()函数筛选        40
2.8.3 选择函数which()        40
2.9 向量化的ifelse()函数        41
2.9.1 扩展案例:度量相关性        42
2.9.2 扩展案例:对鲍鱼数据集重新编码        44
2.10 测试向量相等        46
2.11 向量元素的名称        47
2.12 关于c()的更多内容        48
第3章 矩阵和数组        49
3.1 创建矩阵        49
3.2 一般矩阵运算        50
3.2.1 线性代数运算        50
3.2.2 矩阵索引        51
3.2.3 扩展案例:图像操作        52
3.2.4 矩阵元素筛选        55
3.2.5 扩展案例:生成协方差矩阵        57
3.3 对矩阵的行和列调用函数        58
3.3.1 使用apply()函数        58
3.3.2 扩展案例:寻找异常值        60
3.4 增加或删除矩阵的行或列        61
3.4.1 改变矩阵的大小        61
3.4.2 扩展案例:找到图中距离最近的一对端点        63
3.5 向量与矩阵的差异        65
3.6 避免意外降维        66
3.7 矩阵的行和列的命名问题        68
3.8 高维数组        68
第4章 列表        71
4.1 创建列表        71
4.2 列表的常规操作        72
4.2.1 列表索引        72
4.2.2 增加或删除列表元素        73
4.2.3 获取列表长度        75
4.2.4 扩展案例:文本词汇索引        75
4.3 访问列表元素和值        78
4.4 在列表上使用apply系列函数        79
4.4.1 lapply()和sapply()的使用        79
4.4.2 扩展案例:文本词汇索引(续)        80
4.4.3 扩展案例:鲍鱼数据        82
4.5 递归型列表        83
第5章 数据框        85
5.1 创建数据框        85
5.1.1 访问数据框        85
5.1.2 扩展案例:考试成绩的回归分析(续)        86
5.2 其他矩阵式操作        87
5.2.1 提取子数据框        87
5.2.2 缺失值的处理        88
5.2.3 使用rbind()和cbind()等函数        89
5.2.4 使用apply()        90
5.2.5 扩展案例:工资研究        90
5.3 合并数据框        92
5.4 应用于数据框的函数        95
5.4.1 在数据框上应用lapply()和sapply()函数        95
5.4.2 扩展案例:应用Logistic模型        95
5.4.3 扩展案例:学习中文方言的辅助工具        96
第6章 因子和表        102
6.1 因子与水平        102
6.2 因子的常用函数        103
6.2.1 tapply函数        103
6.2.2 split()函数        105
6.2.3 by()函数        106
6.3 表的操作        107
6.3.1 表中有关矩阵和类似数组的操作        109
6.3.2 扩展案例: 提取子表        111
6.3.3 扩展案例:在表中寻找频数最大的单元格        113
6.4 其他与因子和表有关的函数        114
6.4.1 aggregate()函数        115
6.4.2 cut()函数        115
第7章 R语言编程结构        116
7.1 控制语句        116
7.1.1 循环        116
7.1.2 对非向量集合的循环        119
7.1.3 if-else结构        120
7.2 算术和逻辑运算符及数值        121
7.3 参数的默认值        122
7.4 返回值        123
7.4.1 决定是否显式调用return ()        124
7.4.2 返回复杂对象        124
7.5 函数都是对象        124
7.6 环境和变量作用域的问题        127
7.6.1 顶层环境        127
7.6.2 变量作用域的层次        128
7.6.3 关于ls()的进一步讨论        131
7.6.4 函数(几乎)没有副作用        131
7.6.5 扩展案例:显示调用框的函数        132
7.7 R语言中没有指针        134
7.8 向上级层次进行写操作        136
7.8.1 利用超赋值运算符对非局部变量进行写操作        136
7.8.2 用assign()函数对非局部变量进行写操作        137
7.8.3 扩展案例:用R语言实现离散事件仿真        138
7.8.4 什么时候使用全局变量        145
7.8.5 闭包        147
7.9 递归        148
7.9.1 Quicksort的具体实现        149
7.9.2 拓展举例:二叉查找树        150
7.10 置换函数        155
7.10.1 什么是置换函数        155
7.10.2 扩展案例:可记录元素修改次数的向量类        156
7.11 写函数代码的工具        158
7.11.1 文本编辑器和集成开发环境        158
7.11.2 edit()函数        158
7.12 创建自己的二元运算符        159
7.13 匿名函数        159
第8章 数学运算与模拟        161
8.1 数学函数        161
8.1.1 扩展例子:计算概率        161
8.1.2 累积和与累积乘积        162
8.1.3 最小值和最大值        162
8.1.4 微积分        163
8.2 统计分布函数        164
8.3 排序        165
8.4 向量和矩阵的线性代数运算        166
8.4.1 扩展示例:向量叉积        169
8.4.2 扩展示例:确定马尔科夫链的平稳分布        170
8.5 集合运算        171
8.6 用R做模拟        173
8.6.1 内置的随机变量发生器        173
8.6.2 重复运行时获得相同的随机数流        175
8.6.3 扩展案例:组合的模拟        175
第9章 面向对象的编程        177
9.1 S3类        177
9.1.1 S3泛型函数        177
9.1.2 实例:线性模型函数lm()中的OOP        178
9.1.3 寻找泛型函数的实现方法        179
9.1.4 编写S3类        181
9.1.5 使用继承        182
9.1.6 扩展示例:用于存储上三角矩阵的类        183
9.1.7 扩展示例:多项式回归程序        187
9.2 S4类        191
9.2.1 编写S4类        191
9.2.2 在S4类上实现泛型函数        193
9.3 S3类和S4类的对比        193
9.4 对象的管理        194
9.4.1 用ls()函数列出所有对象        194
9.4.2 用rm()函数删除特定对象        194
9.4.3 用save()函数保存对象集合        195
9.4.4 查看对象内部结构        196
9.4.5 exists()函数        197
第10章 输入与输出        198
10.1 连接键盘与显示器        198
10.1.1 使用scan()函数        198
10.1.2 使用readline()函数        200
10.1.3 输出到显示器        201
10.2 读写文件        202
10.2.1 从文件中读取数据框或矩阵        202
10.2.2 读取文本文件        203
10.2.3 连接的介绍        203
10.2.4 扩展案例:读取PUMS普查数据        204
10.2.5 通过URL在远程计算机上访问文件        208
10.2.6 写文件        209
10.2.7 获取文件和目录信息        210
10.2.8 扩展案例:多个文件内容的和        211
10.3 访问互联网        211
10.3.1 TCP/IP概述        212
10.3.2 R中的socket        212
10.3.3 扩展案例:实现R的并行计算        213
第11章 字符串操作        216
11.1 字符串操作函数概述        216
11.1.1 grep()        216
11.1.2 nchar()        216
11.1.3 paste()        217
11.1.4 sprintf()        217
11.1.5 substr()        217
11.1.6 strsplit()        217
11.1.7 regexpr()        218
11.1.8 gregexpr()        218
11.2 正则表达式        218
11.2.1 扩展案例:检测文件名的后缀        219
11.2.2 扩展案例:生成文件名        220
11.3 在调试工具edtdbg中使用字符串工具        221
第12章 绘图        224
12.1 创建图形        224
12.1.1 基础图形系统的核心:plot()函数        224
12.1.2 添加线条:abline()函数        225
12.1.3 在保持现有图形的基础上新增一个绘图窗口        226
12.1.4 扩展案例:在一张图中绘制两条密度曲线        227
12.1.5 扩展案例:进一步考察多项式回归        228
12.1.6 添加点:points()函数        231
12.1.7 添加图例:legend()函数        231
12.1.8 添加文字:text()函数        232
12.1.9 精确定位:locator()函数        232
12.1.10 保存图形        233
12.2 定制图形        233
12.2.1 改变字符大小:cex选项        233
12.2.2 改变坐标轴的范围:xlim和ylim选项        234
12.2.3 添加多边形:polygon()函数        235
12.2.4 平滑散点:lowess()和loess()函数        236
12.2.5 绘制具有显式表达式的函数        237
12.2.6 扩展案例:放大曲线的一部分        237
12.3 将图形保存到文件        240
12.3.1 R图形设备        240
12.3.2 保存已显示的图形        241
12.3.3 关闭R图形设备        241
12.4 创建三维图形        241
第13章 调试        243
13.1 调试的基本原则        243
13.1.1 调试的本质:确认原则        243
13.1.2 从小处着手        243
13.1.3 模块化的、自顶向下的调试风格        244
13.1.4 反漏洞        244
13.2 为什么要使用调试工具        244
13.3 使用R的调试工具        245
13.3.1 利用debug()和browser()函数进行逐步调试        245
13.3.2 使用浏览器命令        246
13.3.3 设置断点        246
13.3.4 使用trace()函数进行追踪        247
13.3.5 使用traceback()和debugger()函数对崩溃的程序进行检查        248
13.3.6 扩展案例:两个完整的调试会话        248
13.4 更方便的调试工具        256
13.5 在调试模拟数据的代码时请确保一致性        258
13.6 语法和运行时错误        258
13.7 在R上运行GDB        259
第14章 性能提升:速度和内存        260
14.1 编写快速的R代码        260
14.2 可怕的for循环        260
14.2.1 用向量化提升速度        261
14.2.2 扩展案例:在蒙特卡罗模拟中获得更快的速度        262
14.2.3 扩展案例:生成幂次矩阵        266
14.3 函数式编程和内存问题        267
14.3.1 向量赋值问题        267
14.3.2 改变时拷贝        268
14.3.3 扩展案例:避免内存拷贝        269
14.4 利用Rprof()来寻找代码的瓶颈        270
14.4.1 利用Rprof()来进行监视        270
14.4.2 Rprof()的工作原理        271
14.5 字节码编译        273
14.6 内存无法装下数据怎么办        273
14.6.1 分块        274
14.6.2 利用R软件包来进行内存管理        274
第15章 R与其他语言的接口        275
15.1 编写能被R调用的C/C++函数        275
15.1.1 R与C/C++交互的预备知识        275
15.1.2 例子:提取方阵的次对角线元素        275
15.1.3 编译和运行程序        276
15.1.4 调试R/C程序        277
15.1.5 扩展案例:预测离散取值的时间序列        279
15.2 从Python调用R        281
15.2.1 安装RPy        281
15.2.2 RPy语法        282
第16章 R语言并行计算        284
16.1 共同外链问题        284
16.2 snow包简介        285
16.2.1 运行snow代码        285
16.2.2 分析snow代码        287
16.2.3 可以获得多少倍的加速        287
16.2.4 扩展案例:K均值聚类        288
16.3 借助于C        290
16.3.1 利用多核机器        291
16.3.2 扩展案例:利用OpenMP解决共同外链问题        291
16.3.3 运行OpenMP代码        292
16.3.4 OpenMP代码分析        293
16.3.5 其他OpenMP指令        293
16.3.6 GPU编程        294
16.4 普遍的性能考虑        295
16.4.1 开销的来源        295
16.4.2 简单并行程序,以及那些不简单的        296
16.4.3 静态和动态任务分配        297
16.4.4 软件炼金术:将一般的问题转化为简单并行问题        299
16.5 调试R语言并行计算的代码        299
附录A 安装R        300
附录B 安装和使用包        301
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2013-6-24 09:22:16
第1章
快 速 入 门
如前言所述,R是一种针对统计分析和数据科学的功能全面的开源统计语言。它在商业、工业、政府部门、医药和科研等涉及数据分析的领域都有广泛的应用。
本章将给出R的简单介绍——如何调用、能做什么以及使用什么文件。这里只介绍你在理解后面几章的例子时所需的基础知识,具体的细节将会在后面的章节中加以介绍。
如果你的公司或大学允许,R可能已经安装在你的系统中。如果还没安装,请参考附录A中的安装指南。
1.1 怎样运行R
R可以在两种模式下运行:交互模式和批处理模式。常用的是交互模式。在这种模式下,你键入命令,R将显示结果,然后你再键入新的命令,如此反复进行操作。而批处理模式不需要与用户进行互动。这对于生产工作是非常有帮助的,比如一个程序必须定期重复运行,如每天运行一次,用批处理模式则可以让处理过程自动运行。
1.1.1 交互模式
在Linux或Mac的系统中,只需在终端窗口的命令行中键入R,就可以开始一个R会话。在Windows系统下,点击R图标来启动R。
启动后显示的是欢迎语,以及R提示符,也就是>符号。屏幕的显示内容如下:


现在就可以开始执行R命令了。这时候显示的窗口叫做R控制台。
举个简单例子,考虑一个标准正态分布,其均值为0且方差为1。如果随机变量X服从这个标准正态分布,那么它的取值将以0为中心,或正或负,平均值为0。现在要生成一个新的随机变量Y=|X|。因为我们已经取了绝对值,Y的值将不会以0为中心,并且Y的均值也将是正值。
下面来计算Y的均值。我们的方法基于模拟N(0,1)分布随机变量的取值:


这行代码将会生成100个随机变量,计算它们的绝对值,然后计算它们绝对值的均值。
标签[1]表示这行的第一项是输出结果的第一项。在这个例子中,输出结果只有一行(且只有一项),所以标签[1]显得有点多余。但是当输出结果有很多项会占据很多行时,这种标签会很有帮助。例如,输出结果有两行,且每行最多有6项,则第二行将会以标签[7]开头。


在这里,输出结果有10个数值,举例来说,第二行的标签[7]可以让你快速判断出0.687701是输出结果的第8项。
也可以把R的命令保存在文件里。通常,R代码文件都会有后缀.R或者.r。如果你创建一个名为z.R的文档,可以键入下面的命令来执行该文件中的代码:


1.1.2 批处理模式
有时候自动处理R会话能带来便利。例如,你可能希望运行一个用来绘图的R脚本,而不需要你亲自启动R来执行脚本,这时就要用批处理模式运行R。
举个例子,文件z.R中是绘图的代码,内容如下:


以#标记的部分是注释,它们会被R解释器忽略掉。注释的作用是以更易读的形式来提示代码的用途。
下面一步步讲解前面代码的作用:
调用pdf()函数告诉R我们想把创建的图形保存在PDF文件xh.pdf中。
调用rnorm()函数(rnorm代表random normal)生成100个服从N(0,1)分布的随机变量。
对这些随机变量调用hist()函数生成直方图。
调用dev.off()函数关闭正在使用的图形“设备”,也就是本例中的xh.pdf文件。这就是实际上把文件写入磁盘的机制。
我们可以自动运行上面的代码,而不用进入R的交互模式,只需要调用一条操作系统shell命令(例如通常在Linux系统中使用的$命令提示符)来调用R:


用PDF阅读器打开保存的文件,可看到直方图(这里展示的只是简单的不加修饰的直方图,R可以生成更加复杂的图形),这表明上面的代码已执行。
1.2 第一个R会话
用数字1、2、4生成一个简单的数据集(用R的说法就是“向量”),将其命名为x:


R语言的标准赋值运算符是<-。也可以用=,不过并不建议用它,因为在有些特殊的情况下它会失灵。注意,变量的类型并不是固定不变的。在这里,我们把一个向量赋值给x,也许之后会把其他类型的值赋给它。我们会在1.4节介绍向量和其他类型。
c表示“连接”(英文是concatenate)。在这里,我们把数字1、2、4连接起来。更精确地说,连接的是分别包含三个数字的三个一元向量。这是因为可以把任何数字看作一元向量。
接下来我们也可以这样做:


这样就把q赋值为(1,2,4,1,2,4,8)(没错,还包括了x的副本)。
我们来确认一下数据是不是真的在x中。要在屏幕上打印向量,只需直接键入它的名称。如果你在交互模式下键入某个变量名(或更一般的,某个表达式),R就会打印出变量的值(或表达式的值)。熟悉其他语言(比如Python)的程序员会觉得这个特性很熟悉。例如,输入下面的命令:


果然,x包含数字1、2、4。
向量的个别元素靠[ ]来访问。下面来看看如何打印x的第三个元素:


正如在其他语言里一样,称选择器(这里的3)为索引(index)或者下标(subscript)。这些概念与ALGOL家族的语言(比如C和C++)类似。值得注意的是,R向量的元素的索引(下标)是从1开始的,而非0。
提取子集是向量的一个非常重要的运算。下面是个例子:


表达式x[2:3]代表由x的第2个至第3个元素组成的子向量,在这里也就是2和4组成的子向量。
可以很容易求得本例中数据集的均值和标准差,如下:


这里再次展示了在命令提示符下键入表达式来打印表达式的值。在第一行,表达式调用的是函数mean(x)。函数的返回值会自动打印出来,而不需要调用R的print()函数。
如果想把求得的均值保存在变量里,而不是打印在屏幕上,可以执行下面的代码:


同样,我们来确认一下y是否真的包含x的均值:


正如前面提到过的,我们用#来加上注释,如下:


注释对于写有程序代码的文档是很有价值的,不过在交互式会话中注释也很有用,因为R会记录命令历史(1.6节会讨论这一点)。如果你保存了会话,之后又恢复会话,注释可以帮你回忆起当时在做什么。
最后,我们从R的内置数据集(这些数据集是用来做演示的)里取出一个做些操作。你可以用下面的命令得出一份这些数据集的列表:


其中一个数据集名为Nile,包含尼罗河水流量的数据。我们来计算这个数据集的均值和标准差:


我们还可以画出数据的直方图:


此时会弹出一个包含直方图的窗口,如图1-1所示。这幅图是极其简单的,不过R有各种可选的变量来修饰图形。例如,可以通过设定breaks变量来改变分组;调用hist(z,breaks=12)可以画出数据集z的带有12个分组的直方图;还可以创建更漂亮的标签、改变颜色,以及其他一些改变来创建更有信息量且吸引眼球的图形。当你更熟悉R之后,就有能力构建更复杂、绚丽多彩的精美图形。

图1-1 尼罗河数据的简单展示
最后调用q()函数以退出R(另一种方法是,在Linux中按下快捷键CTRL-D,或者在Mac中按下CMD-D):


最后一句提示是询问你是否希望保存变量以待下次运行时继续处理。如果回答y,则所有对象将会在下次启动R的时候自动加载。这是非常重要的特性,特别是在处理庞大的数据集或很多数据集时。回答y也会保存会话的命令历史。1.6节会继续介绍如何保存工作空间(workspace)和命令历史。
1.3 函数入门
和大多数编程语言一样,R语言编程的核心是编写“函数”。函数就是一组指令的集合,用来读取输入、执行计算、返回结果。
我们先定义一个函数oddcount(),以此简单介绍函数的用法。这个函数的功能是计算整数向量中奇数的个数。一般情况下,我们会用文本编辑器编写好函数代码并保存在文件中,不过在这个简单粗略的例子中,我们只需要在R的交互模式中一行行输入代码。接下来,我们还会在几个测试案例中调用这个函数:


首先,我们告诉R想定义一个名为oddcount的函数,该函数有一个参数x。左花括号引出了函数体的开始部分。本例中,每行写一条R语句。
在函数体结束前,R会用+作为提示符,而不是用平常的>,以提醒用户现在还在定义函数。(实际上,+是续行符号,不是新输入的提示符。)在你键入右花括号来结束函数体之后,R又恢复使用>提示符。
定义完函数之后,本例调用了两次oddcount()函数。由于向量(1,3,5)中有3个奇数,所以调用oddcount(c(1,3,5))的返回值为3。(1,2,3,7,9)有4个奇数,所以第二次调用的返回值为4。
注意,在R中取余数的求模运算符是%%,见上面例子中的注释。例如,38除以7的余数为3。


例如,我们看看下面代码的运行结果:


首先,把x[1]赋值给n,然后测试n是奇数还是偶数。如果像本例中那样,n是奇数,则计数变量k增加。接着把x[2]赋值给n,测试其是奇数还是偶数,以此类推,重复后面的过程。
顺便说一句,C/C++程序员也许会把前面的循环写成这样:


在这里,length(x)是x的元素个数。假设x有25个元素。则1:length(x)就是1:25,意思是依次取1、2、3、……、25。上面的代码也能奏效(除非x的长度为0),但是R语言编程的戒律之一就是要尽可能避免使用循环,如果不能避免,就要让循环更简洁。重新看看这段代码原来的版本:


它更简单清晰,因为我们不需要使用length()函数和数组下标。
在代码的末尾,我们使用了return语句。


这条语句把k的计算结果返回给调用它的代码。不过,直接像下面这样写也可以达到目的:


在没有显式调用return()时,R语言的函数会返回最后计算的值。不过,这个方法必须慎重使用,7.4.1节会详细讨论这个问题。
在编程语言的术语里,x是函数oddcount()的形式参数(英文名称是formal argument或formal parameter,简称“形参”)。在前面例子第一次调用函数时,c(1,3,5)称为实际参数(actual argument,简称“实参”)。这两个术语暗示了这样的事实:函数定义中的x只是个占位符,而c(1,3,5)才是在计算中实际用到的参数。同样,在第二次调用函数时,c(1,2,3,7,9)是实际参数。
1.3.1 变量的作用域
只在函数体内部可见的变量对这个函数来说是“局部变量”。在oddcount()中,k和n都是局部变量。它们在函数返回值以后就撤销了:


需要注意的是,R函数中的形式参数是局部变量,这点非常重要。比如运行下面的命令:


现在,假如oddcount() 的代码改变了x的值,则z的值不会改变。调用oddcount()之后,z的取值还和之前一样。在计算函数调用的取值时,R会把每个实际参数复制给对应的局部参数变量,继而改变那些在函数外不可见的变量的取值。本书第7章将详细介绍“作用域法则”,上面提到的这些只是简单的例子。
全局变量是在函数之外创建的变量,在函数内部也可以访问。下面是个例子:


这里的y就是全局变量。
可以用R的“超赋值运算符”(superassignment operator)<<-在函数内部给全局变量赋值,将在第7章详细介绍。
1.3.2 默认参数
R语言也经常用到“默认参数”。考虑下面这样的函数定义:


如果程序员没有在函数调用时给y设定一个值,则y将初始化为2。同理,z也有默认值TRUE。
现在考虑下面的调用:


这里,数值12是x的实际参数,而且我们接受了y的默认值2,不过我们覆盖了z的默认值,将其设定为FALSE。
上面这个例子也表明:与其他编程语言一样,R语言也有“布尔类型”,包括TRUE和FALSE两个逻辑值。
注意 R语言允许TRUE和FALSE缩写为T和F。不过,如果你有名为T或F的变量,那么为了避免麻烦还是最好不要使用这样的缩写形式。
1.4 R语言中一些重要的数据结构
R有多种数据结构。本节将简单介绍几种常用的数据结构,使读者在深入细节之前先对R语言有个大概的认识。这样,读者至少可以开始尝试一些很有意义的例子,即使这些例子背后更多的细节还需要过一段时间才能揭晓。
1.4.1 向量,R语言中的战斗机
向量类型是R语言的核心。很难想象R语言代码或者R交互式会话可以一点都不涉及向量。
向量的元素必须属于某种“模式”(mode),或者说是数据类型。一个向量可以由三个字符串组成(字符模式),或者由三个整数元素组成(整数模式),但不可以由一个整数元素和两个字符串元素组成。
第2章将详细介绍向量。
标量
标量,或单个的数,其实在R中并不存在。正如前面提到的,单个的数实际上是一元向量。请看下面的命令:


前面提到过,符号[1]表示后面这行的开头是向量的第一个元素,本例中为x[1]。所以可以看出,R语言确实把x当做向量来看,也就是只有一个元素的向量。
1.4.2 字符串
字符串实际上是字符模式(而不是数值模式)的单元素向量。


第一个例子创建了数值向量x,也就是数值模式的。然后创建了两个字符模式的向量:y是单元素(也就是一个字符串)的向量,z由两个字符串组成。
R语言有很多种字符串操作函数。其中有些函数可以把字符串连接到一起或者把它们拆开,比如下面的两个函数:


第11章将会介绍字符串的细节。
1.4.3 矩阵
R中矩阵的概念与数学中一样:矩形的数值数组。从技术层面说,矩阵是向量,不过矩阵还有两个附加的属性:行数和列数。下面是一些例子:


首先,使用函数rbind()(rbind是row bind的缩写,意思是按行绑定)把两个向量结合成一个矩阵,这两个向量是矩阵的行,并把矩阵保存在m中(另一个函数cbind()把若干列结合成矩阵)。然后键入变量名,我们知道这样可以打印出变量,以此确认生成了我们想要的矩阵。最后,计算向量(1,1)和m的矩阵积。你也许已经在线性代数课程中学过矩阵乘法运算,在R语言中它的运算符是*。
矩阵使用双下标作为索引,这点与C/C++非常相似,只不过下标是从1开始,而不是0。


R语言的一个非常有用的特性是,可以从矩阵中提取出子矩阵,这与从向量中提取子向量非常相似。例子如下:


第3章将会详细介绍矩阵。
1.4.4 列表
和R语言的向量类似,R语言中的列表也是值的容器,不过其内容中的各项可以属于不同的数据类型(C/C++程序员可以把它与C语言的结构体做类比)。可以通过两部分组成的名称来访问列表的元素,其中用到了美元符号$。下面是个简单的例子:


表达式x$u指的是列表x中的组件u。列表x还包含另一个组件v。
列表的一种常见用法是把多个值打包组合到一起,然后从函数中返回。这对统计函数特别有用,因为统计函数有时可能有复杂的结果。例如,考虑1.2节提到的R语言中基础直方图函数hist(),为R中内置的尼罗河数据集调用该函数。


这条语句生成了一幅直方图,不过hist()函数同样有返回值,可以这样保存:


hn里面是什么?我们来看看:


现在不要试图去理解上面的所有东西。现在需要知道的是,除了绘制直方图之外,hist()还会返回包含若干个组件的列表。在这里,这些组件描述了直方图的特征。例如,组件breaks告诉我们直方图里的直条从哪里开始到哪里结束,组件counts是每个直条里观测值的个数。
R语言的设计者把hist()返回的信息打包到一个R列表中,这样可以通过美元符号$来访问,并用其他R语言命令进行操作。
要打印hn,也可以直接键入它的变量名:


另一种打印列表的较为简洁方式是使用str()函数:


这里的str代表structure(结构)。这个函数可以显示任何R对象的内部结构,不只限于列表。
1.4.5 数据框
一个典型的数据集包含多种不同类型的数据。例如在一个员工数据集里,可能有字符串数据(比如员工姓名),也可能有数值数据(比如工资)。因此,如有一个50个员工的数据集,其中每个员工有4个变量,虽然这样的数据集看起来像是50行4列的矩阵,但是这在R语言中并不符合矩阵的定义,因为它混合了多种数据类型。
此时应该用数据框,而不是矩阵。R语言中的数据框其实是列表,只不过列表中每个组件是由前面提到的“矩阵”数据的一列所构成的向量。实际上,可以用下面的方式创建数据框:


不过,通常数据框是通过读取文件或数据库来创建的。
本书第5章将详细介绍数据框。
1.4.6 类
R语言是一门面向对象的编程语言。对象是类的实例。类要比你目前见过的数据类型更加抽象。本节将简单介绍S3类的使用。(名称来源于第三代S语言,这是R语言的灵感来源。)大多数R对象都是基于这些类,并且它们非常简单。它们的实例仅仅是R列表,不过还附带一个属性:类名。
例如,前面提到的直方图函数hist()的(非图形)输出是一个包含多个组件的列表,而break和count都是它的组件。它还有一个“属性”(attribute),用来指定列表的类,即histogram类。


读到这里,你可能会产生疑问:“如果S3类的对象都是列表,那为什么还需要类的概念?”答案是,类需要用在泛型函数中。泛型函数代表一个函数族,其中每个函数都有相似的功能,但是适用于某个特定的类。
一个常用的泛型函数是summary()。如果R用户想使用统计函数,如hist(),但是不确定怎样处理它的输出结果(可能会输出很多内容),输出结果不仅仅是个列表,还是个S3类,这时可以对输出结果简单地调用summary()函数。
反过来说,summary()函数实际上是生成摘要的函数族,其中每个函数处理某个特定的类。当你在某个输出结果上调用summary()函数,R会为要处理的类寻找合适的摘要函数,并使用列表的更加友好的方式来展示。因此,对hist()的输出结果调用summary()函数会生成与之相适应的摘要,而对回归函数lm()调用summary()时也会生成与之相适应的摘要。
plot()函数是另一个泛型函数。你可以对任何一个R对象使用plot()函数,R会根据对象的类寻找合适的画图函数。
类也可以用来组织对象。类与泛型函数结合使用,可以开发出灵活的代码,以处理各种不同的但是相关联的任务。第9章将讨论关于类的更深层次的话题。
1.5 扩展案例:考试成绩的回归分析
在接下来的案例中,我们会从头到尾进行一个简单的统计回归分析。这个例子实际上没有多少编程技术,不过它说明了如何使用前面提到的一些数据结构,包括R的S3对象。同样,它在后面的章节里也充当了编程案例的基础。
ExamsQuiz.txt文件包含了我所教班级的成绩。下面是该文件的前几行:


数字表示的是学生成绩的学分绩点。比如绩点3.3对应的就是平常所说的B+。每一行包含的是一个学生的数据,由期中考试成绩、期末考试成绩和平均小测验成绩组成。此例的兴趣点在于用期中考试成绩和平均小测验成绩来预测期末成绩。
先来读入数据文件。


这个数据文件的第一行不是记录的变量名,也就是说没有表头行,所以在函数调用中设定header=FALSE。这是前文提到过的关于默认参数的一个例子。实际上,表头参数的默认值已经是FALSE了(关于这一点,可以在R里查看函数read.table()的在线帮助),所以没必要做前面那样的设定,不过这样做会更明了。
数据现在在examsquiz中,它是数据框类的R对象。


为了检查数据文件刚才是否已读入,查看一下数据的前几行:


由于缺少数据表头行,R自动把列名设置为V1、V2和V3。行号出现在每行的最左边。可能你会觉得数据文件有表头比较好,用有意义的名称(比如Exam1)来标识变量。在后面的例子中,我们通常会设定变量名。
我们来用期中考试成绩(examsquiz的第一列)预测期末考试成绩(examsquiz的第二列):


这里调用lm()函数(lm是linear model的缩写),让R拟合下面的预测方程:
期末考试成绩预测值=β0+β1×期中考试成绩
其中,β0和β1都是用本例的数据估计出来的常数。换句话说,我们用数据中的数对(期中考试成绩,期末考试成绩)拟合了一条直线。拟合过程是用经典的最小二乘法来完成的。(如果你没有相关的背景知识也不用担心。)
注意,存储在数据框第一列的期中考试成绩是用examsquiz[,1]表示,省略了第一维的下标(代表行号)表示我们引用的是数据框的一整列。期末考试也是用类似的方式引用的。这样,我们调用上面的lm()命令,利用examsquiz的第一列来预测第二列。
也可以这样写:


前面提到过,数据框是种各元素都为向量的列表。在这里,各列是列表的组件V1、V2和V3。
lm()的返回结果现在是保存于变量lma中的对象。它是lm类的一个实例。可以调用attributes()函数列出它的所有组件。


和往常一样,调用str(lma)可以得到lma的更详细说明。βi的估计值保存在lma$coefficients中。在命令提示符下键入系数的变量名就可以显示系数。
在键入组件名时也可以使用缩写形式,只要缩写后的组件名不发生混淆即可。例如,如果一个列表由组件xyz、xywa和xbcde构成,则第二个和第三个组件的名称可以分别缩写为xyw和xb。因此我们可以键入下面的命令:


因为lma$coefficients是一个向量,所以比较容易打印。但是当打印对象lma本身的时候是这样的:


为什么R只打印出这些项,而没有打印出lma的其他组件?这个问题的答案是,R在这里使用的print()函数是另一个泛型函数的例子,作为一个泛型函数,print()实际上把打印的任务交给了另一个函数——print.lm(),这个函数的功能是打印lm类的对象,即上面函数展示的内容。
可以用前面讨论过的泛型函数summary()打印输出lma的更详细的内容。它实际上在后台调用了summary.lm(),得出针对某个特定回归模型的摘要:


许多其他泛型函数都是针对这个类定义的。可以查看在线帮助来获取关于lm()的更多细节。(1.7节将讨论如何使用R的在线文档。)
要用期中考试成绩和测验成绩预测期末考试成绩,可以使用记号+。


注意,+号并不表示计算两个量的和。它仅仅是预测变量(predictor variable)的分隔符。
1.6 启动和关闭R
与很多成熟完善的应用软件类似,用户可以在启动文件中自定义R的行为。另外,R可以保存全部或者部分会话,比如记录你用R做过什么,并输出到文件里。如果希望每次开始R会话的时候执行一些R命令,那么你可以把这些命令保存到.Rprofile文件中,并把该文件放置于你个人的主目录或者当前运行R的目录下。当然R搜索.Rprofile文件时会最先搜索后一个目录,这样就可以针对特定的项目进行自定义配置。
例如,要设置调用edit()时R启动的文本编辑器,你可以在.Rprofile文件中加入下面的这一行(如果你使用的是Linux系统):


options()函数用来配置R,也就是调整各种设置。可以使用与你的操作系统相对应的符号(斜杠或反斜杠)来设定编辑器的完整路径。
另一个例子,在我家Linux电脑里的.Rprofile文件中,有这么一行:


这条命令会在R的搜索路径中自动添加一个包含我的全部辅助包的目录。
与大多数程序一样,R也有当前工作目录(current working directory)的说法。如果你使用的是Linux或者Mac系统,当前工作目录就是你启动R时的目录。在Windows中,当前工作目录很可能是“我的文档”目录。如果此时在R会话中引用文件,则会认为文件在那个目录下。可以键入下面的命令查看当前工作目录:


可以调用setwd()函数来修改工作目录,并将目标目录作为参数。例如:


这条命令会把工作目录设置为q。
和在进行交互式R会话时一样,R会把你提交的命令记录下来。当退出R时,R会询问你“是否保存工作空间映像?”,如果你回答“是”,则R会保存你在本次会话中所创建的所有对象,并在下次会话中恢复。这意味着下次你可以从上次停止的地方继续,而不必从头开始。
工作空间保存于名为.Rdata的文件中,该文件位于启动R会话的位置(Linux下)或者R的安装目录下(Windows下)。.Rhistory文件用来记录你之前用过的命令,查看该文件可以帮助你回忆工作空间是如何创建的。
如果想更快地启动或关闭R,那么在启动R时使用vanilla选项可以跳过加载上面那些文件以及结束时保存会话的过程。


其他选项介于vanilla和“加载所有文件”之间。要查找更多关于启动文件的信息,可查询R的在线帮助,如下:


1.7 获取帮助
有很多种资源可以帮你学习关于R的更多知识,其中包括R自身的一些工具,当然,还有网上的资料。
开发者们做了很多工作使R更加自文档化。下面我们将介绍一些R内置的帮助工具,以及互联网上的资源。
1.7.1 help()函数
想获取在线帮助,可调用help()。例如,要获取seq()函数的信息,就键入下面的命令:


调用help()的快捷方式是用问号(?):


在使用help函数时,特殊字符和一些保留字必须用引号括起来。例如,要获取<运算符的帮助信息,必须键入下面的命令:


想查看在线帮助是如何讲解for循环的,要键入:


1.7.2 example()函数
每个帮助条目都附带有例子。R的一个非常好用的特性是,example()函数会为你运行例子代码。示例如下:




sep()函数可以生成多种等差数值序列。运行example(seq)让R展示若干个seq()的例子。
想象一下这对绘图多么有帮助!如果你想看看R的某个绘图函数的功能,example()函数会给你一个“图形化”的演示。
下面的命令将给出一个简单且精美的例子:


它会展示persp()函数的一系列样图。其中一幅如图1-2所示。当你准备浏览下一幅图时,只需在R的控制台中按下回车键。注意,每个例子的代码都会在控制台中显示,所以你可以试着调整参数。

图1-2 persp()函数的一个例子
1.7.3 如果你不太清楚要查找什么
你可以使用help.search()函数在R的文档中进行Google风格的搜索。比如,你需要一个生成多元正态分布的随机变量的函数。为了确定哪个函数能达到目的,你可以尝试使用下面的命令:


我们会得到一个反馈信息,包含下面的摘要:


可以看到,包MASS中的函数mvrnorm()可以完成任务。
help.search()还有个快捷方式:


1.7.4 其他主题的帮助
R的内部帮助文件不仅限于特定函数的页面。例如,前一节提到的函数mvrnorm()在包MASS中。可以键入下面的命令获取这个函数的信息。


键入下面的命令,你还可以得到整个包的信息:


还可以获得一般主题的帮助。例如,如果你对文件感兴趣,就键入下面的命令:


它会给出很多文件操作函数的信息,比如file.create()。
下面是一些其他主题:


你可能会发现浏览这些主题会很有帮助,甚至可以漫无目的地浏览。
1.7.5 批处理模式的帮助
前面提到过,R的批处理命令允许你直接在操作系统的shell中运行命令。要获取某个批处理命令的帮助,可以键入:


例如,要查询INSTALL命令(附录B将介绍)的所有相关选项,可以键入:


1.7.6 互联网资源
在网上有很多关于R的优秀资源。以下是其中部分资源:
R语言主页(http://www.r-project.org/)上提供了R项目的手册,点击Manuals即可浏览。
R语言主页上还列出了多种R语言的搜索引擎,点击Search即可。
R包sos能够对R语言的材料进行精密搜索,可按照附录B的说明来安装R包。
我经常使用RSeek搜索引擎:http://www.rseek.org/
你可以在R语言的邮件列表服务器r-help上发信提问。可以在http://www.r-project.org/mail.html上获取这个以及其他邮件列表的信息。有多种界面可供选择。我喜欢用Gmane (http://www.gmane.org/)。
由于R语言的名称只是一个字母,所以很难在通用搜索引擎(比如Google)上搜索到相关信息。不过还是可以用些技巧来解决。一种方法是使用Google的文件类型准则。比如要搜索关于permutations 的R语言脚本(文件名后缀是.R),输入:


选项-rebol是要求Google排除关于“rebol”的页面,这是因为编程语言REBOL也有相同的后缀。
CRAN(R语言综合资料网,网址为http://cran.r-project.org/)是一个存放用户捐献的R代码的网站,所以这是一个很好的Google搜索词。例如,搜索“lm CRAN”会帮你找到R语言中关于lm()函数的资料。



二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2013-6-24 09:22:50
第2章
向  量
R语言最基本的数据类型是向量(vector)。第1章已经给出了向量的一些例子,本章将详细介绍向量。首先考察向量与R语言的其他数据类型之间的关系。与C语言家族不同,R语言中,单个数值(标量)没有单独的数据类型,它只不过是向量的一种特例。而另一方面,R语言中矩阵是向量的一种特例,这一点与C语言家族相同。
接下来我们会用大量时间关注以下话题:
循环补齐:在一定情况下自动延长向量。
筛选:提取向量子集。
向量化:对向量的每一个元素应用函数。
这些运算是R编程的核心,在本书的其他部分也会经常提到它们。
2.1 标量、向量、数组与矩阵
在许多编程语言中,向量与标量(即单个数值)不同。例如,考虑下面的C代码:


这段代码请求编译器给一个x的整型变量x分配空间,并给一个名为y的三元素整型数组(C语言中的术语,类似于R中的向量)分配内存空间。但在R中,数字实际上被当做一元向量,因为数据类型里没有标量。
R语言中变量类型称为模式(mode)。回顾第1章,同一向量中的所有元素必须是相同的模式,可以是整型、数值型(浮点数)、字符型(字符串)、逻辑型(布尔逻辑)、复数型等等。如果在程序中查看变量x的类型,可以调用函数typeof(x)进行查询。
不同于ALGOL家族的编程语言(比如C和Python)中的向量索引,R中向量索引从1开始。
2.1.1 添加或删除向量元素
与C语言类似,R中向量是连续存储的,因此不能插入或删除元素,而这跟Python语言中的数组不同。在R中,向量的大小在创建时已经确定,因此如果想要添加或删除元素,需要重新给向量赋值。
例如,把一个元素添加到一个四元向量的中间,如下代码所示:




在这里,我们创建了一个四元向量,赋值给x。为了在其第三和第四元素之间插入一个新的元素168,我们把x的前三个元素、168和x的第四个元素按顺序连起来,这样就创建出新的五元向量,而此时x并没发生变化。接下来再把这个新的向量赋值给x。
这一结果看似已经改变了x中存储的向量,但实际上创建了新的向量并把它存储到x。这样的区别看上去可能微乎其微,但它是有影响的。例如,在某些情况下,它可能限制R的快速执行的潜力,这一问题将在第14章讨论。
注意 对于C语言背景的读者来讲,x本质上是一个指针,重赋值是通过将x指向新向量的方式实现的。
2.1.2 获取向量长度
可以使用函数length()获得向量的长度。


在本例中,我们已经知道x的长度,所以实际上没有必要查询它。但在写一般函数的代码时,经常需要知道向量参数的长度。
例如,假设我们想要这样一个函数,用它判断其向量参数(假设存在这个值)中第一个1所在位置的索引值。下面是一种(不一定有效率的)代码的写法:


如果不要length()函数,则需要添加第二个参数在first1()上,命名为n,用于指定x的长度。
注意在该例中,将循环写成以下形式则无法运行:


这一方法的问题在于,它不能让我们获得所需元素的索引。因此,需要一个显式循环,这就需要计算x的长度。
该循环另一个问题是:要仔细进行编码,因为length(x)可能为0。下面看看在这种情况下,循环中的语句1:length(x)会发生什么:




在循环过程中,变量i先取值为1,然后取值0,但当x为空时,这显然不是我们想要的。
另一种较为保险的方法是,使用R 的高级函数seq(),我们将在2.4.4节中进行讨论。
2.1.3 作为向量的矩阵和数组
你将看到,数组和矩阵(在某种意义上说,甚至包括列表)实际上都是向量。只不过它们还有额外的类属性。例如矩阵有行数和列数。我们将在下一章详细讨论,但在本章我们没必要作区分,因为它们属于向量,在本章中讲到的一切内容,同样适用于它们。
考虑下面的例子:


这里2×2的矩阵m按列存储为一个四元向量,即(1,3,2,4)。现在对它加上(10,11,12,13),得到向量(11,14,14,17),但最终如例子中的结果,R记得我们是对矩阵进行操作,因此返回2×2的矩阵。
2.2 声明
通常,编译语言要求你声明变量,即在使用前告诉编译器变量的存在。这是前面提到的C语言的例子:


和大多数的脚本语言(例如Python和Perl)一样,R中不需要声明变量。例如,下面这行代码:


这行代码前没有事先引用z,它完全是合法(并且普遍)的。
但是,如果要引用向量中特定的元素,就必须事先告知R。例如,我们希望y是一个二元向量,由5和12两元素构成。下面的语句无法正常工作:


对于上面的例子,必须先创建y,比如按这种方式:


以下同样可行:


这种方法同样正确,因为右边创建了一个新向量,然后绑定(bind)给变量y。
我们写R代码时,不能突然冒出诸如y[2]这样的语句,其原因归咎于R这种函数式语言的特性。在函数式语言中,读写向量中的元素,实际上由函数来完成。如果R事先不知道y是一个向量,那么函数将没有执行的对象。
对于绑定,由于变量没有事先声明,则它们的类型不受限制。以下一系列操作完全是有效的:


x先被绑定为一个数值型向量,然后被绑定为字符串变量。(再次提醒C或C++背景的程序员,x只是一个指针,在不同的时间可以指向不同类型的对象。)
2.3 循环补齐
在对两个向量使用运算符时,如果要求这两个向量具有相同的长度,R会自动循环补齐(recycle),即重复较短的向量,直到它与另一个向量长度相匹配。下面是一个例子:


例子中较短的向量被循环补齐,因此运算其实是像下面这样执行的:


下面是一个更为巧妙的例子:




再次提醒读者,矩阵实际上是个长向量。在这里,3×2的矩阵x是一个六元向量,它在R中一列一列的存储。换句话说,在存储方面,x与c(1,2,3,4,5,6)相同。我们把二元向量c(1,2)加到这个六元向量上,则所加的二元向量要再重复两次才能变成六个元素。换句话说,实际的运算如下:


不仅如此,在相加之前,c(1,2,1,2,1,2)在形式上也由向量转变为与x相同维数的矩阵,即:


也就是说,最后结果是计算下面的式子:

2.4 常用的向量运算
接下来将介绍一些常用的向量运算,包括算术和逻辑运算、向量索引以及一些创建向量的有用方法。然后将给出两个使用这些运算的扩展案例。
2.4.1 向量运算和逻辑运算
记住R是一种函数式语言,它的每一个运算符,包括下例中的+,实际上也是函数。


再回顾一次,标量实际上是一元向量,因此向量也可以相加,+算子按元素逐一进行运算。


如果你熟悉线性代数,当将两个向量相乘时,你也许会对所发生的感到惊讶。


但请记住,由于*函数的使用方式,实际上是元素和元素相乘。上例结果中的第一个元素5,是x的第一个元素1,与c(5,0,-1)中第一个元素5相乘的结果,以此类推。
同样的道理适用于其他数值运算符。下面有一个例子:


2.4.2 向量索引
R中最重要和最常用的一个运算符是索引,我们使用它来选择给定向量中特定索引的元素来构成子向量。索引向量的格式是向量1[向量2],它返回的结果是,向量1中索引为向量2的那些元素。


注意,元素重复是允许的。


负数的下标代表我们想把相应元素剔除。


像这样的情况,使用length()函数通常很有用。例如,假设我们想选择向量z中除最后一个元素外的其他全部元素。以下代码可以实现:


或更简单的代码如下:


对于上例,更常用的方法是z[1:2]。我们的程序可能需要处理长于二元的向量,此时第二种方法就更为通用。
2.4.3 用:运算符创建向量
R中有一些运算符在创建向量时十分有用。我们从第1章介绍过的冒号运算符:开始。它生成指定范围内数值构成的向量。


回顾本章,前面在循环语句中使用过它,如下所示:


要注意运算符优先级的问题。


在表达式1:i-1中,冒号运算符的优先级高于减号,因此先计算1:i,得到1:2,然后再减1。这意味着二元向量减去一元向量。这就要用到循环补齐,一元向量(1)将扩展为(1,1),与二元向量1:2的长度匹配。按元素逐一相减,得到结果(0,1)。
另一方面,在表达式1:(i-1)中,括号的优先级高于减号。也就是说,先计算出i-1,表达式最终结果为1:1,也就是上例所看到的结果。
注意 在命令窗口中输入?Syntax,可以 从R自带的帮助文件里获得运算符优先级的详细说明。
2.4.4 使用seq()创建向量
比:运算符更为一般的函数是seq()(由sequence得来),用来生成等差序列。例如,鉴于3:8生成向量(3,4,5,6,7,8),其元素间隔为1(4-3=1,5-4=1,以此类推),用seq()函数可以生成间隔为3的向量,如下所示:


间隔也可以不为整数,例如0.1。


前面我们在2.1.2节中提到的空向量问题,用seq()可以方便地进行处理。在那里,我们处理一个以下开头的循环:


如果x为空,这个循环不应该有任何迭代,但由于1:length(x)= (1,0),它实际上做了两次迭代。我们把语句写成如下形式可以改正问题:


下面对seq()函数做一个简单的测试,来看看为什么这样可行:


可以看到,如果x非空,seq(x)与1:length(x)的结果相同,但如果x为空,seq(x)正确地计算出空值NULL,导致上面的循环迭代0次。
2.4.5 使用rep()重复向量常数
rep()(由repeat得出)函数让我们可以方便地把同一常数放在长向量中。调用的格式是rep(x, times),即创建times*length(x)个元素的向量,这个向量由是x重复times次构成。例如:


rep()函数还有一个参数each,与times参数不同的是,它指定x交替重复的次数。


2.5 使用all()和any()
any() 和all() 函数非常方便快捷,它们分别报告其参数是否至少有一个或全部为TRUE。


例如,假设R执行下列代码:


第一步计算x>8,得到:


any()函数判断这些值是否至少一个为TURE。all()函数的功能类似,它判断这些值是否全部为TRUE。
2.5.1 扩展案例:寻找连续出现1 的游程
假设一个向量由若干0 和1 构成,我们想找出其中连续出现1 的游程。例如,对于向量(1,0,0,1,1,1,0,1,1),从它第4索引处开始有长度为3的游程,而长度为2的游程分别始于第4,第5和第8索引的位置。因此,用语句findruns(c(1,0,0,1,1,1,0,1,1),2)调用下面展示的函数,返回结果(4,5,8)。代码如下:


第五行,我们需要判断从x[i]开始的连续k个值,即x[i],x[i+1],...,x[i+k-1]的值,是否全部为1。表达式x[i:(i+k-1)]语句给出了上述子向量的值,然后使用all()函数检验它是否是一个游程。
我们对它进行一下测试:


尽管前面的代码中使用all()比较好,但建立向量runs的过程并不理想。向量的内存分配过程比较耗时,由于调用c(runs,i)时给新的向量分配了内存空间,每次执行时都会减慢代码的运行速度。(这与新向量赋值给runs无关,我们仍然给向量分配了内存空间。)


在较短的循环中,这样做可能没问题,但当应用程序的运行性能受到重点关注时,这里有更好的方法。
一种替代方法是预先分配的内存空间,像这样:


在第3行,我们给一个长度为n的向量分配了内存空间。这意味着在执行循环的过程中,可以避免分配新的内存。第8行代码做的只是填充runs。在退出函数之前,我们在第12行重新定义runs,来删除该向量中没用的部分。
这种方法更好,第一版代码可能会有很多次内存分配,而第二版代码将之减少为两次。如果我们确实需要提高速度,可能考虑使用C语言重新编码,这会在第14章中讨论。
2.5.2 扩展案例:预测离散值时间序列
假设我们观察到取值为0或1的数据,每个时刻一个值。为了了解具体应用,假设这是每天的天气数据:1代表有雨,0代表没有雨。假设已经知道最近几天是否下雨,我们希望预测明天是否会下雨。具体而言,对于某个k值,我们会根据最近k天的天气记录来预测明天的天气。我们将使用“过半数规则”(majority rule:):如果在最近k期里1的数量大于等于k/2,那么预测下一个值为1,否则,预测下一个值为0。例如如果k=3,最近三期的数据为1、0、1,则预测下一期值为1。
但是,我们应该如何选择k?显然,如果选择的值太小,则给我们用以预测的样本量太小。如果取值过大,导致我们使用过于早期的数据,而这些数据只有很少或根本没有预测价值。
一个解决方案是针对已知的数据(称为训练集),变换不同的k值,看看预测效果如何。
在天气的例子中,假设我们有500天的数据,假设我们考虑使用k=3。为了评价k值的预测能力,我们基于前三天的数据来“预测”每天的数据,然后将预测值与已知值进行对比。以此类推,对于k=1、k=2、k=4,我们做同样的事情,直到k值足够大。然后,我们使用训练数据中表现最好的k值,用于未来的预测。
那么我们如何编写R代码?这里有一个简单的方法:


这段代码的核心在第7行。此处要预测第i+k天的值(预测结果保存在pred[i]),利用的是之前k天的值,也即第i天,……,第i+k-1天的值。因此,我们需要算出这些天中1的个数。由于我们处理的是0-1数据,1的数量可以简单地使用这些天x[j]的总和,它可以很方便地用以下方法获取:


使用sum()函数和向量索引使得计算更简捷,避免了循环,因此它更简单更快速。这是R语言典型的用法。
第9行的表达式也是同样的道理:


在这里,pred包含预测值,而x[(k+1):n]是这些天的实际值。前者减去后者,得到的值要么为0,要么为1,或-1。在这里,1或-1对应两个方向的预测误差,即当真实值为1时预测值为0,或者真实值为0时预测为1。再用abs()函数求出绝对值,得到0和1的序列,后者表示预测有误差。
这样,我们就能知道哪些天的预测有误差,然后使用mean()来计算错误率,在这里我们应用了这一数学原理:即0-1数据的均值是1的比例。这是R语言的一个常见技巧。
上述preda()的编码是相当直截了当的,它的优点是简单和紧凑。然而,它可能很慢。我们可以尝试用向量化循环来加快速度,正如2.6节所讨论的那样。然而在这里它不能解决加速的主要障碍,即这些代码中所有的重复计算都不能避免。在循环中对于i的相邻两个取值,调用sum()函数求和的向量只相差两个元素。这会减慢速度,除非k值非常小。
所以,我们重写代码,计算过程中利用上一步计算的结果。在循环的每一次迭代中,将更新前一次得到的总和,而不是从头开始计算新的总和。


关键在第9行。在这里从总和sm里减去最早的元素x[i-1],再加上新的元素(x[i+k-1]),从而更新sm。
另一种方法是使用R函数cumsum(),它能计算向量的累积和(cumulative sums)。这里是一个例子:


在这里,y的累加和是5=5,5+2=7,5 + 2 + (-3) = 4,5 + 2 + (-3) + 8 = 12,这些值由cumsum()返回。
在上面的例子里,建议用cumsum()的差值替代preda()中的表达式sum(x[i:(i+(k-1))。


在求x中连续k个元素(称为窗口)之和的时候,没有像下面这样使用sum()函数:


而是计算窗口的结束和开头处的累积和之差,像这样:


注意,我们在向量的累积和前面添加了0:


这是为了保证在i=1时能计算出正确的值。
predb()函数里每次循环迭代要做两次减法运算,对predc()来说只需要做一次。
2.6 向量化运算符
假设我们希望对向量x中的每一个元素使用函数f()。在很多情况下,我们可以简单地对x调用f()就能完成。
这可以简化我们的代码,不仅如此,还能将代码运行效率显著提高到数百倍甚至更多。
提高R代码执行速度的有效方法之一是向量化(vectorize),这意味着应用到向量上的函数实际上应用在其每一个元素上。
2.6.1 向量输入,向量输出
之前在本章你已经看到向量化运算的一些例子,即+和*运算符。另一个例子是>。


在这里,>函数分别运用在u[1] 和v[1],得到结果TRUE,然后是u[2] 和 v[2],得到结果FALSE,以此类推。
关键在于,如果一个函数使用了向量化的运算符,那么它也被向量化了,从而使速度提升成为可能。下面有一个例子:


在这里,w()使用了向量化的运算符+,从而w()也是向量化的。正如你看到的,存在无数个向量化的函数,因为用简单的向量化函数可以构建更复杂的函数。
注意,甚至超越函数—(平方根、对数、三角函数等)也是向量化的。


这也适用于其他许多内置的R函数。例如,让我们对向量y应用round()函数,其作用是四舍五入到最近整数:


上例的关键在于,round()函数能应用到向量y中的每一个元素上。记住,标量实际上是一元向量,所以通常情况下对单个数值使用round()函数,只是一种特殊情形。


在这里,我们使用内置的函数round(),但用你自己编写的函数同样可以做到这点。
正如前面提到过的,诸如+这样的运算符实际上也是函数。例如,考虑下面的代码


上例中三元向量的每个元素都加上了4,原因在于+实际上也是函数!下面这样写就看得明显了:


同时也要注意,循环补齐在这里起了关键作用,4被循环补齐为(4,4,4)。
我们知道R有没有标量,那么我们考察一下那些看上去有标量参数的向量化函数。


在这里定义的f(),我们希望c是一个标量,但实际上它当然是一个长度为1的向量。即使我们调用f()时给c指定的是单个数值,在f()计算x+c时,它也会通过循环补齐的方式延展为一个向量。因此对于本例中调用的f(1:3,1),x+c的值变为如右所示:
这带来了代码安全性的问题。f()中没有什么告知我们不能使用显式的向量给c赋值,例如:


你需要通过计算确认(4,16,36)是否的确是期望的输出。
如果你确实想把c限制为标量,则需要插入一些判断语句,比如这样:


2.6.2 向量输入,矩阵输出
到目前为止,我们涉及的向量化函数应用于标量时返回值也是标量。对单个数值调用sqrt()函数得到的结果也是单个数值,如果我们将此函数应用到八元向量,则得到的输出结果也是八个数组成的向量。
但如果函数本身的返回值就是向量会怎么样呢?例如这里的z12():


对5使用函数z12(),将得到二元向量(5,25),如果我们将它应用在八元向量,则它生成16个数值:


把结果排列成8×2的矩阵可能会更自然,可以用matrix函数来实现:


但我们可以用sapply()(它是simplify apply的缩写)来简化这一切。调用sapply(x,f)即可对x的每一个元素使用函数f(),并将结果转化为矩阵。下面是一个例子:


我们得到2×8而不是8×2维的矩阵,但它同样是有用的。第4章将进一步讨论sapply()函数。
2.7 NA与NULL值
用过其他脚本语言的读者也许会知道“查无此物”的值,例如Python中的None和Perl中的undefined。R有两个类似值:NA和NULL。
在统计数据集,我们经常遇到缺失值,在R中表示为NA。而NULL代表不存在的值,而不是存在但未知的值。让我们看看它们在具体情形下是怎么用的。
2.7.1 NA的使用
在R的很多统计函数中,我们要求函数跳过缺失值(也就是NA)。如下例所示:


在第一个调用中,因为x中有一个缺失值NA,导致mean()无法计算均值。但通过把可选的参数na.rm(意思为移除NA)设置为真(T),可以计算其余元素的均值。相比之下,R会自动跳过空值NULL,我们将在下一节介绍。
下面几个NA值的模式都不一样:


2.7.2 NULL的使用
NULL的一个用法是在循环中创建向量,其中每次迭代都在这个向量上增加一个元素。在这个简单的例子中,我们建立了偶数向量:


回顾第1章, 是模运算符(modulo operator),它给出除法运算的余数。例如13除以4的余数是1,即13 %% 4 =1。(算术和逻辑运算符列表见7.2节。)因此,例子中的循环开始于一个空向量,然后依次向其中添加2、4等元素。
当然,这只是一个人为的例子,并且这里有更好的方法完成这件事。下面是寻找1:10中偶数的另外两种方法:


这里关键是为了阐述NA与NULL的区别。如果在前例中使用NA而不是用NULL,则会得到多余的NA:


这里你可以看到,NULL值被作为不存在而计数:


NULL是R的一种特殊对象,它没有模式。
2.8 筛选
反映R函数式语言特性的另一个特征是“筛选”(filtering)。这使我们可以提取向量中满足一定条件的元素。筛选是R中常用的运算之一,因为统计分析往往关注满足一定条件的数据。
2.8.1 生成筛选索引
我们先看一个简单的例子:


查看这段代码,凭直觉想想 “我们的目的是什么?”。可以看出我们要求R提取z中平方大于8的所有元素,然后将这些元素构成的子向量赋值给w。
筛选是R中很关键的运算,因此我们有必要从技术细节上探究一下R是怎样实现上述意图的。我们来逐步研究:


表达式z*z > 8得出的是布尔值向量。对你而言,弄清楚这个结果如何产生是非常重要的。
首先,注意表达式z*z > 8中所有东西都是向量或向量运算符:
因为z是向量,所以z*z同样是向量(并且长度与z一致)。
通过循环补齐,这里的数字8(长度为1的向量)补齐为向量(8,8,8,8)。
运算符>,像+一样,实际上是个函数。
对于最后这一点,我们看个例子:


因此,下面的


实际上是


换句话说,我们对向量使用函数—它也是向量化的另一个例子,与你看到的其他向量化一样。在本例中,结果是一个布尔值向量。然后用得到的布尔值向量筛选出z中所需的元素:


下一个例子将更有针对性。在这里,我们将再次用z定义提取条件,但接着用该结果从另一个向量y,而不是从z中提取子向量,如下所示:


或者,可以像下面这样写更简洁:


再次强调,这个例子要说的是,我们使用向量z决定筛选另一个向量y的索引。相反,前面的例子是使用z筛选它自身。
下面是另一个例子,其中涉及赋值。设我们有一个向量x,要将其中所有比3大的元素替换为0。事实上,我们可以非常简洁地使用一行代码。


让我们检查一下:


2.8.2 使用subset()函数筛选
也可以使用subset()函数做筛选。当对向量使用该函数时,它与普通的筛选方法的区别在于处理NA值的方式上。


我们使用前一节提到的普通筛选方法,R会认为“x[5]是未知的,因此其平方是否大于5同样是未知的。”但也许你不希望NA出现在结果中。当你希望在结果中剔除NA值时,使用subset()将免去自己移除NA的麻烦。
2.8.3 选择函数which()
正如你所看到的,筛选是从向量z中提取满足一定条件的元素。但是,在某些情况下,我们希望找到z中满足条件元素所在的位置。此时可以使用which(),如下所示:


结果表明z中的第一、第三和第四元素平方大于8。
和筛选一样,了解前面的代码到底发生了什么是很重要的。下面的表达式:


计算得到(TRUE,FALSE,TRUE,TRUE)。which()函数简单地报告出在后面的表达式中哪些元素为TRUE。
which()有一个非常方便(尽管有点浪费)的用法,是在一个向量中找出满足一定条件的元素首次出现的位置。例如,回顾本书2.1.2节代码,找出向量中的第一个1。


这里有另一种写法能达成目标:


调用which()产生x中所有1的索引。这些索引将以向量形式给出,然后我们取该向量中的第一个元素,即是第一个1的索引。
这一代码更加简洁。但另一方面,它也比较浪费,因为它找出了x中所有的1,而我们只需要第一个。因此,尽管它是向量化方法,可能更快,但如果x中第一个1出现在靠前的位置,则此方法实际上要慢一些。
2.9 向量化的ifelse()函数
除了多数语言中常见的if-then-else结构,R还有一个向量化的版本:ifelse()函数。它的形式如下:


其中b是一个布尔值向量,而u和v是向量。
该函数返回的值也是向量,如果b[i]为真,则返回值的第i个元素为u[i],如果b[i]为假,则返回值的第i个元素为v[i]。这一概念相当抽象,因此我们看一个例子:


在这里,我们希望产生一个向量,这个向量在x中对应元素为偶数的位置取值是5,且在x中对应元素为奇数的位置取值12。因此,对应到形式参数b的实际参数是(F,T,F,T,F,T,F,T,F,T)。对应到u的第二个实际参数5,通过循环补齐成为(5,5,...)(十个5)。第三个参数12,同样循环补齐为(12,12,...)。
这里有另一个例子:


我们返回的向量由x的元素乘以2或3构成,到底是乘以2还是乘以3,取决于该元素是否大于6。
再次申明,弄明白这里真正发生了什么很重要。表达式x>6是一个布尔值向量。如果第i个元素为真,则返回值的第i个元素将被设定为2*x的第i个元素,否则,它将被设定为3*x[i],以此类推。
ifelse()相对于标准的if-then-else结构的优点是,它是向量化语句,因此有可能快很多。
2.9.1 扩展案例:度量相关性
评估两个变量的统计关系时,除了标准相关性度量方法(Pearson级差相关系数)之外,还有几种备选方法。一些读者可能听过的Spearman秩相关。这些度量方法有不同的目标,比如针对异常值的稳健性,异常值指的是那些取值很极端的数据,即很有可能出错的数据。
在这里,我们提出一种新的度量方法,这不是统计学上的新发现(实际上它与广泛使用的Kendall’s  τ方法有关),只是为了阐述本章中的一些R编程技术,尤其是ifelse()。
考虑向量x和y,它们是时间序列,比如它们是每小时收集的气温和气压测量值。我们定义两者的相关性为x和y同时上升或下降次数占总观测数的比例,即y[i+1]-y[i]与x[i+1]-x[i]符号相同时的次数占总数i的比例。以下是代码:


这里有一个例子:


在本例中,x和y在10次中同时上升3次(第一次同时上升是12到13,2到3),并同时下降1次。得到相关性的估计为4/10=0.4。
让我们看看它是如何工作的。首先需要把x和y的值编码为1和-1,1代表当前观测值较上期增加。这是在第5行和第6行进行。
例如,当我们对16个元素的v,调用findud()函数,想想看在第5行发生了什么。v[-1]会是一个15元素的向量,它从v的第2个元素开始。同样,v[-length(v)]也将是一个15元素的向量,它从v的第1个元素开始。结果是我们用右移一个时间段的序列值减去原始序列值。这个差值序列给我们提供了每个时间段序列增长/减少的状态—这正是我们想要的。
然后,我们需要依据差值的正负来把差值变换成1和-1。调用ifelse()可以简单而简洁地做到,并且它比循环版本的代码耗费更短的执行时间。
这里本应该写两个调用findud()的语句,一次对x,另一次对y。但实际上,我们把x和y放入列表中,然后使用lapply()函数,这样可以避免重复的代码。如果我们对很多向量采用相同操作,而不是只用两个,尤其是对于向量个数可变的情况,像这样使用lapply()可以使代码更加简洁明了,并且它可能稍快一些。
然后计算匹配的比例,如下所示:


需要注意的是lapply()返回一个列表。其组件是以1或-1编码的向量。语句ud[[1]] == ud[[2]]返回一个向量,其值由TRUE和FALSE构成,它们分别被mean()视作1和0。取均值就求出我们要的比例。
更高级的版本将使用R的diff()函数,该函数对向量做“滞后”运算。举例来说,我们可能要比较每个元素与它后面第三个元素(用术语说就是“滞后三期”)。默认的滞后期是一期,也就是我们这里所需的:


前面例子中的第5个行代码可以改写成:


R的另一个高级函数sign()可以使代码更简洁,它根据其参数向量中的数值是正值、零或负值,将其分别转化为1、0、或-1。这里有一个例子:


使用sign(),我们能把undcorr()函数改写为一行,如下所示:


这无疑比最初的版本简短得多。但哪一个更好?对于大多数人,可能会用更长的时间才能想到这么写。并且尽管代码变短,但其实变得更难理解了。
所有的R程序员需要在简洁和清晰之间找到“恰到好处”的平衡点。
2.9.2 扩展案例:对鲍鱼数据集重新编码
由于其参数向量化的特性,ifelse()函数可以嵌套使用。下面的例子是鲍鱼的数据集,性别被编码为M、F或I(是Infant的缩写,这里意为幼虫)。我们希望将这些字符重新编码为1、2或3。实际的数据集包含超过4000条的观测值,但对于我们的例子,我们假设只有很少的数据存储在g中:


嵌套的ifelse()实际上做了什么?让我们仔细看一下。首先,为了表述更具体一些,我们找出函数ifelse()中形式参数的名字:

记住,对test中每一个取值为真的元素,函数使用yes中对应的元素作为结果。同样,如果test[i]值为假,则函数计算结果为no[i]。这样生成的所有值组成一个向量作为返回值。
在我们的例子中,R首先执行外层的ifelse(),其中的test是g==”M”,yes是1(会被循环补齐);no将是(之后)执行ifelse(g=="F",2,3)得到的结果。现在由于test[1]取真,则生成yes[1],也就是1。因此,外部函数调用所得的返回值第一个元素是1。
下一步,R将计算test[2],该值为假,因此R需要计算no[2]。R现在需要执行内部的ifelse()调用。之前并没有这样做,因为直到现在才需要它。R使用“惰性求值”(lazy evaluation)的原则,这意味着只有当需要时表达式才被计算,否则不计算。
R现在将计算ifelse(g=="F",2,3),得到(3,2,2,3,3,3,2),这是外部ifelse()的参数no,因此后者返回的第二个元素将是(3,2,2,3,3,3,2)中的第二个元素,即2。
当外层ifelse()函数调用执行到test[4]时,其取值为假,因此将返回no[4]。由于R已经计算过no,它有所需的值,即3。
需要注意,涉及的向量可能是矩阵的列,这是一个非常常见的情况。假设鲍鱼数据存储在矩阵ab中,性别是它的第一列。如果我们想像前例一样对其重新编码的话,可以这样做:


假设我们希望按照性别形成子集。可以使用which()来寻找M、F和I对应元素的编号。

更进一步,我们可以把这些子集保存在一个列表中,像如下这样:

需要注意的是,R的for()循环可以对字符串向量进行循环,我们正是利用了这一事实。(在4.4节中,你会看到一种更有效的方法。)
我们可以使用编码后的数据来绘制一些图形,探索鲍鱼数据集中的各种变量。通过给文件添加以下表头来概括变量的性质:

例如,可以分雄雌两组,针对直径和长度作图。使用以下代码:

首先,我们读取数据集,将其赋值给变量aba为了提示我们这是鲍鱼数据)。read.csv()类似于在第1章使用过的read.table(),我们将在第6章和第10章讨论。然后构造abam和abaf,分别是aba下对应雄性和雌性的两个子矩阵。
接下来,我们来作图。第一条作图命令绘制了雄性鲍鱼直径对长度的散点图。第二条命令绘制的是雌性的图。因为希望此图与雄性的叠加在同一张图形上,我们设置参数new=FALSE,告诉R不要创建一个新的图形。参数pch=”x”意思是我们希望绘制在雌性图形上的字符用x,而不是默认的o字符。
图2-1显示了(整个数据集的)图形。顺便说一句,它并不十分让人满意。显然,直径和长度具有很强的相关性,以至于这些点密集地填充了部分图形,雄性和雌性的图形几乎完全一致(尽管雄性具有更多的变化)。这在统计图形中是一个常见的问题。更精细的图形分析会更有启发性,但至少我们看到了强相关的证据,而相关性在性别上的差距并不是很大。
在前面的例子中,我们可以用ifelse来压缩绘图代码。利用这样一个事实:pch参数可以是向量而不仅仅是单个字符。换句话说,我们可对每个点绘制不同的字符。


(在这里,我们省略了重新编码为1、2和3的过程,但出于某些原因,你可能希望保留它。)
2.10 测试向量相等
假设我们要测试两个向量是否相等,使用==的朴素方法将不可行:


发生什么了?问题在于,我们处理的是向量化。与R中其他运算符一样,==是一个函数。


事实上,==是一个向量化的函数。语句x==y是将函数==()应用到x和y的每一组元素上,得到一个布尔值的向量。
那么我们可以做什么呢?一种选择是结合==的向量化本质,应用函数all():


对==的结果应用all()函数会询问其所有元素是否全为真,它与询问x与y是否完全一致有同样的效果。
甚至更好地是,我们可以简单地使用identical函数,像这样:


但要小心,因为正如identical这个词的字面意思,identical函数判断的是两个对象是否完全一样。看看下面这个R会话:


因此,符号:产生的是整数,而c()产生的是浮点数。但是谁能直接看出来呢?
2.11 向量元素的名称
可以给向量元素随意指定名称。例如,假设有一个50个元素组成的向量,表示美国每个州的人口。可以用州的名称给每个元素命名,如“Montana”和“New Jersey”。也可以给图形里的点命名,以此类堆。
可以用name()函数给向量中的元素命名,或查询向量元素的名称。


把向量元素名称赋值为NULL,可以将其移除。


甚至可以用名称来引用向量中的元素。


2.12 关于c()的更多内容
在本节中,我们将讨论与连接函数c()相关的一些其他内容,有时经常用到。
如果传递到c()中的参数有不同的类型,则它们将被降级为同一类型,该类型最大限度地保留它们的共同特性,如下所示:


在第一个例子中,我们混合了整数型和字符型,R会选择把它们都转换为后者的类型。在第二个例子中,对于混合的表达式,R认为列表类型有较低的优先级。本书4.3节将对这一点作深入探讨。
你可能不会写如此组合的代码,但你可能会遇到发生这种情况的代码,因此理解它的效果显得尤为重要。
另一个需要注意的关键点是,c()函数对向量有扁平化的效果,就像该例:


熟悉诸如Python等其他语言的人,可能预期前面这段代码会生成一个两层的对象。在R语言中这种事不会发生在向量上,尽管存在两层的列表,这在第4章将会看到。
在下一章,我们将接着讲解向量的两个非常重要的特例,即矩阵和数组。
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2013-6-24 09:23:24
第3 章
矩阵和数组
矩阵(matrix)是一种特殊的向量,包含两个附加的属性:行数和列数。所以矩阵也和向量一样,有模式的概念,例如数值型和字符型。(但反过来,向量却不能看作是只有一列或一行的矩阵。)
数组(array)是R里更一般的对象,矩阵是数组的一个特殊情形。数组可以是多维的。例如一个三维的数组可以包含行、列和层(layer),而一个矩阵只有行和列两个维度。本章主要讨论矩阵,本章最后一节会简述更高维的数组。
R的强大之处就在于它丰富的矩阵运算。本章主要讲述这些运算,尤其注重类似于向量的取子集和向量化运算方面。
3.1 创建矩阵
矩阵的行和列的下标都从1开始。例如矩阵a左上角的元素记作a[1, 1]。矩阵在R中是按列存储的,也就是说先存储第一列,再存储第二列,以此类推,如2.1.3小节所示。
创建矩阵的方法之一就是使用matrix()函数:


这里把第一列(即1和2)与第二列(3和4)连接在一起。因此数据是(1,2,3,4)。然后我们给出行数和列数。由于R是按列存储的,这就决定了这四个数在矩阵中的位置。
上例指定了矩阵中全部的4个元素,因此没必要同时设定列数ncol和行数nrow这两个参数,只需要给出其中一个就够了。4个元素排成两行,就意味着列数为2。


注意,当我们打印输出y时,R向我们展示了它表示行和列的记号。例如[, 2]表示矩阵第二列,如下:


另外一种创建矩阵的方法是为矩阵的每一个元素赋值:


用这种方法需要事先向R声明y是一个矩阵,并且给出它的行数和列数。
尽管R的矩阵是按列存储的,但是可以通过把matrix()的byrow参数设置为TRUE,使矩阵元素按行排列。以下是使用byrow的一个例子:

需要注意的是,尽管这样设置,但是矩阵本身依然是按列存储的,参数byrow改变的只是数据输入的顺序。当读取的数据文件是按这种方式组织时,可能会更方便。
3.2 一般矩阵运算
前面介绍了创建矩阵的基本方法,现在我们来看一些常用的矩阵运算,包括线性代数运算、矩阵索引和矩阵元素筛选。
3.2.1 线性代数运算
你可以对矩阵进行各种线性代数运算,比如矩阵相乘、矩阵数量乘法和矩阵加法。针对以前例子中的y,以下为这三种运算的实例:


关于矩阵线性代数运算的更多细节请参见8.4节。
3.2.2 矩阵索引
2.4.2节中的向量运算同样适用于矩阵,例如:

这里我们提取了矩阵z中第2、3列的所有元素组成了一个子矩阵。
下面的例子提取的是矩阵的行:

还可以对一个矩阵的子矩阵进行赋值:

这里给y的第1、3行赋了新的值。
下面是另一个给子矩阵赋值的例子:


向量的负值索引用来排除掉某些元素,这种操作同样适用于矩阵。


命令第二行的意思是,取出矩阵y中除第二行外的所有行。
3.2.3 扩展案例:图像操作
图像文件本质上就是矩阵,因为像素点也都是按行和列排列的。一张灰度图像会把每一个像素点的亮度存储为矩阵的一个元素。例如,图像中位于第28行、第88列的像素点的灰度值就存储在矩阵的第28行、第88列。如果图像是彩色的话,就需要三个矩阵来存储,分别记录红、黄、蓝的强度值。这里我们只以灰度图像为例。
以一张拉什莫尔山国家纪念公园的照片为例,我们用pixmap包读取图像。(附录B介绍了下载和安装R包的方法。)


读取文件mtrush1.pgm,并返回一个pixmap类的对象。再把这个对象在R里画出来,如图3-1所示。

图3-1 读取拉什莫尔山图片文件
我们来看一下pixmap类的组成内容:


pixmap类属于S4类型,它用符号@来访问各个组件,而不是用符号$。S3和S4的问题将在第9章讨论,不过这里最关键的是灰度矩阵mtrush1@grey。在这个例子里,这个矩阵有194行和259列。
这个例子的灰度是用0.0到1.0之间的数值表示的,0.0表示黑色,1.0表示白色,中间值代表不同程度的灰色。例如,图片第28行、第88列的点非常亮。


为了演示矩阵操作,我们将罗斯福总统的脸覆盖上(对不起了,总统先生,我对你没有个人恩怨)。使用R中locator()函数来找到需要盖住部分的行号和列号。当调用这个函数时,R会等待用户点击图片上的任意点,然后返回该点的准确坐标。用这种方式可以找到罗斯福总统的脸对应区域是从84行到163行,从135列到177列。(注意,pixmap对象的行编号和locator()是相反的:pixmap是自上而下递增的,而locator()恰恰相反。)于是,为了把这部分图像覆盖上,我们把这一区域的所有像素点取值设为1.0。


效果如图3-2所示。

图3-2 拉什莫尔山的图片,覆盖住罗斯福总统的头像之后
如果只是想将罗斯福总统的脸模糊掉,而不是去掉,可以在图片上增加随机噪声。代码如下:


如注释所显示的,我们生成随机噪声,然后把目标像素点矩阵和噪声进行加权平均。参数q控制噪声的权重,q值越大就越模糊。随机噪声取自U(0,1),即区间(0,1)上的均匀分布。注意,下面是矩阵操作:


调用上面的函数尝试一下:


效果如图3-3所示。

图3-3 拉什莫尔山图片,罗斯福总统的头像模糊处理之后
3.2.4 矩阵元素筛选
和向量一样,矩阵也可以做筛选。但是需要注意一下语法上的不同。首先来看一个简单的例子:


下面来分析一下这条命令,正如我们在第2章所讲那样:


首先判断x的第二列向量x[, 2]的哪些元素大于等于3,把结果赋值给布尔向量j。
然后在x中使用j:


x[j, ]的行与向量j中的取值为TRUE的行对应,也就对应于矩阵第二列中元素大于等于3的行。
于是就有了本例一开始的结果:


为了提高性能,需要注意的是,计算j时使用的是完全向量化运算。这是因为:
x[,2]是向量。
运算符>=用于比较两个向量。
数值3被自动重复,变成一个由数值3组成的向量。
还需要注意,虽然这个例子中j是通过x定义且用于提取x中的元素,但事实上筛选规则可以基于除被筛选变量之外的变量。以下例子同样使用上述的x:


表达式 z  2 ==1用于判断z的每个元素是否是奇数,返回的结果是(TRUE, FALSE, TRUE)。因此我们提取出了x的第一、三行。
再看另外一个例子:




这里也是同样的做法,但是使用了稍微复杂的条件来提取矩阵的行。(用类似的方式可以提取列或子矩阵。)首先表达式m[,1] > 1把m的第一列的每个元素同1进行比较,返回(FALSE,TRUE,TRUE)。类似的是,第二个表达式m[,2] > 5返回(FALSE,FALSE,TRUE)。对(FALSE,TRUE,TRUE)和(FALSE,TRUE,TRUE)进行逻辑“与”运算,得到(FALSE,FALSE,TRUE)。以运算后的向量为行索引,得到矩阵m的第三行。
注意,这里需要使用 & 运算符,而不是 &&,前者是向量的逻辑“与”运算,后者是用于if语句的标量逻辑“与”运算。想了解更多可以参见7.2节中的运算符列表。
细心的读者可能注意到前面的例子中有一些反常的地方。我们要得到的是一个1行2列的子矩阵,可是返回的却是一个由两个元素组成的向量。元素是正确的,可是数据类型却不对。如果把返回值输入到其他的矩阵函数里可能会导致错误。解决办法是通过设定参数drop,告诉R让返回结果保持数据的二维属性。3.6节会介绍如何避免意外降维,并详细讲解drop的用法。
由于矩阵也是向量,所以向量的运算也适用于矩阵。例如:


这行命令说明,从向量索引的角度来看,m的第1、3、5、6个元素大于2。第5个元素指的是m第2行第2列的元素,即10,的确比2大。
3.2.5 扩展案例:生成协方差矩阵
这个例子演示row()和col()函数,这两个函数的参数都是矩阵。例如对于矩阵a,row(a[2,8])返回a中对应元素的行号,即2。然而,显然我们知道row(a[2,8])在第2行,那么这个函数有什么用途呢?
先来看一个例子。如果我们想要用MASS库的mvrnorm()函数生成多元正态分布的随机数,需要指定协方差矩阵。协方差矩阵是对称的,就是说矩阵中第1行第2列的元素等于第2行第1列的元素。
假如我们在处理一个n元正态分布,它的协方差矩阵有n行n列。我们希望这n个随机变量方差都为1,每两个变量间的相关性都是rho。例如当n=3,rho=0.2时,需要的矩阵就是:

下面就是生成这种矩阵的代码:


下面来解释它的运算过程。首先col()返回的是矩阵元素的列号,和row()功能相似。然后第三行的row(m)返回一个由整数组成的矩阵,每个整数代表矩阵m中对应元素的行号。例如:


因此表达式row(m) == col(m)返回一个由FALSE和TRUE组成的矩阵,矩阵的对角线位置是TRUE,其他位置是FALSE。再次提醒一下读者,本例中的二目运算符"=="是一个函数,row()和col()也是函数。因此,下面这个表达式:


会作用到矩阵m的每一个元素上,最后返回一个由FALSE和TRUE组成的矩阵,其行数和列数与m的相同。表达式ifelse()同样是一个函数调用。


在本例中,刚才提到的由TRUE和FALSE组成的矩阵传递给ifelse()函数做参数,返回一个矩阵,对角线元素为1,其他位置为0。
3.3 对矩阵的行和列调用函数
*apply()函数系列是R中最受欢迎同时也是最常用的,该函数系列包括apply()、tapply()和lapply()。这里我们主要介绍apply()。apply()函数允许用户在矩阵的各行或各列上调用指定的函数。
3.3.1 使用apply()函数
以下是apply()函数的一般形式:


参数解释如下:
m 是一个矩阵。
dimcode 是维度编号,若取值为1代表对每一行应用函数,若取值为2代表对每一列应用函数。
f是应用在行或列上的函数。
fargs是f的可选参数集。
例如,对矩阵z的每一列应用函数mean():


本例也可以用colMeans()函数直接实现,不过这里主要为了提供使用apply()的简单示例。
apply()里也可以使用用户自己定义的函数,就和使用R内部函数(比如mean())一样。下面是使用自定义函数f的例子:


f()函数把一个向量除以(2,8)。(如果向量x的长度大于2,那么(2,8)就会循环补齐)。apply()函数则对z的每一行分别调用f()。z的第一行是(1,4),所以在调用f()时,形式参数x对应的实际参数是(1,4)。然后R计算(1,4)/(2,8)的值,这在R中是向量对应元素运算,得到(0.5,0.5)。对其他两行的计算与此类似。
你可能会惊讶,所得结果y是一个2×3的矩阵而非3×2的。第一行的计算结果(0.5, 0.5)构成apply()函数输出结果的第一列,而不是第一行。这就是apply()函数的默认方式。如果所调用的函数返回的是一个包含k个元素的向量,那么apply()的结果就有k行。如果需要的话,可以使用转置函数t(),例如:


如果所调用的函数只返回一个标量(即单元素向量),那么apply()的结果就是一个向量,而非矩阵。
在用apply()时,待调用的函数至少需要一个参数。上例中,f()的形式参数在这里对应的实际参数就是矩阵的一行(或一列)。有时,待调用函数需要多个参数,用apply()调用这类函数时,需要把这些额外的参数列举在函数名字后面,用逗号隔开。
例如,我们有一个由0和1组成的矩阵,想要生成如下向量:向量每个元素对应矩阵的每行,如果该行前d个元素中1较多,向量的对应元素就取1,反之取0。其中d是可以变的参数。程序可以这样写:


这里3和2是函数copymaj()中形式参数d的实际取值。矩阵的第一行是(1,0,1,1,0),当d取3时,前d个元素是(1,0,1)。1占多数,因此copymaj()返回1。所以apply()返回的第一个元素就是1。
使用apply()函数并不像很多人以为的那样,能使程序的运行速度加快。其优点是使程序更紧凑,更易于阅读和修改,并且避免产生使用循环语句时可能带来的bug。此外,并行运算是R目前发展的方向之一,apply()这类函数会变得越来越重要。例如,snow包中的clusterApply()函数能够把子矩阵的数据分配到多个网络节点上,在每个网络节点上对子矩阵调用给定的函数,达到并行计算的目的。
3.3.2 扩展案例:寻找异常值
在统计学中,“异常值”(outlier)指的是那些和大多数观测值离得很远的少数点。所以异常值要么是有问题(例如数字写错了),要么是不具有代表性(例如比尔盖茨的收入和华盛顿州居民的收入相比)。检测异常值有很多方法,我们这里构造一个非常简单的方法。
假如矩阵rs用来存储零售业销售数据,每行对应一家商店,一行里的观测值对应每天的销售数据。我们用非常简单的方法识别出每家商店数据中偏离最远的观测值,也就是与中位数差别最大的观测值。代码如下:


像这样调用函数:


那么上述中的findols(rs)是如何运作的?首先需要定义一个函数,作为调用apply()函数时的参数。
该函数要应用于销售数据矩阵的每一行,并返回该行中最异常的元素所在位置。函数findol()就是为完成此目的而定义的,请看代码的第4、5行。(注意,我们在一个函数的内部又定义了另一个函数。如果内部函数很短的话,这么做是很常见的。)在表达式xrow-mdn中,向量xrow减去单元素向量mdn,而前者的长度通常大于1,因此在做向量减法运算之前,mdn被循环补齐(recycling),扩展到与xrow的长度一致。
代码第五行使用R函数which.max(),而不是max()。因为max()返回的是向量元素的最大值,而which.max()返回最大值所在位置(即索引)。这正是我们需要的。
最后在第七行,对x的每一行调用findol(),得到各行“异常值”所在位置。
3.4 增加或删除矩阵的行或列
严格来说,矩阵的长度和维度是固定的,因此不能增加或删除行或列。但是可以给矩阵重新赋值,这样可以得到和增加或删除一样的效果。
3.4.1 改变矩阵的大小
回忆之前通过重新赋值改变向量大小的方法:


第一个例子里,x原来长度为5,通过拼接和重新赋值,将其长度变为6。事实上我们没有改变x的长度,而是生成一个新的向量,然后赋值给x。
注意 重新赋值的过程可能会在用户看不见的情况下进行,在14章我们将会介绍。例如,即使是x[2]<-12这种小操作事实上都是一个重新赋值的过程。
类似的操作可以用来改变矩阵的大小。例如,函数rbind()(代表row bind,按行组合)和函数cbind()(代表column bind,按列组合)可以给矩阵增加行或列。


这里,cbind()把一列由1组成的向量和z组合在一起,创建了一个新矩阵。上面我们只是直接输出了结果,实际上也可以把这个新的矩阵赋值给z(或其他变量),如下所示:


注意,有时也会用到循环补齐(recycling):


在这里,1被循环补齐为由四个1组成的向量。
函数cbind()和rbind()还可以用来快速生成一些小的矩阵,例如:


不过,请谨慎使用cbind()!和创建向量一样,创建一个新的矩阵是很耗时间的(毕竟矩阵也属于向量)。在下面的代码中,cbind()创建了一个新矩阵:


新的矩阵正好被赋值给z,也就是说我们给这个新矩阵取名为z,与原来的矩阵同名,而原来的矩阵被覆盖了)。问题是创建新矩阵会减低程序速度,如果在循环中重复创建矩阵,将浪费大量的时间。
因此在循环中每次往矩阵中添加一行(列),最后矩阵会变成一个大矩阵,这种做法是不可取的,最好一开始就定义好一个大矩阵。这个事先定义的矩阵是空的,但是在循环过程中逐行或列进行赋值,这种做法避免了循环过程中每次进行耗时的矩阵内存分配。
也可以通过重新赋值来删除矩阵的行或列:


3.4.2 扩展案例:找到图中距离最近的一对端点
计算图中多个端点之间距离是计算机或统计学中常见的例子。这类问题在聚类算法和基因问题中经常出现。
我们以计算城市之间的距离为例,这比计算DNA链间距离更直观。
假设有一个距离矩阵,其第i行第j列的元素代表城市i和城市j间的距离。我们需要写一个函数,输入城市距离矩阵,输出城市间最短的距离,以及对应的两个城市。代码如下:




以下是一个调用该函数的例子:


最小值是6,位于在第3行第4列。可以看到apply()函数在这里起重要作用。
我们的任务很简单:找到矩阵中最小的非零元素。首先找到每行中的最小值—仅一个命令apply()即可对所有行达到此目的—然后找到这些最小值中最小的那一个。但如你所见,这段代码的逻辑还是略微有些复杂。
注意一个很关键的地方,这个矩阵是对称的,因为城市i到城市j的距离与从城市j到城市i的距离相等。因此在找第i行最小值时,只需要在第i+1,i+2,……n等元素中搜索(n是矩阵的总列数)。因此在调用apply()时,矩阵d的最后一行是可以跳过不用管的。
由于矩阵可能很大,如果有1000个城市的话,那么矩阵就有100万个元素。所以我们要利用矩阵的对称性来节省时间。但是这样也带来一个问题:在计算每行最小值时,我们需要知道该行在原矩阵中的行号, 而apply()函数不能直接提供给它所调用的函数。所以在代码的第6行,我们给原矩阵增加了一列,为相应的行号,目的是让apply()函数所调用的函数能够识别出行号。
apply()所调用的函数是imin()函数,它的定义开始于代码第15行,该函数寻找形式参数x所代表的这一行的最小元素及其所在位置的索引。例如,对例子中矩阵q的第一行调用函数imin(),可求出最小值是8,出现在该行第4列的位置。为求得最小元素的位置,第18行使用了函数which.min(),这是R语言中非常方便的函数。
请注意第19行。之前我们利用矩阵的对称性,在寻找最小值的时候跳过了每行的前半部分,这一点可从第18行表达式中的(i+1):(lx-1)看出。但是这也意味着which.min()返回的最小值的位置索引是相对于范围(i+1): (lx-1)的。例如在q的第三行,尽管第4个元素最小,但是which.min()返回的是1。因此我们需要在which.min()的结果上增加i,如第19行所示。
最后,要正确使用apply()的输出结果需要费点儿功夫。对于上面例子中的矩阵q,apply()函数将会返回一个矩阵wmins:

这个矩阵的第二行包含d矩阵的上三角部分各行最小值,第一行则是这些值的索引。例如wmins的第一列表明,q的第一行中最小值是8,位于第4列。
第9行代码选出整个矩阵最小值所在的行号i,在矩阵q的例子中结果为6。第10行则求出该行最小值在第j列,在矩阵q的例子中结果为4。就是说整个矩阵的最小值在第i行第j列,这个信息在11行将会被用到。
同时,apply()输出结果的第一行是各行最小值所在的列。这样我们就可以找出距离最近的城市分别是什么了。我们已经知道城市3是两个城市之一,而wmins第一行第三列对应的是4,因此城市3和城市4是距离最近的,程序第9行和第10行表示了这一推理过程。
如果该矩阵中最小值是唯一的,那么就有更简单的方法:


这个方法可行,但是有一些潜在的问题。上面这段新代码中最关键的一行是:


这段代码直接找到d的最小元素的所在位置。其中参数arr.ind=TRUE指定,which()函数返回的坐标必须是矩阵下标—也就是行号和列号,而不是一个单一的向量索引。如果没有这个参数,d就会被当作向量来处理。
前面提到过,这段新代码只有当最小值唯一时才有用。如果此条件不成立,which()函数会返回多个行/列数对,与我们的目标相悖。如果我们用原来那版代码,当d有多个最小值时,只会返回其中一个。
另外一个问题是效率。新的代码实质上包含两个(隐性的)循环:一个是计算最小值smallest,另一个是调用which()。因此新的代码会比原来那版更慢。
在这两种方法中,如果特别关注运行速度,并且可能有多个最小值,那么最好选择原来那版代码,否则就选用另一个。而后者的简洁性使之更容易阅读和维护。
3.5 向量与矩阵的差异
在本章开始的时候,我说过矩阵就是一个向量,只是多了两个属性:行数和列数。这里,我们再深入说明这个问题。考虑以下例子:




因为z是向量,因此我们可以求它的长度:


但是作为一个矩阵,z不仅仅是一个向量:


换句话说,从面向对象编程的角度说,矩阵类(matrix class)是实际存在的。如第1章所说,R的大部分类都是S3类,用$符号就可访问其各组件。矩阵类有一个dim属性,是一个由矩阵的行数和列数组成的向量。本书第9章讲详细介绍关于类的更多细节问题。
以用dim()函数访问dim属性:


行数和列数还可以分别用nrow()和ncol()函数访问:


这些其实都是对dim函数的一个简单封装。我们之前提到,在交互式模式中,只要直接输入对象名称就可以看见它的内容:


当要写一个以矩阵为参数的通用库函数,上面这几个函数将会很有用。因为能直接得到该矩阵的行数和列数,就不再需要两个额外的参数来输入行数和列数,这样更省事。这是面向对象编程的好处之一。
3.6 避免意外降维
在统计学领域,“降维”(dimension reduction)是有益的,也存在很多降维的统计学方法。假设我们需要处理10个变量,如果能把变量个数降到3,却还能保留数据的主要信息,何乐而不为呢?
但是在R里,降维指的完全是另外一件事情,而且通常要避免。比如我们有一个4行的矩阵,提取其中的一行:


这个看似没有问题,但是注意看r的显示格式,是向量的格式,而非矩阵的格式。也就是说,r是一个长度为2的向量,而不是一个1乘2的矩阵。我们可以用几种方法来验证它的确已经变成向量了:


可以看到z是有行数与列数的,但是r没有。类似的,str()显示z的行索引区间为1:4,列索引区间为1:2。而r的索引区间是1:2。毫无疑问,r是一个向量而非矩阵。
把r变成向量看似没有问题,但在某些涉及大量矩阵操作的程序中会引起错误。也许程序在大部分情况下都能正常运行,但在少数情况下就是通不过。例如某个程序从一个给定的矩阵里提取一个子矩阵,然后对这个子矩阵进行一些矩阵操作。如果这个子矩阵只有一行,R会把它当作向量处理,后面的矩阵操作无法运行在这个向量上,程序会出错。
幸好R里有办法禁止矩阵自动减少维度:使用drop参数。仍以上述矩阵z为例:


现在r是一个1乘以2的矩阵而非由两元素组成的向量。
因此,需要经常性地在矩阵操作代码里使用参数drop=FALSE。
为什么说drop是一个参数呢?因为 [ 事实上也是一个函数,跟+等操作符一样。请看以下代码:


对原本就是向量的对象,可以使用as.matrix()函数将其转化为矩阵,如下所示:


3.7 矩阵的行和列的命名问题
访问矩阵元素最直接的方法是通过行号和列号,但也可以使用行名与列名。例如:


如上例所示,这些名称可以用来访问指定的列。rownames()函数的功能与此类似。
一般在编写R代码时,给行和列命名并不是那么重要,但在分析某些数据时会很有用。
3.8 高维数组
在统计学领域,R语言中典型的矩阵用行表示不同的观测,比如不同的人,而用列表示不同变量,比如体重血压等,因此矩阵一般都是二维的数据结构。但是假如我们的数据采集自不同的时间,也就是每个人每个变量每个时刻记录一个数。时间就成为除了行和列之外的第三个维度。在R中,这样的数据称为数组(arrays)。
举个简单的例子,考虑学生和考试成绩的数据。假设每次考试分为两个部分,因此每次考试需要给每个学生记录两个分数。假设有两次考试,只有三个学生。第一次考试的数据是:


学生1在第一次考试中得分为46和30分,学生2得21和25分。第二次考试的成绩是:


现在要把两次考试的成绩合并到一个数据结构里,命名为tests。tests共分为两个数据层(layer),一层对应一次考试,每层都是三行两列。firsttest在第一层,sedondtest在第二层。
第一层的三行对应于第一次考试的三个学生,每行的两列对应于考试的两个部分成绩。用array()函数创建这个数据结构:


参数dim=c(3,2,2) 指明数据共有两层(第二个2),每层分别有三行两列。这个参数最后会成为数组的dim属性。


tests中的每个元素现在都有三个下标,比矩阵多一个。这三个下标按顺序与$dim向量中的三个元素一一对应。例如,学生3在第一次考试的第二个部分中的得分如下:


print函数会逐层显示出数组的内容:


我们之前把两个矩阵合并成一个三维数组,同样,也可以把两个或更多个三维数组合并成四维的数组,以此类推。
数组的一个最常用的场合是表(table)的计算。参见6.3节中三维表的例子。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

2013-6-24 09:24:57
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

点击查看更多内容…
相关推荐
栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群