数据分析技术丛书

R的极客理想——工具篇

张丹　著

ISBN：978-7-111-47507-1

本书纸版由机械工业出版社于2014年出版，电子版由华章分社（北京华章图文信息有限公司）全球范围内制作与发行。

版权所有，侵权必究

客服热线：+ 86-10-68995265

客服信箱：service@bbbvip.com

官方网址：www.hzmedia.com.cn

新浪微博 @研发书局

腾讯微博 @yanfabook

目录

序一

序二

前言

第一部分 R基础

第1章 R语言基础包

1.1 R是最值得学习的编程语言

1.2 R的历史版本安装

1.3 fortunes记录R语言的大智慧

1.4 formatR代码自动化排版

1.5 多人在线协作R开发RStudio Server

1.6 R和JSON的傻瓜式编程

1.7 R语言的高质量图形渲染库Cairo

1.8 caTools：一个奇特的工具集

第2章 时间序列基础包

2.1 R语言时间序列基础库zoo

2.2 可扩展的时间序列xts

2.3 时间序列可视化plot.xts

第3章 R性能监控包

3.1 R语言本地缓存工具memoise

3.2 R语言性能监控工具Rprof

3.3 R语言性能可视化工具lineprof

第二部分 R服务器

第4章 R语言的跨平台通信

4.1 Rserve与Java的跨平台通信

4.2 Rsession让Java调用R更简单

4.3 解惑rJava R与Java的高速通道

4.4 Node.js与R跨平台通信

第5章 R的服务器实现

5.1 R语言服务器程序Rserve详解

5.2 Rserve的R语言客户端RSclient

5.3 FastRWeb：跑在Web上的R程序

5.4 R语言构建Websocket服务器

第三部分 数据库和大数据

第6章 数据库和NoSQL

6.1 RMySQL数据库编程指南

6.2 R利剑NoSQL 之MongoDB

6.3 R利剑NoSQL之Redis

6.4 R利剑NoSQL之Cassandra

6.5 R利剑NoSQL之Hive

6.6 用RHive从历史数据中提取逆回购信息

第7章 RHadoop

7.1 R语言为Hadoop注入统计血脉

7.2 RHadoop安装与使用

7.3 RHadoop实验：统计邮箱出现次数

7.4 RHadoop实现基于MapReduce的协同过滤算法

7.5 rHBase安装与使用

7.6 解决RHadoop安装错误：PipeMapRed.waitOutputThreads（）

第四部分 附录

附录A Java环境安装

附录B MySQL数据库安装

附录C Redis数据库安装

附录D MongoDB数据库安装

附录E Cassandra数据库安装

附录F Hadoop安装

附录G Hive环境安装

附录H HBase安装

序一

生活中我是三分熟的牛排，张丹兄是五分熟的牛排，一天他给我发邮件邀请我写序，我好些天都没答复，为什么呢？因为我们互相都不熟啊！嘿嘿，好冷。从没有人找我写过序，张丹有胆找一个浑身负能量爆棚的新手及话痨写，还说自由发挥，风格不限，作者自己都不怕，我怕啥，大不了把脖子往你们面前一横！当然那是张丹的脖子……其实我自己看书一般不太看推荐序（除非里面有重大八卦），因为推荐序里通常也就是友情帮点一百二十八个赞，你要是像鲁迅先生那样半夜爬起来翻看推荐序，当然你肯定不会看见“吃人”两个字，而是看见一个特殊的变量值：NULL。哎呀，我这只是夸张的说法啦，推荐序没那么糟糕，我也并非和张丹完全不熟，我看过他写的很多博客文章。好了，言归正传。在下谢益辉，身患统计学永久性脑损伤，目前在RStudio当码农，天天蹲A村村口敲代码。十年前在中国人民大学统计学院上大三（哥也年轻过），侥幸没被统计计算课上的R语言折磨死，按照我们疯人院（没有含沙射影的意思，请读者切勿自动匹配）的规矩：你上回没弄死我，这次你在前面跑，换我来弄死你好了。就这样R语言成了我快乐生活的一部分。咦，为什么有一种细思恐极的感觉？

作为一名不那么极的R极客，我自然乐意看到一本写给极客的书。在这个数据时代（千万别再跟我提“大”数据，否则我立刻变身纯生牛排给你看），各种新技术风起云涌，需要有人坐一段时间冷板凳，为我们收拾整理这些技术，让那些从证明三种中心极限定理的苦海中逃出来的研究生们毕了业不要立马又陷入另一片五种数据库引擎的苦海。我花了一晚上加一白天的时间才看完《R的极客理想》的书稿，看不懂和看得懂的地方都跳着看，看不懂主要是因为我没有计算机背景（我学习的第一门计算机语言是VB，你们笑我可以，别笑出声就好，不过我还写过VBScript呢，这次你们可以笑出声了），例如Java，我多年前曾经自学过一阵，现在都忘光了，写个“你好世界”的程序都需要搜一下；看得懂的地方主要是纯R的内容，不过有些地方还是慢慢看了，比如formatR包那里，主要是看张丹有没有黑我。算他识相没黑我，那好，我在序言里可以放心……黑他了；江湖险恶，你不中箭谁中箭？

整本书涵盖的内容比较广，每一节的篇幅相对较短，我觉得这种写作风格挺好，每天晚上睡前打开一节看看，在口水浸湿书之前应该可以看完一节，既能学到知识又合理利用了睡前时间。如作者自己所说，这书不是写给初学者的，所以看第1章的不要被“R基础”的标题给骗了，全是些奇门遁甲之术，一个基础包都没介绍！虽然我不敢讲R是不是最值得学习的程序语言，但我对1.1节的观点深表同意：R往往用两三行代码解决问题，不会时时考虑最优与否（很多需要优化的地方都已经用C/Fortan等底层语言打包好了）。R“它爹”S语言的主要作者John Chambers在几十年前就说了：S语言的目的是让我们（统计学家）快速而可靠地把脑子里的想法变成软件。将数据拿到手之后甩开膀子从各个角度去分析就好了，想画图画图，想跑模型跑模型，而不必先考虑定义一个结构体以及某个成员是整型还是浮点型。书里主要用到的操作系统是Windows和Ubuntu，不过苹果（OS X）用户不必担心，R在OS X上跑起来也是妥妥的，个人推荐用Homebrew安装R。第1章介绍了一系列奇特R包，从中我可以看出作者钻研这些包的乐趣，极客需要这种小乐趣的推动，后面书中我们还会持续看到作者钻研的迹象，他胆敢把自己失败的经历都写出来，实在是很勇敢，但这也反映了极客的真实生活嘛，哪有干什么都一帆风顺的？奇特R包系列里包括了我的formatR包，这个包有几处小小的坑，提醒广大读者注意看文档，代码格式主要还是得靠程序员自觉，不能老依靠formatR这样的自动整理代码的包。fortunes包里的确有大智慧（不是炒股软件），但我感觉主要还是各种恶搞和冷笑话，码农的心思你别猜，别猜别猜。RStudio服务器版是个好东西，未来的趋势可能是啥都存储在服务器上，浏览器控制一切，再也没有黑乎乎的SSH窗口。不过前两天我一个同学给我打电话哭诉我坑了他，因为RStudio的服务器版太好用了，他把代码都放服务器上，每天就在浏览器里写代码，不再从本地传来传去，结果服务器硬盘坏了，没备份。我感到很不好意思，这周打羽毛球我都没敢再叫他。RJSONIO的作者本来是我很敬佩的一位大极客，可能是R界兴趣最广泛的极客之一，可他的代码极少写单元测试。导致我被坑了几次之后越来越不敢相信他的编码质量，哎，上天给聪明极客的惩罚就是让他们失去单元测试。不过话说回来，什么JavaScript/JSON啊，HTML/CSS啊，都是非常容易入门的技术，作为统计出身的我，大力推荐大家掌握一点这种低投入高产出而且又有趣的计算机知识。Cairo是另一位大极客的作品，我在他手下呆过两个多月，其聪明程度以及掌握的计算机知识之广总令我惊叹，例如后面章节中介绍的Rserve、FastRWeb以及RCassandra都是他单枪匹马的作品，一个比一个极，问题就是极过了以至于很少有人知道，所以也没见广泛应用。有时候我都想，你懂得这么多东西你家里人知道吗？他家里人知不知道没关系，起码现在我们知道张丹帮了个大忙，让他的几份作品至少多了一些中国读者。有志于成为极客的码农们，在忍受孤独的同时，我觉得真的应该好好想想为什么你做的东西没有广为人知，就算你不想这个问题，也该想想怎么到今天还没有女朋友吧，这两个问题背后肯定有共同原因。如张丹引用我的话所说，Cairo包的高质量在现今的R版本中已经不算太大优势了，因为R本身已经支持Cairo库，例如其PNG图片输出和Cairo包的输出质量上几乎是一样的，但有一个特例除外，就是散点图中点的形状为某一个类型的圆点时（看不懂这句话的人请查阅points函数的帮助），基础R画出来有锯齿，而Cairo包没有，至于pch取值多少时有锯齿，这问题就留给你们自己去探索吧。caTools这个神奇到莫名其妙的包，某种程度上反映了一些R包作者广泛的兴趣以及没有正规计算机背景的特点，这事情难说好坏。

也许是缘于对金融的兴趣，张丹在第2章介绍了时间序列数据的处理。金融是才疏学浅的我不太能理解的行业注定了我穷困一生的命运），时间序列也是我比较弱的功课，除了R自带的ts）函数以及简单的ARMA模型，我脑子里剩下的时间序列的知识已经没多少了。zoo和xts是我听过无数遍但从来没使过的R包，从此也可以看出R的应用领域太广泛了，我用了10年R也没用过这两个流行的包，主要是因为我不太做时间序列相关的工作。看完这一章我觉得读者不妨也研究一下R的基础图形，主要是graphics包，掌握一点基本的画点画线技术，对图形的灵活应用会很有帮助，如今R的图形天下基本被ggplot2占据，但我还是老土的基础“图形党”没办法，我学R的时候ggplot2还没出生），我觉得不是所有的数据都适合ggplot2的。这一章介绍了时间序列数据处理以及可视化，如果是做时间序列的预测，我听说过无数遍forcast包，但至今未得一试。

R的性能一直以来都是计算机专业人士对R的槽点，张丹和我都表示没有压力，但这不表示性能不是问题，当然谁都希望自己的程序跑得快，好省下几分钟时间去写另外300行C代码提升下一个程序的速度。第3章提供了提速以及找代码瓶颈的工具。对于memoise包本身我其实没太大兴趣，但它的源代码是值得一看的，R里面的函数或称闭包，Closure）和环境是很有意思的话题，路远坑多，慢走不送。性能监控也是一个优化代码的重要手段，作者介绍了基础工具Rprof()以及酷炫工具lineprof包，让我们知道自己的代码的瓶颈所在。最后作者讲，R语言需要更多IT人的推动，我实在不能同意更多。R作为统计学家写给统计学家的语言，总是会有些坑，需要专业人士来帮忙填补；另一方面，这帮顽固的统计学家完全无视界面的重要性，看看R官方网站有多朴素就知道了，简直是土得连渣都掉不下来，你跑去跟他反映，他只是一道冰冷眼神就可以杀死你。话说做网页前端的IT人士你们在哪儿呢？

第二部分的两章介绍R的服务器应用，前面说了我是Java外行，所以不敢乱点评。只记得那时候Urbanek大人一路上手舞足蹈给我解释FastRWeb的原理，然而我回去看到/var/FastRWeb/这个目录的时候就已经暗自决定抛弃它了。我个人对那些需要sudo才能运行且存放在非标准位置的程序有抵触心理，因为我下次一定会忘记怎么运行它以及配置文件在什么位置。Rserve和FastRWeb在它们被发明的年代里绝对都是划时代的，在服务器端跑基于R的服务是很多码农的梦想，还记得那些年我们一起追的Rweb吗？如今回头看看，还有多少人记得并用着CGI？可喜的是，张丹在第5章也介绍了WebSocket技术，这也是一个相对较新、很有趣且有用的话题，建议读者好好研究。书中提到websockets包已经被移出CRAN，什么原因我也不知道，不过我基本上确定httpuv包可以取代它，也许是websockets的作者看到httpuv的工作之后决定不要重复劳动了。httpuv包是shiny包的核心技术之一，如今捣鼓R的服务器端应用，怎么能忽略shiny？shiny比Rserve/FastRWeb出现晚了近十年，为什么前者迅速流行起来，而后者尽管带着划时代的思想和技术，却被广大用户忽略，极客们应该再次好好想想。有读者可能会说，切，shiny是你们厂（RStudio）的产品，你当然自卖自夸啦！是不是这样呢，我们且留给时间检验。

第三部分的两章就是数据库八仙过海各显神通了，我仅仅粗浅了解一点MySQL，请Hadoop/NoSQL同行们不要笑出声。作为入门教程，这些章节都不错，从安装到“你好世界”的例子都有介绍，十八般武艺入门之后的事情大家都知道，遇到问题搜索就是了。

极客不是一种身份，而是一种态度。在我眼里，这个词是中性的，极客不代表一个人有多牛，而是他的钻研态度、好奇心以及对新技术的识别和接受能力。有些很牛很聪明的人，未必能把聪明才智转化为生产力（请勿对号入座）。张丹这本书给大家提供了一条通向R的极客之路，但这绝对不是终点。技术人士容易沉迷于技术，就像科学研究人士迷信某一种科学一样，唉，我就是这样浑身负能量。希望读者通过这本书能感受到作者探索的乐趣，保持开放心态，积极学习，然后找到适合自己的极客理想（以及女朋友！相信我，后者会让前者更快实现）。写序似乎应该说点鼓励的话吧，我没写过也不清楚贵圈的规矩，那么就引用麦太的话好了：从前有一位小朋友他很努力学习，后来他发财了。

谢益辉

2014年6月23日于A村

（吝啬的房东一直不给我修空调，已热哭，决定在最后这个黄金广告位狠狠黑她一把，叫她随便得罪码农！）

序二

我有时会问自己会不会因为名字而买一本书？当我看到“R的极客理想”时，我就找到了答案。把这个书名做个分词，去除停止词之后有三个词：“R”、“极客”和“理想”。这是耐人寻味的三个词。

R是我现在谋生的工具，我对它有着十多年的感情。我亲历了R从和GAUSS进行比较的时代走过和SAS进行比较的时代、来到了和Python及Julia这样的语言进行比较的时代，从最开始的无人问津到如今的炙手可热。R的资料在互联网上可以说汗牛充栋，但是中文书籍仍然很少。张丹是圈内著名的博主，明永玲是圈内著名的编辑，有幸被邀约写这篇序言时，我对这个组合非常看好。

阅读内容之后，我发现这本书有太多和其他R语言书籍不一样的地方。传统的R语言书籍大多是基于统计的思维展开的，通过介绍统计方法在R中的实现来学习R，这就使得很多统计出身的用户可以很容易地和其他统计软件进行类比，从而加速学习的过程。进入大数据的时代后，R作为数据处理的神器也越来越受关注，R语言的书籍也开始以数据为中心，从数据的获取、处理、分析一直到可重复研究和可视化展现，在应用层面进行全方位的介绍。但是，作为一种编程语言，程序员视角的R书籍还是非常少的。张丹的这本书刚好可以覆盖这部分的内容。

极客是R圈中比较少见的一种生物，尤其是来自极客之乡IT界的正宗极客。R的最初用户基本上都是统计圈的，但是最近几年R能够在国内越来越火，主要得益于IT界的贡献。R在欧美火的时间更早，但发展的趋势也大抵如此。从R的本性来说，它本不该是极客关注的语言，因为其对外部功能延伸的追求远甚于对内部语言完美的追求，从S语言设计之初就声明了人的时间远比计算机的时间宝贵，尤其是分析建模人员的时间。这种比较乡愿的风格最初是不为极客所喜的。但是因为其极端易用，在惜时如金的产业界快速地流行了起来，自然而然地产生了大量的难题。于是高贵冷艳、魅惑狂狷的极客们就参与进来了。我认识的张丹，就是这样一位极客。

R是一个很奇怪的东西，没有编程基础的人可以很容易地入门，但是很难有信心觉得自己成了高手。编程高手初学时常常是破口大骂，但是很快就中了R的迷毒。说到底，还是因为R本质上是统计学家发明的语言，和模型打交道的能力比和计算机打交道的能力更强大。虽然如此，随着R的扩展包越来越丰富，用户在享受便利性的同时也增加了理解上的风险，函数背后的机制不再是黑箱，那么，R中的IT高手的见解就变得非常重要。从他们的视角来看R、使用R，无论是对于统计背景还是IT背景的用户都有很强的借鉴意义。

R流的是实用主义的血，看上去和理想是背道而驰的。但R毕竟是没有灵魂的工具，它的性格应该取决于用它的人。在张丹的这本书里，我看到了理想的光辉。我看过不少书也自己写书，感觉介绍知识是最简单的事，但是表明观点是最困难的。对于作者来说，观点越不鲜明就越能避免犯错。但是对于读者来说，尤其是初学的读者，很容易陷入对自己的怀疑中。而张丹的这本书会直接告诉读者应该怎样做，照着代码操作一遍就能解决问题。虽然有些建议可能不是最好的解决方案，但至少是足够好的，在实际的应用中可以解决问题。在这一点上，理想主义的作者和实用主义的R实现了完美的结合。

当然，整本书在“R”、“极客”、“理想”之间实现了更加完美的结合。

李舰

2014年8月1日于上海

前言

为什么要写这本书

我是一名程序员，前后做了10年的程序开发工作。在这10年间，我从程序员一路做到架构师，经历了太多的系统和应用。我做过手机游戏，写过编程工具；做过大型Web应用系统，写过公司内部CRM；做过SOA的系统集成，写过基于Hadoop的大数据工具；做过外包，做过电商，做过团购，做过支付，做过SNS，也做过移动SNS。以前只用Java，然后开始用PHP……如同其他程序员一样，我一度陶醉于追求各种技术的创新，但始终有一个问题困扰着我，那就是如何才能够将我所掌握的技术转变成价值？这就好比我面对着一座金山，我拥有先进的技术，可以制作各种性能稳定、功能卓越的挖掘机器，但我不懂如何将矿石提纯，变成金子！每每看到别人利用我的技术挖掘出金子时，我只能满脸的羡慕，心中无限的不甘。

直到遇见R语言，我豁然开朗。R语言为我从另外一个角度开启了宝藏的大门，也让我对自己的职业重新思考、规划，最后坚定了我向统计、金融行业的转型。如果你也存在以上的问题，不如随着本书一起进入R语言的世界，领略R语言特有的魅力，通过对R语言的学习，重新认识大数据的价值，更深一步地提升个人价值。

随着我与统计、金融领域的朋友交流地逐步深入，我深刻地体会到，他们对R语言的实际使用也存在着很大的问题和困惑。比如，他们在某些实验室环境下，使用R语言可以很轻松、很顺利地实现预期效果，但是移植到真实环境下，面对浩瀚繁复的大数据，在使用R语言的过程中出现了很多问题。这就好比面对一座金山，他们掌握着先进的提纯技术，但他们所使用的挖掘、采集工具却还停留在石器时代！使用工具的落后，使他们要面对大量R语言之外的问题，这让他们应接不暇，甚至崩溃！有的人甚至因此认为，R语言只是一种实验室语言，至少以现在的技术水平无法将它运用到现实生活中，R语言在现实生活中广泛应用，简直是天方夜谭！

是的，如果你是一名没有计算机背景的R语言使用者，你在实际使用中也同样会遇到许多这样或那样的问题，面对这些棘手的问题寝食难安，尝试着通过各种方式寻求解决方案。其实，在计算机领域，这些问题已经早就有了成熟、有效的解决方案。

本书的内容来自我在R语言实际使用过程中的经验总结，基本都是我在工作中使用R语言的真实记录，其中涉及计算机、互联网、数据库、大数据、统计、金融等领域，详细总结了R语言在实际使用时与Java、MySQL、Redis、MongoDB、Cassandra、Hadoop、Hive、HBase等技术综合运用的解决方案，具有实战性，可操作性强。如果你与R语言接触时间不长，本书可以让你看到R语言在各行业、各领域所散发的魅力；如果你在某行业使用R语言已经有一段时间了，可能在使用R语言的过程中遇到了瓶颈，本书将让你看到R语言在与其他计算机语言结合后所迸发的强大活力；如果你是技术人员，本书中有全局观的案例实施，也许会给你带来新的启发，甚至跟我一样，重新规划自己的职业生涯，找到学习、奋斗的新方向；如果你是企业的中高层管理者，在本书中可以看到我们已经实现的技术成果，如果需要，你甚至可以按照书中记录的详细操作步骤，直接在企业环境中实施，直接获利！

在此，我不得不强调，本书不是入门书，不讲R的语法，如果你想学习R语言的基础语言入门知识，那么，你来错地方了。但是，如果你已经具备了一定的R语言基础，但不一定具有计算机语言背景，我将告诉你R语言在真实环境下到底都能够做什么，并且详细地告诉你怎样一步一步地实施。

在与各界R语言初学者的交流中，我发现，入门后，学习R语言最大的问题，在于如何使用R语言的众多软件包，而介绍这方面的图书很难找到，只有一些网上流传的小册子。本书涉及了30个R语言包，并结合我的使用心得及案例分析，相信会解决大家R语言入门后的困扰。

本书是“R的极客理想”系列图书的第一本，姊妹篇《R的极客理想—高级开发篇》将深入介绍R语言底层原理，并使用R语言开发出企业级的应用。

本书的使用环境涉及Linux Ubuntu和Windows 7两种操作系统，R语言包的2.15.3和3.0.1两个版本，在每一节中都有明确的标识。

R语言还在不断地进步和更新，它将引导一场数据的革命，跨学科的结合是时代趋势，也是我们的机遇！

读者对象

本书适合以下R语言工作者：

·计算机背景的软件工程师；

·数据库背景的DBA；

·数据分析背景的数据科学家；

·统计背景的科研工作者；

·大专院校相关专业的学生。

如何阅读本书

本书的内容分为四个部分。

第一部分是R基础（第1～3章），介绍了为什么要学习R语言，R语言不同版本的安装，以及R语言中常用的12个软件包。帮助读者快速了解R语言的工具包、时间序列包和性能监控包。

第二部分是R服务器（第4～5章），介绍了R语言与其他编程语言的通信，以及R语言作为服务器的应用。帮助读者打通R语言与其他编程语言的通道，并实现R语言的服务器应用。

第三部分是数据库和大数据（第6～7章），介绍了R语言与各种数据库的通信，以及R语言与Hadoop集成。帮助读者打通R语言与各种数据库层的通道，并实现R语言对基于Hadoop大数据的处理。

第四部分是附录，介绍了Java、各种数据库以及Hadoop的安装方式。笔者希望读者可以在不借助其他参考书的情况下，完成书中所有实例。

本书为工具书，每节之间没有特别的顺序要求，你可以选择任何你感兴趣的章节进行阅读。如果你是一名初学者，想全面掌握R语言，请按顺序阅读全部的章节。

勘误和支持

由于笔者的水平有限，加之编写时间仓促，书中难免会出现一些错误或者不准确的地方，恳请读者批评指正。为此，笔者创建一个在线的图书交流网站（https://onbook.me），方便与读者进行沟通。如果读者在阅读过程中遇到问题，也可以在网站中留言，我将尽量在线上为你提供最满意的解答。书中的全部源代码都可以从华章公司网站（www.hzbook.com）或本书交流网站下载，我也会及时更新代码。本书为黑白印刷，关于更绚丽的彩色图片，读者运行源代码即可看到。如果你有什么宝贵意见，欢迎发送邮件至bsspirit@gmail.com，期待能够得到你真挚的反馈。

致谢

感谢我的团队，林伟林、林伟平、邓一硕，让我们因R语言走到一起。感谢机械工业出版社华章公司的编辑明永玲，帮助我审阅全部章节，引导我顺利完成书稿。感谢我的爸爸、妈妈和爱人，感谢你们对我工作上的支持和生活上的照顾！

谨以此书献给我最亲爱的家人以及众多R语言爱好者们！

张丹

2014年5月于中国北京

第一部分　R基础

■第1章　R语言基础包

■第2章　时间序列基础包

■第3章　R性能监控包

第1章　R语言基础包

本章主要介绍了为什么要学习R语言，R语言软件的安装，R语言的开发工具，以及R语言中常用的几个软件包，以帮助读者快速了解R语言的工具包，激发读者对R语言的学习兴趣。

1.1　R是最值得学习的编程语言

问题

为什么要学R语言？

引言

如果要问在Node、Lua、Python、Ruby和R这5种语言中，哪个语言在2014年的应用前景会更好，我会毫不犹豫地选择R，而且我认为R语言不仅是2014年，也是以后更长一段时间内的明星。在本书开篇，我们就谈谈为什么R语言是最值得学习的编程语言。

1.1.1　我的编程背景

本人是程序员、架构师，从编程入门到今天，一直深信着Java是改变世界的语言，Java已经做到了，而且一直很辉煌。但当Java越来越强大，覆盖领域越来越多，变得无所不能的时候，它反而不够专业，这就给了其他语言发展的机会。

我已使用Java语言11年，R语言3年，Node 1年，对于这个问题“哪个语言在2014年的应用前景会更好”，我选择R语言。

1.1.2　为什么我会选择R

从下面的9个方面来说明我选择R的原因。

·R的基因

·R的发展

·R的社区和资源

·R的哲学

·R的使用者

·R的语法

·R的思维模式

·R解决的问题

·R的不足

1.R的基因

1992年，新西兰奥克兰大学的Ross Ihaka和Robert Gentleman两位统计学家，为了方便教授初等统计课程，发明了一种编程语言，因为他们名字的首字母都是R，于是R便成为这门语言的名称。

从开始学习R语言，我就开始了知识的跨界思考。统计学基于概率论，概率论又基于数学，用计算机的方式编程，同时解决某个领域的实际问题。多种学科知识的交集，决定着我们解决问题的能力。统计的基因，让R语言与众不同！

2.R的发展

R一直在小众领域成长着，最早也只有统计学家在用，主要用来代替SAS做统计计算。然而时代在进步，随着大数据的爆发，R终于在这一波浪潮中被工业界所发现。然后，越来越多有工程背景的人加入到这个圈子，对R的计算引擎、性能以及各种程序包进行改进和升级，让R获得了新生。

我们现在用到的R语言软件，已经越来越接近工业软件的标准了。由工程师推动的R的发展，其速度远远地超过了由统计学家推动的发展。随着人们对数据分析需求的进一步增加，R会以更快的速度继续发展，将成为免费的、开源的数据分析软件的代名词。

3.R的社区和资源

R的发展离不开R的各种社区支持，尤其是R的官方社区支持。在R的官方网站中，我们可以下载到R语言软件、R的第三方软件包和R的其他支持软件。当然，我不得不承认R的官方网站（http://www.r-project.org/）从Web页上看起来太简陋了，稍微调整一下CSS样式表，都会比现在好看很多。也许这种简单、无修饰也是统计学家的基因吧。R语言的社区资源同其他语言一样丰富，除了官方社区，还有开发者论坛（http://r.789695.n4.nabble.com/）、R-Journal列表（http://journal.r-project.org/）、软件包列表、R语言图书列表以及R用户组等。

R是自由软件，因此开发者可以开发自己的软件包，封装自己的功能，然后在CRAN （http://cran.rstudio.com/）上面发布。截止到2014年2月，共有5236个R包在CRAN上面发布。可能很多人会说只有5236个包，数量太少了。这是因为CRAN是需要提交申请的，每个包都必须经过R语言小组审核、检查后才会发布出来，而且审核非常严格。高质量是发布一个新的R包的基本要求。由于CRAN过于严格的审查，让很多开发者选择在RForge（https://r-forge.r-project.org/）上发布R包，还有些R包是基于Github发布的，我也在Github上面发布了自己的R包：https://github.com/bsspirit/chinaWeather。

下面列出与R语言相关的主要社区和资源。

·R官方网站：http://www.r-project.org/

·R开发者论坛：http://r.789695.n4.nabble.com/

·CRAN：http://cran.rstudio.com/

·RForge：https://r-forge.r-project.org/

·R新闻和博客：http://www.r-bloggers.com/

·统计之都：http://cos.name/

4.R的哲学

每种语言都有自己的设计理念和哲学，而我体会的R的哲学就是“静下心做事情”。R不需要很长的代码，也不需要设计模式。一个函数调用，传几个参数，就能实现一个复杂的统计模型。我们需要思考的是用什么模型、传什么参数，而不是怎么进行程序设计。我们可能会用R实现“从一个数学公式，变成一个统计模型”的过程，我们也可能会考虑“如何让一个分类器结果更准确”，但我们不必思考一个算法的“时间复杂度是多少，空间复杂度是多少”。

R的哲学，可以让你把数学和统计学的知识，变成计算模型，这也是R的基因所决定的。

5.R的使用者

R语言早期主要是学术界统计学家在用，后来逐渐被其他很多领域的学者所用。R语言有各种不同的应用领域，包括统计分析、应用数学、计量经济、金融分析、财经分析、人文科学、数据挖掘、人工智能、生物信息学、生物制药、全球地理科学、数据可视化等。

近几年，由互联网引发的大数据革命让工业界的人开始认识R，加入R。当越来越多的有工程背景的人加入到R语言使用者的队伍后，R才开始向着全领域发展，逐步实现工业化的要求。现在，R已不仅仅是学术界的语言，它还是工业界必备的语言。

下面列出一些推动R语言在工业界发展的R包。

·RevolutionAnalytics公司的RHadoop产品，让R可以直接调用Hadoop集群资源。

·RStudio公司的RStudio产品，给了我们对编辑软件新的认识。

·RMySQL、ROracle、RJDBC打通了R和数据库之间的访问通道。

·rmongodb、rredis、RHive、rHBase、RCassandra打通了R和NoSQL数据库之间的访问通道。

·Rmpi、snow打通了单机多核并行计算的通道。

·Rserve、rwebsocket打通了R语言的跨平台通信的通道。

6.R的语法

R是面向对象语言，语法如同Python。但R的语法很自由，很多函数的名字看起来都是那么随意，这也是R的哲学的一部分吧！例如，看到如下这样的赋值语法，有其他语言基础的程序员，肯定会崩溃的。

> a<-c(1,2,3,4)->b

> a

[1] 1 2 3 4

> b

[1] 1 2 3 4

随机取正态分布N(0,1)的10个数，又是这么的简单。

> rnorm(10)

[1] -0.694541401 1.877780959 -0.178608091 0.004362026

[5] 0.836891967 1.794961298 0.115284187 0.155175219

[9] 0.464028612 -0.842569561

用R画鸢尾花的数据集的散点图，有非常好的可视化效果。

> data(iris) #

加载数据集

> head(iris) #

查看前6

行数据集

Sepal.Length Sepal.Width Petal.Length Petal.Width Species

1

5.1         3.5          1.4         0.2  setosa

2          4.9         3.0          1.4         0.2  setosa

3          4.7         3.2          1.3         0.2  setosa

4          4.6         3.1          1.5         0.2  setosa

5          5.0         3.6          1.4         0.2  setosa

6          5.4         3.9          1.7         0.4  setosa

输出结果见图1-1。

图1-1　鸢尾花的数据集的散点图

正是因为R自由的哲学，让R的语法独特而简洁，我已经喜欢上这种哲学了。

7.R的思维模式

R语言让我跳出了原有的思维定式。使用R语言，我们应该像统计学家那样思考问题，而不是拘泥于程序员的思维模式。统计学家的思维模式，是先考虑为什么，再考虑做什么。而程序员的思维模式，是直接考虑怎么做，等有了结果再考虑为什么。

R语言是直接面向数据的语言。在我们的日常生活中，无论做什么事情都会产生数据，上网有浏览数据，买东西有消费数据，就算什么都不干，也会受大气PM2.5的影响，有空气污染指数数据。利用R语言，我可以直接分析这些数据。面向什么业务，就分析什么数据，不需要从产品经理向程序员的角色转换，不需要考虑有什么功能，更不需要考虑程序设计的事。跳出程序员的思维模式，我们所能认知的东西会更多，于是也能找到更适合自己的定位。

8.R解决的问题

当数据成为生产资料的时候，R就是为人们能运用生产资料创造价值的生产工具，R语言主要解决的是数据的问题。整个人类文明所获得的全部数据中，有90%以上是自互联网诞生以来产生的；当Hadoop帮助人们解决了大数据存储的问题后，如何发现数据的价值，则成为当前最火的话题。R语言具有强大的统计分析能力，这就让它成为数据分析最好的工具。所以，R要解决的问题，就是如何挖掘数据价值的问题。

9.R的不足

尽管前面说了R的各种优点，但我们依然不能说R就是完美无缺的，因为R也有很多不足。具体来说，R的缺点有下面5个。

·R软件是统计学家编写的，并不如软件工程师编写的软件那么健壮。

·R软件的性能，存在一些问题。

·R语言很自由，语法命名不太规范，需要花时间熟悉。

·R语言的内核编程，要比普通的R包使用，难度大得多。

·R语言结合了很多数学、概率、统计的基础知识，学起来有一定门槛。

R的这些不足，都是可以克服的。当有更多有工程背景的人加入的时候，R语言会比现在更强大，会帮助使用者创造更多的价值。

1.1.3　R的应用前景

R可以做所有SAS能做的事情。SAS系统全称为Statistics Analysis System，是国际上最知名的商业分析软件工具之一。SAS用于决策支持的大型集成信息系统，其重要组成部分和核心功能是统计分析功能。在数据处理和统计分析领域，SAS系统被誉为国际上的标准软件系统，堪称统计软件界的巨无霸。

R和SAS处于完全的竞争的关系中，R的免费和开放，让R有着更广阔的应用前景。下面给出当今R应用最热门的领域。

·统计分析：统计分布、假设检验、统计建模。

·金融分析：量化策略、投资组合、风险控制、时间序列、波动率。

·数据挖掘：数据挖掘算法、数据建模、机器学习。

·互联网：推荐系统、消费预测、社交网络。

·生物信息学：DNA分析、物种分析。

·生物制药：生存分析、制药过程管理。

·全球地理科学：天气、气候、遥感数据。

·数据可视化：静态图、可交互的动态图、社交图、地图、热图、与各种JavaScript库的集成。

本书会介绍R语言在统计分析、金融分析、数据挖掘、推荐系统、社交网络等领域的应用。R有着非常广阔的应用前景，而且R也将成为新一代的最有能力创造价值的工具。

1.1.4　时代赋予R的任务

R语言是在大数据时代被工业界了解和认识的语言，R语言被时代赋予了挖掘数据价值、发现数据规律以及创造数据财富的任务。R语言也是帮助人们发挥智慧和创造力的最好的生产工具，因此我们不仅要学好R语言，还要用好R语言，为社会注入更多的创新的生产力。

总而言之，在这5种语言中，R是最特殊的，R被赋予了与其他语言不同的使命。R的基因决定了R将成为2014年，也可能是以后更长一段时间的明星。因此我认为“R是最值得学习的编程语言”。不论你正在读书，还是已经工作，掌握R语言这个工具并找最适合自己的位置将会前途无量。

1.2　R的历史版本安装

问题

在Linux Ubuntu上，如何安装不同版本的R？

引言

R语言已进入到了3.0的时代，但有些第三方的R包还处于2.15的状态，没有升级，如RHadoop等。我们要用这些R包的时候，就需要指定版本的R软件。对于Windows来说，这是很简单的操作，只要安装不同的(.exe)文件就行了；对于Linux系统来说，就不那么容易了，需要我们手动进行配置。不熟悉Linux系统的同学，在这里就很容易卡住。所以，本节就讲一下如何在Linux Ubuntu系统中安装R语言软件包的指定版本。

1.2.1　R在Windows中安装

通过R的官方网站（http://cran.r-project.org/），我们可以下载Linux、MacOS、Windows系统的R语言安装包。R在Windows系统中安装非常简单，下载可执行文件(.exe)，双击即可进行安装。安装后就能运行R语言的界面，如图1-2所示。

图1-2　R在Windows系统中的安装界面

1.2.2　R在Linux Ubuntu中安装

本书使用的Linux是Ubuntu 12.04.2LTS 64bit的系统，安装R语言软件包可以通过Ubuntu的apt-get工具进行安装。下面就介绍在Linux Ubuntu中安装R语言的过程。

~  R  #

检查R

是否已安装

The program 'R' is currently not installed. You can install it by typing:

sudo apt-get install r-base-core

~ sudo apt-get install r-base-core #

根据提示安装R

语言软件包

~ R --version #

检查R

的版本

R version 2.14.1 (2011-12-22)

Copyright (C) 2011 The R Foundation for Statistical Computing

ISBN 3-900051-07-0

Platform: x86_64-pc-linux-gnu (64-bit)

~R#检查R是否已安装

前面的检查结果表明，我们安装的是R的默认版本，即2.14.1版，这与本书中R的版本是不符的，接下来我们希望安装最新版本R的软件包。

1.2.3　R的最新版本安装

首先，删除Linux Ubuntu系统中原有的R软件包，代码如下：

~ sudo apt-get autoremove r-base-core #

删除系统中原有的R

软件包

接下来，找到一个Ubuntu的软件源镜像（http://mirror.bjtu.edu.cn/cran/bin/linux/ubuntu/），Linux Ubuntu 12.04对应的名字是precise，进入到precise/目录，找到r-base-core相关的文件，发现有多个R的版本。把这个软件源，增加到apt的sources.list文件中，代码如下：

~ sudo sh -c "echo deb http://mirror.bjtu.edu.cn/cran/bin/linux/ubuntu

precise/ >>/etc/apt/sources.list" #

在sources.list

文件最下面，新加一行

~ sudo apt-get update #

更新源

~ sudo apt-get install r-base-core #

再次安装R

语言软件包

~ R

–version #

检查R

的版本

R version 3.0.3 (2014-03-06) -- "Warm Puppy"

Copyright (C) 2014 The R Foundation for Statistical Computing

Platform: x86_64-pc-linux-gnu (64-bit)

这时我们就安装了最新的R语言版本—3.0.3版。

1.2.4　R的指定版本安装

由于本书中的例子，大部分是基于3.0.1完成的，而RHadoop的例子是基于2.15.3完成的，因此我们还需要指定R的安装版本。

1.安装R的2.15.3版本

~ sudo apt-get autoremove r-base-core #

删除系统中原有的R

软件包

~ sudo apt-get install r-base-core=2.15.3-1precise0precise1 #

安装R

的2.15.3

版本

~ R

–version   #

检查R

语言软件包版本

R version 2.15.3 (2013-03-01) -- "Security Blanket"

Copyright (C) 2013 The R Foundation for Statistical Computing

ISBN 3-900051-07-0

Platform: x86_64-pc-linux-gnu (64-bit)

2.安装R的3.0.1版本

~ sudo apt-get install r-base-core=3.0.1-6precise0 #

安装R

的3.0.1

版本

~ R

–version #

检查R

语言软件包版本

R version 3.0.1 (2013-05-16) -- "Good Sport"

Copyright (C) 2013 The R Foundation for Statistical Computing

Platform: x86_64-pc-linux-gnu (64-bit)

这样我们就可以很方便地指定安装不同版本的R的语言软件包，满足不同的应用需求！

1.3　fortunes记录R语言的大智慧

问题

如何才能更深入地了解R，它的起源、成长、经历是怎样的？

引言

R语言是在大数据“火”起来后，映入人们眼球的。但R语言的社区已经存在很多年，我们并不知道，R语言在很长的历史时期，有着什么样的智慧。不过，正有人悄悄地记录着R语言的大智慧。

1.3.1　fortunes介绍

fortunes库是一个R语言的语录集，截至2013年12月14日，一共总结了360条R-help的留言。这些都是R语言智慧的精华，让R语言的后辈使用者，可以更了解R语言的本身，了解R的精神。

1.3.2　fortunes安装

本节使用的系统环境是：

·Linux:Ubuntu 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

注　fortunes同时支持Windows 7环境和Linux环境。

Fortunes的安装过程如下。

~ R  #

启动R

程序

> install.packages("fortunes") #

安装fortunes

包

> library(fortunes)  #

加载fortunes

包

> ?fortunes  #

查看帮助

1.3.3　fortunes包的使用

fortunes包的使用非常简单，只有一个函数fortune()。

> fortune() #

随机查看一条语录

Barry Rowlingson: Your grid above has 8*6 = 42 points.

(That was a subtle Hitchhikers Guide To The Galaxy reference there, honest, and

not a stupid dumb multiplication mistake on my part after working four 18-hour

days on the trot...)

Peter Dalgaard: [...] Don't panic, just throw yourself at the ground and miss.

-- Barry Rowlingson and Peter Dalgaard

R-help (March 2004)

> fortune(108) #

指定查看一条语录

Actually, I see it as part of my job to inflict R on people who are perfectly

happy to have never heard of it. Happiness doesn't equal proficient and

efficient. In some cases the proficiency of a person serves a greater good than

their momentary happiness.

-- Patrick Burns

R-help (April 2005)

完整的语录下载地址是http://cran.r-project.org/web/packages/fortunes/vignettes/fortunes.pdf。静下心来阅读这些智慧精华就能更了解R语言本身。想用好一门语言，就需要更深入地了解它。

1.4　formatR代码自动化排版

问题

如何写出让别人看得懂，且符合规范的代码呢？

引言

新手写的代码，大都不注重代码规范，以为实现功能就算完成了。这种代码不仅别人不愿意读，过几个月后再看自己都会觉得很烂。不仅仅新手如此，大多数程序员写的代码都没有考虑如何让别人看着更方便。程序员最痛苦的事情，不是每天加班写程序，而是每天加班读懂别人写的程序。最后，有人实在忍受不了其他人的丑陋代码，便开始制定代码编程规范，又有人去实现代码的自动化排版工具。formatR就是这样的一个R语言自动化排版的工具。

1.4.1　formatR介绍

formatR包是一个实用的包，提供了R代码格式化功能，可以自动设置空格、缩进、换行等代码格式，让代码看起来更友好。formatR包中的API中主要有下面5个函数。

·tidy.source:对代码进行格式化

·tidy.eval:输出格式化后的R代码和运行结果

·usage:格式化函数定义，并按指定宽度输出

·tidy.gui:一个GUI工具，支持编辑并格式化R代码

·tidy.dir:对某个目录下，所有R脚本进行格式化

1.4.2　formatR安装

本节使用的系统环境是：

·Win764bit

·R:3.0.1x86_64-w64-mingw32/x64b4bit

注　formatR同时支持Windows 7环境和Linux环境。

formatR的安装过程如下：

~ R  #

启动R

程序

> install.packages("formatR") #

安装formatR

包

library(formatR)  # formatR

加载

1.4.3　formatR的使用

1.字符串格式化

tidy.source()函数，以字符串作为输入参数，对代码格式化。

> tidy.source(text = c("{if(TRUE)1 else 2; if(FALSE){1+1", "## comments", "} else 2}"))

{

if (TRUE)

1 else 2

if (FALSE) {

1 + 1

## comments

} else 2

}

通过执行tidy.source()函数，把代码进行了重新格式化，让我们一眼就可以看得懂。

2.文件格式化

messy.R是一个不太规范的R程序文件。我们读入这个文件，然后通过tidy.source()函数，以文件对象作为输入参数，进行代码格式化。

> messy = system.file("format", "messy.R", package ="formatR")

> messy

[1] "C:/Program Files/R/R-3.0.1/library/formatR/format/messy.R"

messy.R的原始代码输出：

> src = readLines(messy)

> cat(src,sep="\n")

# a single line of comments is preserved

1+1

if(TRUE){

x=1 # inline comments

}else{

x=2;print('Oh no... ask the right bracket to go away!')}

1*3  # one space before this comment will become two!

2+2+2    # 'short comments'

lm(y~x1+x2, data=data.frame(y=rnorm(100),x1=rnorm(100),x2=rnorm(100)))

### only'single quotes' are allowed in comments

1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1 ## comments after a long line

'a character string with \t in it'

## here is a long long long long long long long long long long long long long

long long long long long long long comment

格式化后的代码输出：

> tidy.source(messy)

# a single line of comments is preserved

1 + 1

if (TRUE) {

x = 1 # inline comments

} else {

x = 2

print("Oh no... ask the right bracket to go away!")

}

1 * 3 # one space before this comment will become two!

2 + 2 + 2 # 'short comments'

lm(y ~ x1 + x2, data = data.frame(y = rnorm(100), x1 = rnorm(100), x2 =rnorm(100))) ### only 'single quotes' are allowed in comments

1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 +

1 + 1 + 1 ## comments after a long line

"a character string with \t in it"

## here is a long long long long long long long long long long long long longlong long long

##long long long long comment

可以看出，格式化后的输出，经过了空格、缩进、换行、注释等处理，代码可读性就增强了。

3.格式化并输出R脚本文件

新建R脚本文件demo.r。

~ vi demo.r

a<-1+1;a;matrix(rnorm(10),5);

if(a>2) { b=c('11',832);"#a>2";} else print('a is invalid!!')

格式化demo.r。

> x = "demo.r"

> tidy.source(x)

a <- 1 + 1

a

matrix(rnorm(10), 5)

if (a > 2) {

b = c("11", 832)

"#a>2"

} else print("a is invalid!!")

输出格式化结果到文件demo2.r，如图1-3所示。

> f="demo2.r"

> tidy.source(x, keep.blank.line = TRUE, file = f)

> file.show(f)

图1-3　输出格式化结果到文件

4.输出格式化代码和运行结果

使用tidy.eval()函数，以字符串形式，执行R脚本：

> tidy.eval(text = c("a<-1+1;a", "matrix(rnorm(10),5)"))

a <- 1 + 1

a

## [1] 2

matrix(rnorm(10), 5)

## [,1] [,2]

## [1,] 0.65050729 0.1725221

## [2,] 0.05174598 0.3434398

## [3,] -0.91056310 0.1138733

## [4,] 0.18131010 -0.7286614

## [5,] 0.40811952 1.8288346

这样直接在当前的运行环境中，就输出了代码和运行结果。

5.格式化函数定义

通过usage()函数可以只打印出函数定义，跳过函数细节。以var()函数为例，输入var，默认会打印出一个函数细节。

> var

function (x, y = NULL, na.rm = FALSE, use)

{

if (missing(use))

use <- if (na.rm)

"na.or.complete"

else "everything"

na.method <- pmatch(use, c("all.obs", "complete.obs", "pairwise.complete.obs",

"everything", "na.or.complete"))

if (is.na(na.method))

stop("invalid 'use' argument")

if (is.data.frame(x))

x <- as.matrix(x)

else stopifnot(is.atomic(x))

if (is.data.frame(y))

y <- as.matrix(y)

else stopifnot(is.atomic(y))

.Call(C_cov, x, y, na.method, FALSE)

}

<bytecode: 0x0000000008fad030>

<environment: namespace:stats>

> usage(var) #

通过usage

，只打印函数定义

var(x, y = NULL, na.rm = FALSE, use)

有时候函数定义也很长，比如lm()函数，通过usage的width参数可以控制函数的显示宽度。

> usage(lm)

lm(formula, data, subset, weights, na.action, method = "qr", model = TRUE, x =

FALSE, y = FALSE, qr = TRUE, singular.ok = TRUE, contrasts = NULL, offset, ...)

> usage(lm,width=30) # usage

的width

参数，控制函数的显示宽度

lm(formula, data, subset, weights,

na.action, method = "qr", model = TRUE,

x = FALSE, y = FALSE, qr = TRUE,

singular.ok = TRUE, contrasts = NULL,

offset, ...)

6.GUI工具

tidy.gui()函数是一个GUI的工具，可以在界面上编辑并格式化R代码。首先安装gWidgetsRGtk2库：

> install.packages("gWidgetsRGtk2")

also installing the dependencies 'RGtk2', 'gWidgets'

打开GUI控制台：

> library("gWidgetsRGtk2")

> g = tidy.gui()

我们输入一段不太好看的代码，如图1-4所示。

图1-4　tidy的GUI

点击“转换”，结果如图1-5所示，可以看到，在GUI的编辑器中，R语言的代码被格式化了！

图1-5　格式化后的代码

7.格式化目录中的文件

tidy.dir()函数可以批量格式化文件，对目录中的所有文件进行格式化操作。下面新建目录:dir，并在目录dir中新建两个R脚本文件：dir.r,dir2.r。

~ mkdir dir #

新建目录 dir

~ cd dir

~ vi dir.r #

用vi

新建文件dir.r

a<-1+1;a;matrix(rnorm(10),5);

~ vi dir2.r

if(a>2) { b=c('11',832);"#a>2";} else print('a is invalid!!')

执行tidy.dir：

> tidy.dir(path="dir")

tidying dir/dir.r

tidying dir/dir2.r

分别查看dir.r和dir2.r：

~ vi dir.r

a <- 1 + 1

a

matrix(rnorm(10), 5)

~ vi dir2.r

if (a > 2) {

b = c("11", 832)

"#a>2"

} else print("a is invalid!!")

我们发现不规则的代码，已经被格式化了！

1.4.4　formatR的源代码解析

通过上面的使用，我们不难发现，formatR包的核心函数就是tidy.source()函数，从Github上面找到源代码：https://github.com/yihui/formatR/blob/master/R/tidy.R。我将在代码中增加注释：

tidy.source = function(

source = 'clipboard', keep.comment = getOption('keep.comment', TRUE),

keep.blank.line = getOption('keep.blank.line', TRUE),

replace.assign = getOption('replace.assign', FALSE),

left.brace.newline = getOption('left.brace.newline', FALSE),

reindent.spaces = getOption('reindent.spaces', 4),

output = TRUE, text = NULL,

width.cutoff = getOption('width'), ...

) {

if (is.null(text)) { #

判断输入来源为剪贴板

if (source == 'clipboard' && Sys.info()['sysname'] == 'Darwin') {

source = pipe('pbpaste')

}

} else { #

判断输入来源为字符串

source = textConnection(text); on.exit(close(source))

}

text = readLines(source, warn = FALSE) #

按行读取来源数据

if (length(text) == 0L || all(grepl('^\\s*$', text))) { #

文件行数判断

if (output) cat('\n', ...)

return(list(text.tidy = text, text.mask = text))

}

if (keep.blank.line && R3) { #

空行处理

one = paste(text, collapse = '\n') # record how many line breaks before/after

n1 = attr(regexpr('^\n*', one), 'match.length')

n2 = attr(regexpr('\n*$', one), 'match.length')

}

if (keep.comment) text = mask_comments(text, width.cutoff, keep.blank.line)

#

注释处理

text.mask = tidy_block(text, width.cutoff, replace.assign && length(grep('=', text)))

#

把输入的R

代码，先转成表达式，再转回字符串。用来实现对每个语句的截取

text.tidy = if (keep.comment) unmask.source(text.mask) else text.mask

#

对注释排版

text.tidy = reindent_lines(text.tidy, reindent.spaces) #

重新定位缩进

if (left.brace.newline) text.tidy = move_leftbrace(text.tidy) #

扩号换行

if (keep.blank.line && R3) text.tidy = c(rep('', n1), text.tidy, rep('', n2))

#

增加首尾空行

if (output) cat(paste(text.tidy, collapse = '\n'), '\n', ...)

#

在console

打印格式化后的结果

invisible(list(text.tidy = text.tidy, text.mask = text.mask))

#

返回，但不打印结果

}

1.4.5　源代码中的Bug

在读源代码的过程中，我发现有一个小问题，即在R 3.0.1版本，没有对向右赋值操作(->)进行处理。我已经就这个问题给作者提Bug了，参见https://github.com/yihui/formatR/issues/31。Bug测试代码如下:

> c('11',832)->x2

> x2

[1] "11" "832"

> tidy.source(text="c('11',832)->x2") #

格式化代码

c("11", 832) <- x2

> tidy.eval(text="c('11',832)->x2")

c("11", 832) <- x2

Error in eval(expr, envir, enclos) : object 'x2' not found

Bug已修复。作者回复：“这个问题已经在R 3.0.2中修正了。”

> formatR::tidy.source(text="c('11',832)->x2") #

格式化代码

x2 <- c("11", 832)

> sessionInfo()

R version 3.0.2 (2013-09-25)

Platform: x86_64-pc-linux-gnu (64-bit)

locale:

[1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C

[3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8

[5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8

[7] LC_PAPER=en_US.UTF-8 LC_NAME=C

[9] LC_ADDRESS=C LC_TELEPHONE=C

[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C

attached base packages:

[1] stats graphics grDevices utils datasets methods base

loaded via a namespace (and not attached):

[1] formatR_0.10.3

formatR包提供的功能非常实用，特别是读别人写的不规范的代码的时候。建议各IDE厂商能把formatR作为标准的格式化工具直接嵌入编辑器的工具中。让我们把阅读别人的代码，也变成一件快乐的事情吧。

1.5　多人在线协作R开发RStudio Server

问题

R语言开发，哪个工具最好用？

引言

RStudio是R语言开发中的利器，是最好用的R语言IDE集成环境。RStudio Server更是利器中的神器。不仅提供了Web的功能，可以安装到远程服务器上，通过Web进行访问，还支持多用户的协作开发。如此神器，快来动手试一下吧。

1.5.1　RStudio和RStudio Server

RStudio是一个强大的、免费的、开源的R语言集成开发环境的应用软件，可以安装在Windows、Linux和Mac不同操作系统上。RStudio Server是一个基于Web访问的RStudio云端开发环境，需要安装在Linux服务器上面，支持多用户远程访问使用。

1.5.2　RStudio Server安装

本文使用的系统环境是：

·Linux:Ubuntu Server 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

·IP:192.168.1.13

注　RStudio Server只支持Linux系统环境。下载最新版本RStudio Server的地址是http://www.rstudio.com/ide/download/server.html。

在Linux Ubuntu环境中，下载并安装64位的Rstudio Server：

~ sudo apt-get install gdebi-core

~ sudo apt-get install libapparmor1 # Required only for Ubuntu, not Debian

~ wget http://download2.rstudio.org/rstudio-server-0.97.551-amd64.deb

~ sudo gdebi rstudio-server-0.97.551-amd64.deb

安装后，RStudio Server会自动启动运行。

~ ps -aux|grep rstudio-server #

查看RStudio Server

运行进程

998       2914  0.0  0.1 192884  2568 ?        Ssl  10:40   0:00 /usr/lib/

rstudio-server/bin/rserver

可以看到，RStudio Server的服务已启动，8787端口被打开。

1.5.3　RStudio Server使用

通过浏览器，我们访问RStudio Server:http://192.168.1.13:8787，IP地址为RStudio Server服务器的地址，如图1-6所示。

图1-6　RStudio Server登录界面

RStudio Server登录需要用Linux系统的用户账号。如果想增加或减少用户，直接对Linux系统用户进行操作就可以了。我的环境中用户登录，用户名是conan，密码是conan111。登录之后看到的界面如图1-7所示。

图1-7　RStudio Server的Web界面

1.RStudio Server的系统配置

RStudio Server主要有两个配置文件，默认文件不存在。

/etc/rstudio/rserver.conf

/etc/rstudio/rsession.conf

设置端口和ip控制：

~ vi /etc/rstudio/rserver.conf

www-port=8080 #

监听端口

www-address=127.0.0.1 #

允许访问的IP

地址，默认为0.0.0.0

重启Rstudio Server服务器，配置生效：

~ sudo rstudio-server restart

会话配置管理：

~ vi /etc/rstudio/rsession.conf

session-timeout-minutes=30 #

会话超时时间

r-cran-repos=http://ftp.ctex.org/mirrors/CRAN/ #

设置CRAN

资源库

2.RStudio Server 的系统管理

启动、停止、重启RStudio Server服务器的命令如下：

~ sudo rstudio-server start   #

启动

~ sudo rstudio-server stop    #

停止

~ sudo rstudio-server restart #

重启

查看运行中的R进程：

~ sudo rstudio-server active-sessions

PID     TIME COMMAND

6817 00:00:03 /usr/lib/rstudio-server/bin/rsession -u zd

指定PID,停止运行中的R进程：

~ sudo rstudio-server suspend-session 6817

~ sudo rstudio-server active-sessions #

再次查看进程

PID    TIME COMMAND

停止所有运行中的R进程：

~ sudo rstudio-server suspend-all

强制停止运行中的R进程，此操作优先级最高，立刻执行。

~ sudo rstudio-server force-suspend-session <pid>

~ sudo rstudio-server force-suspend-all

RStudio Server临时下线，不允许Web访问，并给用户友好的错误提示：

~ sudo rstudio-server offline

rstudio-server start/running, process 6880

RStudio Server上线：

~ sudo rstudio-server online

rstudio-server start/running, process 6908

RStudio Server的其他操作和单机版的RStudio一样。

1.5.4　RStudio Server多人协作

1.增加新用户和新用户组

~ sudo groupadd hadoop #

创建Hadoop

用户组

~ sudo useradd hadoop -g hadoop #

创建Hadoop

用户并加入到Hadoop

用户组

~ sudo passwd hadoop #

设置Hadoop

用户的密码

~ sudo adduser hadoop sudo #

增加Hadoop

用户到sudo

组

~ sudo mkdir /home/hadoop #

创建Hadoop

用户的home

目录

~ sudo chown -R hadoop:hadoop /home/hadoop #

给/home/hadoop

目录及子目录，设置用户权限

以Hadoop用户登录，检查用户是否设置成功

~ ssh hadoop@localhost #

通过 ssh

远程登录

~ bash

~ pwd #

查看登录后的访问目录

/home/hadoop

新打开浏览器窗口通过Hadoop账号登录，如图1-8所示。

图1-8　以Hadoop账号登录

2.Git代码共享

首先安装Git：

~ sudo apt-get install git

~ ssh-keygen -t rsa #

生成rsa

密钥对

~ cat /home/conan/.ssh/id_rsa.pub #

查看公钥

ssh-rsa

AAAAB3NzaC1yc2EAAAADAQABAAABAQDMmnFyZe2RHpXaGmENdH9kSyDyVzRas4GtRwMNx+qQ

4QsB8xVTrIbFayG2ilt+P8UUkVYO0qtUJIaLRjGy/SvQzzL7JKX12+VyYoKTfKvZZnANJ414d6oZpbDw

sC0Z7JARcWsFyTW1KxOMyesmzNNdB+F3bYN9sYNiTkOeVNVYmEQ8aXywn4kcljBhVpT8PbuHl5eadSLt

5zpN6bcX7tlquuTlRpLi1e4K+8jQo67H54FuDyrPLUYtVaiTNT/xWN6IU+DQ9CbfykJ0hrfDU1d1LiLQ

4K2Fdg+vcKtB7Wxez2wKjsxb4Cb8TLSbXdIKEwSOFooINw25g/Aamv/nVvW1 conan@conan-deskop

接下来，我们需要把本地项目上传到Github。首先在Github上创建一个新的项目rstudio-demo，地址为https://github.com/bsspirit/rstudio-demo，通过下面的操作上传本地目录到rstudio-demo项目。

~ mkdir /home/conan/R/github #

创建rstudio-demo

项目目录

~ cd /home/conan/R/github

~ git init #

初始化Git

~ git add #

增加当前目录及子目录到本地Git

库

~ git commit -m 'first comment' #

在本地Git

库提交

~ git remote add origin git@github.com:bsspirit/rstudio-demo.git

#

绑定当前目录和github

的项目

~ git push -u origin master #

上传本地Git

库中的代码到Github

打开RStudio设置到/home/conan/R/github目录，tools–>version control –>project setup，如图1-9所示。

图1-9　在RStudio中，配置Github地址

在RStudio中修改sayHello.r的代码：

sayHello<-function(name){

print(paste("hello",name))

}

sayHello("Conan")

sayHello("World")

点击tools–>version control–>commit提交，如图1-10所示。

图1-10　通过RStudio进行Git操作

上传到Github，只需要点击tools–>version control–>push，如图1-11所示。

图1-11　在Github上查看提交的操作

RStudio有如此强大的功能，极大地降低了编程的门槛。还没有用过的同学，赶紧去体验一把极客的感觉吧！

1.6　R和JSON的傻瓜式编程

问题

如何让R语言的数据类型转换成JSON数据类型？

引言

JSON作为一种轻量级数据格式，被大量地应用在各种程序环境中。JSON（JavaScript Object Notation）是JavaScript的内嵌的标准对象，同时也是MongoDB的表结构存储类型。JSON是半结构化的，可以表达出丰富的文档含义。JSON文档比XML文档要少很多，更适合于网络传输。早期R语言编程很少会用到JSON，但随着R语言的壮大，R也在伸向各种领域，JSON就是与其他领域的一个交点。如何让R语言傻瓜式转型JSON呢？请看下文介绍。

1.6.1　rjson包介绍

rjson是一个R语言与JSON进行转换的包，非常简单，支持R自身语言转型和基于C类库的转型两种方式。rjson包提供的函数只有3个，fromJSON(),newJSONParser(),toJSON()。后面我们将介绍如何使用rjson包。本节使用的系统环境是：

·Windows 7:x86_64-w64-mingw32/x64(64-bit)

·R:version 3.0.1

注　rjson同时支持Windows 7环境和Linux环境。

1.安装并加载rjson

> install.packages("rjson") #

安装rjson

> library(rjson)  #

加载rjson

接下来，我们进行测试。新建JSON文件fin0.json：

~ vi fin0.json

{

"table1": {

"time": "130911",

"data": {

"code": [

"TF1312",

"TF1403",

"TF1406"

],

"rt_time": [

130911,

130911,

130911

]

}

},

"table2": {

"time": "130911",

"data": {

"contract": [

"TF1312",

"TF1312",

"TF1403"

],

"jtid": [

99,

65,

21

]

}

}

}

2.调用函数fromJSON():从JSON到R

从fin0.json文件中，读取JSON并解析成R语言对象。我们通常把字节或者文字格式转型为程序对象的过程叫反序列化过程，与之相反的过程，叫做序列化过程。

> json_data <- fromJSON(paste(readLines("fin0.json"), collapse=""))

> json_data

$table1

$table1$time

[1] "130911"

$table1$data

$table1$data$code

[1] "TF1312" "TF1403" "TF1406"

$table1$data$rt_time

[1] 130911 130911 130911

$table2

$table2$time

[1] "130911"

$table2$data

$table2$data$contract

[1] "TF1312" "TF1312" "TF1403"

$table2$data$jtid

[1] 99 65 21

检查转型后，对应R语言的数据类型：

> class(json_data)

[1] "list"

> class(json_data$table2)

[1] "list"

> class(json_data$table2$data)

[1] "list"

> class(json_data$table2$data$jtid)

[1] "numeric"

> class(json_data$table1$data$code)

[1] "character"

我们看到原JSON对象转型后，除最内层外，其他都被解析为R的列表（list）类型，最内层则是基本（numeric,character）类型。在R对象结构中取JSON数据的一个叶子节点，JSON的索引路径为json.table1.data.code[0]。

> json_data$table1$data$code

[1] "TF1312" "TF1403" "TF1406"

> json_data$table1$data$code[1]

[1] "TF1312"

3.toJSON()从R到JSON

把R对象转型为JSON串，这个过程叫做序列化过程。还以刚才的json_data为例。

> json_str<-toJSON(json_data)

> print(json_str)

[1] "{\"table1\":{\"time\":\"130911\",\"data\":{\"code\":[\"TF1312\",\"TF1403\",

\"TF1406\"],\"rt_time\":[130911,130911,130911]}},\"table2\":{\"time\":\"130911\",\"data\":{\"contract\":[\"TF1312\",\"TF1312\",\"TF1403\"],\"jt id\":[99,65,21]}}}"

> cat(json_str)

{"table1":{"time":"130911","data":{"code":["TF1312","TF1403","TF1406"],"rt_time":[130911,130911,130911]}},"table2":{"time":"130911","data":{"contract":["TF1312","TF1312","TF1403"],"jtid":[99,65,21]}}}

我们只要使用toJSON()函数，就可以实现R对象向JSON的转型。如果用print()函数输出，结果就是带转义的输出(\")；如果直接用cat函数输出，结果就是标准的JSON串格式。把JSON输出到文件fin0_out.json,有2种方法，即writeLines()和sink()。

> writeLines(json_str, "fin0_out1.json") # writeLines

方法

> sink("fin0_out2.json") # sink

方法

> cat(json_str)

> sink()

虽然写法不同，但是输出结果是一个样的，writeLines最后新建一个空行。

{"table1":{"time":"130911","data":{"code":["TF1312","TF1403","TF1406"],"rt_time":

[130911,130911,130911]}},"table2":{"time":"130911","data":{"contract":["TF1312","TF1312","TF1403"],"jtid":[99,65,21]}}}

4.C语言库和R语言库转型，并进行性能测试

我们对fromJSON进行性能测试：

> system.time( y <- fromJSON(json_str,method="C") )

用户

系统

流逝

0    0    0

> system.time( y2 <- fromJSON(json_str,method = "R") )

用户

系统

流逝

0.02 0.00 0.02

> system.time( y3 <- fromJSON(json_str) )

用户

系统

流逝

0    0    0

我们可以看到，基于C语言库的操作比基于R语言库的要快，因为数据量很小，所以0.02并不明显。当JSON串很大的时候，这个差距就会变得相当大了。fromJSON默认使用C语言库的方法，所以我们平时处理不用加method='C'的参数。下面做toJSON的性能测试。

> system.time( y <- toJSON(json_data,method="C") )

用户

系统

流逝

0   0     0

> system.time( y2 <- toJSON(json_data,method = "R") )

用户

系统

流逝

0.02 0.00 0.01

> system.time( y3 <- toJSON(json_data) )

用户

系统

流逝

0   0     0

1.6.2　RJSONIO包介绍

RJSONIO包，提供了2个主要的操作，把JSON串反序列化成R对象，把R对象序列化成JSON串。RJSONIO包的两个主要函数是fromJSON(),toJSON()，它还包括几个辅助函数，即asJSVars(),basicJSONHandler(),Bob(),isValidJSON(),readJSONStream()。RJSONIO包解决了rjson包序列化大对象慢的问题。RJSONIO依赖于底层的C语言类库libjson。

1.安装并加载RJSONIO

> install.packages("RJSONIO")

> library(RJSONIO)

2.fromJSON()从JSON到R

同rjson一样，测试fromJSON()函数。

> json_data <- fromJSON(paste(readLines("fin0.json"), collapse=""))

> json_data

$table1

$table1$time

[1] "130911"

$table1$data

$table1$data$code

[1] "TF1312" "TF1403" "TF1406"

$table1$data$rt_time

[1] 130911 130911 130911

$table2

$table2$time

[1] "130911"

$table2$data

$table2$data$contract

[1] "TF1312" "TF1312" "TF1403"

$table2$data$jtid

[1] 99 65 21

我们发现与rjson的结果是一样，R对象除最内层外，其他都是列表（list）类型。下面取叶子节点：

> json_data$table1$data$code

[1] "TF1312" "TF1403" "TF1406"

> json_data$table1$data$code[1]

[1] "TF1312"

3.toJSON()从R到JSON

做toJSON的性能测试：

> print(json_str)

[1] "{\n \"table1\": {\n \"time\": \"130911\",\n\"data\": {\n \"code\":[\"TF1312\", \"TF1403\", \"TF1406\" ],\n\"rt_time\": [ 1.3091e+05, 1.3091e+05,1.3091e+05 ] \n} \n},\n\"table2\": {\n \"time\": \"130911\",\n\"data\": {\n\"contract\": [ \"TF1312\", \"TF1312\", \"TF1403\" ],\n\"jtid\": [99,65,21 ] \n} \n} \n}"

> cat(json_str)

{

"table1": {

"time": "130911",

"data": {

"code": [ "TF1312", "TF1403", "TF1406" ],

"rt_time": [ 1.3091e+05, 1.3091e+05, 1.3091e+05 ]

}

},

"table2": {

"time": "130911",

"data": {

"contract": [ "TF1312", "TF1312", "TF1403" ],

"jtid": [99,65,21 ]

}

}

}

toJSON的函数输出与rjson是不一样的，这个输出是格式化的。下面输出到文件：

> writeLines(json_str, "fin0_io.json")

文件结果：

{

"table1": {

"time": "130911",

"data": {

"code": [ "TF1312", "TF1403", "TF1406" ],

"rt_time": [ 1.3091e+05, 1.3091e+05, 1.3091e+05 ]

}

},

"table2": {

"time": "130911",

"data": {

"contract": [ "TF1312", "TF1312", "TF1403" ],

"jtid": [     99,     65,     21 ]

}

}

}

4.isValidJSON()验证JSON是否合法

验证JSON的格式是否合法。

> isValidJSON(json_str)

Error in file(con, "r") : cannot open the connection

> isValidJSON(json_str,TRUE) #

合法JSON

[1] TRUE

> isValidJSON(I('{"foo" : "bar"}')) #

合法JSON

[1] TRUE

> isValidJSON(I('{foo : "bar"}')) #

不合法JSON

[1] FALSE

5.asJSVars()转换为JavaScript变量格式

> cat(asJSVars( a = 1:10, myMatrix = matrix(1:15, 3, 5)))

a = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] ;

myMatrix = [ [ 1, 4, 7, 10, 13 ],

[ 2, 5, 8, 11, 14 ],

[ 3, 6, 9, 12, 15 ] ] ;

得到两个JavaScript变量，即数组a和二维数组myMatrix.

1.6.3　自定义JSON的实现

在R语言中，我们最常用的类型是data.frame，接下来我们自己实现data.frame类型和JSON的转换。首先，把R的data.frame对象转成我们定义的JSON格式。

定义输出JSON的格式：

[

{

"code": "TF1312",

"rt_time": "152929",

"rt_latest": 93.76,

"rt_bid1": 93.76,

"rt_ask1": 90.76,

"rt_bsize1": 2,

"rt_asize1": 100,

"optionValue": -0.4,

"diffValue": 0.6

}

]

定义R语言data.frame类型对象：

> df<-data.frame(

code=c('TF1312','TF1310','TF1313'),

rt_time=c("152929","152929","152929"),

rt_latest=c(93.76,93.76,93.76),

rt_bid1=c(93.76,93.76,93.76),

rt_ask1=c(90.76,90.76,90.76),

rt_bsize1=c(2,3,1),

rt_asize1=c(100,1,11),

optionValue=c(-0.4,0.2,-0.1),

diffValue=c(0.6,0.6,0.5)

)

> df

code rt_time rt_latest rt_bid1 rt_ask1 rt_bsize1 rt_asize1 optionValue diffValue

1 TF1312 152929      93.76   93.76 90.76           2       100        -0.4       0.6

2 TF1310 152929      93.76   93.76 90.76           3         1         0.2       0.6

3 TF1313 152929      93.76   93.76 90.76           1        11        -0.1       0.5

直接使用toJSON，输出的JSON串按列组合成了数组，并不是我们想要的。

> cat(toJSON(df))

{

"code": ["TF1312", "TF1310", "TF1313"],

"rt_time": ["152929", "152929", "152929"],

"rt_latest": [93.76, 93.76, 93.76],

"rt_bid1": [93.76, 93.76, 93.76],

"rt_ask1": [90.76, 90.76, 90.76],

"rt_bsize1": [2, 3, 1],

"rt_asize1": [100, 1, 11],

"optionValue": [-0.4, 0.2, -0.1],

"diffValue": [0.6, 0.6, 0.5]

}

我们要对data.frame类型进行数据处理：

> library(plyr) #

用plyr

进行数据转换

> cat(toJSON(unname(alply(df, 1, identity))))

[

{

"code": "TF1312",

"rt_time":  "152929",

"rt_latest":  93.76,

"rt_bid1":    93.76,

"rt_ask1":    90.76,

"rt_bsize1":      2,

"rt_asize1":    100,

"optionValue": -0.4,

"diffValue":    0.6

},

{

"code": "TF1310",

"rt_time": "152929",

"rt_latest": 93.76,

"rt_bid1":   93.76,

"rt_ask1":   90.76,

"rt_bsize1":     3,

"rt_asize1":     1,

"optionValue": 0.2,

"diffValue":   0.6

},

{

"code": "TF1310",

"rt_time": "152929",

"rt_latest": 93.76,

"rt_bid1":   93.76,

"rt_ask1":   90.76,

"rt_bsize1":     3,

"rt_asize1":     1,

"optionValue": 0.2,

"diffValue":   0.6

},

{

"code": "TF1313",

"rt_time": "152929",

"rt_latest": 93.76,

"rt_bid1":   93.76,

"rt_ask1":   90.76,

"rt_bsize1":     1,

"rt_asize1":    11,

"optionValue":-0.1,

"diffValue":   0.5

}

]

输出的结果已经通过alply()函数做了数据变换，这正是我希望的按行输出的结果。

1.6.4　JSON性能比较

性能比较我们做2组测试，一组是rjson和RJSONIO对大对象进行序列化（toJSON）测试，另一组是RJSONIO包序列化（toJSON）列式输出和行式输出的测试。

1.rjson和RJSONIO对大对象进行序列化（toJSON）测试

创建一个rjson测试脚本，在命令行运行。

> library(rjson)

> df<-data.frame(

+   a=rep(letters,10000),

+   b=rnorm(260000),

+   c=as.factor(Sys.Date()-rep(1:260000))

+ )

> system.time(rjson::toJSON(df))

1.01 0.02 1.03

> system.time(rjson::toJSON(df))

1.01 0.03 1.04

> system.time(rjson::toJSON(df))

0.98 0.05 1.03

同样，再创建一个RJSONIO测试脚本，在命令行运行。

> library(RJSONIO)

> df<-data.frame(

+   a=rep(letters,10000),

+   b=rnorm(260000),

+   c=as.factor(Sys.Date()-rep(1:260000))

+ )

> system.time(RJSONIO::toJSON(df))

2.23 0.02 2.24

> system.time(RJSONIO::toJSON(df))

2.30 0.00 2.29

> system.time(RJSONIO::toJSON(df))

2.25 0.01 2.26

对比结果发现，rjson的性能优于RJSONIO。

2.rjson和RJSONIO，序列化（toJSON）列式输出和行式输出的测试

创建一个rjson测试脚本，在命令行运行。

> library(rjson)

> library(plyr)

> df<-data.frame(

+   a=rep(letters,100),

+   b=rnorm(2600),

+   c=as.factor(Sys.Date()-rep(1:2600))

+ )

> system.time(rjson::toJSON(df))

0.01 0.00 0.02

> system.time(rjson::toJSON(df))

0.01 0.00 0.02

> system.time(rjson::toJSON(unname(alply(df, 1, identity))))

1.55 0.02 1.56

> system.time(rjson::toJSON(unname(alply(df, 1, identity))))

0.83 0.00 0.83

同样，再创建一个RJSONIO测试脚本，在命令行运行。

> library(RJSONIO)

> library(plyr)

> df<-data.frame(

+   a=rep(letters,100),

+   b=rnorm(2600),

+   c=as.factor(Sys.Date()-rep(1:2600))

+ )

> system.time(RJSONIO::toJSON(df))

0.03 0.00 0.03

> system.time(RJSONIO::toJSON(df))

0.04 0.00 0.03

> system.time(RJSONIO::toJSON(unname(alply(df, 1, identity))))

2.82 0.02 2.84

> system.time(RJSONIO::toJSON(unname(alply(df, 1, identity))))

2.06 0.00 2.06

通过测试我们发现，用toJSON直接列式输出，比行式输出要高效得多，这是因为行输出需要多一步数据处理的过程。这里说rjson比RJSONIO高效，而前面说RJSONIO包解决了rjson包序列化大对象慢的问题，似乎有些矛盾。当然，我的一次的测试不能证明这个结论的，也希望大家在有条件、有精力的情况下，自己做一些测试。

1.7　R语言的高质量图形渲染库Cairo

问题

如何让R语言画出无锯齿的高清图？

引言

R语言不仅在统计分析和数据挖掘领域计算能力强大，它在数据可视化领域也不逊于昂贵的商业软件。当然，R在可视化上强大，其背后离不开各种开源软件包的支持，Cairo就是这样一个用于矢量图形处理的类库。Cairo可以创建高质量的矢量图形（GIF、SVG、PDF、PostScript）和位图（PNG、JPEG、TIFF），同时支持在后台程序中高质量渲染！本节将介绍Cairo在R语言中的使用。

1.7.1　Cairo介绍

Cairo是一个用于图形绘图和渲染的免费库，支持复杂的2D的绘图功能，支持硬件加速。虽然，Cairo是用C语言编写的，但提供多种语言的接口，允许其他语言直接调用，包括有C++、C#、Java、Python、Perl、Ruby、Scheme、Smalltalk等语言。Cairo发布的许可协议为GNU Lesser General Public License version 2.1（LGPL）或Mozilla Public License 1.1（MPL）。R语言Cairo接口的官方发布页是http://www.rforge.net/Cairo/。

1.7.2　Cairo包安装

本节使用的系统环境是：

·Linux:Ubuntu 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

注　caTools同时支持Windows 7环境和Linux环境。

Cairo包在Linux Ubuntu系统中的安装过程如下：

~ sudo apt-get install libcairo2-dev # Cairo

的底层依赖库

~ sudo apt-get install libxt-dev

~ R  #

启动R

程序

> install.packages("Cairo")  #

安装Cairo

包

1.7.3　Cairo使用

Cairo使用起来非常简单，和基础包grDevices中的函数对应。

·CairoPNG:对应grDevices:png()。

·CairoJPEG:对应grDevices:jpeg()。

·CairoTIFF:对应grDevices:tiff()。

·CairoSVG:对应grDevices:svg()。

·CairoPDF:对应grDevices:pdf()。

我常用的图形输出，就是png和svg。下面检查Cairo的兼容性：

> library(Cairo) #

加载Cairo

包

> Cairo.capabilities() #

检查Cairo

包支持的图片格式

png   jpeg   tiff   pdf    svg      ps      x11    win raster

TRUE   TRUE  FALSE  TRUE   TRUE    TRUE     TRUE  FALSE   TRUE

从兼容性的检查结果，我们可以查看Cairo支持的图形输出格式：

·支持：png、jpeg、pdf、svg、ps、x11（Linux桌面）、raster

·不支持：tiff、win（win桌面）

注　如果是Windows系统，则x11为FALSE,win为TRUE。

下面比较CairoPNG()和png()的输出效果。

1.散点图

首先我们来画一个6000个点的散点图。

> x<-rnorm(6000) #

随机取6000

个点坐标

> y<-rnorm(6000)

> png(file="plot4.png",width=640,height=480) # png

函数

> plot(x,y,col="#ff000018",pch=19,cex=2,main = "plot")

> dev.off()

> CairoPNG(file="Cairo4.png",width=640,height=480) # CairoPNG

函数

> plot(x,y,col="#ff000018",pch=19,cex=2,main = "Cairo")

> dev.off()

在当前目录，会生成2个png文件，即plot4.png和Cairo4.png，见图1-12和图1-13。

图1-12　以png()函数生成的图

图1-13　以CairoPNG()函数生成的图

SVG图形输出代码为：

> svg(file="plot-svg4.svg",width=6,height=6)

> plot(x,y,col="#ff000018",pch=19,cex=2,main = "plot-svg")

> dev.off()

> CairoSVG(file="Cairo-svg4.svg",width=6,height=6)

> plot(x,y,col="#ff000018",pch=19,cex=2,main = "Cairo-svg")

> dev.off()

在当前目录，会生成2个svg文件，即plot-svg4.svg和Cairo-svg4.svg，可以把svg的图片拖拽到浏览器中显示。

2.三维截面图

接下来，我们再画一个三维截面图，比较函数png()和CairoPNG()输出的效果。

> x <- seq(-10, 10, length= 30)

> y <- x

> f <- function(x,y) { r <- sqrt(x^2+y^2); 10 * sin(r)/r }

> z <- outer(x, y, f)

> z[is.na(z)] <- 1

> png(file="plot2.png",width=640,height=480) # PNG

图

> op <- par(bg = "white", mar=c(0,2,3,0)+.1)

> persp(x, y, z,theta = 30, phi = 30,expand = 0.5,col = "lightblue",ltheta =120,shade = 0.75,ticktype = "detailed",xlab = "X", ylab = "Y", zlab ="Sinc(r)",main = "Plot")

> par(op)

> dev.off()

> CairoPNG(file="Cairo2.png",width=640,height=480)

> op <- par(bg = "white", mar=c(0,2,3,0)+.1)

> persp(x, y, z,theta = 30, phi = 30,expand = 0.5,col = "lightblue",ltheta =120,shade = 0.75,ticktype = "detailed",xlab = "X", ylab = "Y", zlab ="Sinc(r)",main = "Cairo")

> par(op)

> dev.off()

在当前目录，会生成2个png文件，即plot2.png和Cairo2.png，见图1-14和图1-15。

图1-14　以png()函数生成的图

图1-15　以CairoPNG()函数生成的图

3.大量文字的图片

最后，我们针对包含大量文字的图片比较png()和CairoPNG()函数输出的效果。

> library(MASS) #

加载MASS

包

> data(HairEyeColor) #

加载HairEyeColor

数据集

> x <- HairEyeColor[,,1]+HairEyeColor[,,2]

> n <- 100

> m <- matrix(sample(c(T,F),n^2,replace=T), nr=n, nc=n)

> png(file="plot5.png",width=640,height=480) # PNG

图

> biplot(corresp(m, nf=2), main="Plot")

> dev.off()

> CairoPNG(file="Cairo5.png",width=640,height=480)

> biplot(corresp(m, nf=2), main="Cairo")

> dev.off()

在当前目录，会生成2个png文件，即plot5.png和Cairo5.png，见图1-16和图1-17。

图1-16　以png()函数生成的图

图1-17　以CairoPNG()函数生成的图

我们查看两个文件的属性，发现以png直接生成的图54KB，以CairoPNG生成的图43.8KB，如图1-18所示。

图1-18　对比两幅图的文件属性

SVG图形输出代码为：

> svg(file="plot-svg5.svg",width=6,height=6)

> biplot(corresp(m, nf=2), main="Plot-svg")

> dev.off()

> CairoSVG(file="Cairo-svg5.svg",width=6,height=6)

> biplot(corresp(m, nf=2), main="Cairo-svg")

> dev.off()

在当前目录，会生成2个svg文件，即plot-svg5.svg和Cairo-svg5.svg。

就以上的3个例子来说，我们分辨不出CairoPNG()和png()之间有太大区别，只是Cairo感觉更淡、更柔和一些。关于这一点谢益辉补充说，看不出区别是因为现在R的png()设备本来就是默认用cairo，几年前png()不用cairo，所以有时候PNG图的质量很差。多数情况下是几乎没有区别，只有少数情况下抗锯齿的表现不一样。

1.8　caTools：一个奇特的工具集

问题

R除了统计，还能干什么？

引言

R语言生来就是自由的，不像Java和PHP等有统一的规范约束。R语言不仅命名、语法各包各异，就连功能也是各种混搭。caTools库就是这种混搭库，包括了不相关的几组函数工具集，有图片处理的，有编解码的，有分类器的，有向量计算的，有科学计算的，而且都很好用！以至于我都不知道如何用简短的语言去描述这个包了！唯有用“奇特”来概括它的特点。

1.8.1　caTools介绍

caTools是一个基础的工具包，包括移动窗口（Moving Window）统计，二进制图片读写，快速计算曲线的面积AUC，LogitBoost分类器，base64的编码器/解码器，快速计算舍入、误差、求和、累计求和等函数。下面分别介绍caTools包中的API。

（1）二进制图片读写

·read.gif &write.gif:gif格式图片的读写。

·read.ENVI &write.ENVI:ENVI格式图片的读写，如GIS图片。

（2）base64的编码器/解码器:

·base64encode:编码器。

·base64decode:解码器。

注　Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64，所以每6个位元为一个单元，对应某个可打印字符。

（3）快速计算曲线的面积AUC

·colAUC:计算ROC曲线的面积（AUC）。

·combs:向量元素的无序组合。

·trapz:数值积分形梯形法则。

（4）LogitBoost分类器

·LogitBoost:logitBoost分类算法。

·predict.LogitBoost:预测logitBoost分类。

·sample.split:把数据切分成训练集和测试集。

（5）快速计算工具

·runmad:计算向量中位数。

·runmean:计算向量均值。

·runmin&runmax:计算向量最小值和最大值。

·runquantile:计算向量位数。

·runsd:计算向量标准差。

·sumexact,cumsumexact:无误差求和，针对编程语言关于double类型的精度优化。

1.8.2　caTools安装

本节使用的系统环境是：

·Linux:Ubuntu Server 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

注　caTools同时支持Windows 7环境和Linux环境。

安装caTools包非常简单，只需要一条命令就可以完成。

~ R #

启动R

程序

> install.packages("caTools") #

执行caTools

安装命令

1.8.3　caTools使用

在R环境中，加载caTools类库：

> library(caTools)

1.二进制图片读写gif

（1）写一个gif图片

> write.gif(volcano, "volcano.gif", col=terrain.colors, flip=TRUE, scale="always",comment="Maunga Whau Volcano") #

取datasets::volcano

数据集，写入volcano.gif

（2）读一个gif图片到内存，再从内存输出

> y = read.gif("volcano.gif", verbose=TRUE, flip=TRUE) #

读入图片到变量y

GIF image header

Global colormap with 256 colors

Comment Extension

Image [61 x 87]: 3585 bytes

GIF Terminator

> attributes(y) #

查看变量的属性

$names

[1] "image"       "col"          "transparent"    "comment"

> class(y$image) #

查看图片存储矩阵

[1] "matrix"

> ncol(y$image) #

列数

[1] 61

> nrow(y$image) #

行数

[1] 87

> head(y$col,10) #

颜色表，取前10

个

[1] "#00A600FF" "#01A600FF" "#03A700FF" "#04A700FF" "#05A800FF" "#07A800FF" "#08A900FF"

[8] "#09A900FF" "#0BAA00FF" "#0CAA00FF"

> y$comment #

查看图片备注

[1] "Maunga Whau Volcano"

> image(y$image, col=y$col, main=y$comment, asp=1) #

通过y

变量画图，如图1-19

所示[1]

（3）创建一个Git动画

> x <- y <- seq(-4*pi, 4*pi, len=200)

> r <- sqrt(outer(x^2, y^2, "+"))

> image = array(0, c(200, 200, 10))

> for(i in 1:10) image[,,i] = cos(r-(2*pi*i/10))/(r^.25)

> write.gif(image, "wave.gif", col="rainbow")

> y = read.gif("wave.gif")

> for(i in 1:10) image(y$image[,,i], col=y$col, breaks=(0:256)-0.5, asp=1)

在当前的操作目录，会生成一个wave.gif 的文件，如图1-20所示。动画演示效果参见本书在线资源中的文件wave.gif。

图1-19　火山地形图

图1-20　同心圆变化动画

我们看到，caTools与谢益辉的animation包有一样的GIF输出动画功能。但使用caTools做gif动画，不需要依赖其他软件库。而animation的saveGIF函数需要依赖于ImageMagick或者GraphicsMagick等第三方软件。

2.Base64的编码器/解码器

Base64常用于处理文本数据的场合，表示、传输、存储一些二进制数据，包括MIME的email、email via MIME、在XML中存储复杂数据。Base64的编码解码，只支持向量（vector）类型，不支持data.frame和list类型。把一个boolean向量编解码，代码如下：

把一个字符串编解码，代码如下：

> size=1 #

设置每个元素占用的字节数

> x = (10*runif(10)>5)

> y = base64encode(x, size=size)

> z = base64decode(y, typeof(x), size=size)

> x #

原始数据

[1] FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE

> y #

编码后的密文

[1] "AAAAAAEBAAAAAA=="

> z #

解码后的明文

[1] FALSE FALSE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE

把一个字符串编解码，代码如下：

> x = "Hello R!!" # character

> y = base64encode(x)

> z = base64decode(y, typeof(x))

> x #

原始数据

[1] "Hello R!!"

> y #

编码后的密文

[1] "SGVsbG8gUiEh"

> z #

解码后的明文

[1] "Hello R!!"

错误测试：把一个数据框编解码。

> data(iris)

> class(iris)

[1] "data.frame"

> head(x) #

原始数据

Sepal.Length Sepal.Width Petal.Length Petal.Width Species

1          5.1         3.5          1.4         0.2  setosa

2          4.9         3.0          1.4         0.2  setosa

3          4.7         3.2          1.3         0.2  setosa

4          4.6         3.1          1.5         0.2  setosa

5          5.0         3.6          1.4         0.2  setosa

6          5.4         3.9          1.7         0.4  setosa

> base64encode(x) #

对数据框进行编码

Error in writeBin(x, raw(), size = size, endian = endian) :

can only write vector objects

3.ROC曲线

ROC曲线就是接收者操作特征曲线（receiver operating characteristic curve）的简称，这是一种坐标图式的分析工具，主要用于选择最佳的信号侦测模型、舍弃次佳的模型和在同一模型中设定最佳阈值。ROC曲线首先由二战中的电子工程师和雷达工程师发明，用来侦测战场上的敌军载具（飞机、船舰等），即信号检测，之后很快就被引进心理学来进行信号的知觉检测。数十年来，ROC分析被用于医学、无线电、生物学、犯罪心理学等领域，而且最近在机器学习（machine learning）和数据挖掘（data mining）领域也得到了很好的发展。

取MASS::cats数据集，3列分别是Sex（性别）、Bwt（体重）、Hwt（心脏重量）。

> library(MASS)

> data(cats) #

加载数据集

> head(cats) #

打印前6

行数据

Sex Bwt Hwt

1   F 2.0 7.0

2   F 2.0 7.4

3   F 2.0 9.5

4   F 2.1 7.2

5   F 2.1 7.3

6   F 2.1 7.6

> colAUC(cats[,2:3], cats[,1], plotROC=TRUE) #

计算ROC

的曲线面积AUC

，输出图片，如图1-21

所示

Bwt        Hwt

F vs. M 0.8338451 0.759048

从AUC判断分类器（预测模型）优劣的标准：

·AUC=1，是完美分类器，采用这个预测模型时，不管设定什么阈值都能得出完美预测。在绝大多数预测的场合，不存在完美分类器。

·0.5<AUC<1，优于随机猜测。这个分类器（模型）如果妥善设定阈值的话，能有预测价值。

·AUC=0.5，跟随机猜测一样（例如丢硬币），模型没有预测价值。

·AUC<0.5，比随机猜测还差；但只要总是反预测而行，就优于随机猜测。

从图1-21我们看到Bwt和Hwt都在（0.5,1）之间，因此，cats的数据集是一个真实有效数据集。如果cats的数据集，是一个通过分类预测的数据集，用AUC对数据集的评分，就可以检验分类器的好坏了。

图1-21　ROC曲线

4.向量元素的无序组合

combs(v,k)函数，用于创建无序的组合的矩阵，矩阵的列表示以几种元素进行组合，矩阵的行表示每种不同的组合。参数v是向量；k是数值，小于等于v的长度[1:length(v)]。

> combs(2:5, 3)

[,1] [,2] [,3]

[1,]    2    3    4

[2,]    2    3    5

[3,]    2    4    5

[4,]    3    4    5

> combs(c("cats", "dogs", "mice"), 2)

[,1]   [,2]

[1,] "cats" "dogs"

[2,] "cats" "mice"

[3,] "dogs" "mice"

> a = combs(1:4, 2) #

快速组合构建矩阵

> a

[,1] [,2]

[1,]    1    2

[2,]    1    3

[3,]    1    4

[4,]    2    3

[5,]    2    4

[6,]    3    4

> b = matrix( c(1,1,1,2,2,3,2,3,4,3,4,4), 6, 2)

> b

[,1] [,2]

[1,]    1    2

[2,]    1    3

[3,]    1    4

[4,]    2    3

[5,]    2    4

[6,]    3    4

5.数值积分梯形法则

梯形法则原理：将被积函数近似为直线函数，被积的部分近似为梯形。

> x = (1:10)*pi/10

> trapz(x, sin(x))

[1] 1.934983

> x = (1:1000)*pi/1000

> trapz(x, sin(x))

[1] 1.999993

6.LogitBoost分类器

取datasets::iris数据集，5列分别是Sepal.Length（花萼长）、Sepal.Width（（花萼宽）、Petal.Length（花瓣长）、Petal.Width（花瓣宽）、Species（种属）。

> head(iris)

Sepal.Length Sepal.Width Petal.Length Petal.Width Species

1          5.1         3.5          1.4         0.2  setosa

2          4.9         3.0          1.4         0.2  setosa

3          4.7         3.2          1.3         0.2  setosa

4          4.6         3.1          1.5         0.2  setosa

5          5.0         3.6          1.4         0.2  setosa

6          5.4         3.9          1.7         0.4  setosa

> Data = iris[,-5]

> Label = iris[, 5]

> model = LogitBoost(Data, Label, nIter=20) #

训练模型

> model #

模型数据

$Stump

feature threshhold sign feature threshhold sign feature threshhold sign

[1,]        3        1.9   -1       2        2.9   -1       4        1.6    1

[2,]        4        0.6   -1       3        4.7   -1       3        4.8    1

[3,]        3        1.9   -1       2        2.0   -1       4        1.7    1

[4,]        4        0.6   -1       3        1.9    1       3        4.9    1

[5,]        3        1.9   -1       4        1.6   -1       4        1.3    1

[6,]        4        0.6   -1       1        6.5    1       2        2.6   -1

[7,]        3        1.9   -1       3        1.9    1       4        1.7    1

[8,]        4        0.6   -1       2        2.0   -1       2        3.0   -1

[9,]        3        1.9   -1       3        5.0   -1       3        5.0    1

[10,]       4        0.6   -1       2        2.9    1       1        4.9   -1

[11,]       3        1.9   -1       3        1.9    1       3        4.4    1

[12,]       4        0.6   -1       2        2.0   -1       4        1.7    1

[13,]       3        1.9   -1       3        5.1   -1       2        3.1   -1

[14,]       4        0.6   -1       2        2.0   -1       3        5.1    1

[15,]       3        1.9   -1       3        1.9    1       1        6.5   -1

[16,]       4        0.6   -1       4        1.6   -1       3        5.1    1

[17,]       3        1.9   -1       2        3.1    1       2        3.1   -1

[18,]       4        0.6   -1       3        1.9    1       1        4.9   -1

[19,]       3        1.9   -1       2        2.0   -1       4        1.4    1

[20,]       4        0.6   -1       3        5.1   -1       2        2.2   -1

$lablist

[1] setosa versicolor virginica

Levels: setosa versicolor virginica

attr(,"class")

[1] "LogitBoost"

> Lab = predict(model, Data) #

分类预测，Lab

只显示分类的结果， Prob

显示各分类的概率

> Prob = predict(model, Data, type="raw")

> t = cbind(Lab, Prob) #

结果合并，打印前6

列

> head(t)

Lab setosa  versicolor     virginica

[1,]   1      1 0.017986210  1.522998e-08

[2,]   1      1 0.002472623  3.353501e-04

[3,]   1      1 0.017986210  8.315280e-07

[4,]   1      1 0.002472623  4.539787e-05

[5,]   1      1 0.017986210  1.522998e-08

[6,]   1      1 0.017986210  1.522998e-08

前6条数据，Lab列表示数据属于分类1，即setosa。其他3列setosa、versicolor、virginica，分类表示属于该分类的概率是多少。下面设置迭代次数，比较分类结果与实际数据结果。

> table(predict(model, Data, nIter= 2), Label) #

设置迭代次数为2

Label

setosa versicolor virginica

setosa           48          0         0

versicolor        0         45         1

virginica         0          3        45

> table(predict(model, Data, nIter=10), Label) #

设置迭代次数为10

Label

setosa versicolor virginica

setosa           50          0         0

versicolor        0         47         0

virginica         0          1        47

> table(predict(model, Data), Label)   #

默认迭代次数,

训练时LogitBoost

的nIter

值

Label

setosa versicolor virginica

setosa           50          0         0

versicolor        0         49         0

virginica         0          0        48

从上面3次测试结果可以看出，迭代次数越多模型分类越准确。下面随机划分训练集和测试集，并分类预测。

> mask = sample.split(Label) #

随机取训练集

> length(which(mask)) #

训练集99

条记录

[1] 99

> length(which(!mask)) #

测试集51

条记录

[1] 51

> model = LogitBoost(Data[mask,], Label[mask], nIter=10) #

训练模型

> table(predict(model, Data[!mask,], nIter=2), Label[!mask]) #

分类预测

setosa versicolor virginica

setosa           16          0         0

versicolor        0         15         3

virginica         0          1        12

> table(predict(model, Data[!mask,]), Label[!mask])

setosa versicolor virginica

setosa           17          0         0

versicolor        0         16         4

virginica         0          1        13

7.快速计算工具runmean

均线在股票交易上非常流行，是一种简单、实用的看盘指标。下面我们对时间序列数据取均线，输出结果如图1-22所示。

Time Series:

Start = 1

End = 150

Frequency = 1

[1] 200.1 199.5 199.4 198.9 199.0 200.2 198.6 200.0 200.3 201.2 201.6 201.5

[13] 201.5 203.5 204.9 207.1 210.5 210.5 209.8 208.8 209.5 213.2 213.7 215.1

[25] 218.7 219.8 220.5 223.8 222.8 223.8 221.7 222.3 220.8 219.4 220.1 220.6

> plot(BJsales,col="black", lty=1,lwd=1, main = "Moving Window Means")

> lines(runmean(BJsales, 3), col="red", lty=2, lwd=2)

> lines(runmean(BJsales, 8), col="green", lty=3, lwd=2)

> lines(runmean(BJsales,15), col="blue", lty=4, lwd=2)

> lines(runmean(BJsales,24), col="magenta", lty=5, lwd=2)

> lines(runmean(BJsales,50), col="cyan", lty=6, lwd=2)

图1-22　时间序列数据的均线图

我们从图1-22中看到6条线，黑色是原始数据，其他各种颜色线代表不同单位的均线。比如，红色（red）线表示3个点的平均，绿色（green）线表示8个点的平均。 [2]

8.快速计算工具组合

对数据集取最大路径（runmax）、最小路径（runmin）、均值路径（runmean）和中位数路径（runmed），输出结果如图1-23所示。

> n=200;k=25

> set.seed(100)

> x = rnorm(n,sd=30) + abs(seq(n)-n/4)

> plot(x, main = "Moving Window Analysis Functions (window size=25)")

> lines(runmin (x,k), col="red",lty=1, lwd=1)

> lines(runmax (x,k), col="cyan",lty=1, lwd=1)

> lines(runmean(x,k), col="blue",lty=1, lwd=1)

> lines(runmed (x,k), col="green",lty=2, lwd=2)

图1-23　路径图

9.无误差求和

各种编程语言，用计算机做小数计算的时候，都会出现计算误差的情况，R语言也有这个问题。首先用sum()函数求和。

> x = c(1, 1e20, 1e40, -1e40, -1e20, -1) #

计算误差求和

> a = sum(x); print(a)

[1] -1e+20

> b = sumexact(x); print(b) #

无计算误差求和

[1] 0

我们对向量x求和，各组值加起来正好为0。但是sum()函数，结果是-1e+20，这是由于编程精度的问题造成的计算误差。通过sumexact()函数修正后，就没有误差了。下面用cumsum()函数累积求和。

> a = cumsum(x); print(a) #

计算误差累积求和

[1] 1e+00 1e+20 1e+40 0e+00 -1e+20 -1e+20

> b = cumsumexact(x); print(b) #

无计算误差累积求和

[1] 1e+00 1e+20 1e+40 1e+20 1e+00 0e+00

cumsum()函数同样出现了精度的误差，需要用cumsumexact()函数来修正。

最后还是要用“奇特”来概括这个工具集，相信你也发现了它的奇特。

[1] 读者可自己运行代码，查看生成的彩色图片。—编辑注

[2] 请读者运行源代码来查看彩图。—编辑注

第2章　时间序列基础包

本章主要介绍了时间序列数据处理的3个工具包，帮助读者掌握时间序列在R语言中的数据结构和基本使用。

2.1　R语言时间序列基础库zoo

问题

R语言怎么处理时间序列数据？

引言

时间序列分析是一种动态数据处理的统计方法，通过对时间序列数据的分析，我们可以感觉到世界正改变着什么！R语言作为统计分析的利器，对时间序列处理有着强大的支持。在R语言中，单独为时间序列数据定义了一种数据类型zoo，zoo是时间序列的基础，也是股票分析的基础。本节将介绍zoo库在R语言中的结构和使用。

2.1.1　zoo包介绍

zoo是一个R语言类库，zoo类库中定义了一个名为zoo的S3类型对象，用于描述规则的和不规则的有序的时间序列数据。zoo对象是一个独立的对象，包括索引、日期、时间，只依赖于基础的R环境。zooreg对象继承了zoo对象，只能用于规则的时间序列数据。R语言中很多其他的程序包，都是以zoo和zooreg作为时间序列数据的基础的！zoo包的API主要有6类，下面一一介绍。

（1）基础对象

·zoo:有序的时间序列对象。

·zooreg:规则的时间序列对象，继承zoo对象。与zoo相比，不同之处在于zooreg要求数据是连续的。

（2）类型转换

·as.zoo:把一个对象转型为zoo类型。

·plot.zoo:为plot函数提供zoo的接口。

·xyplot.zoo:为lattice的xyplot函数提供zoo的接口。

·ggplot2.zoo:为ggplot2包提供zoo的接口。

（3）数据操作

·coredata:查看或编辑zoo的数据部分。

·index:查看或编辑zoo的索引部分。

·window.zoo:按时间过滤数据。

·merge.zoo:合并多个zoo对象。

·read.zoo:从文件读写zoo序列。

·aggregate.zoo:计算zoo数据。

·rollapply:对zoo数据的滚动处理。

·rollmean:对zoo数据的滚动计算均值。

（4）NA值处理

·na.fill:NA值的填充。

·na.locf:替换NA值。

·na.aggregate:计算统计值替换NA值。

·na.approx:计算插值替换NA值。

·na.StructTS:计算季节Kalman滤波替换NA值。

·na.trim:过滤有NA的记录。

（5）辅助工具

·is.regular:检查是否是规则的序列。

·lag.zoo:计算步长和差分。

·MATCH:取交集。

·ORDER:值排序，输出索引。

（6）显示控制

·yearqtr:以年季度显示时间。

·yearmon:以年月显示时间。

·xblocks:作图沿x轴分割图形。

·make.par.list:用于给plot.zoo和xyplot.zoo数据格式转换。

2.1.2　zoo安装

本节使用的系统环境是：

·Win764bit

·R:3.0.1x86_64-w64-mingw32/x64b4bit

注　zoo同时支持Windows 7环境和Linux环境。

zoo包的安装过程如下：

~ R #

启动R

程序

> install.packages("zoo") #

安装zoo

包

> library(zoo) #

加载zoo

包

2.1.3　zoo包的使用

1.zoo对象

zoo对象包括两部分，即数据部分和索引部分。首先是函数定义：

zoo(x = NULL, order.by = index(x), frequency = NULL)

其中x是数据部分，允许类型为向量、矩阵、因子；order.by是索引部分，字段唯一性要求，用于排序；frequency是每个时间单元显示的数量。

以下代码构建一个zoo对象，以时间为索引，产生的结果是图2-1。特别要注意的一点是，zoo对象可以接受不连续的时间序列数据。

> x.Date <- as.Date("2003-02-01") + c(1, 3, 7, 9, 14)

– 1 #

定义一个不连续的日期的向量

> x.Date

[1] "2003-02-01" "2003-02-03" "2003-02-07" "2003-02-09" "2003-02-14"

> class(x.Date)

[1] "Date"

> x <- zoo(rnorm(5), x.Date) #

定义不连续的zoo

对象

> x

2003-02-01 2003-02-03 2003-02-07 2003-02-09 2003-02-14

0.01964254 0.03122887 0.64721059 1.47397924 1.29109889

> class(x)

[1] "zoo"

> plot(x) #

画图显示

图2-1　时间序列图

接下来，我们以数字为索引创建多组时间序列。用如下代码，生成一个有12个元素的4行3列的矩阵，以数字0:10为索引，创建一个zoo类型对象y，并以图形输出y，产生的结果如图2-2所示。

> y <- zoo(matrix(1:12, 4, 3),0:10)

> y

0  1 5  9

1  2 6 10

2  3 7 11

3  4 8 12

4  1 5  9

5  2 6 10

6  3 7 11

7  4 8 12

8  1 5  9

9  2 6 10

10 3 7 11

> plot(y) #

矩阵的每一列为一组时间序列图

图2-2　分组的时间序列图

2.zooreg对象

首先是函数定义：

zooreg(data, start = 1, end = numeric(), frequency = 1,

deltat = 1, ts.eps = getOption("ts.eps"), order.by = NULL)

下面是参数说明。

·data:数据部分，允许类型为向量、矩阵、因子。

·start:时间部分，开始时间。

·end:时间部分，结束时间。

·frequency:每个时间单元显示的数量。

·deltat:连续观测的采样周期，不能与frequency同时出现，例如，取每月的数据，为1/12。

·ts.eps:时间序列间隔，当数据时间间隔小于ts.eps时，使用ts.eps作为时间间隔。通过getOption(“ts.eps”)设置，默认是1e-05。

·order.by:索引部分，字段唯一性要求，用于排序,继承zoo的order.by。

以下代码构建一个zooreg对象，以连续的年（季度）时间为索引，产生的结果是图2-3。

> zooreg(1:10, frequency = 4, start = c(1959, 2))

1959(2) 1959(3) 1959(4) 1960(1) 1960(2) 1960(3) 1960(4) 1961(1) 1961(2) 1961(3)

1       2      3       4       5       6       7      8       9       10

> as.zoo(ts(1:10, frequency = 4, start = c(1959, 2)))

1959(2) 1959(3) 1959(4) 1960(1) 1960(2) 1960(3) 1960(4) 1961(1) 1961(2) 1961(3)

1       2      3       4       5       6       7      8       9       10

> zr<-zooreg(rnorm(10), frequency = 4, start = c(1959, 2))

> plot(zr)

图2-3　zooreg对象的时间序列图

3.zoo对象与zooreg对象的区别

zoo对象与zooreg对象的区别体现在计算步长和差分方面。

·lag（步长）：zoo根据索引计算，zooreg根据值计算。

·diff（差分）：zoo根据索引计算，zooreg根据值计算。

例如，对同一组值不连续的数据（1,2,3,6,7,8），二者的计算结果如下。

> x <- c(1, 2, 3, 6, 7, 8)

> zz <- zoo(x, x)

> zr <- as.zooreg(zz)

> lag(zz, k = -1) #

计算步长

2 3 6 7 8

1 2 3 6 7

> lag(zr, k = -1)

2 3 4 7 8 9

1 2 3 6 7 8

> diff(zz) #

计算差分

2 3 6 7 8

1 1 3 1 1

> diff(zr)

2 3 7 8

1 1 1 1

4.zoo对象的类型转换

首先，把对象从其他类型转型到zoo类型。

> as.zoo(rnorm(5))   #

把一个基本类型的向量转型到zoo

类型

1           2           3             4            5

-0.4892119  0.5740950    0.7128003     0.6282868    1.0289573

> as.zoo(ts(rnorm(5),   start = 1981,    freq = 12))

1981(1)    1981(2)      1981(3)       1981(4)      1981(5)

2.3198504  0.5934895   -1.9375893    -1.9888237    1.0944444

> x <- as.zoo(ts(rnorm(5), start = 1981, freq = 12)); x #

把一个ts

类型转型到zoo

类型

1981(1)    1981(2)     1981(3)    1981(4)     1981(5)

1.8822996  1.6436364   0.1260436 -2.0360960  -0.1387474

其次，把对象从zoo类型转型到其他类型。

> as.matrix(x) #

把zoo

类型，转型到矩阵

x

1981(1)   1.8822996

1981(2)   1.6436364

1981(3)   0.1260436

1981(4)  -2.0360960

1981(5)  -0.1387474

> as.vector(x) #

把zoo

类型，转型到数字向量

[1]  1.8822996 1.6436364   0.1260436   -2.0360960 -0.1387474

> as.data.frame(x) #

把zoo

类型，转型到数据框

x

1981(1)   1.8822996

1981(2)   1.6436364

1981(3)   0.1260436

1981(4)  -2.0360960

1981(5)  -0.1387474

> as.list(x) #

把zoo

类型，转型到列表

[[1]]

1981(1)   1981(2)     1981(3)    1981(4)    1981(5)

1.8822996  1.6436364   0.1260436 -2.0360960 -0.1387474

5.用ggplot2画时间序列

由于ggplot2不支持zoo类型的数据，因此需要通过ggplot2::fortify()函数，调用zoo::fortify.zoo()函数，把zoo类型转换成ggplot2可识别的类型后，ggplot2才可以对zoo类型数据的画图。以下代码用ggplot2画zoo类型的时间序列图，产生的结果是图2-4。

> library(scales)

> x.Date <- as.Date(paste(2003, 02, c(1, 3, 7, 9, 14), sep = "-")) #

构建数据对象

> x <- zoo(rnorm(5), x.Date)

> xlow <- x - runif(5)

> xhigh <- x + runif(5)

> z <- cbind(x, xlow, xhigh)

> z #

显示数据集

x        xlow       xhigh

2003-02-01 -0.36006612 -0.88751958 0.006247816

2003-02-03  1.35216617  0.97892538 2.076360524

2003-02-07  0.61920828  0.23746410 1.156569424

2003-02-09  0.27516116  0.09978789 0.777878867

2003-02-14  0.02510778 -0.80107410 0.541592929

#

对zoo

类型的数据，用fortify()

转换成data.frame

类型

> g<-ggplot(aes(x = Index, y = Value), data = fortify(x, melt = TRUE))

> g<-g+geom_line()

> g<-g+geom_line(aes(x = Index, y = xlow), colour = "red", data = fortify(xlow))

> g<-g+geom_ribbon(aes(x = Index, y = x, ymin = xlow, ymax = xhigh), data =fortify(x), fill = "darkgray")

> g<-g+geom_line()

> g<-g+xlab("Index") + ylab("x")

> g

图2-4　用ggplot2画的时间序列图

6.zoo对象的数据操作

使用coredata()函数修改zoo类型的数据部分。

> x.date <- as.Date(paste(2003, rep(1:4, 4:1), seq(1,20,2), sep = "-"))

> x <- zoo(matrix(rnorm(20), ncol = 2), x.date)

> coredata(x)  #

查看数据部分

[,1]        [,2]

[1,] -1.04571765  0.92606273

[2,] -0.89621126  0.03693769

[3,]  1.26938716 -1.06620017

[4,]  0.59384095 -0.23845635

[5,]  0.77563432  1.49522344

[6,]  1.55737038  1.17215855

[7,] -0.36540180 -1.45770721

[8,]  0.81655645  0.09505623

[9,] -0.06063478  0.84766496

[10,] -0.50137832 -1.62436453

> coredata(x) <- matrix(1:20, ncol = 2) #

修改数据部分

> x #

查看修改后的数据集

2003-01-01  1 11

2003-01-03  2 12

2003-01-05  3 13

2003-01-07  4 14

2003-02-09  5 15

2003-02-11  6 16

2003-02-13  7 17

2003-03-15  8 18

2003-03-17  9 19

2003-04-19  10 20

使用index()函数修改zoo类型的索引部分。

> x.date <- as.Date(paste(2003, rep(1:4, 4:1), seq(1,20,2), sep = "-"))

> x <- zoo(matrix(rnorm(20), ncol = 2), x.date)

> index(x) #

查看索引部分

[1] "2003-01-01" "2003-01-03" "2003-01-05" "2003-01-07" "2003-02-09"

[6] "2003-02-11" "2003-02-13" "2003-03-15" "2003-03-17" "2003-04-19"

> index(x) <- 1:nrow(x) #

修改索引部分

> index(x)  #

查看修改后的索引部分

[1]    1 2 3  4   5   6  7  8   9  10

使用window.zoo()函数按时间过滤数据。

> x.date <- as.Date(paste(2003, rep(1:4, 4:1), seq(1,20,2), sep = "-"))

> x <- zoo(matrix(rnorm(20), ncol = 2), x.date)

> window(x, start = as.Date("2003-02-01"), end = as.Date("2003-03-01"))

#

取日期从2003-02-01

到2003-03-01

之间的数据

2003-02-09   0.7021167 -0.3073809

2003-02-11   2.5071111  0.6210542

2003-02-13  -1.8900271  0.1819022

> window(x, index = x.date[1:6], start = as.Date("2003-02-01"))

#

取日期从2003-02-01

开始的，且索引日期在x.date[1:6]

中的数据

2003-02-09  0.7021167 -0.3073809

2003-02-11  2.5071111  0.6210542

> window(x, index = x.date[c(4, 8, 10)]) #

取索引日期在x.date[c(4, 8, 10)]

中的数据

2003-01-07  1.4623515 -1.198597

2003-03-15 -0.5898128  1.318401

2003-04-19 -0.4209979 -1.648222

使用merge.zoo()合并多个zoo对象。

> y1 <- zoo(matrix(1:10, ncol = 2), 1:5);y1 #

创建2

个zoo

数据

1 1 6

2 2 7

3 3 8

4 4 9

5 5 10

> y2 <- zoo(matrix(rnorm(10), ncol = 2), 3:7);y2

3  1.4810127  0.13575871

4 -0.3914258  0.06404148

5  0.6018237  1.85017952

6  1.2964150 -0.12927481

7  0.2211769  0.32381709

> merge(y1, y2, all = FALSE)  #

以相同的索引值合并数据

y1.1 y1.2       y2.1       y2.2

3    3    8  0.9514985  1.7238941

4    4    9 -1.1131230 -0.2061446

5    5   10  0.6169665 -1.3141951

> merge(y1, y2,  all = FALSE, suffixes = c("a", "b")) #

自定义数据列的名字

a.1 a.2         b.1        b.2

3   3   8  0.9514985   1.7238941

4   4   9 -1.1131230  -0.2061446

5   5   10 0.6169665  -1.3141951

> merge(y1, y2, all = TRUE)   #

合并完整的数据集，空数据默认以NA

填充

y1.1 y1.2       y2.1        y2.2

1    1    6         NA          NA

2    2    7         NA          NA

3    3    8  0.9514985   1.7238941

4    4    9 -1.1131230  -0.2061446

5    5   10  0.6169665  -1.3141951

6   NA   NA  0.5134937   0.0634741

7   NA   NA  0.3694591  -0.2319775

> merge(y1, y2, all = TRUE, fill = 0) #

合并完整的数据集，空数据以0

填充

y1.1 y1.2       y2.1        y2.2

1    1    6  0.0000000   0.0000000

2    2    7  0.0000000   0.0000000

3    3    8  0.9514985   1.7238941

4    4    9 -1.1131230  -0.2061446

5    5   10  0.6169665  -1.3141951

6    0    0  0.5134937   0.0634741

7    0    0  0.3694591  -0.2319775

使用aggregate.zoo()函数对zoo数据进行计算。

> x.date <- as.Date(paste(2004, rep(1:4, 4:1), seq(1,20,2), sep = "-"))

#

创建zoo

类型数据集x

> x <- zoo(rnorm(12), x.date); x

2004-01-01  2004-01-03  2004-01-05  2004-01-07  2004-02-09  2004-02-11

0.67392868  1.95642526 -0.26904101 -1.24455152 -0.39570292  0.09739665

2004-02-13  2004-03-15  2004-03-17  2004-04-19

-0.23838695 -0.41182796 -1.57721805 -0.79727610

> x.date2 <- as.Date(paste(2004, rep(1:4, 4:1), 1, sep = "-")); x.date2

#

创建时间向量x.date2

[1] "2004-01-01" "2004-01-01" "2004-01-01" "2004-01-01" "2004-02-01"

[6] "2004-02-01" "2004-02-01" "2004-03-01" "2004-03-01" "2004-04-01"

> x2 <- aggregate(x, x.date2, mean); x2 #

计算x

以x.date2

为时间分割规则的均值

2004-01-01 2004-02-01 2004-03-01 2004-04-01

0.2791904 -0.1788977 -0.9945230 -0.7972761

7.zoo对象数据函数化处理

使用rollapply()函数对zoo数据进行函数化处理。

> z <- zoo(11:15, as.Date(31:35))

> rollapply(z, 2, mean) #

从起始日开始，计算连续2

日的均值

1970-02-01 1970-02-02 1970-02-03 1970-02-04

11.5       12.5       13.5       14.5

> rollapply(z, 3, mean) #

从起始日开始，计算连续3

日的均值

1970-02-02 1970-02-03 1970-02-04

12         13         14

等价操作变换：用rollapply()实现aggregate()的操作。

> z2 <- zoo(rnorm(6))

> rollapply(z2, 3, mean, by = 3) # means of nonoverlapping groups of 3

2          5

-0.3065197  0.6350963

> aggregate(z2, c(3,3,3,6,6,6), mean) # same

3          6

-0.3065197  0.6350963

等价操作变换：用rollapply()实现rollmean()的操作。

> rollapply(z2, 3, mean)  # uses rollmean which is optimized for mean

2          3           4          5

-0.3065197  -0.7035811 -0.1672344  0.6350963

> rollmean(z2, 3) # same

2          3           4          5

-0.3065197  -0.7035811 -0.1672344  0.6350963

8.NA值处理

使用na.fill()函数进行NA填充。

> z <- zoo(c(NA, 2, NA, 3, 4, 5, 9, NA));z #

创建有NA

值的zoo

对象

1  2  3  4  5  6  7  8

NA  2 NA  3  4  5  9 NA

> na.fill(z, "extend")  #

用extend

的方法，填充NA

值，即NA

前后项的均值填充

1    2   3   4   5   6   7   8

2.0  2.0 2.5 3.0 4.0 5.0 9.0 9.0

> na.fill(z, -(1:3))    #

自定义填充NA

值，即-(1:3)

循环填充

1  2   3  4   5  6  7   8

-1  2  -2  3   4 5  9  -3

> na.fill(z, c("extend", NA)) #

用extend

，配合自定义的方法，即extend

和自定义规则循环填充

1  2   3  4   5  6  7   8

2  2  NA  3   4  5  9   9

使用na.locf()函数进行NA替换。

> z <- zoo(c(NA, 2, NA, 3, 4, 5, 9, NA, 11));z

1  2  3  4  5  6  7  8  9

NA  2 NA  3  4  5  9 NA 11

> na.locf(z) #

用NA

的前一项的值，替换NA

值

2  3  4  5  6  7  8  9

2  2  3  4  5  9  9 11

> na.locf(z, fromLast = TRUE) #

用NA

的后一项的值，替换NA

值

1  2  3  4  5  6  7  8  9

2  2  3  3  4  5  9 11 11

使用na.aggregate()函数的统计计算的值替换NA值。

> z <- zoo(c(1, NA, 3:9),c(as.Date("2010-01-01") + 0:2,as.Date("2010-02-01") +

0:2,as.Date("2011-01-01") + 0:2));z

2010-01-01 2010-01-02 2010-01-03 2010-02-01 2010-02-02 2010-02-03 2011-01-01

1         NA          3          4          5          6          7

2011-01-02 2011-01-03

8          9

> na.aggregate(z)   #

计算排除NA

的其他项的均值，替换NA

值

2010-01-01 2010-01-02 2010-01-03 2010-02-01 2010-02-02 2010-02-03 2011-01-01

1.000      5.375      3.000      4.000      5.000      6.000      7.000

2011-01-02 2011-01-03

8.000      9.000

> na.aggregate(z, as.yearmon) #

以索引的年月分组的均值，替换NA

值

2010-01-01 2010-01-02 2010-01-03 2010-02-01 2010-02-02 2010-02-03 2011-01-01

1          2          3          4          5          6          7

2011-01-02 2011-01-03

8          9

> na.aggregate(z, months) #

以索引的月份分组的均值，替换NA

值

2010-01-01 2010-01-02 2010-01-03 2010-02-01 2010-02-02 2010-02-03 2011-01-01

1.0        5.6        3.0        4.0        5.0        6.0        7.0

2011-01-02 2011-01-03

8.0        9.0

> na.aggregate(z, format, "%Y") #

以正则表示的索引的年份分组的均值，替换NA

值

2010-01-01 2010-01-02 2010-01-03 2010-02-01 2010-02-02 2010-02-03 2011-01-01

1.0        3.8        3.0        4.0        5.0        6.0        7.0

2011-01-02 2011-01-03

8.0        9.0

使用na.approx()函数计算插值替换NA值。

> z <- zoo(c(2, NA, 1, 4, 5, 2), c(1, 3, 4, 6, 7, 8));z

1  3  4  6  7  8

2 NA  1  4  5  2

> na.approx(z)

1        3        4        6        7        8

2.000000 1.333333 1.000000 4.000000 5.000000 2.000000

> na.approx(z, 1:6)

1   3   4   6   7   8

2.0 1.5 1.0 4.0 5.0 2.0

使用na.StructTS()函数计算季节Kalman滤波替换NA值，产生的结果是图2-5。

> z <- zooreg(rep(10 * seq(4), each = 4) + rep(c(3, 1, 2, 4), times = 4),

start = as.yearqtr(2000), freq = 4)

> z[10] <- NA

> zout <- na.StructTS(z);zout

> plot(cbind(z, zout), screen = 1, col = 1:2, type = c("l", "p"), pch = 20)

图2-5　替换NA值的时间序列

使用na.trim()函数，去掉有NA的行。

> xx <- zoo(matrix(c(1, 4, 6, NA, NA, 7), 3), c(2, 4, 6));xx

2 1 NA

4 4 NA

6 6  7

> na.trim(xx)

6 6 7

9.数据显示格式

以“年+季度”格式输出

> x <- as.yearqtr(2000 + seq(0, 7)/4);x #

以年季默认格式输出

[1] "2000 Q1" "2000 Q2" "2000 Q3" "2000 Q4" "2001 Q1" "2001 Q2" "2001 Q3"

[8] "2001 Q4"

> format(x, "%Y Quarter %q") #

以年季自定义格式输出

[1] "2000 Quarter 1" "2000 Quarter 2" "2000 Quarter 3" "2000 Quarter 4"

[5] "2001 Quarter 1" "2001 Quarter 2" "2001 Quarter 3" "2001 Quarter 4"

> as.yearqtr("2001 Q2")

[1] "2001 Q2"

> as.yearqtr("2001 q2")

[1] "2001 Q2"

> as.yearqtr("2001-2")

[1] "2001 Q2"

以“年+月份”格式输出

> x <- as.yearmon(2000 + seq(0, 23)/12) ;x #

以年月默认格式输出

[1] "

一月 2000" "

二月 2000" "

三月 2000" "

四月 2000" "

五月 2000"

[6] "

六月 2000" "

七月 2000" "

八月 2000" "

九月 2000" "

十月 2000"

[11] "

十一月 2000" "

十二月 2000" "

一月 2001" "

二月 2001" "

三月 2001"

[16] "

四月 2001" "

五月 2001" "

六月 2001" "

七月 2001" "

八月 2001"

[21] "

九月 2001" "

十月 2001" "

十一月 2001" "

十二月 2001"

> as.yearmon("mar07", "%b%y")

[1] NA

> as.yearmon("2007-03-01")

[1] "

三月 2007"

> as.yearmon("2007-12")

[1] "

十二月 2007"

10.区间分割

使用xblock()函数，以不同的颜色划分3个区间，即（-Inf，15）、[15，30]和（30，Inf），产生的是图2-6。

图2-6　有分割线的时间序列

> set.seed(0)

> flow <- ts(filter(rlnorm(200, mean = 1), 0.8, method = "r"))

> rgb <- hcl(c(0, 0, 260), c = c(100, 0, 100), l = c(50, 90, 50), alpha = 0.3)

> plot(flow)

> xblocks(flow > 30, col = rgb[1]) ## high values red

> xblocks(flow < 15, col = rgb[3]) ## low value blue

> xblocks(flow >= 15 & flow <= 30, col = rgb[2]) ## the rest gray

11.从文件读入时间序列数据创建zoo对象

我们首先创建一个文件，并将其命名为read.csv，代码如下。

~ vi read.csv

2003-01-01,1.0073644,0.05579711

2003-01-03,-0.2731580,0.06797239

2003-01-05,-1.3096795,-0.20196174

2003-01-07,0.2225738,-1.15801525

2003-02-09,1.1134332,-0.59274327

2003-02-11,0.8373944,0.76606538

2003-02-13,0.3145168,0.03892812

2003-03-15,0.2222181,0.01464681

2003-03-17,-0.8436154,-0.18631697

2003-04-19,0.4438053,1.40059083

然后读入文件并生成zoo序列。

> r <- read.zoo(file="read.csv",sep = ",", format = "%Y-%m-%d") #

以zoo

格式读入数据

> r  #

查看数据

V2          V3

2003-01-01    1.0073644  0.05579711

2003-01-03   -0.2731580  0.06797239

2003-01-05   -1.3096795 -0.20196174

2003-01-07    0.2225738 -1.15801525

2003-02-09    1.1134332 -0.59274327

2003-02-11    0.8373944  0.76606538

2003-02-13    0.3145168  0.03892812

2003-03-15    0.2222181  0.01464681

2003-03-17   -0.8436154 -0.18631697

2003-04-19    0.4438053  1.40059083

> class(r)    #

查看数据类型

[1] "zoo"

我们已经完全掌握了zoo库及zoo对象的使用，接下来就可以放手去用R处理时间序列数据了！

2.2　可扩展的时间序列xts

问题

如何进行复杂的时间序列数据处理？

引言

本节将继续2.1节，介绍zoo包的扩展实现。看上去简单的时间序列，却内含复杂的规律。zoo作为时间序列的基础库，是面向通用的设计，可以用来定义股票数据，也可以分析天气数据。但由于业务行为的不同，我们需要更多的辅助函数帮助我们更高效地完成任务。xts扩展了zoo，提供更多的数据处理和数据变换的函数。

2.2.1　xts介绍

xts是对时间序列数据（zoo）的一种扩展实现，目标是为了统一时间序列的操作接口。实际上，xts类型继承了zoo类型，丰富了时间序列数据处理的函数，API定义更贴近使用者，更实用，更简单！

1.xts数据结构

xts扩展zoo的基础结构，由3部分组成，如图2-7所示。

·索引部分：时间类型向量。

·数据部分：以矩阵为基础类型，支持可以与矩阵相互转换的任何类型。

·属性部分：附件信息，包括时区和索引时间类型的格式等。

图2-7　xts数据结构

2.xts的API介绍

（1）xts基础

·xts:定义xts数据类型，继承zoo类型。

·coredata.xts:查看或编辑xts对象的数据部分。

·xtsAttributes:查看或编辑xts对象的属性部分。

·[.xts]:用[]语法，取数据子集。

·dimnames.xts:查看或编辑xts维度名。

·sample_matrix:测试数据集，包括180条xts对象的记录，matrix类型。

·xtsAPI:C语言API接口。

（2）类型转换

·as.xts:转换对象到xts（zoo）类型。

·as.xts.methods:转换对象到xts函数。

·plot.xts:为plot函数提供xts的接口作图。

·.parseISO8601:把字符串（ISO8601格式）输出为POSIXct类型的，包括开始时间和结束时间的list对象。

·firstof:创建一个开始时间，POSIXct类型。

·lastof:创建一个结束时间，POSIXct类型。

·indexClass:取索引类型。

·.indexDate:索引的日期。

·.indexday:索引的日期，同.indexDate。

·.indexyday:索引的年（日）值。

·.indexmday:索引的月（日）值。

·.indexwday:索引的周（日）值。

·.indexweek:索引的周值。

·.indexmon:索引的月值。

·.indexyear:索引的年值。

·.indexhour:索引的时值。

·.indexmin:索引的分值。

·.indexsec:索引的秒值。

（3）数据处理

·align.time:以下一个时间对齐数据，秒，分钟，小时。

·endpoints:按时间单元提取索引数据。

·merge.xts:合并多个xts对象，重写zoo::merge.zoo函数。

·rbind.xts:数据按行合并，为rbind函数提供xts的接口。

·split.xts:数据分割，为split函数，提供xts的接口。

·na.locf.xts:替换NA值，重写zoo:na.locf函数。

（4）数据统计

·apply.daily:按日分割数据，执行函数。

·apply.weekly:按周分割数据，执行函数。

·apply.monthly:按月分割数据，执行函数。

·apply.quarterly:按季分割数据，执行函数。

·apply.yearly:按年分割数据，执行函数。

·to.period:按期间分割数据。

·period.apply:按期间执行自定义函数。

·period.max:按期间计算最大值。

·period.min:按期间计算最小值。

·period.prod:按期间计算指数。

·period.sum:按期间求和。

·nseconds:计算数据集包括多少秒。

·nminutes:计算数据集包括多少分。

·nhours:计算数据集包括多少时。

·ndays:计算数据集包括多少日。

·nweeks:计算数据集包括多少周。

·nmonths:计算数据集包括多少月。

·nquarters:计算数据集包括多少季。

·nyears:计算数据集包括多少年。

·periodicity:查看时间序列的期间。

（5）辅助工具

·first:从开始到结束设置条件取子集。

·last:从结束到开始设置条件取子集。

·timeBased:判断是否是时间类型。

·timeBasedSeq:创建时间的序列。

·diff.xts:计算步长和差分。

·isOrdered:检查向量是否是顺序的。

·make.index.unique:强制时间唯一，增加毫秒随机数。

·axTicksByTime:计算X轴刻度标记位置按时间描述。

·indexTZ:查询xts对象的时区。

2.2.2　xts包的安装

本节使用的系统环境是：

·Win764bit

·R:3.0.1x86_64-w64-mingw32/x64b4bit

注　xts同时支持Windows 7环境和Linux环境。

xts的安装过程如下：

~ R #

启动R

程序

> install.packages("xts") #

安装xts

包

also installing the dependency 'zoo'

> library(xts)  #

加载xts

2.2.3　xts包的使用

1.xts对象的基本操作

查看xts包中的测试数据集sample_matrix。

> data(sample_matrix) #

加载sample_matrix

数据集

> head(sample_matrix) #

查看sample_matrix

数据集的前6

条数据

Open     High      Low    Close

2007-01-02 50.03978 50.11778 49.95041 50.11778

2007-01-03 50.23050 50.42188 50.23050 50.39767

2007-01-04 50.42096 50.42096 50.26414 50.33236

2007-01-05 50.37347 50.37347 50.22103 50.33459

2007-01-06 50.24433 50.24433 50.11121 50.18112

2007-01-07 50.13211 50.21561 49.99185 49.99185

接下来，定义一个xts类型对象。

> sample.xts <- as.xts(sample_matrix, descr='my new xts object')

#

创建一个xts

对象，并设置属性descr

> class(sample.xts)  # xts

是继承zoo

类型的对象

[1] "xts" "zoo"

> str(sample.xts)    #

打印对象结构

An 'xts' object on 2007-01-02/2007-06-30 containing:

Data: num [1:180, 1:4] 50 50.2 50.4 50.4 50.2 ...

- attr(*, "dimnames")=List of 2

..$ : NULL

..$ : chr [1:4] "Open" "High" "Low" "Close"

Indexed by objects of class: [POSIXct,POSIXt] TZ:

xts Attributes:

List of 1

$ descr: chr "my new xts object"

> attr(sample.xts,'descr')  #

查看对象的属性descr

[1] "my new xts object"

在[]中，通过字符串匹配进行xts数据查询。

> head(sample.xts['2007'])   #

选出2007

年的数据

Open     High      Low    Close

2007-01-02 50.03978 50.11778 49.95041 50.11778

2007-01-03 50.23050 50.42188 50.23050 50.39767

2007-01-04 50.42096 50.42096 50.26414 50.33236

2007-01-05 50.37347 50.37347 50.22103 50.33459

2007-01-06 50.24433 50.24433 50.11121 50.18112

2007-01-07 50.13211 50.21561 49.99185 49.99185

> head(sample.xts['2007-03/']) #

选出2007

年03

月的数据

Open     High      Low    Close

2007-03-01 50.81620 50.81620 50.56451 50.57075

2007-03-02 50.60980 50.72061 50.50808 50.61559

2007-03-03 50.73241 50.73241 50.40929 50.41033

2007-03-04 50.39273 50.40881 50.24922 50.32636

2007-03-05 50.26501 50.34050 50.26501 50.29567

2007-03-06 50.27464 50.32019 50.16380 50.16380

> head(sample.xts['2007-03-06/2007']) #

选出2007

年03

月06

日到2007

年的数据

Open     High      Low    Close

2007-03-06 50.27464 50.32019 50.16380 50.16380

2007-03-07 50.14458 50.20278 49.91381 49.91381

2007-03-08 49.93149 50.00364 49.84893 49.91839

2007-03-09 49.92377 49.92377 49.74242 49.80712

2007-03-10 49.79370 49.88984 49.70385 49.88698

2007-03-11 49.83062 49.88295 49.76031 49.78806

> sample.xts['2007-01-03']  #

选出2007

年01

月03

日的数据

Open     High     Low    Close

2007-01-03 50.2305 50.42188 50.2305 50.39767

2.用xts对象画图

用xts对象可以画曲线图（图2-8）和K线图（图2-9），下面是产生这两种图的代码，首先是曲线图：

> data(sample_matrix)

> plot(as.xts(sample_matrix))

Warning message:

In plot.xts(as.xts(sample_matrix)) :

only the univariate series will be plotted

警告信息提示，只有单变量序列将被绘制，即只画出第一列数据sample_matrix[,1]的曲线。

图2-8　曲线图

然后是K线图：

> plot(as.xts(sample_matrix), type='candles') #

画K

线图

图2-9　K线图

3.xts对象的类型转换

创建首尾时间函数firstof()和lastof()。

> firstof(2000) # 2000

年的第一天，时分秒显示省略

[1] "2000-01-01 CST"

> firstof(2005,01,01)

[1] "2005-01-01 CST"

> lastof(2007)  # 2007

年的最后一天，最后一秒

[1] "2007-12-31 23:59:59.99998 CST"

> lastof(2007,10)

[1] "2007-10-31 23:59:59.99998 CST"

创建首尾时间。

> .parseISO8601('2000') #

以ISO8601

格式，创建2000

年首尾时间

$first.time

[1] "2000-01-01 CST"

$last.time

[1] "2000-12-31 23:59:59.99998 CST"

> .parseISO8601('2000-05/2001-02')

#

以ISO8601

格式，创建2000

年05

月开始，2001

年02

月结束的时间

$first.time

[1] "2000-05-01 CST"

$last.time

[1] "2001-02-28 23:59:59.99998 CST"

> .parseISO8601('2000-01/02')

$first.time

[1] "2000-01-01 CST"

$last.time

[1] "2000-02-29 23:59:59.99998 CST"

> .parseISO8601('T08:30/T15:00')

$first.time

[1] "1970-01-01 08:30:00 CST"

$last.time

[1] "1970-12-31 15:00:59.99999 CST"

创建以时间类型为索引的xts对象。

> x <- timeBasedSeq('2010-01-01/2010-01-02 12:00') #

创建POSIXt

类型时间

> head(x)

[1] "2010-01-01 00:00:00 CST"

[2] "2010-01-01 00:01:00 CST"

[3] "2010-01-01 00:02:00 CST"

[4] "2010-01-01 00:03:00 CST"

[5] "2010-01-01 00:04:00 CST"

[6] "2010-01-01 00:05:00 CST"

> class(x)

[1] "POSIXt" "POSIXct"

> x <- xts(1:length(x), x) #

以时间为索引创建xts

对象

> head(x)

[,1]

2010-01-01 00:00:00    1

2010-01-01 00:01:00    2

2010-01-01 00:02:00    3

2010-01-01 00:03:00    4

2010-01-01 00:04:00    5

2010-01-01 00:05:00    6

> indexClass(x)

[1] "POSIXt" "POSIXct"

格式化索引时间的显示。

> indexFormat(x) <- "%Y-%b-%d %H:%M:%OS3" #

通过正则格式化索引的时间显示

> head(x)

[,1]

2010-

一月-01 00:00:00.000   1

2010-

一月-01 00:01:00.000   2

2010-

一月-01 00:02:00.000   3

2010-

一月-01 00:03:00.000   4

2010-

一月-01 00:04:00.000   5

2010-

一月-01 00:05:00.000   6

查看索引时间。

> .indexhour(head(x)) #

按小时取索引时间

[1] 0 0 0 0 0 0

> .indexmin(head(x))  #

按分钟取索引时间

[1] 0 1 2 3 4 5

4.xts对象的数据处理

数据对齐。

> x <- Sys.time() + 1:30

> align.time(x, 10)    #

整10

秒对齐，秒位为10

的整数倍

[1] "2013-11-18 15:42:30 CST" "2013-11-18 15:42:30 CST"

[3] "2013-11-18 15:42:30 CST" "2013-11-18 15:42:40 CST"

[5] "2013-11-18 15:42:40 CST" "2013-11-18 15:42:40 CST"

[7] "2013-11-18 15:42:40 CST" "2013-11-18 15:42:40 CST"

[29] "2013-11-18 15:43:00 CST" "2013-11-18 15:43:00 CST"

> align.time(x, 60)    #

整60

秒对齐，秒位为0

，分位为整数

[1] "2013-11-18 15:43:00 CST" "2013-11-18 15:43:00 CST"

[3] "2013-11-18 15:43:00 CST" "2013-11-18 15:43:00 CST"

[5] "2013-11-18 15:43:00 CST" "2013-11-18 15:43:00 CST"

[7] "2013-11-18 15:43:00 CST" "2013-11-18 15:43:00 CST"

[9] "2013-11-18 15:43:00 CST" "2013-11-18 15:43:00 CST"

[11] "2013-11-18 15:43:00 CST" "2013-11-18 15:43:00 CST"

按时间分割数据，并计算。

> xts.ts <- xts(rnorm(231),as.Date(13514:13744,origin="1970-01-01"))

> apply.monthly(xts.ts,mean) #

按月计算均值，以每月的最后一日显示

[,1]

2007-01-31  0.17699984

2007-02-28  0.30734220

2007-03-31 -0.08757189

2007-04-30  0.18734688

2007-05-31  0.04496954

2007-06-30  0.06884836

2007-07-31  0.25081814

2007-08-19 -0.28845938

> apply.monthly(xts.ts,function(x) var(x))

#

按月计算自定义函数(

方差)

，以每月的最后一日显示

[,1]

2007-01-31   0.9533217

2007-02-28   0.9158947

2007-03-31   1.2821450

2007-04-30   1.2805976

2007-05-31   0.9725438

2007-06-30   1.5228904

2007-07-31   0.8737030

2007-08-19   0.8490521

> apply.quarterly(xts.ts,mean)     #

按季计算均值，以每季的最后一日显示

[,1]

2007-03-31 0.12642053

2007-06-30 0.09977926

2007-08-19 0.04589268

> apply.yearly(xts.ts,mean) #

按年计算均值，以年季的最后一日显示

[,1]

2007-08-19 0.09849522

使用to.period()函数按间隔分割数据。

> data(sample_matrix)

> to.period(sample_matrix) #

默认按月分割矩阵数据

sample_matrix.Open sample_matrix.High sample_matrix.Low sample_matrix.Close

2007-01-31          50.03978           50.77336          49.76308            50.22578

2007-02-28          50.22448           51.32342          50.19101            50.77091

2007-03-31          50.81620           50.81620          48.23648            48.97490

2007-04-30          48.94407           50.33781          48.80962            49.33974

2007-05-31          49.34572           49.69097          47.51796            47.73780

2007-06-30          47.74432           47.94127          47.09144            47.76719

> class(to.period(sample_matrix))

[1] "matrix"

> samplexts <- as.xts(sample_matrix)    #

默认按月分割xts

类型数据

> to.period(samplexts)

samplexts.Open samplexts.High samplexts.Low samplexts.Close

2007-01-31       50.03978       50.77336      49.76308        50.22578

2007-02-28       50.22448       51.32342      50.19101        50.77091

2007-03-31       50.81620       50.81620      48.23648        48.97490

2007-04-30       48.94407       50.33781      48.80962        49.33974

2007-05-31       49.34572       49.69097      47.51796        47.73780

2007-06-30       47.74432       47.94127      47.09144        47.76719

> class(to.period(samplexts))

[1] "xts" "zoo"

使用endpoints()函数，按间隔分割索引数据。

> data(sample_matrix)

> endpoints(sample_matrix)  #

默认按月分割

[1]   0  30  58  89 119 150 180

> endpoints(sample_matrix, 'days',k=7) #

按每7

日分割

[1]   0   6  13  20  27  34  41  48  55  62  69  76  83  90  97  104  111  118  125

[20] 132 139 146 153 160 167 174 180

> endpoints(sample_matrix, 'weeks')    #

按周分割

[1]    0   7  14  21  28  35  42  49  56  63  70  77  84  9 1 98  105  112  119  126

[20] 133 140 147 154 161 168 175 180

> endpoints(sample_matrix, 'months') #

按月分割

[1]   0  30  58  89  119  150  180

使用merge()函数进行数据合并，按列合并。

> (x <- xts(4:10, Sys.Date()+4:10)) #

创建2

个xts

数据集

[,1]

2013-11-22    4

2013-11-23    5

2013-11-24    6

2013-11-25    7

2013-11-26    8

2013-11-27    9

2013-11-28   10

> (y <- xts(1:6, Sys.Date()+1:6))

[,1]

2013-11-19    1

2013-11-20    2

2013-11-21    3

2013-11-22    4

2013-11-23    5

2013-11-24    6

> merge(x,y)  #

按列合并数据，空项以NA

填空

x  y

2013-11-19  NA  1

2013-11-20  NA  2

2013-11-21  NA  3

2013-11-22   4  4

2013-11-23   5  5

2013-11-24   6  6

2013-11-25   7 NA

2013-11-26   8 NA

2013-11-27   9 NA

2013-11-28  10 NA

> merge(x,y, join='inner') #

按索引合并数据

x y

2013-11-22 4 4

2013-11-23 5 5

2013-11-24 6 6

> merge(x,y, join='left') #

以左侧为基础合并数据

x  y

2013-11-22  4  4

2013-11-23  5  5

2013-11-24  6  6

2013-11-25  7 NA

2013-11-26  8 NA

2013-11-27  9 NA

2013-11-28 10 NA

使用rbind()函数进行数据合并，按行合并。

> x <- xts(1:3, Sys.Date()+1:3)

> rbind(x,x)  #

按行合并数据

[,1]

2013-11-19    1

2013-11-19    1

2013-11-20    2

2013-11-20    2

2013-11-21    3

2013-11-21    3

使用split()函数进行数据切片，按行切片。

> data(sample_matrix)

> x <- as.xts(sample_matrix)

> split(x)[[1]]  #

默认按月进行切片，打印第一个月的数据

Open     High      Low    Close

2007-01-02 50.03978 50.11778 49.95041 50.11778

2007-01-03 50.23050 50.42188 50.23050 50.39767

2007-01-04 50.42096 50.42096 50.26414 50.33236

2007-01-05 50.37347 50.37347 50.22103 50.33459

2007-01-06 50.24433 50.24433 50.11121 50.18112

2007-01-07 50.13211 50.21561 49.99185 49.99185

2007-01-08 50.03555 50.10363 49.96971 49.98806

> split(x, f="weeks")[[1]] #

按周切片，打印前1

周数据

Open     High      Low    Close

2007-01-02 50.03978 50.11778 49.95041 50.11778

2007-01-03 50.23050 50.42188 50.23050 50.39767

2007-01-04 50.42096 50.42096 50.26414 50.33236

2007-01-05 50.37347 50.37347 50.22103 50.33459

2007-01-06 50.24433 50.24433 50.11121 50.18112

2007-01-07 50.13211 50.21561 49.99185 49.99185

2007-01-08 50.03555 50.10363 49.96971 49.98806

NA值处理。

> x <- xts(1:10, Sys.Date()+1:10)

> x[c(1,2,5,9,10)] <- NA

> x

[,1]

2013-11-19   NA

2013-11-20   NA

2013-11-21    3

2013-11-22    4

2013-11-23   NA

2013-11-24    6

2013-11-25    7

2013-11-26    8

2013-11-27   NA

2013-11-28   NA

> na.locf(x) #

取NA

的前一个，替换NA

值

[,1]

2013-11-19   NA

2013-11-20   NA

2013-11-21    3

2013-11-22    4

2013-11-23    4

2013-11-24    6

2013-11-25    7

2013-11-26    8

2013-11-27    8

2013-11-28    8

> na.locf(x, fromLast=TRUE) #

取NA

后一个，替换NA

值

[,1]

2013-11-19    3

2013-11-20    3

2013-11-21    3

2013-11-22    4

2013-11-23    6

2013-11-24    6

2013-11-25    7

2013-11-26    8

2013-11-27   NA

2013-11-28   NA

5.xts对象的数据统计计算

对xts对象可以进行各种数据统计计算，比如取开始时间和结束时间，计算时间区间，按期间计算统计指标。

（1）取xts对象的开始时间和结束时间，具体代码如下：

> xts.ts <- xts(rnorm(231),as.Date(13514:13744,origin="1970-01-01"))

> start(xts.ts) #

取开始时间

[1] "2007-01-01"

> end(xts.ts) #

取结束时间

[1] "2007-08-19"

> periodicity(xts.ts) #

以日为单位，打印开始和结束时间

Daily periodicity from 2007-01-01 to 2007-08-19

（2）计算时间区间函数，具体代码如下：

> data(sample_matrix)

> ndays(sample_matrix) #

计算数据有多少日

[1] 180

> nweeks(sample_matrix) #

计算数据有多少周

[1] 26

> nmonths(sample_matrix) #

计算数据有多少月

[1] 6

> nquarters(sample_matrix) #

计算数据有多少季

[1] 2

> nyears(sample_matrix) #

计算数据有多少年

[1] 1

（3）按期间计算统计指标，具体代码如下：

> zoo.data <- zoo(rnorm(31)+10,as.Date(13514:13744,origin="1970-01-01"))

> ep <- endpoints(zoo.data,'weeks') #

按周获得期间索引

> ep

[1]   0   7  14  21  28  35  42  49  56  63  70  77  84  91  98 105 112 119

[19] 126 133 140 147 154 161 168 175 182 189 196 203 210 217 224 231

> period.apply(zoo.data, INDEX=ep, FUN=function(x) mean(x))  #

计算周的均值

2007-01-07 2007-01-14 2007-01-21 2007-01-28 2007-02-04 2007-02-11 2007-02-18

10.200488   9.649387  10.304151   9.864847  10.382943   9.660175   9.857894

2007-02-25 2007-03-04 2007-03-11 2007-03-18 2007-03-25 2007-04-01 2007-04-08

10.495037   9.569531  10.292899   9.651616  10.089103   9.961048  10.304860

2007-04-15 2007-04-22 2007-04-29 2007-05-06 2007-05-13 2007-05-20 2007-05-27

9.658432   9.887531  10.608082   9.747787  10.052955   9.625730  10.430030

2007-06-03 2007-06-10 2007-06-17 2007-06-24 2007-07-01 2007-07-08 2007-07-15

9.814703  10.224869   9.509881  10.187905  10.229310  10.261725   9.855776

2007-07-22 2007-07-29 2007-08-05 2007-08-12 2007-08-19

9.445072  10.482020   9.844531  10.200488   9.649387

> head(period.max(zoo.data, INDEX=ep)) #

计算周的最大值

[,1]

2007-01-07 12.05912

2007-01-14 10.79286

2007-01-21 11.60658

2007-01-28 11.63455

2007-02-04 12.05912

2007-02-11 10.67887

> head(period.min(zoo.data, INDEX=ep)) #

计算周的最小值

[,1]

2007-01-07 8.874509

2007-01-14 8.534655

2007-01-21 9.069773

2007-01-28 8.461555

2007-02-04 9.421085

2007-02-11 8.534655

> head(period.prod(zoo.data, INDEX=ep)) #

计算周的一个指数值

[,1]

2007-01-07 11140398

2007-01-14 7582350

2007-01-21 11930334

2007-01-28 8658933

2007-02-04 12702505

2007-02-11 7702767

6.xts对象的时间序列操作

检查时间类型。

> class(Sys.time());timeBased(Sys.time()) # Sys.time()

是时间类型POSIXct

[1] "POSIXct" "POSIXt"

[1] TRUE

> class(Sys.Date());timeBased(Sys.Date()) # Sys.Date()

是时间类型Date

[1] "Date"

[1] TRUE

> class(20070101);timeBased(20070101) # 20070101

不是时间类型

[1] "numeric"

[1] FALSE

使用timeBasedSeq()函数创建时间序列。

> timeBasedSeq('1999/2008') #

按年

[1] "1999-01-01" "2000-01-01" "2001-01-01" "2002-01-01" "2003-01-01"

[6] "2004-01-01" "2005-01-01" "2006-01-01" "2007-01-01" "2008-01-01"

> head(timeBasedSeq('199901/2008')) #

按月

[1] "

十二月 1998" "

一月 1999" "

二月 1999" "

三月 1999" "

四月 1999"

[6] "

五月 1999"

> head(timeBasedSeq('199901/2008/d'),40) #

按日

[1]  "

十二月1998" "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999"

[6]  "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999"

[11] "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999"

[16] "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999"

[21] "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999"

[26] "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999" "

一月 1999"

[31] "

一月 1999" "

一月 1999" "

二月 1999" "

二月 1999" "

二月 1999"

[36] "

二月 1999" "

二月 1999" "

二月 1999" "

二月 1999" "

二月 1999"

> timeBasedSeq('20080101 0830',length=100) #

按数量创建，100

分钟的数据集

$from

[1] "2008-01-01 08:30:00 CST"

$to

[1] NA

$by

[1] "mins"

$length.out

[1] 100

按索引取数据first()和last()。

> x <- xts(1:100, Sys.Date()+1:100)

> head(x)

[,1]

2013-11-19    1

2013-11-20    2

2013-11-21    3

2013-11-22    4

2013-11-23    5

2013-11-24    6

> first(x, 10) #

取前10

条数据

[,1]

2013-11-19    1

2013-11-20    2

2013-11-21    3

2013-11-22    4

2013-11-23    5

2013-11-24    6

2013-11-25    7

2013-11-26    8

2013-11-27    9

2013-11-28   10

> first(x, '1 day') #

取1

天的数据

[,1]

2013-11-19    1

> last(x, '1 weeks') #

取最后1

周的数据

[,1]

2014-02-24   98

2014-02-25   99

2014-02-26  100

计算步长lag()和差分diff()。

> x <- xts(1:5, Sys.Date()+1:5)

> lag(x) #

以1

为步长

[,1]

2013-11-19  NA

2013-11-20   1

2013-11-21   2

2013-11-22   3

2013-11-23   4

> lag(x, k=-1, na.pad=FALSE) #

以-1

为步长，并去掉NA

值

[,1]

2013-11-19   2

2013-11-20   3

2013-11-21   4

2013-11-22   5

> diff(x)  # 1

阶差分

[,1]

2013-11-19  NA

2013-11-20   1

2013-11-21   1

2013-11-22   1

2013-11-23   1

> diff(x,lag=2) # 2

阶差分

[,1]

2013-11-19  NA

2013-11-20  NA

2013-11-21   2

2013-11-22   2

2013-11-23   2

使用isOrdered()函数，检查向量是否排序好的。

> isOrdered(1:10, increasing=TRUE)

[1] TRUE

> isOrdered(1:10, increasing=FALSE)

[1] FALSE

> isOrdered(c(1,1:10), increasing=TRUE)

[1] FALSE

> isOrdered(c(1,1:10), increasing=TRUE, strictly=FALSE)

[1] TRUE

使用make.index.unique()函数，强制唯一索引。

> x <- xts(1:5, as.POSIXct("2011-01-21") + c(1,1,1,2,3)/1e3)

> x

[,1]

2011-01-21 00:00:00.000    1

2011-01-21 00:00:00.000    2

2011-01-21 00:00:00.000    3

2011-01-21 00:00:00.002    4

2011-01-21 00:00:00.003    5

> make.index.unique(x) #

增加毫秒级精度，保证索引的唯一性

[,1]

2011-01-21 00:00:00.000999    1

2011-01-21 00:00:00.001000    2

2011-01-21 00:00:00.001001    3

2011-01-21 00:00:00.002000    4

2011-01-21 00:00:00.003000    5

查询xts对象时区。

> x <- xts(1:10, Sys.Date()+1:10)

> indexTZ(x) #

时区查询

[1] "UTC"

> tzone(x)

[1] "UTC"

> str(x)

An 'xts' object on 2013-11-19/2013-11-28 containing:

Data: int [1:10, 1] 1 2 3 4 5 6 7 8 9 10

Indexed by objects of class: [Date] TZ: UTC

xts Attributes:

NULL

xts给了zoo类型时间序列更多的API支持，这样我们就有了更方便的工具，可以做各种时间序列的转换和变形了。

2.3　时间序列可视化plot.xts

问题

如何把时间序列可视化？

引言

r-bloggers的一篇博文：plot.xts is wonderful!（http://www.r-bloggers.com/plot-xts-is-wonderful/），让我有动力继续发现xts的强大。xts扩展了zoo的基础数据结构，并提供了更丰富的功能函数。xtsExtra补充库从可视化的角度出发，提供了一个简单而效果非凡的作图函数plot.xts。本节将用plot.xts来演示xts对象的时间序列可视化！

2.3.1　xtsExtra介绍

xtsExtra是xts包的功能补充包，该软件包在Google Summer of Code 2012发布，最终将合并到xts包，不过在笔者写这本书的时候还没有合并。xtsExtra提供的主要功能就是plot.xts()函数。xts::plot.xts()函数与xtsExtra::plot.xts()函数还是有差别的，下面我们就详细介绍其中的差别！

2.3.2　xtsExtra安装

本节使用的系统环境是：

·Win764bit

·R:3.0.1x86_64-w64-mingw32/x64b4bit

注　xtsExtra同时支持Windows 7环境和Linux环境。

由于xtsExtra没有发布到CRAN，我们要从R-Forge下载。

~ R #

启动R

程序

> install.packages("xtsExtra", repos="http://R-Forge.R-project.org")

#

从R-Forge

下载xtsExtra

包

> library(xtsExtra) #

加载xtsExtra

包

xtsExtra::plot.xts()函数覆盖了xts::plot.xts()函数。

2.3.3　xtsExtra包的使用

plot.xts()函数的参数列表如下：

> names(formals(plot.xts))

[1] "x"            "y"            "screens"            "layout.screens"      "..."

[6] "yax.loc"      "auto.grid"    "major.ticks"        "minor.ticks"         "major.format"

[11] "bar.col.up"   "bar.col.dn"   "candle.col"         "xy.labels"           "xy.lines"

[16] "ylim"         "panel"        "auto.legend"        "legend.names"        "legend.loc"

[21] "legend.pars"  "events"       "blocks"             "nc"                  "nr"

下面画一个简单的时间序列图，如图2-10所示。

图2-10　时间序列

> data(sample_matrix)

> sample_xts <- as.xts(sample_matrix)

> plot(sample_xts[,1])

> class(sample_xts[,1])

[1] "xts" "zoo"

从图2-10似乎看不出xtsExtra::plot.xts()函数与xts::plot.xts()函数的不同效果。接下来，我们画点稍微复杂的图形。

1.画K线图

下面就来画K线图，默认红白配色，如图2-11所示。

> plot(sample_xts[1:30, ], type = "candles")

图2-11　K线图

画K线图：自定义颜色，如图2-12所示。

> plot(sample_xts[1:30, ], type = "candles", bar.col.up = "blue", bar.col.dn =

"violet", candle.col = "green4")

2.对panel配置

画基本面板，如图2-13所示。

> plot(sample_xts[,1:2])

图2-12　自定义颜色的K线图

图2-13　基本面板

画多行面板，如图2-14所示。

> plot(sample_xts[,rep(1:4, each = 3)])

图2-14　多行面板

画自由组合面板，如图2-15所示。

> plot(sample_xts[,1:4], layout.screens = matrix(c(1,1,1,1,2,3,4,4),ncol = 2,

byrow = TRUE))

图2-15　自由组合面板

通过画K线图和面板，就能发现plot.xts()函数提供很多种的画图参数设置，让我们画时间序列图可以有更丰富的可视化表示表现形式。

3.对screens配置

画双屏幕显示，每屏幕2条线，如图2-16所示。

> plot(sample_xts, screens = 1:2)

图2-16　双屏显示

画双屏幕显示，指定曲线出现的屏幕和颜色，如图2-17所示。

> plot(sample_xts, screens = c(1,2,1,2), col = c(1,3,2,2))

图2-17　双屏显示且自定义颜色

画双屏幕显示，指定不同的坐标系，如图2-18所示。

> plot(10^sample_xts, screens = 1:2, log= c("","y"))

图2-18　双屏显示，并设置不同坐标系

画双屏幕显示，指定不同的输出图形，如图2-19所示。

> plot(sample_xts[1:75,1:2] - 50.5, type = c("l","h"), lwd = c(1,2))

图2-19　双屏显示且设置不同的输出图形

画多屏幕，并分组显示，如图2-20所示。

> plot(sample_xts[,c(1:4, 3:4)], layout = matrix(c(1,1,1,1,2,2,3,4,5,6), ncol = 2,

byrow = TRUE), yax.loc = "left")

图2-20　多屏显示，并多屏分组

4.对events配置

画基本事件分割线，如图2-21所示。

> plot(sample_xts[,1], events = list(time = c("2007-03-15","2007-05-01"), label

= "bad days"), blocks = list(start.time = c("2007-03-05", "2007-04-15"), end.

time = c("2007-03-20","2007-05-30"), col = c("lightblue1", "lightgreen")))

图2-21　事件分割线

5.双坐标的时间序列

画双坐标视图，如图2-22所示。

> plot(sample_xts[,1],sample_xts[,2])

图2-22　双坐标视图

画双坐标梯度视图，如图2-23所示。

> cr <- colorRampPalette(c("#00FF00","#FF0000"))

> plot(sample_xts[,1],sample_xts[,2], xy.labels = FALSE, xy.lines = TRUE, col =

cr(NROW(sample_xts)), type = "l")

图2-23　双坐标梯度视图

6.对xts类型转换作图

以ts数据类型作图，如图2-24所示。

> tser <- ts(cumsum(rnorm(50, 0.05, 0.15)), start = 2007, frequency = 12)

> class(tser)

[1] "ts"

> plot(tser)

图2-24　ts数据类型作图

以xts类型作图，自动增加了背景坐标线，如图2-25所示。

> plot.xts(tser)

图2-25　xts类型作图

7.用barplot作图

barplot()函数作条形图，如图2-26所示。

> x <- xts(matrix(abs(rnorm(72)), ncol = 6), Sys.Date() + 1:12)

> colnames(x) <- LETTERS[1:6]

> barplot(x)

图2-26　barplot图

我们看到xtsExtra::plot.xts()函数提供了强大的作图功能，很容易做出包含丰富元素的时间序列！

第3章　R性能监控包

本章主要介绍了R语言性能相关的3个工具包，帮助读者找到程序中性能的瓶颈。

3.1　R语言本地缓存工具memoise

问题

如何提高R程序性能？

引言

缓存技术在计算机系统中运用得非常广泛，对于高并发访问的应用来说，缓存技术是性价比最高的性能提升方案，特别是对于重复性计算，缓存能为我们节省大量的CPU时间，可能达到99%。R语言以统计计算著名，但其中很多算法包都是在进行大量重复的计算。优化正在进行，改变已经开始。以Hadley Wickham为代表的R语言领军人物，正在让R快起来！

3.1.1　memoise介绍

memoise是一个非常简单的缓存包，以本地缓存为基础，减少单机的重复性计算，从而间接地提升了单机的CPU性能。当用相同的参数对同一个函数执行第二次计算的时候，如果可以直接用第一次计算过的结果作为第二次计算的结果，而不是再重算一遍，那么我们就节省了CPU时间。

memoise包的API非常简单，只有2个函数。

·memoize:定义缓存函数，把一个函数计算的结果加载到本地缓存。

·forget:重置缓存函数，把一个函数的计算结果从本地缓存中删除。

3.1.2　memoise安装

本节使用的系统环境是：

·Win764bit

·R:3.0.1x86_64-w64-mingw32/x64b4bit

注　memoise安装同时支持Windows 7环境和Linux环境。

memoise安装的过程如下：

~ R #

启动R

程序

> install.packages("memoise") #

安装memoise

包

> library(memoise) # memoise

加载

3.1.3　memoise使用

下面我们进行缓存实验，假设一个函数运行时CPU耗时1秒。

> fun <- memoise(function(x) { Sys.sleep(1); runif(1) })#

定义缓存函数，运行时停1

秒

> system.time(print(fun())) #

第一次执行fun

函数，等待1

秒完成

[1] 0.05983416

用户

系统

流逝

0    0    1

> system.time(print(fun())) #

第二次执行fun

函数，从缓存中获得结果，瞬时完成

[1] 0.05983416

用户

系统

流逝

0    0    0

> forget(fun) #

重置缓存函数，清空函数的缓存数据

[1] TRUE

> system.time(print(fun())) #

第三次执行fun

函数，等待1

秒完成

[1] 0.6001663

用户

系统

流逝

0    0    1

> system.time(print(fun())) #

第四次执行fun

函数，从缓存中获得结果，瞬时完成

[1] 0.6001663

用户

系统

流逝

0    0    0

对上面执行过程的具体描述如下：

（1）通过memoise()函数，创建缓存函数fun()。

（2）第一次执行fun()函数，等待1秒。

（3）第二次执行fun()函数，无等待，直接从缓存中获得结果。

（4）通过forget()函数，清空缓存中fun()函数的结果。

（5）第三次执行fun()函数，由于fun()函数的结果被清空，所以重新执行fun()函数，等待1秒。

（6）第四次执行fun()函数，无等待，直接从缓存中返回结果。

3.1.4　memoise()函数源代码分析

memoise项目是一个典型的以面向对象编程方式实现的R语言项目，R语言的面向对象编程将在本书姊妹篇《R的极客理想—高级开发篇》一书中给大家详细介绍。memoise项目的源代码在Gihub上可以找到，项目地址是https://github.com/hadley/memoise。

1.memoise()函数源代码

接下来，我们解读memoise()函数的源代码，memoise()函数主要做了3件事。

（1）new_cache()函数创建新的缓存空间，给f()函数。

（2）生成f()函数的hash值，作为key。

（3）返回缓存后的f函数引用。

memoise <- memoize <- function(f) { #

定义同名函数memoise

和memoize

cache <- new_cache() #

创建新的缓存空间

memo_f <- function(...) { #

生成hash

值

hash <- digest(list(...))

if (cache$has_key(hash)) {

cache$get(hash)

} else {

res <- f(...)

cache$set(hash, res)

res

}

}

attr(memo_f, "memoised") <- TRUE

return(memo_f)

}

2.forget()函数源代码

forget()函数主要做了2件事。

（1）检查环境中是否缓存了f()函数。

（2）如果有f()函数的缓存，则清空f()函数的缓存值。

forget <- function(f) {

if (!is.function(f)) return(FALSE) #

判断函数是否在缓存中

env <- environment(f)

if (!exists("cache", env, inherits = FALSE)) return(FALSE)

cache <- get("cache", env) #

清空函数的缓存值

cache$reset()

TRUE

}

3.私有函数：new_cache()函数源代码

new_cache()函数主要做了3件事。

（1）在new_cache()函数里，定义cache对象，保存在env的环境中。

（2）通过new_cache()函数，构造list类型对象，包括reset,set,get,has_key,keys方法。

（3）通过list对象，对cache对象进行访问操作。

new_cache <- function() {

cache <- NULL #

定义cache

对象

cache_reset <- function() { #

定义清空缓存的函数

cache <<- new.env(TRUE, emptyenv())

}

cache_set <- function(key, value) { #

定义设置缓存的函数

assign(key, value, env = cache)

}

cache_get <- function(key) { #

定义取得缓存的函数

get(key, env = cache, inherits = FALSE)

}

cache_has_key <- function(key) { #

检查缓存是否存在

exists(key, env = cache, inherits = FALSE)

}

cache_reset() #

运行清空缓存

list( #

返回缓存对象

reset = cache_reset,

set = cache_set,

get = cache_get,

has_key = cache_has_key,

keys = function() ls(cache)

)

}

从源代码中，我们不仅能发现memoise包的设计思想，还能感受到作者对R语言深刻的理解。memoise包的代码简洁、高效，值得大家用心学习。

3.2　R语言性能监控工具Rprof

问题

如何找到性能瓶颈？

引言

随着R语言使用越来越深入，R语言的计算性能问题越来越突显。如何能清楚地了解一个算法对CPU的耗时，将成为性能优化的关键因素。值得庆幸的是，R的基础库提供了性能监控的函数Rprof()。

3.2.1　Rprof()函数介绍

Rprof()函数是R语言核心包自带的一个性能数据日志函数，可以打印出函数的调用关系和CPU耗时的数据。然后通过summaryRprof()函数分析Rprof()生成的日志数据，获得性能报告。最后使用profr库的plot()函数，对报告进行可视化。

3.2.2　Rprof()函数的定义

本节使用的系统环境是：

·Win764bit

·R:3.0.1x86_64-w64-mingw32/x64b4bit

注　Rprof和profr同时支持Windows 7环境和Linux环境。

Rprof()函数在基础包utils中定义，不用安装，直接可以使用。查看Rprof()函数的定义。

~ R #

启动R

程序

> Rprof #

查看Rprof()

函数定义

function (filename = "Rprof.out", append = FALSE, interval = 0.02,

memory.profiling = FALSE, gc.profiling = FALSE, line.profiling = FALSE,

numfiles = 100L, bufsize = 10000L)

{

if (is.null(filename))

filename <- ""

invisible(.External(C_Rprof, filename, append, interval,

memory.profiling, gc.profiling, line.profiling, numfiles,

bufsize))

}

<bytecode: 0x000000000d8efda8>

Rprof()函数用来生成记录性能指标的日志文件，通常我们只需要指定输出文件（filename）就可以了。

3.2.3　Rprof()函数使用:股票数据分析案例

以本书6.6节股票数据作为测试数据集，数据文件为000000_0.txt，1.38MB，在随书的代码文件中可以找到。本节只是性能测试，关于数据的业务含义，请参看6.6节内容。

> bidpx1<-read.csv(file="000000_0.txt",header=FALSE)

> names(bidpx1)<-c("tradedate","tradetime","securityid","bidpx1","bidsize1",

"off erpx1", "offersize1")

> bidpx1$securityid<-as.factor(bidpx1$securityid)

> head(bidpx1)

tradedate tradetime securityid bidpx1 bidsize1 offerpx1 offersize1

1 20130724 145004 131810 2.620    6960 2.630  13000

2 20130724 145101 131810 2.860   13880 2.890   6270

3 20130724 145128 131810 2.850  327400 2.851   1500

4 20130724 145143 131810 2.603   44630 2.800  10650

5 20130724 144831 131810 2.890   11400 3.000  77990

6 20130724 145222 131810 2.600 1071370 2.601  35750

> object.size(bidpx1)

1299920 bytes

字段的具体解释：tradedate是交易日期，tradetime是交易时间，securityid是股票ID，bidpx1是买一价，bidsize1是买一量，offerpx1是卖一价，offersize1是卖一量。

计算任务：以securityid分组，计算每小时的买一价的平均值和买一量的总量。

> library(plyr) #

加载plyr

包，用于数据处理

> fun1<-function(){ #

数据处理封装到fun1()

函数

+     datehour<-paste(bidpx1$tradedate,substr(bidpx1$tradetime,1,2),sep="")

+     df<-cbind(datehour,bidpx1[,3:5])

+     ddply(bidpx1,.(securityid,datehour),summarize,price=mean(bidpx1),size=sum(bidsize1))

+ }

> head(fun1())

securityid     datehour     price       size

1     131810   2013072210  3.445549  189670150

2     131810   2013072211  3.437179  131948670

3     131810   2013072212  3.421000        920

4     131810   2013072213  3.509442  299554430

5     131810   2013072214  3.578667  195130420

6     131810   2013072215  1.833000     718940

以system.time()函数查看fun1()函数运行时间，运行2次，系统耗时近似，确定第二次没有缓存。

> system.time(fun1())

用户

系统

流逝

0.08 0.00 0.07

> system.time(fun1())

用户

系统

流逝

0.06 0.00 0.06

用Rprof()函数记录性能指标数据，并输出到文件。

> file<-"fun1_rprof.out" #

定义性能日志的输出文件位置

> Rprof(file)   #

开始性能监控

> fun1()    #

执行计算函数

> Rprof(NULL)     #

停止性能监控，并输出到文件

查看生成的性能指标文件：fun1_rprof.out。

~ vi fun1_rprof.out #

用vi

编辑器打开文件

sample.interval=20000

"substr" "paste" "fun1"

"paste" "fun1"

"structure" "splitter_d" "ddply" "fun1"

".fun" "" ".Call" "loop_apply" "llply" "ldply" "ddply" "fun1"

".fun" "" ".Call" "loop_apply" "llply" "ldply" "ddply" "fun1"

".fun" "" ".Call" "loop_apply" "llply" "ldply" "ddply" "fun1"

"[[" "rbind.fill" "list_to_dataframe" "ldply" "ddply" "fun1"

我们其实看不明白这个日志，不知道它到底有什么意义。所以，还要通过summaryRprof()函数来解析这个日志。下面通过summaryRprof()函数查看统计报告。

> summaryRprof(file) #

读入文件

$by.self

self.time self.pct total.time total.pct

".fun"           0.06    42.86       0.06     42.86

"paste"          0.02    14.29       0.04     28.57

"[["             0.02    14.29       0.02     14.29

"structure"      0.02    14.29       0.02     14.29

"substr"         0.02    14.29       0.02     14.29

$by.total

total.time total.pct self.time self.pct

"fun1"                    0.14    100.00      0.00     0.00

"ddply"                   0.10     71.43      0.00     0.00

"ldply"                   0.08     57.14      0.00     0.00

".fun"                    0.06     42.86      0.06    42.86

".Call"                   0.06     42.86      0.00     0.00

""                        0.06     42.86      0.00     0.00

"llply"                   0.06     42.86      0.00     0.00

"loop_apply"              0.06     42.86      0.00     0.00

"paste"                   0.04     28.57      0.02    14.29

"[["                      0.02     14.29      0.02    14.29

"structure"               0.02     14.29      0.02    14.29

"substr"                  0.02     14.29      0.02    14.29

"list_to_dataframe"       0.02     14.29      0.00     0.00

"rbind.fill"              0.02     14.29      0.00     0.00

"splitter_d"              0.02     14.29      0.00     0.00

$sample.interval

[1] 0.02

$sampling.time

[1] 0.14

数据解释：

·$by.self是当前函数的耗时情况，其中self.time为实际运行时间，total.time为累计运行时间。

·$by.total是整体函数调用的耗时情况，其中self.time为实际运行时间，total.time为累计运行时间。

从$by.self观察，我们发现时间主要花在.fun函数上。

·.fun:实际运行时间0.06，占当前函数时间的42.86%。

·paste：实际运行时间0.02，占当前函数时间的14.29%。

·"[["：实际运行时间0.02，占当前函数时间的13.29%。

·"structure"：实际运行时间0.02，占当前函数时间的14.29%。

·"substr"：实际运行时间0.02，占当前函数时间的14.29%。

对应在$by.total中，从底到上（1～4），按执行顺序排序。

·4fun1:累计运行时间0.14，占总累计运行时间的100%，实际运行时间0.00。

·3.fun:累计运行时间0.06，占总累计运行时间的42.86%，实际运行时间0.06。

·2paste：累计运行时间0.04，占总累计运行时间的28.57%，实际运行时间0.02。

·1splitter_d：累计运行时间0.02，占总累计运行时间的14.297%，实际运行时间0.00。

这样我们就知道了每个调用函数的CPU时间，进行性能优化就从最耗时的函数开始。

3.2.4　Rprof()函数使用:数据下载案例

我们先安装stockPortfolio包，需要通过stockPortfolio下载股票行情的数据，然后使用Rprof()函数监控下载过程的程序耗时情况。

> install.packages("stockPortfolio")  #

安装stockPortfolio

包

> library(stockPortfolio)   #

加载stockPortfolio

包

> fileName <- "Rprof2.log"

> Rprof(fileName)   #

开始性能监控

> gr <- getReturns(c("GOOG", "MSFT", "IBM"), freq="week")

#

下载Google,

微软,IBM

的股票数据

> gr

Time Scale: week

Average Return

GOOG         MSFT         IBM

0.004871890  0.001270758 0.001851121

> Rprof(NULL) #

完成性能监控

> summaryRprof(fileName)$by.total[1:8,] #

查看性能报告

total.time total.pct self.time self.pct

"getReturns"               6.76    100.00      0.00     0.00

"read.delim"               6.66     98.52      0.00     0.00

"read.table"               6.66     98.52      0.00     0.00

"scan"                     4.64     68.64      4.64    68.64

"file"                     2.02     29.88      2.02    29.88

"as.Date"                  0.08      1.18      0.02     0.30

"strptime"                 0.06      0.89      0.06     0.89

"as.Date.character"        0.06      0.89      0.00     0.00

从打印出的$by.total前8条最耗时的记录来看，实际运行时间(self.time)主要花在file(2.02)和scan(4.64)。

3.2.5　用profr包可视化性能指标

profr包提供了对Rprof()函数输出的性能报告可视化展示功能，比summaryRprof()的纯文字的报告，要更友好，有利于阅读。首先，我们要安装profr包。

> install.packages("profr") #

安装profr

> library(profr) #

加载profr

包

> library(ggplot2) #

用ggplot2

包来画图，

加载ggplot2

包

数据可视化的第一个例子，即股票数据分析案例。下面的代码段产生图3-1和图3-2。

> file<-"fun1_rprof.out"> plot(parse_rprof(file)) # plot

画图

> ggplot(parse_rprof(file)) # ggplot2

画图

图3-1　第一个例子中用plot画的图

数据可视化的第二个例子，即数据下载案例。下面的代码段生成图3-3和图3-4。

> fileName <- "Rprof2.log"

> plot(parse_rprof(fileName))

> ggplot(parse_rprof(fileName))

图3-2　第一个例子中用ggplot2画的图

图3-3　第二个例子中用plot画的图

图3-4　第二个例子中用ggplot2画的图

3.2.6　Rprof的命令行使用

Rprof的命令行方法，用来方便地查看日志文件。

1.查看Rprof命令行帮助

~ D:\workspace\R\preforemence\Rprof>R CMD Rprof --help

Usage: R CMD Rprof [options] [file]

Post-process profiling information in file generated by Rprof().

Options:

-h, --help        print short help message and exit #

打印帮助信息

-v, --version     print version info and exit #

打印版本信息

--lines           print line information #

打印显示多行

--total           print only by total #

只显示总数

--self            print only by self #

只显示私有的

--linesonly       print only by line (implies --lines) #

只显示单行(

配合–lines

使用)

--min%total=      minimum % to print for 'by total' #

显示total

的不低于X

的百分比

--min%self=       minimum % to print for 'by self' #

显示self

的不低于X

的百分比

2.Rprof命令行使用

显示完整的报告：

~ D:\workspace\R\preforemence\Rprof>R CMD Rprof fun1_rprof.out

Each sample represents 0.02 seconds.

Total run time: 0.14 seconds.

Total seconds: time spent in function and callees.

Self seconds: time spent in function alone.

%       total       %        self

total     seconds     self    seconds    name

100.0       0.14       0.0      0.00     "fun1"

71.4       0.10       0.0      0.00     "ddply"

57.1       0.08       0.0      0.00     "ldply"

42.9       0.06      42.9      0.06     ".fun"

42.9       0.06       0.0      0.00     ".Call"

42.9       0.06       0.0      0.00     ""

42.9       0.06       0.0      0.00     "llply"

42.9       0.06       0.0      0.00     "loop_apply"

28.6       0.04      14.3      0.02     "paste"

14.3       0.02      14.3      0.02     "[["

14.3       0.02      14.3      0.02     "structure"

14.3       0.02      14.3      0.02     "substr"

14.3       0.02       0.0      0.00     "list_to_dataframe"

14.3       0.02       0.0      0.00     "rbind.fill"

14.3       0.02       0.0      0.00     "splitter_d"

%         self       %      total

self     seconds    total   seconds     name

42.9       0.06      42.9      0.06      ".fun"

14.3       0.02      28.6      0.04      "paste"

14.3       0.02      14.3      0.02      "[["

14.3       0.02      14.3      0.02      "structure"

14.3       0.02      14.3      0.02      "substr"

只显示total指标，占用时间不低于50%的部分。

~ D:\workspace\R\preforemence\Rprof>R CMD Rprof --total --min%total=50 fun1_rprof.out

Each sample represents 0.02 seconds.

Total run time: 0.14 seconds.

Total seconds: time spent in function and callees.

Self seconds: time spent in function alone.

%       total       %        self

total    seconds     self    seconds    name

100.0      0.14       0.0      0.00     "fun1"

71.4      0.10       0.0      0.00     "ddply"

57.1      0.08       0.0      0.00     "ldply"

我们可以通过Rprof进行性能调优，提高代码的性能，让R语言的计算不再是瓶颈。

3.3　R语言性能可视化工具lineprof

问题

有没有一个可视化的性能监控工具？

引言

数据可视化越来越受到人们的追捧，图形比文字更有表达力，基于HTML的可交互的图形比静态的PNG图片更让人惊喜。R语言已经为数据可视化做了充分的准备，比如，图形可视化包ggplot2，世界地图可视化包ggmap，股票可视化包quantmod，基于HTML的可交互的可视化包googleVis等，简简单单的几行代码，就可以让数据变成图片，再让图片变成会动的图片。本节以“性能报告”为切入点，讲述R语言可视化包lineprof。

3.3.1　lineprof介绍

lineprof是一个数据可视化的项目，目标是更友好地可视化性能监控效果。3.2节通过profr库，可以把性能数据以图片的形式输出，但仅仅是一张静态图片。而lineprof可以做得更好，生成基于shiny的可交互的网页，让用户自己动手发现问题。

lineprof项目也是Hadley Wickham的作品。目前项目只在Github上面发布，项目地址是https://github.com/hadley/lineprof。lineprof库的API主要有以下两类函数。

（1）功能函数：

·focus:设置显示高度zoom。

·auto_focus:自动设置显示高度zoom。

·lineprof:记录CPU和内存的占用。

·shine:用shiny输出。

（2）内部函数：辅助功能函数

·align:源代码对齐。

·find_ex:用于加载demo。

·line_profile:格式化性能监控数据的输出（Rprof）。

·parse_prof:格式化输出。

·reduce_depth:设置输出的深度。

3.3.2　lineprof安装

本节使用的系统环境是：

·Linux:Ubuntu Server 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

·IP:192.168.1.201

注　lineprof只支持Linux环境。

由于项目lineprof还没有发布到CRAN，在lineprof包安装时，仅支持从Github安装。所以，需要通过devtools包来安装Github上面发布的R语言项目。

~ R #

启动R

程序

> library(devtools) #

加载devtools

> install_github("lineprof") #

通过devtools

工具，安装linepro

> install_github("shiny-slickgrid", "wch") #

安装制定的shiny

控制台

> library(lineprof) #

加载lineprof

3.3.3　lineprof使用

我们使用官方提供的例子，介绍lineprof包使用。

> source(find_ex("read-delim.r")) #

加载脚本文件read-delim.r

> wine <- find_ex("wine.csv") #

加载测试数据集

> x <- lineprof(read_delim(wine, sep = ","), torture = TRUE) #

执行read_delim

算法，

并通过lineprof

记录性能指标Zooming to read-delim.r (97% of total time)

在例子的资源中，read-delim.r是目标函数的脚本文件，wine.csv是测试数据集，x:lineprof是生成的数据报告。

查看文件:read-delim.r。

function(file, header = TRUE, sep = ",", stringsAsFactors = TRUE) {

# Determine number of fields by reading first line

first <- scan(file, what = character(1), nlines = 1, sep = sep, quiet = TRUE)

p <- length(first)

# Load all fields

all <- scan(file, what = as.list(rep("character", p)), sep = sep,

skip = if (header) 1 else 0, quiet = TRUE)

# Convert from strings to appropriate types

all[] <- lapply(all, type.convert, as.is = !stringsAsFactors)

# Set column names

if (header) {

names(all) <- first

rm(first)

} else {

names(all) <- paste0("V", seq_along(all))

}

# Convert list into data frame

class(all) <- "data.frame"

attr(all, "row.names") <- c(NA_integer_, -length(all[[1]]))

all

}

加载文件wine.csv。

> df<-read.csv(file=wine)

> object.size(df) #

数据集占内存大小

20440 bytes

> head(df

，3) #

显示前3

行数据

type    alcohol     malic     ash       alcalinity  magnesium       phenols    flavanoids

nonflavanoids       proanthocyanins     color       hue dilution    proline

1  A       14.23       1.71      2.43      15.6        127             2.80       3.06

0.28    2.29        5.64      1.04      3.92        1065

2  A       13.20       1.78      2.14      11.2        100             2.65       2.76

0.26    1.28        4.38      1.05      3.40        1050

3  A       13.16       2.36      2.67      18.6        101             2.80       3.24

0.30    2.81        5.68      1.03      3.17        1185

x对象:lineprof生成的数据报告。

> x #

记录性能指标的对象

Reducing depth to 2 (from 8)

Common path:

time alloc release dups ref src

1  0.002 0.001   0.000    0 #3  read_delim

2  0.049 0.009   0.003   11 #3  read_delim/scan

3  0.026 0.001   0.008    0 #4  read_delim

4  0.379 0.072   0.006   14 #7  read_delim/scan

5  0.003 0.000   0.000    0 #11 read_delim

6  0.106 0.015   0.030    3 #11 read_delim/lapply

7  0.008 0.004   0.000    3 #11 read_delim

8  0.210 0.028   0.077   36 #16 read_delim/rm

9  0.004 0.001   0.000    1 #22 read_delim

10 0.035 0.005   0.004    8 #23 read_delim/[[

11 0.002 0.000   0.000    1 #23 read_delim/length

12 0.001 0.000   0.000    1 #23 read_delim/c

13 0.006 0.004   0.000    1 #23 read_delim

14 0.001 0.000   0.000    0 #23 read_delim/attr<-

用shinySlickgrid库，把性能指标数据可视化，并以网页形式输出。

> library(shinySlickgrid) #

加载shinySlickgrid

库

> shine(x) #

启动shiny

程序

Loading required package: shiny

Shiny URLs starting with /lineprof will mapped to /home/conan/R/x86_64-pc-linuxgnu-

library/3.0/lineprof/www

Shiny URLs starting with /slickgrid will mapped to /home/conan/R/x86_64-pc-linuxgnu-

library/3.0/shinySlickgrid/slickgrid

Listening on port 6742

shiny会自动在后台打开一个Web服务器，默认的Web访问端口是6742，之后就可以通过浏览器远程访问了。打开浏览器地址栏输入http://192.168.1.201:6742，如图3-5所示。

图3-5　可视化性能监控

在图3-5中，网页的表格一共有6列，其中#是行号，source code是监控的目标函数源代码，t是当前行执行的总（total）时间（秒），r是释放（released）内存量，a是分配（allocated）内存量，d是重复（duplicates）次数。下面给出函数性能数据的解释。

·#6（第6行），用于加载数据，总时间占用0.309s，分配内存0.064mb，方法重复次数为14次

·#15（第15行），用于清除数据，总时间占用0.179s，释放内存0.065mb，方法重复次数为37次

通过lineprof可视化工具，我们生成的报告更灵活，更直观，更好看，支持在网页上进行用户交互，甚至是网页版本的。如果再和3.2节R语言性能监控工具Rprof的效果比较一下，你会惊叹R语言是如此强大，它的进步如此神速。当然，R语言具有无限的潜力，需要更多人来推动它进步，希望你也能给它助力。

第二部分 　R服务器

■第4章　R语言的跨平台通信

■第5章　R的服务器实现

第4章　R语言的跨平台通信

本章主要介绍了R语言跨平台通信的4个工具包，帮助读者实现R语言与Java和JavaScript语言之间的通信程序。

4.1　Rserve与Java的跨平台通信

问题

Java怎么调用R？

引言

当前主流的异构跨平台通信组件Apache Thrift已经火遍大江南北，支持15种编程语言，但是到目前为止还没有加入R语言。要让R实现跨平台的通信，就只能从R的社区（CRAN）中找方案，像rJava、RCpp、rpy都是2种语言结合的方案，这些方案类似地会把R引擎加载到其他语言内存环境。这类方案的优点是高效，缺点是紧耦合，扩展受限，接口程序无法重用。

Rserve给了我们一种新的选择，它是抽象R语言网络接口，基于TCP/IP协议实现与多语言之间的通信。Rserve支持R语言与其他语言通信，通过C/S结构的程序调用，支持C/C++、Java、PHP、Python、Ruby、Node.js等。Rserve支持远程连接、用户认证、文件传输等功能。我们可以设计R作为后台服务引擎，处理统计建模、数据分析、绘图等的任务。本节我们就来体验Rserve与Java的跨平台通信。

4.1.1　Rserve安装

本节使用的系统环境是：

·Linux Ubuntu 12.04.2LTS 64bit server

·R:3.0.1x86_64-pc-linux-gnu

·IP 192.168.1.201

注　Rserve同时支持Windows 7环境和Linux环境，由于Rserve主要用来做通信服务器，建议使用Linux环境。

Rserve的安装过程如下：

~ R #

启动R

程序

> install.packages("Rserve") #

安装Rserve

~ R CMD Rserve   #

在命令行启动Rserve

服务器

~ ps -aux|grep Rserve   #

查看进程

conan

7142  0.0  1.2 116296 25240 ? Ss 09:13 0:00 /usr/lib/R/bin/Rserve

~ netstat -nltp|grep Rserve #

查看端口

tcp       0      0 127.0.0.1:6311 0.0.0.0:* LISTEN 7142/Rserve

这时Rserve服务器已经启动，端口是6311。127.0.0.1表示只接受来自IP为127.0.0.1的请求访问，即只允许本地应用访问。如果我们想远程访问Rserve，就需要开启远程模式，这就需要在启动命令时增加参数–RS-enable-remote：

~ kill -9 7142 #

杀掉刚才的Rserve

守护进程

~ R CMD Rserve --RS-enable-remote #

以远程模式启动Rserve

~ netstat -nltp|grep Rserve #

查看端口

tcp        0      0 0.0.0.0:6311    0.0.0.0:* LISTEN 7173Rserve

00.0.0说明不限IP访问了，我们接下来就可以从远程来访问Rserve了。

4.1.2　用Java远程连接Rserve

Java程序在Windows 7中通过EclipseIDE工具直接运行，让Java远程连接Linux系统上的Rserve服务器。Windows 7环境是：

·Java:Oracle SUN JDK 1.6.0_4564bit

·Eclipse:Juno Service Release 2

·IP:192.168.1.13

Rserve所在Linux服务器的IP为192.168.1.201。

1.下载Java客户端JAR包

通过链接（http://www.rforge.net/Rserve/files/），下载Java客户端JAR包REngine.jar 和RserveEngine.jar。通过这两个Jar包，就可以实现Java和Rserve的通信了。

·REngine.jar:用于R和Java数据类型的映射。

·RserveEngine.jar:用于Rserve的通信程序。

这两个类库说明，可以查看官方的javadoc文件，网址是http://rforge.net/org/doc/。这两个JAR包是Java编译后的二进制文件，没有提供源代码文件。

2.在Eclipse中创建Java工程

在Eclipse中新建Java工程，并加载Jar包环境中，加载后如图4-1所示。

图4-1　Eclipse中的Java工程

3.Java编程实现

下面我们来写一个Java的类Demo1.java，完成Java远程调用Rserve服务器的实现，Demo1.java中包括2个方法。

·main()方法：是Java程序启动的入口，实例化一个demo对象，然后调用callRserve()方法。

·callRserve()方法：创建远程访问Rserve的socket连接，以字符串的方式向Rserve服务器发送两条R的语句，在Rserve上计算并返回结果，最后在Java中输出结果。

Demo1.java代码如下。

package org.conan.r.rserve;

import org.rosuda.REngine.REXP;

import org.rosuda.REngine.REXPMismatchException;

import org.rosuda.REngine.Rserve.RConnection;

import org.rosuda.REngine.Rserve.RserveException;

public class Demo1 {

/**

* Main

方法用于启动Java

应用

*/

public static void main(String[] args) throws RserveException, REXPMismatchException {

Demo1 demo = new Demo1();

demo.callRserve();

}

/**

*

访问Rserve

*/

public void callRserve() throws RserveException, REXPMismatchException {

RConnection c = new RConnection("192.168.1.201");//

创建访问连接

REXP x = c.eval("R.version.string"); //

执行一条R

的语句

System.out.println(x.asString()); //

在Java

中打印返回结果

double[] arr = c.eval("rnorm(10)").asDoubles(); //

执行rnorm(10)

语句

for (double a : arr) { //

在Java

中循环打印返回结果

System.out.print(a + ",");

}

}

}

运行结果如下。

R version 3.0.1 (2013-05-16)

1.7695224124757984,-0.29753038160770323,0.26596993631142246,1.4027325257239547,

-0.30663565983302676,-0.17594309812158912,0.10071253841443684,0.9365455161259986,

0.11272119436439701,0.5766373030674361,

通过Rserve我们就非常简单地实现了Java和R的通信，更准确地说，是Java访问Rserve服务器，实现了基于TCP/IP的通信。我们解决了通信的问题后，就可以发挥想象，把R更广泛地用起来了。本节只是简单地介绍了Rserve的安装和启动，关于Rserve服务器详细的使用和配置，请查看5.1节。

4.2　Rsession让Java调用R更简单

问题

Java调用R有没有更简单的方法？

引言

Rserve作为R语言的通信接口，已经成为了R语言扩展的重要通道。但Rserve过于底层，用Java调用Rserve的API接口，也有些难于理解。于是就出现了Rsession项目，Rsession是对Rserve的封装，提供了更高层的API接口，包括Rserve服务器控制和多会话机制，并支持Windows环境。Rsession简化了Java调用Rserve的API接口的难度，提供了一种简单的方式，让Java可以访问远程或本地的Rserve实例。另一个R和Java通信的库JRI，并不支持多会话机制，在下一节中会介绍。

4.2.1　Rsession下载

本节使用的系统环境是：

·Win764bit

·R:3.0.1x86_64-w64-mingw32/x64b4bit

注　Rsession同时支持Windows 7环境和Linux环境。

1.直接下载发行包

Rsession包括3个jar包：REngine.jar、Rserve.jar和Rsession.jar，可以通过链接http://rsession.googlecode.com/files/libRsession.zip下载发行包，下载之后解压缩就可以直接使用了。

2.下载源代码，自己编译发行包。

也可以通过源代码下载Rsession，Rsession项目的SVN地址是http://rsession.googlecode.com/svn/trunk/Rsession/。从SVN下载Rsession项目的操作如下：

~ cd d:\workspace\java   #

进入本地目录

~ svn checkout http://rsession.googlecode.com/svn/trunk/ rsession-read-only

#

通过svn

下载

~ mv rsession-read-only rsession   #

把目录改名

~ cd rsession\Rsession    #

进行Rsession

代码目录

Rsession项目是通过Ant构建的，我们可以自己编译、打包。Ant是一个Java的自动化构建工具。

~ ant #

用ant

构建项目

Buildfile: d:\workspace\java\rsession\Rsession\build.xml

clean:

clean-dist:

init:

[mkdir] Created dir: d:\workspace\java\rsession\Rsession\build

[mkdir] Created dir: d:\workspace\java\rsession\Rsession\dist\lib

resource:

[copy] Copying 28 files to d:\workspace\java\rsession\Rsession\dist\lib

[copy] Copied 12 empty directories to 1 empty directory under d:\workspace\

java\rsession\Rsession\dist\lib

compile:

[javac] d:\workspace\java\rsession\Rsession\build.xml:33: warning:

'includeantruntime' was not set, defaulting to build.sysclasspath=last;

set to false for repeatable builds

[javac] Compiling 10 source files to d:\workspace\java\rsession\Rsession\build

dist:

[jar] Building jar: d:\workspace\java\rsession\Rsession\dist\lib\Rsession.jar

[zip] Building zip: d:\workspace\java\rsession\Rsession\dist\libRsession.zip

BUILD SUCCESSFUL

Total time: 2 seconds

运行上面的ant命令，就在目录d:\workspace\java\rsession\Rsession\dist\下面生成了发行包，libRsession.zip。与直接下载的libRsession.zip文件不同的是，自己手动编译打包的libRsession.zip文件中，解压后多了一个文件jmatharray.jar。我觉得这个文件可能是编译过程中需要的依赖包，在运行时是不需要的，有没有都不影响我们的程序运行。

4.2.2　用Eclipse构建Rsession项目

用Eclipse构建Rsession项目，复制Rsession\dist\目录文件到项目，并加载到项目的环境变量，如图4-2所示。

图4-2　用Eclipse构建Rsession项目

4.2.3　Rsession的API介绍

查看类库Rsession.jar，如图4-3所示，可以看到Rsession的各个API，包括接口类、功能类和辅助类，下面分别介绍。

图4-3　Rsession.jar文件结构

（1）接口类

·BusyListener:用于监听R引擎的状态。

·EvalListener:用于监听R引擎执行R脚本的情况。

·Logger:用于日志输出。

·UpdateObjectsListener:用于监听R运行时环境的变化。

（2）功能类

·Rdaemon:RServe的守护进程。

·RLogPanel:显示R日志的控件。

·RObjectsPanel:显示R变量的控件。

·RserverConf:连接Rserve实例的配置文件。

·Rsession:连接Rserve实例。

·StartRserve:启动本地的Rserve。

（3）辅助类

·RegistryHog:用于从Windows系统的注册表读取R的安装信息。

·StreamHog:用于进程之间的通信。

4.2.4　Rsession使用

接下来，我们用Rsession访问远程的Rserve服务器。

1.Rserve服务器端环境

远程服务器系统环境：

·Linux:Ubuntu 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

远程的Rserve环境：

·Rserve:Rserve v1.7-1

·IP:192.168.1.201，允许远程访问

·端口:6311

·登录认证:用户名:conan,密码:conan

·字符编码:utf-8

Rserve服务器环境与5.2节Rserve的R语言客户端RSclient环境配置相同。

2.Java程序代码

下面我们来写一个Java的类，实现Java对远程Rserve服务器的调用。首先新建Java程序文件RsessionDemo.java。

package org.conan.r.rsession;

import java.io.File;

import java.util.Properties;

import org.math.R.RserverConf;

import org.math.R.Rsession;

import org.rosuda.REngine.REXPMismatchException;

public class RsessionDemo {

/**

* Main

方法用于启动Java

应用

*/

public static void main(String args[]) throws REXPMismatchException {

RserverConf rconf = new RserverConf("192.168.1.201", 6311, "conan",

"conan", new Properties()); //

建立远程连接

Rsession s = Rsession.newInstanceTry(System.out, rconf);

double[] rand = s.eval("rnorm(5)").asDoubles(); //

执行R

脚本

System.out.println(rand);

s.set("demo", Math.random()); //

创建一个R

对象

s.eval("ls()");

s.save(new File("./output/save.Rdata"), "demo"); //

保存R

运行时状态到文件

s.rm("demo"); //

删除R

对象demo

s.eval("ls()");

s.load(new File("./output/save.Rdata")); //

从文件加载R

环境

s.eval("ls()");

s.eval("print(demo)");

s.set("df", new double[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, { 10,11, 12 } }, "x1", "x2", "x3"); //

创建一个data.frame

对象

double df$x1_3 = s.eval("df$x1[3]").asDouble();

System.out.println(df$x1_3);

s.rm("df");

s.eval("getwd()"); //

生成一个图形文件

s.toJPEG(new File("./output/plot.png"), 400, 400, "plot(rnorm(10))");

String html = s.asHTML("summary(rnorm(100))"); //

以HTML

格式输出

System.out.println(html);

String txt = s.asString("summary(rnorm(100))"); //

以文本格式输出

System.out.println(txt);

System.out.println(s.installPackage("sensitivity", true)); //

安装新类库

s.end();

}

}

3.运行日志输出

为了方便阅读，我把对应的Java程序与日志放在一起显示。首先执行R脚本。

double[] rand = s.eval("rnorm(5)").asDoubles(); //

执行R

脚本

for(double ran:rand){

System.out.print(ran+",");

}

[eval] rnorm(5) //

日志输出

org.rosuda.REngine.REXPDouble@5f934ad[5]{0.08779203903807914,

0.039929482749452114,-0.8788534039223883,-0.8875740206608903,

-0.8493446334021442}0.08779203903807914,0.039929482749452114,

-0.8788534039223883,-0.8875740206608903,-0.8493446334021442

然后创建R对象并保存R环境。

s.set("demo", Math.random()); //

创建一个R

对象

s.eval("ls()");

s.save(new File("./output/save.Rdata"), "demo"); //

保存R

环境到本地到文件

s.rm("demo"); //

删除R

对象demo

s.eval("ls()");

s.load(new File("./output/save.Rdata")); //

从文件加载R

环境

s.eval("ls()");

s.eval("print(demo)");

[set] demo //

日志输出

再创建一个data.frame对象。

s.set("df", new double[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }, { 10, 11, 12} }, "x1", "x2", "x3");

double df$x1_3 = s.eval("df$x1[3]").asDouble();

System.out.println(df$x1_3);

s.rm("df");

[set] df //

日志输出

在本地生成一个图形文件，如图4-4所示。

图4-4　toJPEG图片

s.toJPEG(new File("./output/plot.png"), 400, 400, "plot(rnorm(10))");

[set] plotfile_1100539400 //

日志输出

以HTML格式输出。

String html = s.asHTML("summary(rnorm(100))");

System.out.println(html);

<html> Min. 1st Qu. Median Mean 3rd Qu. Max. <br/>-2.332000

-0.659900 0.036920 0.004485 0.665800 2.517000 </html> //

日志输出

以文本格式输出。

String txt = s.asString("summary(rnorm(100))"); // format in text

System.out.println(txt);

Min. 1st Qu. Median Mean 3rd Qu. Max. //

日志输出

-3.19700 -0.65330 -0.09893 -0.07190 0.53300 2.29000

安装新类库。

System.out.println(s.installPackage("sensitivity", true)); //

安装新类库

trying to load package sensitivity //

日志输出

package sensitivity is not installed.

package sensitivity not yet installed.

[eval] install.packages('sensitivity',repos='http://cran.cict.

fr/',dependencies=TRUE)

org.rosuda.REngine.REXPNull@4d47c5fc

request package sensitivity install...

package sensitivity is not installed.

!   package sensitivity installation failed.

Impossible to install package sensitivity !

如果对比5.1节Rserve的JavaAPI，你可能会感觉Rsession更友好！Rsession封装了Java调用R的过程，对于有Java基础的同学可以快速上手，让Java应用也能玩转统计计算。动起手来，创造就在自己手中。

4.3　解惑rJava R与Java的高速通道

问题

JAVA和R能不能双向通信？

引言

Java语言在工业界长期处于霸主地位，Java语法、JVM、JDK、Java开源库，在近10年得到了爆发式的发展，几乎覆盖了应用开发的所有领域。伴随着Java的全领域发展，问题也随之而来了。语法越来越复杂，近似的项目越来越多，学好Java变得很难。对于没有IT背景的统计人员，学用Java更是难于上青天。

R一直是统计圈内处于佼佼者的语言，语法简单，学习曲线不太长也不太陡。如果能结合Java的通用性和R的专业性，碰撞出的火花，将会缤纷绚烂。本节将介绍R与Java连接的高速通道，rJava实现双向通信方案。

4.3.1　rJava介绍

rJava是一个R语言和Java语言的通信接口，通过底层JNI实现调用，允许在R中直接调用Java的对象和方法。rJava还提供了Java调用R的功能，是通过JRI（Java/R Interface）实现的。JRI现在已经被嵌入到rJava的包中，我们也可以单独试用这个功能。现在rJava包已经成为很多基于Java开发R包的基础功能组件。正是由于rJava是底层接口，并使用JNI作为接口调用，所以效率非常高。在JRI的方案中，Java虚拟机（JVM）通过内存直接加载R的计算引擎，调用过程性能几乎无损耗，因此是非常高效的连接通道，是R和Java通信的首选开发包。

4.3.2　rJava安装

本节使用的Linux系统环境是：

·Linux:Ubuntu 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

·Java:SUN 1.6.0_2964bit

注　rJava同时支持Windows 7环境和Linux环境。

安装rJava包时，笔者建议使用root权限来安装rJava包。因为rJava是跨平台调用的一个基础包，后面讲到的RHadoop项目也是基于rJava开发的，当跨平台的程序调用时，在Linux下会有权限检查，为了减少错误，建议以root权限安装rJava包。这里在安装rJava之前，假设读者在Linux系统中已经安装好了Java环境。Java环境的安装，请参考附录A。

~ sudo R CMD javareconf #

以root

权限配置rJava

环境

~ sudo R #

以root

权限启动R

> install.packages("rJava") #

安装rJava

包

> library(rJava) #

加载rJava

包

4.3.3　rJava实现R调用Java

在R环境中，使用rJava包编程。

> search() #

检查当前环境已加载的包

[1] ".GlobalEnv"       "package:rJava"     "package:stats"

[4] "package:graphics" "package:grDevices" "package:utils"

[7] "package:datasets" "package:methods"   "Autoloads"

[10] "package:base"

> .jinit() #

启动JVM

> s <- .jnew("java/lang/String", "Hello World!") #

声明并赋值到字符串

> s

[1] "Java-Object{Hello World!}"

> .jcall(s,"I","length") #

查看字符串长度

[1] 12

> .jcall(s,"I","indexOf","World") #

索引World

的位置

[1] 6

> .jmethods(s,"concat") #

查看concat

的方法声明

[1] "public java.lang.String java.lang.String.concat(java.lang.String)"

> .jcall(s,"Ljava/lang/String;","concat",s) #

使用concat

方法连接字符串

[1] "Hello World!Hello World!"

> print(s) #

打印字符串对象

[1] "Java-Object{Hello World!}"

> .jstrVal(s) #

打印字符串值

[1] "Hello World!"

rJava优化过的方法调用，用$来调用方法。

> s$length() #

同.jcall(s,"I","length")

[1] 12

> s$indexOf("World") #

同.jcall(s,"I","indexOf","World")

[1] 6

4.3.4　rJava(JRI)实现Java调用R (Windows 7)

在Windows 7中安装rJava，系统环境是：

·Win7:x86_64-w64-mingw32/x64(64-bit)

·R:version 3.0.1

·JAVA:SUN JDK 1.6.0_4564bit

设置环境变量。

PATH: C:\Program Files\R\R-3.0.1\bin\x64;D:\toolkit\java\jdk6\bin;;D:\toolkit\

java\jdk6\jre\bin\server

JAVA_HOME: D:\toolkit\java\jdk6

CLASSPATH: C:\Program Files\R\R-3.0.1\library\rJava\jri

在R中安装rJava。

~ R #

启动R

程序

> install.packages("rJava") #

安装rJava

包

> library(rJava) #

加载rJava

> .jinit()

> s <- .jnew("java/lang/String", "Hello World!") #R

调用Java

变量测试

> s

[1] "Java-Object{Hello World!}"

启动Eclipse编写程序，如图4-5所示。

图4-5　在Eclipse创建Rserve项目

下面我们来写一个Java的类DemoRJava.java，实现Java调用JRI的程序。DemoRJava.java中包括2个方法。

·main()方法：是Java程序启动的入口，实例化一个demo对象，然后调用callRJava()方法。

·callRJava()方法：创建Rengine对象，在JVM启动启动R引擎，以字符串的方式向R引擎传两条R的语句，在Java中输出结果。

新建Java程序文件DemoRJava.java。

package org.conan.r.rjava;

import org.rosuda.JRI.Rengine;

public class DemoRJava {

/**

* Main

方法用于启动Java

应用

*/

public static void main(String[] args) {

DemoRJava demo = new DemoRJava();

demo.callRJava();

}

public void callRJava() {

Rengine re = new Rengine(new String[] { "--vanilla" }, false, null);

//

实例化 Rengine

对象，启动R

引擎

if (!re.waitForR()) {

System.out.println("Cannot load R");

return;

}

String version = re.eval("R.version.string").asString();//

执行一条R

的语句

System.out.println(version); //

打印结果

double[] arr = re.eval("rnorm(10)").asDoubleArray(); //

循环打印数组

for (double a : arr) {

System.out.print(a + ",");

}

re.end();//

关闭R

引擎

}

}

在Eclipse启动设置VM参数，如图4-6所示。

-Djava.library.path="C:\Program Files\R\R-3.0.1\library\rJava\jri\x64"

图4-6　配置Eclipse的启动参数

运行结果：

R version 3.0.1 (2013-05-16)

0.04051018703700011,-0.3321596519938258,0.45642459001166913,-1.1907153494936031,

1.5872266854172385,1.3639721994863943,-0.6309712627586983,-1.5226698569087498,

-1.0416402147174952,0.4864034017637044,

在Eclipse中完成打包DemoRJava.jar，上传到Linux环境，继续测试。

4.3.5　rJava(JRI)实现Java调用R (Ubuntu)

新建目录DemoRJava，上传DemoRJava.jar到DemoRJava，Shell命令如下。

~ mkdir /home/conan/R/DemoRJava

~ cd /home/conan/R/DemoRJava

~ ls -l

-rw-r--r-- 1 conan conan 1328 Aug 8 2013 DemoRJava.jar

运行Jar包。

~ export R_HOME=/usr/lib/R

~ java -Djava.library.path=/usr/local/lib/R/site-library/rJava/jri -cp /usr/

local/lib/R/site-library/rJava/jri/JRI.jar:/home/conan/R/DemoRJava/DemoRJava.jar

org.conan.r.rjava.DemoRJava

运行结果如下：

R version 3.0.1 (2013-05-16)

0.6374494596732511,1.3413824702002808,0.04573045670001342,-0.6885617932810327,

0.14970067632722675,-0.3989493870007832,-0.6148250252955993,

0.40132038323714453,-0.5385260423222166,0.3459850956295771,

我们完成了R和Java的互调。包括了R通过rJava调用Java，Java通过JRI调用R。并演示了Windows 7和Linux Ubuntu中的使用方法。

4.4　Node.js与R跨平台通信

问题

Node.js怎么调用R？

引言

做Web开发还没有用过Node.js的同学已经落后了。Node.js是基于JavaScript实现的一款后台程序开发平台，以笔者个人的体验来说，Node.js的开发效率比PHP还要高一些，完全异步加载，性能一点也不落后，假以时日还有赶超PHP的趋势。

HTML5作为Web前端大量使用JavaScript，各种炫效果层出不穷。如果能把R语言的效果图，都用HTML5重新做渲染，并增加时时通信和用户交互，两种语言各取优势，必然产生惊为天人的效果。长话短说，下面就来介绍Node.js与R跨平台通信

4.4.1　Node.js简单介绍

Node.js是一个可以快速构建网络服务及应用的平台，基于Chrome抯JavaScript runtime，也就是说，实际上它是对Google V8引擎（应用于Google Chrome浏览器）进行了封装。V8引擎执行JavaScript的速度非常快，性能非常好。Node.js对一些特殊用例进行了优化，提供了替代的API，使得V8在非浏览器环境下运行得更好。

4.4.2　R语言配置环境

本节中介绍的Node.js与R的跨平台通信，对于R语言的支持库是Rserve，通过Rserve提供一个R的TCP/IP的通信协议，实现Node.js和R的通信。首先，我已经在Linux Ubuntu上配置好了Rserve环境。本节使用的系统环境是：

·Linux:Ubuntu 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

·IP:192.168.1.201

Rserve环境:Rserve v1.7-1，端口:6311，允许远程访问。查看Rserve的进程：

~ ps -aux|grep Rserve

conan     9736  0.0   1.2 116288 25440 ? Ss 13:11 0:01 /usr/lib/R/bin/

Rserve --RS-enable-remote

~ netstat -nltp|grep Rserve

tcp        0      0 0.0.0.0:6311 0.0.0.0:* LISTEN 9736/Rserve

4.4.3　Node.js配置环境

Node.js环境也比较简单，以Express3库作为基础的Web框架，rio库是让Node.js与Rserve通信的依赖库。如果你是Node.js的新手，需要从头学习，请参考笔者的博客“从零开始Node.js系列文章”，如果仅仅搭建一个Node.js测试环境，请参考“准备Node.js开发环境Ubuntu”一文。

Windows 7中的开发环境：

·Node:v0.10.5

·NPM:1.2.19

·IP:192.168.1.13

·Express:3.2.2

现在我们已经在Windows 7中，创建好了Express3的项目。项目目录：D:\workspace\project\investment\webui。

~ D:\workspace\project\investment\webui>ls

README.md app.js models node-rio-dump.bin node_modules package.json public

routes views

安装rio库。

~ D:\workspace\project\investment\webui>npm install rio

rio@0.9.0 node_modules\rio

├── hexy@0.2.5

└── binary@0.3.0 (buffers@0.1.1, chainsaw@0.1.0)

4.4.4　Node.js与R跨平台通信

下面我们开始写Node.js程序，来实现跨平台的通信。首先在app.js配置文件中，增加一个路由路径。

~ vi app.js

var vis = require('./routes/vis') //

代码有省略

app.get('/vis/rio',vis.rio);

然后在路由目录/routes增加一个路由文件vis.js。

~ vi /routes/vis.js

var rio = require("rio");

exports.rio = function(req, res){

options = {

host : "192.168.1.201", //

远程Rserve

服务器地址

port : 6311, //

远程Rserve

服务器端口

callback: function (err, val) { //

计算结果的回调函数

if (!err) { //

正常返回

console.log("RETURN:"+val);

return res.send({'success':true,'res':val}); //

把结果传给界面

} else { //

错误返回

console.log("ERROR:Rserve call failed")

return res.send({'success':false});

}

},

}

rio.enableDebug(true); //

开启调试模式

rio.evaluate("pi / 2 * 2 * 2",options); //

运行R

代码

};

在上面代码中，我们实现了rio与Rserve的远程连接。以字符的形式，把R的语句（pi/2*2*2）作为参数，传给远程的Rserve服务器，在Rserve服务器中计算，再通过callback的方法，获得R结果的返回值。

打开浏览器（http://localhost:3000/vis/rio），从Web界面上可以看到（pi/2*2*2）计算的结果是6.283185307179586。在浏览器中计算结构返回值是一个JSON格式的对象。

{

"success": true,

"res": 6.283185307179586

}

输出命令行日志。

Connected to Rserve

Supported capabilities --------------

Sending command to Rserve

00000000: 0300 0000 1400 0000 0000 0000 0000 0000 ................

00000010: 0410 0000 7069 202f 2032 202a 2032 202a ....pi./.2.*.2.*

00000020: 2032 0001                               .2..

Data packet

00000000: 2108 0000 182d 4454 fb21 1940           !....-DT{!.@

Type SEXP 33

Response value: 6.283185307179586

RETURN:6.283185307179586

GET /vis/rio 200 33ms - 49b

Disconnected from Rserve

Closed from Rserve

我可以看到Node.js与Rserve的通信情况，Response value:6.283185307179586，与页面上面是一致的。接下来，我们改一下R的运行脚本。rnorm(10)是取10个标准正态分布N(0,1)的随机数。

rio.evaluate("rnorm(10)",options); //

运行R

代码

浏览器返回的计算结果的JSON对象。

{

"success": true,

"res": [

-0.011531884725262991,

0.5106443501593562,

-0.05216533321965309,

1.9221980152236238,

0.5205238122633465,

-0.3275367539102907,

-0.06588102930129405,

1.5410418730008988,

1.308169913050071,

0.005044179478212583

]

}

命令行日志：

Connected to Rserve

Supported capabilities --------------

Sending command to Rserve

00000000: 0300 0000 1000 0000 0000 0000 0000 0000 ................

00000010: 040c 0000 726e 6f72 6d28 3130 2900 0101 ....rnorm(10)...

Data packet

00000000: 2150 0000 f6ca 0c5e 079e 87bf 9b4a fad1 !P..vJ.^...?.JzQ

00000010: 3257 e03f eda2 5320 6ab5 aabf 2b25 bdb4 2W`?m"S.j5*?+%=4

00000020: 52c1 fe3f ebba ce8d 21a8 e03f bc17 92b7 RA~?k:N.!(`?<..7

00000030: 5cf6 d4bf ca9f 4642 94dd b0bf 1be3 e485 \vT?J.FB.]0?.cd.

00000040: 1ba8 f83f 5a94 2293 43ee f43f 1724 4e9e .(x?Z.".Cnt?.$N.

00000050: 34a9 743f                               4)t?

Type SEXP 33

Response value: -0.011531884725262991,0.5106443501593562,-0.05216533321965309,

1.9221980152236238,0.5205238122633465,-0.3275367539102907,

-0.06588102930129405,1.5410418730008988,1.308169913050071,

0.005044179478212583

RETU RN:-0.011531884725262991,0.5106443501593562,-0.05216533321965309,

1.9221980152236238,0.5205238122633465,-0.3275367539102907,

-0.06588102930129405,1.5410418730008988,1.308169913050071,

0.005044179478212583

GET /vis/rio 200 30ms - 285b

Disconnected from Rserve

Closed from Rserve

我们实现了Node.js与R的跨平台通信，有点感觉像打通了中国功夫的“任督二脉”，从此以后，成为一代高手。

第5章　R的服务器实现

本章主要介绍R语言服务器的4个工具包，帮助读者实现用R语言创建Socket服务器、Web服务器和WebSocket服务器运行环境。

5.1　R语言服务器程序Rserve详解

问题

Rserve怎么用？

引言

在4.1节中，我们初识了Rserve，用于R和Java的通信，但那里说得不够详细，用起来处处有坑。接下来，深入细节再学学。

Rserve作为一个R语言与其他语言的通信（TCP/IP）接口，被多个项目所依赖。Rserve服务端配置和运行都非常简单，客户端由多种语言实现，如C/C++、Java等。R自己的客户端实现RSclient项目将在下一节介绍。本节详细剖析Rserve作为服务器端应用的配置和使用。

5.1.1　Rserve的启动

本节使用的系统环境是：

·Linux：Ubuntu 12.04.2LTS 64bit

·R：3.0.1x86_64-pc-linux-gnu

注　Rserve同时支持Windows 7环境和Linux环境，由于Rserve主要用来做通信服务器，建议使用Linux环境。

Rserve的安装过程如下：

~ R #

启动R

程序

> install.packages("Rserve") #

安装Rserve

> library(Rserve) #

加载Rserve

启动Rserve服务器有两种方式，即在R运行环境中启动Rserve服务器和在命令行中启动Rserve服务器。在R的运行环境中启动Rserve服务器时，我们可以用到Rserve的函数。

·Rserve()函数：单独启动一个守护进程作为Rserve实例。

·run.Rserve()函数：在当前的进程中启动Rserve实例。

·self函数：主是用于与当前进程中的Rserve服务器程序交互，包括4个函数self.ctrlEval()、self.ctrlSource()、self.oobSend()、self.oobMessage()，只是这几个函数很少会用到。

1.在程序中启动Rserve服务器

> library(Rserve)

> Rserve()

#

单独启动一个守护进程作为Rserve

实例

Starting Rserve:

/usr/lib/R/bin/R CMD /home/conan/R/x86_64-pc-linux-gnu-library/3.0/Rserve/libs//Rserve

查看Rserve进程。

~ ps aux | grep R

conan 8799 0.1 1.5 121748 32088 pts/0 S+ 22:30 0:00 /usr/lib/R/bin/exec/R

conan 8830 0.0 1.2 116336 25044 ? Ss 06:46 0:00 /home/conan/R/

x86_64-pc-linux-gnu-library/3.0/Rserve/libs//Rserve

~ netstat -nltp|grep Rserve

tcp      0    0 127.0.0.1:6311      0.0.0.0:* LISTEN 8830/Rserve

在这种情况下，当前的R运行环境并没有被中断，而在系统后台单独启动了一个Rserve实例。用run.Rserve()函数启动，则是在当前的R运行环境启动Rserve。

> run.Rserve() #

在当前的进程中启动Rserve

实例

-- running Rserve in this R session (pid=30664), 1 server(s) --

(This session will block until Rserve is shut down)

~ ps aux|grep R #

查看R

的进程

30664 pts/0 00:00:00 R

~ netstat -nltp|grep R

tcp      0    0 127.0.0.1:6311        0.0.0.0:*         LISTEN         30664/R

2.在命令行中启动Rserve服务器

首先查看Rserve命令行帮助。

~ R CMD Rserve --help

Usage: R CMD Rserve []

Options: --help this help screen

--version prints Rserve version (also passed to R)

--RS-port listen on the specified TCP port

--RS-socket use specified local (unix) socket instead of TCP/IP.

--RS-workdir use specified working directory root for connections.

--RS-encoding set default server string encoding to .

--RS-conf load additional config file.

--RS-settings dumps current settings of the Rserve

--RS-source source the specified file on startup.

--RS-enable-control enable control commands

--RS-enable-remote enable remote connections

All other options are passed to the R engine.

在命令行启动Rserve，并打开远程访问模式。

~ R CMD Rserve --RS-enable-remote

查看Rserve的进程。

~ ps -aux|grep Rserve

conan 27639 0.0 1.2 116288 25236 ? Ss 20:41 0:00 /usr/lib/R/bin/

Rserve --RS-enable-remote

~ netstat -nltp|grep Rserve

tcp    0     0 0.0.0.0:6311        0.0.0.0:*          LISTEN         27639/Rserve

5.1.2　Rserve高级使用：Rserve配置管理

我们可以通过配置文件Rserv.conf管理Rserve服务器，在配置文件中可以定义服务器的启动脚本。通过命令查看Rserve服务器默认的配置信息。

~ R CMD Rserve --RS-settings

Rserve v1.7-1

config file: /etc/Rserv.conf

working root: /tmp/Rserv

port: 6311

local socket: [none, TCP/IP used]

authorization required: no

plain text password: not allowed

passwords file: [none]

allow I/O: yes

allow remote access: no

control commands: no

interactive: yes

max.input buffer size: 262144 kB

当前Rserve服务器的配置说明如下。

·config file:如果本地无此文件/etc/Rserv.conf，系统会默认跳过这项。

·working root:R运行时工作目录/tmp/Rserv。

·port:通信端口6311。

·local socket:TCP/IP协议。

·authorization:认证未开启。

·plain text password:不允许明文密码。

·passwords file:密码文件，未指定。

·allow I/O:允许IO操作。

·allow remote access:远程访问未开启。

·control commands:命令控制未开启。

·interactive:允许交互。

·max.input buffer size:文件上传限制262MB。

修改默认配置，增加远程访问，并设置加载脚本。新建文件/etc/Rserv.conf。

~ sudo vi /etc/Rserv.conf

workdir /tmp/Rserv

remote enable

fileio enable

interactive yes

port 6311

maxinbuf 262144

encoding utf8

control enable

source /home/conan/R/RServe/source.R

eval xx=1

source选项用来配置Rserve服务器启动时加载的文件，例如，初始化系统变量、初始化系统函数等。eval选项用来定义环境变量。

增加Rserve服务器初始化的启动脚本。

~ vi /home/conan/R/RServe/source.R

cat("This is my Rserve!!")

print(paste("Server start at",Sys.time()))

再次查看服务器配置。

~ R CMD Rserve --RS-settings

Rserve v1.7-1

config file: /etc/Rserv.conf

working root: /tmp/Rserv

port: 6311

local socket: [none, TCP/IP used]

authorization required: yes

plain text password: allowed

passwords file: [none]

allow I/O: yes

allow remote access: yes

control commands: yes

interactive: yes

max.input buffer size: 262144 kB

重新启动Rserve服务器。

~ R CMD Rserve

R version 3.0.1 (2013-05-16) -- "Good Sport"

Copyright (C) 2013 The R Foundation for Statistical Computing

Platform: x86_64-pc-linux-gnu (64-bit)

This is my Rserve!![1] "Server start at 2013-10-30 22:38:10"

Rserv started in daemon mode.

查看日志：source.R在启动时候被执行。

"This is my Rserve!![1] "Server start at 2013-10-30 22:38:10""

查看进程。

~ ps -aux|grep Rserve

conan   28339  0.0   1.2 116292 25240 ?   Ss   22:31   0:00 /usr/lib/R/bin/Rserve

~ netstat -ntlp|grep Rserve

tcp      0      0 0.0.0.0:6311          0.0.0.0:* LISTEN 28339/Rserve

00.0.0表示允许远程访问，不限制IP。

5.1.3　Rserve高级使用：用户登录认证

在本地环境，无认证情况下，使用RSclient访问Rserve。关于RSclient的使用请参考5.2节。

~ R

>  library(RSclient) #

加载RSclient

>  conn<-RS.connect()

>  RS.eval(conn,rnorm(10))

[1]  0.03230305  0.95710725  -0.33416069  -0.37440009  -1.95515719  -0.22895924

[7]  0.39591984  1.67898842  -0.01666688  -0.26877775

修改配置，增加用户登录认证，并允许明文密码，修改文件/etc/Rserv.conf。

~ sudo vi /etc/Rserv.conf

workdir /tmp/Rserv

remote enable

fileio enable

interactive yes

port 6311

maxinbuf 262144

encoding utf8

control enable

source /home/conan/R/RServe/source.R

eval xx=1

auth required

plaintext enable

再次使用RSclient直接访问时，认证报错。

> library(RSclient)

> conn<-RS.connect()

> RS.eval(conn,rnorm(10))

Error in RS.eval(conn, rnorm(10)) :

command failed with status code 0x41: authentication failed

用RSclient登录后，再连接。

> library(RSclient)

> conn<-RS.connect()

> RS.login(conn,"conan","conan",authkey=RS.authkey(conn))

[1] TRUE

> RS.eval(conn,rnorm(5))

[1] -1.19827684 0.72164617 0.22225934 0.09901505 -1.54661436

这里用户登录认证，是绑定的操作系统用户。我们还可以在Rserv.conf配置文件中指定uid,gid参数，从而更细粒度地控制服务器权限。

本节详细地介绍了Rserve的安装、启动、配置、使用等。有了这些知识积累，我们就可以利用Rserve来构建企业级的上线应用了。

5.2　Rserve的R语言客户端RSclient

问题

R原生程序怎么访问Rserve？

引言

RSclient是实现Rserve通信的R语言客户端程序，这种通信对于实际应用架构来说是很有帮助的，因为这不仅可以统一Rserve的接口，还可以从架构上实现R语言的跨虚拟机的分步式程序设计。

之前我们讲过，Rserve是基于TCP/IP协议的，支持R语言与其他语言通信基于C/S结构的网络通信服务器端程序。那么如何让R语言与Rserve服务器通信呢？RSclient就是Rserve客户端访问的R语言实现，通过RSclient可以让R语言访问Rserve服务器实例。这样，R语言本身用就有了分步式程序调用的基础了。

5.2.1　配置Rserve服务器

本节使用的系统环境是：

·Linux:Ubuntu 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

·Rserve:Rserve v1.7-1

注　Rserve同时支持Windows 7环境和Linux环境，由于Rserve主要用来做通信服务器，建议使用Linux环境。

启动Rserve服务器。

~ R CMD Rserve #

通过命令启动Rserve

~ ps -aux|grep Rserve

conan 28339 0.0 1.2 116292 25240 ? Ss 22:31 0:00 /usr/lib/R/bin/Rserve

~ netstat -ntlp|grep Rserve

tcp     0   0 0.0.0.0:6311       0.0.0.0:*       LISTEN        28339/Rserve

Rserve的环境是：

·IP：192.168.1.201，允许远程访问

·端口：6311

·登录认证：用户名：conan，密码：conan

·字符编码：utf-8

~ R CMD Rserve --RS-settings #

查看Rserve

服务器的配置

Rserve v1.7-1

config file: /etc/Rserv.conf

working root: /tmp/Rserv

port: 6311

local socket: [none, TCP/IP used]

authorization required: yes

plain text password: allowed

passwords file: [none]

allow I/O: yes

allow remote access: yes

control commands: yes

interactive: yes

max.input buffer size: 262144 kB

启动Rserve服务器后，接下来，我们使用RSclient访问Rserve服务器。

5.2.2　RSclient安装

由于RSclient和Rserve通信可以通过远程访问，所以我们使用Windows 7的客户端环境安装RSclient。RSclient的系统环境：

·Win764bit

·R:3.0.1x86_64-w64-mingw32/x64b4bit

RSclient的安装和加载。

~ R   #

启动R

程序

> install.packages("RSclient") #

安装RSclient

> library(RSclient)   #

加载RSclient

5.2.3　RSclient的API

RSclient的API分为两组，Rclient（旧版本）和RCC（新版本）。Rclient旧版本API函数名是大小写字母连写的，如下所示：

RSassign       RSattach       RSclose

RSconnect      RSdetach       RSeval       RSevalDetach

RShowDoc       RSiteSearch    RSlogin      RSserverEval

RSserverSource RSshutdown     RSclient

RCC新版本API函数名是以“.”分隔的，如下所示：

RS.assign          RS.authkey          RS.close           RS.collect

RS.connect         RS.eval             RS.eval.qap        RS.login

RS.oobCallbacks    RS.server.eval      RS.server.shutdown RS.server.source

RS.switch

本节主要介绍新版本API的使用，其中客户端操作函数如下。

·RS.connect:创建与Rserve的连接。

·RS.close:关闭与Rserve的连接。

·RS.login:登录认证。

·RS.authkey:设置认证时加密算法。

·RS.eval:在Rserve远程执行R语句。

·RS.eval.qap:远程执行R语句，使用Rserve QAP序列化对象代替本地对象。

·RS.collect:等待异步执行的eval的结果，一起返回。

·RS.assign:远程执行赋值。

·RS.switch:协议转换，从QAP1到TLS QAP1。

·RS.oobCallbacks:回调函数，通过OOB_SEND函数和OOB_MSG函数执行。

服务器管理函数（Rserve启动时，需要设置–RS-enable-control）如下。

·RS.server.eval:服务器控制函数，执行脚本。

·RS.server.shutdown:服务器控制函数，关闭服务器。

·RS.server.source:服务器控制函数，执行服务器端本地文件。

5.2.4　RSclient的使用

> library(RSclient)

> conn<-RS.connect(host="192.168.1.201") #

建立远程连接

> conn

Rserve QAP1 connection 0x000000000445cd60 (socket 308, queue length 0)

> RS.login(conn,"conan","conan",authkey=RS.authkey(conn))

#

登录Rserve

服务器，并完成认证检查

[1] TRUE

> RS.eval(conn,rnorm(5)) #

执行脚本

[1] -2.6762608 1.4435144 -0.4298395 -0.7046573 -1.4056073

> RS.assign(conn,"xx",99) #

设置变量

raw(0)

> RS.eval(conn,xx-55)

[1] 44

> RS.eval(conn,head(rnorm(10000000)),wait=TRUE) #

同步执行

[1] -4.20217390 0.22353317 -1.70256992 0.30053213 -0.01427486 -0.70522254

> RS.eval(conn,head(rnorm(10000000)),wait=FALSE)

#

异步执行，结果要通过RS.collect()

再取一次

NULL

> RS.collect(conn)

[1] -0.2814752 0.3215521 -1.0978825 -0.8534461 -0.2459560 -0.4804882

> RS.close(conn) #

关闭连接

NULL

> conn

Closed Rserve connection 0x000000000445cc80

5.2.5　两个客户端同时访问

客户端A的操作。

~ R

> library(RSclient)

> conn<-RS.connect(host="192.168.1.201")

> RS.login(conn,"conan","conan",authkey=RS.authkey(conn))

> RS.assign(conn,"A",1234)

> RS.eval(conn,A)

[1] 1234

> RS.eval(conn,getwd())

[1] "/tmp/Rserv/conn29039"

客户端B的操作。

~ R

> library(RSclient)

> conn<-RS.connect(host="192.168.1.201")

> RS.login(conn,"conan","conan",authkey=RS.authkey(conn))

> RS.assign(conn,"B",5678)

> RS.eval(conn,B)

[1] 5678

> RS.eval(conn,ls())

[1] "B"

> RS.eval(conn,getwd())

[1] "/tmp/Rserv/conn29040"

我们看到A、B两个客户端建立接口后，服务器端的Rserve会在两个单独的空间运行。因此，A、B两个客户端的访问是相互独立的。如果想让两个客户端通信，我们应该怎么去测试这个想法呢？答案是通过全局变量。在客户端A，设置Rserve服务器全局变量，代码如下。

> RS.server.eval(conn, "G<-999") :

command failed with status code 0x48: access denied

出现拒绝访问的错误，虽然我们已经打开了--RS-enable-control，但还是有错误，不知道是不是Rserve的bug。全局变量这种方式被证明是不行的。那么我们还可以通过中间存储的方式，间接地实现两个客户端的交互，把需要交互数据存储在MySQL和Redis中。

通过RSclient我们实现了R语言远程连接Rserve服务器。如果大家发散一下思维，就可以通过Rserve和RSclient构建一个纯R的分步式计算环境，再配合上其他的开源技术，R真的能达到线上应用的标准。

5.3　FastRWeb：跑在Web上的R程序

问题

R做的图怎么在Web上显示？

引言

一直以来R语言都被用在基于个人电脑的客户端程序上。我们习惯性地下载R安装包，在自己的电脑上面装好，写算法，运行。然后，以贴图或者文档的形式，发表自己的作品。R如果可以运行在Server端，把结果以Web的方式发布，这才是互联网的工作方式！FastRWeb提供一种方式，可以实现B/S结构的R应用。

5.3.1　FastRWeb介绍

FastRWeb是一个基础架构环境，可以让R脚本运行在任何WebServer上，用于展示数据和图形。用户通过URL地址与R脚本进行通信交互。FastRWeb可以快速地构建一个R的Web环境，FastRWeb底层依赖于CGI程序，也就是说，只要能支持CGI程序的WebServer，就可以运行FastRWeb。下面以R语言的WebServer库Rserve为例，部署FastRWeb，实现R的Web应用。

FastRWeb的架构如图5-1所示，从图中我们可以看出其架构原理：

1）浏览器通过HTPP请求访问Web服务器；2）Web服务器通过socket把请求发给Rserve服务器；3）Rserve调用FastRWeb环境，运行R的脚本，返回数据和图片；4）浏览器得到结果，并在Web上展示。

图5-1　FastRWeb架构图

5.3.2　FastRWeb安装

本节使用的系统环境是：

·Linux:Ubuntu 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

·IP:192.168.1.201

注　FastRWeb只支持Linux环境。

FastRWeb的安装过程如下。

~ R

> install.packages("FastRWeb") #

安装FastRWeb

包

由于FastRWeb依赖于Cairo库，Cairo安装会用到Linux本地库，请参考1.7节。下面安装Rserve，关于Rserve的使用介绍，请参考5.1节。

> install.packages("Rserve") #

安装Rserve

包

创建FastRWeb基于Rserve的运行环境。

~ cd /home/conan/R/x86_64-pc-linux-gnu-library/3.0/FastRWeb #

进入FastRWeb

的安装目录

~ sudo ./install.sh #

执行安装脚本

Done.

Please check files in /var/FastRWeb/code

If they match tour setup, you can start Rserve using

/var/FastRWeb/code/start

查看生成的目录/var/FastRWeb/code/。

~ cd /var/FastRWeb/code/

~ ls -l

-rw-r--r-- 1 conan conan 1210 Oct 29 16:07 README

-rw-r--r-- 1 conan conan 79 Oct 29 17:50 rserve.conf

-rw-r--r-- 1 conan conan 2169 Oct 29 17:51 rserve.R

-rwxr-xr-x 1 conan conan 457 Oct 29 17:35 start

其中README是帮助说明，rserve.conf是Rserve的启动参数，rserve.R是Rserve的启动脚本，start是启动命令。

修改Rserve启动的配置文件rserve.conf。

~ vi rserve.conf

http.port 8888

remote enable

source /var/FastRWeb/code/rserve.R

control enable

默认情况下，RServe提供socket通信接口，为了Web测试方便，我们改成用HTTP的通信接口。下面修改文件rserve.R，在文件上的最上面增加2行代码。

~ vi rserve.R

#

增加的内容

library(FastRWeb)

.http.request <- FastRWeb:::.http.request

这样我们就改好了，让http协议作为通信的接口。

5.3.3　FastRWeb使用

启动FastRWeb服务。

~ sudo ./start

R CMD Rserve --RS-conf /var/FastRWeb/code/rserve.conf --vanilla --no-save --RSenable-remote

R version 3.0.1 (2013-05-16) -- "Good Sport"

Copyright (C) 2013 The R Foundation for Statistical Computing

Platform: x86_64-pc-linux-gnu (64-bit)

Starting Rserve on conan

Loading packages...

XML: TRUE

Cairo: TRUE

Matrix: TRUE

FastRWeb: TRUE

Rserv started in daemon mode.

从启动日志，我们看出XML、Cairo、Matrix、FastRWeb、Rserv几个包都加载了，而且正常运行。查看系统进程和端口：

~ ps -aux|grep Rserve

conan    23739   0.0   1.4 120140 28916 ?         Ss    16:47   0:00 /usr/lib/R/bin/

Rserve --RS-conf /var/FastRWeb/code/rserve.conf --vanilla --no-save --RS-enable-remote

~ sudo netstat -nltp|grep Rserve

tcp      0    0 0.0.0.0:8888          0.0.0.0:*             LISTEN      25778/Rserve

tcp      0    0 0.0.0.0:6311          0.0.0.0:*             LISTEN      25778/Rserve

有2个端口被打开了，一个是Rseve的socket端口6311，一个是http端口8888。通过Web访问，用浏览器打开：http://192.168.1.201:8888/example1.png，如图5-2所示。

图5-2　rnorm(100)函数Web可视化图

图5-2的R代码实际对应的是文件/var/FastRWeb/web.R/example1.png.R。

~ vi /var/FastRWeb/web.R/example1.png.R

run <- function(...) {

p <- WebPlot(600, 600)

plot(rnorm(100), rnorm(100), pch = 19, col = 2)

p

}

修改文件/var/FastRWeb/web.R/example1.png.R，在浏览器中刷新页面，结果如图5-3所示。

~ vi /var/FastRWeb/web.R/example1.png.R

run <- function(...) {

p <- WebPlot(600, 600)

plot(rnorm(400), rnorm(400), pch = 20, col = 3)

p

}

图5-3　rnorm(400)函数Web可视化图

这样就实现了通过Web来执行R脚本，在Web上画图。在目录/var/FastRWeb/web.R/下面有几个例子，大家可以参考一下，然后改写自己的R语言脚本。

~ ls -l /var/FastRWeb/web.R

-rw-r--r-- 1 conan conan 790 Oct 29 16:07 common.R

-rw-r--r-- 1 conan conan 316 Oct 29 20:01 example1.png.R

-rw-r--r-- 1 conan conan 520 Oct 29 16:07 example2.R

-rw-r--r-- 1 conan conan 174 Oct 29 16:07 index.R

-rw-r--r-- 1 conan conan 215 Oct 29 16:07 info.R

-rw-r--r-- 1 conan conan 64 Oct 29 16:07 main.R

-rw-r--r-- 1 conan conan 167 Oct 29 16:07 README

-rw-r--r-- 1 conan conan 214 Oct 29 16:07 tmp.R

R脚本文件example2.R可以接受页面传参数。

根据FastRWeb的说明，FastRWeb可以通过CGI与任何WebServer通信，这样我们就可以把R脚本部署在PHP、Python、Ruby、Java等高级语言的服务器上面了。R-bloggers上有一篇文章，讲FastRWeb部署在基于Apache的XAMPP环境中（http://www.r-bloggers.com/setting-up-fastrweb-on-mac-os-x/）。感兴趣的同学，自行尝试吧！

5.4　R语言构建Websocket服务器

问题

R语言怎么实现Websocket？

引言

R语言从一门统计语言正向着工业化语言发展。不仅支持Web的可视化、Web的基本操作，还支持Websocket。我们的互联网应用程序不用再绕道Rserve，直接通过Websocket协议，就能实现与R语言的交互。R语言正在发生着技术革命，更先进，更便捷……

5.4.1　websockets介绍

Websocket协议是基于HTML5规范的在浏览器上实现客户端和服务器端通信的协议。Websocket主要有以下4点优势：1）显著降低网络开销；2）减少服务器的处理开销；3）简化Web服务器端推送数据的模型；4）简化服务器和客户端之间的耦合状态。

websockets包是R语言的一个Websocket接口的类库。通过websockets包，可以非常简单地使用R语言构建一个Websocket服务器实例。下面介绍websockets包的API。

·create_server:创建一个Websocket服务器实例，并绑定端口。

·daemonize:绑定Websocket服务器实例守护进程到R的控制台，不支持Windows。

·http_response:发送HTTP Response请求到socket。

·http_vars:解析HTTP GET/POST参数列表。

·service:注册Websocket实例的服务队列。

·set_callback:在Websocket实例中定义R函数。

·static_file_service:静态文件。

·static_text_service:静态文本。

·websocket:创建一个Websocket客户端实例。

·websocket_broadcast:向注册在同一个Websocket服务器实例的客户端发广播。

·websocket_close:关闭客户端连接。

·websocket_write:通过Websocket进行数据传输。

5.4.2　websockets安装

本节使用的系统环境是：

·Linux:Ubuntu Server 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

·IP:192.168.1.201

注　websockets只支持Linux环境。

websockets的安装过程如下：

~ R #

启动R

程序

> install.packages("websockets") #

安装websockets

> library(websockets) #

加载websockets

'websockets'R3.0.2

websockets库依赖于caTools库，caTools是一个工具集，请参考1.8节。

笔者在2014年4月整理时发现websockets包在2014年3月2日被移出了CRAN库，目前还不知道是什么原因。原文网页地址http://cran.r-project.org/web/packages/websockets/index.html。

Package 'websockets' was removed from the CRAN repository.

Formerly available versions can be obtained from the archive.

Archived on 2014-03-02 at the request of the maintainer.

这样我们在安装websockets包的时候，通过install.packages()的函数就会报错。

> install.packages("websockets")

Installing package into '/home/conan/R/x86_64-pc-linux-gnu-library/3.0'

(as 'lib' is unspecified)

警告信息：

package 'websockets' is not available (for R version 3.0.1)

我们需要下载安装包，手动安装。

~ wget http://cran.r-project.org/src/contrib/Archive/websockets/

websockets_1.1.7.tar.gz #

下载最新的websockets

包

~ R CMD INSTALL websockets_1.1.7.tar.gz #

在当前目录安装websockets

* installing to library '/home/conan/R/x86_64-pc-linux-gnu-library/3.0'

ERROR: dependencies 'caTools', 'digest' are not available for package 'websockets'

* removing '/home/conan/R/x86_64-pc-linux-gnu-library/3.0/websockets'

安装过程中出现错误，提示为缺少依赖包caTools、digest，所以我们需要先安装这两个依赖包。

~ R #

启动R

程序

> install.packages("caTools") #

安装依赖包

> install.packages("digest")

~ R CMD INSTALL websockets_1.1.7.tar.gz #

回到命令行，再次安装websockets

包，安装成功

* installing to library '/home/conan/R/x86_64-pc-linux-gnu-library/3.0'

* installing *source* package 'websockets' ...

**

成功将'websockets'

程序包解包并MD5

和检查

** libs

gcc -std=gnu99 -I/usr/share/R/include -DNDEBUG -DLWS_NO_FORK -fpic -O3 -pipe

-g -c libsock.c -o libsock.o

gcc -std=gnu99 -shared -o websockets.so libsock.o -L/usr/lib/R/lib -lR

installing to /home/conan/R/x86_64-pc-linux-gnu-library/3.0/websockets/libs

** R

** demo

** inst

** preparing package for lazy loading

** help

*** installing help indices

** building package indices

** installing vignettes

'websockets.Rnw'

** testing if installed package can be loaded

* DONE (websockets)

~ R #

启动R

程序

> library(websockets) #

加载websockets

包

通过手动的方式，我们就安装好了websockets包。

5.4.3　快速启动websockets服务器demo

websockets包提供了一个demo，通过demo（websockets）函数可以直接启动一个简单的Websocket服务器。

> library(websockets)

'websockets'R3.0.2

> demo(websockets) #

启动demo

查看服务器端进程：

~ netstat -nltp|grep r

tcp     0    0 0.0.0.0:7681      0.0.0.0:*      LISTEN       2231/rsession

在浏览器中打开网页http://192.168.1.201:7681，我们就能看到websockets实现的demo应用，如图5-4所示。注意，浏览器必须支持HTML5，建议使用Chrome浏览器。

图5-4　websockets的demo应用

输出服务器端的日志：

Websocket client socket 20 has closed.

Websocket client socket 8 has been established.

Websocket client socket 21 has closed.

5.4.4　R语言创建Websocket服务器实例

接下来，我们创造一个自定义的Websocket服务器应用。

> library(websockets) #

加载类库

> text = "<html><body><h1>Hello world</h1></body></html>" #

浏览器的HTTP

输出

> w = create_server(port=7681,webpage=static_text_service(text)) #

创建服务实例

> recv = function(DATA, WS, ...){ #

监听receive

+ cat("Receive callback\n")

+ D = ""

+ if(is.raw(DATA)){D = rawToChar(DATA)}

+ cat("Callback:You sent",D,"\n")

+ websocket_write(DATA=paste("You sent",D,"\n",collapse=" "),WS=WS)

+}

> set_callback('receive',recv,w)

> cl = function(WS){ #

监听closed

+ cat("Websocket client socket ",WS$socket," has closed.\n")

>}

> set_callback('closed',cl,w)

> es = function(WS){ #

建立连接

+ cat("Websocket client socket ",WS$socket," has been established.\n")

+}

> set_callback('established',es,w)

> while(TRUE) service(w) #

对所有的连接进行监听

通过上面的代码，我们就创建好了Websocket应用的服务器端部分。

5.4.5　R语言创建Websocket客户端连接

接下来，我们再用R语言创建Websocket应用的客户端部分，用Websocket的服务器进行通信。首先在Linux环境中按如下方式新建一个文件client.r：

~ vi client.r

library(websockets) #

加载类库

client = websocket("ws://192.168.1.201",port=7681) #

创建客户端实例

rece<-function(DATA, WS, HEADER) { D='' if(is.raw(DATA)){ cat("raw data")

D = rawToChar(DATA) } cat("==>",D,"\n")} #

监听receive

set_callback("receive",rece, client)

websocket_write("2222", client) #

向服务器发请求

service(client) #

输出服务器的返回值

websocket_close(client) #

关闭连接

运行客户端的程序：

> library(websockets)

> client = websocket("ws://192.168.1.201",port=7681)

> rece<-function(DATA, WS, HEADER) { + D='' + if(is.raw(DATA)){ +

cat("raw data") +     D = rawToChar(DATA) + } + cat("==>",D,"\n")}

> set_callback("receive",rece, client)

> websocket_write("2222", client)

[1] 1

> service(client)

raw data==> You sent 2222

> websocket_close(client)

Client socket 3 was closed.

从程序运行的结果看出已经实现了客户端与服务器端的通信过程。

5.4.6　用浏览器HTML5原生API客户端连接

接下来，我们在Chrome浏览器中编写JavaScript代码，调用原生HTML5的API访问Websocket服务器。在Chrome浏览器中打开网页：http://192.168.1.201:7681，按F12打开“开发者工具”再切换到JavaScript控制台“Console”，在Console中输入JavaScript程序代码，如图5-5所示。

原生的HTML5程序：

var ws = new WebSocket("ws://192.168.1.201:7681");

ws.onopen = function(){

console.log("connecting");

};

ws.onmessage = function(message){

console.log(message.data);

console.log(message);

};

function postToServer(msg){

ws.send(msg);

}

function closeConnect(){

ws.close();

console.log("closed");

}

postToServer('browser');

closeConnect();

图5-5　浏览器原生JavaScript访问Websocket服务器通信

这样我们就完成了R语言构建的Websocket服务器测试，又给R语言与其他语言通信打开了一条便利的通道。

第三部分　数据库和大数据

■第6章　数据库和NoSQL

■第7章　RHadoop

第6章　数据库和NoSQL

本章主要介绍R语言访问数据库的5个工具包，帮助读者实现用R语言连接MySQL、MongoDB、Redis、Cassandra和Hive的操作。本章最后一节介绍了一个基于Hive的金融大数据的案例。

6.1　RMySQL数据库编程指南

问题

R语言如何访问MySQL？

引言

MySQL是一款最常用的开源数据库软件，安装简单（关于MySQL的安装和配置，请参考附录B），运行稳定，非常适用于中小型的数据存储。R作为数据分析的工具，当然要支持数据库驱动接口。让R和MySQL配合在一起，所能爆发出的能量是巨大的。

RMySQL是一个R语言程序包，提供了访问MySQL数据库的R语言接口程序，RMySQL的依赖于DBI包。RMySQL不仅提供了基本的数据库访问、SQL查询，还封装了一些方法，比如读整表、分页、data.frame快速插入等功能。掌握好RMySQL，数据库编辑将得心应手！

由于操作系统的原因，Windows和Linux有不一样的字符集，不一样的运行时环境。所以，本节将分别介绍如何在Linux和Windows上面安装和使用RMySQL。

6.1.1　RMySQL在Linux下安装

Linux系统环境：

·Linux:Ubuntu 12.04.2LTS 64bit server

·Linux字符集:en_US.UTF-8

·R:3.0.1,x86_64-pc-linux-gnu(64-bit)

·MySQL:Ver 14.14Distrib 5.5.2964bit server

·MySQL字符集:utf8

1.查看Linux下的系统环境

~ uname -a # Linux

内核

Linux conan 3.5.0-23-generic #35~precise1-Ubuntu SMP Fri Jan 25 17:13:26 UTC 2013

x86_64 x86_64 x86_64 GNU/Linux

~ cat /etc/issue  # Linux

版本

Ubuntu 12.04.2 LTS \n \l

~ locale    # Linux

系统字符集设置LANG=en_US.UTF-8

LANGUAGE=

LC_CTYPE="en_US.UTF-8"

LC_NUMERIC="en_US.UTF-8"

LC_TIME="en_US.UTF-8"

LC_COLLATE="en_US.UTF-8"

LC_MONETARY="en_US.UTF-8"

LC_MESSAGES="en_US.UTF-8"

LC_PAPER="en_US.UTF-8"

LC_NAME="en_US.UTF-8"

LC_ADDRESS="en_US.UTF-8"

LC_TELEPHONE="en_US.UTF-8"

LC_MEASUREMENT="en_US.UTF-8"

LC_IDENTIFICATION="en_US.UTF-8"

LC_ALL=en_US.UTF-8

~ mysql --version    #

查看MySQL

数据库服务器版本

mysql Ver 14.14 Distrib 5.5.29, for debian-linux-gnu (x86_64) using readline 6.2

mysql> show variables like '%char%'; #

查看MySQL

字符集

+--------------------------+----------------------------+

| Variable_name            | Value                      |

+--------------------------+----------------------------+

| character_set_client     |                       utf8 |

| character_set_connection | utf8                       |

| character_set_database   | utf8                       |

| character_set_filesystem | binary                     |

| character_set_results    | utf8                       |

| character_set_server     | utf8                       |

| character_set_system     | utf8                       |

| character_sets_dir       | /usr/share/mysql/charsets/ |

+--------------------------+----------------------------+

8 rows in set (0.00 sec)

2.在R环境中安装RMySQL

~ R   #

启动R

程序

> install.packages('RMySQL') #

安装RMySQL

#

省略部分输出

Configuration error:

could not find the MySQL installation include and/or library

directories. Manually specify the location of the MySQL

libraries and the header files and re-run R CMD INSTALL.

INSTRUCTIONS:

1. Define and export the 2 shell variables PKG_CPPFLAGS and

PKG_LIBS to include the directory for header files (*.h)

and libraries, for example (using Bourne shell syntax):

export PKG_CPPFLAGS="-I"

export PKG_LIBS="-L -lmysqlclient"

Re-run the R INSTALL command:

R CMD INSTALL RMySQL_.tar.gz

2. Alternatively, you may pass the configure arguments

--with-mysql-dir= (distribution directory)

or

--with-mysql-inc= (where MySQL header files reside)

--with-mysql-lib= (where MySQL libraries reside)

in the call to R INSTALL --configure-args='...'

R CMD INSTALL --configure-args='--with-mysql-dir=DIR' RMySQL_.tar.gz

ERROR: configuration failed for package 'RMySQL'

* removing '/home/conan/R/x86_64-pc-linux-gnu-library/3.0/RMySQL'

The downloaded source packages are in

'/tmp/Rtmpu0Gn88/downloaded_packages'

Warning message:

In install.packages("RMySQL") :

installation of package 'RMySQL' had non-zero exit status

安装报错，提示我们需要增加MySQL安装目录的配置参数--with-mysql-dir。下面我们解决安装出错的问题。

~ sudo apt-get install libdbd-mysql libmysqlclient-dev #

安装MySQL

类库

~ whereis mysql #

找到MySQL

的安装目录

mysql: /usr/bin/mysql /etc/mysql /usr/lib/mysql /usr/bin/X11/mysql /usr/share/

mysql /usr/share/man/man1/mysql.1.gz

~ ls /tmp/Rtmpu0Gn88/downloaded_packages #

找到刚刚下载的文件，RMySQL_.tar.gz

DBI_0.2-7.tar.gz RMySQL_0.9-3.tar.gz

~ R CMD INSTALL --configure-args='--with-mysql-dir=/usr/lib/mysql' /tmp/

Rtmpu0Gn88/downloaded_packages/RMySQL_0.9-3.tar.gz #

通过命令重新安装RMySQL

#

省略部分输出

installing to /home/conan/R/x86_64-pc-linux-gnu-library/3.0/RMySQL/libs

** R

** inst

** preparing package for lazy loading

Creating a generic function for 'format' from package 'base' in package 'RMySQL'

Creating a generic function for 'print' from package 'base' in package 'RMySQL'

** help

*** installing help indices

** building package indices

** installing vignettes

** testing if installed package can be loaded

* DONE (RMySQL)

这次RMySQL安装成功。

3.在MySQL中建库建表

~ mysql -uroot

–p #

通过MySQL

命令行客户端工具，登录本地MySQL

mysql> create database rmysql; #

创建一个数据库叫rmysql

Query OK, 1 row affected (0.00 sec)

mysql> grant all on rmysql.* to rmysql@'%' identified by 'rmysql';

#

新建一个用户，用户名rmysql

，密码rmysql

，并授权这个用户可以远程操作rmysql

库

Query OK, 0 rows affected (0.00 sec)

mysql> grant all on rmysql.* to rmysql@localhost identified by 'rmysql';

#

授权rmysql

用户，可以本地操作rmysql

库

Query OK, 0 rows affected (0.00 sec)

mysql> use rmysql #

切换到rmysql

库

Database changed

mysql> CREATE TABLE t_user( #

创建一个表 t_user

-> id INT PRIMARY KEY AUTO_INCREMENT,

-> user varchar(12) NOT NULL UNIQUE

-> )ENGINE=INNODB DEFAULT CHARSET=utf8;

Query OK, 0 rows affected (0.07 sec)

mysql> INSERT INTO t_user(user) values('A1'),('AB'),('fens.me'); #

插入3

条数据

Query OK, 3 rows affected (0.04 sec)

Records: 3 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM t_user; #

查询数据

+----+---------+

| id | user    |

+----+---------+

| 1  | A1      |

| 2  | AB      |

| 3  | fens.me |

+----+---------+

3 rows in set (0.00 sec)

4.通过R程序读MySQL数据库数据

~ R    #

启动R

程序

> library(RMySQL) #

加载RMySQL

Loading required package: DBI

> conn <- dbConnect(MySQL(), dbname = "rmysql", username="rmysql",

password="rmysql") #

建立连接

> users = dbGetQuery(conn, "SELECT * FROM t_user") #

执行SQL

> users   #

查看数据

id    user

1 1       A1

2 2       AB

3 3  fens.me

> dbDisconnect(conn) #

断开连接

[1] TRUE

好了，我们已经实现了在Linux Ubuntu下面R和MySQL的连接。

6.1.2　RMySQL在Windows 7下安装

Windows系统环境：

·Win 764bit

·Win字符集:gbk,utf8

·R:3.0.1,x86_64-w64-mingw32/x64(64-bit)

·MySQL:mysql Ver 14.14Distrib 5.6.11,for Win64(x86_64)

1.查看Windows 7下的系统环境

~ R --version   #

查看R

版本

R version 3.0.1 (2013-05-16) -- "Good Sport"

Copyright (C) 2013 The R Foundation for Statistical Computing

Platform: x86_64-w64-mingw32/x64 (64-bit)

~ mysql --version # MySQL

版本

mysql Ver 14.14 Distrib 5.6.11, for Win64 (x86_64)

mysql> show variables like '%char%'; # MySQL

字符集

+--------------------------+------------------------------------+

| Variable_name            | Value                              |

+--------------------------+------------------------------------+

| character_set_client     | gbk                                |

| character_set_connection | gbk                                |

| character_set_database   | utf8                               |

| character_set_filesystem | binary                             |

| character_set_results    | gbk                                |

| character_set_server     | utf8                               |

| character_set_system     | utf8                               |

| character_sets_dir       | D:\toolkit\mysql56\share\charsets\ |

+--------------------------+------------------------------------+

8 rows in set (0.07 sec)

2.在R环境中安装RMySQL

~ R    #

启动R

程序

> install.packages('RMySQl')    #

安装RMySQL

package 'RMySQl' is not available (for R version 3.0.1)

又遇到错误，提示没有对应的RMySQL安装版本。由于RMySQL没有提供Windows版本的发行包，所以我们必须在Windows中手动编译RMySQL的包后，再进行安装。

> install.packages("RMySQL", type="source") #

下载RMySQL

源代码包

URLhttp://cran.dataguru.cn/src/contrib/RMySQL_0.9-3.tar.gz'

Content type 'application/x-gzip' length 165363 bytes (161 Kb)

URL

downloaded 161 Kb

找到源代码包RMySQL_0.9-3.tar.gz。

~ dir C:\Users\Administrator\AppData\Local\Temp\RtmpsfqQjK\downloaded_packages

2013-09-24 13:16 165,363 RMySQL_0.9-3.tar.gz

通过源代码包安装。

~ D:\workspace\R\mysql>R CMD INSTALL C:\Users\Administrator\AppData\Local\Temp\

RtmpsfqQjK\downloaded_packages\RMySQL_0.9-3.tar.gz

* installing to library 'C:/Program Files/R/R-3.0.1/library'

* installing *source* package 'RMySQL' ...

** 'RMySQL'MD5

checking for $MYSQL_HOME... not found... searching registry...

#

省略部分输出

readRegistry("SOFTWARE\\MySQL AB", hive = "HLM", maxdepth = 2) :

Registry key 'SOFTWARE\MySQL AB' not found

ERROR: configuration failed for package 'RMySQL'

* removing 'C:/Program Files/R/R-3.0.1/library/RMySQL'

又报错了，我们还需要设置MYSQL_HOME的环境变量。

~ set MYSQL_HOME=D:\toolkit\mysql56

注　MYSQL_HOME建议设置在系统环境变量中。

再一次安装RMySQL。

~D:\workspace\R\mysql>R CMD INSTALL C:\Users\Administrator\AppData\Local\Temp\

RtmpsfqQjK\downloaded_packages\RMySQL_0.9-3.tar.gz

#

省略部分输出

gcc.exe: error: D:\toolkit\mysql56/bin/libmySQL.dll: No such file or directory

ERROR: compilation failed for package 'RMySQL'

* removing 'C:/Program Files/R/R-3.0.1/library/RMySQL'

错误不断，这次提示为没有找到动态链接库文件D:\toolkit\mysql56/bin/libmySQL.dll。

~cp D:\toolkit\mysql56\lib\libmysql.dll D:\toolkit\mysql56\bin\

~mv D:\toolkit\mysql56\bin\libmysql.dll D:\toolkit\mysql56\bin\libmySQL.dll

#

复制动态链接库libmySQL.dll

再一次安装RMySQL。

~ D:\workspace\R\mysql>R CMD INSTALL C:\Users\Administrator\AppData\Local\Temp\

RtmpsfqQjK\downloaded_packages\RMySQL_0.9-3.tar.gz

#

省略部分输出

installing to C:/Program Files/R/R-3.0.1/library/RMySQL/libs/x64

** R

** inst

** preparing package for lazy loading

Creating a generic function for 'format' from package 'base' in package 'RMySQL'

Creating a generic function for 'print' from package 'base' in package 'RMySQL'

** help

*** installing help indices

** building package indices

** installing vignettes

** testing if installed package can be loaded

MYSQL_HOME defined as D:\toolkit\mysql56

* DONE (RMySQL)

终于安装成功了，值得庆祝！

3.在MySQL中建库建表

操作过程与在Linux中操作过程一致。

~ mysql -uroot -p

mysql> create database rmysql;

Query OK, 1 row affected (0.04 sec)

mysql> grant all on rmysql.* to rmysql@'%' identified by 'rmysql';

Query OK, 0 rows affected (0.00 sec)

mysql> grant all on rmysql.* to rmysql@localhost identified by 'rmysql';

Query OK, 0 rows affected (0.00 sec)

mysql> use rmysql

Database changed

mysql> CREATE TABLE t_user(

-> id INT PRIMARY KEY AUTO_INCREMENT,

-> user varchar(12) NOT NULL UNIQUE

-> )ENGINE=INNODB DEFAULT CHARSET=utf8;

Query OK, 0 rows affected (1.01 sec)

mysql>

mysql> INSERT INTO t_user(user) values('A1'),('AB'),('fens.me');

Query OK, 3 rows affected (0.05 sec)

Records: 3 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM t_user;

+----+---------+

| id | user    |

+----+---------+

| 1  | A1      |

| 2  | AB      |

| 3 | fens.me  |

+----+---------+

3 rows in set (0.03 sec)

4.通过R程序读MySQL数据库数据

如果刚才没有把MYSQL_HOME的变量写到环境变量中，每次在启动R之前都要设置这个变量。

~ set MYSQL_HOME=D:\toolkit\mysql56 #

在当前环境中设置环境变量

~ R    #

启动R

程序

> library(RMySQL)     #

加载RMySQL

DBI

MYSQL_HOME defined as D:\toolkit\mysql56

> conn <- dbConnect(MySQL(), dbname = "rmysql", username="rmysql",

password="rmysql")    #

建立连接

> users = dbGetQuery(conn, "SELECT * FROM t_user") #

执行SQL

> users

id     user

1   1      A1

2   2      AB

3   3 fens.me

> dbDisconnect(conn) #

断开连接

[1] TRUE

好了，我们又实现了在Windows 7下的R和MySQL的连接。

6.1.3　RMySQL函数使用

环境都安装好了，接下来我们就具体使用RMySQL包了。本节主要介绍RMySQL辅助操作、数据库操作以及Windows的字符集设置。

1.RMySQL辅助函数

> conn <- dbConnect(MySQL(), dbname = "rmysql", username="rmysql", #

建立本地连接

password="rmysql",client.flag=CLIENT_MULTI_STATEMENTS)

> conn <- dbConnect(MySQL(), dbname = "rmysql", username="rmysql", #

建立远程连接

password="rmysql",host="192.168.1.201",port=3306)

> dbDisconnect(conn) #

关闭连接

> dbListTables(conn) #

查看数据库中所有的表

[1] "t_user"

> dbListFields(conn, "t_user") #

查看表的字段

[1] "id" "user"

> summary(MySQL(), verbose = TRUE) #

查询MySQL

信息

<MySQLDriver:(23864)>

Driver name: MySQL

Max connections: 16

Conn. processed: 3

Default records per fetch: 500

DBI API version:

> summary(conn, verbose = TRUE) # MySQL

连接实例信息

<MySQLConnection:(23864,2)>

User: root

Host: localhost

Dbname: rmysql

Connection type: localhost via TCP/IP

MySQL server version: 5.6.11

MySQL client version: 5.6.11

MySQL protocol version: 10

MySQL server thread id: 35

No resultSet available

> dbListConnections(MySQL()) # MySQL

连接信息

[[1]]

<MySQLConnection:(23864,2)>

2.RMySQL数据库操作

> t_demo<-data.frame( #

建表并插入数据

a=seq(1:10),

b=letters[1:10],

c=rnorm(10)

)

> dbWriteTable(conn, "t_demo", t_demo)

> dbReadTable(conn, "t_demo") #

获得整个表数据

a b           c

1   1 a  0.98868164

2   2 b -0.66935770

3   3 c  0.27703638

4   4 d  1.36137156

5   5 e -0.70291017

6   6 f  1.61235088

7   7 g  0.17616068

8   8 h  0.29700017

9   9 i  0.19032719

10 10 j -0.06222173

> dbWriteTable(conn, "t_demo", t_demo, append=TRUE) #

插入新数据

> dbReadTable(conn, "t_demo")

row_names a b            c

1          1 1 a   0.98868164

2          2 2 b  -0.66935770

3          3 3 c   0.27703638

4          4 4 d   1.36137156

5          5 5 e  -0.70291017

6          6 6 f   1.61235088

7          7 7 g   0.17616068

8          8 8 h   0.29700017

9          9 9 i   0.19032719

10       10 10 j  -0.06222173

11         1 1 a   0.98868164

12         2 2 b  -0.66935770

13         3 3 c   0.27703638

14         4 4 d   1.36137156

15         5 5 e  -0.70291017

16         6 6 f   1.61235088

17         7 7 g   0.17616068

18         8 8 h   0.29700017

19         9 9 i   0.19032719

20       10 10 j  -0.06222173

> dbWriteTable(conn, "t_demo", t_demo, overwrite=TRUE) #

覆盖原表数据

> d0 <- dbGetQuery(conn, "SELECT * FROM t_demo where c>0") #

查询数据

> class(d0)

[1] "data.frame"

> d0

row_names a b         c

1          1 1 a 0.9886816

2          3 3 c 0.2770364

3          4 4 d 1.3613716

4          6 6 f 1.6123509

5          7 7 g 0.1761607

6          8 8 h 0.2970002

7          9 9 i 0.1903272

> rs <- dbSendQuery(conn, "SELECT * FROM t_demo where c>0")

#

执行SQL

脚本查询，并分页

> class(rs)

[1] "MySQLResult"

attr(,"package")

[1] "RMySQL"

> mysqlCloseResult(rs)

[1] TRUE

> d1 <- fetch(rs, n = 3)

> d1

row_names a b         c

1         1 1 a 0.9886816

2         3 3 c 0.2770364

3         4 4 d 1.3613716

> summary(rs, verbose = TRUE) #

查看集统计信息

row_names               a                b                 c

Length:7             Min. :1.000     Length:7           Min. :0.1762

Class :character     1st Qu.:3.500   Class :character   1st Qu.:0.2337

Mode :character      Median :6.000   Mode :character    Median :0.2970

Mean :5.429                        Mean :0.7004

3rd Qu.:7.500                      3rd Qu.:1.1750

Max. :9.000                        Max. :1.6124

> dbWriteTable(conn, "t_demo", t_demo,row.names=FALSE,overwrite=TRUE)

#

不插入row.names

字段

> dbGetQuery(conn, "SELECT * FROM t_demo where c>0")

a b         c

1 1 a 0.9886816

2 3 c 0.2770364

3 4 d 1.3613716

4 6 f 1.6123509

5 7 g 0.1761607

6 8 h 0.2970002

7 9 i 0.1903272

> if(dbExistsTable(conn,'t_demo')){ #

删除表

+     dbRemoveTable(conn, "t_demo")

+ }

[1] TRUE

执行SQL

语句dbSendQuery

。

> query<-dbSendQuery(conn, "show tables")

> data <- fetch(query, n = -1)

> data

Tables_in_rmysql

1           t_demo

2           t_user

> mysqlCloseResult(query)

[1] TRUE

特别提示，尽量避免用dbWriteTable()函数，因为它会删除你原来定义的表结构，创建新的表结构再插入数据！

3.Windows的字符集设置

在Windows 7中，向MySQL数据库中插入中文时，由于字符编码是GBK，所以会出现中文乱码。下面通过MySQL命令行客户端插入中文。

mysql> INSERT INTO t_user(user) values('

小朋友'),('

你好'),('

正确了'); #

插入中文数据

Query OK, 3 rows affected (0.07 sec)

Records: 3 Duplicates: 0 Warnings: 0

mysql> select * from t_user; #

查询数据，中文显示正确

+----+---------+

| id | user    |

+----+---------+

| 1  | A1      |

| 2  | AB      |

| 3  | fens.me |

| 5  |

你好    |

| 4  |

小朋友  |

| 6  |

正确了  |

+----+---------+

6 rows in set (0.07 sec)

通过RMySQL查询。

> dbGetQuery(conn, "SELECT * FROM t_user") #

查询数据，中文显示为乱码

id    user

1  1      A1

2  2      AB

3  3 fens.me

4  5      ??

5  4     ???

6  6     ???

在R的API中，设置GBK字符集。

> dbDisconnect(conn)

> conn <- dbConnect(MySQL(), dbname = "rmysql", username="root",

password="",client.flag=CLIENT_MULTI_STATEMENTS)

> dbSendQuery(conn,'SET NAMES gbk') #

设置GBK

字符集

> query<-dbSendQuery(conn, "SELECT * FROM t_user") #

查询数据

> data <- fetch(query, n = -1)

> mysqlCloseResult(query)

[1] TRUE

> data   #

中文显示正确

id    user

1  1      A1

2  2      AB

3  3 fens.me

4  5

你好

5  4

小朋友

6  6

正确了

至此我们在Windows 7下面修正字符编码的问题。

6.1.4　RMySQL案例实践

接下来，我们再做一个远程数据库连接的案例。在Windows 7的环境使用RMySQL客户端连接远程Linux上的MySQL数据库服务器。

1.在远程数据库创建表

通过SQL语句，先在MySQL数据库建一张新表t_blog，主键索引，唯一键索引。下面是建表语句。

id INT PRIMARY KEY AUTO_INCREMENT,

title varchar(12) NOT NULL UNIQUE,

author varchar(12) NOT NULL,

length int NOT NULL,

create_date timestamp NOT NULL DEFAULT now()

)ENGINE=INNODB DEFAULT CHARSET=UTF8;

mysql> desc t_blog; #

查看表结构

+-------------+-------------+------+-----+-------------------+----------------+

| Field       | Type        | Null | Key | Default           | Extra          |

+-------------+-------------+------+-----+-------------------+----------------+

| id          | int(11)     | NO   | PRI | NULL              | auto_increment |

| title       | varchar(12) | NO   | UNI | NULL              |                |

| author      | varchar(12) | NO   |     | NULL              |                |

| length      | int(11)     | NO   |     | NULL              |                |

| create_date | timestamp   | NO   |     | CURRENT_TIMESTAMP |                |

+-------------+-------------+------+-----+-------------------+----------------+

5 rows in set (0.00 sec)

mysql> show indexes from t_blog; #

查看表的索引

+--------+------------+----------+--------------+-------------+-----------+-----

--------+----------+--------+------+------------+---------+---------------+

| Table  | Non_unique | Key_name | Seq_in_index | Column_name | Collation |

Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |

+--------+------------+----------+--------------+-------------+-----------+-----

--------+----------+--------+------+------------+---------+---------------+

| t_blog | 0          | PRIMARY  | 1            | id          | A         |

3 |     NULL | NULL   |      | BTREE      |          |               |

| t_blog | 0          | title    | 1            | title       | A         |

3 |     NULL | NULL   |      | BTREE      |         |                |

+--------+------------+----------+--------------+-------------+-----------+-----

--------+----------+--------+------+------------+---------+---------------+

2 rows in set (0.00 sec)

mysql> INSERT INTO t_blog(title,author,length) values('

你好，第一篇','Conan',20),

('RMySQL

数据库编程','Conan',99),('R

的极客理想系列文章','Conan',15);#

插入数据

mysql> select * from t_blog; #

查询表

+----+------------------------------+--------+--------+---------------------+

| id | title                        | author | length | create_date         |

+----+------------------------------+--------+--------+---------------------+

| 1  |

你好，第一篇                 | Conan  | 20     | 2013-08-15 00:13:13 |

| 2  | RMySQL

数据库编程            | Conan  | 99     | 2013-08-15 00:13:13 |

| 3  | R

的极客理想系列文章 | Conan | 15     | 2013-08-15 00:13:13          |

+----+------------------------------+--------+--------+---------------------+

3 rows in set (0.00 sec)

2.RMySQL访问远程MySQL

用RMySQL远程访问数据库，并插入数据，包括中文字段，再取出数据。

> library(RMySQL)

> conn <- dbConnect(MySQL(), dbname = "rmysql", username="rmysql", password="rmy

sql",host="192.168.1.201",port=3306) #

建立远程连接

> dbSendQuery(conn,'SET NAMES gbk') #

设置GBK

字符编码

> dbSendQuery(conn,"INSERT INTO t_blog(title,author,length) values('R

插入的新文章','Conan',50)"); #

执行SQL

，插入中文数据

> query<-dbSendQuery(conn, "SELECT * FROM t_blog") #

查询数据

Warning message:

In mysqlExecStatement(conn, statement, ...) :

RS-DBI driver warning: (unrecognized MySQL field type 7 in colmun 4 imported as character)

> data <- fetch(query, n = -1) #

查询数据

> mysqlCloseResult(query)

[1] TRUE

> print(data) #

中文显示正常

id            title     author    length            create_date

1  1

你好，第一篇      Conan        20    2013-08-15 00:13:13

2  2  RMySQL

数据库编程    Conan        99    2013-08-15 00:13:13

3  3 R

的极客理想系列文章   Conan        15    2013-08-15 00:13:13

4  4      R

插入的新文章    Conan        50    2013-08-15 00:29:45

>

> dbDisconnect(conn)

[1] TRUE

掌握RMySQL的各种使用技巧并且理解原理，就能少犯错误，提高工作效率！

6.2　R利剑NoSQL 之MongoDB

问题

R如何连接MongoDB？

引言

MongoDB作为一种文档型的NoSQL数据库，使用起来非常灵活，它回避了关系型数据库前期的复杂数据库设计。MongoDB存储基于JSON格式，同时用JavaScript作为数据库操作语言，给了使用者无限想象的空间，可以通过编程在MongoDB服务器中解决非常复杂的条件查询的问题。本节将介绍通过rmongodb包让R程序轻松连接MongoDB。

6.2.1　MongoDB环境准备

首先是环境准备，这里我选择了Linux Ubuntu操作系统，大家可以根据自己的使用习惯选择顺手的Linux。

·Linux:Ubuntu 12.04.2LTS 64bit server

·MongoDB:v2.4.9

·IP:192.168.1.199

关于MongoDB的安装和配置，请参考附录D。下面查看MongoDB服务器环境，使用/etc/init.d/mongodb命令启动MongoDB，默认端口:port=27017。

~ sudo /etc/init.d/mongodb start     #

启动mongodb

Rather than invoking init scripts through /etc/init.d, use the service(8)

utility, e.g. service mongodb start

Since the script you are attempting to invoke has been converted to an

Upstart job, you may also use the start(8) utility, e.g. start mongodb

mongodb start/running, process 1878

~ ps -aux|grep mongo #

查看系统进程

mongodb 1878 3.3 0.4 348168 37488 ? Ssl 10:19 0:01 /usr/bin/mongod

--config /etc/mongodb.conf

~ cat /var/log/mongodb/mongodb.log #

查看启动日志

Mon Mar 31 10:19:28.764 [initandlisten] MongoDB starting : pid=1878 port=27017

dbpath=/var/lib/mongodb 64-bit host=rbook

Mon Mar 31 10:19:28.764 [initandlisten] db version v2.4.9

Mon Mar 31 10:19:28.764 [initandlisten] git version: 52fe0d21959e32a5bdbecdc6205

7db386e4e029c

Mon Mar 31 10:19:28.764 [initandlisten] build info: Linux ip-10-2-29-40 2.6.21.7-

2.ec2.v1.2.fc8xen #1 SMP Fri Nov 20 17:48:28 EST 2009 x86_64 BOOST_LIB_VERSION=1_49

Mon Mar 31 10:19:28.764 [initandlisten] allocator: tcmalloc

Mon Mar 31 10:19:28.764 [initandlisten] options: { config: "/etc/mongodb.conf",

dbpath: "/var/lib/mongodb", logappend: "true", logpath: "/var/log/mongodb/

mongodb.log" }

Mon Mar 31 10:19:28.853 [initandlisten] journal dir=/var/lib/mongodb/journal

Mon Mar 31 10:19:28.853 [initandlisten] recover : no journal files present, no

recovery needed

Mon Mar 31 10:19:28.872 [initandlisten] waiting for connections on port 27017

Mon Mar 31 10:19:28.872 [websvr] admin web console waiting for connections on

port 28017

MongoDB运行时配置：

·进程号：pid=1878

·端口：port=27017

·数据文件目录：dbpath=/var/lib/mongodb

·主机名：host=rbook

·IP：192.168.1.199

使用MongoDB的命令行客户端程序mongo打开Mongo Shell。Mongo Shell的简单操作主要有：查看数据库、切换数据库、查看数据集。

~ mongo    #

打开mongo shell

MongoDB shell version: 2.0.6

connecting to: test

> show dbs #

进入mongo Shell,

列表显示数据库

db      0.0625GB

feed    0.0625GB

foobar  0.0625GB

local   (empty)

> use foobar #

切换数据库

switched to db foobar

> show collections #

列表显示数据集

blog

system.indexes

接下来，我们通过使用R语言的MongoDB客户端rmongodb远程访问MongoDB服务器进行测试。

6.2.2　rmongodb函数库

rmongodb是R语言访问MongoDB数据库的客户端通信接口程序，rmongodb提供了153个函数，对应MongoDB的各种操作。比起其他NoSQL数据库程序包来说，这个项目真是工程浩大啊。虽然函数很多，但是用法都是比较简单的，对R语言支持足够灵活，代码也比较简洁。我们不一一列出这153个rmongodb函数，仅挑选一些常用的函数进行介绍。感兴趣的读者可以从rmongodb的官方文档中找到所有的函数。

mongo<-mongo.create() #

建立mongo

连接

mongo.is.connected(mongo) #

查看接连是否正常

buf <- mongo.bson.buffer.create() #

创建一个BSON

对象缓存

mongo.bson.buffer.append(buf, "name", "Echo") #

给对象buf

增加element

增加对象类型的element：

score <- c(5, 3.5, 4)

names(score) <- c("Mike", "Jimmy", "Ann")

mongo.bson.buffer.append(buf, "score", score)

增加数组类型的element：

mongo.bson.buffer.start.array(buf, "comments")

mongo.bson.buffer.append(buf, "0", "a1")

mongo.bson.buffer.append(buf, "1", "a2")

mongo.bson.buffer.append(buf, "2", "a3")

mongo.bson.buffer.finish.object(buf) #

关闭数组类型的element

b <- mongo.bson.from.buffer(buf) #

取出缓存数据

ns="db.blog" #

数据库.

数据集

插入一条记录：

mongo.insert(mongo,ns,b)

db.blog.insert(b)

创建查询对象query：

buf <- mongo.bson.buffer.create()

mongo.bson.buffer.append(buf, "name", "Echo")

query <- mongo.bson.from.buffer(buf)

创建查询返回值对象：

buf <- mongo.bson.buffer.create()

mongo.bson.buffer.append(buf, "name", 1)

fields <- mongo.bson.from.buffer(buf)

执行单条记录查询：

mongo.find.one(mongo, ns, query, fields)

db.blog.findOne({query},{fields})

执行列表记录查询：

mongo.find(mongo, ns, query, fields)

db.blog.find({query},{fields})

创建修改器对象objNew：

buf <- mongo.bson.buffer.create()

mongo.bson.buffer.start.object(buf, "$inc")

mongo.bson.buffer.append(buf, "age", 1L)

mongo.bson.buffer.finish.object(buf)

objNew <- mongo.bson.from.buffer(buf)

执行修改操作：

mongo.update(mongo, ns, query, objNew)

db.blog.update({query},{objNew})

单行代码修改操作：

mongo.update(mongo, ns, query, list(name="Echo", age=25))

db.blog.update({query},{objNew})

删除所选对象：

mongo.remove(mongo, ns, query)

db.blog.remove({query},{objNew})

mongo.destroy(mongo) #

销毁mongo

连接

6.2.3　rmongodb基本使用操作

R语言的客户端环境：

·Win 764bit

·R:3.0.1x86_64-w64-mingw32/x64b4bit

上一节介绍了rmongodb函数库的一些基本函数，但我们还没有安装rmongodb类库。

~ R    #

启动R

程序

> install.packages("rmongodb") #

安装rmongodb

> library(rmongodb)   #

加载类库

然后，通过mongo.create()函数与MongoDB数据库服务器建立连接。如果是本地连接，mongo.create()不需要参数。下面例子使用远程连接，增加host参数指定MongDB服务器的IP地址。

> mongo<-mongo.create(host="192.168.1.199") #

远程连接mongodb server

使用mongo.is.connected()函数，检查是否连接正常。这条语句在开发时会经常使用到。在用R语言建模时，如果对象或者函数使用错误，连接会被自动断开。由于MongoDB的异常机制，断开时不会有提示。大家要手动使用这条命令测试连接是否正常。

> print(mongo.is.connected(mongo)) #

查看是否连接正常

[1] TRUE

接下来，定义两个变量db和ns。db是我们需要使用的数据库，ns是数据库+数据集。

> db<-"foobar" #

定义db

> ns<-"foobar.blog" #

定义db.collection

下面我们创建一个JSON对象，并把这个对象存入MongoDB。

{

"_id" : ObjectId("51663e14da2c51b1e8bc62eb"),

"name" : "Echo",

"age" : 22,

"gender" : "Male",

"score" : {

"Mike" : 5,

"Jimmy" : 3.5,

"Ann" : 4

},

"comments" : [

"a1",

"a2",

"a3"

]

}

组织BSON类型数据：

> buf <- mongo.bson.buffer.create()

> mongo.bson.buffer.append(buf, "name", "Echo")

> mongo.bson.buffer.append(buf, "age", 22L)

> mongo.bson.buffer.append(buf, "gender", 'Male')

对象类型：

> score <- c(5, 3.5, 4)

> names(score) <- c("Mike", "Jimmy", "Ann")

> mongo.bson.buffer.append(buf, "score", score)

数组类型：

> mongo.bson.buffer.start.array(buf, "comments")

> mongo.bson.buffer.append(buf, "0", "a1")

> mongo.bson.buffer.append(buf, "1", "a2")

> mongo.bson.buffer.append(buf, "2", "a3")

> mongo.bson.buffer.finish.object(buf)

> b <- mongo.bson.from.buffer(buf)

> mongo.insert(mongo,ns,b) #

插入mongodb

单条显示插入的数据：

> buf <- mongo.bson.buffer.create()

> mongo.bson.buffer.append(buf, "name", "Echo")

> query <- mongo.bson.from.buffer(buf)

> print(mongo.find.one(mongo, ns, query))

然后，分别使用修改器$inc、$set、$push进行操作。首先使用$inc修改器，修改给age加1。

> buf <- mongo.bson.buffer.create()

> mongo.bson.buffer.start.object(buf, "$inc")

> mongo.bson.buffer.append(buf, "age", 1L)

> mongo.bson.buffer.finish.object(buf)

> objNew <- mongo.bson.from.buffer(buf)

> mongo.update(mongo, ns, query, objNew)

> print(mongo.find.one(mongo, ns, query))

使用$set修改器，修改age=1。

> buf <- mongo.bson.buffer.create()

> mongo.bson.buffer.start.object(buf, "$set")

> mongo.bson.buffer.append(buf, "age", 1L)

> mongo.bson.buffer.finish.object(buf)

> objNew <- mongo.bson.from.buffer(buf)

> mongo.update(mongo, ns, query, objNew)

> print(mongo.find.one(mongo, ns, query))

使用$push修改器，给comments数组追加“Orange”数据。

> buf <- mongo.bson.buffer.create()

> mongo.bson.buffer.start.object(buf, "$push")

> mongo.bson.buffer.append(buf, "comments", "Orange")

> mongo.bson.buffer.finish.object(buf)

> objNew <- mongo.bson.from.buffer(buf)

> mongo.update(mongo, ns, query, objNew)

> print(mongo.find.one(mongo, ns, query))

使用简化修改语句，给对象重新赋值。

> mongo.update(mongo, ns, query, list(name="Echo", age=25))

> print(mongo.find.one(mongo, ns, query))

最后删除对象，并断开连接。

> mongo.remove(mongo, ns, query) #

删除对象

> mongo.destroy(mongo) #

销毁mongo

连接

6.2.4　rmongodb性能测试的案例

我们已经清楚了rmongodb的使用，接下来，我们做一个性能测试案例：批量插入数据，使用修改器批量修改数据，并比较3种修改器的速度。首先批量插入数据函数。

> library(stringr) #

加载stringr

包，用来实现字符串操作

> batch_insert<-function(arr=1:10,ns){     #

批量插入数据

+   mongo_insert<-function(x){

+     buf <- mongo.bson.buffer.create()

+     mongo.bson.buffer.append(buf, "name", str_c("Dave",x))

+     mongo.bson.buffer.append(buf, "age", x)

+     mongo.bson.buffer.start.array(buf, "comments")

+     mongo.bson.buffer.append(buf, "0", "a1")

+     mongo.bson.buffer.append(buf, "1", "a2")

+     mongo.bson.buffer.append(buf, "2", "a3")

+     mongo.bson.buffer.finish.object(buf)

+     return(mongo.bson.from.buffer(buf))

+   }

+   mongo.insert.batch(mongo, ns, lapply(arr,mongo_insert))

+}

批量修改，$inc修改器函数。

> batch_inc<-function(data,ns){

+    for(i in data){

+      buf <- mongo.bson.buffer.create()

+      mongo.bson.buffer.append(buf, "name", str_c("Dave",i))

+      criteria <- mongo.bson.from.buffer(buf)

+      buf <- mongo.bson.buffer.create()

+      mongo.bson.buffer.start.object(buf, "$inc")

+      mongo.bson.buffer.append(buf, "age", 1L)

+      mongo.bson.buffer.finish.object(buf)

+      objNew <- mongo.bson.from.buffer(buf)

+      mongo.update(mongo, ns, criteria, objNew)

+   }

+}

批量修改，$set修改器函数。

> batch_set<-function(data,ns){

+    for(i in data){

+      buf <- mongo.bson.buffer.create()

+      mongo.bson.buffer.append(buf, "name", str_c("Dave",i))

+      criteria <- mongo.bson.from.buffer(buf)

+      buf <- mongo.bson.buffer.create()

+      mongo.bson.buffer.start.object(buf, "$set")

+      mongo.bson.buffer.append(buf, "age", 1L)

+      mongo.bson.buffer.finish.object(buf)

+      objNew <- mongo.bson.from.buffer(buf)

+      mongo.update(mongo, ns, criteria, objNew)

+    }

+ }

批量修改，$push修改器函数。

> batch_push<-function(data,ns){

+    for(i in data){

+      buf <- mongo.bson.buffer.create()

+      mongo.bson.buffer.append(buf, "name", str_c("Dave",i))

+      criteria <- mongo.bson.from.buffer(buf)

+      buf <- mongo.bson.buffer.create()

+      mongo.bson.buffer.start.object(buf, "$push")

+      mongo.bson.buffer.append(buf, "comments", "Orange")

+      mongo.bson.buffer.finish.object(buf)

+      objNew <- mongo.bson.from.buffer(buf)

+      mongo.update(mongo, ns, criteria, objNew)

+    }

}

上面3个修改器程序中使用的都是for循环的写法，因此for循环的性能损耗对三者应该是一样的，我们就不考虑这个影响因素了。下面执行程序，比较3种修改速度。

> ns="foobar.blog"   #

指定表

> data=1:1000   #

循环次数

> mongo.remove(mongo, ns) #

清空数据

[1] TRUE

> system.time(batch_insert(data, ns)) #

批量插入

user system elapsed

0.25   0.00    0.28

> system.time(batch_inc(data, ns)) # $inc

修改器

user system elapsed

0.47   0.27    2.50

> system.time(batch_set(data, ns)) # $set

修改器

user system elapsed

0.77   0.48    3.17

> system.time(batch_push(data, ns)) # $push

修改器

user system elapsed

0.81   0.41    4.23

从结果上看，$push最慢，3种修改器的速度分别是$push>$set>$inc。由于修改器分别针对不同的类型进行操作，push是对数组操作，set是对任意值操作，inc是对数字操作，所以以上的测试结果仅供参考。

在1.6节中，我们介绍了用R语言处理JSON数据，这里我们又打通R语言和MongoDB的连接，同时MongoDB的数据存储格式又是JSON格式的，从三者关系来看，基于JSON的半结构化的数据处理基础已经完整了，R也将在半结构化的数据处理领域占有一席之地。

6.3　R利剑NoSQL之Redis

问题

R如何连接Redis？

引言

Redis是常用的基于内存的Key-Value数据库，比Memcache更先进，支持多种数据结构，高效，快速。用Redis可以很轻松地解决高并发的数据访问问题，作为时时数据存储也非常不错。本节将介绍rredis，让R程序轻松连接Redis。

6.3.1　Redis环境准备

首先环境准备，这里我选择了Linux Ubuntu操作系统，大家可以根据自己的使用习惯选择顺手的Linux。

·Linux:Ubuntu 12.04.2LTS 64bit server

·MongoDB:2.2.12

·IP:192.168.1.101

关于Redis的安装和配置，请参考附录C。查看Redis服务器环境，使用/etc/init.d/redis-server命令，启动redis-server，默认端口port=6379。

~ /etc/init.d/redis-server start #

启动redis

Starting redis-server: redis-server.

~ ps -aux|grep redis  #

查看系统进程

redis    20128  0.0 0.0   10676   1428 ?        Ss     16:39     0:00 /usr/bin/redisserver/etc/redis/redis.conf

~ cat /var/log/redis/redis-server.log #

查看启动日志

[20128] 14 Apr 16:39:43 * Server started, Redis version 2.2.12

[20128] 14 Apr 16:39:43 # WARNING overcommit_memory is set to 0! Background save

may fail under low memory condition. To fix this issue add 'vm.overcommit_memory= 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl

vm.overcommit_memory=1' for this to take effect.

[20128] 14 Apr 16:39:43 * The server is now ready to accept connections on port 6379

使用Redis的命令行客户端程序redis-cli，打开Redis Shell。Redis Shell的简单操作有插入一条记录和查询记录。

~ redis-cli   #

打开Redis Shell

redis 127.0.0.1:6379> help set #

查看set

命令的用法

SET key value

summary: Set the string value of a key

since: 0.07

group: string

redis 127.0.0.1:6379> set a 178 #

插入一条记录 key = a, value = 178

OK

redis 127.0.0.1:6379> get a #

查看记录

"178"

接下来，我们使用R语言的Redis客户端rredis远程访问Redis服务器进行测试。

6.3.2　rredis函数库

rredis是R语言访问Redis数据库的客户端通信接口程序，提供了100个函数，对应Redis的各种操作。虽然rredis的函数很多，但是用法都是比较简单的，对R语言支持足够灵活，代码也比较简洁。我们不一一列出100个rredis函数的，仅挑选一些常用的函数介绍。感兴趣的读者可以从rredis的官方文档中，找到所有的函数。

redisConnect(), redisClose() #

建立连接，关闭连接

redisFlushDB(), redisFlushAll() #

清空当前/

所有数据库数据

redisKeys(), redisDBSize() #

列出所有KEY

值，KEY

的数量

redisSelect(0) #

选择切换数据库:0

是默认数据库

redisSet('x',runif(5)), redisMSet(list(x=pi,y=runif(5),z=sqrt(2)))

#

插入string

对象，批量插入

redisGet('x'), redisMGet(c('x','y','z')) #

读取string

对象，批量读取

redisDelete('x') #

删除对象

redisLPush('a',1), redisRPush('a','A') #

左边插入数组对象,

右边插入数组对象

redisLPop('a'), redisRPop('a') #

左边弹出一个数组对象，

右边弹出一个数组对象

redisLRange('a',0,-1) #

从左边显示数组对象列表

redisSAdd('A',runif(2)) #

插入set

类型对象

redisSCard('A'), redisSMembers('A') #

显示set

对象有几个元素，列表显示set

对象元素

redisSDiff(c('A','B')),redisSInter(c('A','B')),redisSUnion(c('A','B'))

#

显示两个set

对象的差集、交集和并集

6.3.3　rredis基本使用操作

R语言的客户端环境是：

·Win764bit

·R:3.0.1x86_64-w64-mingw32/x64b4bit

我将按照下面的5个操作类别进行介绍。

·redis的基本操作：建立连接，切换数据库，列表显示所有KEY值，清空当前数据库数据，清空所有数据库数据，关闭链接。

·string类型操作：插入，读取，删除，插入并设置过期时间，批量操作。

·list类型操作：插入，读取，弹出。

·set类型操作：插入，读取，交集，差集，并集。

·rredis与redis-cli的交互操作。

1.redis的基本操作

首先，要安装rredis类库，并加载类库。

> install.packages("rredis") #

安装rredis

> library(rredis) #

加载rredis

类库

通过redisConnect()函数，建立与Redis服务器的连接。如果是本地连接，redis Connect()不要参数，下面例子使用远程连接，增加host参数配置IP地址redisConnect(host=“192.168.1.101”,port=6379)。

> redisConnect(host="192.168.1.101",port=6379) #

远程连接redis server

> redisKeys() #

列出所有的keys

[1] "x" "data"

> redisDBSize() #

显示有多少个key

[1] 2

> redisSelect(1) #

切换数据库1

[1] "OK"

> redisKeys()

NULL

> redisSelect(0) #

切换数据库0

[1] "OK"

> redisKeys()

[1] "x" "data"

> redisFlushDB() #

清空当前数据库数据

[1] "OK"

> redisFlushAll() #

清空所有数据库数据

[1] "OK"

> redisClose() #

关闭链接

2.string类型操作

> redisSet('x',runif(5)) #

插入对象

[1] "OK"

> redisGet('x') #

读取对象

[1] 0.67616159 0.06358643 0.07478021 0.32129140 0.16264615

> redisExpire('x',1) #

设置数据过期时间

Sys.sleep(1)

> redisGet('x')

NULL

> redisMSet(list(x=pi,y=runif(5),z=sqrt(2))) #

批量插入

[1] TRUE

> redisMGet(c('x','y','z')) #

批量读取

$x

[1] 3.141593

$y

[1] 0.9249501 0.3444994 0.6477250 0.1681421 0.2646853

$z

[1] 1.414214

> redisDelete('x') #

删除数据

[1] 1

> redisGet('x')

NULL

3.list类型操作

> redisLPush('a',1) #

从数组左边插入数据

> redisLPush('a',2)

> redisLPush('a',3)

> redisLRange('a',0,2) #

显示从数组左边0~2

的数据

[[1]]

[1] 3

[[2]]

[1] 2

[[3]]

[1] 1

> redisLPop('a') #

从数据左边弹出一个数据

[1] 3

> redisLRange('a',0,-1) #

显示从数组左边0~(-1)

的数据

[[1]]

[1] 2

[[2]]

[1] 1

> redisRPush('a','A') #

从数组右边插入数据

> redisRPush('a','B')

> redisLRange('a',0,-1) #

显示从数组左边0~(-1)

的数据

[[1]]

[1] 2

[[2]]

[1] 1

[[3]]

[1] "A"

[[4]]

[1] "B"

> redisRPop('a') #

从数据右边弹出一个数据

4.set类型操作

> redisSAdd('A',runif(2))

> redisSAdd('A',55)

> redisSCard('A') #

显示对象有几个元素

[1] 2

> redisSMembers('A') #

列表显示set

对象元素

[[1]]

[1] 55

[[2]]

[1] 0.6494041 0.3181108

> redisSAdd('B',55)

> redisSAdd('B',rnorm(3))

> redisSCard('B') #

显示对象有几个元素

[1] 2

> redisSMembers('B') #

列表显示set

对象元素

[[1]]

[1] 55

[[2]]

[1] 0.1074787 1.3111006 0.8223434

> redisSDiff(c('A','B')) #

差集

[[1]]

[1] 0.6494041 0.3181108

> redisSInter(c('A','B')) #

交集

[[1]]

[1] 55

> redisSUnion(c('A','B')) #

并集

[[1]]

[1] 55

[[2]]

[1] 0.1074787 1.3111006 0.8223434

[[3]]

[1] 0.6494041 0.3181108

5.rredis与redis-cli交互

从Redis客户端插入数据，再用rredis读取数据。

~ redis-cli   #

打开redis

客户端

redis 127.0.0.1:6379> set shell "Greetings, R client!"

OK

> redisGet('shell')   #

读取数据

[1] "Greetings, R client!"

用rredis插入数据，再用Redis客户端读取数据。

> redisSet('R', 'Greetings, shell client!') #

插入数据

[1] "OK"

redis 127.0.0.1:6379> get R #

读取数据(

有乱码)

"X\\x00\x00\x00\x02\x00\x02\x0f\x00\x00\x02\x03\x00\x00\x00\x00\x10\x00\x00\x00\x01\x00\x04\x00\\x00\x00\x00\x18Greetings, shell client!"

转型以Raw的数组方式存储（charToRaw），就可以解决乱码的问题。

> redisSet('R', charToRaw('Greetings, shell client!'))

[1] TRUE

redis 127.0.0.1:6379> get R    #

正常读取数据

"Greetings, shell client!"

6.3.4　rredis测试案例

我们已经清楚了rredis的使用，接下来，我们做一个测试案例。读入一个数据文件，从左到右分别是用户id、口令、邮箱，在Redis里建立合适的数据模型，方便存储和查询。首先，定义数据模型。

KEY:

·users:用户id

VALUE:

·id:用户id

·pw:口令

·email:邮箱

数据文件：data5.txt。

wolys # wolysopen111 # wolys@21cn.com

coralshanshan # 601601601 # zss1984@126.com

pengfeihuchao # woaidami # 294522652@qq.com

simulategirl # @#$9608125 # simulateboy@163.com

daisypp # 12345678 # zhoushigang_123@163.com

sirenxing424 # tfiloveyou # sirenxing424@126.com

raininglxy # 1901061139 # lixinyu23@qq.com

leochenlei # leichenlei # chenlei1201@gmail.com

z370433835 # lkp145566 # 370433835@qq.com

cxx0409 # 12345678 # cxx0409@126.com

xldq_l # 061222ll # viv093@sina.com

接下来，读入数据文件到内存。然后建立Redis连接，以循环方式把数据插入到Redis，最后以users:wolys为KEY，输出对应的VALVE值。

> data<-scan(file="data5.txt",what=character(),sep=" ") #

读入数据

> data<-data[which(data!='#')]

> data

[1] "wolys"                  "wolysopen111"          "wolys@21cn.com"

[4] "coralshanshan"          "601601601"             "zss1984@126.com"

[7] "pengfeihuchao"          "woaidami"              "294522652@qq.com"

[10] "simulategirl"          "@#$9608125"            "simulateboy@163.com"

[13] "daisypp"               "12345678"              "zhoushigang_123@163.com"

[16] "sirenxing424"          "tfiloveyou"            "sirenxing424@126.com"

[19] "raininglxy"            "1901061139"            "lixinyu23@qq.com"

[22] "leochenlei"            "leichenlei"            "chenlei1201@gmail.com"

[25] "z370433835"            "lkp145566"             "370433835@qq.com"

[28] "cxx0409"               "12345678"              "cxx0409@126.com"

[31] "xldq_l"                "061222ll"              "viv093@sina.com"

> redisConnect(host="192.168.1.101",port=6379) #

连接redis

连接

> redisFlushAll()

> redisKeys()

> id<-NULL #

循环插入数据

> for(i in 1:length(data)){

+  if(i %% 3 == 1) {

+    id<-data[i]

+    redisSAdd(paste("users:",id,sep=""),paste("id:",id,sep=""))

+  } else if(i %% 3 == 2) {

+    redisSAdd(paste("users:",id,sep=""),paste("pw:",data[i],sep=""))

+  } else {

+    redisSAdd(paste("users:",id,sep=""),paste("email:",data[i],sep=""))

+  }

+}

> redisKeys() #

列出所有的KEY

[1] "users:cxx0409"       "users:sirenxing424"  "users:simulategirl"

"users:xldq_l"

[5] "users:coralshanshan" "users:raininglxy"    "users:pengfeihuchao"

"users:leochenlei"

[9] "users:daisypp"       "users:wolys"         "users:z370433835"

> redisSMembers("users:wolys") #

通过KEY

查询VALUE

[[1]]

[1] "pw:wolysopen111"

[[2]]

[1] "email:wolys@21cn.com"

[[3]]

[1] "id:wolys"

> redisClose() #

关闭redis

连接

这样我们就完成了整个的测试案例。Redis是一种非常高效的内存数据库，用R和Redis结合，可以构建无比强大的时时计算应用。

6.4　R利剑NoSQL之Cassandra

问题

R如何连接Cassandra？

引言

Cassandra是由Facebook开发，然后开源的一套分步式数据存储系统，用于海量数据的高伸展性存储。Cassandra的无中心设计、一致性哈希、BloomFilter等技术为之后的NoSQL产品提供新颖的设计思路。本节将介绍RCassandra，让R程序轻松连接Cassandra。

6.4.1　Cassandra环境准备

Cassandra的名称来源于希腊神话，是特洛伊的一位悲剧性的女先知的名字，因此项目的Logo是一只放光的眼睛。Cassandra的数据会写入多个节点，来保证数据的可靠性，在一致性、可用性和网络分区耐受能力（CAP）的折衷问题上，Cassandra比较灵活，用户在读取时可以指定要求所有副本一致（高一致性）、读到一个副本即可（高可用性）或是通过选举来确认多数副本一致即可（折中）。这样，Cassandra可以适用于有节点、网络失效以及多数据中心的场景。

首先是环境准备，这里我选择了Linux Ubuntu操作系统，大家可以根据自己的使用习惯选择顺手的Linux。Cassandra服务器环境是：

·Linux Ubuntu 12.04.2LTS 64bit server

·Java JDK 1.6.0_45

·Cassandra 1.2.15

关于Cassandra的安装和配置，请参考附录E。启动Cassandra服务器，使用bin/cassandra命令。

~cd /home/conan/tookit/cassandra1215 #

进入cassandra

安装目录

~ bin/cassandra #

启动cassandra

~ ps -aux|grep cassandra #

查看系统进程

~ cat /var/log/cassandra/system.log |less #

查看启动日志

INFO [main] 2014-03-22 06:28:08,777 CassandraDaemon.java (line 119) Logging initialized

INFO [main] 2014-03-22 06:28:08,795 CassandraDaemon.java (line 144) JVM vendor/version: Java HotSpot(TM) 64-Bit Server VM/1.6.0_45

INFO [main] 2014-03-22 06:28:08,799 CassandraDaemon.java (line 182) Heap size:2051014656/2051014656

6.4.2　RCassandra函数库

RCassandra支持的函数只有17个。前面介绍过rredis有100个函数，rmongodb有153个函数，相比之下RCassandra的函数是比较少的。而且这17个函数，并没有覆盖Cassandra的所有操作，就连一些基本的操作都没有函数支持，要在命令行处理，不知道是什么原因。希望RCassandra能继续发展，完善没有实现的功能函数。

1.17个函数

下面列出了这17个函数，并与Cassandra的命令做了对比说明。

RC.close                RC.insert                 RC.cluster.name

RC.login                RC.connect                RC.mget.range

RC.consistency          RC.mutate                 RC.describe.keyspace

RC.read.table           RC.describe.keyspaces     RC.use

RC.get                  RC.version                RC.get.range

RC.write.table          RC.get.range.slices

2.Cassandra和RCassandra的基本操作对比：

（1）连接到集群

Cassandra:connect 192.168.1.200/9160;

RCassandra:conn<-RC.connect(host="192.168.1.200",port=9160)

（2）查看当前集群名字

Cassandra:show cluster name;

RCassandra:RC.cluster.name(conn)

（3）列出当前集群所有keyspaces

Cassandra:show keyspaces;

RCassandra:RC.describe.keyspaces(conn)

（4）查看DEMO的keyspace

Cassandra:show schema DEMO;

RCassandra:RC.describe.keyspace(conn,'DEMO')

（5）选择DEMO的keyspace

Cassandra:use DEMO;

RCassandra:RC.use(conn,'DEMO')

（6）设置一致性级别

Cassandra:consistencylevel as ONE;

RCassandra:RC.consistency(conn,level="one")

（7）插入数据

Cassandra

：set Users[1][name] = scott;

RCassandra

：RC.insert(conn,'Users','1', 'name', 'scott')

（8）插入数据框

Cassandra

：NA

RCassandra

：RC.write.table(conn, "Users", df)

（9）读取列族所有数据

Cassandra:list Users;

RCassandra

：RC.read.table(conn,"Users")

（10）读取数据

Cassandra:get Users[1]['name'];

RCassandra

：RC.get(conn,'Users','1', c('name'))

（11）退出连接

Cassandra:exit; quit;

RCassandra:RC.close(conn)

6.4.3　RCassandra基本使用操作

我们使用RCassandra进行基本的函数操作，以R语言中iris的数据集为例，介绍如何利用RCassandra操作Cassandra数据库。使用R语言的RCassandra客户端远程访问Cassandra服务器进行测试。

客户端环境

·Linux:Ubuntu 12.04.2LTS 64bit server

·R:3.0.1,x86_64-pc-linux-gnu(64-bit)

首先，启动R程序，安装RCassandra包。然后建立服务器连接。

~ R #

启动R

程序

> install.packages('RCassandra') #

安装RCassandra

> library(RCassandra)   #

加载RCassandra

类库

> conn<-RC.connect(host="192.168.1.200") #

建立服务器连接

> RC.cluster.name(conn)   #

当前集群的名字(2

个节点集群的名字)

[1] "case1"

> RC.version(conn) #

当前协议的版本

[1] "19.36.0"

keyspaces的操作：

> RC.describe.keyspaces(conn) #

列出所有keyspaces

配置信息

> RC.describe.keyspace(conn, "DEMO") #

列出叫的DEMO

的keyspaces

配置信息

下面是数据的操作。注意RCassandra是不能创建列族的，需要提前通过Cassandra命令创建一个列族。

> head(iris) #

插入iris

数据

Sepal.Length Sepal.Width Petal.Length Petal.Width Species

1          5.1         3.5          1.4         0.2  setosa

2          4.9         3.0          1.4         0.2  setosa

3          4.7         3.2          1.3         0.2  setosa

4          4.6         3.1          1.5         0.2  setosa

5          5.0         3.6          1.4         0.2  setosa

6          5.4         3.9          1.7         0.4  setosa

> RC.write.table(conn, "iris", iris) #iris

是一个data.frame

attr(,"class")

[1] "CassandraConnection"

> RC.get(conn, "iris", "1", c("Sepal.Length", "Species"))

#

查看第1

行Sepal.Length

列和Species

的值

key  value                  ts

1   Sepal.Length           5.1 1.372881e+15

2         Species setosa       1.372881e+15

#

注：ts

是时间戳

> RC.get.range(conn, "iris", "1") #

查看第1

行

key  value           ts

1 Petal.Length    1.4 1.372881e+15

2  Petal.Width    0.2 1.372881e+15

3 Sepal.Length    5.1 1.372881e+15

4  Sepal.Width    3.5 1.372881e+15

5      Species setosa 1.372881e+15

> r <- RC.get.range.slices(conn, "iris") #

查看数据集

class(r)

[1] "list"

> r[[1]]

key   value           ts

1 Petal.Length     1.7 1.372881e+15

2  Petal.Width     0.4 1.372881e+15

3 Sepal.Length     5.4 1.372881e+15

4  Sepal.Width     3.9 1.372881e+15

5      Species setosa  1.372881e+15

> rk <- RC.get.range.slices(conn, "iris", limit=0)

> y <- RC.read.table(conn, "iris")

> y <- y[order(as.integer(row.names(y))),]

> head(y)

Petal.Length Petal.Width Sepal.Length Sepal.Width Species

1          1.4         0.2          5.1          3.5 setosa

2          1.4         0.2          4.9          3.0 setosa

3          1.3         0.2          4.7          3.2 setosa

4          1.5         0.2          4.6          3.1 setosa

5          1.4         0.2          5.0          3.6 setosa

6

不支持的常用操作：

·创建keyspaces，除keyspaces

·创建列族，删除列族

·删除一行

·删除一行的某列数据

6.4.4　RCassandra使用案例

下面通过一个业务需求的例子加深我们对RCassandra的认识。这是一个非常简单的业务场景，业务需求：（1）创建一个Users列族，包含name,password两列；（2）在已经存在数据的情况下，动态增加一个新列age。首先在Cassandra命令行，创建列族Users。

[default@DEMO] create column family Users

...     with key_validation_class = 'UTF8Type'

...     and comparator = 'UTF8Type'

...     and default_validation_class = 'UTF8Type';

89a2fb75-f7d0-399e-b017-30a974b19f4a

然后在RCassandra插入数据，包含name和password两列。

> df<-data.frame(name=c('a1','a2'),password=c('a1','a2')) > print(df)

name password

1   a1       a1

2   a2       a2

> RC.write.table(conn, "Users", df) #

插入数据

attr(,"class")

[1] "CassandraConnection"

> RC.read.table(conn,"Users") #

查看数据

name password

2    a2       a2

1    a1       a1

新插入一行KEY=1234，并增加age列。

> RC.insert(conn,'Users','1234', 'name', 'scott')

> RC.insert(conn,'Users','1234', 'password', 'tiger')

> RC.insert(conn,'Users','1234', 'age', '20')

> RC.read.table(conn,"Users") #

查看数据

age  name password

1234  20 scott    tiger

2     NA    a2       a2

1     NA    a1       a1

修改KEY=1的行中，name=a11,age=12。

> RC.insert(conn,'Users','1', 'name', 'a11')

> RC.insert(conn,'Users','1', 'age', '12')

> RC.read.table(conn,"Users") #

查看数据

age  name password

1234  20 scott    tiger

2     NA    a2       a2

1     12   a11       a1

6.4.5　Cassandra的没落

随着Hadoop的兴起以及Hadoop家族产品HBase的广泛使用，有越来越多的基于Cassandra构建的应用，开始向HBase迁移。Cassandra的没落，在技术上可能存在的一些原因。

（1）读的性能太慢。

无中心的设计，造成读数据时通过逆熵做计算，性能损耗很大，甚至会严重影响服务器运作。

（2）数据同步太慢（最终一致性延迟可能非常大）。

由于无中心设计，要靠各节点传递信息。相互发通知告知状态，如果副本集有多份，其中又出现节点有宕机的情况，那么做到数据的一致性，延迟可能非常大，效率也很低的。

（3）用插入和更新代替查询，缺乏灵活性，所有查询都要求提前定义好。

与大多数数据库为读优化不同，Cassandra的写性能理论上是高于读性能的，因此非常适合流式的数据存储，尤其是写负载高于读负载的。与HBase比起来，它的随机访问性能要高很多，但不是很擅长区间扫描，因此可以作为HBase的即时查询缓存，由HBase进行批量的大数据处理，由Cassandra提供随机查询的接口。

（4）不支持直接接入Hadoop，不能实现MapReduce。

现在大数据的代名词就是Hadoop，作为海量数据的框架，不支持Hadoop及MapReduce就将被取代。除非Cassandra能够给出其他的定位，或者海量数据解决方案。DataStax公司，正在用Cassandra重构HDFS的文件系统，不知道是否可以成功。让我期待Cassandra未来的发展吧！

6.5　R利剑NoSQL之Hive

问题

R如何连接Hive？

引言

Hive是Hadoop的一个程序接口，使用了类SQL的语法，数据分析人员可以快速掌握。Hive让Java的世界变得简单而轻巧，让Hadoop普及到了程序员以外的人。从Hive开始，分析师们也能玩转大数据。本节将介绍RHive，让R程序轻松连接Hive。

6.5.1　Hive环境准备

Hive是建立在Hadoop上的数据仓库基础构架。它提供了一系列的工具，可以用来进行数据提取转化加载（ETL），这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。Hive定义了简单的类SQL查询语言，称为HQL，它允许熟悉SQL的用户查询数据。Hive没有专门的数据格式，可以很好地工作在Thrift之上，控制分隔符，也允许用户指定数据格式。

Hive与关系数据库的区别：

·数据存储不同：Hive基于Hadoop的HDFS，关系数据库则基于本地文件系统。

·计算模型不同：Hive基于Hadoop的MapReduce，关系数据库则基于索引的内存计算模型。

·应用场景不同：Hive是OLAP数据仓库系统提供海量数据查询的，实时性很差；关系数据库是OLTP事务系统，为实时查询业务服务。

·扩展性不同：Hive基于Hadoop很容易通过分布式增加存储能力和计算能力，关系数据库水平扩展很难，要不断增加单机的性能。

Hive是基于Hadoop开发的数据仓库产品，所以首先我们要有Hadoop的环境。关于Hive的安装和配置，请参考附录G。首先环境准备，这里我选择了Linux Ubuntu操作系统，大家可以根据自己的使用习惯选择顺手的Linux。

·Linux Ubuntu 12.04.2LTS 64bit server

·Java JDK 1.6.0_45

·Hadoop 1.1.2

·hive 0.9.0

·IP:192.168.1.210

Hive安装好后，启动hiveserver的服务。

~ nohup hive --service hiveserver& #

通过nohup

在后台启动hiveserver

服务

Starting Hive Thrift Server

Hive Shell的简单操作：

~ hive shell #

打开hive shell

Logging initialized using configuration in file:/home/conan/hadoop/hive-0.9.0/conf/

hive-log4j.proper              ties

Hive history file=/tmp/conan/hive_job_log_conan_201306261459_153868095.txt

hive> show tables; #

查看hive

的表

hive_algo_t_account

o_account

r_t_account

Time taken: 2.12 seconds

hive> select * from o_account; #

查看o_account

表的数据

1       abc@163.com     2013-04-22 12:21:39

2       dedac@163.com   2013-04-22 12:21:39

3       qq8fed@163.com  2013-04-22 12:21:39

4       qw1@163.com     2013-04-22 12:21:39

5       af3d@163.com    2013-04-22 12:21:39

6       ab34@163.com    2013-04-22 12:21:39

7       q8d1@gmail.com  2013-04-23 09:21:24

8       conan@gmail.com 2013-04-23 09:21:24

9       adeg@sohu.com   2013-04-23 09:21:24

10      ade121@sohu.com 2013-04-23 09:21:24

11      addde@sohu.com  2013-04-23 09:21:24

Time taken: 0.469 seconds

6.5.2　RHive安装

接下来，我们在装有Hive的同一台机器上装RHive。客户端环境是：

·Linux:Ubuntu 12.04.2LTS 64bit server

·R:3.0.1,x86_64-pc-linux-gnu(64-bit)

注　RHive只支持Linux环境。

首先，安装R依赖库rJava。

~ sudo R CMD javareconf #

建议使用root

权限安装rJava

~ sudo R #

启动R

程序

> install.packages("rJava")

> install.packages("RHive") #

安装RHive

> library(RHive) #

加载RHive

Loading required package: rJava

Loading required package: Rserve

This is RHive 0.0-7. For overview type '?RHive'.

HIVE_HOME=/home/conan/hadoop/hive-0.9.0

call rhive.init() because HIVE_HOME is set.

加载RHive时，本地Hive的环境变量，都会自动加载到R语言的运行时环境中。

6.5.3　RHive函数库

RHive提供了52个函数，对应Hive的操作。我们不一一列出所有RHive函数库，仅挑选一些常用的函数与Hive操作进行对比。感兴趣的读者可以从RHive的官方文档中，找到所有的函数。下面是Hive和RHive的基本操作对比。

（1）连接到Hive

Hive:hive shell

RHive:rhive.connect("192.168.1.210")

（2）列出所有Hive的表

Hive:show tables;

RHive:rhive.list.tables()

（3）查看表结构

Hive:desc o_account;

RHive:rhive.desc.table('o_account');

rhive.desc.table('o_account',TRUE)

（4）执行HQL查询

Hive:select * from o_account;

RHive:rhive.query('select * from o_account')

（5）查看hdfs目录

Hive:dfs -ls /;

RHive:rhive.hdfs.ls()

（6）查看hdfs文件内容

Hive:dfs -cat /user/hive/warehouse/o_account/part-m-00000;

RHive:rhive.hdfs.cat('/user/hive/warehouse/o_account/part-m-00000')

（7）断开连接

Hive:quit;

RHive:rhive.close()

6.5.4　RHive基本使用操作

> rhive.init() #

初始化

> rhive.connect("192.168.1.210") #

连接hive

> rhive.list.tables() #

查看所有表

tab_name

1 hive_algo_t_account

2           o_account

3         r_t_account

> rhive.desc.table('o_account'); #

查看表结构

col_name data_type comment

1          id       int

2       email    string

3 create_date    string

> rhive.query("select * from o_account");    #

执行HQL

查询

id           email         create_date

1   1     abc@163.com 2013-04-22 12:21:39

2   2   dedac@163.com 2013-04-22 12:21:39

3   3  qq8fed@163.com 2013-04-22 12:21:39

4   4     qw1@163.com 2013-04-22 12:21:39

5   5    af3d@163.com 2013-04-22 12:21:39

6   6    ab34@163.com 2013-04-22 12:21:39

7   7  q8d1@gmail.com 2013-04-23 09:21:24

8   8 conan@gmail.com 2013-04-23 09:21:24

9   9   adeg@sohu.com 2013-04-23 09:21:24

10 10 ade121@sohu.com 2013-04-23 09:21:24

11 11  addde@sohu.com 2013-04-23 09:21:24

> rhive.close() #

关闭连接

[1] TRUE

> rhive.block.sample('o_account', subset="id<5") #

创建临时表

[1] "rhive_sblk_1372238856"

> rhive.query("select * from rhive_sblk_1372238856"); #

查看临时表

id          email         create_date

1 1     abc@163.com 2013-04-22 12:21:39

2 2   dedac@163.com 2013-04-22 12:21:39

3 3  qq8fed@163.com 2013-04-22 12:21:39

4 4     qw1@163.com 2013-04-22 12:21:39

> rhive.hdfs.ls('/user/hive/warehouse/rhive_sblk_1372238856/') #

查看hdfs

的文件

permission owner      group length      modify-time

1 rw-r--r-- conan supergroup 141 2013-06-26 17:28

file

1 /user/hive/warehouse/rhive_sblk_1372238856/000000_0

> rhive.hdfs.cat('/user/hive/warehouse/rhive_sblk_1372238856/000000_0')

1abc@163.com2013-04-22 12:21:39

2dedac@163.com2013-04-22 12:21:39

3qq8fed@163.com2013-04-22 12:21:39

4qw1@163.com2013-04-22 12:21:39

> rhive.basic.cut('o_account','id',breaks='0:100:3') #

按范围分割字段数据

[1] "rhive_result_20130626173626"

attr(,"result:size")

[1] 443

> rhive.query("select * from rhive_result_20130626173626");

email         create_date     id

1      abc@163.com 2013-04-22 12:21:39  (0,3]

2    dedac@163.com 2013-04-22 12:21:39  (0,3]

3   qq8fed@163.com 2013-04-22 12:21:39  (0,3]

4      qw1@163.com 2013-04-22 12:21:39  (3,6]

5     af3d@163.com 2013-04-22 12:21:39  (3,6]

6     ab34@163.com 2013-04-22 12:21:39  (3,6]

7   q8d1@gmail.com 2013-04-23 09:21:24  (6,9]

8  conan@gmail.com 2013-04-23 09:21:24  (6,9]

9    adeg@sohu.com 2013-04-23 09:21:24  (6,9]

10 ade121@sohu.com 2013-04-23 09:21:24  (9,12]

11  addde@sohu.com 2013-04-23 09:21:24  (9,12]

Hive操作HDFS。

> rhive.hdfs.ls() #

查看hdfs

文件目录

permission owner      group length      modify-time  file

1  rwxr-xr-x conan supergroup      0 2013-04-24 01:52 /HBase

2  rwxr-xr-x conan supergroup      0 2013-06-23 10:59  /home

3  rwxr-xr-x conan supergroup      0 2013-06-26 11:18 /rhive

4  rwxr-xr-x conan supergroup      0 2013-06-23 13:27   /tmp

5  rwxr-xr-x conan supergroup      0 2013-04-24 19:28  /user

> rhive.hdfs.cat('/user/hive/warehouse/o_account/part-m-00000') #

查看hdfs

文件内容

1abc@163.com2013-04-22 12:21:39

2dedac@163.com2013-04-22 12:21:39

3qq8fed@163.com2013-04-22 12:21:39

通过RHive我们就打通了R和Hive的数据通道，可以用R来实现基于Hive系统的大数据的操作。下一节中，我会介绍RHive在金融背景下的大数据的案例。

6.6　用RHive从历史数据中提取逆回购信息

问题

RHive如何进行金融数据分析？

引言

笔者接触金融时间并不太长，操作的第一个产品就是逆回购。逆回购对于大部分人来说，都是一个新鲜词，就算是炒股多年的玩家，可能也是在2013年6月份发生银行缺钱的事件之后才了解逆回购的。隔夜的银行间拆借利率达到了30%，简单来说银行缺钱了！各种机构分分出售股票、债券，兑换成现金借给银行。个人用户也都取出存款，通过逆回购，把钱借给银行。30%的利率，让所有人在那一周都为之兴奋，只有银行在惶恐。

6.6.1　逆回购简介

我们首先解释债券回购的含义，简单地说，债券质押式回购就是交易双方以债券为质押品的一种短期资金借贷行为。其中债券持有人（正回购方）将债券质押而获得资金使用权，到约定的时间还本并支付一定的利息，从而“赎回”债券。而资金持有人（逆回购方）就是正回购方的交易对手。在实际交易中债券是质押给了第三方，即中国结算公司，这样交易双方更加安全，交易更便捷。

可回购的债券包括所有的国债、绝大部分企业债、公司债和分离债的纯债，它们都可用于债券回购交易。沪深交易所每周都会公布可回购债券的折算率，上面没有但可交易的品种就是不可回购的债券。折算率就是把债券质押时，交易所按债券面值给出的可质押的比率。

现在交易的回购品种很多，不过我们下面仅列出个人投资者经常参与的公司债（包括企业债等）回购品种。上海证券交易所可交易的质押式回购（括号中为交易代码）分为1日（204001）、2日（204002）、3日（204003）、4日（204004）、7日（204007）、14日（204014）、28日（204028）、91日（204091）、182日（204182）共9个品种。深圳交易所按回购期限分为1日（131810）、2日（131811）、3日（131800）、4日（131809）、7日（131801）、14日（131802）、28日（131803）、91日（131805）、182日（131806）共49个品种。其中经常交易的只有沪深1日和7日4个品种，并且沪市的日均交易量又远远大于深市的交易量。

6.6.2　历史数据存储结构

本节使用的系统环境是：

·Linux:Ubuntu 12.04.2LTS 64bit

·R:3.0.1x86_64-pc-linux-gnu

·内网域名:c1.wtmart.com

Hive中的表结构是：

> rhive.desc.table('t_reverse_repurchase')

col_name data_type

1  tradedate    string

2  tradetime    string

3 securityid    string

4     bidpx1    double

5   bidsize1    double

6   offerpx1    double

7 offersize1    double

其中tradedate是交易日期，tradetime是交易时间，securityid是股票ID，bidpx1是买一，bidsize1是买一交易量，offerpx1是卖一，bidsize1是卖一交易量。

6.6.3　通过用RHive提取数据

登录c1.wtmart.com服务器，打开R的客户端。

~ R #

启动R

程序

> library(RHive) #

加载RHive

> rhive.init() #

初始化

> rhive.connect("c1.wtmart.com") #

连接到Hive

集群

SLF4J: Class path contains multiple SLF4J bindings.

SLF4J: Found binding in [jar:file:/home/cos/toolkit/hive-0.9.0/lib/slf4j-log4j12-

1.6.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: Found binding in [jar:file:/home/cos/toolkit/hadoop-1.0.3/lib/slf4jlog4j12-

1.4.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]

SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.

> rhive.list.tables() #

查看当前的表

tab_name

1            t_hft_day //

历史数据表

2            t_hft_tmp //

临时表

4 t_reverse_repurchase //

逆回购表

查看所有股票历史数据分片：测试数据从20130627–20130726。

> rhive.query("SHOW PARTITIONS t_hft_day"); #

查看t_hft_day

表的分片

partition

1  tradedate=20130627

2  tradedate=20130628

3  tradedate=20130701

4  tradedate=20130702

5  tradedate=20130703

6  tradedate=20130704

7  tradedate=20130705

8  tradedate=20130708

9  tradedate=20130709

10 tradedate=20130710

11 tradedate=20130712

12 tradedate=20130715

13 tradedate=20130716

14 tradedate=20130719

15 tradedate=20130722

16 tradedate=20130723

17 tradedate=20130724

18 tradedate=20130725

19 tradedate=20130726

分为提取“上交所一天逆回购”（204001）和“深交所一天逆回购”（131810），从7月22日至7月26日的一周数据。

> rhive.drop.table("t_reverse_repurchase") #

删除逆回购表

> rhive.query("CREATE TABLE t_reverse_repurchase AS SELECT tradedate,tradetime, securityid,bidpx1,bidsize1,offerpx1,offersize1 FROM t_hft_day where

tradedate>=20130722 and securityid in (131810,204001)");#

创建逆回购表，并插入数据

> rhive.query("SELECT securityid,count(1) FROM t_reverse_repurchase group by securityid"); #

查看数据结果集

securityid  X_c1

1     131810 17061

2     204001 12441

加载到R的内存中。

> bidpx1<-rhive.query("SELECT securityid,concat(tradedate,tradetime) as

tradetime,bidpx1 FROM t_reverse_repurchase");

#

从Hive

查询数据，并赋值给R

的变量bidpx1

> nrow(bidpx1) #

查看记录条数

[1] 29502

> head(bidpx1)  #

查看数据

securityid      tradetime bidpx1

1      131810 20130724145004 2.620

2      131810 20130724145101 2.860

3      131810 20130724145128 2.850

4      131810 20130724145143 2.603

5      131810 20130724144831 2.890

6      131810 20130724145222 2.600

用ggplot2做数据可视化，画出数据两个产品的一周的走势，所图6-1所示。

> library(ggplot2) #

加载ggplot2

> g<-ggplot(data=bidpx1, aes(x=as.POSIXct(tradetime,format="%Y%m%d%H%M%S"), y=bidpx1))

> g<-g+geom_line(aes(group=securityid,colour=securityid))

> g<-g+xlab('tradetime')+ylab('bidpx1')

> ggsave(g,file="01.png",width=12,height=8)

#

图片输出到文件

图6-1　一周走势

对于一周的走势我们看不太清楚，再画出数据两个产品在2013年07月26日一天的走势，所图6-2所示。

> bidpx1<-rhive.query("SELECT securityid,concat(tradedate,tradetime) as tradetime,bidpx1 FROM t_reverse_repurchase WHERE tradedate=20130726");

> g<-ggplot(data=bidpx1, aes(x=as.POSIXct(tradetime,format="%Y%m%d%H%M%S"),y=bidpx1))

> g<-g+geom_line(aes(group=securityid,colour=securityid))

> g<-g+xlab('tradetime')+ylab('bidpx1')

> ggsave(g,file="02.png",width=12,height=8)

图6-2　一天走势

6.6.4　策略模型及实现

打印出两幅图片的两条曲线，我们可以看到131810一直在追随204001的变化，并且大部情况都低于204001。下面做一个简单的相关性的策略分析，即通过204001变化，判断131810的卖点。

1.把131810和204001按每分钟标准化。

2.设置当131810和204001有交点的时候，提取卖出信号。

3.当131810和204001的后一个交点的卖一价格大于前一个交点的卖一价格10%以上，则把后一交点作为局部最优的卖出信号点。

提取131810和204001的数据，存储在t_reverse_repurchase表中：

> library(RHive) #

登录R

> rhive.init()

> rhive.connect("c1.wtmart.com")

> rhive.drop.table("t_reverse_repurchase") #

提取131810

和204001

的数据

> rhive.query("CREATE TABLE t_reverse_repurchase AS SELECT tradedate,tradetime,securityid,bidpx1,bidsize1,offerpx1,offersize1 FROM t_hft_day where securityid in (131810,204001)");

> rhive.query("select count(1),tradedate from t_reverse_repurchase group by tradedate") #

查看数据集

X_c0 tradedate

1  4840  20130627

2  4792  20130628

3  4677  20130701

4  3124  20130702

5  2328  20130703

6  3787  20130704

7  4294  20130705

8  4977  20130708

9  4568  20130709

10 6619  20130710

11 5633  20130712

12 6159  20130715

13 5918  20130716

14 6200  20130719

15 6074  20130722

16 5991  20130723

17 5899  20130724

18 5346  20130725

19 6192  20130726

获得一天的数据并做ETL。首先加载软件包：

library

（ggplot2

）

library

（scales

）

library

（plyr

）

把一周的数据加载到内存：

bidpx1<-rhive.query(paste("SELECT securityid,tradedate,tradetime,bidpx1 FROM

t_reverse_repurchase WHERE tradedate>=20130722"));

加载一天的数据并做ETL：

oneDay<-function(date){

d1<-bidpx1[which(bidpx1$tradedate==date),]

d1$tradetime2<-round( as.numeric(as.character(d1$tradetime))/100)*100

d1$tradetime2[which(d1$tradetime2<100000)]<-paste(0,d1$tradetime2[which(d1$tra

detime2<100000)],sep="")

d1$tradetime2[which(d1$tradetime2=='1e+05')]='100000'

d1$tradetime2[which(d1$tradetime2=='096000')]='100000'

d1$tradetime2[which(d1$tradetime2=='106000')]='110000'

d1$tradetime2[which(d1$tradetime2=='126000')]='130000'

d1$tradetime2[which(d1$tradetime2=='136000')]='140000'

d1$tradetime2[which(d1$tradetime2=='146000')]='150000'

d1

}

通过均值标准化：

meanScale<-function(d1){

ddply(d1, .(securityid,tradetime2), summarize, bidpx1=mean(bidpx1))

}

找到要分析的点：

bigger_point<-function(a1,a2){

idx<-c()

for(i in intersect(a1$tradetime2,a2$tradetime2)){

i1<-which(a1$tradetime2==i)

i2<-which(a2$tradetime2==i)

if(a1$bidpx1[i1]-a2$bidpx1[i2]>=-0.02){

idx<-c(idx,i1)

}

}

idx

}

去掉连续的索引值：

remove_continuous_point<-function(idx){

idx[-which(idx-c(NA,rev(rev(idx)[-1]))==1)]

}

idx<-bigger_point(a1,a2)

remove_continuous_point(idx)

}

发现局部最优卖出点：

findOptimize<-function(d3){

idx2<-which((d3$bidpx1-c(NA,rev(rev(d3$bidpx1)[-1])))/d3$bidpx1>0.1)

if(length(idx2)<1)

print("No Optimize point")

d3[idx2,]

}

画图查看结果，代码如下：

draw<-function(d2,d3,d4,date,png=FALSE){

g<-ggplot(data=d2, aes(x=strptime(paste(date,tradetime2,sep=""),format="%Y%m%d%H%M%S"), y=bidpx1))

g<-g+geom_line(aes(group=securityid,colour=securityid))

g<-g+geom_point(data=d3,aes(size=1.5,colour=securityid)) if(nrow(d4)>0){

g<-g+geom_text(data=d4,aes(label= format(d4$bidpx1,digits=4)),colour="blue",hjust=0, vjust=0)

}

g<-g+xlab('tradetime')+ylab('bidpx1')

if(png){

ggsave(g,file=paste(date,".png",sep=""),width=12,height=8)

}else{

g

}

}

下面进行执行策略封装：

date<-20130722

d1<-oneDay(date)

d2<-meanScale(d1)

a1<-d2[which(d2$securityid==131810),]

a2<-d2[which(d2$securityid==204001),]

d3<-d2[findPoint(a1,a2),]

d4<-findOptimize(d3)

draw(d2,d3,d4,as.character(date),TRUE)

下面分别生成一周数据的5张图，20130722对应图6-3、20130723对应图6-4、20130724对应图6-5、20130725对应图6-6和20130726对应图6-7。对这一周数据的回测，在图中显示了程序化策略给出的信号点，都是比较理想的卖出时机，比我自己在当天操作时卖出的利率要高不少。

通过对一周数据的比较我们发现，这个简单的策略能给我们带来一些收益。虽然不是全局最优的，但比人为的判断会更有依据。

图6-3　2013年07月22日策略可视化

图6-4　2013年07月23日策略可视化

图6-5　2013年07月24日策略可视化

图6-6　2013年07月25日策略可视化

图6-7　2013年07月26日策略可视化

第7章　RHadoop

本章主要介绍了使用R语言通过RHadoop工具访问Hadoop集群，帮助读者实现用R进行HDFS的管理，MapReduce程序开发和HBase数据访问。本章使用R语言实现了基于物品的协同过滤算法的MapReduce程序案例，比Java程序更精炼。

7.1　R语言为Hadoop注入统计血脉

问题

R为什么要结合Hadoop？

引言

R语言和Hadoop分别属于两个不同的学科，有着两类完全不同的用户，基于两种不同的知识体系，做着不同的事情。但数据却是它们的交集，数据让R语言结合Hadoop成为一种跨学科选择，一种挖掘数据价值的工具。

7.1.1　Hadoop介绍

对于IT界的人员来说，Hadoop算是耳熟能详的技术了。Hadoop是一个分布式系统基础架构，由Apache基金会管理。用户可以在不了解分布式底层细节的情况下开发分布式程序，充分利用集群的威力高速运算和存储。Hadoop实现了一个分布式文件系统（Hadoop Distributed File System），简称HDFS。HDFS有着高容错性的特点，并且设计用来部署在低廉的硬件上。而且它提供高传输率来访问应用程序的数据，适合那些有着超大数据集的应用程序。

Hadoop的家族成员众多，包括Hive、HBase、Zookeeper、Avro、Pig、Ambari、Sqoop、Mahout、Chukwa等，下面分别一句话介绍这些Hadoop家族的成员。

·Hive是基于Hadoop的一个数据仓库工具，可以将结构化的数据文件映射为一张数据库表，通过类SQL语句快速实现简单的MapReduce统计，不必开发专门的MapReduce应用，十分适合数据仓库的统计分析。

·Pig是一个基于Hadoop的大规模数据分析工具，它提供了SQL-LIKE语言，即Pig Latin，该语言的编译器会把类SQL的数据分析请求转换为一系列经过优化处理的MapReduce运算。

·HBase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统，利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。

·Sqoop是一个用来将Hadoop和关系型数据库中的数据相互转移的工具，可以将一个关系型数据库（MySQL、Oracle、Postgres等）中的数据导进到Hadoop的HDFS中，也可以将HDFS的数据导进到关系型数据库中。

·Zookeeper是一个为分布式应用所设计的分布的、开源的协调服务，它主要是用来解决分布式应用中经常遇到的一些数据管理问题，简化分布式应用协调及其管理的难度，提供高性能的分布式服务。

·Mahout是基于Hadoop的机器学习和数据挖掘的一个分布式框架。Mahout用MapReduce实现了部分数据挖掘算法，解决了并行挖掘的问题。

·Avro是一个数据序列化系统，设计用于支持数据密集型、大批量数据交换的应用。Avro是新的数据序列化格式与传输工具，将逐步取代Hadoop原有的IPC机制。

·Ambari是一种基于Web的工具，支持Hadoop集群的供应、管理和监控。

·Chukwa是一个开源的用于监控大型分布式系统的数据收集系统，它可以将各种类型的数据收集成适合Hadoop处理的文件保存在HDFS中供Hadoop进行各种MapReduce操作。

自2006年Hadoop以MapReduce和HDFS独立发展开始到2013年，不过7年时间，Hadoop的家族已经孵化出多个Apache的顶级项目。特别是最近两三年，发展速度越来越快，又融入了很多新技术，比如YARN、Hcatalog、Oozie、Cassandra），都有点让我们学不过来了。关于Hadoop家族的更多介绍，可以参考笔者的博客“Hadoop家族系列文章”。

7.1.2　为什么要让Hadoop结合R语言

R语言和Hadoop在各自领域中都是明星，而且发挥着重要的作用。当说起Hadoop结合R语言的时候，很多软件开发人员从计算机的角度，都会提出下面2个问题。

·问题1：Hadoop的家族如此之强大，为什么还要结合R语言？

·问题2：Hadoop家族中Mahout同样可以做数据挖掘和机器学习，和R语言的区别是什么？

下面我尝试着做一个解答。

1.Hadoop的家族如此强大，为什么还要结合R语言？

（1）Hadoop家族的强大之处，在于对大数据的处理，让原来的不可能的GB、TB、PB级数据量的计算任务，成为了可能。

（2）R语言的强大之处，在于统计分析。在没有Hadoop之前，我们对于大数据的处理，要取样本，假设检验，做回归，长久以来R语言都是统计学家专属的工具。

（3）从上面两点我们可以看出，Hadoop重点是全量数据分析，而R语言重点是样本数据分析，所以把两种技术放在一起，刚好是取长补短！

下面模拟一个场景：对1PB的新闻网站访问日志做分析，预测未来流量变化。具体实施过程，可能分为下面4步。

·用R语言进行样本数据分析，对业务目标构建回归模型，并定义指标。

·用Hadoop从海量日志数据中，提取全量指标数据。

·再用R语言，对指标数据进行验证和调优。

·最后用Hadoop分步式算法，重写R语言的模型，部署上线。

这个场景中，R和Hadoop分别都起着非常重要的作用。如果按照程序员的思维模式，所有事情都用Hadoop去做，就会没有数据建模和证明的过程，“预测的结果”一定是有问题的。如果按照统计学家的思维模式，所有的事情都用R去做，以抽样方式，得到的“预测的结果”也是有偏差的。所以让R语言结合Hadoop是工业界的必然的导向，也是工业界和学术界的交集，同时也为交叉学科的研究人员提供了无限广阔的想象空间。

2.Mahout同样可以做数据挖掘和机器学习，和R语言的区别是什么？

（1）Mahout是基于Hadoop的数据挖掘和机器学习的算法框架，Mahout的重点同样是解决大数据的计算的问题。

（2）Mahout目前已支持的算法包括协同过滤、推荐算法、聚类算法、分类算法、LDA、朴素Bayes、随机森林等，Mahout的算法中大部分都是基于距离的算法，可以通过矩阵分解后，充分利用MapReduce的并行计算框架，高效地完成计算任务。

（3）Mahout有很多空白点，许多数据挖掘算法是很难实现MapReduce并行化的。而且，Mahout的现有模型都是通用计算模型，直接用到项目中，计算结果只会比随机结果好一些。Mahout二次开发，要求有深厚的Java和Hadoop的技术基础，最好兼有线性代数、概率统计、算法导论等领域的基础知识，所以想玩转Mahout也不是一件容易的事情。

（4）R语言同样提供了Mahout支持的绝大多数算法（专有算法除外），并且还有大量的Mahout不支持的算法，算法的增长速度比Mahout快N倍。并且开发简单，参数配置灵活，对小型数据集运算速度非常快。

虽然，Mahout同样可以做数据挖掘和机器学习，但是和R语言的擅长领域并不重合。集百家之长，在适合的领域选择合适的技术，才能真正地保质保量做好软件。

7.1.3　如何让Hadoop结合R语言

从上文的介绍我们可以了解到，Hadoop和R语言是可以互补互助的，但从目前的企业应用来看，R语言和Hadoop仍然都是在独立使用。不过一旦市场有需求，自然会有人来填补这个空白点。到底如何让Hadoop结合R语言呢？我列出5种结合的思路。

1.RHadoop

RHadoop是一款Hadoop和R语言结合的产品，由RevolutionAnalytics公司开发，并将代码开源到Github社区上面。RHadoop包含三个R包（rmr，rhdfs，rHBase），分别是对应Hadoop系统架构中的MapReduce、HDFS、HBase三个部分。关于RHadoop的安装和使用，在本章中会有详细的介绍。

2.RHive

RHive是一款通过R语言直接访问Hive的工具包，是由NexR一个韩国公司研发的。关于RHive的安装和使用，在6.5节和6.6节已经介绍过了。

3.重写Mahout

用R语言重写Mahout的实现也是一种结合的思路，我也做过相关的尝试，在本书的姊妹篇《R的极客理想—高级开发篇》中会有详细介绍。

4.Hadoop调用R

上面说的都是R如何调用Hadoop，当然我们也可以反相操作，打通Java和R的连接通道，让Hadoop调用R的函数。目前，这部分还没有商家做出成形的产品。

我写了3个例子让Java可以调用R，大家可以自己尝试着结合，做出不一样的应用来，请参考4.1节、4.2节和4.3节。

5.R和Hadoop在实际中的案例

R和Hadoop的结合，技术门槛还是有点高的。对于一个人来说，不仅要掌握Linux、Java、Hadoop、R的技术，还要具备软件开发、算法、概率统计、线性代数、数据可视化、行业背景等一些基本素质。在公司部署这套环境，同样需要多个部门，多种人才的配合。Hadoop运维、Hadoop算法研发、R语言建模、R语言MapReduce开发、软件测试等，所以，这样的案例并不太多。我做过一些尝试和努力，本书介绍了3个项目的实现思路，参见6.6节、7.3节和7.4节。

7.1.4　展望未来

对于R和Hadoop的结合，在近几年，肯定会生成爆发式的增长的。但由于跨学科会造成技术壁垒，人才会远远跟不上市场的需求。所以，肯定会有更多的大数据工具被开发出来！

7.2　RHadoop安装与使用

问题

R如何连接Hadoop？

引言

RHadoop产品第一次实现了R语言与Hadoop的结合，进行海量数据分析。Hadoop主要用来存储海量数据，R语言完成MapReduce算法，用来替代Java的MapReduce实现。有了RHadoop可以让广大的R语言爱好者，有更强大的工具处理大数据，10G、100G、TB、PB，由于大数据所带来的单机性能问题可能会一去不复返了。但对于单独的R语言使用者、Java使用者或者Hadoop使用者来说，同时具备三种知识并不容易。

7.2.1　环境准备

首先是环境准备，这里我选择了Linux Ubuntu操作系统12.04的64位版本，大家可以根据自己的使用习惯选择顺手的Linux。

Java（JDK）一定要用Oracle SUN官方的版本，请从官网下载（http://www.oracle.com/technetwork/java/javase/downloads/index.html）。Linux Ubuntu操作系统的自带的OpenJDK会有各种不兼容。JDK请选择1.6.x的版本，JDK1.7版本也可能会有不兼容情况。R语言环境请安装R 2.15.3版本，R 2.14版本和R 3.0以上版本不能够兼容RHadoop。本节使用的系统环境是：

·Linux Ubuntu 12.04.2LTS 64bit server

·R 2.15.364bit

·Java JDK 1.6.x

·Hadoop 1.1.2

·IP:192.168.1.243

RHadoop只支持Linux系统环境。关于Hadoop的安装和配置，请参考附录F。

7.2.2　RHadoop安装

RHadoop项目包含三个R包（rmr、hdfs和rHBase），由于这三个库不能在CRAN中找到，所以需要自己下载。

1.下载RHadoop相关的3个程序包

这三个程序包是rmr-2.1.0、rhdfs-1.0.5和rHBase-1.1，下载地址为https://github.com/RevolutionAnalytics/RHadoop/wiki/Downloads。虽然RHadoop已经推出Windows版本的rhdfs和rmr2，但作为Hadoop的应用，还是建议大家使用Linux。

2.RHadoop的安装，建议使用root权限操作

~ sudo

–i #

切换到root

用户

~ whoami

root

~ mv rmr-2.1.0.tar.gz /root/R #

复制程序包到目录/root/R

~ mv rhdfs-1.0.5.tar.gz /root/R

~ mv rHBase-1.1.tar.gz /root/R

接下来，我们需要先安装这三个库的依赖库。

3.安装依赖库

由于RHadoop项目依赖于Java的环境，所以我们先要安装rJava，在4.3节我们已经安装过了rJava，这里不多解释。

~ R CMD javareconf #

在R

运行环境中配置Java

的环境变量

~ R #

启动R

程序

> install.packages("rJava") #

安装rJava

包

然后，还要安装其他的几个依赖库，reshape2、Rcpp、iterators、itertools、digest、RJSONIO、functional，通过install.packages()函数都可以直接安装。

> install.packages("reshape2")

> install.packages("Rcpp")

> install.packages("iterators")

> install.packages("itertools")

> install.packages("digest")

> install.packages("RJSONIO")

> install.packages("functional")

4.安装rhdfs库

接下来安装rhdfs库，在环境变量中增加HADOOP_CMD和HADOOP_STREAMING两个变量，可以用export在当前命令窗口中增加。但为下次方便使用，最好把变量增加到系统环境变量/etc/environment文件中。再用R CMD INSTALL命令安装rhdfs包就可以顺利完成了。

在当前运行环境中，设置环境变量。

~ export HADOOP_CMD=/root/hadoop/hadoop-1.1.2/bin/hadoop

~ export HADOOP_STREAMING=/root/hadoop/hadoop-1.1.2/contrib/streaming/hadoopstreaming-1.1.2.jar

把环境变量写到/etc/environmen文件。

~ sudo vi /etc/environment

HADOOP_CMD=/root/hadoop/hadoop-1.1.2/bin/hadoop

HADOOP_STREAMING=/root/hadoop/hadoop-1.1.2/contrib/streaming/hadoop-streaming-1.1.2.jar

~ . /etc/environment #

让环境变量生效

~ R CMD INSTALL /root/R/rhdfs_1.0.5.tar.gz #

安装rhdfs

库

5.安装rmr库

安装rmr库，使用R CMD INSTALL命令就可以顺利完成了。

~ R CMD INSTALL rmr2_2.1.0.tar.gz

6.安装rHBase库

安装rHBase库，会在7.5节介绍，这里暂时跳过。

7.列出所有的安装包

最后，我们可以查看一下，RHADOOP都安装了哪些库。

~ ls /disk1/system/usr/local/lib/R/site-library/

digest functional iterators itertools plyr Rcpp reshape2 rhdfs rJava

RJSONIO rmr2 stringr

由于我的硬盘是外接的，使用mount和软连接（ln-s）挂载了R类库的目录，所以是R的类库在/disk1/system目录下。一般R的类库目录是/usr/lib/R/site-library或者/usr/local/lib/R/site-library，用户也可以使用whereis R的命令查询自己电脑上R类库的安装位置。

7.2.3　RHadoop程序开发

安装好rhdfs和rmr2两个包后，我们就可以使用R尝试Hadoop的操作了。

1.rhdfs包的基本操作

rhdfs包的使用。

> library(rhdfs) #

加载rhdfs

包

Loading required package: rJava

HADOOP_CMD=/root/hadoop/hadoop-1.1.2/bin/hadoop

Be sure to run hdfs.init()

> hdfs.init() #

初始化R

与Hadoop

建立连接

对比Hadoop命令和RHadoop函数的操作。

（1）查看hdfs文件目录

·Hadoop的命令：hadoop fs-ls/user

·R语言函数：hdfs.ls("/user/")

用Hadoop命令查看Hadoop目录。

~hadoop fs -ls /user

Found 4 items

drwxr-xr-x -root supergroup

0

2013-02-01

12:15

/user/conan

drwxr-xr-x -root supergroup

0

2013-03-06

17:24

/user/hdfs

drwxr-xr-x -root supergroup

0

2013-02-26

16:51

/user/hive

drwxr-xr-x -root supergroup

0

2013-03-06

17:21

/user/root

rhdfs函数查看Hadoop目录。

>

hdfs.ls

（"/user/"

）

permission  owner

group size

modtime

file

1

drwxr-xr-x

root

supergroup

0

2013-02-01

12:15

/user/conan

2

drwxr-xr-x

root

supergroup

0

2013-03-06

17:24

/user/hdfs

3

drwxr-xr-x

root

supergroup

0

2013-02-26

16:51

/user/hive

4

drwxr-xr-x

root

supergroup

0

2013-03-06

17:21

/user/root

（2）查看Hadoop数据文件

·Hadoop的命令：hadoop fs-cat/user/hdfs/o_same_school/part-m-00000

·R语言函数：hdfs.cat("/user/hdfs/o_same_school/part-m-00000")

用Hadoop命令查看Hadoop数据文件。

~hadoop fs -cat /user/hdfs/o_same_school/part-m-00000

10,3,tsinghua university,2004-05-2615:21:00.0

23,4007,

北京第一七一中学,2004-05-3106:51:53.0

51,4016,

大连理工大学,2004-05-2709:38:31.0

89,4017,Amherst College,2004-06-0116:18:56.0

92,4017,

斯坦福大学,2012-11-2810:33:25.0

99,4017,Stanford University Graduate School of Business,2013-02-1912:17:15.0

通过rhdfs函数查看Hadoop数据文件。

>

hdfs.cat

（"/user/hdfs/o_same_school/part-m-00000"

）

[1]

"10,3,tsinghua university,2004-05-2615:21:00.0"

[2]

"23,4007,

北京第一七一中学,2004-05-3106:51:53.0"

[3]

"51,4016,

大连理工大学,2004-05-2709:38:31.0"

[4]

"89,4017,Amherst College,2004-06-0116:18:56.0"

[5]

"92,4017,

斯坦福大学,2012-11-2810:33:25.0"

[6]

"99,4017,Stanford University Graduate School of Business,2013-02-1912:17:15.0"

2.rmr算法的任务

下面介绍rmr2包的使用，同时比较普通的R程序与基于Hadoop的R程序。

> library

（rmr2

）#

加载 rmr2

包

Loading required package: Rcpp

Loading required package: RJSONIO

Loading required package: digest

Loading required package: functional

Loading required package: stringr

Loading required package: plyr

Loading required package: reshape2

普通的R语言程序：

> small.ints =1:10

> sapply

（small.ints,function

（x

）x^2

）

[1]

1

4

9

16

25

36

49

64

81

100

基于Hadoop的R语言程序：

> small.ints = to.dfs(1:10)

13/03/07 12:12:55 INFO util.NativeCodeLoader: Loaded the native-hadoop library

13/03/07 12:12:55 INFO zlib.ZlibFactory: Successfully loaded & initialized

native-zlib library

13/03/07 12:12:55 INFO compress.CodecPool: Got brand-new compressor

> mapreduce(input = small.ints, map = function(k, v) cbind(v, v^2)) #

执行Map

任务

packageJobJar: [/tmp/RtmpWnzxl4/rmr-local-env5deb2b300d03, /tmp/RtmpWnzxl4/

rmr-global-env5deb398a522b, /tmp/RtmpWnzxl4/rmr-streaming-map5deb1552172d, /root/

hadoop/tmp/hadoop-unjar7838617732558795635/] [] /tmp/

streamjob4380275136001813619.jar tmpDir=null

13/03/07 12:12:59 INFO mapred.FileInputFormat: Total input paths to process : 1

13/03/07 12:12:59 INFO streaming.StreamJob: getLocalDirs(): [/root/hadoop/tmp/

mapred/local]

13/03/07 12:12:59 INFO streaming.StreamJob: Running job: job_201302261738_0293

13/03/07 12:12:59 INFO streaming.StreamJob: To kill this job, run:

13/03/07 12:12:59 INFO streaming.StreamJob: /disk1/hadoop/hadoop-1.1.2/

libexec/../bin/hadoop job  -Dmapred.job.tracker=hdfs://192.168.1.243:9001 -kill

job_201302261738_0293

13/03/07 12:12:59 INFO streaming.StreamJob: Tracking URL:

http://192.168.1.243:50030/jobdetails.jsp?jobid=job_201302261738_0293

13/03/07 12:13:00 INFO streaming.StreamJob:  map 0%  reduce 0%

13/03/07 12:13:15 INFO streaming.StreamJob:  map 100%  reduce 0%

13/03/07 12:13:21 INFO streaming.StreamJob:  map 100%  reduce 100%

13/03/07 12:13:21 INFO streaming.StreamJob: Job complete: job_201302261738_0293

13/03/07 12:13:21 INFO streaming.StreamJob: Output: /tmp/RtmpWnzxl4/file5deb791fcbd5

> from.dfs("/tmp/RtmpWnzxl4/file5deb791fcbd5")  #

查看hdfs

输出的结果

$key

NULL

$val

v

[1,]  1   1

[2,]  2   4

[3,]  3   9

[4,]  4  16

[5,]  5  25

[6,]  6  36

[7,]  7  49

[8,]  8  64

[9,]  9  81

[10,] 10 100

因为MapReduce只能访问HDFS文件系统，所以首先需要用to.dfs()函数把数据存储到HDFS文件系统里。MapReduce的运算结果再用from.dfs()函数从HDFS文件系统中取出。

3.rmr算法的wordcount任务

用rmr2实现wordcount任务，对文件中的单词计数。

我在HDFS上提前放置了数据文件/user/hdfs/o_same_school/part-m-00000。实现基于MapReduce的wordcount()函数，执行wordcount()函数，最后用from.dfs()函数从HDFS中取得结果。

> input<- '/user/hdfs/o_same_school/part-m-00000'     #

定义Hadoop

上数据文件的位置

> wordcount = function(input, output = NULL, pattern = " "){  # wordcount

算法函数

wc.map = function(., lines) {     # map

函数

keyval(unlist( strsplit( x = lines,split = pattern)),1)

}

wc.reduce =function(word, counts ) {   # reduce

函数

keyval(word, sum(counts))

}

mapreduce(input = input ,output = output, input.format = "text",

# mapreduce

计算函数

map = wc.map, reduce = wc.reduce,combine = T)

}

> wordcount(input)  #

执行wordcount

任务

> from.dfs("/tmp/RtmpfZUFEa/file6cac626aa4a7")  #

查看hdfs

输出的结果

$key

[1] "-"

[2] "04:42:37.0"

[3] "06:51:53.0"

[4] "07:10:24.0"

[5] "09:38:31.0"

[6] "10:33:25.0"

[7] "10,3,tsinghua"

[8] "10:42:10.0"

[9] "113,4017,Stanford"

[10] "12:00:38.0"

$val

$val

[1] 1 2 1 2 1 1 1 4 1 1 2 1 1 1 1 2 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

[39] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

这样，就完成了RHadoop中的rhdfs和rmr2包的安装与使用。虽然RHadoop配置起来有点麻烦，但看到R语言在现实MapReduce过程的精炼的代码，还是值得的。

7.3　RHadoop实验：统计邮箱出现次数

问题

RHadoop怎么用？

引言

通过RHadoop我们可以用R语言实现MapReduce的开发，比起上百行的Java代码来说，R的代码是这么的简洁。本节将介绍一个统计需求案例。

7.3.1　需求描述

本节使用的系统环境：

·Linux Ubuntu 12.04.2LTS 64bit server

·R 2.15.364bit

·Java JDK 1.6.x

·Hadoop 1.0.3

通过给定的实验数据，计算邮箱域出现了多少次？并按邮箱域出现次数从大到小排序。用RHadoop实现MapReduce算法。实验数据为hadoop15.txt。

wolys@21cn.com

zss1984@126.com

294522652@qq.com

simulateboy@163.com

zhoushigang_123@163.com

sirenxing424@126.com

lixinyu23@qq.com

chenlei1201@gmail.com

370433835@qq.com

cxx0409@126.com

viv093@sina.com

q62148830@163.com

65993266@qq.com

要求输出计算结果，如下格式。

163.com,14

sohu.com,2

7.3.2　算法实现

1.计算邮箱域出现了多少次

~ R  #

启动R

程序

> library(rhdfs)

> library(rmr2)

> data<-read.table(file="hadoop15.txt")  #

从本地读入数据到内存

> d0<-to.dfs(keyval(1, data))   #

从内存把数据上传到hdfs

> from.dfs(d0)   #

查看HDFS

上面的d0

的数据

$key

[1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

[39] 1 1 1

$val

V1

1 wolys@21cn.com

2 zss1984@126.com

3 294522652@qq.com

4 simulateboy@163.com

5 zhoushigang_123@163.com

6 sirenxing424@126.com

7 lixinyu23@qq.com

8 chenlei1201@gmail.com

9 370433835@qq.com

10 cxx0409@126.com

自定义mr()函数，计算邮箱域出现了多少次。

> mr<-function(input=d0){     #

创建mr()

函数

+    map<-function(k,v){

+    #

截取字符串@

后面的部分

+        keyval(word(as.character(v$V1), 2, sep = fixed('@')),1)

+    }

+    reduce =function(k, v ) {

+    #

对有相同邮箱域名计数求和

+        keyval(k, sum(v))

+    }

+    d1<-mapreduce(input=input,map=map,reduce=reduce,combine=TRUE)

+ }

> d1<-mr(d0)   #

运行mr()

函数

> from.dfs(d1)   #

查看HDFS

上面的d0

的数据

$key

[1] "126.com" "163.com" "21cn.com" "gmail.com" "qq.com"

[6] "sina.com" "sohu.com" "yahoo.cn" "yahoo.com.cn"

$val

[1] 9 14 1 1 9 2 2 1 2

2.按次数从大到小排序

自定义sort()函数，按域名出现次数进行排序。

> sort<-function(input=d1){   #

创建sort()

函数

+    map<-function(k,v){

+    #

对上一步d1

的结果集进行格式化

+        keyval(1,data.frame(k,v))

+    }

+    reduce<-function(k,v){

+    #

对数据集进行排序

+        v2<-v[order(as.integer(v$v),decreasing=TRUE),]

+        keyval(1,v2)

+    }

+    d2<-mapreduce(input=input,map=map,reduce=reduce,combine=TRUE)

+ }

> d2<-sort(d1)   #

执行排序

> result<-from.dfs(d2)   #

把结果从HDFS

上读入内存

> result$val   #

输出排序后的结果

k v

2 163.com 14

1 126.com 9

5 qq.com 9

6 sina.com 2

7 sohu.com 2

9 yahoo.com.cn 2

3 21cn.com 1

4 gmail.com 1

8 yahoo.cn 1

注意，第二步排序的reduce过程，本节使用了基于单节点的写法，对于海量数据的情况这个代码还需要优化。短短的几行代码，就完成功能，从代码上看，比Java好看太多了！

7.4　RHadoop实现基于MapReduce的协同过滤算法

问题

RHadoop如何实现基于MapReduce的协同过滤算法？

引言

由于RHadoop的rmr2包对Hadoop操作有一些特殊性，所以代码实现有一定难度。需要深入学习的同学，请多尝试并思考MapReduce算法中的key/value值的设计。

7.4.1　基于物品推荐的协同过滤算法介绍

越来越多的互联网应用，都开始设计自己的推荐系统。推荐算法主要使用协同过滤算法，根据用户活跃度和物品流行度，可以分为基于用户的协同过滤算法和基于物品的协同过滤算法。

·基于用户的协同过滤算法，是给用户推荐和他兴趣相似的其他用户喜欢的物品。

·基于物品的协同过滤算法，是给用户推荐和他之前喜欢的物品相似的物品。

基于物品的协同过滤算法，是目前广泛使用的一种推荐算法，像Netflix、YouTube、Amazon等很多的互联网公司都在使用。设计推荐算法主要分为两步：

（1）计算物品之间的相似度；

（2）根据物品的相似度和用户的历史行为给用户生成推荐列表。

为了介绍算法模型，我们选择一组很小的测试数据集。该测试数据集摘自《Mahout In Action》一书第49页，原数据集中第8行，“3,101,2.5”改为“3,101,2.0”，数据集中每行3个字段，依次是用户ID、物品ID、对物品的评分。在服务上创建数据文件small.csv。

~ vi small.csv   #

用vi

编辑数据文件

1,101,5.0

1,102,3.0

1,103,2.5

2,101,2.0

2,102,2.5

2,103,5.0

2,104,2.0

3,101,2.0

3,104,4.0

3,105,4.5

3,107,5.0

4,101,5.0

4,103,3.0

4,104,4.5

4,106,4.0

5,101,4.0

5,102,3.0

5,103,2.0

5,104,4.0

5,105,3.5

5,106,4.0

7.4.2　R语言本地程序实现

首先，通过R语言实现基于物品的协同过滤算法，与RHadoop实现进行对比。这里我使用《Mahout In Action》一书第6章介绍的分步式基于物品的协同过滤算法进行实现。算法的思想，分为3步实现。首先建立物品的同现矩阵，其次建立用户对物品的评分矩阵，然后矩阵计算推荐结果。

1.建立物品的同现矩阵

按用户分组，找到每个用户所选的物品单独出现计数及两两一组计数。例如：用户ID为3的用户，分别给101、104、105、107这4个物品打分。

（1）(101,101),(104,104),(105,105),(107,107)，单独出现计算各加1。

（2）(101,104),(101,105),(101,107),(104,105),(104,107),(105,107)，两个一组计数各加1。

（3）最后，把所有用户的计算结果求和，生成一个三角矩阵，再补全三角矩阵，就建立了物品的同现矩阵。如下面矩阵所示：

[101] [102] [103] [104] [105] [106] [107]

[101]   5     3     4     4     2     2     1

[102]   3     3     3     2     1     1     0

[103]   4     3     4     3     1     2     0

[104]   4     2     3     4     2     2     1

[105]   2     1     1     2     2     1     1

[106]   2     1     2     2     1     2     0

[107]   1     0     0     1     1     0     1

2.建立用户对物品的评分矩阵

按用户分组，找到每个用户所选的物品及评分，例如，用户ID为3的用户，分别给(3,101,2.0)、(3,104,4.0)、(3,105,4.5)、(3,107,5.0)这4个物品打分。

（1）找到物品评分(3,101,2.0),(3,104,4.0),(3,105,4.5),(3,107,5.0)。

（2）建立用户对物品的评分矩阵。

U3

[101] 2.0

[102] 0.0

[103] 0.0

[104] 4.0

[105] 4.5

[106] 0.0

[107] 5.0

3.矩阵计算推荐结果

同现矩阵*评分矩阵=推荐结果，如图7-1所示。推荐给ID为3的用户的结果是(103,24.5),(102,18.5),(106,16.5)。

图7-1　矩阵乘法（摘自《Mahout In Action》）

按照上面的描述过程，我们用R语言代码进行实现：

> library(plyr)   #

加载plyr

包

> train<-read.csv(file="small.csv",header=FALSE)   #

读取数据集

> names(train)<-c("user","item","pref")

> train   #

查看train

数据集

user item pref

1 1    101  5.0

2 1    102  3.0

3 1    103  2.5

4 2    101  2.0

5 2    102  2.5

6 2    103  5.0

7 2    104  2.0

8 3    101  2.0

9 3    104  4.0

10 3    105  4.5

11 3    107  5.0

12 4    101  5.0

13 4    103  3.0

14 4    104  4.5

15 4    106  4.0

16 5    101  4.0

17 5    102  3.0

18 5    103  2.0

19 5    104  4.0

20 5    105  3.5

21 5    106  4.0

> usersUnique<-function(){   #

计算用户列表

+     users<-unique(train$user)

+     users[order(users)]

+ }

> itemsUnique<-function(){   #

计算商品列表方法

+     items<-unique(train$item)

+     items[order(items)]

+ }

> users<-usersUnique()   #

用户列表

> users

[1] 1 2 3 4 5

> items<-itemsUnique()   #

商品列表

> items

[1] 101 102 103 104 105 106 107

> index<-function(x) which(items %in% x)   #

建立商品列表索引

> data<-ddply(train,.(user,item,pref),summarize,idx=index(item))

> data

user item pref idx

1 1    101  5.0  1

2 1    102  3.0  2

3 1    103  2.5  3

4 2    101  2.0  1

5 2    102  2.5  2

6 2    103  5.0  3

7 2    104  2.0  4

8 3    101  2.0  1

9 3    104  4.0  4

10 3    105  4.5  5

11 3    107  5.0  7

12 4    101  5.0  1

13 4    103  3.0  3

14 4    104  4.5  4

15 4    106  4.0  6

16 5    101  4.0  1

17 5    102  3.0  2

18 5    103  2.0  3

19 5    104  4.0  4

20 5    105  3.5  5

21 5    106  4.0  6

> cooccurrence<-function(data){   #

同现矩阵

+     n<-length(items)

+     co<-matrix(rep(0,n*n),nrow=n)

+     for(u in users){

+         idx<-index(data$item[which(data$user==u)])

+         m<-merge(idx,idx)

+         for(i in 1:nrow(m)){

+             co[m$x[i],m$y[i]]=co[m$x[i],m$y[i]]+1

+         }

+     }

+     return(co)

+ }

> recommend<-function(udata=udata,co=coMatrix,num=0){  #

推荐算法

+     n<-length(items)+     pref<-rep(0,n)

+     pref[udata$idx]<-udata$pref

+

+     userx<-matrix(pref,nrow=n)   #

用户评分矩阵

+

+

+     r<-co %*% userx  #

同现矩阵*

评分矩阵

+

+     r[udata$idx]<-0   #

推荐结果排序

+     idx<-order(r,decreasing=TRUE)

+     topn<-data.frame(user=rep(udata$user[1],length(idx)),item=items[idx],val=r[idx])

+     topn<-topn[which(topn$val>0),]

+

+     if(num>0){   #

推荐结果取前num

个

+         topn<-head(topn,num)

+     }

+

+     return(topn)   #

返回结果

+ }

> co<-cooccurrence(data)   #

生成同现矩阵

> co

[,1] [,2] [,3] [,4] [,5] [,6] [,7]

[1,]  5    3    4    4    2    2    1

[2,]  3    3    3    2    1    1    0

[3,]  4    3    4    3    1    2    0

[4,]  4    2    3    4    2    2    1

[5,]  2    1    1    2    2    1    1

[6,]  2    1    2    2    1    2    0

[7,]  1    0    0    1    1    0    1

> recommendation<-data.frame()   #

计算推荐结果

> for(i in 1:length(users)){

+     udata<-data[which(data$user==users[i]),]

+     recommendation<-rbind(recommendation,recommend(udata,co,0))

+ }

> recommendation   #

输出推荐结果

user item val

1 1    104  33.5

2 1    106  18.0

3 1    105  15.5

4 1    107  5.0

5 2    106  20.5

6 2    105  15.5

7 2    107  4.0

8 3    103  24.5

9 3    102  18.5

10 3    106  16.5

11 4    102  37.0

12 4    105  26.0

13 4    107  9.5

14 5    107 11.5

7.4.3　R基于Hadoop分步式程序实现

R语言实现的MapReduce算法，可以基于R的数据对象实现，不必如Java一样使用文本存储。算法思想同上面R语言实现思想，略有复杂，具体如下。

（1）建立物品的同现矩阵，首先是按用户分组，得到所有物品出现的组合列表；其次是对物品组合列表进行计数，建立物品的同现矩阵。

（2）建立用户对物品的评分矩阵。

（3）合并同现矩阵和评分矩阵。

（4）计算推荐结果列表。

（5）按输入格式得到推荐评分列表。

通过MapReduce实现时，所有操作都要使用Map和Reduce的任务完成，程序实现过程略有变化，如图7-2所示。下面按照上面列出来的5个步骤，进行矩阵分析。

图7-2　分布式程序设计（摘自《Mahout In Action》）

1.建立物品的同现矩阵

（1）按用户分组，得到所有物品出现的组合列表。

·key:物品列表向量

·val:物品组合向量

$key

[1] 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 102 102 102 102

[20] 102 102 102 103 103 103 103 103 103 103 103 103 103 103 104 104 104 104 104

[39] 104 104 104 104 104 104 104 105 105 105 105 106 106 106 106 107 107 107 107

[58] 101 101 101 101 101 101 102 102 102 102 102 102 103 103 103 103 103 103 104

[77] 104 104 104 104 104 105 105 105 105 105 105 106 106 106 106 106 106

$val

[1] 101 102 103 101 102 103 104 101 104 105 107 101 103 104 106 101 102 103 101

[20] 102 103 104 101 102 103 101 102 103 104 101 103 104 106 101 102 103 104 101

[39] 104 105 107 101 103 104 106 101 104 105 107 101 103 104 106 101 104 105 107

[58] 101 102 103 104 105 106 101 102 103 104 105 106 101 102 103 104 105 106 101

[77] 102 103 104 105 106 101 102 103 104 105 106 101 102 103 104 105 106

（2）对物品组合列表进行计数，建立物品的同现矩阵

·key:物品列表向量

·val:同现矩阵的数据框值(item,item,freq)

矩阵格式要与“2.建立用户对物品的评分矩阵”的格式一致，把异构的两种数据源合并为同一种数据格式，为“3.合并同现矩阵和评分矩阵”做数据基础。

$key

[1] 101 101 101 101 101 101 101 102 102 102 102 102 102 103 103 103 103 103 103

[20] 104 104 104 104 104 104 104 105 105 105 105 105 105 105 106 106 106 106 106

[39] 106 107 107 107 107

$val

k   v   freq

1 101 101 5

2 101 102 3

3 101 103 4

4 101 104 4

5 101 105 2

6 101 106 2

7 101 107 1

8 102 101 3

9 102 102 3

10 102 103 3

11 102 104 2#

省略部分输出

2.建立用户对物品的评分矩阵

·key:物品列表

·val:用户对物品打分矩阵

$key

[1] 101 101 101 101 101 102 102 102 103 103 103 103 104 104

$val

item user pref

1 101  1    5.0

2 101  2    2.0

3 101  3    2.0

4 101  4    5.0

5 101  5    4.0

6 102  1    3.0

7 102  2    2.5

8 102  5    3.0

9 103  1    2.5

10 103  2    5.0

11 103  4    3.0

12 103  5    2.0

13 104  2    2.0

14 104  3    4.0

15 104  4    4.5

3.合并同现矩阵和评分矩阵

这一步操作是MapReduce中比较特殊的，因为数据源是两个异构数据源，进行MapReduce的操作。在第二步时，我们已经把两种格式合并为一样的，第三步使用equijoin这个rmr2包的函数，进行矩阵合并。

·key:NULL

·val:合并的数据框

$key

NULL

$val

k.l v.l freq.l item.r user.r pref.r

1 103 101 4      103    1      2.5

2 103 102 3      103    1      2.5

3 103 103 4      103    1      2.5

4 103 104 3      103    1      2.5

5 103 105 1      103    1      2.5

6 103 106 2      103    1      2.5

7 103 101 4      103    2      5.0

8 103 102 3      103    2      5.0

9 103 103 4      103    2      5.0

10 103 104 3      103    2      5.0

11 103 105 1      103    2      5.0

12 103 106 2      103    2      5.0

13 103 101 4      103    4      3.0

4.计算推荐结果列表

把第三步中的矩阵，进行合并计算，得到推荐结果列表。

·key:物品列表

·val:推荐结果数据框

$key

[1] 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101 101

$val

k.l v.l user.r v

1 101 101 1      25.0

2 101 101 2      10.0

3 101 101 3      10.0

4 101 101 4      25.0

5 101 101 5      20.0

6 101 102 1      15.0

7 101 102 2      6.0

8 101 102 3      6.0

9 101 102 4      15.0

10 101 102 5      12.0

11 101 103 1      20.0

12 101 103 2      8.0

13 101 103 3      8.0

14 101 103 4      20.0

15 101 103 5      16.0

16 101 104 1      20.0

17 101 104 2      8.0

18 101 104 3      8.0

#

省略部分输出

5.按输入格式得到推荐评分列表

对推荐结果列表，进行排序处理，输出排序后的推荐结果。

·key:用户ID

·val:推荐结果数据框

$key

[1] 1 1 1 1 1 1 1 2 2 2 2 2 2 2 3 3 3 3 3 3 3 4 4 4 4 4 4 4 5 5 5 5 5 5 5

$val

user item pref

1 1    101  44.0

2 1    103  39.0

3 1    104  33.5

4 1    102  31.5

5 1    106  18.0

6 1    105  15.5

7 1    107  5.0

8 2    101  45.5

9 2    103  41.5

10 2    104  36.0

11 2    102  32.5

12 2    106  20.5

13 2    105  15.5

14 2    107  4.0

15 3    101  40.0

16 3    104  38.0

17 3    105  26.0

18 3    103  24.5

19 3    102  18.5

20 3    106  16.5

21 3    107  15.5

22 4    101  63.0

23 4    104  55.0

24 4    103  53.5

25 4    102  37.0

26 4    106  33.0

27 4    105  26.0

28 4    107  9.5

29 5    101  68.0

30 5    104  59.0

31 5    103  56.5

32 5    102  42.5

33 5    106  34.5

34 5    105  32.0

35 5    107  11.5

6.rmr2使用提示

（1）rmr.options(backend='hadoop')

这里backend有两个值，hadoop和local。hadoop是默认值，使用Hadoop环境运行程序。local是一个本地测试的设置，已经不建议再使用。我在开发时，试过local设置，运行速度非常快，模拟了Hadoop的运行环境。但是，local模式下的代码，不能和Hadoop模式下完全兼容，变动也比较大，因此不建议大家使用。

（2）equijoin(...,outer=c('left'))

这里outer包括了4个值，c('''',''left'',''right'',''full'')，非常像数据库中两个表的join操作。

（3）keyval(k,v)

用于MapReduce的操作需要key和valve保存数据。如果直接输出或者输出的未加key，会有一个警告：Converting to.dfs argument to keyval with a NULL key。再上一节中，rmr2的例子中就有类似的情况，请大家注意修改代码。

> to.dfs(1:10)

Warning message:

In to.dfs(1:10) : Converting to.dfs argument to keyval with a NULL key

下面展示完整的运行代码：

> library(rmr2)   #

加载rmr2

包

> train<-read.csv(file="small.csv",header=FALSE)  #

输入数据文件

> names(train)<-c("user","item","pref")

> rmr.options(backend = 'hadoop')  #

使用rmr

的hadoop

格式，hadoop

是默认设置。

> train.hdfs = to.dfs(keyval(train$user,train))  #

把数据集存入HDFS

> from.dfs(train.hdfs)   #

打印计算结果

#STEP 1,

建立物品的同现矩阵

# 1)

按用户分组，得到所有物品出现的组合列表。

> train.mr<-mapreduce(

+  train.hdfs,

+  map = function(k, v) {

+    keyval(k,v$item)

+  }

+  ,reduce=function(k,v){

+    m<-merge(v,v)

+    keyval(m$x,m$y)

+  }

+ )

> from.dfs(train.mr)    #

打印计算结果

# 2)

对物品组合列表进行计数，建立物品的同现矩阵

> step2.mr<-mapreduce(

+  train.mr,

+  map = function(k, v) {

+    d<-data.frame(k,v)

+    d2<-ddply(d,.(k,v),count)

+    key<-d2$k

+    val<-d2

+    keyval(key,val)

+  }

+)

> from.dfs(step2.mr)   #

打印计算结果

# 2.

建立用户对物品的评分矩阵

> train2.mr<-mapreduce(

+  train.hdfs,

+  map = function(k, v) {

+    #df<-v[which(v$user==3),]

+    df<-v

+    key<-df$item

+    val<-data.frame(item=df$item,user=df$user,pref=df$pref)

+    keyval(key,val)

+  }

+)

> from.dfs(train2.mr)   #

打印计算结果

#3.

合并同现矩阵和评分矩阵

> eq.hdfs<-equijoin(

+  left.input=step2.mr,

+  right.input=train2.mr,

+  map.left=function(k,v){

+    keyval(k,v)

+  },

+  map.right=function(k,v){

+    keyval(k,v)

+  },

+  outer = c("left")

+)

> from.dfs(eq.hdfs)   #

打印计算结果

# 4.

计算推荐结果列表

> cal.mr<-mapreduce(

+  input=eq.hdfs,

+  map=function(k,v){

+    val<-v

+    na<-is.na(v$user.r)

+    if(length(which(na))>0) val<-v[-which(is.na(v$user.r)),]

+    keyval(val$k.l,val)

+  }

+  ,reduce=function(k,v){

+    val<-ddply(v,.(k.l,v.l,user.r),summarize,v=freq.l*pref.r)

+    keyval(val$k.l,val)

+  }

+)

> from.dfs(cal.mr)   #

打印计算结果

# 5.

按输入格式得到推荐评分列表

> result.mr<-mapreduce(

+  input=cal.mr,

+  map=function(k,v){

+    keyval(v$user.r,v)

+  }

+  ,reduce=function(k,v){

+    val<-ddply(v,.(user.r,v.l),summarize,v=sum(v))

+    val2<-val[order(val$v,decreasing=TRUE),]

+    names(val2)<-c("user","item","pref")

+    keyval(val2$user,val2)

+  }

+)

> from.dfs(result.mr)   #

打印计算结果

本节提供了R语言基于MapReduce的方法实现协同过滤算法的一种思路，算法可能不是最优的，希望大家有时间写出更好的算法来！随着R语言及Hadoop的发展，相信会有越来越多的算法应用会使用这种方式！

如果想对比Java基于MapReduce实现基于物品的协同过滤算法，可以参考作者的博客文章用“Hadoop构建电影推荐系统”和“Mahout分步式程序开发基于物品的协同过滤ItemCF”。

7.5　rHBase安装与使用

问题

R如何访问HBase？

引言

HBase是Hadoop家族中的一个分布式数据库产品，HBase支持高并发读写，列式数据存储，高效的索引，自动分片，自动Region迁移等许多优点，已经越来越多地被业界认可，并被实际应用。

7.5.1　HBase环境准备

首先环境准备，这里继续7.2节中的RHadoop的环境。

·Linux Ubuntu 12.04.2LTS 64bit server

·Java JDK 1.6.0_45

·Hadoop 1.1.2

·HBase-0.94.2

·thrift-0.8.0

关于HBase的安装和配置，请参考附录H。查看HBase服务器环境。用bin/start-HBase.sh命令，启动HBase服务器，默认端口port=60000。

~ /home/conan/hadoop/hadoop-1.1.2/bin/start-all.sh  #

启动Hadoop

~ /home/conan/hadoop/HBase-0.94.2/bin/start-HBase.sh  #

启动HBase

~ /home/conan/hadoop/HBase-0.94.2/bin/HBase-daemon.sh start thrift

#

启动HBase

的Thrift

服务

~ jps  #

查看HBase

进程

12041 HMaster

12209 HRegionServer

13222 ThriftServer

31734 TaskTracker

31343 DataNode

31499 SecondaryNameNode

13328 Jps

31596 JobTracker

11916 HQuorumPeer

31216 NameNode

7.5.2　rHBase安装

在7.2节中，我们已经下载了rHBase-1.1.tar.gz包，暂时跳过了rHBase部分的介绍，接下来我们继续安装rHBase。配置RHadoop的环境，只一条命令就完成rHBase的安装！

~R CMD INSTALL rHBase_1.1.1.tar.gz

7.5.3　rHBase函数库

rHBase支持的函数有16个，可以实现对HBase的各种操作。

1.rHBase的16个函数

下面列出了这16个函数，并与HBase的命令做了对比说明。

hb.compact.table     hb.describe.table    hb.insert             hb.regions.table

hb.defaults          hb.get               hb.insert.data.frame  hb.scan

hb.delete            hb.get.data.frame    hb.list.tables        hb.scan.ex

hb.delete.table      hb.init              hb.new.table          hb.set.table.mode

2.HBase和rHBase的基本操作对比：

建表

HBase Shell:  create 'student_shell','info'

rHBase:hb.new.table("student_rHBase","info")

列出所有表

HBase Shell: list

rHBase:hb.list.tables()

显示表结构

HBase Shell: describe 'student_shell'

rHBase:hb.describe.table("student_rHBase")

插入一条数据

HBase Shell: put 'student_shell','mary','info:age','19'

rHBase: hb.insert("student_rHBase",list(list("mary","info:age", "24")))

读取数据

HBase Shell:  get 'student_shell','mary'

rHBase: hb.get('student_rHBase','mary')

删除表（HBase需要两条命令，rHBase仅是一个操作）

HBase Shell: disable 'student_shell';drop 'student_shell'

rHBase:hb.delete.table('student_rHBase')

3.执行HBase的基本操作

我们分别使用HBase Shell和rHBase的对HBase数据库进行基本的函数操作。先用HBase Shell进行操作。

~ /home/conan/hadoop/HBase-0.94.18/bin/HBase shell  #

启动HBase shell

> create 'student_shell','info'  #

建表

> list

TABLE

student_shell

> describe 'student_shell'   #

显示表结构

DESCRIPTION                                                          ENABLED

{NAME => 'student_shell', FAMILIES => [{NAME => 'info', DATA_BLOCK_ true

ENCODING => 'NONE', BLOOMFILTER => 'NONE', REPLICATION_SCOPE => '0'

, VERSIONS => '3', COMPRESSION => 'NONE', MIN_VERSIONS => '0', TTL

=> '2147483647', KEEP_DELETED_CELLS => 'false', BLOCKSIZE => '65536

', IN_MEMORY => 'false', ENCODE_ON_DISK => 'true', BLOCKCACHE => 't

rue'}]}

>  put 'student_shell','mary','info:age','19'  #

插入一条数据

>  get 'student_shell','mary'    #

查询数据

COLUMN                      CELL

info:age                   timestamp=1365414964962, value=19

> disable 'student_shell'   #

删除表

> drop 'student_shell'

接下来用rHBase包的函数进行同样的操作。

~ R   #

启动R

程序

> library(rHBase)  #

加载rHBase

> hb.init()   #

在R

语言环境中初始化HBase

<pointer: 0x16494a0>

attr(,"class")

[1] "hb.client.connection"

> hb.new.table("student_rHBase","info",opts=list(maxversions=5,x=list(maxversions

=1L, compression='GZ',inmemory=TRUE)))   #

建表

[1] TRUE

> hb.list.tables()  #

查看所有的表

$student_rHBase

maxversions     compression     inmemory        bloomfiltertype bloomfiltervecsize

info:   5       NONE    FALSE   NONE    0

bloomfilternbhashes blockcache timetolive

info:                   0      FALSE         -1

> hb.describe.table("student_rHBase")   #

查看表结构

maxversions     compression     inmemory        bloomfiltertype bloomfiltervecsize

info:   5       NONE    FALSE   NONE    0

bloomfilternbhashes blockcache timetolive

info:                  0      FALSE         -1

> hb.insert("student_rHBase",list(list("mary","info:age", "24")))   #

插入一条数据

[1] TRUE

> hb.get('student_rHBase','mary')    #

查询数据

[[1]]

[[1]][[1]]

[1] "mary"

[[1]][[2]]

[1] "info:age"

[[1]][[3]]

[[1]][[3]][[1]]

[1] "24"

> hb.delete.table('student_rHBase')   #

删除表

[1] TRUE

这样我们就实现了R语言对HBase的访问操作。

7.6　解决RHadoop安装错误：PipeMapRed.waitOutputThreads()

问题

RHadoop安装过程中，PipeMapRed.waitOutputThreads()的错误，如何解决？

引言

一行错误难倒一片同学，在使用RHadoop的rmr2包过程中，经常会遇到的一个错误是PipeMapRed.waitOutputThreads():subprocess failed with code 1。错误原因是什么，又该如何解决呢？本节将告诉你答案。

可能出错的代码，如下所示。

> small.ints = to.dfs(1:10)

> mapreduce(input = small.ints, map = function(k, v) cbind(v, v^2))

> from.dfs("/tmp/RtmpWnzxl4/file5deb791fcbd5")

7.6.1　rmr2运行错误日志

本节使用的系统环境：

·Linux Ubuntu 12.04.2LTS 64bit server

·R 2.15.364bit

·Java JDK 1.6.x

·Hadoop 1.1.2

·IP:192.168.1.243

R运行过程中的错误日志：

packageJobJar: [/tmp/Rtmpdf7egm/rmr-local-env3cbb70983, /tmp/Rtmpdf7egm/rmr-

global-env3cbb654b85fe, /tmp/Rtmpdf7egm/rmr-streaming-map3cbba213f2e, /home/

conan/hadoop/tmp/hadoop-unjar1697638502297829404/] [] /tmp/

streamjob4620072667602885650.jar tmpDir=null

13/06/23 10:44:25 INFO mapred.FileInputFormat: Total input paths to process : 1

13/06/23 10:44:25 INFO streaming.StreamJob: getLocalDirs(): [/home/conan/hadoop/

tmp/mapred/local]

13/06/23 10:44:25 INFO streaming.StreamJob: Running job: job_201306231032_0001

13/06/23 10:44:25 INFO streaming.StreamJob: To kill this job, run:

13/06/23 10:44:25 INFO streaming.StreamJob: /home/conan/hadoop/hadoop-1.1.2/

libexec/../bin/hadoop job  -Dmapred.job.tracker=hdfs://master:9001 -kill

job_201306231032_0001

13/06/23 10:44:25 INFO streaming.StreamJob: Tracking URL: http://master:50030/

jobdetails.jsp?jobid=job_201306231032_0001

13/06/23 10:44:26 INFO streaming.StreamJob:  map 0%  reduce 0%

13/06/23 10:45:04 INFO streaming.StreamJob:  map 100%  reduce 100%

13/06/23 10:45:04 INFO streaming.StreamJob: To kill this job, run:

13/06/23 10:45:04 INFO streaming.StreamJob: /home/conan/hadoop/hadoop-1.1.2/

libexec/../bin/hadoop job  -Dmapred.job.tracker=hdfs://master:9001 -kill

job_201306231032_0001

13/06/23 10:45:04 INFO streaming.StreamJob: Tracking URL: http://master:50030/

jobdetails.jsp?jobid=job_201306231032_0001

13/06/23 10:45:04 ERROR streaming.StreamJob: Job not successful. Error:

# of failed Map Tasks exceeded allowed limit. FailedCount: 1. LastFailedTask:

task_201306231032_0001_m_000000

13/06/23 10:45:04 INFO streaming.StreamJob: killJob...

Streaming Command Failed!

Error in mr(map = map, reduce = reduce, combine = combine, vectorized.reduce,  :

hadoop streaming failed with error code 1

我们光看上面的日志，根本发现不了Hadoop的实际错误是什么！

7.6.2　定位错误到Hadoop日志

接下来需要定位到实际错误的位置。为了方便查询日志，我们打开jobtracker的控制台（http://192.168.1.210:50030/jobtracker.jsp）。并找到刚才出现错误的日志中有提示的网页位置（http://master:50030/jobdetails.jsp?jobid=job_201306231032_0001），如图7-3所示。

图7-3　jobtracker控制台

查看map的错误，现在已经明确有了错误定义。

Running a job using hadoop streaming and mrjob: PipeMapRed.waitOutputThreads():

subprocess failed with code 1

java.lang.RuntimeException: PipeMapRed.waitOutputThreads(): subprocess failed

with code 1 at

org.apache.hadoop.streaming.PipeMapRed.waitOutputThreads(PipeMapRed.java:362) at

org.apache.hadoop.streaming.PipeMapRed.mapRedFinished(PipeMapRed.java:576) at

org.apache.hadoop.streaming.PipeMapper.close(PipeMapper.java:135) at

org.apache.hadoop.mapred.MapRunner.run(MapRunner.java:57) at

org.apache.hadoop.streaming.PipeMapRunner.run(PipeMapRunner.java:36) at

org.apache.hadoop.mapred.MapTask.runOldMapper(MapTask.java:436) at

org.apache.hadoop.mapred.MapTask.run(MapTask.java:372) at

org.apache.hadoop.mapred.Child$4.run(Child.java:255) at

java.security.AccessController.doPrivileged(Native Method) at

javax.security.auth.Subject.doAs(Subject.java:396) at

org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.

java:1121) at

org.apache.hadoop.mapred.Child.main(Child.java:249)

下面我们将通过两种方法，排查并解决错误。

7.6.3　从Hadoop入手找解决办法—失败

查看Hadoop错误列表：code 1的错误

"OS error code 1: Operation not permitted""OS error code 2: No such file or directory"

从错误描述来看，这里是一个权限的错误。我来分析RHadoop运行时环境中的用户和用户组的权限。

·Hadoop的启动用户:用户conan,用户组conan

·R语言的启动用户:用户conan,用户组conan

既然都是相同权限的用户，怎么会有权限的问题呢？我在另外一个Hadoop环境做了测试，使用root用户，就没有发生上面的错误。

好了，这样问题已经很具体了，我们去网上查找错误原因。通过google搜索“org.apache.hadoop.security.AccessControlException”。根据网上找到的答案，我们在Hadoop的配置中进行调整，在hdfs-site.xml中增加dfs.permissions.superusergroup的定义。至于superusergroup配置项的默认值用什么我们可以在系统中查看。

~ hadoop fs -ls /

drwxr-xr-x   - conan supergroup          0 2013-04-24 01:52 /HBase

drwxr-xr-x   - conan supergroup          0 2013-04-25 04:59 /home

drwxr-xr-x   - conan supergroup          0 2013-06-23 10:44 /tmp

drwxr-xr-x   - conan supergroup          0 2013-04-24 19:28 /user

这样就得到了默认的group的名字是supergroup，我们修改hdfs-site.xml，增加dfs.permissions.superusergroup的定义。

~ vi $HADOOP_HOME/conf/hdfs-site.xml

<configuration>

<property>

<name>dfs.data.dir</name>

<value>/home/conan/hadoop/data</value>

</property>

<property>

<name>dfs.replication</name>

<value>1</value>

</property>

<property>

<name>dfs.permissions</name>

<value>false</value>

</property>

<property>

<name>dfs.permissions.superusergroup</name>

<value>supergroup</value>

</property>

</configuration>

重启Hadoop集群，重启R程序，重新执行rmr2脚本。还是有错误，没有解决错误问题。

7.6.4　从RHadoop入手找解决办法—成功

我们找到RHadoop的Github的issue 122:https://github.com/RevolutionAnalytics/RHa-doop/issues/122，发现其他人也遇到了类似的错误，看到这issue已经是closed状态，证明错误已经解决。根据作者的回答我们发现，错误是由于：“R的类库在特定的用户下面，而不是标准位置，导致类库不能被Hadoop找到”。在我的环境中，发现也是同样的配置与错误描述一致。

~ R   #

启动R

程序

> .libPaths()  #

查看R

包的安装目录

[1] "/home/conan/R/x86_64-pc-linux-gnu-library/2.15"

[2] "/usr/local/lib/R/site-library"

[3] "/usr/lib/R/site-library"

[4] "/usr/lib/R/library"

~ ls /home/conan/R/x86_64-pc-linux-gnu-library/2.15  #

通过命令查看目录中的R

包

colorspace  functional  iterators  munsell  RColorBrewer  rhdfs    rmr2

dichromat   ggplot2     itertools  plyr     Rcpp          rJava    scales

digest      gtable      labeling   proto    reshape2      RJSONIO  stringr

~ ls /usr/local/lib/R/site-library   # /usr/local

目录是空的

这时发现，我环境中安装的所有类库，都在/home/conan目录下面，而/usr/local/lib/R/site-library目录下，一个包都没有。于是我们按照RHadoop作者指示，以root权限，把RHadoop包进行重新安装。

~ sudo -i   #

切换到root

下面

~ R                          #

查看R

的类库路径

> .libPaths()

[1] "/usr/local/lib/R/site-library" "/usr/lib/R/site-library"

[3] "/usr/lib/R/library"

~ R CMD javareconf   #

在root

下进行安装

~ R   #

启动R

程序

> install.packages("rJava")  #

安装rmr2

的依赖包

> install.packages("reshape2")

> install.packages("Rcpp")

> install.packages("iterators")

> install.packages("itertools")

> install.packages("digest")

> install.packages("RJSONIO")

> install.packages("functional")

~ cd /home/conan/R

~ R CMD INSTALL rmr2_2.1.0.tar.gz   #

安装rmr2

包

~ ls /usr/local/lib/R/site-library   # /usr/local

目录有了R

包

digest  functional  iterators  itertools  plyr  Rcpp  reshape2  rJava  RJSONIO  rmr2  stringr

依赖包都安装在了/usr/local/lib/R/site-library下面，我们退出root用户，重新启动R程序测试。

~ exit   #

退出root

用户

~ whoami   #

检查当前用户

conan

~ R   #

重新启动R

程序

> library(rmr2)

Loading required package: Rcpp

Loading required package: RJSONIO

Loading required package: digest

Loading required package: functional

Loading required package: stringr

Loading required package: plyr

Loading required package: reshape

> small.ints = to.dfs(1:10)   #

再次运行 rmr2

程序

> mapreduce(input = small.ints, map = function(k, v) cbind(v, v^2))

packageJobJar: [/tmp/RtmpM87JEc/rmr-local-env1c7588ca7ed, /tmp/RtmpM87JEc/rmr-

global-env1c77fdcab5f, /tmp/RtmpM87JEc/rmr-streaming-map1c76a4ddf6e, /home/conan/

hadoop/tmp/hadoop-unjar6992113986427459004/] [] /tmp/streamjob2762947354578034435.

jar tmpDir=null

13/06/23 13:27:36 INFO mapred.FileInputFormat: Total input paths to process : 1

13/06/23 13:27:36 INFO streaming.StreamJob: getLocalDirs(): [/home/conan/hadoop/

tmp/mapred/local]

13/06/23 13:27:36 INFO streaming.StreamJob: Running job: job_201306231141_0007

13/06/23 13:27:36 INFO streaming.StreamJob: To kill this job, run:

13/06/23 13:27:36 INFO streaming.StreamJob: /home/conan/hadoop/hadoop-1.1.2/

libexec/../bin/hadoop job  -Dmapred.job.tracker=hdfs://master:9001 -kill

job_201306231141_0007

13/06/23 13:27:36 INFO streaming.StreamJob: Tracking URL:

http://master:50030/jobdetails.jsp?jobid=job_201306231141_0007

13/06/23 13:27:37 INFO streaming.StreamJob:  map 0%  reduce 0%

13/06/23 13:27:51 INFO streaming.StreamJob:  map 100%  reduce 0%

13/06/23 13:27:58 INFO streaming.StreamJob:  map 100%  reduce 100%

13/06/23 13:27:58 INFO streaming.StreamJob: Job complete: job_201306231141_0007

13/06/23 13:27:58 INFO streaming.StreamJob: Output: /tmp/RtmpM87JEc/file1c722c5c6ae

执行成功，错误解决！

本节从发现错误、定位错误、查找原因、解决错误4个步骤解决上面的问题。希望给遇到问题束手无策的同学，不仅提供直接的错误帮助，更有一个思路上的启发，提高自己的动手能力！

到这里为止，本书的正文部分就结束了，感谢你的阅读。希望这本书能为你开启新的大门。另外，本书的姊妹篇《R的极客理想—高级开发篇》会为你介绍更多的R语言深入学习的内容，还有很多的东西值得探索。笔者很期待你能使用在本书里学到的工具和技术开发出一些有趣的应用。

第四部分　附录

■附录A　Java环境安装

■附录B　MySQL数据库安装

■附录C　Redis数据库安装

■附录D　MongoDB数据库安装

■附录E　Cassandra数据库安装

■附录F　Hadoop安装

■附录G　Hive环境安装

■附录H　HBase安装

附录A　Java环境安装

A.1　Java在Windows中安装

Java环境安装是指Java开发环境的安装，包括JDK安装和Java的环境配置。在Windows中，很多的Java应用通过JRE就可以运行。但对Java的开发者来说，我们需要安装JDK，而且必须是Oracle SUN官方发布的JDK。

在Windows系统上安装JDK是件非常简单的事情，下载可执行文件(.exe)，双击即可安装。下载地址是http://www.oracle.com/technetwork/java/javase/downloads/index.html。大家可以下载最新版本的Java SE 8，或者较早的JDK版本。本书中使用的是1.6.0_45版本的JDK。我安装在目录D:\toolkit\java\jdk6。安装好JDK后，就开始配置环境变量，如图A-1所示。选择：控制面板–>系统和安全–>系统属性–>高级–>环境变量。

JAVA_HOME=D:\toolkit\java\jdk6  #

新建JAVA_HOME

变量

PATH=D:\toolkit\java\jdk6\bin   #

新建PATH

变量

注　如果PATH变量已存在，则在最后增加JDK的路径，用分号(;)与上个配置做分隔。

保存后，打开新的CMD命令窗口，测试Java的命令行操作。

~ C:\Users\Administrator>java -version   #

查看Java

安装版本

java version "1.6.0_45"

Java(TM) SE Runtime Environment (build 1.6.0_45-b06)

Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode)

~ C:\Users\Administrator>java   #

测试Java

命令

~ C:\Users\Administrator>javac   #

测试Javac

命令

图A-1　配置Java环境变量

这样我们就在Windows的系统中，配置好了Java环境。

A.2　Java在Linux Ubuntu中安装

Java安装，指定的是安装Oracle SUN官方的JDK软件包，而不是在Linux Ubuntu系统中通过apt-get安装OpenJDK。在Linux Ubuntu中，下载并安装JDK，这里我选择安装Java SE 1.6.45的64位版本的JDK。请一定注意版本，必须使用Oracle SUN官方的JDK软件包。

A.2.1　下载JDK

不知道Oracle官方网站是什么时候调整下载权限的，JDK下载需要先登录，因此直接使用wget命令，无法完成JDK的下载。我们有两种解决方法，一是从浏览器中登录下载，然后再传到Linux的服务器；二是用wget命令通过浏览器中的用户cookies模拟登录，再进行下载。读者可以用第一种方式下载，对于第二种方式可以查看笔者的博客文章http://blog.fens.me/linux-java-install/。

A.2.2　安装JDK

通过shell命令安装JDK。

~ sh ./jdk-6u45-linux-x64.bin  #

安装JDK

~ pwd   #

查看当前目录

/home/conan/tookit

~ tree -L 2   #

查看安装JDK

安装后的目录

.

├── jdk1.6.0_45

│

├── bin

│

├── COPYRIGHT

│

├── db

│

├── include

│

├── jre

│

├── lib

│

├── LICENSE

│

├── man

│

├── README.html

│

├── src.zip

│

└── THIRDPARTYLICENSEREADME.txt

└── jdk-6u45-linux-x64.bin

A.2.3　设置Java的环境变量

用vi编辑环境变量文件/etc/environemt。

~ sudo vi /etc/environment

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/

home/conan/tookit/jdk1.6.0_45/bin"

JAVA_HOME=/home/conan/tookit/jdk1.6.0_45

检查环境变量的设置。

~ . /etc/environment  #

让环境变量生效

~ export|grep jdk   #

检查环境变量

declare -x OLDPWD="/home/conan/tookit/jdk1.6.0_45"

declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/

usr/games:/home/conan/tookit/jdk1.6.0_45/bin"

~ echo $JAVA_HOME   #

打印变量JAVA_HOME

/home/conan/tookit/jdk1.6.0_45

运行Java命令。

~ java -version

java version "1.6.0_45"

Java(TM) SE Runtime Environment (build 1.6.0_45-b06)

Java HotSpot(TM) 64-Bit Server VM (build 20.45-b01, mixed mode)

通过上面的操作，我们就在Linux Ubuntu中的系统安装完成Java的环境。

附录B　MySQL数据库安装

B.1　MySQL在Windows中安装

在Windows系统上安装MySQL数据库是件非常简单的事情，下载压缩包，解压即可。下载地址：http://dev.mysql.com/downloads/mysql/。

·MySQL服务器运行命令：MySQL安装目录/bin/mysqld.exe

·MySQL客户端运行命令：MySQL安装目录/bin/mysql.exe

B.2　MySQL在Linux Ubuntu中安装

本书使用的Linux是Ubuntu 12.04.2LTS 64bit的系统，安装MySQL数据库软件包可以通过apt-get实现。首先在Linux Ubuntu中安装MySQL数据库。

~sudo apt-get install mysql-server    #

安装MySQL

服务器端

安装过程会弹出提示框，输入root用户的密码，我在这里设置密码为mysql。安装完成后，MySQL服务器会自动启动，我们检查MySQL服务器程序

~ ps -aux|grep mysql   #

检查MySQL

服务器系统进程

mysql     3205  2.0  0.5 549896 44092 ?        Ssl  20:10   0:00 /usr/sbin/mysqld

conan     3360  0.0  0.0  11064   928 pts/0    S+   20:10   0:00 grep --color=a

uto mysql

~ netstat -nlt|grep 3306  #

检查MySQL

服务器占用端口

tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN

~ sudo /etc/init.d/mysql status  #

通过启动命令检查MySQL

服务器状态

Rather than invoking init scripts through /etc/init.d, use the service(8)

utility, e.g. service mysql status

Since the script you are attempting to invoke has been converted to an

Upstart job, you may also use the status(8) utility, e.g. status mysql

mysql start/running, process 3205

~ service mysql status   #

通过系统服务命令检查MySQL

服务器状态

mysql start/running, process 3205

B.3　通过命令行客户端访问MySQL

安装MySQL服务器，会自动地一起安装MySQL命令行客户端程序。在本机输入MySQL命令就可以启动，客户端程序访问MySQL服务器。

~ mysql

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 42

Server version: 5.5.35-0ubuntu0.12.04.2 (Ubuntu)

Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its

affiliates. Other names may be trademarks of their respective

owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

输入用户名和密码，登录服务器。

~ mysql -uroot -p

Enter password:

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 37

Server version: 5.5.35-0ubuntu0.12.04.2 (Ubuntu)

mysql>

MySQL的一些简单的命令操作。

mysql> show databases;   #

查看所有的数据库

+--------------------+

| Database           |

+--------------------+

| information_schema |

| test               |

+--------------------+

2 rows in set (0.00 sec)

mysql> show variables like '%char%'; #

查看数据库的字符集编码

+--------------------------+----------------------------+

| Variable_name            | Value                      |

+--------------------------+----------------------------+

| character_set_client     | utf8                       |

| character_set_connection | utf8                       |

| character_set_database   | utf8                       |

| character_set_filesystem  | binary                     |

| character_set_results    | utf8                       |

| character_set_server     | latin1                     |

| character_set_system     | utf8                       |

| character_sets_dir       | /usr/share/mysql/charsets/ |

+--------------------------+----------------------------+

8 rows in set (0.00 sec)

B.4　修改MySQL服务器的配置

接下来，我需要做一些配置，让MySQL符合基本的开发要求。

B.4.1　将字符编码设置为UTF-8

默认情况下，MySQL的字符集是latin1，因此在存储中文的时候，会出现乱码的情况，所以我们需要把字符集统一改成UTF-8。用vi打开MySQL服务器的配置文件my.cnf：

~ sudo vi /etc/mysql/my.cnf

[client]   #

在[client]

标签下，增加客户端的字符编码

default-character-set=utf8

[mysqld]  #

在[mysqld]

标签下，增加服务器端的字符编码

character-set-server=utf8

collation-server=utf8_general_ci

B.4.2　让MySQL服务器被远程访问

默认情况下，MySQL服务器不允许远程访问，只允许本机访问，所以我们需要设置打开远程访问的功能。用vi打开MySQL服务器的配置文件my.cnf：

~ sudo vi /etc/mysql/my.cnf

#bind-address            = 127.0.0.1    #

注释bind-address

修改后，重启MySQL服务器。

~ sudo /etc/init.d/mysql restart

重新登录服务器

~ mysql -uroot -p

Enter password:

Welcome to the MySQL monitor.  Commands end with ; or \g.

Your MySQL connection id is 37

Server version: 5.5.35-0ubuntu0.12.04.2 (Ubuntu)

mysql> show variables like '%char%';    #

再次查看字符串编码

+--------------------------+----------------------------+

| Variable_name            | Value                      |

+--------------------------+----------------------------+

| character_set_client     | utf8                       |

| character_set_connection | utf8                       |

| character_set_database   | utf8                       |

| character_set_filesystem | binary                     |

| character_set_results    | utf8                       |

| character_set_server     | utf8                       |

| character_set_system     | utf8                       |

| character_sets_dir       | /usr/share/mysql/charsets/ |

+--------------------------+----------------------------+

8 rows in set (0.00 sec)

我们检查MySQL的网络监听端口。

~ netstat -nlt|grep 3306   #

检查MySQL

服务器占用端口

tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN

我们看到网络监听从127.0.0.1:3306变成00.0.0.0:3306，表示MySQL已经允许远程登录访问。

通过上面的操作，我们就把MySQL数据库服务器，在Linux Ubuntu系统中安装完成。

附录C　Redis数据库安装

C.1　Redis在Windows中安装

在Windows系统中安装Redis数据库是件非常简单的事情，下载可执行安装文件(.exe)，双击安装即可。下载地址是https://github.com/rgl/redis/downloads。

·Redis服务器运行命令：Redis安装目录/redis-server.exe

·Redis客户端运行命令：Redis安装目录/redis-cli.exe

C.2　Redis在Linux Ubuntu中安装

本书使用的Linux是Ubuntu 12.04.2LTS 64bit的系统，安装Redis数据库软件包可以通过apt-get实现。

~ sudo apt-get install redis-server  #

安装Redis

服务器端

安装完成后，Redis服务器会自动启动，我们检查一下Redis服务器程序。

~ ps -aux|grep redis   #

检查Redis

服务器系统进程

redis     4162  0.1  0.0  10676  1420 ?        Ss   23:24   0:00 /usr/bin/redis-

server /etc/redis/redis.conf

conan     4172  0.0  0.0  11064   924 pts/0    S+   23:26   0:00 grep --color=auto

redis

~ netstat -nlt|grep 6379

tcp        0      0 127.0.0.1:6379          0.0.0.0:*               LISTEN

~ sudo /etc/init.d/redis-server status  #

通过启动命令检查Redis

服务器状态

redis-server is running

C.3　通过命令行客户端访问Redis

安装Redis服务器，会自动地一起安装Redis命令行客户端程序。在本机输入redis-cli命令就可以启动，客户端程序访问Redis服务器。

~ redis-cli   #

启动Redis

命令行客户端

redis 127.0.0.1:6379>

redis 127.0.0.1:6379> help   #

命令行的帮助

redis-cli 2.2.12

Type: "help @" to get a list of commands in

"help " for help on

"help " to get a list of possible help topics

"quit" to exit

redis 127.0.0.1:6379> keys *   #

查看所有的key

列表

1) "k1"

2) "test"

3) "value"

C.4　修改Redis的配置

C.4.1　使用Redis的访问账号

默认情况下，访问Redis服务器是不需要登录验证的，为了增加安全性我们可以设置Redis服务器的访问密码。设置访问密码为redisredis。首先用vi打开Redis服务器的配置文件redis.conf：

~ sudo vi /etc/redis/redis.conf

requirepass redisredis    #

取消注释requirepass

，访问密码为redisredis

C.4.2　让Redis服务器被远程访问

默认情况下，Redis服务器不允许远程访问，只允许本机访问，所以我们需要设置打开远程访问的功能。用vi打开Redis服务器的配置文件redis.conf。

~ sudo vi /etc/redis/redis.conf

#bind 127.0.0.1  #

注释bind

修改后，重启Redis服务器。

~ sudo /etc/init.d/redis-server restart

Stopping redis-server: redis-server.

Starting redis-server: redis-server.

未使用密码登录Redis服务器。

~ redis-cli

redis 127.0.0.1:6379> keys *

(error) ERR operation not permitted

发现可以登录，但无法执行命令了。登录Redis服务器，输入密码。

~  redis-cli -a redisredis

redis 127.0.0.1:6379> keys *

1) "key2"

2) "key3"

3) "key4"

登录后，一切正常。我们检查Redis的网络监听端口。

~ netstat -nlt|grep 6379  #

检查Redis

服务器占用端口

tcp        0      0 0.0.0.0:6379            0.0.0.0:*               LISTEN

我们看到网络监听从127.0.0.1:3306变成00.0.0.0:3306，表示Redis已经允许远程登录访问。我们在远程的另一台Linux访问Redis服务器。

~ redis-cli -a redisredis -h 192.168.1.199

redis 192.168.1.199:6379> keys *

1) "key2"

2) "key3"

3) "key4"

远程访问正常。通过上面的操作，我们就把Redis数据库服务器成功安装在Linux Ubuntu系统。

附录D　MongoDB数据库安装

D.1　MongoDB在Windows中安装

在Windows系统中安装MongoDB数据库是件非常简单的事情，下载可执行安装文件（.exe），双击安装即可。下载地址是http://www.mongodb.org/downloads。

·MongoDB服务器运行命令：MongoDB安装目录/bin/mongod.exe

·MongoDB客户端运行命令：MongoDB安装目录/bin/mongo.exe

D.2　MongoDB在Linux Ubuntu中安装

本书使用的Linux是Ubuntu 12.04.2LTS 64bit的系统，安装MongoDB数据库软件包可以通过apt-get实现，但我们需要从官方提供的MongoDB软件源下载安装。首先修改apt的source.list文件，增加10gen的设置。

~  sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10

#

下载密钥文件

Executing: gpg --ignore-time-conflict --no-options --no-default-keyring --secret-

keyring /tmp/tmp.kVFab9XYw0 --trustdb-name /etc/apt/trustdb.gpg --keyring /etc/

apt/trusted.gpg --primary-keyring /etc/apt/trusted.gpg --keyserver hkp://

keyserver.ubuntu.com:80 --recv 7F0CEB10

gpg:

下载密钥'7F0CEB10'

，从 hkp

服务器 keyserver.ubuntu.com

gpg:

密钥 7F0CEB10

：公钥“Richard Kreuter

”已导入

gpg:

没有找到任何绝对信任的密钥

gpg:

合计被处理的数量：1

gpg:

已导入：1  (RSA: 1)

#

在source.list

中增加MongoDB

软件源的配置

~ echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen'

| sudo tee /etc/apt/sources.list.d/mongodb.list

deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen

~ sudo apt-get update  #

更新软件源列表

在Linux Ubuntu中安装MongoDB数据库。

~ sudo apt-get install mongodb-10gen  #

安装MongoDB

服务器端

安装完成后，MongoDB服务器会自动启动，我们检查MongoDB服务器程序。

~  ps -aux|grep mongo   #

检查MongoDB

服务器系统进程

mongodb   6870  3.7  0.4 349208 39740 ?        Ssl  10:27   2:23 /usr/bin/mongod

--config /etc/mongodb.conf

~  netstat -nlt|grep 27017   #

通过启动命令检查MongoDB

服务器状态

tcp        0      0 0.0.0.0:27017           0.0.0.0:*               LISTEN

~ sudo /etc/init.d/mongodb status  #

通过启动命令检查MongoDB

服务器状态

Rather than invoking init scripts through /etc/init.d, use the service(8)

utility, e.g. service mongodb status

Since the script you are attempting to invoke has been converted to an

Upstart job, you may also use the status(8) utility, e.g. status mongodb

mongodb start/running, process 6870

~ sudo service mongodb status   #

通过系统服务检查MongoDB

服务器状态

mongodb start/running, process 6870

通过Web的控制台，查看MongoDB服务器的状态。在浏览器输入http://ip:28017，就可以打开通过Web的控制台了，如图D-1所示。

图D-1　MongoDB的Web控制台

D.3　通过命令行客户端访问MongoDB

安装MongoDB服务器，会自动地一起安装MongoDB命令行客户端程序。在本机输入mongo命令就可以启动客户端程序访问MongoDB服务器。

~ mongo  #

启动MongoDB

客户端

MongoDB shell version: 2.4.9

connecting to: test

Welcome to the MongoDB shell.

> help   #

查看命令行帮助

db.help()                    help on db methods

db.mycoll.help()             help on collection methods

sh.help()                    sharding helpers

rs.help()                    replica set helpers

help admin                   administrative help

help connect                 connecting to a db help

help keys                    key shortcuts

help misc                    misc things to know

help mr                      mapreduce

show dbs                     show database names

show collections             show collections in current database

show users                   show users in current database

show profile                 show most recent system.profile entries with time >= 1ms

show logs                    show the accessible logger names

show log [name]              prints out the last segment of log in memory,'global'

is default

use                  set current database

db.foo.find()                list objects in collection foo

db.foo.find( { a : 1 } )     list objects in foo where a == 1

it                           result of the last line evaluated; use to further iterate

DBQuery.shellBatchSize = x   set default number of items to display on shell

exit                         quit the mongo shell

MongoDB服务器，默认情况下是允许外部访问的。这样单节点的MongoDB，我们已经成功地安装在Linux Ubuntu系统中。

附录E　Cassandra数据库安装

E.1　在Ubuntu中环境准备

Cassandra是基于Java开发的NoSQL数据库软件，Cassandra没有提供Windows系统安装版本。我在这里也只介绍Cassandra在Linux Ubuntu系统中的安装。由于Cassandra是用Java开发的，因此我们需要先安装好Java的环境，Java的安装请参考附录A。

Cassandra没有提供apt的软件源安装，我们需要自己去官方网站下载Cassandra软件包进行安装。Cassandra下载页：http://cassandra.apache.org/download/。下载Cassandra时，发现cassandra有两个版本在并行发布。因此我们有两种选择，最新2.0分支的版本2.0.6（2014-03-10发布），或者1.2分支的版本1.2.15（2014-02-07）。本节将以1.2分支1.2.15版本为例，进行单机的安装和配置。

系统环境：

·Linux Ubuntu 12.04.2LTS 64bit server

·Java JDK 1.6.0_45

E.2　下载Cassandra软件包

下载Cassandra：

~ wget http://apache.dataguru.cn/cassandra/1.2.15/apache-cassandra-1.2.15-bin.

tar.gz  # 1.2.15

版本下载

~ tar xvf apache-cassandra-1.2.15-bin.tar.gz  #

解压软件包

~ mv apache-cassandra-1.2.15/ cassandra1215  #

对Cassandra

解压目录改名

~ cd cassandra1215/   #

进入目录

E.3　配置Cassandra

首先设置Cassandra数据目录。

·data_file_directories：数据文件目录

·commitlog_directory：日志文件目录

·saved_Caches_directory：缓存文件目录

用vi打开cassandra的配置文件cassandra.yaml。

~ vi conf/cassandra.yaml

data_file_directories:

- /var/lib/cassandra/data

commitlog_directory: /var/lib/cassandra/commitlog

saved_caches_directory: /var/lib/cassandra/saved_caches

确认操作系统中，这几个目录已被创建。同时确认/var/log/cassandra/目录，对于操作用户是可写的。

~ sudo mkdir -p /var/lib/cassandra/data   #

新建目录

~ sudo mkdir -p /var/lib/cassandra/saved_caches

~ sudo mkdir -p /var/lib/cassandra/commitlog

~ sudo mkdir -p /var/log/cassandra/

~ sudo chown -R conan:conan /var/lib/cassandra   #

把目录归属改成操作用户

~ sudo chown -R conan:conan /var/log/cassandra/

E.4　设置环境变量

~ sudo vi /etc/environment

CASSANDRA_HOME=/home/conan/toolkit/cassandra1215

~ . /etc/environment    #

让环境变量生效

~ echo $CASSANDRA_HOME  #

查看环境变量

/home/conan/toolkit/cassandra1215

E.5　启动Cassandra服务器

通过命令，启动Cassandra服务器。

~ bin/Cassandra   # -f

参数是绑定到console

，不加-f

则是后台启动。

~ ps -axu|grep cassandra  #

查看Cassandra

系统进程

~ netstat -nlt|grep 9160  #

查看系统端口

tcp        0      0 127.0.0.1:9160          0.0.0.0:*               LISTEN

E.6　用客户端访问cassandra

通过客户端程序访问Cassandra服务器。

~ bin/cassandra-cli

Connected to: "Test Cluster" on 127.0.0.1/9160

Welcome to Cassandra CLI version 1.2.15

Type 'help;' or '?' for help.

Type 'quit;' or 'exit;' to quit.

[default@unknown] ?   #

查看命令行帮助

单节点的Cassandra，我们已经成功能安装在Linux Ubuntu系统中。

附录F　Hadoop安装

F.1　在Ubuntu中环境准备

Hadoop支持在Linux、Windows、Mac上进行开发，但很难实现在Windows和Mac大规模部署，我在这里也重点介绍Hadoop在Linux Ubuntu系统中的安装和使用。

由于Hadoop是用Java开发的，因此我们需要先安装好Java的环境，Java的安装请参考附录A。Hadoop没有提供apt的软件源安装，我们需要自己去官方网络下载Hadoop软件包进行安装。Hadoop的下载页是http://hadoop.apache.org/#Download+Hadoop。系统环境是：

·Linux Ubuntu 12.04.2LTS 64bit server

·Java JDK 1.6.0_45

F.2　找到Hadoop历史版本

这里我们需要安装Hadoop的版本是1.1.2。打开Hadoop的下载页面http://www.apache.org/dyn/closer.cgi/hadoop/common/。随便打开一个下载镜像，我们都找不到1.1.2这个版本，如图F-1所示。

再去看看Github上Hadoop发布源的情况，如图F-2所示。找到release-1.1.2。

https://github.com/apache/hadoop-common/releases/tag/release-1.1.2

用Linux命令wget下载源代码：

~ wget https://github.com/apache/hadoop-common/archive/release-1.1.2.tar.gz

图F-1　Hadoop镜像

在Github上面，查看branch-1.1。

https://github.com/apache/hadoop-common/tree/branch-1.1

图F-2　Hadoop发布源

F.3　用源代码构建Hadoop环境

Linux系统环境：

·Linux Ubuntu 64bit Server 12.04.2LTS

·Java 1.6.0_29

·Ant 1.8.4

注　Github上面最新hadoop-3.0.0-SNAPSHOT，包结构已经完全改变，用Maven代替了Ant+Ivy的构建过程。

F.3.1　安装Hadoop

~ tar xvf release-1.1.2.tar.gz

~ mkdir /home/conan/hadoop/

~ mv hadoop-common-release-1.1.2 /home/conan/hadoop/

~ cd /home/conan/hadoop/

~ mv hadoop-common-release-1.1.2/ hadoop-1.1.2

查看Hadoop目录

~ cd  hadoop-1.1.2

~ ls -l

drwxrwxr-x  2 conan conan   4096 Mar  6  2013 bin

-rw-rw-r--  1 conan conan 120025 Mar  6  2013 build.xml

-rw-rw-r--  1 conan conan 467130 Mar  6  2013 CHANGES.txt

drwxrwxr-x  2 conan conan   4096 Oct  3 02:31 conf

drwxrwxr-x  2 conan conan   4096 Oct  3 02:28 ivy

-rw-rw-r--  1 conan conan  10525 Mar  6  2013 ivy.xml

drwxrwxr-x  4 conan conan   4096 Mar  6  2013 lib

-rw-rw-r--  1 conan conan  13366 Mar  6  2013 LICENSE.txt

drwxrwxr-x  2 conan conan   4096 Oct  3 03:35 logs

-rw-rw-r--  1 conan conan    101 Mar  6  2013 NOTICE.txt

-rw-rw-r--  1 conan conan   1366 Mar  6  2013 README.txt

-rw-rw-r--  1 conan conan   7815 Mar  6  2013 sample-conf.tgz

drwxrwxr-x 16 conan conan   4096 Mar  6  2013 src

在根目录下面，没有hadoop-*.jar的各种类库，也没有依赖库。

F.3.2　执行Ant进行编译

从官方网站（http://ant.apache.org/bindownload.cgi）下载Ant。

~ wget http://archive.apache.org/dist/ant/binaries/apache-ant-1.8.4-bin.tar.gz

~ tar xvf apache-ant-1.8.4-bin.tar.gz

~ mkdir /home/conan/toolkit/

~ mv apache-ant-1.8.4 /home/conan/toolkit/

~ cd /home/conan/toolkit/

~ mv apache-ant-1.8.4 ant184

设置Ant到环境变量。

~  sudo vi /etc/environment  #

编辑environment

文件

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/

home/conan/toolkit/jdk16/bin:/home/conan/toolkit/ant184/bin"

JAVA_HOME=/home/conan/toolkit/jdk16

ANT_HOME=/home/conan/toolkit/ant184

~ . /etc/environment   #

让环境变量生效

~ ant   #

运行Ant

命令，检查环境变量是否配置成功

Buildfile: build.xml does not exist!

Build failed

用Ant编译Hadoop。

~ sudo apt-get install autoconf   #

安装编译用的库

~ sudo apt-get install libtool

~ cd /home/conan/hadoop/hadoop-1.1.2/  #

用Ant

编译Hadoop

~ ant

要等几分钟，然后build成功。下面查看生成的build目录。

~ ls -l build

drwxrwxr-x  3 conan conan    4096 Oct  3 04:06 ant

drwxrwxr-x  2 conan conan    4096 Oct  3 04:02 c++

drwxrwxr-x  3 conan conan    4096 Oct  3 04:05 classes

drwxrwxr-x 13 conan conan    4096 Oct  3 04:06 contrib

drwxrwxr-x  2 conan conan    4096 Oct  3 04:05 empty

drwxrwxr-x  2 conan conan    4096 Oct  3 04:02 examples

-rw-rw-r--  1 conan conan     423 Oct  3 04:05 hadoop-client-1.1.3-SNAPSHOT.jar

-rw-rw-r--  1 conan conan 4035744 Oct  3 04:05 hadoop-core-1.1.3-SNAPSHOT.jar

-rw-rw-r--  1 conan conan     426 Oct  3 04:05 hadoop-minicluster-1.1.3-SNAPSHOT.jar

-rw-rw-r--  1 conan conan  306827 Oct  3 04:05 hadoop-tools-1.1.3-SNAPSHOT.jar

drwxrwxr-x  4 conan conan    4096 Oct  3 04:02 ivy

drwxrwxr-x  3 conan conan    4096 Oct  3 04:02 src

drwxrwxr-x  6 conan conan    4096 Oct  3 04:02 test

drwxrwxr-x  3 conan conan    4096 Oct  3 04:05 tools

drwxrwxr-x  9 conan conan    4096 Oct  3 04:02 webapps

发现hadoop-.jar都是hadoop--1.1.3-SNAPSHOT.jar结尾的，合理的解释就是：上一个版本的release就是下一个版本的SNAPSHOT。我们修改文件build.xml第31行，让属性version的value为1.1.2，这样生成出来的包名就会变成hadoop-*-1.1.2.jar，再重新启动Ant。

~ vi build.xml

<property name="version" value="1.1.2"/>

~ rm -rf build

~ ant

~ cp build/*.jar .  #

把生成的hadoop-*-1.1.2.jar

复制到根目录

这个就生成好了Hadoop环境，而且这个Hadoop环境和我们之前下载的一样。

F.4　快速Hadoop配置环境脚本

我们按照下面的7个步骤进行配置：配置环境变量，设置Hadoop的3个配置文件，创建Hadoop目录，配置hostname和hosts，生成SSH免登录密钥，第一次启动格式化HDFS，启动Hadoop的服务。

（1）配置系统环境变量

~ sudo vi /etc/environment

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/

home/conan/toolkit/jdk16/bin:/home/conan/toolkit/ant184/bin:/home/conan/toolkit/

maven3/bin:/home/conan/toolkit/tomcat7/bin:/home/conan/hadoop/hadoop-1.1.2/bin"

JAVA_HOME=/home/conan/toolkit/jdk16

ANT_HOME=/home/conan/toolkit/ant184

MAVEN_HOME=/home/conan/toolkit/maven3

HADOOP_HOME=/home/conan/hadoop/hadoop-1.1.2

HADOOP_CMD=/home/conan/hadoop/hadoop-1.1.2/bin/hadoop

HADOOP_STREAMING=/home/conan/hadoop/hadoop-1.1.2/contrib/streaming/hadoop-streaming

-1.1.2.jar

~ . /etc/environment  #

让环境变量生效

（2）设置Hadoop的3个配置文件

#core-site.xml

~ vi conf/core-site.xml

<configuration>

<property>

<name>fs.default.name</name>

<value>hdfs://master:9000</value>

</property>

<property>

<name>hadoop.tmp.dir</name>

<value>/home/conan/hadoop/tmp</value>

</property>

<property>

<name>io.sort.mb</name>

<value>256</value>

</property>

</configuration>

#hdfs-site.xml

~ vi conf/hdfs-site.xml

<configuration>

<property>

<name>dfs.data.dir</name>

<value>/home/conan/hadoop/data</value>

</property>

<property>

<name>dfs.replication</name>

<value>1</value>

</property>

<property>

<name>dfs.permissions</name>

<value>false</value>

</property>

</configuration>

#mapred-site.xml

~ vi conf/mapred-site.xml

<configuration>

<property>

<name>mapred.job.tracker</name>

<value>hdfs://master:9001</value>

</property>

</configuration>

（3）创建Hadoop目录

~ mkdir /home/conan/hadoop/data

~ mkdir /home/conan/hadoop/tmp

~ sudo chmod 755 /home/conan/hadoop/data/

~ sudo chmod 755 /home/conan/hadoop/tmp/

（4）配置hostname和hosts

~ sudo hostname master

~ sudo vi /etc/hosts

192.168.1.210   master

127.0.0.1       localhost

（5）生成SSH免登录密钥

~ ssh-keygen -t rsa

~ cat ~/.ssh/id_rsa.pub >>  ~/.ssh/authorized_keys

（6）第一次启动格式化HDFS

~ bin/hadoop namenode -format

（7）启动Hadoop的服务

~ bin/start-all.sh

检查Hadoop运行状态。

~ jps

15574 DataNode

16324 Jps

15858 SecondaryNameNode

16241 TaskTracker

15283 NameNode

15942 JobTracker

查看Hadoop节点的运行情况。

~ bin/hadoop dfsadmin -report

F.5　为Windows环境编译hadoop-core.jar

在Windows环境开发中，经常会遇到一个错误权限检查的异常，我们需要修改FileUtil.java文件，注释688行--693行。

~ vi src/core/org/apache/hadoop/fs/FileUtil.java

685 private static void checkReturnValue(boolean rv, File p,

686                                        FsPermission permission

687                                        ) throws IOException {

688   /*  if (!rv) {

689       throw new IOException("Failed to set permissions of path: " + p +

690                             " to " +

691                             String.format("%04o", permission.toShort()));

692     }

693   */

694   }

用Ant重新打包后，就生成了可以在Windows环境中运行的hadoop-core-1.1.2.jar！于是单节点的Hadoop，我们已经成功地安装在Linux Ubuntu系统中。

附录G　Hive环境安装

G.1　Hive的安装

装好Hadoop的环境后，我们可以把Hive装在namenode服务上。Hadoop的环境配置，请参考附录F。Hive的安装首先下载hive-0.9.0.tar.gz ，然后解压到/home/cos/toolkit/hive-0.9.0。进入Hive目录，创建配置文件。

~ cd /home/cos/toolkit/hive-0.9.0

~ cp hive-default.xml.template hive-site.xml   #

复制一份配置文件

~ cp hive-log4j.properties.template hive-log4j.properties  #

复制一份日志文件

修改配置文件hive-site.xml。设置把Hive的元数据存储到MySQL中。

~ vi conf/hive-site.xml

<property>

<name>javax.jdo.option.ConnectionURL</name>

<value>jdbc:mysql://localhost:3306/hive_metadata?createDatabaseIfNotExist=true</value>

<description>JDBC connect string for a JDBC metastore</description>

</property>

<property>

<name>javax.jdo.option.ConnectionDriverName</name>

<value>com.mysql.jdbc.Driver</value>

<description>Driver class name for a JDBC metastore</description>

</property>

<property>

<name>javax.jdo.option.ConnectionUserName</name>

<value>hive</value>

<description>username to use against metastore database</description>

</property>

<property>

<name>javax.jdo.option.ConnectionPassword</name>

<value>hive</value>

<description>password to use against metastore database</description>

</property>

<property>

<name>hive.metastore.warehouse.dir</name>

<value>/user/hive/warehouse</value>

<description>location of default database for the warehouse</description>

</property>

修改日志文件hive-log4j.properties。

#log4j.appender.EventCounter=org.apache.hadoop.metrics.jvm.EventCounter

log4j.appender.EventCounter=org.apache.hadoop.log.metrics.EventCounter

设置环境变量。

~ sudo vi /etc/environment

PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/

usr/local/games:/home/cos/toolkit/ant184/bin:/home/cos/toolkit/jdk16/bin:/home/

cos/toolkit/maven3/bin:/home/cos/toolkit/hadoop-1.0.3/bin:/home/cos/toolkit/hive-

0.9.0/bin"

JAVA_HOME=/home/cos/toolkit/jdk16

ANT_HOME=/home/cos/toolkit/ant184

MAVEN_HOME=/home/cos/toolkit/maven3

HADOOP_HOME=/home/cos/toolkit/hadoop-1.0.3

HIVE_HOME=/home/cos/toolkit/hive-0.9.0

HADOOP_STREAMING=/home/conan/hadoop/hadoop-1.0.3/contrib/streaming/hadoop-streaming-

1.0.3.jar

CLASSPATH=/home/cos/toolkit/jdk16/lib/dt.jar:/home/cos/toolkit/jdk16/lib/tools.jar

在hdfs上面，创建Hive的目录。

$HADOOP_HOME/bin/hadoop fs -mkidr /tmp

$HADOOP_HOME/bin/hadoop fs -mkidr /user/hive/warehouse

$HADOOP_HOME/bin/hadoop fs -chmod g+w /tmp

$HADOOP_HOME/bin/hadoop fs -chmod g+w /user/hive/warehouse

在MySQL中创建数据库。

create database hive_metadata;

grant all on hive_metadata.* to hive@'%' identified by 'hive';

grant all on hive_metadata.* to hive@localhost identified by 'hive';

ALTER DATABASE hive_metadata CHARACTER SET latin1;

手动上传MySQL的jdbc库到hive/lib。

~ ls /home/cos/toolkit/hive-0.9.0/lib

mysql-connector-java-5.1.22-bin.jar

启动hive服务器。

~ bin/hive --service metastore &     #

启动metastore

服务

Starting Hive Metastore Server

~ bin/hive --service hiveserver &    #

启动HiveServer

服务

Starting Hive Thrift Server

~ bin/hive shell   #

启动Hive

客户端

Logging initialized using configuration in file:/root/hive-0.9.0/conf/hive-log4j.properties

Hive history file=/tmp/root/hive_job_log_root_201211141845_1864939641.txt

hive> show tables

OK

t_hive

Time taken: 0.099 seconds

这样我们成功地把Hive安装在Linux Ubuntu系统中，并简单地介绍了使用方法。

附录H　HBase安装

H.1　在Ubuntu中环境准备

HBase是基于Java开发的运行在Hadoop平台上的分布式NoSQL数据库软件，HBase没有提供Windows系统安装版本。我在这里也只介绍HBase在Linux Ubuntu系统中的安装。

由于HBase是运行在Hadoop平台上面的，因此我们需要先安装好Hadoop的环境，Hadoop的安装请参考附录F。HBase没有提供apt的软件源安装，我们需要自己去官方网站下载HBase软件包进行安装。HBase下载页：http://www.apache.org/dyn/closer.cgi/HBase/。系统环境：

·Linux Ubuntu 12.04.2LTS 64bit server

·Java JDK 1.6.0_45

·Hadoop 1.1.2

H.2　HBase安装

H.2.1　下载HBase

~ wget http://www.gaidso.com/apache/HBase/stable/HBase-0.94.18.tar.gz

#

通过wget

命令下载

~ tar xvf HBase-0.94.18.tar.gz   #

解压HBase

~ mv HBase-0.94.18/ /home/conan/hadoop/   #

移动HBase

目录到文件夹

~ cd /home/conan/hadoop/HBase-0.94.18     #

进入目录

H.2.2　配置HBase

修改启动文件HBase-env.sh。

~ vi conf/HBase-env.sh

export JAVA_HOME=/home/conan/toolkit/jdk16  #

打开注释

export HBase_CLASSPATH=/home/conan/hadoop/hadoop-1.1.2/conf

export HBase_MANAGES_ZK=true

修改配置文件HBase-site.xml。

~ vi conf/HBase-site.xml

<configuration>

<property>

<name>HBase.rootdir</name>

<value>hdfs://master:9000/HBase</value>

</property>

<property>

<name>HBase.cluster.distributed</name>

<value>true</value>

</property>

<property>

<name>dfs.replication</name>

<value>1</value>

</property>

<property>

<name>HBase.zookeeper.quorum</name>

<value>master</value>

</property>

<property>

<name>HBase.zookeeper.property.clientPort</name>

<value>2181</value>

</property>

<property>

<name>HBase.zookeeper.property.dataDir</name>

<value>/home/conan/hadoop/hdata</value>

</property>

</configuration>

复制Hadoop环境的配置文件和类库。

~ cp ~/hadoop/hadoop-1.1.2/conf/hdfs-site.xml conf/

~ cp ~/hadoop/hadoop-1.1.2/hadoop-core-1.1.2.jar lib/

~ mkdir /home/conan/hadoop/hdata

H.2.3　启动Hadoop和HBase服务器

~ /home/conan/hadoop/hadoop-1.1.2/bin/start-all.sh

~ /home/conan/hadoop/HBase-0.94.18/bin/start-HBase.sh

~ jps  #

查看HBase

进程

13838 TaskTracker

13541 JobTracker

15946 HMaster

16756 Jps

12851 NameNode

13450 SecondaryNameNode

13133 DataNode

15817 HQuorumPeer

16283 HRegionServer

H.2.4　打开HBase命令行客户端访问HBase

~ bin/HBase shell

HBase Shell; enter 'help<RETURN>' for list of supported commands.

Type "exit<RETURN>" to leave the HBase Shell

Version 0.94.18, r1577788, Sat Mar 15 04:46:47 UTC 2014

HBase(main):002:0> help   #

查看HBase

命令行帮助

H.3　Thrift安装

安装完成HBase后，我们还需要安装Thrift，因为其他语言调用HBase的时候，是通过Thrift连接的。Thrift是需要本地编译的，官方没有提供二进制安装包，首先下载thrift-0.9.1，Thrift下载页：http://thrift.apache.org/download。

H.3.1　下载Thrift

下载Thrift有两种方式，直接下载源代码发行包，或者通过Git下载源代码，请选择其中一种方式下载。首先直接下载源代码发行包thrift-0.9.1.tar.gz：

~ wget http://apache.fayea.com/apache-mirror/thrift/0.9.1/thrift-0.9.1.tar.gz

~ tar xvf thrift-0.9.1.tar.gz

~ mv thrift-0.9.1/ /home/conan/hadoop/

~ cd /home/conan/hadoop/

注　后文中的各种错误，都是发行包引起的，不建议使用这个包。

通过Git下载源代码。

~ git clone https://git-wip-us.apache.org/repos/asf/thrift.git thrift-git

~ mv thrift-git/ /home/conan/hadoop/

~ cd /home/conan/hadoop/

为了避免各种出错，建议使用Git下载源代码编译安装。

H.3.2　通过Git源代码安装Thrift

运行安装命令。

~ cd /home/conan/hadoop/thrift-git    #

进行thrift-git

目录

~ git checkout -b thrift-0.9.1 0.9.1  #

复制0.9.1

标签到新分支thrift-0.9.1

~ ./bootstrap.sh   #

产生配置脚本

# Thrift

是需要本地编译的，安装Thrift

的依赖包：

~ sudo apt-get install libboost-dev libboost-test-dev libboost-program-options-

dev libevent-dev automake libtool flex bison pkg-config g++ libssl-dev

~ ./configure   #

生成配置信息

~ make    #

编译Thrift

~ sudo make install   #

安装Thrift

使用Git源代码版本安装好了Thirft。下面查看Thrift版本。

~ thrift -version

Thrift version 0.9.1

接下来，我们启动HBase的Thrift Server服务。

~ /home/conan/hadoop/HBase-0.94.18/bin/HBase-daemon.sh start thrift

#

启动HBase

的Thrift

服务

starting thrift, logging to /home/conan/hadoop/HBase-0.94.18/bin/../logs/HBase-

conan-thrift-master.out

~ jps   #

检查系统进程

13838 TaskTracker

13541 JobTracker

15946 HMaster

32120 Jps

12851 NameNode

13450 SecondaryNameNode

13133 DataNode

32001 ThriftServer

15817 HQuorumPeer

16283 HRegionServer

我们看到ThriftServer已被启动，然后我们就可以使用多种语言，通过Thrift来访问HBase了，这样就完成了HBase的安装。

Table of Contents

序一

序二

前言

第一部分 R基础

第1章 R语言基础包

1.1 R是最值得学习的编程语言

1.2 R的历史版本安装

1.3 fortunes记录R语言的大智慧

1.4 formatR代码自动化排版

1.5 多人在线协作R开发RStudio Server

1.6 R和JSON的傻瓜式编程

1.7 R语言的高质量图形渲染库Cairo

1.8 caTools：一个奇特的工具集

第2章 时间序列基础包

2.1 R语言时间序列基础库zoo

2.2 可扩展的时间序列xts

2.3 时间序列可视化plot.xts

第3章 R性能监控包

3.1 R语言本地缓存工具memoise

3.2 R语言性能监控工具Rprof

3.3 R语言性能可视化工具lineprof

第二部分 R服务器

第4章 R语言的跨平台通信

4.1 Rserve与Java的跨平台通信

4.2 Rsession让Java调用R更简单

4.3 解惑rJava R与Java的高速通道

4.4 Node.js与R跨平台通信

第5章 R的服务器实现

5.1 R语言服务器程序Rserve详解

5.2 Rserve的R语言客户端RSclient

5.3 FastRWeb：跑在Web上的R程序

5.4 R语言构建Websocket服务器

第三部分 数据库和大数据

第6章 数据库和NoSQL

6.1 RMySQL数据库编程指南

6.2 R利剑NoSQL 之MongoDB

6.3 R利剑NoSQL之Redis

6.4 R利剑NoSQL之Cassandra

6.5 R利剑NoSQL之Hive

6.6 用RHive从历史数据中提取逆回购信息

第7章 RHadoop

7.1 R语言为Hadoop注入统计血脉

7.2 RHadoop安装与使用

7.3 RHadoop实验：统计邮箱出现次数

7.4 RHadoop实现基于MapReduce的协同过滤算法

7.5 rHBase安装与使用

7.6 解决RHadoop安装错误：PipeMapRed.waitOutputThreads（）

第四部分 附录

附录A Java环境安装

附录B MySQL数据库安装

附录C Redis数据库安装

附录D MongoDB数据库安装

附录E Cassandra数据库安装

附录F Hadoop安装

附录G Hive环境安装

附录H HBase安装