机器学习:实用案例解析


机器学习:实用案例解析 Drew Conway & John Myles White 著 陈开江 刘逸哲 孟晓楠 译 罗森林 审校 机械工业出版社 O’Reilly Media, Inc.授权机械工业出版社出版 Beijing • Cambridge • Farnham • Köln • Sebastopol • T o k y o machine_learn_hacker-文前.indd 2 2013.4.1 1:17:14 PM 图书在版编目(CIP)数据 机器学习:实用案例解析/(美)康威(Conway, D.)等著;陈开江,刘逸哲, 孟晓楠译. —北京:机械工业出版社,2013.3 (O’Reilly精品图书系列) 书名原文:Machine Learning for Hackers ISBN 978-7-111-41731-6 I. 机… II. ①康… ②陈… ③刘… ④孟… III.机器学习 IV. TP181 中国版本图书馆CIP数据核字(2013)第042873号 北京市版权局著作权合同登记 图字:01-2012-4851号 ©2012 Drew Conway and John Myles White. Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and China Machine Press, 2013. Authorized translation of the English edition, 2012 O’Reilly Media, Inc., the owner of all rights to publish and sell the same. All rights reserved including the rights of reproduction in whole or in part in any form. 英文原版由O’Reilly Media, Inc. 出版2012。 简体中文版由机械工业出版社出版 2013。英文原版的翻译得到O’Reilly Media, Inc.的授权。此简体中文版 的出版和销售得到出版权和销售权的所有者 —— O’Reilly Media, Inc.的许可。 版权所有,未得书面许可,本书的任何部分和全部不得以任何形式重制。 封底无防伪标均为盗版 本书法律顾问 北京市展达律师事务所 书 名/ 机器学习:实用案例解析 书 号/ ISBN 978-7-111-41731-6 责任编辑/ 秦健 封面设计/ Karen Montgomery,张健 出版发行/ 机械工业出版社 地 址/ 北京市西城区百万庄大街22号(邮政编码 100037) 印 刷/ 开 本/ 178毫米×233毫米 16开本 20印张(含1印张彩插) 版 次/ 2013年4月第1版 2013年4月第1次印刷 定 价/ 69.00元(册) 凡购本书,如有缺页、倒页、脱页,由本社发行部调换 客服热线:(010)88378991 88361066 购书热线:(010)68326294 88379649 68995259 投稿热线:(010)88379604 读者信箱:hzjsj@hzbook.com machine_learn_hacker-文前.indd 3 2013.4.1 1:17:14 PM O’Reilly Media, Inc.介绍 O’Reilly Media通过图书、杂志、在线服务、调查研究和会议等方式传播创新知识。自1978年 开始,O’Reilly一直都是前沿发展的见证者和推动者。超级极客们正在开创着未来,而我们关 注真正重要的技术趋势——通过放大那些“细微的信号”来刺激社会对新科技的应用。作为 技术社区中活跃的参与者,O’Reilly的发展充满了对创新的倡导、创造和发扬光大。 O’Reilly为软件开发人员带来革命性的“动物书”;创建第一个商业网站(GNN);组织了 影响深远的开放源代码峰会,以至于开源软件运动以此命名;创立了Make杂志,从而成为 DIY革命的主要先锋;公司一如既往地通过多种形式缔结信息与人的纽带。O’Reilly的会议和 峰会集聚了众多超级极客和高瞻远瞩的商业领袖,共同描绘出开创新产业的革命性思想。作 为技术人士获取信息的选择,O’Reilly现在还将先锋专家的知识传递给普通的计算机用户。无 论是通过书籍出版,在线服务或者面授课程,每一项O’Reilly的产品都反映了公司不可动摇的 理念——信息是激发创新的力量。 业界评论 “O’Reilly Radar博客有口皆碑。” ——Wired “O’Reilly凭借一系列(真希望当初我也想到了)非凡想法建立了数百万美元的业务。” ——Business 2.0 “O’Reilly Conference是聚集关键思想领袖的绝对典范。” ——CRN “一本O’Reilly的书就代表一个有用、有前途、需要学习的主题。” ——Irish Times “Tim是位特立独行的商人,他不光放眼于最长远、最广阔的视野并且切实地按照 Yogi Berra的建议去做了:‘如果你在路上遇到岔路口,走小路(岔路)。’回顾 过去Tim似乎每一次都选择了小路,而且有几次都是一闪即逝的机会,尽管大路也 不错。” ——Linux Journal ormintro.indd 1 2013.4.1 1:18:24 PM 当今各行业,尤其是互联网,数据规模越来越大,要从中有效地发现模式来提高生产 力,用传统的方式已经几乎不可能,只能借助计算机来完成诸多使命。因此,机器学习 这一新兴的学科变得越来越重要,它已经在搜索、推荐、数据挖掘等多个领域闪耀光 芒。机器学习是一门交叉学科,内容涉及概率论、统计学、高等数学、计算机科学等多 门学科。该学科致力于设计一种让计算机具有“学习”能力的算法,通过发现经验数据 中隐藏的模式,实现对未知数据的预测。 大数据时代是机器学习最美好的时代,因为数据不再是问题,各类问题都可以收集到海 量的数据。但是,对于很多人来说,这一门交叉学科本身却神秘而陌生,对于没有系 统学习过相关基础学科的人来说尤其感到“高不可攀”。如今已出版的机器学习相关书 籍中,很多都有这个特点:公式多,晦涩难懂。这让很多程序员出身的人望而却步。然 而,在第一次读到本书的英文版时,译者就彻底相信:机器学习完全可以讲解得通俗易 懂,让知识 的传递实现“润物细无声”。 本书秉承的原则是:实践出真知,只要多动手,没有攻克不了的技术难题。因此作者预 期的阅读对象是如电脑黑客般的人,要求对技术有发自内心的求知欲和好奇心,愿意自 己动手而非纸上谈兵。全书精心选择了12个机器学习案例,由浅入深,面面俱到,既有 基础知识(如数据分析),也有当前热门的社交网站推荐案例。书中的每一个案例都由 作者娓娓道来,逐一剖析关键算法的代码,没有丝毫学究气息,触动每个机器学习初学 者的内心最深处。 书中所有算法都采用R语言实现。R语言是一门用于统计学的开源脚本语言,基于它的开 源性,有来自世界各地的开源拥护者贡献的各种统计学相关的程序包,稳定且方便,尤 其是它对数据可视化的支持,更是一柄利器,既轻巧又实用。书中所有源代码和数据在 译者序 machine_learn_hacker-译者序.indd 1 2013.4.1 1:17:35 PM 原书的官方网站上都可以免费下载。在阅读过程中,犹如作者亲至身侧,为你讲解代码 和思路,为你排除错误和优化效果。 全书案例既有分类问题,也有回归问题;既包含监督学习,也涵盖无监督学习。所选择 的案例妙趣横生,如分析UFO目击记录、破译密码、预测股票、分析美国参议员“结 党”的情况,等等,这里就不“剧透”了,大家自己去享受学习的乐趣吧。 书中12个案例之间的依赖关系不是特别强(除R语言基础知识外,其余某几章仅有个别 知识点之间存在依赖性),可以像连续剧一样,逐一播放,也可以像一个个小品一般, 挑感兴趣的内容分别播放。学习完这些案例之后,相信你会窥见机器学习的一斑,然后 再根据自己的实际情况更深入地学习。 本书翻译工作由三位来自互联网世界的工程师通力协作完成,其中,来自新浪微博的陈 开江负责完成前言及第1~4章的翻译;来自阿里B2B的刘逸哲负责完成第5、8、9和11章 的翻译;来自阿里一淘的孟晓楠负责完成第6、7、10和12章的翻译;同时,全书审校工 作由来自北京理工大学的罗森林教授义务承担。 本书能够得以出版,首先要感谢机械工业出版社的吴怡编辑,是她给了我们三位工程师 这个学习知识并传递知识的机会,她经验丰富,在翻译过程中给予了我们许多建设性 的指导意见。其次,要感谢罗森林教授,他在百忙之中为我们担任全书的审校工作,从 而让国内的机器学习者能感受到这本书应有的魅力。最后,我们要感谢互联网,因为译 者与本书的缘分始于互联网,从看到原书、报名翻译、组成翻译团队、翻译过程中的讨 论,所有这样都是通过互联网完成的。 虽然经过罗森林教授认真审校并且给我们提出了宝贵意见,但是由于译者本身水平有 限,书中译文势必还存在不妥甚至错误之处,恳请机器学习界的广大前辈、同仁们不吝 赐教,促使我们继续为大家更好地传递先进技术,让更多机器学习爱好者成为机器学习 的黑客。 我们坚信集体智慧是再高的个人智慧都无法企及的,因此真诚希望大家一起来贡献自己 的 智慧。三位译者的微博分别为:http://weibo.com/kaijiangidan(陈开江,@刑无刀)、 http://weibo.com/liuyizhe10(刘逸哲,@刘逸哲)、http://weibo.com/u/1911115643(孟晓 楠,@XiaonanMeng)。无论是对翻译本身有任何意见或建议,还是对机器学习方面有 心得,都欢迎大家到我们的微博上交流、切磋,我们一起贡献自己的智慧,在集体智慧 中互相学习,共同进步。 machine_learn_hacker-译者序.indd 2 2013.4.1 1:17:35 PM i 目录 前言 ...............................................................................1 第1章 使用R语言 ............................................................9 R与机器学习 .........................................................................................................10 第2章 数据分析 ............................................................ 36 分析与验证 ............................................................................................................36 什么是数据 ............................................................................................................37 推断数据的类型 ....................................................................................................40 推断数据的含义 ....................................................................................................42 数值摘要表 ............................................................................................................43 均值、中位数、众数 .............................................................................................44 分位数 ...................................................................................................................46 标准差和方差 ........................................................................................................47 可视化分析数据 ....................................................................................................49 列相关的可视化 ....................................................................................................68 第3章 分类:垃圾过滤 .................................................. 77 非此即彼:二分类 .................................................................................................77 漫谈条件概率 ........................................................................................................81 试写第一个贝叶斯垃圾分类器 ..............................................................................82 machine_learn_hacker-table.indd 1 2013.4.1 1:16:16 PM ii | 目录 第4章 排序:智能收件箱 ............................................... 97 次序未知时该如何排序 .........................................................................................97 按优先级给邮件排序 .............................................................................................98 实现一个智能收件箱 ...........................................................................................102 第5章 回归模型:预测网页访问量 ............................... 128 回归模型简介 ......................................................................................................128 预测网页流量 ......................................................................................................142 定义相关性 ..........................................................................................................152 第6章 正则化:文本回归 ............................................. 155 数据列之间的非线性关系:超越直线 .................................................................155 避免过拟合的方法 ...............................................................................................164 文本回归 .............................................................................................................174 第7章 优化:密码破译 ................................................ 182 优化简介 .............................................................................................................182 岭回归 .................................................................................................................188 密码破译优化问题 ...............................................................................................193 第8章 PCA:构建股票市场指数 .................................. 203 无监督学习 ..........................................................................................................203 主成分分析 ..........................................................................................................204 第9章 MDS:可视化地研究参议员相似性 .................... 212 基于相似性聚类 ..................................................................................................212 如何对美国参议员做聚类 ...................................................................................219 第10章 kNN:推荐系统 .............................................. 229 k近邻算法 ............................................................................................................229 R语言程序包安装数据 ........................................................................................235 machine_learn_hacker-table.indd 2 2013.4.1 1:16:16 PM 目录 | iii 第11章 分析社交图谱 .................................................. 239 社交网络分析 ......................................................................................................239 用黑客的方法研究Twitter的社交关系图数据 ......................................................244 分析Twitter社交网络 ...........................................................................................252 第12章 模型比较 ........................................................ 270 SVM:支持向量机 ..............................................................................................270 算法比较 .............................................................................................................280 参考文献 .................................................................... 287 machine_learn_hacker-table.indd 3 2013.4.1 1:16:16 PM 1 致机器学习的黑客们 为了更好地阐释本书的切入点,很有必要对“机器学习”与“黑客”这两个词语下个 定义。 什么是机器学习?简单来说,机器学习就是一套工具和方法,凭借这些工具和方法我们 可以从观测到的样本中提炼模式、归纳知识。举个例子,如果我们要让计算机识别信封 上的邮政编码,那么需要这样的数据:首先是信封的图片数据,其次信封上必须有收件 人的邮政编码。换句话说,在特定情境下,我们可以记录研究对象的行为,从中学习, 然后对其行为建模,该模型反过来促进我们对该情境有更深入的理解。在实际项目中, 机器学习需要数据,而且对当今的应用程序来说不是一点点数据(有可能达TB级的数 据)。大多数机器学习技术不担心没有数据,现代企业运营所产生的数据量之大意味着 这些技术应用的春天来了。 什么是黑客呢?在我们眼中,黑客就是喜好用新技术进行实验、解决问题的人,而与 “网络罪犯”、“不法少年”这些世俗字眼完全无关。如果你曾经手捧O’Re illy最新出 版的一本关于一门新计算机语言的图书,跌跌撞撞地敲下代码,并最终调试通过了你的 第一个程序,那么你就称得上是一名黑客。或者,你曾把新买来的小机器大卸八块,并 最终弄懂了它的整个机械结构,那么你也是个黑客。通常,黑客这样做并无特别的原 因,只是为了享受这个过程,只是为了要彻底了解一门未知的技术。 计算机黑客对事物原理有一种与生俱来的好奇心和动手的热情,他们(与之相对应的还 有汽车黑客、生活黑客、美食黑客,等等)还有软件设计和开发的经验,他们就是以前 写过程序的人,甚至很可能使用过很多种语言。对于一个黑客来说,UNIX不是一个四 前言 machine_learn_hacker-all.indd 1 2013.4.1 1:13:17 PM 2 | 前言 个字母的单词,工作时用命令行导航和bash shell操作与用图形用户界面一样熟练。处理 文本时,黑客首先想到的就是正则表达式,以及sed、awk、grep这些工具。在本书的写 作中,我们也假设读者在这些方面的知识水平比较高。 本书的组织结构 机器学习融合了许多传统领域的理论和技术,诸如数学、统计学和计算机科学等。因 此,学习本学科存在许多切入点。由于数学和统计学是机器学习的理论基础,因此新 手应该在一定程度上掌握机器学习基础技术的范式。市面上已有很多这方面的优秀书 籍,如Hastie、Tibshirani、Friedman三人的经典著作《统计学习基础》(The Elements of Statistical Learning,[HTF09],完整信息见参考书目)注1。但是在黑客们的人生理念 里,很重要的一部分就是:边做边学。很多黑客在面对问题的时候,更习惯于在实际操 作过程中寻找解决方案,而非从理论基础出发推导解决方案。 从这个角度而言,教授机器学习的另一种方法就是采用案例式教学。例如,在讲解推荐 系统时,我们会提供一批训练样本和一版模型,然后看看模型如何使用训练样本。类似 的参考书有很多,比较新的一本是Segaran的《集体智慧编程》(Programming Collective Intelligence,[Seg07])。以上的讨论只是介绍了操作方式,却没有解释为什么这样做。 在理解了一个方法的原理时,我们也许还想知道为何这个方法适用于某个情境,或者为 何解决了某个特定的问题。 因此,为了给机器学习的黑客们提供一本更全面的参考书,我们必须在学科理论的深度 和应用探索的广度之间寻求一个平衡。为了达到这个目的,我们决定采用案例教学的方 式来教授机器学习。 最好的学习方法是:首先带着问题思考,然后专心研究解决问题的方法。这也是案例教 学能有效执行的机理所在。不同之处在于,我们并不是拿一些还没有成熟解决方法的 机器学习问题来举例,而是讨论一些已深入理解和广泛研究的问题,并列举了一些特定 的案例辅以说明。对于这些案例,有些方法可以很好地解决问题,而有些方法却根本不 适用。 基于上述指导思想,本书的每一章都是基于特定机器学习问题的独立案例研究。本书前 几个案例从分类讲到回归(第1章会进一步讨论),然后又讨论了聚类、降维、最优化 问题等。需要说明的是,不是所有的问题都可以简单地归为分类问题或者回归问题,书 中涉及的一些案例同时包含分类与回归问题(有些比较明显,有些不易察觉)。以下是 本书中出现的所有案例研究的简要介绍(按出现先后顺序)。 注1: 这本书也可以从http://www-stat.stanford.edu/~tibs/ElemStatLearn/免费下载。 machine_learn_hacker-all.indd 2 2013.4.1 1:13:17 PM 前言 | 3 文本分类:垃圾邮件识别 这一章介绍由电子邮件文本数据引起的二分类问题。在此要处理的是机器学习中的 经典问题:将某个输入识别为两个类中的一个,在这里指的就是正常邮件(合法的 电子邮件)或垃圾邮件(用户不希望看到的邮件)。 项目排序:智能收件箱 与上个案例一样,这里还是采用电子邮件文本数据,但是不再研究二分类问题,而 是上升到研究一组具体的类别。具体来说,就是要在某一电子邮件中识别并抽取适 当的特征,这些特征使该邮件在所有邮件中处于优先阅读的位置。 回归模型:预测网页访问量 现在介绍机器学习的第二个基本工具——线性回归。这里要处理的是关系大致逼近 一条直线的数据。在这个案例研究中,目的是预测互联网上排名前1000(2011年) 的网页访问量。 正则化:文本回归 有时候,我们并不能用一条直线很好地描述数据间的关系。为了描述这个关系就要 用另一种函数来拟合,但同时又要防止出现过拟合。正则化的方法可以克服这一问 题,同时通过一个案例加以说明,主要目的是理解O’Reilly图书中词与词之间的 关系。 最优化:密码破解 机器学习中几乎每一个算法都可以看成是最优化问题,比如,将预测错误率最小 化。这里绍一个经典的算法来实现最优化,并尝试用这个算法破解一段字母密码。 无监督学习:构建股票市场指数 到目前为止我们的讨论还局限于有监督的学习技术。在此要介绍机器学习方法上的 另一半:无监督学习。两者最主要的不同是:有监督学习方法是使用结构化数据进 行预测,而无监督学习是为了在数据中发现结构。因此,我们将用一批股票市场的 数据来构建一个指数,这个指数可以衡量整体市场行情的好坏。 空间相似度:用投票记录对美国参议员聚类 这里介绍这样一个概念:样本点的空间距离。为了实现对参议员聚类,需要设计距 离的测算方法,以及样本点基于空间距离的聚类方法。我们用美国参议员记名投票 的数据,根据其所得投票记录对这些立法者进行聚类。 推荐系统:给用户推荐R语言包 为深入讨论空间相似度,我们将讨论如何搭建一个基于样本空间密度的推荐系统。 这一章介绍K近邻算法,并根据程序员安装的R语言函数包,用这个算法来给他们 推荐其他的R语言包。 machine_learn_hacker-all.indd 3 2013.4.1 1:13:17 PM 4 | 前言 社交网络分析:在Twitter上感兴趣的人 这一章会结合之前讨论过的许多概念,并引入一些新的概念与方法,用Twitter数据 设计并搭建一个“可能感兴趣的人”的推荐系统。在这个例子中,将搭建一个系统 用于下载Twitter数据,发现其中的圈子,然后用基本的社交网络分析技术向用户推 荐可能感兴趣的人。 模型比较:给你的问题找到最佳算法 最后一章讨论的是用于选择解决问题的最佳机器学习方法的技巧。这一章将介绍最 后一个算法——支持向量机,并采用在第3章中介绍的垃圾邮件数据来比较其与其 他算法的优劣。 我们在探索这些案例的过程中用到的基本工具就是R统计编程语言(http://www.r-project. org/)。R语言非常适合于机器学习的案例研究,因为它是一种用于数据分析的高水平、 功能性脚本语言。很多基础算法框架已经内置在R语言中,或者已经在一些R语言包中实 现了,这些包可以在综合R档案网(Comprehensive R Archive Network,CRAN)注2上 找到。这可以避免为每一个实际项目写基础功能代码,从而把我们从重复劳动中解放出 来,把精力放在思考问题的本身上。 本书约定 本书使用了以下排版约定: 斜体 用于新术语、URL、电子邮件地址、文件名与文件扩展名。 等宽字体(Constant width) 用于表明程序清单,以及在段落中引用的程序中的元素,如变量、函数名、数据 库、数据类型、环境变量、语句、关键字。 等宽粗体(Constant width bold) 用于表明命令,或者需要读者逐字输入的文本内容。 等宽斜体(Constant width italic) 用于表示需要使用用户提供的值或者由上下文决定的值来替代的文本内容。 注意: 这个图标代表一个技巧、建议或一般性说明。 注2: 关于CRAN的更多信息,请浏览http://cran.r-project.org/。 machine_learn_hacker-all.indd 4 2013.4.1 1:13:17 PM 前言 | 5 警告: 这个图标代表一个警告或注意事项。 示例代码的使用 本书提供代码的目的是帮你快速完成工作。一般情况下,你可以在你的程序或文档中使 用本书中的代码,而不必取得我们的许可,除非你想复制书中很大一部分代码。比方 说,你在编写程序时,用到了本书中的几个代码片段,这不必得到我们的许可。但若将 O’Reilly图书中的代码制作成光盘并进行出售或传播,则需获得我们的许可。引用示例 代码或书中内容来解答问题无需许可。将书中很大一部分的示例代码用于你个人的产品 文档,这需要我们的许可。 如果你引用了本书的内容并标明版权归属声明,我们对此表示感谢,但这也不是必 需的。版权归属声明通常包括:标题、作者、出版社和ISBN号,例如:“Machine Learning for Hackers by Drew Conway and John Myles White (O’Reilly). Copyright 2012 Drew Conway and John Myles White, 978-1-449-30371-6”。 如果你认为你对示例代码的使用已经超出上述范围,或者你对是否需要获得示例代码的 授权还不清楚,请随时联系我们:permissions@oreilly.com。 联系我们 有关本书的任何建议和疑问,可以通过下列方式与我们取得联系: 美国: O’Reilly Media,Inc. 1005 Gravenstein Highway North Sebastopol,CA 95472 中国: 北京市西城区西直门南大街2号成铭大厦C座807室(100035) 奥莱利技术咨询(北京)有限公司 我们会在本书的网页中列出勘误表、示例和其他信息。可以通过http://shop.oreilly.com/ product/0636920018483.do访问该页面。 要评论或询问本书的技术问题,请发送电子邮件到: bookquestions@oreilly.com machine_learn_hacker-all.indd 5 2013.4.1 1:13:17 PM 6 | 前言 想了解关于O’Reilly图书、课程、会议和新闻的更多信息,请访问以下网站: http://www.oreilly.com.cn http://www.oreilly.com 还可以通过以下网站关注我们: 我们在Facebook上的主页:http://facebook.com/oreilly 我们在Twitter上的主页:http://twitter.com/oreillymedia 我们在Youtube上的主页:http://www.youtube.com/oreillymedia 致谢 两位作者的共同致谢: 首先,我们要感谢编辑Julie Steele,他为我们第一本书的问世出力不少。其次,我们要 感谢Melanie Yarbrough和Genevieve d’Entremont,由于他们细致入微的校对工作,才 使本书得以出版。此外,我们还要感谢O’Reilly公司给我们的书建言献策的其他幕后工 作人员。除了O’Reilly的工作人员,我们还要感谢本书的技术审校者Mike Dewar、Max Shron、Matt Canning、Paul Dix和 Maxim Khesin,他们的意见让这本书有了很大的提 升,本书若再有错误出现,那就是我们自己的问题了。 我们也想感谢NYC Data Brunch数据中心的成员们,最初是他们鼓励我们写这本书的, 并且给我们提供场地来完善机器学习教学这一想法。我们特别要感谢Hilary Mason把我 们引荐给了O’Reilly公司。 最后,我们要感谢数据科学圈内的很多朋友,他们非常支持我们,不断鼓励我们,让我 们在整个写作过程中干劲十足。因为知道人们在期待这本书,所以我们在写作整本书这 段漫长旅途上从未停下过脚步。 Drew Conway的致谢: 我想感谢编辑Julie Steele,她欣赏我们的写作动机,并帮助我们出版这本书。我要感 谢在本书写作过程中和完成后所有给我们反馈意见的人们,特别是Mike Dewar、Max Shron、Matt Canning、Paul Dix和 Maxim Khesin。我要感谢我的妻子Kristen,她不仅给 我灵感,而且一直陪伴我、鼓励我。最后,我要感谢我的搭档John,是他提出了写这样 一本书的创意,并且坚信能够完成。 John Myles White的致谢: machine_learn_hacker-all.indd 6 2013.4.1 1:13:17 PM 前言 | 7 首先我要感谢我的搭档Drew和我一起写了这本书。有人合作写一本书,会让整个过程 易于掌控,而且乐在其中。此外,我要感谢我的双亲,他们总是鼓励我去探索感兴趣的 任何一个领域。我也要感谢Jennifer Mitchel 和Jeffrey Achter,是他们不断激励我在本科 阶段把精力放在数学上。大学的岁月刻画了我的世界观,我很感激在这个过程中得到 他们的指导。我同样要感谢我的朋友Harek,因为是他不断地促我奋进、超越自我。此 外,我还要感谢La Dispute乐队的音乐,陪伴我度过整个写作过程。最后,我要感谢所 有在我工作时给我提供过容身之处的人,包括给我提供沙发的朋友们以及Boutique Hotel Steinerwirt 1493酒店和Linger Café咖啡店的老板,因为正是在这两个地方,我完成了本 书的初稿和终稿。 machine_learn_hacker-all.indd 7 2013.4.1 1:13:18 PM machine_learn_hacker-all.indd 8 2013.4.1 1:13:18 PM 9 第1章 使用R语言 机器学习是由软件工程、计算机科学这样的新兴学科与数学、统计学这样的传统学科交 叉形成的一门学科。在本书中,我们会介绍统计学领域的一些有助于人们认识世界的工 具。统计学一直在研究如何从数据中得到可解释的东西,而机器学习则关注如何将数据 变成一些实用的东西。对两者做出如下对比更有助于理解“机器学习”这个术语:机器 学习研究的内容是教给计算机一些知识,再让计算机利用这些知识完成其他的任务。相 比之下,统计学则更倾向于开发一些工具来帮助人类认识世界,以便人类可以更加清晰 地思考,从而做出更佳的决策。 在机器学习中,学习指的是采用一些算法来分析数据的基本结构,并且辨别其中的信号 和噪声,从而提取出尽可能多的(或者尽可能合理的)信息的过程。在算法发现信号或 者说模式之后,其余的所有东西都将被简单判断为噪声。因此,机器学习技术也称为模 式识别算法。我们可以“训练”机器去学习数据是如何在特定情境中产生的,从而 使用 这些算法将许多有用的任务实现自动化。这就引出了训练集(training set)这一术语, 它指的是构建机器学习过程所用到的数据集。观测数据、从中学习、自动化识别过程, 这三个概念是机器学习的核心,同时也是本书的主线。本书的核心问题包含两个特别重 要的模式:分类问题和回归问题,这两类问题在书中会不断出现,当然我们也会教给大 家解决方法。 在本书中,我们假设读者具备相对较高水平的基本编程技能和算法知识。R语言一直是 一门相当小众的编程语言,甚至对于许多资深程序员来说亦是如此。为了尽量让每一个 读者都处于同一起点,本章将介绍一些R语言的入门基础知识。稍后还将介绍一个使用R 语言来处理数据的延伸案例研究。 machine_learn_hacker-all.indd 9 2013.4.1 1:13:18 PM 10 | 第1章 警告: 本章并非要完整地介绍R编程语言。况且仅用一个章节来完整地介绍R也根本不可能。相 反,这一章是为了让读者做好相关知识准备,进而用R来完成机器学习任务,尤其是用R来 加载、探索、清洗以及分析数据等。已有许多不错的资源专注于R基础知识,包括数据类 型、算术运算概念以及最佳编码实践。本章要展示的案例研究与上述这些R基础概念都有关 联。虽然我们会谈及上述每个问题,但都不会太深入。若读者有兴趣对这些知识点进行回 顾,表1-3列出了可供参考的资源。 如果你此前从未接触过R及其语法,我们强烈建议你通过阅读本章来扫扫盲。R不像其他 高层次脚本语言,如Python和Ruby,它的语法独特且晦涩,往往不如其他编程语言容易 上手。如果你此前用过R,但不是用于机器学习,那么在进行案例研究之前也有必要花 时间看看以下综述。 R与机器学习 R是用于统计计算、绘图的语言和操作环境……R提供了丰富多样的统计功能(线 性和非线性建模、经典统计学实验、时间序列分析、分类、聚类……)和绘图技 术,并且具有高度可扩展性。在统计方法学研究中,S语言是一种常用的工具,而 R则以开源的方式跻身其中。 ——用于统计计算的R项目,http://www.r-project.org/ R最大的优势是:它是由统计学家们开发的。R最大的劣势是……它是由统计学家 们开发的。 ——Bo Cowgill,Google公司 在处理和分析数据方面,R是一门非常强大的语言。它在数据科学界以及机器学习界的 迅速流行已经使它成为分析学领域实际上的通用语言。R在数据分析界的成功源于上述 引文中提到的两个因素:首先,R内置了统计学家们所需的技术;其次,R备受统计学界 开源贡献者们的支持与推崇。 专门为统计计算设计的编程语言具有很多技术上的优势。正如R项目所述,这门语言提 供了一座通往S的开源桥梁,而S中所包含的许多基础函数都是高度专业化的统计操作。 例如,用R来执行一个基本的线性回归操作,你只需简单地把数据传入lm函数,然后函 数返回一个包含了详细回归信息(回归系数、标准误差、残差等)的对象。然后,这个 数据可以用plot函数进行可视化,plot函数是专用于对分析结果进行可视化的函数。 与科学计算相关的其他大众编程语言,比如Python,要实现和lm函数相同的功能,需 要若干第三方库分别来完成数据表达(NumPy)、进行分析(SciPy)、将结果可视化 machine_learn_hacker-all.indd 10 2013.4.1 1:13:18 PM 使用R语言 | 11 (matplotlib)等过程。这样复杂的分析步骤,R只需一行代码就可完成,我们将在接下 来的几个章节中见识到这些。 此外,和其他科学计算环境一样,R的基本数据类型也是向量。在本质上,R语言里的 所有的数据都是向量,尽管它们有不同的聚合和组织方式。尽管这种数据结构理念比较 僵化,但考虑到R的应用,这种观点还算合乎逻辑。R中最常用到的数据结构就是数据 框(data frame),可以把它看做R内核中一种带属性的矩阵、一种内部定义的数据表结 构,或者一种关系型数据库式结构。从根本上讲,数据框就是将向量简单地按列聚合的 结果,同时R为其提供一些特别的功能,这样一来,数据框就成为适用于任何形式数据 的理想结构。 警告: 正因其有所长,R也有短板——R并不能很好地处理大数据。尽管已有很多人在努力解决, 但这仍然是一个严重的问题。然而,对于我们将要探讨的案例研究来说,这不是个问题。 我们使用的数据集相对较小,要搭建的系统也都只是原型系统或概念验证模型。这个区别 很重要,因为如果你要搭建Google或Facebook那样规模的企业级机器学习系统,选择R并不 合适。事实上,像Google或Facebook这些公司通常把R作为“数据沙箱”,用于处理数据以 及实验新的机器学习方法。如果某个实验有了成果,那么工程师就会把R中的相关功能用更 适合的语言复现出来,比如C语言。 这种实验精神也孕育出了R用户群的一种集体感。R的社会优势依赖于这个巨大且不断 成长的专家圈,是他们坚持使用并不断丰富这一语言的。正如Bo Cowgill所言,统计学 家强烈希望有一种计算环境能够满足他们的特殊需求,R才得以诞生。因此,很多R用 户都是各自领域的专家,这包括一些完全不同的学科,比如数学、统计学、生物学、化 学、物理学、心理学、经济学以及政治学等。这个专家圈用R大量的基础函数打造了许 许多多的程序包。在本书写作之时,R的程序包资源库CRAN中就已包含2800多个程序 包。在接下来的案例研究中,我们会用到许多最流行的程序包,但这也仅仅是R的皮毛 而已。 虽然Cowgill的话后半句有些言重,但是这进一步强调了R用户群的力量之大。随后我们 会发现,R古怪的语法常常让代码“错误百出”,这足以让许多资深程序员敬而远之。 但是,在一门语言中,所有的语法难题最终都可以攻克,尤其对于那些持之以恒的黑 客们而言。对于非统计学出身的人而言,更大的困难是不熟悉R中内置的数学和统计函 数。比如,使用lm函数时,如果你从未接触过线性回归,你可能就不知道结果里面已经 包含了回归系数、标准误差和残差,你也不知道结果该怎么解释。 因为这门语言是开源的,所以你可以随时查看函数的源代码,看看内部是怎么运行的。 本书的目的之一就是要探索这些函数在机器学习的情境下怎么使用,但这最终也只能让 machine_learn_hacker-all.indd 11 2013.4.1 1:13:18 PM 12 | 第1章 你窥见R所有功能的冰山一角。值得庆幸的是,在R社区中,很多人不仅乐于帮助你理解 这门语言,而且乐于帮助你了解其内部的实现。表1-1列出了一些最佳的R社区。 表1-1:R的社区资源 资源 网址 介绍 RSeek http://rseek.org/ 当核心开发团队决定要开创一个S的开源版,并 称其为R时,他们并没有考虑到在互联网上搜索 与这门语言相关的资料是多么不容易,因为它 只以一个字母命名。此工具专门用于缓解这一 问题,提供了一个集中获得R文档与信息的渠道 Official R mailing lists http://www.r- 这里有一些R语言相关的邮件列表,内容包括最 project.org/ 新公告、程序包、开发以及帮助。许多该语言 mail.html 的核心开发者都经常查看该邮件列表,反馈也 很简明、及时 StackOverflow http://stackover 黑客们都知道StackOverflow.com是寻求包括R flow.com/ 在内任何语言编程技巧的首选网络资源。得益 questions/ 于几位著名R用户的努力,StackOverflow上总 tagged/r 有很多高手一直在添加和回答R相关的问题 #stats Twitter hash tag http://search. 在Twitter上也活跃着相当多的R用户,他们 twitter.com/ 把#rstats作为他们的标签。根据这条线索你可以 search?q= 找到有用的资源链接、找到R高手、提问——前 %23rstats 提是能用140个字符把问题表述清楚 R-Bloggers http://www.r- 上百人都在写博客分享他们如何将R用于研究、 bloggers.com/ 工作或只是为了好玩。R-bloggers.com把这些博 客聚合起来,并且提供一个唯一链接指向所有 与R相关的博客内容。这里也是用案例学习的好 去处 Video Rchive http://www.vcasmo. R用户群不断壮大,关于R的地方性会议也不断 com/user/ 增多。Rchive上传视频和演示文稿,专门记录 drewconway 这些会议上的演讲及专题报告,现在该网站收 录的报告已覆盖了全世界的R用户 本章余下的内容专门教你如何配置好R环境并且使用它,包括下载和安装R,以及下载R 程序包。本章以一个小型的案例研究作为结束,该案例研究将介绍一些我们在之后章节 常常用到的R知识,包括加载、清洗、组织以及分析数据等问题。 machine_learn_hacker-all.indd 12 2013.4.1 1:13:18 PM 使用R语言 | 13 下载和安装R 和其他开源项目一样,R有若干个不同地区的镜像下载站点。如果你的电脑上还没安装 R,第一步就是要下载R。登录http://cran.r-project.org/mirrors.html,选择离你最近的 CRAN镜像站点,选择了镜像站点后,根据你所用的操作系统下载适当的版本。 R依赖一些用C或Fortran编译的库。因此,你可以安装编译好的二进制版本,也可以选择 用源代码安装,这既取决于你的操作系统,又取决于你用源代码安装软件的熟练程度。 接下来我们一一演示如何在Windows、Mac OS X以及Linux环境下安装R,并配有用源代 码安装和二进制安装的说明。 最后要说明一点,R既有32位版本,也有64位版本。依据硬件和操作系统的配置,你可 以选择合适的版本进行安装。 Windows 网站上有两个子目录可提供Windows上的R安装文件:base和contrib。后者是一个包含 了所有扩展包的Windows二进制版本,而前者仅仅是包含基本功能的二进制安装文件。 选择base目录,然后下载最新编译的二进制文件即可进行安装。在R上安装程序包也不 难,而且不受语言的限制,因此没必要用contrib目录下的安装文件进行安装。只要一步 步跟着屏幕上的安装说明即可完成安装。 安装成功之后,在你的开始菜单中就有R应用程序的图标,可以用这些图标打开R图形用 户界面(RGui)和R控制台(R Console),见图1-1。 对于Windows系统下的大多数标准安装,这个过程不会出什么问题。如果你选择了自定 义安装或者在安装过程中遇到了一些问题,你可以在你所选择的镜像站点上找到R for Windows FAQ页面,查询问题。 Mac OS X Mac OS X用户就幸运得多了,因为操作系统已经预装了R。你可以打开Terminal.app, 然后在命令行中输入“R”确认是否已安装R。这样你就万事俱备了!然而,对部分用 户来说,需要安装GUI应用程序来与R Console进行交互,如此就得再安装一个软件。在 Mac OS X下,你也可以选择安装编译好的二进制版本,或者用源代码安装。对于没用 过Linux命令行的用户,我们推荐使用二进制版本安装。只需在http://cran.r-project.org/ mirrors.html上选择镜像站点,下载最新版本,然后按照屏幕说明操作就可以了。安装完 成后,在你的Applications文件夹下有两个文件:R.app(32位版本)和R64.app(64位版 本)。你可根据硬件的配置和Mac OS X的版本选择其中一个版本。 machine_learn_hacker-all.indd 13 2013.4.1 1:13:18 PM 14 | 第1章 图1-1:Windows系统下RGui与R Console 与Windows系统下的安装过程一样,如果你在Mac OS X下用二进制版本安装,那么整个过 程都不会有什么问题。打开刚安装好的R应用程序,出现的console界面和图1-2差不多。 图1-2:Mac OS X系统下64位版本的R控制台界面 machine_learn_hacker-all.indd 14 2013.4.1 1:13:19 PM 使用R语言 | 15 注意: 如果你的Mac OS X是自定义安装的,或者你想根据自己的配置自定义安装R,那么建议 使用源代码安装。在Mac OS X系统下用源代码安装R同时需要C和Fortran两种语言的编译 器,而该操作系统的标准安装中没有这两个编译器。你可以使用Mac Os X开发者工具盘 (DVD)来安装这两个编译器,这个盘在Mac Os X原始安装盘中。你也可以从镜像站点上 的tools目录下找到必需的编译器然后进行安装。 具备源代码安装所需的编译器之后,其安装过程就与大多数使用命令行安装的软件一 样,经典的三个步骤:configure、make和install。用Terminal.app进入放源代码的路径, 执行以下命令: $ ./configure $ make $ make install 由于操作系统中用户权限的不同,在执行第一步之前,你可能需要输入系统密码来激活 sudo权限。如果在二进制安装和源代码安装过程中遇到了任何问题,都可以到镜像站点 的R for Mac OS X FAQ页面查询原因。 Linux 许多版本的linux和MAC OS X一样,都预装了R。只需在命令行敲入“R”,就可以加 载R控制台。接着,你就可以开始编程了!CRAN上也有Debian、RedHat、SUSE以及 Ubuntu这些不同linux版本相应的R安装文件及安装说明。如果你使用其中一个版本进行 安装,我们建议参考针对你的操作系统的安装说明,因为不同版本的Linux操作系统,其 最佳实践存在的差异相当大。 集成开发环境和文本编辑器 由于R是一种脚本语言,因此在接下来的案例研究中大部分的工作都要用集成开发环境 (IDE)或者文本编辑器来完成,而不是直接在R控制台上输入程序。从下一节的内容 里,你会发现有些工作适合直接在控制台上完成,比如安装程序包,但是大多数时候你 还是愿意在IDE或者文本编辑器中写程序。 在Windows或者Mac OS X环境里运行的R图形用户界面,程序中都有一个基本的文本编 辑器。如果你想新建一个空白文档,你可以在菜单上依次选择File→New Document(文 件→新建程序脚本),也可以直接单击窗口顶部的空白文档图标(图1-3中高亮处)。 作为一个黑客,你应该已经有一个IDE或者文本编辑器了,我们建议你在本书的案例研 究过程中从这两者中选择最熟悉的环境来开发。界面上还有很多选项,在此不再一一列 举,我们也不想卷入Emacs和Vim之争。 machine_learn_hacker-all.indd 15 2013.4.1 1:13:19 PM 16 | 第1章 图1-3:R图形用户界面上的文本编辑器图标 安装和加载R程序包 目前已有很多精心设计、维护良好且广泛支持的与机器学习相关的R程序包。在我们要 进行的案例研究中,涉及的程序包主要用于:处理空间数据、进行文本分析、分析网络 拓扑等,还有些程序包用于与网络API进行交互,当然还有其他很多功能,不胜枚举。 因此,我们的任务很大程度上会依赖内置在这些程序包的函数功能。 加载R程序包很简单。实现加载的两个函数是:library和require。两者之间存在细 微差别,在本书中,主要差别是:后者会返回一个布尔值(TRUE或FALSE)来表示是否 加载成功。例如,在第6章中,我们会用到tm程序包来分词。要加载该程序包,我们既 可以用library也可以用require。在下面所举例子中,我们用library来加载tm包,用 require来加载XML包,再用print函数来显示require函数的返回值。可以看到,返回的 布尔值是“TRUE”,可见XML包加载成功了。 library(tm) print(require(XML)) #[1] TRUE 假如XML包还未安装成功,即require函数返回值为“FALSE”,那么我们在调用之前仍 需先安装成功这个包。 注意: 如果你刚安装成功R环境,那么你还需要安装较多的程序包才能完成本书的所有案例研究。 在R环境中安装程序包有两种方法:可以用图形用户界面进行安装,也可以用R控制台中 的install.packages函数来安装。考虑到本书目标读者的水平,我们在本书的案例研究 中会全部采用R控制台进行交互,但还是有必要介绍一下怎么用图形用户界面安装程序 包。在R应用程序的菜单栏上,找到Packages & Data→Package Installer(程序包→安装 程序包),点击之后弹出如图1-4所示的窗口。从程序包资源库的下拉列表中选择CRAN (binaries)(CRAN(二进制))或者CRAN(sources)(CRAN(源代码)),点击 Get List(获取列表)按钮,加载所有可安装的程序包,最新的程序包版本可以从CRAN (sources)(CRAN(源代码))资源库中获取。如果你的计算机上已经安装了所需的 machine_learn_hacker-all.indd 16 2013.4.1 1:13:19 PM 使用R语言 | 17 编译器,我们推荐用源代码安装。接着,选择要安装的包,然后点击Install Selected(安 装所选包),即可安装。 图1-4:用图形用户界面安装R程序包 相比而言,用install.packages函数来安装是一种更佳的方法,因为它在安装方式和安 装路径上更为灵活。这种方法的主要优势之一就是既可以用本地的源代码,也可以用 CRAN上的源代码来安装。虽然以下这种情况不太常见,但仍然有可能会需要。有时你 可能要安装一些CRAN上还未发布的程序包,比如你要将程序包更新到测试版本,那么 你必须用源代码进行安装: install.packages("tm", dependencies=TRUE) setwd("~/Downloads/") install.packages("RCurl_1.5-0.tar.gz", repos=NULL, type="source") 第一行代码中,我们用默认参数从CRAN上安装了tm程序包。tm程序包用于文本挖掘, 在第3章将用它来对电子邮件文本进行分类。install.packages中一个很有用的参数是 suggests,这个参数默认值是FALSE,如果设置为TRUE,就会在安装过程中通知install. packages函数下载并安装初始安装过程所依赖的程序包。为了得到最佳实践,我们推荐 将此参数值一直设置为TRUE,当R应用程序上没有任何程序包的情况下更要如此。 同样还有另一种安装方法,那就是直接使用源代码的压缩文件进行安装。在上一个例子 中,我们用作者网站上的源代码安装了RCurl程序包。用setwd函数确保R的工作路径已 machine_learn_hacker-all.indd 17 2013.4.1 1:13:19 PM 18 | 第1章 设置为保存源代码的目录,然后就可以简单地执行前面的命令从源代码安装了。注意, 这里需要改动两个参数。首先,我们必须设置repos=NULL来告诉函数不要使用CRAN中 任意一个资源库,然后要设置type="source"来告诉函数使用源代码安装。 表1-2:本书中用到的程序包 名称 网址 作者 简介及用法 arm http://cran.r-project.org/ Andrew Gelman等 用于构建多水平/层次回归模型的 web/packages/arm/ 程序包 ggplot2 http://had.co.nz/ggplot2/ Hadley Wickham 是图语法在R中的实现,是创建 高质量图形的首选程序包 glmnet http://cran.r-project.org/ Jerome Friedman、 包含Lasso和elastic-net的正则化 web/packages/glmnet/ Trevor Hastie和 广义线性模型 index.html Rob Tibshirani igraph http://igraph.sourceforge Gabor Csardi 简单的图及网络分析程序,用于 .net/ 模拟社交网络 lme4 http://cran.r-project.org/ Douglas Bates、 提供函数用于创建线性及广义混 web/packages/lme4/ Martin Maechler和 合效应模型 Ben Bolker lubridate https://github.com/ Hadley Wickham 提供方便的函数,使在R环境中 hadley/lubridate 处理日期更为容易 RCurl http://www.omegahat. Duncan Temple Lang 提供了一个与libcurl库中HTTP协 org/RCurl/ 议交互的R接口,用于从网络中 导入原始数据 reshape http://had.co.nz/plyr/ Hadley Wickham 提供一系列工具用于在R中处 理、聚合以及管理数据 RJSONIO http://www.omegahat. Duncan Temple Lang 提供读写JSON(JavaScript org/RJSONIO/ Object Notation)数据的函数, 用于解析来自网络API的数据 tm http://www.spatstat.org/ Ingo Feinerer 提供一系列文本挖掘函数,用于 spatstat/ 处理非结构化文本数据 XML http://www.omegahat. Duncan Temple Lang 用于解析XML及HTML文件,以 org/RSXML/ 便从网络中提取结构化数据 前文已经提到过,在本书中我们会使用一些程序包。表1-2列出了本书的案例研究所用到 的所有程序包,包括对其用途的简单介绍,以及查看每个包详细信息的链接。安装所需 程序包的数量不少,为了加快安装过程,我们创建了一个简短的脚本来检查每个必需的 machine_learn_hacker-all.indd 18 2013.4.1 1:13:19 PM 使用R语言 | 19 程序包是否已安装,若没有安装,它会通过CRAN进行安装。要运行该脚本,先用setwd 函数将工作目录设置为本章代码所在的文件夹,再执行source命令,如下所示: source("package_installer.R") 如果你还没有安装过程序包,系统可能要求你选择一个CRAN的库。一旦设置完成,脚 本就开始运行,你就可以看到所有需要安装的程序包的安装进度。现在,我们就要用R 开始机器学习之旅了!在我们开始案例分析之前,我们仍需要回顾一些常用的R相关的 函数与操作。 机器学习中的R基础 在本书开篇,我们就提出,学习新技术的最好方式是从你渴望解决的问题或你渴望解答 的疑问入手。对任务的较高层次愿景满怀激情,这会让你从案例中有效地学到知识。这 里我们要介绍的R基础知识并不会涉及机器学习问题,但是我们将遇到一些R中数据处理 和管理相关的问题。在案例研究中可以发现,常常要花很多时间来规整数据,使其格式 统一、组织合理以便分析。而花在编写代码、运行分析的时间常常较少。 在接下来这个案例中,我们提出的问题纯属为了好玩。最近,数据服务商Infochimps. com发布了一个数据集,其中含有60 000多条不明飞行物(UFO)的目击记录和报道。 这份数据时间跨度几百年,地域覆盖全世界。虽然数据涵盖全球,但是主要的目击记录 都发生在美国。面对这份数据的时空维度,我们可能会有以下疑问:UFO的出现是否有 周期性规律?美国的不同州出现的UFO记录如果有区别,有哪些区别? 我们要探索的是一个很棒的数据集,因为它数量巨大、高度结构化,同时也很有趣。因 为它是个大文本文件,而且我们在本书中要处理的数据都属于这一类型,所以练习一下 很有用。在这样的文本文件中,常常有一些杂乱的记录,我们会用R中的基本函数和扩 展库来清洗和组织原始数据。本节我们会带你一步一步地了解整个简单分析的过程,并 试图回答前文提到的两个问题。在本章的code文件夹下有一个文件:ufo_sightings.R,这 是本章所用的脚本源代码。我们首先从加载数据和所需的库开始。 加载程序包和数据 首先,加载的是ggplot2程序包,我们会在最后一步——可视化分析的时候用到它。 library(ggplot2) 加载g g p l o t2的过程中,你会注意到这个包还加载了另外两个所需的程序包:p l y r和 reshape。这些包在R中用于处理和组织数据,同时我们也会在本例中用plyr程序包来完 成数据的聚合和组织。 machine_learn_hacker-all.indd 19 2013.4.1 1:13:19 PM 20 | 第1章 其次,从文本文件ufo_awesome.tsv加载数据,文件在本章目录下的data/ufo/中。注意一 点,该文件字段是制表符分隔的(因此扩展名是.tsv),这意味着我们要用read.delim 函数来加载数据。因为R直接使用默认值的地方很多,因此我们在脚本中使用函数时要 特别注意默认参数的设置。假设我们此前从未用过read.delim函数,为了更好地了解R 中参数的意义,则需要阅读帮助文档。或者,假设我们不知道read.delim这个函数的存 在,但需要用一个函数将分隔字段的数据读到一个数据框中。R提供了一些有用的函数 来解决这些问题: ?read.delim # 读取一个函数的帮助文档 ??base::delim # 在所有base包的帮助文档中搜索’delim’ help.search("delimited") # 在所有的帮助文档中搜索“delimited” RSiteSearch("parsing text") # 在R官方网站上搜索“parsing text” 在第一个例子中,我们在函数名前面加了个问号。这样就会打开这个函数的帮助文档, 这是R中很有用的一个快捷方式。我们也可以在一个程序包中搜索一个特定词,这需 要??和::结合使用。双问号表示搜索一个指定的词。在这个例子中,我们用双冒号表示 在所有base包的函数中搜索指定词delim。R也允许你进行半结构化的帮助搜索,这会用 到help.search和RSiteSearch。help.search能在所有已安装的程序包中搜索指定的词, 如上述例子中的delimited。另外,还可以用RSiteSearch搜索R网站上的帮助文档和邮件 列表。请注意,千万别以为我们已经把这章要用的函数或者所有R相关的知识都回顾完 了。我们极力推荐你自己独立地用这些搜索函数去研究R的其他基本函数。 回到UFO的数据处理上,为了正确地读取数据,我们得给read.delim函数手动设置一 些参数。首先,需要告诉函数数据字段是怎么分隔的。我们知道这个文件是制表符分隔 文件,所以把sep设置为制表符。然后,read.delim函数在读取数据的过程中会用一些 启发式规则把每一行转换成R的数据类型。在本例中,每一行的数据类型都是strings (字符串),但是所有r e a d.*函数都默认把字符串转换为f a c t o r类型,这个类型是用 来表示分类变量(categorical variable)的,并不是我们想要的。因此,我们需要设置 stringsAsFactors=FALSE来防止其转换。实际上,把这个默认值设置为FALSE一般都不 会错,尤其是处理不熟悉的数据时。此外,这份数据第一行并没有表头,因此还需要 把表头的参数设置为FALSE,以防止R默认把第一行当做表头。最后,数据中有许多空 元素,我们想把这些空元素设置为R中的特殊值N A,为此,我们显式地定义空字符串为 na.string: ufo<-read.delim("data/ufo/ufo_awesome.tsv", sep="\t", stringsAsFactors=FALSE,header=FALSE, na.strings="") 注意: 上文提到的术语“分类变量”指的是R中的一种数据类型,它表示在一个分类体系中观察到 的一个类别成员。在统计学中,分类变量是一个很重要的概念,因为我们会关心:对于一 定的类型,它由哪些特定观察值构成。在R中,用factor(因子)类型表示分类变量,其本 machine_learn_hacker-all.indd 20 2013.4.1 1:13:19 PM 使用R语言 | 21 质上是给每一个字符串标签赋予一个数值。在本例中,我们用as.factor把某些字符串(比 如说州的缩写)转换成分类变量,它会给数据集中每个缩写州名赋予一个独一无二的数值 ID。这个过程我们今后会多次碰到。 现在,我们已有一个装着所有UFO数据的数据框了。无论何时,只要你操作数据框, 尤其当数据是从外部数据源读入时,我们都推荐你手工查看一下数据。关于手工查看数 据,两个比较好用的函数是head和tail。这两个函数会分别打印出数据框中的前六条和 后六条数据记录: head(ufo) V1 V2 V3 V4 V5 V6 1 19951009 19951009 Iowa City, IA Man repts. witnessing "flash.. 2 19951010 19951011 Milwaukee, WI 2 min. Man on Hwy 43 SW of Milwauk.. 3 19950101 19950103 Shelton, WA Telephoned Report:CA woman v.. 4 19950510 19950510 Columbia, MO 2 min. Man repts. son's bizarre sig.. 5 19950611 19950614 Seattle, WA Anonymous caller repts. sigh.. 6 19951025 19951024 Brunswick County, ND 30 min. Sheriff's office calls to re.. 这个数据框最明显的特点是每一列的名字没有实际意义。参考一下这份数据的文档,我 们可以赋予每一列更有意义的标签。给数据框每一列赋予有意义的名称很重要。这样一 来,不管对自己还是其他人,代码和输出都有更强的可读性。我们会用到names函数, 这个函数既能读取列名,又能写入列名。我们要根据数据文档创建一个与数据集的列名 相应的字符串向量,然后把它作为names函数唯一的参数传入: names(ufo)<-c("DateOccurred","DateReported","Location", "ShortDescription","Duration","LongDescription") 从h e a d函数的输出以及用于创建列标签的文档可知,数据的前两列是日期。R中的 日期是一种特殊的数据类型,这和其他编程语言没什么不同,因此我们想把日期字 符串转换为这种真正的日期数据类型。这需要用到a s.D a t e函数,给它输入日期字符 串,它会尝试将其转换为D a t e对象。在这份数据中,字符串的日期格式是不太常见的 YYYYMMDD。因此,需要给as.Date指定一个日期格式字符串,这样它才知道该怎么 转换。我们从第一列DateOccurred开始转换: ufo$DateOccurred<-as.Date(ufo$DateOccurred, format="%Y%m%d") Error in strptime(x, format, tz = "GMT") : input string is too long 我们遭遇了第一个错误!尽管不知道错在哪,但是这个错误提到了“input string is too long”(输入字符串过长),这说明DateOccurred列中某些数据太长导致日期格式字符 串无法匹配。为什么会出现这种情况呢?我们正在处理的是一个很大的文本文件,因此 可能有些数据在原始数据集就是畸形的。如果出现了这种情况,这些畸形的数据就不能 machine_learn_hacker-all.indd 21 2013.4.1 1:13:20 PM 22 | 第1章 被read.delim函数正确地解析,从而就导致了如上所示的错误。因为我们处理的是真实 世界的数据,所以必须手工清理数据。 转换日期字符串及处理畸形数据 要解决这个问题首先必须准确定位这些有缺陷的数据,然后再决定怎么处理。还好我们 能从这个提示信息中知道错误的原因是数据“过长”。能够被正确解析的字符串肯定都 是8个字符,也就是“YYYYMMDD”这种格式的。为找出有问题的数据行,我们只需 要找出那些长度在8个字符以上的字符串。作为一项最佳实践,我们首先要查看畸形数 据到底是什么样的,以便对错误原因有更深的理解。在这个例子中,我们还是像之前那 样用head函数来检查由逻辑运算返回的结果数据。 然后,为了移除错误的数据行,我们需要用到ifelse函数来构建一个布尔值向量,以便 标识出哪些是8个字符的字符串(TRUE),哪些不是(FALSE)。ifelse函数是一个典型 的逻辑开关,用于布尔测试,而此处用到的是其向量化版本。我们还会在R中遇到很多 向量化操作的例子。这种机制在用于处理数据循环迭代时更占优势,因为这通常(但并 非总是)比直接对向量进行循环更有效注1: head(ufo[which(nchar(ufo$DateOccurred)!=8 | nchar(ufo$DateReported)!=8),1]) [1] "ler@gnv.ifas.ufl.edu" [2] "0000" [3] "Callers report sighting a number of soft white balls of lights headinginan easterly directing then changing direction to the west beforespeeding off to the north west." [4] "0000" [5] "0000" [6] "0000" good.rows<-ifelse(nchar(ufo$DateOccurred)!=8|nchar(ufo$DateReported)!=8, FALSE,TRUE) length(which(!good.rows)) [1] 371 ufo<-ufo[good.rows,] 在这个搜索过程中,我们用到了一些有用的R函数。我们需要知道D a t e O c c u r r e d和 DateReported这两列中每一行字符串的长度,所以用到了nchar函数。如果长度不等于 8,则返回F A L S E。得到了布尔值向量之后,我们想知道数据集中有多少畸形数据。这 样就用到了which函数,它返回一个包含FALSE值的向量。接下来,用length函数计算这 个向量的长度就可以知道畸形数据的条数。仅仅371条数据不规范,最简单的办法就是 注1: 在R咨询中心有对向量化操作原理的简要介绍:能不能不要循环或者说让循环更快点? (How can I avoid this loop or make it faster?)[F08] machine_learn_hacker-all.indd 22 2013.4.1 1:13:20 PM 使用R语言 | 23 移除并忽略它们。一开始我们可能会担心丢弃这371条畸形数据会不会带来什么后果, 但是总共有60 000多条数据,因此简单地忽略它们然后继续转换其他数据,不会有什么 影响: ufo$DateOccurred<-as.Date(ufo$DateOccurred, format="%Y%m%d") ufo$DateReported<-as.Date(ufo$DateReported, format="%Y%m%d") 接下来,我们需要清洗、组织目击地点数据。回忆一下我们之前用head函数查看到的美 国UFO目击数据,地点数据的形式是“City,State”(城市,州)。我们可以通过R中 集成的正则表达式,将字符串拆分成两列,并识别出不规范的数据行。其中识别出不规 范数据尤为重要,因为我们只对在美国的UFO目击数据变化趋势有兴趣,而且用这一步 的信息也能把美国的数据单独挑出来。 组织目击地点数据 为了继续用上述方式处理数据,首先要定义一个输入为字符串的函数,然后执行数据清 洗工作。接下来用apply函数的向量化版本将这个函数应用到地点数据列上: get.location<-function(l) { split.location<-tryCatch(strsplit(l,",")[[1]],error= function(e) return(c(NA, NA))) clean.location<-gsub("^ ","",split.location) if (length(clean.location)>2) { return(c(NA,NA)) } else { return(clean.location) } } 上述函数中有一些细节需要注意。首先,strsplit函数被R的异常处理函数tryCatch所 包围。其次,并非所有地点都是“City, State” (城市,州)这种格式,甚至有的数据连 逗号也没有。strsplit函数在遇到不符合格式的数据会抛出一个异常,因此我们需要捕 获(catch)这个异常。在本例中,对于不包含逗号的数据,我们会返回一个NA向量来表 明这条数据无效。接下来,由于原始数据的开头有一个空格,因此用gsub函数(R的正 则表达式相关函数之一)移除每个字符串开头的空格。最后,增加一步检查,确保每个 返回向量的长度是2。许多非美国地名中会有多个逗号,导致strsplit函数返回的向量 长度大于2。在这种情形下,我们依然返回NA向量。 有了定义好的函数之后,我们要用到lapply函数了,它是“list-apply”(应用后返回 list链表)的简写,用来对L o c a t i o n列的每一条字符串循环迭代地应用我们定义的函 数。前文提到过,R中的apply函数家族相当有用。这些函数的形式都是apply(vector, function),它们在逐一应用到向量元素上之后,返回特定形式的结果。在本例中,我们 用到的是lapply,它返回一个list链表: machine_learn_hacker-all.indd 23 2013.4.1 1:13:20 PM 24 | 第1章 city.state<-lapply(ufo$Location, get.location) head(city.state) [[1]] [1] "Iowa City" "IA" [[2]] [1] "Milwaukee" "WI" [[3]] [1] "Shelton" "WA" [[4]] [1] "Columbia" "MO" [[5]] [1] "Seattle" "WA" [[6]] [1] "Brunswick County" "ND" 从例子中可以看出,R中的list是一个“键-值”对形式的数据结构。键由双方括号索 引,值则包含在单方括号中。在本例中,键就是简单的整数,但是lists也允许用字符 串作为键注2。虽然把数据存储在list中比较方便,但是却并不是我们想要的,因为我们想 把城市和州的信息作为不同的两列加入到数据框中。为此,需要把这个较长的list转换 成一个两列的矩阵(matrix),其中city(城市)数据作为其首列: location.matrix<-do.call(rbind, city.state) ufo<-transform(ufo,USCity=location.matrix[,1], USState=tolower(location.matrix[,2]), stringsAsFactors=FALSE) 为从list构造一个矩阵,我们用到了do.call函数。和apply函数类似,do.call函数是 在一个list上执行一个函数调用。我们还会经常把lapply和do.call函数结合起来用于 处理数据。在上述例子中,传入的函数是rbind,这个函数会把city.state链表中的所 有向量一行一行地合并起来,从而创建一个矩阵。要把这个矩阵并入数据框中,还要用 到transform函数。分别用location.matrix的第一列和第二列创建两个新列:USCity和 U S St a t e。最后,由于州名字的缩写形式不一致,有的采用大写形式,有的采用小写形 式,因此我们用tolower函数把所有的州名字的缩写都变成小写形式。 处理非美国境内的数据 数据清洗要解决的最后一个问题就是处理那些形式上符合“City, State”,但是实际上并 不在美国境内的数据。具体来说,有些UFO目击地点在加拿大,而这些数据同样符合这 样的形式。幸好加拿大各省的缩写和美国各州的缩写并不匹配。利用这一点,我们可以 注2: 要深入理解lists,请参考《Data Manipulation with R》一书中的第1章[Sep18]。 machine_learn_hacker-all.indd 24 2013.4.1 1:13:20 PM 使用R语言 | 25 构造一个美国各州缩写的向量,让USState列数据来匹配这个向量,把匹配上的保留下 来,从而识别出非美国地名: us.states<-c("ak","al","ar","az","ca","co","ct","de","fl","ga","hi","ia","id","il","in", "ks","ky","la","ma","md","me","mi","mn","mo","ms","mt","nc","nd","ne","nh","nj","nm", "nv","ny","oh","ok","or","pa","ri","sc","sd","tn","tx","ut","va","vt","wa","wi","wv", "wy") ufo$USState<-us.states[match(ufo$USState,us.states)] ufo$USCity[is.na(ufo$USState)]<-NA 为了找到USState列中不匹配美国州名缩写的数据,我们用到了match函数。这个函数有 两个参数:第一个参数是待匹配的值,第二个是用于匹配的数据。函数返回值是一个长 度与第一个参数相同的向量,向量中的值是其在第二个参数中所匹配的值的索引。如果 没有在第二个参数中找到匹配的值,函数默认返回N A。在我们的案例中,我们只关心哪 些数据返回值为NA,因为这表明它们没有匹配上美国州名。然后,用is.na函数找出这些 不是美国州名的数据,并且将其在USState列中的值重置为NA。最后,我们还要把USCity 列中对应位置也设置为NA。 现在原始数据框已经处理到位,可以从中只抽取出我们感兴趣的数据了。准确地说,我 们想要的数据只是其中发生在美国的UFO记录子集。通过替换前面几步中不符合条件的 数据,我们用subset命令创建一个新数据框,只保留发生在美国的数据记录: ufo.us<-subset(ufo, !is.na(USState)) head(ufo.us) DateOccurred DateReported Location ShortDescription Duration 1 1995-10-09 1995-10-09 Iowa City, IA 2 1995-10-10 1995-10-11 Milwaukee, WI 2 min. 3 1995-01-01 1995-01-03 Shelton, WA 4 1995-05-10 1995-05-10 Columbia, MO 2 min. 5 1995-06-11 1995-06-14 Seattle, WA 6 1995-10-25 1995-10-24 Brunswick County, ND 30 min. LongDescription USCity USState 1 Man repts. witnessing "flash... Iowa City ia 2 Man on Hwy 43 SW of Milwauk... Milwaukee wi 3 Telephoned Report:CA woman v... Shelton wa 4 Man repts. son's bizarre sig... Columbia mo 5 Anonymous caller repts. sigh... Seattle wa 6 Sheriff's office calls to re... Brunswick County nd 聚合并组织数据 到目前为止,我们已经把数据组织成可以开始分析的程度了。上一节专注于规范数据的 格式,并筛选出要分析的相关数据。本节将继续探索数据,以期进一步缩小我们需要 关注的范围。这份数据有两个基本的维度:空间维度(目击事件发生地点)和时间维度 machine_learn_hacker-all.indd 25 2013.4.1 1:13:20 PM 26 | 第1章 (目击事件发生时间)。上一节我们关注空间维度,这一节将关注时间维度。首先,我 们要对DateOccured这列数据应用summary函数,从而对数据的年代范围有个基本认识: summary(ufo.us$DateOccurred) Min. 1st Qu. Median Mean 3rd Qu. Max. "1400-06-30" "1999-09-06" "2004-01-10" "2001-02-13" "2007-07-26" "2010-08-30" 令人称奇的是,这份数据时间跨度很长,最古老的UFO目击记录可追溯到1400年前! 看到这个异常的年份数据,我们不禁要问的是:这份数据在时间上到底是如何分布的? 是否值得分析整个时间序列?进行直观判断最快的方法就是构建一个直方图。我们将在 第2章详细讨论直方图,不过现在你需要知道的是:直方图就是让你在某个维度上把数 据划分成不同的区间,然后观察数据落入每个区间的频度。此处我们感兴趣的维度是时 间,因此构建一个直方图,把数据按照时间进行区间划分: quick.hist<-ggplot(ufo.us, aes(x=DateOccurred))+geom_histogram()+ scale_x_date(major="50 years") ggsave(plot=quick.hist, filename="../images/quick_hist.png", height=6, width=8) stat_bin: binwidth defaulted to range/30. Use 'binwidth = x' to adjust this. 这里要注意几点。这是我们第一次用到ggplot2程序包,并将在本书所有需要可视化数 据的地方用到。在这个例子中,我们构建了一个非常简单的直方图,它只用了一行代 码。首先,用UFO数据框作为初始化参数创建一个g g p l o t对象。接下来,为了美观, 把x轴设定为DateOccurred列,因为这一列的频数才是我们所感兴趣的。ggplot2操作 对象必须是数据框,而且创建ggplot2对象的第一个参数也必须是一个数据框。ggplot2 是对Leland Wilkinson的图语法(Grammar of Graphics)[Wil05]的一种R实现。这说明 g g p l o t2程序包与这种特别的数据可视化哲学是一脉相承的,所有的可视化都是通过 一系列的层构建而成的。在图1-5所示的直方图中,初始层便是x轴的数据,即UFO的 目击日期。接着用geom_histogram函数添加一个直方图层。在这个例子中,调用geom_ histogram函数时使用了其默认参数,而我们在今后会意识到,这样并不是好的选择。 最后,由于数据的时间跨度太大,我们用scale_x_date函数将x轴标签的时间周期改为 50年。 ggplot对象创建完毕之后,用ggsave函数可以把可视化结果输出到文件里。也可以用 print(quick.hist)把可视化结果输出到屏幕上。请注意,在输出可视化结果时会出现警告 信息。在直方图中给数据划分区间的方式有很多种,在第2章中我们会详细讨论,而这 里输出的警告信息清楚地显示了ggplot2在默认情形下是如何给数据划分区间的。 我们现在就用这份可视化结果对数据一探究竟。 machine_learn_hacker-all.indd 26 2013.4.1 1:13:20 PM 使用R语言 | 27 目击次数 目击时间 图1-5:UFO数据随时间变化的直方图 从图1-5可以看出来,结论显而易见。绝大部分的目击事件发生在1960~2010年,而其中 最主要的又发生在过去20年间。考虑到我们的目的,只需要关注1990~2010年的数据即 可。这样我们就能够在分析过程中排除异常值,只比较相近时间区间的数据。和之前一 样,用subset函数把符合条件的数据挑出来以构建一个新的数据框: ufo.us<-subset(ufo.us, DateOccurred>=as.Date("1990-01-01")) nrow(ufo.us) #[1] 46347 虽然这次移除的数据比我们之前清洗数据时丢弃的还要多,但是留下来可供分析使用 的样本还有46 000多个。为了看清差异,我们重新对这个子集生成直方图,如图1-6所 示。我们注意到这次样本差异更加显著。然后,我们开始组织数据,希望能回答我们的 中心问题:美国境内UFO目击记录如果存在周期性规律,会是什么规律?为解答这个问 题,我们首先要问的是:所谓“周期性”是什么?有很多方式可以把时间序列按照一定 周期聚合:按周、按月、按季度、按年,等等。那么这里用哪种方式来聚合数据最合适 呢?DateOccurred列的数据是精确到天的,但是整个数据集的覆盖面上又存在大量的不 一致。我们所需的聚合方式应该能让各个州的数据量分布相对均匀。此例中,按year- month(年-月)的聚合方式是最佳选择。这种聚合方式也最能回答我们所提问题的核 心,因为按年-月聚合比较容易看出周期性变化。 machine_learn_hacker-all.indd 27 2013.4.1 1:13:21 PM 28 | 第1章 目击次数 目击时间 图1-6:UFO子集数据随时间变化的直方图(1990~2010) 我们需要统计1990~2010年每个州每年-月的UFO目击次数。首先,我们要在数据中新 建一个列,这个列用于保存和当前数据中一样的年和月。用strftime函数把日期对象转 换成一个“YYYY-MM”格式的字符串。我们照旧要设置一个format参数来完成转换: ufo.us$YearMonth<-strftime(ufo.us$DateOccurred, format="%Y-%m") 需要注意的是,我们没有用transform函数给数据框添加一个新列。相反,我们简单地 引用了一个并不存在的列名,R就自动增加了这个列。这两种给数据框添加新列的方法 都有效,而我们会根据具体情况选择使用其中一种方法。接下来我们需要统计每个州在 每个年-月期间目击UFO的次数。这里首次用到了ddply函数,此函数是plyr库中的一 员,而plyr库在处理数据方面非常有用。 plyr函数家族的作用有点像前几年流行起来的map-reduce风格的数据聚合工具。它们把 数据按照一定方式分成不同的组,划分方式对每一条数据都是有意义的,然后对每个组 进行计算并返回结果。在本案例中,我们要做的是用“州名缩写”和“年-月”这一新 增加的列来给数据分组。数据按照这种方式分好组之后,可以对每个组进行数据统计并 把统计结果以一个新列的形式返回。在这里,我们只是简单地用nrow函数按照行数来化 简(reduce)每组数据: machine_learn_hacker-all.indd 28 2013.4.1 1:13:21 PM 使用R语言 | 29 sightings.counts<-ddply(ufo.us,.(USState,YearMonth), nrow) head(sightings.counts) USState YearMonth V1 1 ak 1990-01 1 2 ak 1990-03 1 3 ak 1990-05 1 4 ak 1993-11 1 5 ak 1994-11 1 6 ak 1995-01 1 现在,我们得到了每个州在每个“年-月”里的UFO目击次数。不过从调用head函数的 结果来看,如果直接使用这份统计数据可能存在一些问题,因为其中有大量的值缺失 了。比如,我们看到在阿拉斯加州(Alaska),1990年的1月、3月、5月各有一次目击记 录,但是没有2月和4月的记录。估计在这期间并没有UFO的目击记录,但是在数据集中 的这些位置处也没有包含无UFO目击事件发生的记录,因此,我们得把这些时间记录加 上,目击次数就是0。 我们需要一个覆盖整个数据集的“年-月”向量。用这个向量可以检查哪些“年-月” 已经存在于数据集中,如果不存在就补上,并把次数设置为0。为此我们要用seq.Date 函数创建一个日期序列,然后把格式设定为数据框中的日期格式: date.range<-seq.Date(from=as.Date(min(ufo.us$DateOccurred)), to=as.Date(max(ufo.us$DateOccurred)), by="month") date.strings<-strftime(date.range, "%Y-%m") 有了date.strings这个新的向量,我们还需要新建一个包含所有年-月和州的数据框, 然后用这个数据框去匹配UFO目击数据。我们依旧先用lapply函数创建列,再用do.call 函数将其转换成矩阵并最终转换成数据框: states.dates<-lapply(us.states,function(s) cbind(s,date.strings)) states.dates<-data.frame(do.call(rbind,states.dates), stringsAsFactors=FALSE) head(states.dates) s date.strings 1 ak 1990-01 2 ak 1990-02 3 ak 1990-03 4 ak 1990-04 5 ak 1990-05 6 ak 1990-06 现在,数据框states.dates包含了每一年、每个月和每个州所有组合所对应的所有目 击记录。请注意,现在已经有了阿拉斯加州(Alaska)在1990年2月和3月的记录了。要 给UFO目击记录数据中的缺失值补上0,我们还需要把这个新数据框和原来的数据框合 并。为此,需要用到merge函数,给这个函数输入两个有序数据框,然后它将数据框中 相同的列合并。在我们的案例中,两个数据框分别是按照州名的字母顺序以及年-月的 machine_learn_hacker-all.indd 29 2013.4.1 1:13:21 PM 30 | 第1章 时间顺序排序的。还要告诉函数将数据框中的哪些列进行合并,这就得给参数by.x和参 数by.y指定每个数据框中对应的列名。最后,把参数all设置为TRUE,以告诉函数要把没 匹配上的数据也包含进来并填充为NA。以下V1列中的值为NA的记录即为没有UFO目击数 据的记录: all.sightings<-merge(states.dates, sightings.counts, by.x=c("s","date.strings"), by.y=c("USState","YearMonth"),all=TRUE) head(all.sightings) s date.strings V1 1 ak 1990-01 1 2 ak 1990-02 NA 3 ak 1990-03 1 4 ak 1990-04 NA 5 ak 1990-05 1 6 ak 1990-06 NA 数据聚合的最后一步只是一些简单的修修补补。首先,我们要把all.sightings数据框 的列名改成有意义的名称。方法和本案例一开始的改列名一样。接下来,要把NA值改为 0,这里又要用到is.na函数。最后要把YearMonth和State列的数据转换为合适的类型。 用前面创建的date.range向量和rep函数创建一个和date.range一模一样的向量,并把 年-月字符串转换为Date对象。再强调一次,把日期保存成Date对象要比字符串好,因 为前者可以用数学方法进行比较,而后者却不容易办到。同样,州名缩写最好用分类变量 表示而不用字符串,因此将其转换为factor类型。下一章会详细讲解factor及其他R数据 类型: names(all.sightings)<-c("State","YearMonth","Sightings") all.sightings$Sightings[is.na(all.sightings$Sightings)]<-0 all.sightings$YearMonth<-as.Date(rep(date.range,length(us.states))) all.sightings$State<-as.factor(toupper(all.sightings$State)) 现在,要用可视化方法分析数据了! 分析数据 对于这份数据,我们只用可视化的方式回答前面提出的核心问题。在本书的其余部分, 我们会结合数值分析和可视化分析,但由于本案例仅用作介绍R编程的核心范例,因此 只需要用可视化分析即可。和之前的直方图可视化不同的是,这里我们要更为深入地使 用ggplot2程序包来明明白白地构建一个个的图层。按照这种方式,我们就可以构建这样 一个可视化结果:它可以直接回答每个州UFO目击记录随时间变化的周期性规律问题, 而且看上去也更加专业。 machine_learn_hacker-all.indd 30 2013.4.1 1:13:21 PM 使用R语言 | 31 下面的例子一次性构建好了所有的可视化结果,接下来将分别介绍其中每个图层的 意义: state.plot <- ggplot(all.sightings, aes(x = YearMonth,y = Sightings)) + geom_line(aes(color = "darkblue")) + facet_wrap(~State, nrow = 10, ncol = 5) + theme_bw() + scale_color_manual(values = c("darkblue" = "darkblue"), legend = FALSE) + scale_x_date(breaks = "10 years") + xlab("Time") + ylab("Number of Sightings") + opts(title = "Number of UFO sightings by Month-Year and U.S. State (1990-2010)") ggsave(plot = state.plot, filename = file.path("images", "ufo_sightings.pdf"), width = 14, height = 8.5) 依照惯例,第一步还是用一个数据框作为第一个参数来创建一个ggplot对象。这里我们 用到了前面所创建的数据框all.sightings。在这里,照样为了绘图的美观,先要创建 一个图层,x轴是YearMonth列,y轴是Sightings数据。然后,为了表现各州的周期性 变化,我们给每一个州绘制一幅曲线图。这样就方便观察每个州UFO目击数随时间变 化的峰值、平缓区、波动区。为此,我们要用到geom_line函数,并将color的值设置为 “darkblue”(深蓝色)以便让图形更显眼。 通过整个例子我们看到UFO目击数据相当大,覆盖了相当长一段时期内美国所有州的 记录。有鉴于此,我们需要设计一种方法,将可视化结果进行拆分,从而既能观察到每 个州的数据,又能比较不同州之间的数据。如果我们把所有数据绘制在一个图板中, 那很难观察到差别。为了确认这种方式的确不可行,将上面代码块的前两行 译注1中的 color="darkblue"替换为color=State,并在控制台输入> print(state.plot),然后运 行。更好的方法是把每个州的数据单独绘制图形,并将图形在网格中按顺序放好,这样 易于进行比较。 为了创建出一个分块绘制的图形,我们用到facet_wrap函数,并指明图形面板的构造使 用了变量State,它是一个factor类型,即分类变量。我们也要明确定义网格的行列数, 这个比较容易,因为我们已经知道了一共有50个不同的图形。 ggplot2程序包有很多不同的图形绘制主题。默认的绘制主题就是我们在第一个绘制例 子中用到的:灰色背景、深灰色网格线。严格来讲,这只是个人喜好问题,但是在这里 我们倾向用白色背景,因为这样可以在可视化结果中更容易看到数据之间的不同之处。 我们添加了theme_bw层,这个图层会用白色背景和黑色网格线绘制图形。在操作ggplot2 更熟练之后,我们推荐你多尝试不同的绘制主题,然后找到自己最钟爱的一个。 译注1: 原书中为第一行,实际上第一行是无法成功运行的。 machine_learn_hacker-all.indd 31 2013.4.1 1:13:21 PM 32 | 第1章 其余图层的创建只是为了锦上添花,让可视化结果在视觉和感觉上都更专业一些。虽然 无人对此苛求,但正是这些细节让绘制的可视化数据更贴近专业性而非业余。s c a l e_ color_manual函数用来指明字符串“darkblue”相当于网页安全色“darkblue”。虽然这 样看上去有些重复、多余,但这正是ggplot2的设计核心,它需要明确定义诸如颜色这 样的细节。事实上,ggplot2倾向于用颜色来区别不同的数据类型或分类,因此它偏向 于用factor类型来指明颜色。在前面的案例中,我们明确将颜色定义成了一个字符串类 型,因此还要用scale_color_manual函数定义这个字符串的值。 我们同样用scale_x_date函数指明可视化结果中主要的网格线。因为这份数据时间跨度 是20年,所以要将其间隔设置为5年,然后,将四位数格式的年份作为坐标系的刻度标 签。接着用xlab和ylab函数分别将x轴的名称定为Time(目击时间),y轴的名称设置为 Number of Sightings(目击次数)。最后,用opts函数给整幅图赋予一个标题。opts函数 还有很多的选项可设置,在后面的章节中我们也会用到其中一些,但是还有更多的功能 本书没有涉及。 所有图层都已经完成,现在用ggsave函数把结果渲染成图像并分析数据。 分 析 之 后 , 可 以 发 现 很 多 有 趣 的 现 象 ( 见 图 1 - 7 ) 。 我 们 看 到 加 利 福 尼 亚 州 (California)和华盛顿州(Washington)的UFO目击次数明显多于其他各州。而这两 个州之间也有一些有趣的区别,加利福尼亚州UFO目击次数在时间分布上比较随机,但 在1995年之后又稳步增长,然而华盛顿州的UFO目击次数随时间的周期性变化则始终如 一,自1995年开始,其曲线的波峰和波谷都比较有规律。 我 们 也 观 察 到 , 许 多 州 的 U F O 目 击 次 数 都 出 现 过 突 增 情 况 。 例 如 , 亚 利 桑 那 州 (A r i z o n a)、佛罗里达州(F l o r i d a)、伊利诺伊州(I l l i n o i s)以及蒙大拿州 (Montana)在1997年出现过次数突增,密歇根州(Michigan)、俄亥俄州(Ohio)以 及俄勒冈州(Oregon)在1999年末出现过相似的突增情况。但是在这些州中,只有密歇 根州(Michigan)和俄亥俄州(Ohio)在地理上邻近。如果我们不相信这些是天外来客 造访的结果,那有没有其他可能的解释呢?兴许美国人在世纪之交对天空很警觉,常常 仰望天空,因此才有了比以前多的错误目击记录。 然而,如果你赞同外太空访客真的经常造访地球这种说法,的确有证据可以激发你想继 续探究的好奇心。实际上,通过对地区聚类,也能得到证据发现在美国许多州都有令人 称奇的、稳定的周期性目击记录。这些目击记录似乎真的包含了一些有意义的模式。 machine_learn_hacker-all.indd 32 2013.4.1 1:13:21 PM 使用R语言 | 33 目击时间 目击次数 图1-7:美国各州每年-月UFO目击次数(1990~2010年) machine_learn_hacker-all.indd 33 2013.4.1 1:13:21 PM 34 | 第1章 深入学习R的参考书目 学习完这个入门案例绝不能说明我们已经把这门语言完全介绍给大家了。我们只是用这 个案例介绍了一些R中加载、清洗、组织以及分析数据所用到的模式。在接下来的其他 章节里,我们不仅会多次重复用到其中的许多函数、处理过程,我们还会介绍一些在这 个案例里还没涉及的知识。在继续学习之前,对于那些希望获得更多实践机会以增强对 R熟悉程度的读者而言,有很多很棒的学习资源可供参考。这些资源大体上可以分为参 考书、文献以及网上资源,如表1-3所示。 在第2章我们将介绍探索性数据分析。本章的案例包含了较多的数据探索,但是我们处 理得比较简单快速。下一章我们会更加慎重地对待数据探索的过程。 表1-3:R参考资源 书名 作者 引用 简介 文献参考资源 《Data Manipulation Phil Spector [Spe08] 对此前涉及的数据处理问题有深 with R》 入的回顾与探讨,对此前未涉及 的一些技术也有介绍 《R in a Nutshell》 Joseph Adler [Adl10] 详细探讨R中所有基本函数。这 本书采用了R手册的形式并加入 了很多实际案例 《Introduction to Owen Jones、 [JMR09] 和其他的R入门文献不同,这本 Scientific Programming Robert Mail- 书首先专注于语言本身,然后才 and Simulation lardet和Andrew 设计了模拟实践环节 Using R》 Robinson 《Data Analysis Andrew Gelman [GH06] 这本书侧重于统计分析,但是所 Using Regression 和Jennifer Hill 有例子都使用R,它是相当不错 and Multilevel/ 的一份学习语言和方法的资源 Hierarchical Models》 《ggplot2: Elegant Hadley Wickham [Wic09] 用ggplot2可视化数据的专门手 Graphics for Data 册 Analysis》 machine_learn_hacker-all.indd 34 2013.4.1 1:13:22 PM 使用R语言 | 35 表1-3:R参考资源(续) 书名 作者 引用 简介 网上参考资源 《An Introduction to R》 Bill Venables和 http://cran.r- 源自R核心团队,是一份对R语言 David Smith project.org/ 广泛的、时时更新的入门资料 doc/manuals/ R- intro.html 《The R Inferno》 Patrick Burns http://lib.stat. 一份很不错的R入门资料,适合 cmu.edu/S/ 资深程序员阅读。其摘要一语中 Spoetry/Tutor/ 的:“如果你正在使用R,而且 R_inferno.pdf 感觉生不如死,那么这份资料就 是来拯救你的。” 《R for Programmers》 Norman Matloff http://heather. 和《The R Inferno》比较类似, cs.ucdavis.edu/ 这份资料也面向其他语言的资深 ~matloff/R/ 程序员 RProg.pdf “The Split-Apply- Hadley Wickham http://www. 很不错的入门资料,来自plyr程 Combine Strategy jstatsoft.org/ 序包的作者,在plyr的情景中用 for Data Analysis” v40/i01/paper 很多例子介绍了map-reduce模式 “R Data Analysis UCLA Academic http://www.ats. 一份“罗塞塔石碑”式的入门读 Examples” Technology ucla.edu/stat/r/ 物,面向的是在诸如SAS、SPSS Services dae/default.htm 以及Stata等其他统计语言编程平 台上富有经验的人 machine_learn_hacker-all.indd 35 2013.4.1 1:13:22 PM 36 第2章 数据分析 分析与验证 在数据处理方面,一个屡试不爽的方法就是:把任务清晰地分解成分析和验证两步。 把数据处理分为分析和验证由著名的John Tukey注1首先提出,他强调要为实际数据分析 设计简单的工具。在John Tukey看来,数据分析这一步要做的就是用摘要表(summary table)和基本可视化方法从数据中寻找隐含的模式。本章揭示如何用R中的基本方法来 建立数据的摘要表,然后再讲解怎样从这个表中看出门道。之后,将列举R中一些用于 可视化数据的工具和方法,同时,还会简要介绍基本可视化模式。 在开始第一次数据集分析之旅前,我们得提醒,一个真实存在的危险在时时窥伺你:你 找到的模式也许并不存在。人类的大脑天生就能发现宇宙中的模式,甚至在不太可能 出现模式的地方也能发现。看一眼白云,眨眼间就能发现云的形状,这跟知道多少统计 学知识无关。很多人都深信自己可以在普通的字里行间,比如莎翁的戏剧,发现隐藏的 信息。因为人类很容易发现一些经不起仔细推敲的模式,所以就不能只有数据分析这一 步,必须还要有验证过程。可以这样来看待数据验证:数据分析阶段的可视化结果杂 乱——有时甚至是毫无章法,这种情况让我们处理起来有些吃力,需要厘清思路,此时 数据验证就会发挥作用。 数据验证通常有两种方法: 注1: 同样也发明了bit(位)这个词。 machine_learn_hacker-all.indd 36 2013.4.1 1:13:22 PM 数据分析 | 37 如果你认为自己在一个新的数据集上发现了模式,那就用另一批数据来测试这个模 • 式的正规模型译注1。 利用概率论来测试你在原始数据集中发现的模式是否只是巧合 • 译注2。 相比数据分析,因为数据验证对数学水平要求较高,所以本章只涉及数据的分析方法。 也就是说在具体的案例中我们只关注数据的数值摘要(numeric summary)和一些标准 的可视化方法。我们讲到的数值摘要就是一些基本的统计项目:均值(mean)和众数 (mode)、百分位数(percentile)和中位数(median)、标准差(standard deviation) 和方差(variance)。我们要用到的可视化工具也是最基本的,你可能在统计学入门课 程上已经学过了:直方图、核密度估计以及散点图。这些简单的可视化方法可能一度得 不到重视,但是我们会让你相信,仅用这些基本的方法也能从数据中挖掘出有用的信 息。本书后面的章节才会涉及许多较高深的技术,但是,要训练数据分析的直觉,最好 就是用简单的方法去操作数据。 什么是数据 在介绍数据分析的基本方法之前,我们需要对“数据”这个概念统一认识。对“数据” 这个概念所有可能的定义可以用一整书来论述,因为对任何所谓的“数据集”你都可能 有一堆重要的问题要问。例如,你可能经常想知道你手中的数据是如何产生的,你也想 知道这些数据能否代表真正要研究的数据总体。通过研究亚马逊印第安人婚史,你可能 已经掌握了关于他们社会结构的很多知识,但是能否从中得到一些适用于其他文明的知 识却不得而知。对数据进行解释需要对数据来源有一定的了解。通常,唯一能区别因果 关系和相关关系的方法就是要知道数据由何而来:是从实验中得到的,还是因没有实验 数据而直接观察记录而来的。 虽然这都是些有趣的问题,而且我们也希望你终有一天能够具备这些知识, 注2但是在本 书中我们完全不会涉及数据采集。本章先假定数据分析这个微妙的哲学问题与预测问题 完全无关,后者我们会用机器学习技术来解决。出于实用性考虑,我们要在本书余下内 容中统一采用以下定义: “数据集”无非是一张充满数字和字符串的大表,表中每一行 是现实世界中的单个观测记录,并且每一列是观测记录的一个属性。如果你很熟悉数据 库,那么我们对数据的这个定义在直觉上应该符合你所熟悉的数据库表结构。如果你还 担心你的数据集可能还不仅是单张表,那么我们先假定你已经用过R中的merge、SQL中 的JOIN系列操作或者我们此前提到的其他工具,创建了类似单张表的数据集。 译注1: 即交叉验证。 译注2: 即假设检验。 注2: 你若感兴趣,我们推荐阅读Juden Pearl的《Causality》(因果)[Pea09]。 machine_learn_hacker-all.indd 37 2013.4.1 1:13:22 PM 38 | 第2章 我们称这个定义为“数据方块”(data as rectangle)模型。虽然这个观点大大简化了, 但是却可以在数据分析时非常形象地帮助我们想出许多好主意,我们希望它可以让许多 原本抽象的概念变得具体一些。“数据方块”模型还有另一个作用:它让我们可以自由 地徜徉于数据库设计的思维和纯数学思维之间。如果你还在为不熟悉矩阵而发愁,大可 不必,本书通篇,你都可以把矩阵看成是一个二维数组,即一张大表。只要我们一直想 象自己处理的是一个矩形的数组,我们就能使用很多强大的数学工具而不用关心其中具 体的数学运算是如何执行的。例如,尽管我们所要研究的每一项技术都可以看做矩阵乘 法问题,无论是标准线性回归模型还是现代矩阵分解技术,其中现代矩阵分解技术最近 因Netflix奖而声名鹊起,但是只在第9章才简单地介绍了矩阵的乘法。 因为我们把数据当做方块、表、矩阵,所以会交替使用这几个词,恳请读者朋友耐心 些。当我们谈起数据时无论用的 是哪个词,都请你牢记,它都表示类似表2-1的形式。 表2-1:本书作者 姓名 年龄 Drew Conway 28 John Myles White 29 由于数据由矩形组成,因此我们可以轻松地把运算操作通过绘图的方式表示出来。一份 数据的数值摘要就是把表中的所有行精简为只有几个数字——数据集的每一列常常就精 简为一个数字。图2-1是这类型数值摘要的例子。 M x N 1 x N 1 6 1 3 91 0 5 1 0 2 76 0 2.75 43.5 0.25 图2-1:将每一列摘要成一个数字 与数值摘要相对的是另一种可视化摘要方法,它把数据集中所有数据的某一列概括成一 张图。单列可视化摘要的例子如图2-2所示。 machine_learn_hacker-all.indd 38 2013.4.1 1:13:23 PM 数据分析 | 39 M x 1 1 单列可视化摘要: M x 1 1 x 1 0 0 0 图2-2:将一列摘要成一张图 除了分析单列的方法之外,还有很多方法用于分析数据集中多列之间的关系。例如,计 算两列的相关性,可以把表中任意两列转换成一个数字,这个数字表征了这两列之间关 系的强度,如图2-3所示。 M x 2 1 x 1 1 1 相关性分析: M x 2 1 x 1 3 0 5 0 2 0 -0.68 图2-3:相关性:将两列摘要成一个数字 machine_learn_hacker-all.indd 39 2013.4.1 1:13:23 PM 40 | 第2章 还有其他更为深入的方法。如果你觉得数据中存在很多冗余,那么你可能还需要减少其 中的列数。将数据中的很多列变成几列甚至一列的做法称为降维,对此将在第8章详细 讲解。图2-4列举了一个例子说明降维要达到的目的。 相关性分析: M x N M x 1 M x N M x 1 1 6 1 3 91 0 5 1 0 2 76 0 37.5 -47.5 42.5 -32.5 图2-4:降维:将多列摘要成一列 如图2-1~图2-4这四个图所示,摘要统计和降维是截然不同的两个方向:摘要统计要传 达的是所有数据在某一列(某个属性)上的特点如何;降维要做的是把数据集中所有列 (属性)转换成少数几列,得到的列数据对每一行来说都是唯一的。分析数据时,这两 种方法都可能有用,因为它们都可以将海量数据变得一目了然。 推断数据的类型 在你要对新数据集进行下一步操作之前,首先要弄清楚这个表中的每一列所代表的含 义。有人喜欢把这称作数据字典,就是说你可以在这里给数据集中每一列数据都存放一 条简洁易懂的描述信息。例如,表2-2所示的是一个没有标签的数据集: 表2-2:无标签数据 … … … “1” 73.847017017515 241.893563180437 “0” 58.9107320370127 102.088326367840 在没有任何提示信息的情况下,真的很难知道表中的数字表示什么含义。事实上,首先 要搞清楚的是每一列的数据类型:第一列似乎仅包含字符0或1,但它真的是字符串吗? 在第1章UFO的例子中,我们首先给数据集每一列打上了标签。当我们面对一份没有标 machine_learn_hacker-all.indd 40 2013.4.1 1:13:23 PM 数据分析 | 41 签的数据集时,我们可能会用到R中的一些类型判断函数。三个最重要的类型判断函数 如表2-3所示: 表2-3:R的类型判断函数 R函数 简介 is.numeric 输入向量是数字则返回TRUE,数字包括整数和浮点数;否则返回FALSE is.character 输入向量是字符串则返回TRUE,R中并没有单字符数据类型;否则返回 FALSE is.factor 输入向量是因子(factor)的某个值时返回T R U E,因子在R中用于表示 分类信息;如果你用过SQL中的枚举类型,那么可以把因子看成类似的 类型。它在内部隐藏的表示方式和语义上都与字符串向量有区别:R中 大多数统计函数的操作对象都是数值向量或因子向量,而不是字符串向 量。输入不是因子的某个值时返回FALSE 知道每一列的基本数据类型可能对于我们进行下一步操作非常重要,因为单个R函数常 常因为输入数据的类型不同而进行不同的操作。在使用某些R内置函数之前,当前数据 集中存储的这些0和1字符需要转换成数字,但是当使用另外一些R内置函数时,它们又 会转换成因子类型。从某种程度上来说,这种在不同数据类型之间来回转换的做法是机 器学习中处理分类时司空见惯的做法。许多真正充当标签或起分类作用的变量都以数学 方式编码成数字0和1。可以把这些数字想象成布尔值:0表示正常电子邮件,1表示垃圾 电子邮件。这是用0和1对一个对象的定性属性进行描述的一种方法,这种方法在机器学 习和统计学中叫做虚拟变量编码(dummy coding)。虚拟编码系统和R中的因子还是有 区别的,后者是采用文字标签来表达对象的定性属性。 警告: R中的因子类型可以当做标签,但是这些标签在后台实际上还是编码为数值型:当程序员读 取标签时,这些数值自动地映射为一个字符串索引数组中对应的字符串标签。因为于R在后 台采用的是数值编码,所以你异想天开地把R因子的标签转换成数值可能会产生奇怪的结 果,原因是你得到的数值可能由你自己的编码方案产生,而非原来与R因子的标签关联的数 值。 表2-4~表2-6所示的是同样的数据,但是采用了三种不同的编码方案。 表2-4:因子编码 MessageID IsSpam 1 “yes” 2 “no” machine_learn_hacker-all.indd 41 2013.4.1 1:13:23 PM 42 | 第2章 表2-5:虚拟变量编码 MessageID IsSpam 1 1 2 0 表2-6:物理学家的编码 MessageID IsSpam 1 1 2 -1 在表2-4中,I s S p a m就直接当做R中的因子处理,这样是一种表示定性区别的方法。而 在实际中它既有可能以因子类型载入,也有可能以字符串类型载入,这取决于你所用 的载入函数(详情请参照第1章中对参数stringsAsFactors的介绍)。每拿到一份新数 据时,你都要先决定怎么用R处理每一列,再决定是以因子类型还是以字符串类型加载 其值。 注意: 如果你不确定到底该以何种类型加载时,最好的方法是先以字符串类型加载,之后根据需 要再转换成因子类型。 在表2-5中,IsSpam仍然是一个定性概念,但是却以数字形式表示了布尔型的区别:1表 示IsSpam是true,而0表示IsSpam是false。实际上,很多机器学习算法都要求定性数据是 这种编码方式。例如,R中默认用于逻辑回归和分类算法的函数glm就假定变量是虚拟变 量,这些将会在第3章讲到。 最后,表2-6展示了对相同定性概念进行数值编码的另一种方式。在这种编码系统中,人 们用+1和-1,而不用1和0。这种对定性概念编码的风格很受物理学家青睐。当你看过 的机器学习书籍够多,最终会遇到这种风格。但是本书会完全摒弃这种风格,因为在变 量的不同表示方式间来回切换会导致无谓的混淆。 推断数据的含义 尽管你已经搞清楚了每一列的数据类型,但是对其含义可能仍然不甚了解。确定一张无 标签的数字表到底在描述什么比大家想象的要困难得多。我们再看看之前提到的表,见 表2-7: machine_learn_hacker-all.indd 42 2013.4.1 1:13:23 PM 数据分析 | 43 表2-7:无标签数据 … … … “1” 73.847017017515 241.893563180437 “0” 58.9107320370127 102.088326367840 假如我们告诉你以下这些信息:a)每一行代表一个人;b)第一列是一个虚拟变量,表 示这个人是男性(值为1)还是女性(值为0);c)第二列是每个人的身高,以英寸为 单位;d)第三列是每个人的体重,以磅为单位。知道这些之后,你再来看这张表,是 否觉得豁然开朗?把这些数字放在适当的上下文之中,它们瞬间就变得有意义了,而且 这也让你对这些数字所描述的对象有了一定的想法。 但是,遗憾的是,有时候你得不到这些解释性信息。遇到这种情况,我们能告诉你的方 法就只有一个:用人类的直觉,再辅以大量的Google搜索。不过,在查看了这类数值 摘要表或可视化摘要图之后,这两种摘要表中每一列的含义不明确,你的直觉会大幅改 善,这也算是因祸得福。 数值摘要表 要弄清楚一份新数据的意义,最好的方法之一就是计算所有列的数值摘要。R很适合完 成这个操作。如果数据中只有一个列向量,summary函数会产生出一些值,这些值的意 义再明白不过了,你应该首先看一看: data.file <- file.path('data', '01_heights_weights_genders.csv') heights.weights <- read.csv(data.file, header = TRUE, sep = ',') heights <- with(heights.weights, Height) summary(heights) #Min. 1st Qu. Median Mean 3rd Qu. Max. #54.26 63.51 66.32 66.37 69.17 79.00 对一个数值向量调用R中的summary函数之后就会得到上述例子中这些数值结果: 1. 向量中的最小值 2. 第一个四分位数(也称作第25个百分位数,即大于25%的数据中最小的那个) 3. 中位数(也称作第50个百分位数) 4. 均值 5. 第3个四分位数(也称作第75个百分位数) 6. 最大值 machine_learn_hacker-all.indd 43 2013.4.1 1:13:23 PM 44 | 第2章 如果你想从数据中快速地得到一份数值摘要,这些值基本上就够了。还缺少的值是标准 差,本章稍后会定义一份数值摘要表。在接下来的内容里,我们会教大家怎样单独计算 summary函数所产生的每一个数值,并告诉大家它们都有什么意义。 均值、中位数、众数 区分均值和中位数是所有统计学入门课程中最乏味的内容之一。的确需要一些时间才能 更加熟悉这些概念,但是当你真正处理数据时,我们相信你还是需要将二者区分开来。 考虑到更好地让读者学到知识,我们试图通过两种不同的方式深化并强调这两个术语的 意义。第一,如何通过算法方式计算均值和中位数。对大多数黑客来说,用代码表达想 法比用数学符号更加自然,所以我们认为,自己写函数来计算均值和中位数,这样可能 会让你更加透彻地理解这两个统计学概念的定义公式。第二,在本章末尾还会通过数据 的直方图和密度图来揭示两者的区别。 计算均值相当简单。在R中,一般用m e a n函数计算均值。当然,把均值放在一个黑盒子 函数里面计算不太能直观地体会到均值的含义,因此我们自己实现m e a n函数,命名为 my.mean。因为相关概念在R中已有其他函数可以计算:sum和length,所以仅需一行R 代码。 my.mean <- function(x) { return(sum(x) / length(x)) } 就这一行代码,均值的含义就已明了:只需把向量中的值求和,再除以长度。想必你已 经知道,这个函数用于产生向量x中所有数的平均数。均值太容易计算了,因为它和列 表中数字的排序毫无关系。 中位数就刚好相反:它完全依赖于列表元素的排序。在R中,一般用median函数计算中 位数,但是我们要实现我们自己的版本,称之为my.median: my.median <- function(x) { sorted.x <- sort(x) if (length(x) %% 2 == 0) { indices <- c(length(x) / 2, length(x) / 2 + 1) return(mean(sorted.x[indices])) } else { index <- ceiling(length(x) / 2) return(sorted.x[index]) } } machine_learn_hacker-all.indd 44 2013.4.1 1:13:23 PM 数据分析 | 45 只看代码行数就知道计算中位数比计算均值要复杂一些。第一步,对向量排序,因为中 位数本质上是有序向量中间的那个数。这也是中位数称为第50个百分位数、第二个四 分位数的原因。一旦把向量排好序,就可以顺理成章地计算任何一个百分位数或四分位 数,只需根据向量长度从某处将列表拆分成两段即可。要计算第25个百分位数(即第一 个四分位数),只需把列表按照长度拆分并取前四分之一即可。 以长度为依据的不规范定义存在一个问题,那就是当数据长度是偶数时此定义就行不 通。如果没有一个数明显地处于数据集中间,你需要为此专门设计一个计算方法。在上 述例子的代码中,我们专门对这种长度为偶数的情况进行了处理:若列表长度为偶数, 则在数据集中间有两个数——这两个数所在位置相当于列表长度为奇数的情况下的中位 数——对这两个数求均值即是中位数。 为了解释得更为清楚,下面举一些简单的例子,第一个例子的中位数是中间两个数的均 值,另一个例子的中位数就是中间那个数: my.vector <- c(0, 100) my.vector # [1] 0 100 mean(my.vector) #[1] 50 median(my.vector) #[1] 50 my.vector <- c(0, 0, 100) mean(my.vector) #[1] 33.33333 median(my.vector) #[1] 0 回到原来的身高和体重数据集,计算身高的均值和中位数。顺便检验一下代码是否 正确: my.mean(heights) #[1] 66.36756 my.median(heights) #[1] 66.31807 mean(heights) - my.mean(heights) #[1] 0 median(heights) - my.median(heights) #[1] 0 在这个案例中,均值和中位数非常接近。我们稍后会简单解释为什么这份数据的均值和 中位数本就应该是接近的。 介绍了以上两个重要的统计数值,你可能奇怪为什么还不介绍众数。其中一个原因是: 众数不像均值和中位数那样总是可以用我们处理过的那些向量来简单定义。因为它不易 自动化计算,所以R中并没有计算数值向量众数的内置函数。 machine_learn_hacker-all.indd 45 2013.4.1 1:13:23 PM 46 | 第2章 注意: 对任意向量定义众数是比较困难的,因为若要用数值来定义众数就要求向量中的数字重复 出现。但是当向量中的数值是任意浮点数时,不太可能任意值都会在向量中重复出现。因 此,众数在许多种数据集上仅有可视化定义。 总之,如果你仍不确定众数的数学含义或其理论意义,你就假定它是在数据集中出现次 数最多的那个数。 分位数 前面已经说过,中位数就是出现在数据集的50%那个位置的数。为了对数据范围有更深 入的认识,你可能想知道数据中最小的那个数是什么。这就是数据集的最小值,这可以 用min函数计算: min(heights) #[1] 54.26313 而数据集中最高/最大的那个数则可以用max计算: max(heights) #[1] 78.99874 两者联合确定了数据的范围: c(min(heights), max(heights)) #[1] 54.26313 78.99874 range(heights) #[1] 54.26313 78.99874 对此可以从另一个角度进行理解:在数据集里,min(最小值)指的是0%的数据都小于 它的数字,m a x(最大值)指的是100%的数据都比它小的数字。由此可以自然地联想 到:如何找出数据集中N%的都小于它的数?答案是使用R中的quantile(分位数)函 数。第N个分位数就表示数据集中有N%的数据小于它。 默认情况下,q u a n t i l e会告诉你数据集的0%、25%、50%、75%以及100%位置处的 数据: quantile(heights) # 0% 25% 50% 75% 100% #54.26313 63.50562 66.31807 69.17426 78.99874 要得到其他位置的分位数,需要给quantile的另一个参数probs传入截取位置: quantile(heights, probs = seq(0, 1, by = 0.20)) # 0% 20% 40% 60% 80% 100% #54.26313 62.85901 65.19422 67.43537 69.81162 78.99874 machine_learn_hacker-all.indd 46 2013.4.1 1:13:23 PM 数据分析 | 47 这里用seq函数在0~1之间产生了一个步长为0.2的序列: seq(0, 1, by = 0.20) #[1] 0.0 0.2 0.4 0.6 0.8 1.0 分位数虽然在统计学传统教程中不如均值和中位数那样受重视,但其作用不容忽视。如 果你在运营一个客服部,并记录用户反馈的响应时间,那么可能你关心前99%顾客情况 的收益远大于只关心中位数个数顾客情况。如果数据形状比较奇怪,只关心平均数个数 的顾客情况就更不能反映整体情况了。 标准差和方差 从某种意义上说,均值与中位数都是一组数的“中间的数”:中位数,顾名思义,就是 一组数据的中心位置,而均值实际上则是列表中所有数值加权之后的中心。 不过,对于一份数据而言,它的集中趋势可能只是你关心的一个方面。了解通常数据与 期望值有多大偏移也同样重要,这称为数据的散布。定义数据的范围有很多方式,比如 前面提到的range函数:数据范围由min(最小)值和max(最大)值决定。但是要合理地 定义散布程度,还需要以下两个因素: 散布程度覆盖的应该只是大多数数据,而非全部数据。 • 散布程度不应该完全由数据的两个极端值决定,这两个极值通常只是异常值而无法 • 代表数据集整体情况。 min(最小值)和max(最大值)通常都是异常值,因此用它们来定义散布程度相当不稳 定。换个角度思考,假设我们保持这两个极值不变而改变其余数据,那会怎样?实际上 你可以随意地去除其余数据而仍然保持最大值和最小值不变。也就是说,不论你是有 200万个还是两个数据点,建立在最大值和最小值基础上的定义都只依赖于数据集的两 个数据点。因为我们不能相信任何对大部分数据点都不敏感的摘要,所以要用更好的方 式来定义数据集散布程度。 对数据集进行数值摘要已有很多可行的方法。例如,可以计算包含50%数据的范围是什 么,围绕中位数的数是什么。用R很容易统计出这些信息: c(quantile(heights, probs = 0.25), quantile(heights, probs = 0.75)) 或者可把范围扩大,找一个覆盖95%数据的范围: c(quantile(heights, probs = 0.025), quantile(heights, probs = 0.975)) 这些的确可以很好地衡量数据的散布程度。当你用到更高深的统计学方法时,此类范围 machine_learn_hacker-all.indd 47 2013.4.1 1:13:24 PM 48 | 第2章 定义方法比比皆是。但是历史上的统计学家们却用过一个不太一样的散布程度衡量方 法:该方法明确地定义为方差(variance)。大致来说,这个定义是为了衡量数据集里 面任意数值与均值的平均偏离程度。作为一名黑客,我们要写一个自己的方差计算函 数,而不是写方差的数学计算公式: my.var <- function(x) { m <- mean(x) return(sum((x - m) ^ 2) / length(x)) } 和之前一样,把这个函数与R的var函数作比较,以确保代码正确: my.var(heights) - var(heights) 实现的函数和R内置函数var的执行结果有偏差。从理论上,可以找一些理由来解释:即 使不考虑浮点运算的精度问题,仍然还会有偏差。事实上,另一个重要原因是函数的实 现方式与R内置函数所用的方法不同:在正规的方差定义中,除数并不是向量的长度, 而是向量的长度减1。之所以这样做,是因为从经验数据估算的方差会由于一些细微原 因比其真值要略小。要修补这个误差,假设数据集有n个数据点,你会自然地想到用比 例因子n/(n-1)与方差估计值相乘,由此更新后的my.var函数实现: my.var <- function(x) { m <- mean(x) return(sum((x - m) ^ 2) / (length(x) - 1)) } my.var(heights) - var(heights) 用这个版本的my.var函数计算方差,可以和R内置方差估算函数的结果完全一致。上文 关于浮点运算有偏差的观点在我们进行长向量运算时很常见,但是本例中的向量长度还 不涉及此问题。 用方差衡量数据集散布程度合乎情理,但是它的值几乎比数据集中任何一个值都要大很 多。用一个很直接的方法就可以看到这个现象,看看与均值相差正负单位方差之间的 数值: c(mean(heights) - var(heights), mean(heights) + var(heights)) #[1] 51.56409 81.17103 这个范围实际上比源数据集的范围要大: c(mean(heights) - var(heights), mean(heights) + var(heights)) #[1] 51.56409 81.17103 range(heights) #[1] 54.26313 78.99874 machine_learn_hacker-all.indd 48 2013.4.1 1:13:24 PM 数据分析 | 49 之所以目前的数据范围超出原始数据范围,是因为定义方差的方式是衡量列表中数据与 均值的平方距离,而没有对其进行开方。为使一切都回归原始的范围,需要用标准差 (standard deviation)代替方差,即方差的平方根: my.sd <- function(x) { return(sqrt(my.var(x))) } 在进行下一步操作之前,最好检验一下你的实现和R中的内置函数是否一致,此函数在R 中叫做sd: my.sd(heights) - sd(heights) 因为现在计算的值在正确的范围内,所以有必要重新估算数据范围,看看偏离均值单位 标准差的数值: c(mean(heights) - sd(heights), mean(heights) + sd(heights)) # [1] 62.52003 70.21509 range(heights) #[1] 54.26313 78.99874 既然用的是单位标准差,而不是单位方差,那么得到的数据范围就轻松落入极差 (range)范围内。要对数据内部集中程度有个基本认识,方法之一便是对比基于标准差 的范围和基于分位数的范围: c(mean(heights) - sd(heights), mean(heights) + sd(heights)) # [1] 62.52003 70.21509 c(quantile(heights, probs = 0.25), quantile(heights, probs = 0.75)) #25% 75% #63.50562 69.17426 用quantile函数可以看到,大致有50%的数据落在距离均值正负一个标准差之间的范围之 内。这是个典型的数据特征,对这份身高数据来说更是如此。但是要最终精确地刻画数 据的形状,还需要对数据进行可视化操作,并定义一些正式用语来描述数据的形状。 可视化分析数据 计算数据的数值摘要的意义毋庸置疑,这毕竟是经典统计学的核心。但是对于很多人来 说,数字并不能有效地传递出他们想看到的信息。要发现数据中的模式有一个更有效的 方法,那就是使数据可视化。本节会介绍两个最简单的可视化数据分析方法:一种是单 列可视化,它侧重数据的形状;另一种是双列可视化,它侧重两列之间的关系。除了介 绍数据可视化工具外,我们还将介绍几种标准数据形状,当你拿到新数据时就可以试着 与这些标准形状比对一下。这些理想的形状,又称为分布,是统计学家们研究了很多年 的标准模式。如果你发现自己的数据符合这些形状之一,那么通常可以对数据有个大致 machine_learn_hacker-all.indd 49 2013.4.1 1:13:24 PM 50 | 第2章 推断:数据源如何,有何大致属性,等等。甚至当你的数据只是近似符合这些形状,也 可以用它们作为基本分布形状,叠加出更复杂也更加逼近数据的形状。 言归正传,接下来开始对之前一直在操作的身高和体重数据进行可视化。这实际上是一 份相当复杂的数据,我们多次用它阐释了本书中提出的理念。人们用到的最典型的单列 可视化方法就是直方图。该方法首先把数据集分放到若干个区间里,然后统计每个区间 里数据的条数。如图2-5所示,以1英寸为区间宽度创建直方图来可视化身高数据,R代 码如下: library('ggplot2') data.file <- file.path('data', '01_heights_weights_genders.csv') heights.weights <- read.csv(data.file, header = TRUE, sep = ',') ggplot(heights.weights, aes(x = Height)) + geom_histogram(binwidth = 1) 身高 数量 图2-5:10 000人的身高直方图 machine_learn_hacker-all.indd 50 2013.4.1 1:13:24 PM 数据分析 | 51 你马上就会发现:数据呈钟形。大部分数据处于中间,与均值和中位数接近。但这只是 因所选的直方图类型而造成的假象。检验是否存在假象的方法之一就是尝试不同的区间 宽度。在你使用直方图的时候要始终牢记一点:区间宽度是你强加给数据的一个外部结 构,但是它却同时揭示了数据的内部结构。在构建直方图的时候,如果设置了错误的参 数,那么你发现的模式(即使真实存在)也会轻易地消失。用5英寸的区间宽度重新构 建直方图,如图2-6所示,代码如下: ggplot(heights.weights, aes(x = Height)) + geom_histogram(binwidth = 5) 身高 数量 图2-6:10 000人的身高直方图 当采用一个较大的区间宽度值时,数据的很多结构就不见了。虽然还有个顶峰,但是之 前看到的对称性已经几乎不存在了。这叫做过平滑(oversmoothing),与之相反的问题 machine_learn_hacker-all.indd 51 2013.4.1 1:13:24 PM 52 | 第2章 则称为欠平滑(undersmoothing),也是很危险的。再次调整区间宽度值,这一次是一 个非常小的值:0.001英寸,如图2-7所示: ggplot(heights.weights, aes(x = Height)) + geom_histogram(binwidth = 0.001) 这一次使数据欠平滑了,因为采用了一个相当小的区间宽度值。因为数据量很大,所以 从这个直方图中依然可以发现一些有价值的东西,但如果是一份只有100个数据点的数 据集,你却用了这种区间宽度,基本上一文不值。 身高 数量 图2-7:10 000人的身高直方图 调整区间宽度值是一件枯燥的事,而且即便是最佳的直方图,在我们看来其锯齿状也很 明显,因此我们倾向于选择一种和直方图类似的方法来可视化,即核密度估计(Kernel Density Estimate,KDE)或者叫做密度曲线图(density plot)。尽管密度曲线图也无法 规避直方图令人纠结的欠平滑和过平滑问题,但是我们首先考虑的是美观——密度曲线 machine_learn_hacker-all.indd 52 2013.4.1 1:13:24 PM 数据分析 | 53 图尤其在大数据集上更接近我们所期望的理论形状。此外,密度曲线图也有一些理论优 势:揭示数据潜在的形状,密度曲线图需要的数据点比直方图要少。而且,生成密度曲 线图和直方图一样简单。如图2-8所示,构建了身高数据的第一个密度曲线图: ggplot(heights.weights, aes(x = Height)) + geom_density() 身高 密度 图2-8:10 000人身高数据密度曲线图 密度曲线图的平滑性有助于我们发现数据的模式类型,而这很难在直方图中发现。从密 度曲线图中看到,峰值处竟然有些平坦,不禁令人生疑。因为期望的标准钟形曲线在峰 值处不是平坦的,这让我们很想知道是不是在数据集的峰值处还隐藏着更多的结构。当 你觉得可能有些结构缺失的时候,有一个方法可以试一试:根据其中任意一个定性变量 将曲线分开。现在,根据数据集当中的性别将曲线分离成两部分。在图2-9中,构建一 machine_learn_hacker-all.indd 53 2013.4.1 1:13:24 PM 54 | 第2章 个密度曲线图,它由两个密度曲线叠加而成,但已经通过颜色标明了其所代表的不同 性别: ggplot(heights.weights, aes(x = Height, fill = Gender)) + geom_density() 身高 密度 女性 男性 性别 图2-9:与性别相关的10 000人身高数据密度曲线图 在这张图里我们一下就发现了此前忽视的一个隐藏模式:我们虽然没有看到一个钟形曲 线,却看到了两个部分重叠的钟形曲线。这并不意外,因为男性和女性的平均身高是 不一样的。我们可能希望两个性别的体重数据曲线也是与此相同的钟形曲线结构。如图 2-10所示,给数据集的体重数据构建一个新的曲线图。 我们再一次看到了两个钟形曲线结构的混合。在本书余下内容里,还会比较详细地探讨 这种混合的钟形曲线,但是有必要现在就给这种结构命个名:混合模型,它是由两个标 准分布混合而形成的一个非标准分布。 machine_learn_hacker-all.indd 54 2013.4.1 1:13:24 PM 数据分析 | 55 体重 密度 女性 男性 性别 图2-10:与性别相关的10 000人体重数据密度曲线图 当然,我们需要把标准分布讲得更清楚些,这样才能清晰明白上面那句话的意义,因此 从最理想的数据分布入手:正态分布,也称为高斯分布或钟形曲线。正态分布的例子有 很多,上面的混合分布就是由两个正态分布组合而成的,这里的每一个正态分布叫做一 个分片(facet)。在图2-11中,把此前展示的密度曲线分片,以便读者能看到两个单独 的钟形曲线。在R中,可以用下面的代码实现这种分片: ggplot(heights.weights, aes(x = Weight, fill = Gender)) + geom_density() + facet_grid(Gender ~ .) 分片完成后,两个钟形曲线清晰可见,一个是中心在137.5磅的女性钟形曲线图,另一个 是中心在187.5磅的男性钟形曲线图。这种类型的钟形曲线就是正态分布。这个形状十分 常见,以至于我们下意识觉得“正态”才是数据的“正常形态”。但这是不对的,我们 machine_learn_hacker-all.indd 55 2013.4.1 1:13:25 PM 56 | 第2章 关心的很多事物,从人们的年收入到股价每日的涨跌,用正态分布来描述都不是特别适 合。但是,正态分布在统计学的数学理论中相当重要,因此相比其他大多数分布,关于 正态分布的研究更透彻些。 从更抽象的层面看正态分布,它只是钟形曲线的一种类型,图2-12~图2-14都是钟形 曲线。 体重 密度 女性 男性 性别 女性 男性 图2-11:10 000人体重数据密度曲线图,以性别分片(单位:磅) machine_learn_hacker-all.indd 56 2013.4.1 1:13:25 PM 数据分析 | 57 密度 图2-12:均值为0,方差为1的正态分布 在这些图中,有两个变量:分布的均值,它决定钟形曲线的中心所在;分布的方差,它 决定钟形曲线的宽度。可以通过下面的代码得到不同的钟形曲线图,调整其中的参数, 直到最终的钟形曲线看起来很舒服。调整时修改其中m和s的值即可: m <- 0 s <- 1 ggplot(data.frame(X = rnorm(100000, m, s)), aes(x = X)) + geom_density() 用这段代码生成的曲线的基本形状是一样的;改变m和s只是移动其中心或者伸缩其宽 度。读者从图2-12~图2-14中可以看到,曲线的具体形状在改变,但是其整体轮廓并没 有变化。要注意的是,钟形并不是判断数据是否为正态分布的充分条件,因为还存在其 他钟形分布,稍后会介绍其中一种。利用正态分布,可以定义一些关于数据形状的定性 概念,所以接下来让我们快速了解一些术语。 machine_learn_hacker-all.indd 57 2013.4.1 1:13:25 PM 58 | 第2章 密度 图2-13:均值为1,方差为3的正态分布 首先,重新讨论到现在都还被我们冷落在一旁的众数。之前提到过,连续数值列表的众 数不好定义,因为没有数值重复出现。但是,连续数值的众数却能用可视化的方法解 释清楚:当构建一条密度曲线时,数据的众数就在钟形的峰值处。举个例子,如图2-15 所示。 machine_learn_hacker-all.indd 58 2013.4.1 1:13:25 PM 数据分析 | 59 密度 图2-14:均值为0,方差为5的正态分布 用可视化方法估计众数,密度曲线图比直方图要容易得多,这也是我们推荐使用密度曲 线图的原因所在。当你看到密度曲线图,众数的意义一目了然,然而直接面对数字的时 候,几乎看不到它背后的含义。 既然已经定义了众数,就应该指出正态分布所定义的众数有一个特点:它只有一个 众数,同时也是数据的均值和中位数。作为对比例子,图2-16所示的图有两个众数, 图2-17所示的图有三个众数。 machine_learn_hacker-all.indd 59 2013.4.1 1:13:25 PM 60 | 第2章 密度 图2-15:标出众数的正态分布 当 谈 到 数 据 的 众 数 个 数 时 , 会 用 到 以 下 术 语 : 只 有 一 个 众 数 的 分 布 叫 单 峰 (unimodal);有两个众数的分布叫双峰(bimodal);有两个以上众数的分布叫多峰 (multimodal)。 还可以从一个定性的区别来划分出两类数据,那就是对称分布(symmetric)数据和偏态 分布(skewed)数据。图2-18和图2-19所示分别是对称分布和偏态分布。 machine_learn_hacker-all.indd 60 2013.4.1 1:13:25 PM 数据分析 | 61 密度 图2-16:两个正态分布混合,两个众数都已标出 对称分布的特点是:图2-18中众数的左右两边形状一样。正态分布就有这个特点,该特 点说明观察到小于众数的数据和大于众数的数据的可能性是一样的。对比之下,图2-19 所示图形向右偏斜,说明在众数右侧观察到极值的可能性要大于其左侧,这种图形称为 伽玛分布(gamma distribution)。 machine_learn_hacker-all.indd 61 2013.4.1 1:13:25 PM 62 | 第2章 密度 图2-17:三个正态分布混合,三个众数都已标出 最后我们要根据另一个定性的区别来划分出两类数据:窄尾分布(thin-tailed)数据和 重尾分布(heavy-tailed)数据。稍后我们会用一张标准的图来说明两者之间的区别,但 是这个区别用语言更容易表述。窄尾分布所产生的值通常都在均值附近,99%的可能性 都是这样。比如,正态分布在99%的情况下所产生的数据偏离均值都不会超过三个标准 差。相比之下,另一个钟形分布——柯西分布(Cauchy distribution)大约只有90%的值 落在三个标准差范围内。距离均值越远,这两个分布的特点越不同:正态分布几乎不可 能产生出距离均值有六个标准差的值,然而柯西分布仍有5%的可能性。 machine_learn_hacker-all.indd 62 2013.4.1 1:13:25 PM 数据分析 | 63 密度 图2-18:对称分布 图2-20和图2-21的对比就是窄尾分布和重尾分布的典型区别。 但是,我们认为通过下面这种方法,你可以理解得更直观:亲自用这两种分布分别产生 大量数据,并观察结果。R可以轻松实现这一点,代码如下: set.seed(1) normal.values <- rnorm(250, 0, 1) cauchy.values <- rcauchy(250, 0, 1) range(normal.values) range(cauchy.values) machine_learn_hacker-all.indd 63 2013.4.1 1:13:25 PM 64 | 第2章 密度 图2-19:偏态分布 把结果画出来可以看得更清楚一些: ggplot(data.frame(X = normal.values), aes(x = X)) + geom_density() ggplot(data.frame(X = cauchy.values), aes(x = X)) + geom_density() 本节就正态分布及柯西分布展开的讨论到此为止,接下来我们来对正态分布的本质特性 做个总结:它是单峰的、对称的分布,也是钟形的窄尾分布。柯西分布也是单峰的、对 称的分布,也是钟形曲线,却是重尾分布。 本节是关于密度曲线图的,在讨论了正态分布之后,我们再来看看两幅规范的图像,从 而为本节画上句号:一个是有点偏斜的伽玛分布,另一个是非常偏斜的指数分布。这两 个分布在后面内容中都会用到,因为它们在实际的数据中都出现了。但有必要现在就开 始讲解,以便更加直观地了解偏态分布。 machine_learn_hacker-all.indd 64 2013.4.1 1:13:25 PM 数据分析 | 65 密度 分布类型 柯西分布 正态分布 图2-20:重尾的柯西分布和窄尾的正态分布 首先从伽玛分布入手。它很灵活,推荐你自己动手操作一下。下面是示例代码: gamma.values <- rgamma(100000, 1, 0.001) ggplot(data.frame(X = gamma.values), aes(x = X)) + geom_density() 伽玛分布的数据绘图结果见图2-22。 从图2-22可以看出,伽玛分布是向右倾斜的,这意味着中位数和均值有时差距很大。在 图2-23中,我们把人们玩苹果手机(iPhone)游戏《屋顶狂奔》(Canabalt)的得分绘制 成了密度曲线图。 这份真实的数据与理论上的伽玛分布非常相似。我打赌很多其他游戏得分数据的曲线图 也与此相似,因此,要想分析游戏数据,需要有一份特别有用的理论工具。 machine_learn_hacker-all.indd 65 2013.4.1 1:13:26 PM 66 | 第2章 密度 分布类型 柯西分布 正态分布 柯西分布 正态分布 图2-21:分片绘图:重尾的柯西分布和窄尾的正态分布 还有一点需要记住的是:伽玛分布只有正值。在本书后面讲解的随机最优化方法就需要 一个全部正值的分布。 最后一个要讲解的分布是指数分布(exponential distribution),它是典型的偏态分布。 图2-24是一份满足指数分布的数据。 因为指数分布的众数出现在0值处,所以它特别像是把钟形曲线切掉一半后留下的正值 部分。在满足下列条件的情况下常常会出现指数分布:数据集中频数最高的是0,并且 只有非负值出现。例如,企业呼叫中心常常发现两次收到呼叫请求的间隔时间看上去符 合指数分布。 machine_learn_hacker-all.indd 66 2013.4.1 1:13:26 PM 数据分析 | 67 当你对数据越来越熟悉,对统计学家们所研究的理论分布学习得更深入,你会对这些分 布更加熟悉——最重要的原因是:这些分布会反复出现。到目前为止,你从本节学到的 是一些简单的定性术语,可以用于向别人描述数据:单峰分布和多峰分布;对称分布和 偏态分布;窄尾分布和重尾分布。 密度 图2-22:偏态分布 machine_learn_hacker-all.indd 67 2013.4.1 1:13:26 PM 68 | 第2章 得分 密度 图2-23:《屋顶狂奔》游戏的得分 列相关的可视化 到目前为止,我们只是介绍了数据集中单列处理方法的一些思路。这些方法很有价值: 如果在数据集中发现了相似形状,那会让你得到很多信息。发现一个正态分布就说明均 值和中位数是相等的,也说明在大部分时候你不会观察到偏离均值超过三个标准差的数 值。仅仅学习了一个数据可视化结果,我们就能得到这么多信息。 但是,目前我们所回顾的知识你都能在传统统计学课程中学到,和你热切希望投入其中 的机器学习应用还是有所不同。要进行真正的机器学习,我们需要发现数据集中多个数 据列之间的关系,以此搞清数据所隐含的意义,而且能够预测未来的一些东西。在本书 中我们要接触到的预测问题有如下几个: machine_learn_hacker-all.indd 68 2013.4.1 1:13:26 PM 数据分析 | 69 密度 图2-24:指数分布 根据一个人身高预测其体重; • 根据电子邮件文本预测一封邮件是不是垃圾邮件; • 预测一个人是不是想买此前从未向其推荐过的一件产品。 • 这些问题仍然可以分成两类:回归问题,要预测的是数值,比如体重,已知的是一组其 他数值,比如身高;分类问题,就是给数据贴上标签,比如“垃圾”,已知的是一组数 值,比如类似“伟哥”(viagra)和“西力士”(cialis)这些垃圾词汇的词频。本书余 下内容中很多地方都会介绍回归和分类的方法,在这里我们希望读者谨记两种数据可视 化方法。 machine_learn_hacker-all.indd 69 2013.4.1 1:13:26 PM 70 | 第2章 第一个就是老套的回归图。在回归图中,我们要画出数据的散点图,观察是否有隐含的 形状来体现数据集两列之间的关系。回到之前的身高体重数据集上,接下来给身高和体 重绘制一幅散点图。 如果你对散点图还不是很熟悉,那么你必须知道散点图是一幅二维图像,其中一维相当 于变量x,另一维相当于变量y。要绘制散点图,还需要用到ggplot。 ggplot(heights.weights, aes(x = Height, y = Weight)) + geom_point() 所绘制的散点图如图2-25所示。 身高 体重 图2-25:身高和体重散点图 从图2-25中可以明显地看到身高和体重之间存在某种关系模式:较高的人会较重。这和 直觉明显符合,后面内容将会介绍用于发现这类模式的通用方法。 machine_learn_hacker-all.indd 70 2013.4.1 1:13:26 PM 数据分析 | 71 为了更仔细地研究这个模式,用ggplot2中的平滑方法把所看到的这个线性模式形象地描 绘出来: ggplot(heights.weights, aes(x = Height, y = Weight)) + geom_point() + geom_smooth() 如图2 26所示,这是一个叠加了平滑模式的新散点图。 身高 体重 图2-26:带平滑线性拟合的身高和体重散点图 geom_smooth函数可以根据人们的身高预测其体重。在本例中,预测的趋势是一条简单 的曲线,如图2-26中的浅灰色标注。曲线周围阴影区是体重预测值的范围,当数据量增 加之后,这样的猜测会更准,阴影区也会缩小。因为我们已经用到了所有的数据,所以 要体会这种效果最好的方法就是反其道而行之:移除一些数据,然后观察其中的模式如 何变得越来越不明显。实验的结果见图2-27~图2-29。 machine_learn_hacker-all.indd 71 2013.4.1 1:13:26 PM 72 | 第2章 ggplot(heights.weights[1:20,], aes(x = Height, y = Weight)) + geom_point() +geom_smooth() ggplot(heights.weights[1:200,], aes(x = Height, y = Weight)) + geom_point() +geom_smooth() ggplot(heights.weights[1:2000,], aes(x = Height, y = Weight)) + geom_point() +geom_smooth() 回想一下,如果用一个数据列去预测另一个数据列,且要预测的是数值,那么就称为 回归。相反,若要预测的是标签,就称为分类。而对于分类,你应该知道,和图2-30差 不多。 身高 体重 图2-27:20份数据的身高和体重散点图 在这张图中,用所有数据绘出了每个人的身高和体重,也用颜色标注了每个数据点的性 别。从而清楚地看到数据集有两个不同的群体。要用ggplot2来绘制这幅图,需要运行 下面的代码: ggplot(heights.weights, aes(x = Height, y = Weight, color = Gender)) + geom_point() machine_learn_hacker-all.indd 72 2013.4.1 1:13:27 PM 数据分析 | 73 身高 体重 图2-28:200份数据的身高和体重散点图 这张图是标准的分类图。在分类图中,用数据生成散点图,然后用第三列给不同标签的 数据点标上颜色。对于身高和体重数据,加入的第三列是数据中每个人的性别。再看这 幅图,似乎可以只用身高和体重来猜这个人的性别。分类要做的事就是利用其他数据来 预测诸如性别这样的分类变量,第3章会比较详细地讲解分类的算法。而现在,我们只 简单看看运行一个标准的分类算法之后的结果,如图2-31所示。 machine_learn_hacker-all.indd 73 2013.4.1 1:13:27 PM 74 | 第2章 身高 体重 图2-29:2000份数据的身高和体重散点图 我 们 所 画 的 直 线 有 一 个 富 有 想 象 力 的 名 字 —— “ 分 类 超 平 面 ” ( s e p a r a t i n g hyperplane)。之所以称为“分类超平面”,是因为它把数据分成了两部分:给定一个 人的身高和体重,数据点译注3落在超平面的一侧,你会猜测这个人是女性,而在另一侧 你会猜测他是男性。这是一个相当好的猜测方法,就这份数据集而言,92%的情况下你 会猜对。我们认为这个效果还不错,因为我们使用的分类模型非常简单——它仅仅用了 身高和体重两个特征而已。在实际的分类任务中,我们常常要用几十个、几百个甚至几 千个特征来预测类别。只不过这份数据刚好特别容易上手,我们才选了它来演示。 译注3: 此处的数据点指的是由身高和体重确定的点。 machine_learn_hacker-all.indd 74 2013.4.1 1:13:27 PM 数据分析 | 75 本章即将结束,为了让你对下一章充满期待,我们把实现刚才预测的R代码展示出来。 根据观察,根本不用什么代码,结果就出人意料: heights.weights <- transform(heights.weights, Male = ifelse( Gender == 'Male', 1, 0)) logit.model <- glm(Male ~ Height + Weight, data = heights.weights, family = binomial(link = 'logit')) ggplot(heights.weights, aes(x =Height, y = Weight, color = Gender)) + geom_point() + stat_abline(intercept = -coef(logit.model)[1]/coef(logit.model)[2], slope = -coef(logit.model)[3] / coef(logit.model)[2], geom = 'abline',color = 'black') 在第3章中,我们会倾囊相授如何用现成的机器学习方法来构建自己的分类器。 身高 体重 女性 男性 性别 图2-30:2 000份数据的身高和体重散点图(用颜色标注性别) machine_learn_hacker-all.indd 75 2013.4.1 1:13:27 PM 76 | 第2章 身高 体重 女性 男性 性别 图2-31:带线性拟合的身高和体重散点图 machine_learn_hacker-all.indd 76 2013.4.1 1:13:27 PM
还剩85页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 6 金币 [ 分享pdf获得金币 ] 10 人已下载

下载pdf

pdf贡献者

nw2gc

贡献于2013-09-12

下载需要 6 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf