现代数据科学家-Python-与-R-指南-全-
现代数据科学家 Python 与 R 指南(全)
原文:
zh.annas-archive.org/md5/7d2468ffba808f16fc49f377a169de98
译者:飞龙
第一部分:发现新语言
为了开始我们的探索,我们将回顾 Python 和 R 的历史。通过比较和对比这些起源故事,您将更好地理解数据科学领域每种语言的当前状态。如果您想开始编码,请随意跳到第二部分。
Python 和 R 适用于现代数据科学家
最佳结合
作者:Rick J. Scavetta 和 Boyan Angelov
Python 和 R 适用于现代数据科学家
作者:Rick J. Scavetta 和 Boyan Angelov
版权所有 © 2021 Boyan Angelov 和 Rick Scavetta。
印刷于美利坚合众国。
出版:O’Reilly Media, Inc.,1005 Gravenstein Highway North, Sebastopol, CA 95472。
O’Reilly 图书可供教育、商业或销售促销使用。大多数书籍也提供在线版(oreilly.com
)。有关更多信息,请联系我们的公司/机构销售部门:800-998-9938 或corporate@oreilly.com。
-
编辑:Angela Rufino 和 Michelle Smith
-
制作编辑:Katherine Tozer
-
内部设计:David Futato
-
封面设计:Karen Montgomery
-
插图:Kate Dullea
-
2021 年 9 月:第一版
第一版修订历史
-
2020-11-06:首次发布
-
2021-01-26:第二次发布
-
2021-03-16:第三次发布
-
2021-05-19:第四次发布
查看oreilly.com/catalog/errata.csp?isbn=9781492093404
以获取发布详细信息。
O’Reilly 标志是 O’Reilly Media, Inc. 的注册商标。Python 和 R 适用于现代数据科学家,封面图片及相关商标也是 O’Reilly Media, Inc. 的商标。
本作品所表达的观点属于作者个人观点,不代表出版商的观点。尽管出版商和作者已尽力确保本作品中的信息和指导准确无误,但出版商和作者不对任何错误或遗漏承担责任,包括但不限于因使用或依赖本作品而导致的损害。使用本作品中包含或描述的任何代码示例或其他技术如受开源许可证或他人的知识产权的限制,您有责任确保您的使用符合这些许可证和/或权利。
978-1-492-09340-4
致谢
致我的父母,为我提供了人生中最好的开端。致我的妻子,是我坚实的支柱。致我的孩子们,拥有未来最光明的梦想。
—Boyan Angelov
对于我们所有准备好、愿意和能够通过更广泛的视角看待世界,消除我们中间的“他者”。
—Rick Scavetta
前言
为什么我们写了这本书
我们想向您展示,对工具更加敏感、知情和有目的地的选择是提高生产力的最佳策略。出于这个目标,我们并没有写一本双语词典(好吧,不仅仅是那样,你将在附录 A 中找到这个方便的资源)。关于 Python 与 R 的持续讨论(所谓的“语言战争”)早已不再具有生产力。这让我们想起了马斯洛的锤子:“如果你手头只有一把锤子,看什么都像钉子”。这是一个设定在绝对中的幻想世界观,在那里工具提供了全面解决方案。现实世界的情况是依赖于背景的,一个工匠知道应该适当地选择工具。我们的目标是通过利用所有出色的数据科学工具展示一种新的工作方式。因此,我们旨在开发现代数据科学家思考和工作的方式。
我们选择标题中的“现代”,不仅仅是为了表明我们方法的新颖性。它使我们能够在讨论工具时采取更加细致的立场。什么是现代数据科学?现代数据科学是:
-
集体性。它不是孤立存在的。它融入到更广泛的网络中,如团队或组织。我们避免术语,当它构建桥梁时采纳它(见“技术交互”,下文)。
-
简单性。我们的目标是减少在方法、代码和沟通中的不必要的复杂性。
-
可访问性。这是一个可以评估、理解和优化的开放设计过程。
-
可推广性。它的基本工具和概念适用于许多领域。
-
面向外部。它结合了其他领域的发展,并受其启发和影响。
-
道德和诚实。它以人为本。它采纳了道德工作的最佳实践,并考虑了对社区和社会的更广泛视角的后果。我们避免只为短期利益服务的炒作、潮流和趋势。
然而,未来几年内数据科学家的实际工作描述如何演变,我们可以期待这些永恒原则将为其提供坚实的基础。
技术交互
接受世界比任何单一工具能够服务的更广泛、更多样化和更复杂的事实,这提出了一个挑战,最好是直接和早期地加以解决。
这种广阔的视角导致了技术交互的增加。我们必须考虑编程语言、包、命名约定、项目文件架构、集成开发环境(IDE)、文本编辑器等等,这些都将最适合情况。多样性导致复杂性和混乱。
我们的生态系统变得越来越多样化,因此考虑我们的选择是否作为桥梁或障碍变得更加重要。我们必须始终努力做出能够与同事和社区建立桥梁的选择,避免那些会使我们孤立和僵化的障碍。我们将会遇到各种选择的多样性,每种情况的挑战在于在个人偏好和共同体可访问性之间找到平衡。
这种挑战存在于所有技术交互中。除了工具选择(一种“硬”技能)外,还包括沟通(一种“软”技能)。内容、风格和传播媒介等因素,仅举几例,也充当特定观众的桥梁或障碍。
成为 Python 和 R 的双语者是向更广泛数据科学社区成员建立桥梁的一步。
本书适合谁阅读
本书旨在面向职业生涯中级阶段的数据科学家。因此,它并不试图教授数据科学。尽管如此,初入职业生涯的数据科学家也能从本书中受益,了解现代数据科学环境中各种主题、工具或语言的可能性。
我们的目标是弥合 Python 和 R 社区之间的差距。我们希望摆脱部落式的“我们对抗他们”的心态,转向一个统一、高效的社区。因此,本书适合那些看到扩展技能组合、视角和他们的工作能够为各种数据科学项目增加价值的数据科学家。
忽视我们可以利用的强大工具是不负责任的。我们努力开放心态,接受实现编程目标的新的、高效的方法,并鼓励同事们走出舒适区。
此外,第一部分和附录 A 也可作为在需要快速将某个熟悉的概念在不同语言之间进行映射时的有用参考。
先决条件
为了从本书中获得最大的价值,我们假设读者至少熟悉数据科学中的一种主要编程语言,如 Python 和 R。对于了解相关语言(例如 Julia 或 Ruby)的读者,也能够获得良好的价值。
对数据科学工作的一般领域(如数据整理、数据可视化和机器学习)有基本了解是有益的,但不是必须的,以理解例子、工作流场景和案例研究。
本书的组织方式
我们将本书组织成为成年人学习第二门口语的方式。
在第 I 部分,我们从两种语言的起源开始回顾,然后展示这些起源如何影响当前状态,涵盖关键突破。如果你想直接进入语言内容,可以跳到第 II 部分。
在我们与口语语言的类比中,这有助于解释为什么会有不规则动词和复数形式等怪癖。词源学^(1)是有趣的,有助于你欣赏一门语言,比如德语中看似无穷无尽的复数名词形式,但对于口语来说并非必需。
第二部分深入探讨了两种语言的方言,通过提供一个镜像的视角。首先,我们将讨论 Python 用户如何处理与 R 的工作,然后反过来。这不仅会扩展你的技能集,还会扩展你的思维方式,因为你会体会到每种语言的运作方式。
在这部分中,我们将单独对待每种语言,因为我们开始变得双语。就像掌握口语双语一样,我们需要抵制两种战胜冲动。第一个冲动是指出我们母语中某些东西有多么简单、优雅或以某种方式“更好”。祝贺你,但学习新语言不就是这个意思吧?我们要单独学习每种语言。尽管我们将随着学习指出比较,但它们将帮助我们处理母语的包袱。
第二个冲动是不断尝试在两种语言之间进行逐字和字面的解释。这阻止了我们用新语言思考(甚至梦想)。有时这是不可能的!我喜欢用的例子是德语中的das schmeckt mir,或意大利语中的ho fame,它们的字面翻译非常糟糕,比如“那对我来说尝起来很好”和“我饿了”。关键在于,不同的语言允许不同的结构。这为我们提供了新的工具和新的思考方式,一旦我们意识到不能将一切都简单地映射到我们以前的知识上。把这些章节看作我们将你对一种语言的知识映射到另一种语言的第一步。
第三部分涵盖了语言应用的现代背景。这包括对开源软件包的广泛生态系统的审查,以及工作流特定方法的多样性。本部分将展示为什么在某些情况下更喜欢一种语言,尽管它们在这一点上仍然是独立的语言。这将帮助你决定在大型数据科学项目的各个部分中使用哪种语言。
在口语中,“lost in translation” 是真实存在的。有些事情在某种语言中表达得更好。在德语中,“mir ist heiß” 和 “ich bin heiß” 在英语中都是 “I’m hot”,但德语使用者会区分天气热和身体热。其他词如 Schadenfreude,由 “Schaden”(损害)和 “Freude”(快乐)组成,意味着因他人困境而感到快乐,或者 Kummerspeck,由 “Kummer”(忧虑)和 “Speck”(培根)组成,指因情感进食而增加的体重,是如此完美,以至于没有必要翻译它们。
第 IV 部分详细介绍了存在于语言之间的现代接口。首先,我们成为了双语者,独立使用每种语言。然后,我们确定如何在两种语言之间选择一种。现在,我们将探讨工具,将我们从分离的、互连的 Python 和 R 脚本转变为单一脚本,以一个工作流程将这两种语言交织在一起。
当你不仅仅是双语者,而是在一个双语社区中工作时,真正的乐趣就开始了。你不仅可以独立使用每种语言进行交流,还可以以其他双语者才能理解和欣赏的新颖方式将它们结合起来。双语不仅仅提供了进入新社区的机会,而且本身就创造了一个新的社区。对于纯粹主义者来说,这纯属折磨,但我希望我们已经超越了那个阶段。双语者可以理解这样的警告:“Ordnungsamt 今天正在监控 Bergmannkiez”。理想情况下,你不是因为忘记了单词而替换它们,而是因为这是情境中的最佳选择。并非所有单词都能很好地翻译,比如 Ordnungsamt(管理局?),而 Bergmannkiez 则是柏林的一个社区,本身不应被翻译。有时,一种语言中的词汇更容易传达信息,比如 Mundschutzpflicht,即在冠状病毒大流行期间强制佩戴口罩。
最后,第七章包含一个案例研究,将详细说明如何根据本书内容实施现代数据科学项目。在这里,我们将看到所有先前的部分如何在一个工作流程中结合起来。
让我们来谈谈
数据科学领域不断发展,我们希望本书能帮助您轻松地在 Python 和 R 之间导航。我们很期待听到您的想法,所以请告诉我们您的工作如何改变!您可以通过本书的伴随网站 moderndata.design/
联系我们。在那里,您将找到更新的额外内容和一张方便的 Python/R 双语速查表。
本书使用的约定
本书使用以下印刷约定:
Italic
表示新术语、URL、电子邮件地址、文件名和文件扩展名。
Constant width
用于程序列表以及段落内引用程序元素,例如变量或函数名、数据库、数据类型、环境变量、语句和关键字。
Constant width bold
显示用户应按照字面意思输入的命令或其他文本。
Constant width italic
显示应替换为用户提供的值或由上下文确定的值的文本。
小贴士
该元素表示提示或建议。
注意
该元素表示一般注意事项。
警告
该元素指示警告或注意事项。
使用代码示例
补充材料(代码示例、练习等)可在https://github.com/moderndatadesign/PyR4MDS下载。
如果您有技术问题或使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com。
本书旨在帮助您完成工作。一般来说,如果本书提供示例代码,您可以在自己的程序和文档中使用它。除非您要复制大量代码,否则无需征得我们的许可。例如,编写一个使用本书中多个代码片段的程序无需许可。销售或分发 O’Reilly 图书示例需要许可。引用本书并引用示例代码回答问题无需许可。将本书大量示例代码整合到您产品的文档中需要许可。
我们感谢您的理解,但通常不需要署名。署名通常包括标题、作者、出版商和 ISBN 号。例如:“书名 by Some Author (O’Reilly). Copyright 2012 Some Copyright Holder, 978-0-596-xxxx-x.”
如果您觉得您使用的代码示例不属于合理使用范围或上述许可权限,请随时联系我们,邮件至permissions@oreilly.com。
O’Reilly 在线学习
注意
超过 40 年来,O’Reilly Media一直致力于为公司提供技术和业务培训、知识和见解,帮助其取得成功。
我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专业技能。O’Reilly 的在线学习平台让您随时访问现场培训课程、深度学习路径、交互式编码环境,以及来自 O’Reilly 和其他 200 多家出版商的大量文本和视频。更多信息,请访问http://oreilly.com。
如何联系我们
有关本书的评论和问题,请联系出版社:
-
O’Reilly Media, Inc.
-
1005 Gravenstein Highway North
-
Sebastopol, CA 95472
-
800-998-9938(美国或加拿大)
-
707-829-0515(国际或本地)
-
707-829-0104 (传真)
我们为这本书创建了一个网页,列出勘误、示例和任何额外信息。您可以在http://www.oreilly.com/catalog/catalogpage上访问此页面。
电邮至bookquestions@oreilly.com发表评论或提出有关本书的技术问题。
欲了解我们的图书和课程的新闻和信息,请访问http://oreilly.com。
在 Facebook 上找到我们:http://facebook.com/oreilly
在 Twitter 上关注我们:http://twitter.com/oreillymedia
在 YouTube 上观看我们:http://www.youtube.com/oreillymedia
致谢
致我的父母,给予我最好的人生起点。致我的妻子,作为我的支柱。致我的孩子,拥有最光明的未来梦想。
Boyan Angelov
^(1) 单词起源和含义的研究。
第一章:初生牛犊不怕虎
里克·J·斯卡维塔
博扬·安吉洛夫
我们希望以一句伟大的开场白开始,比如“这是最好的时代,也是最坏的时代……”,但老实说,这只是最好的时代 —— 数据科学正在蓬勃发展!随着它继续成熟,它已经开始分裂成各自的专业领域,就像许多学科随着时间的推移一样。这种成熟是科学计算早期就开始的漫长旅程的结果。我们相信,了解一些 Python 和 R 的起源故事将帮助您更好地理解它们在当今环境中的区别,从而更好地利用它们。
我们不会假装成为科学历史学家,那些追溯伟大发现和人物背后情况的学术人员。我们能做的是为您提供 Python 和 R 的起源及其如何引导我们走向当前局面的精彩摘要。
R 语言的起源
每当我想到 R,我就会想起 FUBU,这是一家成立在 90 年代的街头服装公司。这个名字是一个缩写词,我立即爱上了它:“For Us, By Us”。FUBU 意味着社区,意味着理解你的人民的需求和欲望,并确保你为他们提供了良好的服务。R 就是 FUBU。^(1) 在本章结束时,我确信你会有同样的感觉。一旦我们承认 R 就是 FUBU,它开始变得更有意义了。
我们可以追溯 R 语言的起源到如今传奇般的新泽西贝尔实验室。1976 年,统计编程语言 S 的开发由约翰·钱伯斯领导。一年后,钱伯斯出版了《数据分析的计算方法》,他的同事约翰·图基(也在贝尔实验室)出版了《探索性数据分析》。1983 年,钱伯斯出版了《数据分析的图形方法》。这些书籍为开发计算系统提供了框架,不仅允许统计学家探索、理解和分析数据,还能够传达他们的结果。我们在谈论一个由 FUBU 明星阵容组成的全明星阵容!钱伯斯的合著者包括图基的表亲保罗·A·图基和威廉·克利夫兰。克利夫兰的感知经验实验总结在两本富有洞见的书籍中,至今对数据可视化领域有所启发。在科学计算和统计学的诸多贡献中,图基开发了新颖的可视化方法,如常常被误解的箱线图和克利夫兰为非参数平滑开发了 LOESS 方法。
我们从 S 开始,因为它奠定了最终成为 R 的基础。前一段的信息告诉我们很多关于 S 和 R 的基础知识。首先,统计学家是非常直白的人(S,你懂的吧?)。这是一个非常有用的特征。其次,统计学家需要一种专门用于数据分析的 FUBU 编程语言。他们对制作通用编程语言或操作系统不感兴趣。第三,这些早期关于计算统计学和可视化的书籍简直就是教学美的杰作和精确表达的例子^(2)。尽管技术明显过时,但它们的质量仍然出色。我认为这些书籍为统计学家,尤其是 R 社区,以开放、清晰和包容的方式进行技术交流种下了种子。我相信,这是 R 社区的一大特点,具有深厚的根基。第四,早期对图形方法的强调告诉我们,S 已经关注灵活和高效的数据可视化,这对于理解数据和传达结果都是必要的。因此,S 是关于尽可能轻松地完成最重要的事情,并以真正的 FUBU 方式进行。
最初的 S 发行版在 Unix 上运行,并且是免费提供的。最终,S 成为了 S-PLUS 的实现,并获得了许可。这促使奥克兰大学的 Ross Ihaka 和 Robert Gentleman 于 1991 年发布了另一个开源免费的 S 实现。他们将这个实现命名为 R,以他们的名字首字母命名,这是对 S 名称的戏仿,并且符合使用单个字母命名编程语言的传统。R 的第一个官方稳定 beta 版本v1.0.0
于 2000 年 2 月 29 日发布。在此期间,发生了两个重要的发展。首先,建立了 CRAN(综合 R 档案网络),用于在镜像服务器上托管和归档 R 软件包。其次,还成立了 R 核心团队。这个由志愿者组成的团队(目前包括20 名成员)实现了基本的 R 功能,包括文档、构建、测试和发布,以及支持这一切的基础设施。值得注意的是,一些最初的成员仍然参与其中,包括 John Chambers、Ross Ihaka 和 Robert Gentleman。
自 2000 年 R v1.0.0
以来发生了很多事情,但迄今为止的故事应该已经让你对 R 作为 FUBU 统计计算工具的独特背景有了初步了解。在我们继续 R 的故事之前,让我们先来看看 Python。
Python 的起源
1991 年,随着 Ross Ihaka 和 Robert Gentleman 开始着手开发将成为 R 的项目,荷兰程序员 Guido van Rossum 发布了 Python。Python 的核心愿景实际上是一个人旨在解决当时常见计算问题的愿景。事实上,van Rossum 多年来被亲切地称为终身仁慈独裁者(BDFL),他在 2018 年退出 Python 领导委员会时放弃了这一头衔。
我们看到 S 是从统计学家进行数据分析的需求中产生的,R 是从开源实现的需求中产生的,那么 Python 解决了什么问题?嗯,并不是数据分析 —— 这一点要晚得多。当 Python 出现时,C 和 C++,两种低级编程语言,非常流行。Python 慢慢成为一种解释型、高级别的替代方案,特别是在 2000 年发布 Python v2
(与 R v1.0.0
同年发布)之后。Python 的明确目标是成为一种易于使用和学习的广泛采用的编程语言,具有简单的语法。在这个角色中,它取得了非常好的成功!
你会注意到,与 R 相比,Python 无处不在并且非常多才多艺。你会在网站开发、游戏、系统管理、桌面应用程序、数据科学等领域看到它的身影。当然,R 能做的远不止数据分析,但请记住,R 是 FUBU。如果 R 是 FUBU,Python 则是瑞士军刀。它无处不在,每个人都有一把,但即使它有很多工具,大多数人也只是定期使用单一工具。虽然使用 Python 的数据科学家工作在一个广阔而多样的景观中,他们倾向于找到自己的利基,并专注于他们工作所需的软件包和工作流程,而不是利用这个通用语言的所有方面。
Python 在数据科学领域的广泛流行并非完全因为其数据科学能力。我认为 Python 部分是通过作为通用编程语言的现有用途进入数据科学的。毕竟,探入门槛是成功的一半。分析师和数据科学家在与系统管理和网站开发相关的同事共享和实施脚本时会更容易,因为他们已经知道如何使用 Python 脚本。这在 Python 的广泛采纳中起到了重要作用。Python 很适合利用高性能计算,并有效地实现深度学习算法。R 曾经是,并且可能仍然是,一门较为小众和有些陌生的语言,广泛的计算世界并不真正了解它。
尽管 Python v2
于 2000 年发布,但直到 2005 年,处理数组数据的广泛采用的包才开始萌芽,即NumPy
的发布。此时,SciPy
,自 2001 年以来提供数据科学基础算法(如优化、积分、微分方程等),开始依赖于NumPy
的数据结构。SciPy
还提供了特殊的数据结构,如k维树。
一旦核心数据结构和算法的标准包问题解决了,Python 开始在科学计算中广泛应用。低级别的NumPy
和SciPy
包为高级包如pandas
(2009 年)打下了基础,提供了数据操作和数据结构(如DataFrames
)的工具。有时称为 pyData 栈,从此 Python 开始飞速发展。
语言战争开始
早期的 2000 年为后来被称为“语言之战”的舞台铺平了道路。随着 pyData 栈的形成,Python 和 R 的里程碑开始加热竞争。特别突出的四个事件。
首先,2002 年,BioConductor
成立为一个新的 R 包仓库和框架,用于处理不断增长的各种生物数据。在此之前,生物信息学家依赖于诸如 MatLab 和 Perl(以及经典的命令行工具和一些手动 Web 接口工具)。MatLab 在特定学科如神经科学中仍然受到青睐。然而,Perl 已经大多被BioConductor
取代。BioConductor
对生物信息学的影响难以估量。它不仅提供了处理远程基因序列数据库、表达数据、微阵列等的包仓库,还提供了新的数据结构来处理遗传序列。BioConductor
持续扩展并深深植根于生物信息学社区。
其次,2006 年发布了IPython
包。这是一种在交互式笔记本环境中工作的突破性方式。从 2012 年开始的各种资助后,IPython
最终在 2014 年成熟为Jupyter 项目,现在包括 JupyterLab IDE。用户经常忘记 Jupyter 是“Julia、Python 和 R”的缩写,因为它非常以 Python 为中心。笔记本已经成为 Python 中进行数据科学的主要方式,在 2018 年,Google 发布了Google Colab,一个免费的在线笔记本工具。我们将在第三章中详细探讨这个工具。
第三,2007 年,Hadley Wickham 发表了他的博士论文,其中包括两个会从根本上改变 R 景观的 R 包。第一个叫做reshape
,为后来形成的Tidyverse奠定了基础(稍后会详细讨论)。虽然reshape
早已退役,但它是我们理解数据结构如何影响我们如何思考和处理数据的第一个窥视。第二个ggplot2
则是 Leland Wilkinson 的里程碑之作《图形语法》,提供了直观、高级别的绘图,极大简化了 R 中现有的工具(关于这点详见第五章)。
最后,Python v3 在 2008 年发布。多年来,关于使用 Python 的哪个版本,v2
还是 v3
的问题一直存在。这是因为 Python v3
是不兼容的。幸运的是,自从 2020 年 Python v2
被弃用后,这个问题已经为你解决了。令人惊讶的是,尽管如此,2020 年后你仍然可以购买到预装有 Python 2 的新款 MacBook Pro,因为一些遗留脚本仍然依赖于它。所以 Python 2 仍然存在。
数据科学主导地位之争
到此时,Python 和 R 都已经有了广泛应用于各种数据科学应用的强大工具。随着所谓的“语言战争”持续进行,其他关键发展使每种语言找到了自己的领域。
Python 和 R 都包含在特定的构建中。对于 Python,这是 Anaconda 发行版,目前仍然广泛使用(详见第三章)。对于 R,则是 Revolution Analytics 公司推出的Revolution R Open。尽管他们的 R 构建从未被社区广泛接受,但该公司被微软收购,显示了对 R 语言的强大企业支持。
2011 年,Python 社区预见到机器学习的繁荣,推出了scikit-learn
包。2016 年,随着tensorflow
和keras
用于深度学习的发布,同时得到了健康的企业支持。这也突显了 Python 作为一个高级解释器坐在高性能平台上的优势。例如,你会发现 AWS lambda 用于大规模高并发编程,Numba
用于高性能计算,以及前面提到的TensorFlow用于高度优化的 C++。除了数据科学之外,Python 在部署模型方面的声誉超越了 R,这一点也不足为奇。
2011 年还见证了RStudio IDE 的发布,由同名公司推出,未来几年内,R 社区开始集中在这个工具上。在很多方面,使用 R 就意味着使用 RStudio。RStudio 在推广 R 作为适用于各种数据中心用途的编程语言方面的影响也很重要。
当所有这些都在发生的同时,R 社区的一个不断增长的部分开始转向一套包,其中许多包由 Hadley Wickham 编写或领导,这些包开始重新构思和简化典型的数据工作流程。这些包的大部分工作是标准化 R 函数语法以及输入和输出数据存储结构。最终,这套包开始被非正式地称为“Hadleyverse”。在斯坦福大学 UseR! 2016 年会议的主题演讲中,Wickham 放弃了这一说法,点燃了数字火焰以消除他的名字,并创造了“Tidyverse”这一术语。自加入 RStudio 以来,该公司一直在积极开发和推广 Tidyverse 生态系统,这显然已成为 R 中占主导地位的方言。我们将在第二章中更深入地探讨这一点。
我们可以想象 R 至少包含 2 种“范式”或“方言”。它们可以混合使用,但每种都有自己独特的风格。基础 R^(3)是大多数 R 所采用的,并且可能仍然是如此。Tidyverse
重新构想了基础 R,在一个广泛的包和函数组成的宇宙中发挥作用,这些包和函数能很好地协同工作,通常依赖于管道并且偏爱数据框。我认为BioConductor
提供了另一种方言,专注于特定学科,即生物信息学。你毫无疑问会发现一些大型包可能包含足够多的特殊性,以至于你可能认为它们是自成一体的方言,但我们不要深究这个问题。R 现在正处于阈值,一些用户只了解(或被教导)Tidyverse
的做事方式。基础 R 和 Tidyverse R 之间的区别似乎微不足道,但我见过许多新的 R 学习者努力理解为什么 Tidyverse 存在。部分原因是多年来基础 R 代码仍在积极使用且不可忽视。尽管 Tidyverse 的支持者认为这些包让初学者的生活更加轻松,但竞争的方言可能造成不必要的困惑。
我们也可以想象 Python 包含不同的方言。Python 的“原生”安装是最基本的安装,与导入 pyData 堆栈的环境运行方式不同。大多数数据科学家在 pyData 堆栈中操作,因此方言之间的混淆较少。
关于合作和社区建设的融合
有一段时间,语言之争中主导的态度似乎是“我们对他们”。看着别人电脑屏幕的不屑表情。Python 或 R 似乎最终会在数据科学领域中消失。你好,单一文化!一些数据科学家仍然支持这一点,但我们猜想你不是他们中的一员。也有一段时间,Python 和 R 试图彼此模仿,只是将工作流迁移,使语言不再重要。幸运的是,这些努力并未成功。Python 和 R 都有独特的优势;试图互相模仿似乎忽略了这一点。
今天,许多 Python 和 R 社区的数据科学家认识到这两种语言都非常优秀、有用且互补。回到前言中的一个关键点,数据科学社区已经达成了合作和社区建设的共识,这对每个人都有益处。
我们准备迎接一个新的双语数据科学家社区。挑战在于,许多使用一种语言的用户不太清楚它们如何互补,以及何时使用哪种语言。多年来已经出现了一些解决方案,但我们将在第四部分详细探讨这一点。
总结思考
到目前为止,你应该对我们在 2021 年的位置及其背后的原因有了一个很好的了解。在下一部分中,我们将向每组用户介绍一种新的语言。
最后一点说明:Python 用户称自己为 Pythonistas,这是一个非常酷的名字!R 没有真正的对应,他们也没有得到一个非常酷的动物,但这就是单字母语言的生活。R 用户通常被称为……等等……用 R 用户!(叹号可选)事实上,官方的年度会议称为 useR!(叹号是必须的),出版商 Springer 也有一系列名为同名的持续更新的很出色的书籍。我们将使用这些名称。
图 1-1 概述了本章我们重点介绍的一些重要事件,以及其他一些值得关注的里程碑。
图 1-1. Python 和 R 数据科学里程碑的时间线。
^(1) 好吧,更像是统计学家们的统计学家,但是“FSBS”听起来没有那么响亮。
^(2) 或许除了用于数据分析的计算方法,我承认我没有阅读过。
^(3) Python 用户可能不熟悉术语“基础”。这指的是语言的内置功能,没有任何额外的包安装。Python 本身在数据分析方面已经非常强大。数据科学家在 Python 中默认会导入 PyData 栈。
第二部分:双语主题 I: 学习一门新语言
在这一部分,我将介绍数据科学的两种核心语言:Python 和 R。与其他介绍不同的是,在介绍另一种语言之前,我期望读者对其中一种语言有一定的熟悉度。简而言之,我希望你带着行李来。我想建议你把行李放在门口,但行李是设计用来搬运的,所以这有点难做到。相反,让我们接纳你的行李吧!认识到 Python 和 R 的运作方式有很大不同,你可能并不总能找到一对一的翻译。那没关系!
当我教完全初学者使用 R 和 Python 时,每节课都是一个元素,是整体的基本组成部分。前四个元素是
-
函数 - 如何执行动作,即动词。
-
对象 - 如何存储信息,即名词。
-
逻辑表达式 - 如何提出问题。
-
索引 - 如何查找信息。
这四个元素之外还有许多层次,但这些是核心、基本的。一旦你对这些元素有了扎实的掌握,你就有了自行深入的工具。我的目标是让你达到这一点。因此,接下来的章节并不是对每种语言的彻底介绍。
附录 A 包含一个快速参考的 Python/R 双语词典。它将帮助你将你熟悉的代码翻译成你尚不熟悉的新语言。
第二章
如果你是一位 Python 精神的 Pythonista 希望进入 useR 精神,请从这里开始。
第三章
如果你是一位使用者希望进入 Python 精神的 useR,请从这里开始。
一旦你熟悉了你的新语言,请继续学习第三部分,以了解在什么情况下使用每种语言最为适合。
第二章:R for Pythonistas
Rick J. Scavetta
欢迎,勇敢的 Python 程序员,来到 useR 的世界^(1)!在本章中,我们的目标是介绍 R 的核心特性,并试图解决你在学习过程中可能遇到的一些困惑。因此,提到我们不打算做什么是有用的。
首先,我们不是为天真的数据科学家编写的。如果你想从零开始学习 R,有很多优秀的资源可供选择;太多了,无法一一列举。我们鼓励你去探索它们,并选择适合你需求和学习风格的资源。在这里,我们将提出可能会让完全的新手感到困惑的话题和问题。我们会绕道解释一些话题,希望能够帮助友好的 Python 程序员更轻松地适应 R。
其次,这不是一本双语词典,你可以在附录 A 中找到它,但是没有上下文,它并不是真正有用的。在这里,我们希望带领你踏上一段探索和理解的旅程。我们希望你能够对 R 有一种感觉,以便开始思考 R,变得双语。因此,为了叙述的完整性,我们可能会在写给完全新手的时候介绍一些项目远比其他时候晚得多的东西。尽管如此,我们希望当你需要在新语言中执行熟悉的任务时,能够回到这一章。
第三,这不是一本全面的指南。一旦你掌握了 R,你就会发现探索更深层次的语言以满足你特定需求的乐趣无穷。正如我们在本书的第一部分中提到的那样,R 社区是多样化的,友好的,欢迎你的——而且乐于助人!我们相信这是少数不那么技术兄弟的文化之一。要了解社区的情况,你可以在 Twitter 上关注 #rstats
。
运行 R
要在本章中完成练习,你可以在 RStudio Cloud 中访问 R,也可以在本地安装 R 和 RStudio。RStudio Cloud 是一个提供访问 R 实例(通过 RStudio IDE)的平台,它允许你上传自己的数据并分享项目。我们将在接下来的段落中介绍这两种方法。
要使用 RStudio Cloud,请在http://rstudio.cloud/ 上创建一个账户,然后转到我们的公开项目。确保在你的工作空间中保存项目的副本,这样你就有了自己的副本,你会在页眉中看到链接。
你的 RStudio 会话应该看起来像图 2-1 中的样子。打开ch02-r4py/r4py.R
,就这样!你已经准备好跟着所有的例子了。要执行命令,请按下ctrl
+ enter
(或 cmd
+ enter
)。
图 2-1. 我们在 RStudio Cloud 中的项目。
要在本地运行 R,您会发现它可以在 Anaconda 发行版中使用,如果您使用的是该发行版,否则您可以直接安装它。首先从https://www.r-project.org/为您的操作系统下载并安装 R。R v4.0
于 2020 年 6 月发布,与 Python v3.x 相比,它具有向后兼容性,但也有一些显著的例外。我们假设您至少正在运行 R 4.0.0
:“再次起飞”。每个发布版都得到一个受花生漫画(经典的漫画和电影系列,由查理·布朗,史努比等主演)启发的名称。我认为这是一个不错的个人化的触摸。接下来,从https://rstudio.com/安装 RStudio Desktop IDE。
最后,设置一个要处理的项目。这与虚拟环境有些不同,我们稍后会讨论。有两种典型的方式可以使用预先存在的文件创建项目。
首先,如果您使用 git
,您会很高兴地知道 RStudio 也是一个基本的 git
图形用户界面客户端。在 RStudio 中,选择 File > New project > Version Control > Git
并输入存储库 URL [*https://github.com/moderndatadesign/PyR4MDS*](https://github.com/moderndatadesign/PyR4MDS)
。项目目录名称将自动使用存储库名称。选择您希望存储存储库的位置,然后单击“创建项目”。
其次,如果您不使用 git
,您可以直接下载并解压来自https://github.com/moderndatadesign/PyR4MDS的存储库。在 RStudio 中,选择 File > Existing Directory
并导航到下载的目录。在该目录中将创建一个新的 R 项目文件 *.Rproj
。
您的 RStudio 会话应该看起来像图 图 2-2。打开 ch02-r4py/r4py.R
,就这样!您已准备好跟随所有示例进行操作。要执行命令,请按 ctrl
+ enter
(或 cmd
+ enter
)。
图 2-2. 我们在 RStudio 中的项目。
项目和包
我们可以通过使用内置数据集开始探索 R,并直接深入 Tidyverse(在第一章介绍),但我想稍作停顿,深呼吸,并从头开始我们的故事。让我们先读取一个简单的 csv 文件。为此,我们将使用 ggplot2
包中实际上已经可用的数据集。对于我们的目的,我们对实际分析不太关心,而是关心它在 R 中的执行方式。我已在书籍的存储库中提供了数据集作为文件。
如果您正确设置了项目(参见上文),您只需要执行以下命令即可。如果此命令不起作用,请不要担心,我们很快会回到这个问题。
diamonds <- read.csv("ch02-r4py/data/diamonds.csv")
就像在 Python 中一样,单引号 (''
) 和双引号 (""
) 是可以互换的,尽管双引号更受欢迎。
现在,文件已导入并作为对象出现在你的全局环境中,你的自定义对象也在其中。你会注意到的第一件事是 RStudio 的环境面板将显示该对象并提供一些摘要信息。这种可爱而简单的设计类似于 VScode 的 Jupyter 笔记本扩展(参见 Chapter 3),它也允许你查看你的环境。虽然这在 RStudio 中是一个标准功能,但在 Python 编写脚本时查看对象列表,或者许多其他语言中,这并不常见。点击对象名称旁边的小蓝箭头将显示文本描述(参见图 Figure 2-3)。
图 2-3. 数据框的下拉菜单。
点击名称将在类似 Excel 的查看器中打开它(参见图 Figure 2-4)。
图 2-4. 表格视图中的数据框。
注意
RStudio 的查看器比 Excel 更好,因为它只在内存中加载你屏幕上看到的内容。你可以搜索特定文本并筛选数据,因此这是一个方便的工具,用来窥探你的数据。
尽管这些功能很好,一些用户认为它们有点过于 GUI^(2) 且有点过于不够 IDE^(3)。Python 爱好者大多数会同意,有些人因此批评 RStudio 的用户体验。我部分同意,因为我看到它可能会鼓励一些不良实践。例如,要导入数据集,你也可以点击“导入数据集…”按钮。如果你在解析文件结构时遇到困难,这可能很方便,但会导致未记录的、不可重现的操作,这非常令人沮丧,因为脚本/项目将不是自包含的。执行导入文件的命令将在控制台中执行,并在历史面板中可见,但除非你明确复制它,否则它不会出现在脚本中。这会导致环境中存在脚本未定义的对象。然而,请记住,RStudio 不等同于 R。你可以在其他文本编辑器中使用 R(例如 ESS(“emacs speaks statistics”)扩展的 emacs)。
如果你无法使用上述命令导入数据,要么(i)该文件不存在于该目录中,要么(ii)你正在错误的 工作目录 中工作,后者更有可能。你可能会被诱导写出像这样糟糕的东西:
diamonds <- read.csv("ch02-r4py/data/diamonds.csv")
在使用 Python 的虚拟环境时,避免使用硬编码路径会更加熟悉。像我们之前所做的那样使用相对路径,确保我们的文件目录包含所有必要的数据文件。工作目录和项目都不是虚拟环境,但它们仍然非常方便,所以让我们来了解一下吧!
工作目录是 R 查找文件的第一个地方。当您使用 R 项目时,工作目录就是您拥有 *.Rproj
文件的位置。因此,ch02-r4py
是我们工作目录中的一个子目录。工作目录的名称或位置无关紧要。只要在 RStudio 中打开项目(*.Rproj
文件),整个项目可以移动到计算机上的任何位置,它依然能够正常工作。
警告
如果您没有使用 R 项目,那么您的工作目录可能是您的主目录,在 RStudio 中显示为 project: (None)
。这很糟糕,因为您将不得不指定文件的完整路径,而不仅仅是项目中的子目录。您会发现命令 getwd()
获取和 setwd()
设置工作目录在许多过时的教程中。请不要使用这些命令!它们会导致硬编码完整文件路径的相同问题。
让我们回到我们的命令 diamonds <- read.csv("ch02-r4py/data/diamonds.csv")
。您已经注意到一些会让经验丰富的 Python 爱好者感到困惑或激怒的事情。有三件事情特别显眼。
首先,请注意,在 R 中通常使用 <-
作为赋值操作符是普遍且甚至更受欢迎的。您可以像在 Python 中那样使用 =
,事实上您会看到一些有经验的用户这样做,但 <-
更加明确,因为 =
也用于在函数调用中为参数分配值,而 Python 爱好者都喜欢明确!
注
<-
赋值操作符实际上是一个传统操作符,起源于标准化前的 QWERTY 键盘,其中 <-
并不意味着将光标向左移动一个空格,而是字面上使 <-
出现。
其次,请注意函数名称是 read.csv()
,不,这不是打字错误。csv()
不是对象 read
的方法,也不是 read
模块 的函数。如果这是一个 Python 命令,这两种解释都是完全可以接受的。在 R 中,除了一些明显的例外,.
并不具有特殊意义。如果您习惯于更面向对象的语言,其中 .
是一个特殊字符,这有点令人恼火。
最后,请注意,我们没有初始化任何包来完成此任务。read.*()
函数变体是基本 R 的一部分。有趣的是,如果这些函数不能满足您的需求,还有更新更便捷的读取文件的方法。例如 read_csv()
函数在 readr
包中。我们知道您很兴奋看到那个 _
!
一般来说,当您看到带有 .
的简单函数时,这些都是旧的基础 R 函数,创建时没有人担心名称中包含 .
会引起混淆。来自较新 Tidyverse 包的函数,例如 readr
,倾向于使用 _
(见第一章)。它们基本上做同样的事情,但稍作调整,使它们更加用户友好。
让我们看看如何使用readr
实现这一点。就像在 Python 中一样,你需要安装这个包。这通常是在 R 控制台中直接完成的,R 中没有pip
的等价物。
使用以下命令:
install.packages("tidyverse")
注意
在 RStudio 中,你可以通过在右下角面板中的“Packages”面板上点击“Install”按钮来安装包。输入tidyverse
,确保选中“install all dependencies”框,并点击 OK。如果你选择这种方式,请不要点击已安装包名称旁边的复选框。这将初始化包,但不会将其记录在你的脚本中。
默认情况下,这将从 CRAN 安装包及其依赖项,CRAN 是官方 R 包的存储库。官方包经过质量控制,并托管在全球镜像服务器上。第一次这样做时,系统将要求你选择一个镜像站点进行安装。大部分情况下,选择哪一个并不重要。在核心 Tidyverse 包和它们的所有依赖项安装过程中,你会看到很多红色文本。这主要是一种方便的方式,一次性安装许多有用的包。
安装包最常见的问题是在包目录中没有写权限。这会提示你创建一个个人图书馆。你可以随时通过以下方式检查你的包安装在哪里
.libPaths()
[1] "/Library/Frameworks/R.framework/Versions/4.0/Resources/library"
如果你有一个个人图书馆,它将显示在第二个位置。
注意
与 Pythonistas 相比,他们倾向于使用虚拟环境,而 R 用户通常只安装一次包,使其在整个系统范围内可用。在尝试为 R 实现项目特定库的解决方案多次失败后,当前的首选是renv
包,即R 环境。
与 Python 类似,在安装包后,需要在每个新的 R 会话中初始化它。当我们说初始化或加载一个包时,我们实际上是指“使用library()
函数加载一个已安装的包,然后附加它到命名空间,即全局环境”。所有你的包组成你的库,因此使用library()
。可以使用library(tidyverse)
加载 Tidyverse 的核心套件包。这是常见的做法,大部分时间没有问题,但你可能希望养成只加载实际需要的包而不是不必要地填充环境的习惯。让我们从包含read_csv()
函数的readr
开始。
# R
library(readr)
这相当于:
# Python equivalent
import readr
尽管 R 使用面向对象编程(OOP),但大部分时间是在后台操作,因此你永远不会看到像这样的包的奇怪别名:
import readr as rr
这在 R 中只是一个外来概念。在附加包之后,该包中的所有函数和数据集都可在全局环境中使用。
警告
这让我想起另一个可能会漂浮的遗留函数。你必须绝对避免使用 attach()
(以及大部分情况下的配对函数 detach()
)。这个函数允许你将一个对象 attach 到你的全局环境中,就像我们附加一个包一样。因此,你可以直接调用对象中的元素,而不必显式指定对象名称,就像我们在不必每次显式调用包名称的情况下调用包中的函数一样。这种方法不再流行的原因是你可能会有许多想要访问的数据对象,因此可能会出现名称冲突问题(即对象 masking)。另外,这种方法也不够明确。
在继续之前,我想解决另一个加载包的问题。你经常会看到:
require(readr)
require()
函数将加载一个已安装的包,并基于成功与否返回 TRUE/FALSE
。这对于测试包是否存在很有用,因此应该仅在必要时使用。大多数情况下,你应该使用 library()
。
好了,让我们再次读取我们的数据集,这次使用 read_csv()
进行一些简单的比较。
> diamonds_2 <- read_csv("R4Py/diamonds.csv")
Parsed with column specification:
cols(
carat = col_double(),
cut = col_character(),
color = col_character(),
clarity = col_character(),
depth = col_double(),
table = col_double(),
price = col_double(),
x = col_double(),
y = col_double(),
z = col_double()
)
你会注意到,我们得到了一个更详细的描述发生了什么。
正如我们之前提到的,Tidyverse 的设计选择往往比其更新的老流程更加用户友好。此输出告诉我们表格数据的列名及其类型(参见 Table 2-2)。
R 语言当前的趋势是使用蛇形命名法,单词间用下划线(“_”)分隔,并且只使用小写字母。尽管在 R 中一直以来都存在风格指南的不严格遵循,但是 Advanced R 书籍 提供了很好的建议。Google 也试图推广一个 R 风格指南,但似乎社区对此并不十分严格。这与 Python 的 PEP 8 风格指南形成对比,后者由 Python 创始人 Guido van Rossum 在 Python 早期发布时编写。
Tibbles 的胜利
到目前为止,我们已经两次导入了我们的数据,使用了两种不同的命令。这样做是为了让你看到 R 在幕后的工作方式以及 Tidyverse 与基础包的一些典型行为。我们已经提到,你可以点击环境查看器中的对象来查看它,但通常也会将其打印到控制台。你可能会试图执行:
> print(diamonds)
但 print()
函数在特定情况下是不必要的,比如在 for
循环内。与 Jupyter 笔记本类似,你可以直接执行对象名称,例如:
> diamonds
这将把对象打印到控制台。我们不会在这里复制它,但如果你执行上面的命令,你会注意到这不是一个好的输出!确实,人们会想知道为什么默认输出在交互模式下允许如此多的内容打印到控制台。现在尝试使用read_csv()
读取的数据帧:
> diamonds_2
# A tibble: 53,940 x 10
carat cut color clarity depth table price x y z
<dbl> <chr> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 0.23 Ideal E SI2 61.5 55 326 3.95 3.98 2.43
2 0.21 Premium E SI1 59.8 61 326 3.89 3.84 2.31
3 0.23 Good E VS1 56.9 65 327 4.05 4.07 2.31
4 0.290 Premium I VS2 62.4 58 334 4.2 4.23 2.63
5 0.31 Good J SI2 63.3 58 335 4.34 4.35 2.75
6 0.24 Very Good J VVS2 62.8 57 336 3.94 3.96 2.48
7 0.24 Very Good I VVS1 62.3 57 336 3.95 3.98 2.47
8 0.26 Very Good H SI1 61.9 55 337 4.07 4.11 2.53
9 0.22 Fair E VS2 65.1 61 337 3.87 3.78 2.49
10 0.23 Very Good H VS1 59.4 61 338 4 4.05 2.39
# … with 53,930 more rows
哇!这比默认的基本 R 版本要好得多。我们有一个整齐的小表,第一行是列名,下面是<>
中的数据类型的 3 个字母代码。我们只看到前 10 行,然后有一个告诉我们有多少行未显示的注释。如果我们的屏幕有太多列,我们将在底部看到它们列出来。试试吧,将你的控制台输出设置得非常窄,然后再次执行命令:
# A tibble: 53,940 x 10
carat cut color clarity
<dbl> <chr> <chr> <chr>
1 0.23 Ideal E SI2
2 0.21 Premium E SI1
3 0.23 Good E VS1
4 0.290 Premium I VS2
5 0.31 Good J SI2
6 0.24 Very G… J VVS2
7 0.24 Very G… I VVS1
8 0.26 Very G… H SI1
9 0.22 Fair E VS2
10 0.23 Very G… H VS1
# … with 53,930 more rows,
# and 6 more variables:
# depth <dbl>, table <dbl>,
# price <dbl>, x <dbl>,
# y <dbl>, z <dbl>
基本的 R 已经对探索性数据分析(EDA)非常好了,但这是下一个级别的便利。那么发生了什么?实际理解这一点非常重要,但首先我们想要强调另外两个有趣的点。
首先,请注意,我们不需要加载整个readr
包来访问read_csv()
函数。我们可以省略library(readr)
,直接使用:
> diamonds_2 <- readr::read_csv("R4Py/diamonds.csv")
双冒号操作符::
用于访问包中的函数。类似于:
from pandas import read_csv
当用户知道他们只需要一个非常特定的函数来自一个包时,或者两个包中的函数可能会冲突时,他们会使用::
。这样可以避免将整个包附加到其命名空间中。
其次,这是我们第一次在 R 中看到实际数据,并且我们立即可以看到编号从 1 开始!(为什么不呢?)。
注意
顺便说一下,打印对象到屏幕上时经常会看到整个表达式周围有圆括号。这只是执行表达式并将对象打印到屏幕上的意思。
(aa <- 8)
它大多数时候只是混乱了命令。除非有必要,否则就直接调用对象。
aa <- 8
aa
此外,只需注释掉(在 RStudio 中使用 ctrl+shift+c)打印行,而不是回去删除所有这些额外的括号,会更容易一些。
好的,让我们来看看这里发生了什么。为什么在打印到控制台时diamonds
和diamonds_2
看起来如此不同。回答这个问题将帮助我们理解一下 R 如何处理对象。为了回答这个问题,让我们看看这些对象的类:
class(diamonds)
[1] "data.frame"
class(diamonds_2)
[1] "spec_tbl_df" "tbl_df" "tbl" "data.frame"
你会从pandas.DataFrame
中熟悉data.frame
(好吧,我们能承认pandas
的DataFrame
只是 R 中data.frame
的一个 Python 实现吗?)。但使用 Tidyverse 的read_csv()
函数生成了一个具有三个额外类的对象。在这里要提到的两个是子类tbl_df
和类tbl
,这两者共同定义了一个tibble(因此tbl
),它具有data.frame
结构的特性tbl_df
。
Tibbles 是 Tidyverse 的核心特性,在基本的 R 对象上有很多优势。例如,打印到控制台。回想一下,调用对象名称只是调用 print()
的快捷方式。print()
反过来又有一个处理数据框的方法,现在我们已经加载了 readr
包,它现在有一个处理 tbl_df
类对象的方法。
因此,在后台,我们看到 OOP 原则隐式地处理对象类并调用适用于给定类的方法。方便!令人困惑吗?隐式!我能理解为什么 Python 程序员会感到恼火,但一旦你克服了这个问题,你会发现你可以轻松地继续工作,而不会遇到太多麻烦。
有关类型和探索的一些说明
让我们更深入地研究一下我们的数据,看看 R 是如何存储和处理数据的。数据框是一个二维异构数据结构。听起来很简单,但让我们进一步解释一下。
表 2-1. 示例数据框
名称 | 维度数 | 数据类型 |
---|---|---|
向量 | 1 | 同质 |
列表 | 1 | 异构 |
数据框 | 2 | 异构 |
矩阵 | 2 | 同质 |
数组 | n | 同质 |
向量是最基本的数据存储形式。它们是一维的和同质的。也就是说,一个接一个地存储每个元素,每个元素都是相同类型的。这就像是一个一维的 numpy
数组,由标量组成。我们不在 R 中引用标量,那只是一个长度为 1 的向量。R 中有许多 类型,以及 4 个常用的“用户定义的原子向量类型”。术语“原子”已经告诉我们,这已经是我们在 Table 2-2 中找到的最基本的东西了。
表 2-2. 数据类型
类型 | 数据框简写 | Tibble 简写 | 描述 |
---|---|---|---|
逻辑 | logi | 二进制 TRUE/FALSE, T/F, 1/0 | |
整数 | int | 整数从 [-Inf,Inf] | |
双精度 | num | 实数从 [-Inf,Inf] | |
字符串 | chr | 所有字母数字字符,包括空格。 |
另外两种较不常见的用户定义的原子向量类型是 raw
和 complex
。
向量是基本的构建块。关于向量有几件事情需要注意,所以让我们先搞清楚这些再回到数据科学的工作马车,那就是心爱的数据框。
根据信息内容的递增顺序排列了 Table 2-2 中列出的四种用户定义的原子向量类型。当你创建一个向量时,R 会尝试找到能够包含该向量中所有信息的最低信息内容类型。例如,逻辑:
> a <- c(TRUE, FALSE)
> typeof(a)
[1] "logical"
logical
是 R 中的 bool
等价物,但很少被称为布尔或二进制。另外,请注意 T
和 F
本身不是 R 中的保留术语,因此不建议用于逻辑向量,尽管它们是有效的。请使用 TRUE
和 FALSE
。让我们来看一下数字:
> b <- c(1, 2)
> typeof(b)
[1] "double"
> c <- c(3.14, 6.8)
> typeof(c)
[1] "double"
R 将根据需要自动在双精度和整数之间进行转换。主要使用双精度进行数学运算,这在数据框中用 numeric
的简写显示出来。除非您明确需要将数字限制为真正的整数,否则 numeric/double 就足够了。如果确实需要将值限制为整数,可以使用 as.*()
函数强制转换为特定类型,或者使用 L
后缀指定数字必须是整数。
> b <- as.integer(c(1, 2))
> typeof(b)
[1] "integer"
> b <- c(1L, 2L)
> typeof(b)
[1] "integer"
字符串是 R 中的字符串版本。你可能知道 Python 中的 str
,这在 R 中是一个常见的函数 str()
,它显示对象的结构。字符在 R 中也经常被称为字符串,包括在参数和包名称中,这是一个不幸的不一致性。
> d <- c("a", "b")
> typeof(d)
[1] "character"
将它们放在使用 data.frame()
创建的基本数据框中,或者使用使用 tibble()
创建的更近期开发的 tibble 中,得到:
my_tibble <- tibble(a = c(T, F),
b = c(1L, 2L),
c = c(3.14, 6.8),
d = c("a", "b"))
my_tibble
# A tibble: 2 x 4
a b c d
<lgl> <int> <dbl> <chr>
1 TRUE 1 3.14 a
2 FALSE 2 6.8 b
注意,由于它是一个 tibble,我们从 print()
中得到了漂亮的输出。当我们查看结构时,我们会看到一些令人困惑的特性:
> str(my_tibble)
tibble [2 × 4] (S3: tbl_df/tbl/data.frame)
$ a: logi [1:2] TRUE FALSE
$ b: int [1:2] 1 2
$ c: num [1:2] 3.14 6.8
$ d: chr [1:2] "a" "b"
str()
是一个经典的基础包函数,提供一些基本的输出,类似于在环境面板中点击对象名称旁边的显示箭头时看到的内容。第一行给出了对象的类别(我们上面已经看到了)。S3 指的是此对象使用的特定面向对象编程系统,本例中是最基本和最宽松的面向对象编程系统。
或者,我们也可以使用 Tidyverse 中 dplyr
包的 glimpse()
函数。
> library(dplyr)
> glimpse(my_tibble)
Rows: 2
Columns: 4
$ a <lgl> TRUE, FALSE
$ b <int> 1, 2
$ c <dbl> 3.14, 6.80
$ d <chr> "a", "b"
注意,表 2-2 还指出了缩写 num
,在 glimpse()
的输出中并没有出现。这指的是“numeric”类别,它可以是双精度浮点数或整数类型。
上述示例告诉我们,data.frame
是一个异构的、二维的、每个长度相同的同质一维向量集合。我们将解释为什么 R 在下面打印所有这些美元符号(不,这与你的工资无关!)
命名(内部)事物
我们已经提到,蛇形命名法是目前 R 中命名对象的当前趋势。但是,命名数据框中的列名则完全不同,因为我们只是从源文件的第一行继承了名称。基础 R 中的数据框,例如使用 read.*()
函数系列获取的或者使用 data.frame()
函数手动创建的,不允许使用任何“非法”字符。非法字符包括所有空格和所有在 R 中保留的字符,例如:
-
算术运算符(
+
,-
,/
,*
, 等), -
逻辑运算符(
&
,|
, 等), -
关系运算符(
==
,!=
,>
,<
, 等) -
括号 (
[
,(
,{
,<
及其关闭符)
此外,尽管它们可以包含数字,但不能以数字开头。让我们看看会发生什么:
# Base package version
data.frame("Weight (g)" = 15,
"Group" = "trt1",
"5-day check" = TRUE)
Weight..g. Group X5.day.check
1 15 trt1 TRUE
所有非法字符都被替换为。
!我知道,没错吧?R 真的很喜欢嘲笑你这些 OOP 狂热者!此外,以数字开头的任何变量现在都以X
开头。
那么如何导入没有标题的文件?
> diamonds_base_nohead <- read.csv("ch02-r4py/data/diamonds_noheader.csv", header = F)
> names(diamonds_base_nohead)
[1] "V1" "V2" "V3" "V4" "V5" "V6" "V7" "V8" "V9" "V10"
在 base R 中,如果没有任何标题,给定的名称是V
,表示“变量”,后面是该列的数字。
通过readr::read_*()
函数中的一个或使用tibble()
创建的相同文件将保留非法字符!这似乎微不足道,但实际上这是对 Tidyverse 的严肃批评,如果你开始干预别人的脚本,这是需要密切关注的问题。让我们看看:
> tibble("Weight (g)" = 15,
+ "Group" = "trt1",
+ "5-day check" = TRUE)
# A tibble: 1 x 3
`Weight (g)` Group `5-day check`
<dbl> <chr> <lgl>
1 15 trt1 TRUE
注意到用于列Weight (g)
和5-day check
的反引号了吗?现在您需要使用它来转义非法字符。也许这会使命令更具信息性,因为您有完整的名称,但无论如何,您可能仍希望保持简短和具有信息性的列名。关于单位(例如重量的克)的信息是不必要的附加信息,应包含在数据集的图例中。
不仅如此,无头数据集的名称也不同:
> diamonds_tidy_nohead <- read_csv("ch02-r4py/data/diamonds_noheader.csv", col_names = F)
> names(diamonds_tidy_nohead)
[1] "X1" "X2" "X3" "X4" "X5" "X6" "X7" "X8" "X9" "X10"
不再使用V
,而是使用X
!这将我们带回到 Tidyverse 作为 R 中的一个独特方言。如果您继承了一个完全基于 base R 的脚本,如果您随意开始添加 Tidyverse 函数,那么您会遇到麻烦。这就像在柏林面包店要求柏林人^(5)一样!
列表
列表是另一种常见的数据结构,但它们与 Python 列表并不完全相同,因此命名可能会令人困惑。事实上,在我们非常短暂的 R 之旅中,我们已经遇到了列表。这是因为``data.frame是
list`类型的特定类。是的,你没听错。
> typeof(my_tibble)
[1] "list"
表 2-1 告诉我们,列表是一种一维的、异质的对象。这意味着这个一维对象中的每个元素可以是不同类型的,实际上列表不仅可以包含向量,还可以包含其他列表、数据框、矩阵等等。如果每个元素是相同长度的向量,我们最终得到的是类data.frame
的表格数据。非常方便,对吧?通常,您会遇到列表作为统计测试的输出,让我们来看看。
PlantGrowth
数据框是 R 中的内置对象。它包含两个变量(即列表中的元素,也称为表格数据中的列):weight
和group
。
> glimpse(PlantGrowth)
Rows: 30
Columns: 2
$ weight <dbl> 4.17, 5.58, 5.18, 6.11, 4.50, 4.6…
$ group <fct> ctrl, ctrl, ctrl, ctrl, ctrl, ctr…
数据集描述了 30 个观察值(即单个植物,也称为表格数据中的行)在groups
描述的三种条件下生长的干燥植物weight
(以克计,感谢数据图例)。方便的glimpse()
函数并不显示这三组,但经典的str()
函数却能显示:
> str(PlantGrowth)
'data.frame': 30 obs. of 2 variables:
$ weight: num 4.17 5.58 5.18 6.11 4.5 4.61 5.17 4.53 5.33 5.14 ...
$ group : Factor w/ 3 levels "ctrl","trt1",..: 1 1 1 1 1 1 1 1 1 1 ...
如果您对<fct>
和Factor w/ 3 levels
感到紧张,那么请耐心等待,我们将在讨论列表之后讨论这个问题。
好的,让我们进行一些测试。我们可能想要为weight
由group
描述定义一个线性模型:
pg_lm <- lm(weight ~ group, data = PlantGrowth)
lm()
是在 R 中定义线性模型的基础性和灵活性函数。我们的模型以公式符号书写,其中weight ~ group
是y ~ x
。你会认出~
作为统计学中“由...描述”的标准符号。输出是一个类lm
的list
类型:
> typeof(pg_lm)
[1] "list"
> class(pg_lm)
[1] "lm"
在这里,有两件事情我们想要提醒你并进行拓展。
首先,记住我们提到过数据框是相同长度的向量集合?现在我们看到,这只是意味着它是一个特殊类型的列表,其中每个元素都是相同长度的向量。我们可以使用$
符号访问列表中的命名元素:
> names(PlantGrowth)
[1] "weight" "group"
> PlantGrowth$weight
[1] 4.17 5.58 5.18 6.11 4.50 4.61 5.17 4.53 5.33 5.14 4.81 4.17 4.41 3.59
[15] 5.87 3.83 6.03 4.89 4.32 4.69 6.31 5.12 5.54 5.50 5.37 5.29 4.92 6.15
[29] 5.80 5.26
注意它的打印方式,沿着一行,并且每行的开头都以[]
开头,其中包含一个索引位置。(我们已经提到过 R 从 1 开始索引了,对吗?)在 RStudio 中,键入$
后,你将得到一个列名的自动完成列表。
我们也可以使用相同的符号访问列表中的命名元素:
> names(pg_lm)
[1] "coefficients" "residuals" "effects" "rank"
[5] "fitted.values" "assign" "qr" "df.residual"
[9] "contrasts" "xlevels" "call" "terms"
[13] "model"
你可以看到列表是存储统计测试结果的一种很好的方式,因为我们有很多不同类型的输出。例如系数
:
> pg_lm$coefficients
(Intercept) grouptrt1 grouptrt2
5.032 -0.371 0.494
是一个命名的 3 元素长的数值向量。(虽然它的元素被命名了,但是对于原子向量,$
运算符无效,但是我们当然还有其他的技巧——见下面的用[]
进行索引)。我们没有深入讨论细节,但你可能意识到,鉴于我们的数据,我们期望在我们的模型中有三个系数(估计)。
考虑残差
:
> pg_lm$residuals
1 2 3 4 5 6 7 8 9 10
-0.862 0.548 0.148 1.078 -0.532 -0.422 0.138 -0.502 0.298 0.108
11 12 13 14 15 16 17 18 19 20
0.149 -0.491 -0.251 -1.071 1.209 -0.831 1.369 0.229 -0.341 0.029
21 22 23 24 25 26 27 28 29 30
0.784 -0.406 0.014 -0.026 -0.156 -0.236 -0.606 0.624 0.274 -0.266
它们存储在一个命名的 30 元素长的数值向量中(记住我们有 30 个观测)。所以列表对于存储异构数据非常方便,在 R 中你会经常见到它们,尽管 Tidyverse 致力于数据框架及其变体。
其次,记住我们提到.
在大多数情况下并没有特殊含义。嗯,这是一个例外,.
确实有含义的一个例外。可能最常见的用法是在定义模型时指定所有。在这里,除了weight
列外,PlantGrowth
只有一个其他列,我们可以写成:
lm(weight ~ ., data = PlantGrowth)
注意
关于变量类型的说明。通过使用y ~ x
的公式,我们在说 x 是“独立”的或者“预测”变量,而 y 则是依赖于 x,或者是对预测的“响应”。
这并不是真正必要的,因为我们这里只有一个自变量,但在某些情况下很方便。ToothGrowth
数据集有一个类似的实验设置,但我们在两种条件下测量牙齿生长的长度,一种是特定的补充剂(supp
)和其剂量(dose
)。
lm(len ~ ., data = ToothGrowth)
# is the same as
lm(len ~ supp + dose, data = ToothGrowth)
但像以往一样,明确地表达有其优点,比如定义更精确的模型:
lm(len ~ supp * dose, data = ToothGrowth)
你能发现两个输出之间的区别吗?指定相互作用是用*
进行的^(6)
关于因子的事实
好吧,我们在继续之前需要澄清的最后一件事是因素
的现象。因素类似于 Python 中的 pandas category
类型。它们是 R 中一个精彩且有用的类。在大多数情况下,它们存在,你不需要担心它们,但要注意,因为它们的使用和误用会使你的生活成为梦想或悲惨。让我们来看看。
名称“因素”在统计学术语中非常常见,我们可以将它们称为分类变量,就像 Python 一样,但你也会看到它们被称为定性和离散变量,无论是在教科书中还是在特定的 R 包中,比如RColorBrewer
和ggplot2
。虽然这些术语都指的是相同类型的变量,但当我们在 R 中说因素时,我们指的是整数类型的类。这就像data
.frame 是列表类型的类一样。观察:
> typeof(PlantGrowth$group)
[1] "integer"
> class(PlantGrowth$group)
[1] "factor"
你可以轻易地识别因素,因为在str()
的输出(见上文)和普通的向量格式化中,级别会被说明:
> PlantGrowth$group
[1] ctrl ctrl ctrl ctrl ctrl ctrl ctrl ctrl ctrl ctrl
[11] trt1 trt1 trt1 trt1 trt1 trt1 trt1 trt1 trt1 trt1
[21] trt2 trt2 trt2 trt2 trt2 trt2 trt2 trt2 trt2 trt2
Levels: ctrl trt1 trt2
级别是统计学家们称为“组”的名称。另一个特征是,虽然我们有字符,但它们没有用引号括起来!这非常奇怪,因为我们实际上可以将它们视为字符,即使它们是整数类型的(参见表 2-2)。您可能会对使用dput()
查看对象的内部结构感兴趣。在这里,我们可以看到我们有一个整数向量c(1L, ...)
和两个属性,标签和类。
> dput(PlantGrowth$group)
structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L,
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L,
3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L),
.Label = c("ctrl", "trt1", "trt2"),
class = "factor")
标签定义了因素中每个级别的名称,并映射到整数,1
是ctrl
,依此类推。所以当我们打印到屏幕上时,我们只看到名称,而不是整数。这通常被接受为是从内存昂贵的时代遗留下来的用例,当时多次保存整数而不是潜在的长字符向量是有意义的。
到目前为止,我们看到的唯一一种因素实际上是描述名义变量的(没有顺序的分类变量),但我们也有一个很好的解决方案来处理序数变量。看看来自钻石数据集的这个变量:
> diamonds$color
[1] E E E I J J I H E H ..
Levels: D < E < F < G < H < I < J
级别有一定的顺序,即 D 在 E 之前,依此类推。
如何找… 东西
好吧,到目前为止,我们已经看到了 R 如何存储数据以及您需要牢记的各种微妙之处,特别是可能会让 Python 开发者出错的事情。让我们继续逻辑表达式和索引,也就是说:如何找… 东西?
逻辑表达式是关系运算符的组合,它们询问是/否的比较问题,以及逻辑运算符,它们组合这些是/否的问题。
让我们从一个向量开始:
> diamonds$price > 18000
[1] FALSE FALSE FALSE FALSE FALSE FALSE
...
这只是问我们的钻石中哪些比$18,000 更贵。这里有三个关键要记住的事情。
首先,较短对象的长度,这里是未分配的数值向量18000
(1 个元素长),将“循环使用”整个较长向量的长度,这里是通过$
符号访问的diamonds
数据框中的price
列(53,940 个元素)。在 Python 中,您可能将其称为广播,当使用numpy
数组时,并将向量化作为一个单独的函数。在 R 中,我们简单地将这两者都称为向量化,或向量循环。
第二,这意味着输出向量的长度与最长向量的长度相同,这里是53,940
个元素。
第三,每当你看到关系运算符或逻辑运算符时,你知道输出向量将始终是逻辑向量。记住逻辑值是TRUE/FALSE
,不是像斯波克先生那样的逻辑。
如果你想结合问题,你需要结合两个完整的问题,比如真的昂贵和小的钻石(高雅!):
> diamonds$price > 18000 & diamonds$carat < 1.5
[1] FALSE FALSE FALSE FALSE FALSE FALSE
...
请注意,上述所有三个关键点仍然成立。当我介绍原子向量类型时,我没有提到逻辑也可以被定义为1
和0
。这意味着我们可以对逻辑向量进行数学运算,这非常方便。我们有多少昂贵的小钻石?
> sum(diamonds$price > 18000 & diamonds$carat < 1.5)
[1] 9
(老实说还不够)。它们代表我的数据集的比例是多少?只需除以总观察数即可。
> sum(diamonds$price > 18000 & diamonds$carat < 1.5)/nrow(diamonds)
[1] 0.0001668521
所以这是询问和结合问题。让我们来看一下使用[]
进行索引。你已经很熟悉[]
,但我们觉得在 R 中它们更直接。以下是总结:
表 2-3. 索引
用途 | 数据对象 | 结果 |
---|---|---|
xx[i] | 向量 | 只包含i 个元素的向量 |
xx | 列表、数据框、tibble | 从列表中提取的第i 个元素 |
xx[i] | 列表、数据框、tibble | 维持原始结构的第i 个元素 |
xx[i,j] | 数据框、tibble 或矩阵 | 数据框、tibble 或矩阵的i 行和j 列 |
xx[i,j,k] | 数组 | 数组的i 行、j 列和k 维度 |
i
、j
和k
是可以在[]
内部使用的三种不同类型的向量之一:
-
整数向量
-
一个逻辑向量,或者
-
如果元素有名称,则包含名称的字符向量。
这对你来说应该已经很熟悉了,就像在 Python 中一样。对于整数和逻辑向量,它们可以是未分配的向量,或者是解析为整数或逻辑向量的对象或函数。数字不需要是整数类型,尽管整数更清晰。使用数字/双倍会向下舍入到最接近的整数,但除非有目的,否则尽量避免在索引时使用实数。
让我们从整数开始。我们在这里又稍作一小跳,讨论一下无处不在的:
运算符,它不会像你的 Pythonista 大脑告诉你的那样运行。我们将从内置字符向量letters
开始,它与在数据框中拥有列一样,例如PlantGrowth$weight
。
> letters[1] # The 1st element (indexing begins at 1)
[1] "a"
所以这非常直接了。如何倒数计数呢?
> letters[-4] # Everything except the 4th element,
> # (*not* the fourth element, counting backwards!)
[1] "a" "b" "c" "e" "f" "g" "h" ...
不,这不会发生,-
意味着排除一个元素,而不是倒数计数,但这是一个不错的尝试。我们也可以排除一系列的值:
> letters[-(1:20)] # Exclude elements 1 through 20
[1] "u" "v" "w" "x" "y" "z"
当然可以索引一系列的值:
> letters[23:26] # The 23rd to the 26th element
[1] "w" "x" "y" "z"
并且记住,我们可以将这个方法与任何可以给我们整数向量的东西结合起来。length()
会告诉我们向量中有多少元素,而lhs:rhs
是函数seq(from = lhs, to = rhs, by = 1)
的简写,它会创建一系列的值,增量步长为by
,在这种情况下默认为 1。
> # The 23rd to the last element
[1] "w" "x" "y" "z"
这意味着在使用:
时,你总是需要一个lhs
和一个rhs
。很遗憾,但这并不起作用:
> letters[23:] # error
在 R 中,错误地使用[]
会产生一个传奇而神秘的错误消息:
> df[1]
Error in df[1] : object of type 'closure' is not subsettable
> t[6]
Error in t[6] : object of type 'closure' is not subsettable
你能告诉我们哪里出错了吗?df
和t
不是定义的数据存储对象,我们可以对其进行索引!它们是函数,因此必须跟随()
,其中我们提供参数。[]
总是用于子集,而这些函数df()
和t()
是闭包类型的函数,不能进行子集操作。所以实际上这是一个非常明确的错误消息,并且提醒我们不要使用含糊不清的简短名称来调用对象,或者混淆函数和数据存储对象。
这一切都很好,但你可能知道索引的真正威力来自于使用逻辑向量来索引特定的TRUE
元素,就像在 Python 中使用类型bool
一样。获取用于索引的逻辑向量的最常见方法是使用逻辑表达式(见上文)。这正是numpy
中掩码的用法。
那么这些花式钻石的颜色是什么?
> diamonds$color[diamonds$price > 18000 & diamonds$carat < 1.5]
[1] D D D D F D F F E
Levels: D < E < F < G < H < I < J
在这里,我们使用价格和克拉来找到我们感兴趣的钻石的颜色。毫不奇怪,它们是最好的颜色分类。你可能会觉得反复写diamonds$
很烦人,但我们会认为这样做更明确,这就是我们在 Python 中引用pandas
的Series
时发生的事情。由于我们正在索引一个向量,我们得到的输出也是一个向量。让我们转向数据框架。我们可以把上面的索引命令写成:
> diamonds[diamonds$price > 18000 & diamonds$carat < 1.5, "color"]
# A tibble: 9 x 1
color
<ord>
1 D
2 D
3 D
4 D
5 F
6 D
7 F
8 F
9 E
正如你所期望的,在`` [i,j]
中,
i总是指*行*(观察值),而
j总是指*列*(变量)。请注意,我们还混合了两种不同类型的输入,但它们在表达式的不同部分工作是因为它们。我们使用一个逻辑向量,其长度与数据框架的观测数相同(感谢向量回收),以获取所有
TRUE行,然后我们使用一个字符向量来提取一个命名元素,回想一下数据框架中的每一列都是一个命名元素。这在 R 中是一个非常典型的表达方式。输出是一个数据框架,具体来说是一个 tibble,因为我们在钻石数据框架上使用了索引,而不是在特定的一维向量上。不要被这个主题困扰,但值得注意的是,如果我们没有一个 tibble,对单列进行索引(在
j`中)会返回一个向量:
> class(diamonds)
[1] "data.frame"
> diamonds[diamonds$price > 18000 & diamonds$carat < 1.5, "color"]
[1] D D D D F D F F E
Levels: D < E < F < G < H < I < J
这确实令人困惑,并突显了我们始终要意识到数据对象的类别的必要性。Tidyverse 试图通过保持数据框架来解决其中一些问题,即使在基础 R 更倾向于回退到向量的情况下也是如此。下面显示的 Tidyverse 索引函数使事情变得更简单(基础包的简写subset()
在大致上也是这样,但在 Tidyverse 上下文中使用filter()
更有效)。
> diamonds %>%
+ filter(price > 18000, carat < 1.5) %>%
+ select(color)
# A tibble: 9 x 1
color
<ord>
1 D
2 D
3 D
4 D
5 F
6 D
7 F
8 F
9 E
我们在书的第一部分介绍了 Tidyverse 的原理,现在我们看到它的实际应用。上面的%>%
允许我们展开对象和函数。例如,我们可以这样写:
> select(filter(diamonds, price > 18000, carat < 1.5), color)
这是一个长而嵌套的函数格式,很难理解。我们可以把%>%
读作“然后”,因此可以将上面的整个命令读作“取出 diamonds 数据集,然后根据这些条件进行过滤,然后仅选择这些列”。这在帮助我们理解和阅读代码方面有很大帮助,这也是为什么dplyr
被描述为数据分析的语法。对象,如 tibbles,是名词,%>%
是我们的标点符号,函数是动词。
表 2-4. 函数描述
函数 | 作用对象 | 描述 |
---|---|---|
select() |
列 | 使用名称或辅助函数提取仅这些列 |
filter() |
行 | 使用逻辑向量保留仅为 TRUE 的行 |
arrange() |
行 | 根据特定列中的值重新排序行 |
--- | --- | --- |
summarise() |
列 | 对列应用聚合函数 |
mutate() |
列 | 对列应用转换函数 |
dplyr
中五个最重要的动词在表 2-4 中列出。我们已经看到filter()
和select()
在实际操作中的应用,接下来让我们看看如何使用summarise()
和mutate()
应用函数。summarise()
用于应用聚合函数,返回单个值,如平均值mean()
或标准偏差sd()
。通常会看到summarise()
与group_by()
函数结合使用。在我们的语法元素类比中,group_by()
是一个副词,它修改动词的操作方式。在下面的例子中,我们使用group_by()
向我们的数据框添加一个Group
属性,因此在summarise
中应用的函数是特定于组的。就像pandas
的DataFrame
中的.groupby()
方法一样!
> PlantGrowth %>%
+ group_by(group) %>%
+ summarise(avg = mean(weight),
+ stdev = sd(weight))
`summarise()` ungrouping output (override with `.groups` argument)
# A tibble: 3 x 3
group avg stdev
<fct> <dbl> <dbl>
1 ctrl 5.03 0.583
2 trt1 4.66 0.794
3 trt2 5.53 0.443
mutate()
用于应用转换函数,该函数返回与输入相同数量的输出。在这些情况下,结合使用 Tidyverse 语法和原生的[]
索引特定值并不罕见。例如,该数据集包含了世界不同地区在四个不同时间点的灌溉面积(千公顷)。
> irrigation <- read_csv("R4Py/irrigation.csv")
Parsed with column specification:
cols(
region = col_character(),
year = col_double(),
area = col_double()
)
> irrigation
# A tibble: 16 x 3
region year area
<chr> <dbl> <dbl>
1 Africa 1980 9.3
2 Africa 1990 11
3 Africa 2000 13.2
4 Africa 2007 13.6
5 Europe 1980 18.8
6 Europe 1990 25.3
7 Europe 2000 26.7
8 Europe 2007 26.3
...
我们可能希望针对每个地区相对于 1980 年的面积折变进行测量。
irrigation %>%
group_by(region) %>%
mutate(area_std_1980 = area/area[year == 1980])
# A tibble: 16 x 4
# Groups: region [4]
region year area area_std_1980
<chr> <dbl> <dbl> <dbl>
1 Africa 1980 9.3 1
2 Africa 1990 11 1.18
3 Africa 2000 13.2 1.42
4 Africa 2007 13.6 1.46
5 Europe 1980 18.8 1
6 Europe 1990 25.3 1.35
7 Europe 2000 26.7 1.42
8 Europe 2007 26.3 1.40
...
就像mutate()
一样,我们可以添加更多的转换,比如每个时间点的百分比变化:
> irrigation <- irrigation %>%
+ group_by(region) %>%
+ mutate(area_std_1980 = area/area[year == 1980],
+ area_per_change = c(0, diff(area)/area[-length(area)] * 100))
> irrigation
# A tibble: 16 x 5
# Groups: region [4]
region year area area_std_1980 area_per_change
<chr> <dbl> <dbl> <dbl> <dbl>
1 Africa 1980 9.3 1 0
2 Africa 1990 11 1.18 18.3
3 Africa 2000 13.2 1.42 20.0
4 Africa 2007 13.6 1.46 3.03
5 Europe 1980 18.8 1 0
6 Europe 1990 25.3 1.35 34.6
7 Europe 2000 26.7 1.42 5.53
8 Europe 2007 26.3 1.40 -1.50
...
重复
注意,在上述示例中我们没有需要循环的地方。你可能本能地想应用for 循环
来计算每个区域的聚合或转换函数,但这并不必要。在 R 中避免 for 循环有点像过去的一种消遣,并且在基础包中使用apply
家族的函数时可以发现这一点。
由于向量化对于 R 非常重要,因此有一个非正式的竞赛,看看你能写多少少的 for 循环。我们想象一些用户可能有一个墙上的标志:“距上次 for 循环的天数:”就像工厂对事故的记录一样。
这意味着有一些非常古老的方法来重复任务,以及一些更新的方法使这一过程更加方便。
表 2-5。基础包apply
家族
功能 | 用途 |
---|---|
apply() |
将函数应用于矩阵或数据框的每一行或列 |
lapply() |
将函数应用于列表中的每个元素 |
sapply() |
简化lapply() 的输出 |
mapply() |
sapply() 的多变量版本 |
tapply() |
对由索引定义的值应用函数 |
emapply() |
在环境中应用函数 |
老派方法依赖于apply
家族的函数,列在表 2-5 中。除了apply()
,其他的发音都是第一个字母然后再加上 apply,因此是“t apply”而不是“tapply”。有一点趋势是摒弃这些重复性的工作马,但你仍然会经常看到它们,因此熟悉它们是值得的。这样做还将帮助您理解为什么 Tidyverse 会出现。例如,让我们回到我们对上面的PlantGrowth
数据框应用的聚合函数。在 apply 函数族中,我们可以使用:
> tapply(PlantGrowth$weight, PlantGrowth$group, mean)
ctrl trt1 trt2
5.032 4.661 5.526
> tapply(PlantGrowth$weight, PlantGrowth$group, sd)
ctrl trt1 trt2
0.5830914 0.7936757 0.4425733
你可以想象这样读取,“从PlantGrowth
数据集中取出重量列,根据组列中的标签分割值,然后对每组值应用均值函数,然后返回一个命名向量”。
如果你想在那里添加更多的功能,你可以看到这是多么繁琐吗?命名向量可能很方便,但它们并不是你想要存储重要数据的典型方式。
试图简化这一过程的一种尝试在plyr
中实现,这是dplyr
的前身。plyr
的发音类似于多功能手持工具“plyer”。我们使用它如下:
library(plyr)
ddply(PlantGrowth, "group", summarize,
avg = mean(weight))
尽管这些方法今天仍然有时会被使用,但它们大多已被数据框架为中心的包所取代,因此在dplyr
中有个 d(读作 d-plyer):
library(dplyr)
PlantGrowth %>%
group_by(group) %>%
summarize(avg = mean(weight))
但是要明确,我们也可以使用其他非常古老的函数返回一个数据框:
> aggregate(weight ~ group, PlantGrowth, mean)
group weight
1 ctrl 5.032
2 trt1 4.661
3 trt2 5.526
哇,这是一个很棒的函数,不是吗?这个东西真的很古老!你仍然会看到它的身影,为什么不呢,一旦你掌握了它,它就是优雅而有效的,即使它仍然只应用一个函数。然而,倾向于使用更统一的 Tidyverse 框架来进行阅读和学习,这意味着古老的方法正在逐渐淡出。
这些函数自 R 早期就存在,直观地反映了统计学家们 一直在做的事情。它们将数据分割成由某些属性定义的块(行、列、分类变量、对象),然后应用某种操作(绘图、假设检验、建模等),最后以某种方式组合输出(数据框、列表等)。这个过程有时被称为“分割-应用-组合”。意识到这个过程的重复性开始让社区清楚如何开始思考数据,以及如何组织数据。从而诞生了“整洁”数据的概念^(7)。
作为迭代的最后一个例子,你可能熟悉 Python 中的 map()
函数。在 Tidyverse 的 purrr
包中可以找到类似的函数。这对于在列表或向量中重复迭代非常方便,但超出了本书的范围。
最后思考
在 Python 中,你经常听到 Pythonic 这个词。这意味着适当的 Python 语法和执行特定操作的首选方法。这在 R 中并不存在;有很多方法可以达到同样的效果,人们会使用各种方案!此外,他们经常混合使用不同的方言。尽管有些方言比其他方言更容易阅读,但这种混合可能会增加语言的学习难度。
加之不断调整的扩展 Tidyverse。函数被标记为实验性的、休眠的、成熟的、稳定的、有问题的、被取代的和归档的。再加上项目特定包管理或虚拟环境使用的相对宽松标准,你可以想象出一定程度的不满和挫折感。
R 正式在 2020 年庆祝其成立 20 周年,其根源比这要古老得多。然而,有时候感觉 R 正处于青少年的快速成长期。它正试图弄清楚自己是如何突然变得更加庞大的,同时也可能显得既笨拙又酷。融合不同的 R 方言将帮助你更深入地发现其全部潜力。
^(1) useR! 是每年一度的 R 会议,也是由 Springer 出版的一系列书籍。
^(2) “图形用户界面”
^(3) “集成开发环境”
^(4) 面向对象编程
^(5) 柏林人(名词):在柏林,是该市的居民。在其他地方:一种美味的果冻填充、糖粉涂抹的甜甜圈。
^(6) 但我们将详细阐述模型定义的内容留给有兴趣的读者去探索。
^(7) 如果你想进一步了解这个主题,可以查阅 Hadley Wickham 的论文 这里
第三章:面向使用者的 Python
瑞克·J·斯卡维塔
欢迎,勇敢的使用者,来到 Pythonista 的美妙世界!对于许多使用者来说,这个新世界可能看起来更加多样化 —— 因此也更加不一致和令人困惑 —— 比他们在 R 中习惯的要多。但不要因为多样性而担忧 —— 应该庆祝它!在本章中,我将帮助您导航通过丰富多样的 Python 丛林,突出您的 Python 同事可能采取的各种路径(工作流),您以后也可以选择探索。同时,请知道,您最终会找到最适合您和您的工作环境的路径,这会随时间而变化,可能并不是这里所概述的那一条。就像任何好的远足一样,把这条路线当作指南,而不是规则书。
我将讨论这一部分介绍中提到的“四要素”的基本内容:函数、对象、逻辑表达式 和 索引。但我首先要回答三个问题。
问题 1:要使用哪个版本和构建(分发)?与 R 不同,Python 有几个不同的版本和构建可供选择。
问题 2:要使用哪些工具?广泛的集成开发环境(IDE)、文本编辑器和笔记本,加上许多实现虚拟环境的方法,增加了更多选择。
问题 3:Python 语言 与 R 语言 相比如何?理解面向对象编程为主导的世界,以及大量的类、方法、函数和关键字,是另一个入门的障碍。
我将依次回答这些问题。我的目标是让您足够熟悉阅读和编写 Python,以便您可以在 第三部分 和 第四部分 继续您的双语旅程。我并不打算为数据科学提供一个全面深入的 Python 入门。为此,请访问 O'Reilly 的 Python 数据分析 和 Python 数据科学手册;这一章将帮助您更好地理解那些书籍。
如果你急于开始使用 Python,可以跳过到关于笔记本的部分,“笔记本”,并访问Google Colab 笔记本来学习 Python 的课程,或者在我们的书库的 GitHub 上访问本章的脚本。
版本和构建
尽管 R 有几种不同的分发版本,但大多数用户仍然使用从 r-project.org^(1) 获取的基本版本。对于 Python,至少有四种常见的 Python 构建(即分发版本)供选择。在每种情况下,您还需要考虑 Python 版本。
首先,你可能已经注意到系统已经安装了 Python 的一个版本。在我使用的 macOS Big Sur (v11.1
) 上,可以通过以下终端命令查看 Python 的版本:
---
$ python --version
Python 2.7.16
---
有趣的是,masOS 也内置了 python3
:
---
$ python3 --version
Python 3.8.6
---
这些是 macOS 内部使用的 Python 安装;不需要触碰它们。
其次,我们有 原始的 Python —— 纯净的、直接来自源代码的 Python 版本。在撰写本文时,这是版本 3.9。2.x 版本已不再受支持,您应该使用 3.x 版本进行未来的数据科学项目。在您确定您将使用的所有软件包与最新版本兼容之前,最好坚持使用最后一个小更新,本例中为 3.8 版本。事实上,您的系统上可能有多个小版本。
要安装您想要的特定版本,请访问 Python 网站 并按照 下载页面 上的说明操作。
安装方式因系统而异。因此,官方 Python 使用和安装指南 是权威资源。如果遇到安装问题,一个好的起点是对错误消息的通用部分进行文字搜索(用双引号括起来)。
表 3-1 提供了其他信息源,但您最好直接访问源头^(2)。
表 3-1. 安装 Python
平台 | 站点 | 替代方案 |
---|---|---|
Linux | python.org |
Python 3 已经安装好了。 |
macOS | python.org |
在终端中使用 brew install python3 。 |
Windows | python.org |
从 Windows Store 安装 Python。 |
其次,有两种常见的 Conda
构建:Anaconda
(又称 Conda
)和 miniconda
。Conda
提供了多种编程语言(包括 Python 和 R)的软件包、依赖项和环境管理,尽管它很少用于 R。这些开源构建包括 Python、一套对数据科学有用的软件包以及一系列集成开发环境(包括 RStudio)。Anaconda 包括免费的个人版本和各种商业版本。顾名思义,miniconda 是一个最小的安装程序。我们将在本书的最后部分再次看到 miniconda
的出现。
Anaconda 网站 上有详细的安装说明。您会注意到,Anaconda 可能不会打包最新版本的 Python。例如,在撰写本文时,Anaconda 打包的是 Python 3.8,而不是 3.9。因此,这为我们上面提到的偏爱原始 Python 3.8 提供了一些理由。Anaconda 是一个流行的构建,但对于我们的目的,我们将坚持使用原始的 Python,以避免那些在这一点上只会分散我们注意力的额外功能。因此,我不会进一步考虑这个选项,但如果您选择这条路,我会在需要时提到一些重要的区别。
第四,您可能决定不使用本地 Python 安装,而是使用由Google Colab提供的流行的在线 Python 版本的 Notebooks 接口^(3). 还有其他在线 Notebook 工具,但详细介绍超出了本书的范围。Notebooks 类似于 RMarkdown 文档,但基于 JSON。我们将在后面更详细地讨论它们。
我敢打赌你已经能够猜到,这种早期阶段的多样性可能会在安装特定问题出现时导致混乱。未来,我们将假设您已经准备好本地或云端安装的 Python。
标准工具
与 R 类似,访问 Python 的方式有很多种。常见的方法包括:在命令行上、IDE、基于云的 IDE、文本编辑器和 Notebooks。为简单起见,我不打算专注于在命令行上执行 Python。如果您熟悉在命令行上执行脚本,这是熟悉的领域。如果不熟悉,待会儿您会遇到的。
IDE 包括 JupyterLab、Spyder、PyCharm 和我们心爱的 RStudio。云原生 IDE 包括 AWS Cloud9。这些都是主题的变体,在我的经验中通常不受 Python 爱好者青睐,尽管有使用云端工具的趋势。听起来很奇怪,IDE 并不那么受欢迎,如果有一个很好的 IDE 为什么不使用呢?我认为答案有两个方面。首先,没有一个 IDE 像 RStudio 在用户中那样成为事实上的首选。其次,由于 Python 的使用案例如此广泛,通常甚至在命令行本身执行,对于许多 Python 爱好者来说,使用 IDE 编码并不那么吸引,特别是如果他们来自编码背景并且在没有 IDE 的情况下感觉很舒适。对我来说,这在某种程度上反映了 Python 比 R 更难但更好的叙述。这两种说法都是错误的!抱歉 😕 尽管如此,您可能会因为看起来舒适的 IDE 而开始使用 Python。在这里,我们认为文本编辑器从长远来看会更好地为您服务。在书的最后部分,当我们将 Python 和 R 合并到一个脚本中时,我们将回到 RStudio。目前,试着抵制默认使用 IDE 的冲动,但请关注可能引导未来趋势的云平台的发展。
文本编辑器
文本编辑器是编写纯 Python 脚本最常见且看似首选的工具。有许多出色的文本编辑器可供选择,每年都在人气上升和下降。Sublime、Atom、Visual Studio Code(VS Code)甚至是古老的编辑器 vim 和 emacs,以及许多其他编辑器,都在广泛使用中。尽管如此,VS Code,这款由微软开发和强力支持的开源编辑器,在过去几年中已成为首选。扩展市场意味着编辑器为包括 Python 和 R 在内的多种语言提供了强大且简便的支持^(4)。因此,我们将专注于 VS Code。你的第一个练习是获取并安装 VS Code。
当你第一次打开 VS Code 时,将会看到欢迎屏幕,如图 3-1)所示。
图 3-1. VS Code 欢迎屏幕。
当你点击左上角的空白文档图标时,将要求你打开一个文件夹或克隆一个 git 仓库(例如来自 GitHub)。我们将选择一个名为 Intro_python
的空文件夹,这个文件夹我们已经创建好了。打开这个文件夹就像在 RStudio 中打开一个项目一样。在这里,我们可以点击新文档图标,并将被要求为新文档命名。就像在图 3-2 中一样,文件名为 helloworld.py
。
图 3-2. 我们的第一个 Python 脚本。
由于文件扩展名,VS Code 自动检测到你希望在此文档中使用 Python 解释器。与许多其他文本编辑器一样,如果它知道要使用的解释器,VS Code 可以直接执行文档中的代码。请注意图 3-2(右下角),VS Code 将自动要求你安装适用于 Python 的适当扩展,因为我们尚未安装它们(并且页脚从紫色变为蓝色,表示正在使用 Python 解释器)。鼓励你访问市场并考虑自行选择其他扩展,但大多数情况下“少即是多”。在图 3-3 中,显示了扩展主页在安装包时的状态。请注意,这个扩展是由微软直接开发和维护的,就像 VS Code 本身一样,所以我们是在可靠的手中^(5)。
图 3-3. 安装 VS Code Python 扩展。
安装扩展后,将会看到扩展的欢迎页面,显示在图 3-4 中。蓝色页脚现在显示了你正在使用的实际 Python 版本。请记住,你的系统上可能安装了许多不同的版本,在这里我使用的是 v3.8.6
。
图 3-4. VS Code Python 扩展的欢迎屏幕。
扩展程序欢迎页面的第一项是“创建一个 Jupyter Notebook”。我们很快就会讨论到这一点;现在值得注意的是,我们可以在 VS Code 中同时使用脚本和 Notebooks。还要注意该项目的第一个要点告诉我们,要打开 Notebook,我们应该在命令面板中运行一个命令,您可以通过 Mac 上的键盘快捷键shift + cmd + P
(或 PC 上的shift + ctrl + P
)来访问该面板^(6)。返回到helloworld.py
文件并使用此键盘快捷键来打开命令面板。这是您执行各种命令以使您的 Python 生活更轻松的地方。命令面板是 RStudio 中相对较新的功能,但长期以来一直是导航文本编辑器的标准方式。从市场安装的每个扩展程序都将添加更多您可以在此处访问的命令。我们的第一个命令将是Create New Integrated Terminal (in Active Workspace)
。您只需开始输入命令,然后让自动完成完成其工作即可。确保选择(in Active Workspace)
选项。请记住,这就像是一个 RStudio 项目,所以我们希望保持在我们的 Active Workspace 中。
这会在屏幕底部打开一个新的终端窗格(图 3-5)。好吧,我们承认,这开始看起来越来越像一个集成开发环境(IDE),但我们不要太兴奋!
图 3-5. 带有命令面板和集成终端窗格的 VS Code。
到目前为止,我们已经选定了一个文本编辑器,并且有了我们的第一个(空的)Python 脚本。看起来我们已经准备好了 — 但还不完全!每当你想创建一个新的 Python 项目时,必须解决两个关键因素:
-
虚拟(开发)环境,以及
-
安装包
虚拟环境
大多数用户习惯于使用 RStudio 项目,这些项目将工作目录绑定到项目目录。这些项目非常方便,因为我们无需硬编码路径,并且被鼓励将所有数据和脚本保存在一个目录中。当您在 VS Code 工作空间中打开整个文件夹时,您已经拥有了这一点。
R 项目和 VS Code 工作空间的一个主要缺点是它们无法提供便携、可重现的开发环境!许多用户只有每个包的单个全局安装(参见.libPaths()
),很少指定特定的 R 版本。
现在,亲爱的用户,让我们彼此诚实吧:如果你还没有遇到过包版本冲突的问题,那么在某个时候你会遇到的。你已经全局更新了一个包,现在一个旧脚本无法工作,因为它调用了一个不推荐使用的函数,或者使用了函数的默认参数,而这些参数已经改变,或者由于包版本冲突的任何其他原因。这在 R 中是一个令人惊讶地常见的问题,在长时间或协作工作中,这实际上是一种真正令人沮丧的做法。多年来,有许多尝试在 R 中实现某种受控开发环境。最近的尝试,也希望这将最终解决问题的方法,是 renv
。如果你没有关注这方面的发展,请访问 RStudio 的 包网站。
Python 爱好者长期以来一直使用虚拟环境来保持其项目的未来兼容性,这是 Python 起源于编程优先方法的标志。在这里,虚拟环境只是项目文件夹中一个隐藏的子目录,例如 .venv
。.
是使它隐藏的关键。你的计算机上有很多隐藏的文件和目录,大部分时间它们是隐藏的,因为你没有在那里乱动的理由。在 .venv
内部,你会找到用于 这个 特定项目的包以及关于 这个 项目使用的 Python 版本的信息。由于每个项目现在都包含一个虚拟环境,其中包含所有的包和适当的包版本(!),只要虚拟环境存在,你就保证项目将持续无限期地工作。我们可以像 Figure 3-6 中可视化不同机器之间的潜在依赖问题,这突显了拥有关于包版本的单一“真相源”的好处。
Figure 3-6. Python 环境中的冲突来源。
就像 Python 中的一切一样,创建虚拟环境有很多方法。我们可以使用 venv
或 virtualenv
包。如果你在使用 Anaconda,你将使用 conda
的替代方法,这里我们不涉及。venv
和 virtualenv
之间有一些细微的差异,但在这个故事的这一点上它们是无关紧要的;让我们只使用 venv
。在你的新终端窗口中执行 Table 3-2 中的一个命令,就像我在 Figure 3-7 中所做的那样。
Table 3-2. 使用 venv 创建(和激活)虚拟环境。
平台 | 创建 | 激活(最好使用 VS Code 的自动激活) |
---|---|---|
macOS X & Linux | python3 -m venv .venv |
source .venv/bin/activate |
Windows | py -3 -m venv .venv |
.venv\scripts\activate |
Figure 3-7. 在我们的活动工作区创建一个新的虚拟环境。
在创建虚拟环境之后,您必须激活它。关于此的终端命令在表 3-2 中给出,但让 VS Code 做它最擅长的事情更方便。它将自动检测到您的新虚拟环境,并询问您是否要激活它(见图 3-8)。走吧!注意左下角的 Python 解释器也会明确提到(.venv): venv
(见图 3-9)。
图 3-8. 在 VS Code 中激活虚拟环境。
如果要求安装 Linter pylint,请继续确认。这是 VS Code 将能够在您的脚本中发现错误的方式。
我们马上就会开始安装包,现在让我们试着执行我们的第一个“Hello, world!”命令。返回到你的空helloworld.py
文件并输入:
#%%
print('Hello, world!')
实际上,打印是不必要的,但它使我们尝试做的事情变得明确。这看起来很像一个简单的 R 函数,对吧?#%%
也不是必需的,但它是 VS Code 中 Python 扩展的一个可爱功能,强烈推荐使用!键入#%%
允许我们将长脚本分成可执行的块。这类似于 R Markdown 块,但更简单,并且用于普通的 Python 脚本。要执行命令,请按shift + enter
或单击run cell
文本,如图 3-9 所示。
图 3-9. 在 Python 中执行您的第一个代码。
您将立即被要求安装 ipyKernel,见图图 3-9(右下角)。确认后,您将获得新窗口中的输出,可见于图 3-10。
图 3-10. 在交互式 Python 查看器中查看命令输出。
好的,现在我们开始做生意了。看起来工作量很大,但是做几次后,你会形成一种常规并掌握要领!
安装软件包
到目前为止,在这个故事中,我们安装了某个版本的 Python,并在 VS Code 中访问了一个工作区,就像在 R 项目中一样。我们还创建了一个虚拟环境,现在准备用我们喜爱的数据科学包来填充它。如果你选择了conda
这条路线,你会使用不同的命令,但也将预装最常见的数据科学包。这听起来非常不错,但你可能会发现,当你需要与其他 Python 开发人员合作时,比如数据工程师或系统管理员,他们可能不会使用 Anaconda。我们觉得,了解 Python 的核心,而不是 Anaconda 提供的所有花里胡哨的东西,也有其优点。因此,我们选择了纯粹的路线。
在我们开始讨论包之前,让我们复习一些必要的术语。在 R 中,库是一组单独的包。对于 Python 也是如此,但是 库 和 包 的使用并不那么严格。例如,pandas
这个包提供了 DataFrame
类的对象,它在 pandas
网站上被称为库和包。这种术语混用在 Python 程序员中很常见,所以如果你是一个严谨的命名者,不要被困扰。不过,模块是值得注意的。包是模块的集合。这是有用的知识,因为我们可以加载整个包或其中的一个特定模块。因此,一般而言:库 > 包 > 模块。
在 R 中,你可以使用 install.packages()
函数从 CRAN 安装包。在 Python 中,有两个等效的 CRAN,即 PyPI(Python 包安装器)用于使用原始 Python,以及 conda
,用于使用 Anaconda 或 miniconda(稍后我们还将看到如何直接在在线笔记本中在 Google Colab 中安装包)。要使用原始 Python 从 PyPI 安装包,你需要在终端中执行命令。回想一下,我们之前还有一个活动的 VS Code 终端窗口。在终端中执行命令 pip install matplotlib
以在你的虚拟环境中安装 matplotlib
包,如图 3-11 所示。pip
是 Python 的包安装器,也有各种版本。
!
图 3-11. 使用命令行将包安装到虚拟环境中。
你几乎在每个虚拟环境中都会安装的包包括 numpy
、pandas
、matplotlib
、seaborn
和 scipy
。你不需要一直安装它们,因为它们的包依赖项会自动处理。如果它们已经安装,pip 会告诉你,并且不会安装任何其他内容。你在这里遇到的最常见的错误消息是你的包版本与你的 Python 版本不兼容。对于这个问题,你可以为你的项目使用不同的 Python 内核^(7),或者指定你想要安装的确切包版本。就像在 R 中一样,你只需要安装包一次,但是每次你激活你的环境时都需要 导入 它(即 初始化 它)(见上文)。包安装与在脚本中导入分开似乎很方便。你可能在 R 脚本中看到许多单独的 install.packages()
函数,这有点烦人。
我想提及另外两个重要点。首先,在终端中使用以下命令检查环境中安装的所有包及其版本:
---
$ pip freeze
---
其次,通过执行以下命令将此输出导出到名为 requirements.txt
的文件中:
---
$ pip freeze > requirements.txt
---
其他用户现在可以使用 requirements.txt
文件通过以下命令安装所有必要的包:
---
$ pip install -r requirements.txt
---
笔记本
如果到目前为止您已经按照教程操作,那么您已经准备好进入第三个问题并开始探索 Python 语言。尽管如此,复习笔记本仍然是值得的,所以请继续阅读。如果您在本地设置 Python 时遇到困难,不要担心!Jupyter Notebooks 是一个让您可以松一口气,将安装问题搁置一旁并重新开始的地方。
Jupyter Notebooks 建立在 IPython 的基础上,IPython 起源于 2001 年。Jupyter 代表 JUlia、PYThon 和 R,现在支持数十种编程语言,并且可以在 JupyterLab IDE 或 Jupyter 中直接使用笔记本。笔记本允许您使用 Markdown 编写文本,添加代码块并查看内联输出。这听起来很像 R Markdown!嗯,是和不是。在底层,R Markdown 是一个平面文本文件,可以呈现为 HTML、doc 或 pdf。笔记本专门基于 JSON 的 HTML,可以原生地处理交互式组件。对于使用 R 的人来说,默认情况下,这有点像带有shiny
运行时的交互式 R Markdown。这意味着您不会将笔记本作为平面文本文件来构成,这在考虑编辑潜力时是一个重要的区别。
在 Python 编码中,通常会使用纯粹的笔记本。例如,如果你涉足处理大数据用于机器学习的云平台,比如 AWS Sagemaker、Google AI 平台或 Azure ML Studio,你将从笔记本开始。正如我们已经看到的,它们受到 VS Code 的支持。其他在线版本包括 Kaggle 竞赛和发布的 Jupyter Notebooks。另一种在线笔记本的变体可以在 Google Colab 服务中找到。这使您能够使用 Python 后端生成和分发在线笔记本,这也是我们用来探索笔记本的工具。
要熟悉使用笔记本,请使用 Jupyter 的这个在线教程这里。只需点击Notebook Basics panel,特别注意键盘快捷键。
如果您想跟进,您可以在这一章节找到 Google Colab 笔记本这里。
图 3-12。使用 Google Colab 开始使用 Python 笔记本的示例。
Python 语言与 R 语言有什么区别呢?
到目前为止,您应该已经选择了两条路中的一条。如果您已经在本地安装了 Python,您应该已经:
-
一个项目目录,您将在其中存储数据和脚本文件。
-
在该目录中设置一个虚拟环境。
-
在该虚拟环境中安装数据科学的典型软件包。
如果您决定使用 Google Colab,您应该已经访问了本章的笔记本(见上文)。
现在是时候通过导入我们将使用的包来开始我们的项目了。在这里,我们会再次看到有多种方法可以做到这一点,但大多数方法都是标准的。让我们来看看。在书的存储库中,您会找到一个练习脚本,其中包含以下命令,或者您可以在 Google Colab Notebook 中跟随。
随着我们逐步介绍这些命令,我们将引入更多新术语——关键字、方法和属性——并讨论它们在 Python 上下文中的含义。
首先,我们可以导入整个包:
import math # Functions beyond the basic maths
这允许我们使用 math
包中的函数。math
包已经安装好了,所以我们不需要使用 pip
,但我们确实需要导入它。
这是我们第一次接触 Python 语言的一个常见而重要的方面:关键字,它们类似于 R 语言中的保留字,但数量更多。现在 Python 中有 35 个关键字,可以分成不同的组(见附录 A)。在这里,import
是一个导入关键字。作为习惯于函数式编程的 useR,在 R 中你会使用 library(math)
。因此,在这种情况下,你可以把关键字看作是函数的快捷方式,实际上在很多情况下确实是这样。这就像 R 中的运算符(比如 <-
、+
、==
、&
等),它们在底层也是函数的快捷方式。它们不是用经典的函数格式写的,但它们本质上确实是函数。
简而言之,关键字是具有非常具体含义的保留字。在这种情况下,import
代表着一个函数,用于导入 math
包中的所有函数。很多关键字都是这样的,但不是全部。我们稍后会看到一些例子。
但首先,既然我们已经从 math 包中得到了函数,让我们试试这个:
math.log(8, 2)
在这里,我们看到 .
具有特定的含义:在 math
包内部,访问 log()
函数。两个参数分别是数字和基数。所以你可以看到为什么 R Tidyverse 倾向于使用 _
而不是 .
符号,以及为什么许多 R 函数中无意义的 .
符号会让许多从面向对象编程语言转来的用户感到沮丧。
其次,我们可以导入整个包并给它一个特定的、通常是标准化的别名。
import pandas as pd # For DataFrame and handling
import numpy as np # Array and numerical processing
import seaborn as sns # High level Plotting
这里是我们的第二个关键字,as
。请注意,它实际上并没有像一个函数一样起作用,除非我们记得 <-
也是一个函数。如果我们发挥一下想象力,我们可以把它想象成在 R 中的以下用法:
dp <- library(dplyr) # nonsense, but just as an idea
useR 不会这样做,但这是与之最接近的类似命令^(8)。as
关键字总是与 import
一起使用,为访问包或模块的函数提供了一个便捷的别名^(9)。因此,它是调用我们想要的确切函数的明确方式。执行此函数以导入数据集以供将来使用:
plant_growth = pd.read_csv('ch03-py4r/data/plant_growth.csv')
注意再次出现的 .
?上面的命令相当于在 R 中执行以下命令:
plant_growth <- readr::read_csv("ch03-py4r/data/plant_growth.csv")
第三,我们可以从一个包中导入一个特定的模块:
from scipy import stats # e.g. for t-test function
这里是我们的第三个关键字from
。它允许我们进入scipy
包并仅导入stats
模块。
第四,我们可以从包中导入特定模块,并为其指定一个通常标准化的别名。
import matplotlib.pyplot as plt # Low level plotting
import statsmodels.api as sm # Modeling, e.g. ANOVA
最后,我们还可以仅从包中导入特定函数:
from statsmodels.formula.api import ols # For ordinary least squares regression
导入数据集
上面,我们看到如何使用pandas
包的函数导入数据集:
plant_growth = pd.read_csv('ch03-py4r/data/plant_growth.csv')
检查数据
在我们开始处理数据之前,查看数据总是一个良好的做法。在 R 中,我们会使用summary()
和str()
,或者如果我们初始化了dplyr
,则会使用glimpse()
。让我们看看这在 Python 中是如何工作的。
plant_growth.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 30 entries, 0 to 29
Data columns (total 2 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 weight 30 non-null float64
1 group 30 non-null object
dtypes: float64(1), object(1)
memory usage: 608.0+ bytes
plant_growth.describe()
weight
count 30.000000
mean 5.073000
std 0.701192
min 3.590000
25% 4.550000
50% 5.155000
75% 5.530000
max 6.310000
plant_growth.head()
weight group
0 4.17 ctrl
1 5.58 ctrl
2 5.18 ctrl
3 6.11 ctrl
4 4.50 ctrl
什么??这是我们第一次遇到这个术语,而且又出现了那个无处不在的点符号!info()
、describe()
和head()
函数是plant_growth
对象的方法。方法是由对象调用的函数。与其他函数一样,我们也可以提供特定的参数,尽管在这些情况下,我们将坚持使用默认值。
特别注意info()
方法的输出。在这里,我们首次看到 Python 中索引从 0 开始,这与许多编程语言相同 —— 那又为什么不呢?这是 Python 编程的一个重要方面。当我们进行索引时,稍后我们将看到这会产生什么后果。
.info()
的输出还告诉我们,我们有一个pandas DataFrame
。我们很快会探索不同的对象类。
如何查看plant_growth
对象的形状(即维度)和列名?
plant_growth.shape
(30, 2)
plant_growth.columns
Index(['weight', 'group'], dtype='object')
在这种情况下,我们正在调用对象的属性,它们不接收任何括号。因此,我们可以看到,任何给定对象都可以调用其类允许的可允许的方法和属性。您将从 R 中知道这一点,当对象的类允许它在具体函数中使用时。在底层,相同的魔术正在发生。R 首先是函数,其次是 OOP。它在那里,只是我们在函数式编程中不需要太多担心它。为了让您了解在 R 中这是如何工作的,请考虑内置数据集sunspots
。它是一个ts
类对象(即时间序列)。
# in R
class(sunspots)
[1] "ts"
plot(sunspots)
您可以使用以下代码查找绘图函数的方法:
# in R
methods(plot)
在这里,您将看到plot.ts()
方法,当您为plot()
函数提供一个ts
类对象时,实际上调用的就是它。
最后,您可能会错过实际查看数据集的能力,就像我们可以使用 RStudio 的查看选项一样。别担心!您可以点击交互式 Python 内核中的表格图标,并查看环境中的所有内容。如果您单击DataFrame
,它将为您打开一个视图以便您查看它。
数据结构与描述性统计
好了,现在我们已经掌握了方法和属性,让我们看看如何生成一些描述性统计数据。pandas DataFrame
非常类似于 R 中的data.frame
或tbl
。它是一个二维表格,其中每一列都是一个Series
,就像在 R 数据框中列是相同长度的向量一样。就像DataFrame
本身一样,Series
也有方法和属性。记住group
列是分类的。到现在为止,这个命令应该对你有意义:
plant_growth['group'].value_counts()
trt2 10
trt1 10
ctrl 10
Name: group, dtype: int64
[[]]
对你来说可能很熟悉,它们按列名索引。然后,这个单列调用一个方法.value_counts()
,在本例中计算每个值的观察次数。
这样如何:
np.mean(plant_growth['weight'])
np
表示我们将使用我们之前导入的numpy
包中的一个函数。在该函数内部,我们提供数值,即plant_growth
DataFrame 的weight
Series。
关于一些汇总统计数据,你能猜到这个方法会做什么吗?
# summary statistics
plant_growth.groupby(['group']).describe()
就像dplyr
的group_by()
函数一样,groupby()
方法将允许按照分类变量对每个子集应用下游方法,在本例中是group
Series。describe()
方法将为每个子集提供一系列汇总统计信息。
这个版本更加具体:
plant_growth.groupby(['group']).agg({'weight':['mean','std']})
你可能猜到.agg()
方法代表聚合。聚合函数返回一个单一值(通常)在 R 中,我们会使用summarise()
函数来指定它。
对于.agg()
方法的输入,{'weight':['mean','std']}
,这是一个字典(类dict
)。你可以将其看作是一个键值对,在这里使用{}
定义:
{'weight':['mean','std']}
我们也可以使用dict()
函数来达到同样的目的:
dict([('weight', ['mean','std'])])
字典是数据存储对象本身,是标准的纯粹 Python 的一部分,并且正如我们在这里看到的,被用作输入方法和函数中的参数。这与 R 中列表的使用方式相似,在特定情况下用于数据存储和作为参数列表。然而,字典更适合被视为关联数组,因为索引仅由键而不是数字完成。我可以说字典更像是 R 中的环境,因为它包含了许多对象但没有索引,但这可能有点牵强。
让我们深入挖掘一下。以下命令产生相同的输出,但格式不同!
# Produces Pandas Series
plant_growth.groupby('group')['weight'].mean()
# Produces Pandas DataFrame
plant_growth.groupby('group')[['weight']].mean()
注意[[]]
与[]
的区别吗?它提醒了你在处理不是 tibbles 的数据框时可能遇到的差异。
数据结构:回到基础
在 Python 中,我们已经看到了三种常见的数据存储对象,pandas DataFrame
、pandas Series
和dict
。只有dict
是来自原始 Python 的,因此在我们进一步探讨之前,我想看看其他一些基本结构:lists
、tuples
和NumPy arrays
。我选择在比预期晚得多的时间介绍它们,因为我希望从直觉和频繁使用的数据框架开始。所以在结束之前,让我们确保掌握了基础知识:
首先,就像在 R 中一样,你会看到 Python 中的四种关键数据类型:
表 3-3. Python 中的数据类型。
类型 | 名称 | 示例 |
---|---|---|
bool |
二进制 | True 和False |
int |
整数 | 7,9,2,-4 |
float |
实数 | 3.14, 2.78, 6.45 |
str |
字符串 | 所有字母数字字符和特殊字符 |
接下来,你将遇到列表,这是一维对象。与 R 中的向量不同,每个元素可以是不同的对象,例如另一个一维列表。这里是两个简单的列表:
cities = ['Munich', 'Paris', 'Amsterdam']
dist = [584, 1054, 653]
注意,[]
定义了一个列表。实际上,我们在前面定义dict
时已经看到了这一点:
{'weight':['mean','std']}
因此,[]
和{}
单独在 Python 中是有效的并且具有不同的行为,与 R 中不同。但请记住,我们之前使用[]
来索引数据框,这与 R 非常相似。
plant_growth['weight']
最后,我们有元组,它们类似于列表,但是不可变,即不能更改,它们由()
定义,如下所示:
('Munich', 'Paris', 'Amsterdam')
元组的常见用法是函数返回多个值时使用。例如,divmod()
函数返回两个数的整数除法和模数的结果:
>>> divmod(10, 3)
(3, 1)
结果是一个元组,但我们可以解包这个元组,并将每个输出分配给单独的对象:
int, mod = divmod(10, 3)
当定义自定义函数时,这非常方便。在 R 中的等价操作是将输出保存到列表中。
精明的用户可能熟悉由zeallot
包引入并由keras
流行化的多重赋值操作符%<-%
。
我想提到的最后一个数据结构是 NumPy 数组。这与一维列表非常相似,但允许进行向量化等操作。例如:
# A list of distances
>>> dist
[584, 1054, 653]
# Some transformation function
>>> dist * 2
[584, 1054, 653, 584, 1054, 653]
这与使用 R 的用法非常不同。如果我们正在处理 NumPy 数组,我们将回到熟悉的领域:
# Make a numpy array
>>> dist_array = np.array(dist)
>>> dist_array * 2
array([1168, 2108, 1306])
索引和逻辑表达式
现在我们有了各种对象,让我们看看如何对它们进行索引。我们已经看到可以使用[]
甚至[[]]
,就像在 R 中看到的那样,但是有几个有趣的区别。请记住,Python 中的索引始终从 0 开始!另外,请注意,在 R 中最常见的操作符之一,即:
,在 Python 中以稍微不同的形式再次出现,这里是[start:end]
。
>>> dist_array
array([ 584, 1054, 653])
>>> dist_array[:2]
array([ 584, 1054])
>>> dist_array()
array([1054, 653])
:
运算符不需要左右两侧。如果一侧为空,则索引从开始或继续到末尾。起始处是包含的,如果指定了结束,那么结束是不包含的。因此:2
获取索引 0 和 1,1:
获取从索引 1 到最后一个未指定的元素,因此是包含的。
对于二维数据框架,我们遇到了 pandas 的.iloc
,“索引位置”和.loc
“位置”方法。
# Rows: 0th and 2nd
>>> plant_growth.iloc[[0,2]]
weight group
0 4.17 ctrl
2 5.18 ctrl
# Rows: 0th to 5th, exclusive
# Cols: 1st
>>> plant_growth.iloc[:5, 0]
0 4.17
1 5.58
2 5.18
3 6.11
4 4.50
对于.loc()
,我们可以引入逻辑表达式,即关系运算符和逻辑运算符的组合,以提出和组合True
/False
问题。
>>> plant_growth.loc[(plant_growth.weight <= 4)]
weight group
13 3.59 trt1
15 3.83 trt1
对于有关索引和逻辑表达式的更多详细信息,请参阅附录中的笔记。
绘图
好的,让我们来看看描述由组别权重的一些数据可视化。在这里,我们有一个箱线图:
sns.boxplot(x='group', y='weight', data=plant_growth)
plt.show()
只是这些点:
sns.catplot(x="group", y="weight", data=plant_growth)
plt.show()
只是均值和它们的标准偏差:
sns.catplot(x="group", y="weight", data=plant_growth, kind="point")
plt.show()
请注意,我正在使用seaborn
包(别名为sns
)进行数据可视化,然后使用matplotlib
的show()
函数将可视化结果打印到屏幕上。
推断统计
在这个数据集中,我们有一个特定的设置,即我们有三个组,并且我们对两个特定的两组比较感兴趣。我们可以通过建立线性模型来实现这一点。
# fit a linear model
# specify model
model = ols("weight ~ group", plant_growth)
# fit model
results = model.fit()
我们可以直接获取模型的系数:
# extract coefficients
results.params.Intercept
results.params["group[T.trt1]"]
results.params["group[T.trt2]"]
最后,让我们来看看我们模型的摘要:
# Explore model results
results.summary()
好的,让我们通过使用这种类型数据的典型统计测试来结束:单因素方差分析。请注意,我们正在使用我们上面拟合的模型results
。
# ANOVA
# compute anova
aov_table = sm.stats.anova_lm(results, typ=2)
# explore anova results
aov_table
print(aov_table)
如果我们想要进行所有成对比较,我们可以转向图基的 Tukey 显著性差异(HSD)事后检验:
from statsmodels.stats.multicomp import pairwise_tukeyhsd
print(pairwise_tukeyhsd(plant_growth['weight'], plant_growth['group']))
在这个例子中,我们从statsmodel
库开始,使用其中的stats
包和multicomp
模块,并从中提取特定的pairwise_tukeyhsd()
函数以导入。在第二行,我们使用连续变量作为第一个参数和分组变量作为第二个参数执行该函数。
最后的想法
在 R 中,自 2016 年以来,已经形成了关于常见实践和工作流程的共识。在 Python 中,有更多的多样性可以在一开始就上手。这种多样性可能看起来令人生畏,但它只是 Python 起源故事和现实世界用例的反映。
如果您是习惯于函数式编程世界的用户,那么掌握面向对象的方法也可能会显得非常困难,但一旦您跨越了这个障碍,您就可以开始利用 Python 真正闪耀的地方,即第三部分的话题。
^(1) 实际上,其他构建很少在合适的公司提到
^(2) 如果您想通过这种方式进入 macOS,请安装homebrew。
^(3) 您需要一个 Google 账户来访问这个免费资源。
^(4) 尽管 R 得到支持,但使用者很少在 VS Code 中工作。
^(5) 好吧,至少我们是某人的手下!
^(6) 敏锐的用户可能已经注意到,在 2020 年底,RStudio v1.4 增加了使用相同键盘快捷键调用的命令面板。
^(7) Python 执行后端。
^(8) 但是请记住,当我们开始同时使用 Python 和 R 时,因为我们会看到非常相似的东西。
^(9) 回顾一下log()
,你更有可能使用np.log()
而不是math.log()
,因为它接受更广泛的输入类型。
第三部分:双语 II:现代背景
在这一部分,您将深入了解这两种语言在现代背景下的应用,包括开源生态系统和有用的工作流程。
这是我们需要覆盖的两个维度,以便获得一个连贯的视角。通过深入研究两者,您将清楚地了解何时以及在何处使用哪种语言、开源软件包和工作流程。
第四章
在本章中,我们将讨论不同数据格式(例如图像或文本)如何被不同的软件包处理以及哪些是最佳的。
第五章
本章涵盖了现代工作流程的最有效方式(例如机器学习和可视化),以提高 R 和 Python 的生产效率。
第四章:数据格式背景
Boyan Angelov
在这一章中,我们将审查 Python 和 R 中用于导入和处理多种格式数据的工具。我们将涵盖一系列包,进行比较和对比,并突出它们的有效性。在这次旅程结束时,您将能够自信地选择包。每个部分通过一个特定的小案例研究展示工具的能力,这些案例基于数据科学家每天遇到的任务。如果您正在将工作从一种语言转换到另一种,或者只是想快速了解如何使用完整、维护良好和特定背景的包,本章将为您指引。
在我们深入讨论之前,请记住开源生态系统是不断变化的。新的发展,例如转换器模型和xAI,似乎每隔一周就会出现。这些通常旨在降低学习曲线并增加开发者的生产力。这种多样性的爆炸也适用于相关的包,导致几乎不断涌现新的(希望是)更好的工具。如果您有非常具体的问题,可能已经有适合您的包,因此您不必重新发明轮子。工具选择可能会令人不知所措,但同时这种多样性的选择可以提高数据科学工作的质量和速度。
本章中的包选择在视野上可能显得有限,因此澄清我们的选择标准是至关重要的。那么,一个好的工具应该具备哪些特质?
-
它应该开源:有大量有价值的商业工具可用,但我们坚信开源工具具有巨大的优势。它们往往更容易扩展和理解其内部工作原理,并且更受欢迎。
-
它应该功能完备:该包应包括一套全面的函数,帮助用户在不依赖其他工具的情况下完成他们的基础工作。
-
它应该维护良好:使用开源软件(OSS)的一个缺点是,有时包的生命周期很短,它们的维护被放弃(所谓的“废弃软件”)。我们希望使用那些正在积极开发的包,这样我们就可以确信它们是最新的。
让我们从一个定义开始。什么是“数据格式”?有几种答案可供选择。可能的候选者包括数据类型、录音格式和文件格式。数据类型与存储在数据库中的数据或编程语言中的类型有关(例如整数、浮点数或字符串)。录音格式是数据如何存储在物理介质上,例如 CD 或 DVD。最后,我们关注的是文件格式,即信息如何为计算目的准备。
有了这个定义,人们可能会想,为什么我们要专门用一整章节来讨论文件格式呢?也许你在其他情境下已经接触过它们,比如用.ppt
或.pptx
扩展名保存 PowerPoint 幻灯片(并且想知道哪个更好)。然而,问题远不止于基本工具的兼容性。信息存储的方式会影响整个下游数据科学流程。例如,如果我们的最终目标是进行高级分析,而信息以文本格式存储,我们必须关注诸如字符编码等因素(这是一个臭名昭著的问题,尤其是对于 Python(1))。为了有效处理这些数据,还需要经历几个步骤(2),比如tokenization和stop word移除。然而,这些步骤对于图像数据则不适用,即使我们的最终目标可能是相同的,比如分类。在这种情况下,更适合的是其他处理技术,比如调整大小和缩放。这些数据处理管道的差异展示在 pipelines_diff 上。总结一下:数据格式是影响你能够做什么、不能做什么的最重要因素。
注意
现在我们在这个上下文中首次使用“管道”这个词,让我们利用这个机会来定义它。你可能听说过“数据是新的石油”的表达。这个表达不仅仅是一种简单的营销策略,而是一种有用的思考数据的方式。在数据和石油处理之间存在惊人的许多类似之处。你可以想象,企业收集的初始数据是最原始的形式,最初可能有限的用途。然后,在它被用于某些应用程序之前,它经历了一系列称为数据处理的步骤(例如用于训练 ML 模型或供给仪表盘)。在石油处理中,这被称为精炼和丰富化 - 使数据对业务目的可用。管道将不同类型的石油(原油、精炼油)通过系统传输到最终状态。在数据科学和工程中,同样的术语可以用来描述处理和交付数据所需的基础设施和技术。
- 常见数据格式管道之间的差异。绿色表示工作流程之间的共享步骤。image::img/pipelines_diff.jpg[""]
在处理特定数据格式时,还需要考虑基础设施和性能。例如,对于图像数据,您需要更多的存储空间。对于时间序列数据,您可能需要使用特定的数据库,例如Influx DB。最后,在性能方面,图像分类通常使用基于卷积神经网络(CNN)的深度学习方法来解决,这可能需要图形处理单元(GPU)。如果没有,模型训练可能会非常缓慢,并成为开发工作和潜在生产部署的瓶颈。
现在我们已经介绍了仔细考虑使用哪些包的原因,让我们来看看可能的数据格式。这个概述在表 4-1 中展示(请注意,这些工具主要设计用于小到中等规模的数据集)。诚然,我们只是浅尝辄止,还有一些显著的遗漏(比如音频和视频)。在这里,我们将专注于最常用的格式。
表 4-1. 数据格式及用于处理其中存储数据的流行 Python 和 R 包概览。
数据类型 | Python 包 | R 包 |
---|---|---|
表格 | pandas |
readr ,rio |
图像 | open-cv ,scikit-image ,PIL |
magickr ,imager ,EBImage |
文本 | nltk ,spaCy |
tidytext ,stringr |
时间序列 | prophet ,sktime |
prophet ,ts ,zoo |
空间 | gdal ,geopandas ,pysal |
rgdal ,sp ,sf ,raster |
这张表远非详尽无遗,我们确信新工具将很快出现,但这些工具是实现我们选择标准的工作马,让我们在接下来的章节中看看它们的表现,看看哪些是最适合这项工作的!
外部与基础包
在第二章和第三章中,我们在学习过程的早期就介绍了包。在 Python 中,我们在一开始就使用了pandas
,而在 R 中,我们也相对迅速地过渡到了tidyverse
。这使我们比如果深入探讨那些初学者不太可能需要的过时语言特性,更快地提高了生产力^(3)。一个编程语言的实用性取决于其第三方包的可用性和质量,而不是语言本身的核心特性。
注
这并不意味着仅仅使用基础 R 就可以完成很多事情(正如您将在即将看到的一些示例中所见),但利用开源生态系统是提高您生产力的基本技能,避免重复造轮子。
回头学习基础知识
过度使用第三方包存在风险,你必须知道何时是回归基础的正确时机。否则,你可能会陷入虚假的安全感,并依赖于像pandas
这样的工具提供的支持。这可能导致在处理更具体的现实世界挑战时遇到困难。
现在让我们通过详细讨论一个我们已经熟悉的主题,来看看这个包与基础语言概念在实践中是如何发挥作用的:表格数据^(4)。在 Python 中至少有两种方法可以做到这一点。首先是使用pandas
:
import pandas as pd
data = pd.read_csv(“dataset.csv”)
其次是使用内置的csv
模块:
import csv
with open(“dataset.csv”, “r”) as file: 
reader = csv.reader(file, delimiter=“,”)
for row in reader: 
print(row)
注意你需要指定file mode,在本例中是"r"
(表示“读取”)。这是为了确保文件不会意外被覆盖,这暗示了一种更面向通用目的的语言。
对于初学者来说,使用循环读取文件可能看起来很奇怪,但这使过程更加明确。
这个例子告诉我们,pandas
中的pd.read_csv()
提供了一种更简洁、方便和直观的导入数据方式。它比纯粹的 Python 少了明确,也不是必需的。pd.read_csv()
本质上是现有功能的便利封装 —— 对我们很有帮助!
在这里,我们看到包有两个功能。首先,正如我们所期望的那样,它们提供新的功能。其次,它们还是现有标准功能的便利封装,使我们的生活更轻松。
这在 R 的rio
包中得到了很好的展示^(5)。rio
代表“R input/output”,它确实如其名称所示^(6)。在这里,单个函数import()
使用文件的文件名扩展名来选择在一系列包中导入的最佳函数。这适用于 Excel、SPSS、stata、SAS 或许多其他常见格式。
另一个 R tidyverse 包vroom
允许快速导入表格数据,并可以在一个命令中读取整个目录的文件,使用map()
函数或for loops
。
最后,经常被忽视以推广 tidyverse 为代价的data.table
包提供了出色的fread()
,它可以以远低于基础 R 或readr
提供的速度导入非常大的文件。
学习如何使用第三方包的实用性在我们尝试执行更复杂的任务时变得更加明显,正如我们下面在处理其他数据格式时将会看到。
现在我们可以欣赏包的优势,我们将展示它们的一些功能。为此,我们将处理几个不同的真实用例,列在 表 4-2 中。我们不会专注于细节实现,而是覆盖那些暴露它们在任务中优点(和缺点)的元素。由于本章重点是数据格式,而 第五章 则全面介绍工作流程,所有这些案例研究都涉及数据处理(如前面在 ??? 中所示)。
注意
为了教学目的,我们省略了部分代码。如果您想跟着做,请查看 书籍存储库 中的可执行代码。
表 4-2. 不同用例的概述
数据格式 | 使用案例 |
---|---|
图像 | 游泳池和汽车检测 |
文本 | Amazon 音乐评论处理 |
时间序列 | 澳大利亚日温度 |
空间 | Loxodonta africana 物种分布数据 |
更多关于如何下载和处理这些数据的信息可以在官方 存储库 中找到。
图像数据
对数据科学家来说,图像提出了一系列独特的挑战。我们将通过处理航空图像处理的挑战来演示最佳方法,这是农业、生物多样性保护、城市规划和气候变化研究日益重要的领域。我们的迷你用例使用了来自 Kaggle 的数据,旨在帮助检测游泳池和汽车。有关数据集的更多信息,请使用 表 4-2 中的网址。
OpenCV 和 scikit-image
正如我们在本章开头提到的,下游目的极大地影响数据处理。由于航空数据经常用于训练机器学习算法,我们的重点将放在准备性任务上。
OpenCV 包是在 Python 中处理图像数据的最常见方法之一。它包含了加载、操作和存储图像所需的所有工具。名称中的 “CV” 代表计算机视觉 - 这是专注于图像数据的机器学习领域。我们还将使用的另一个便捷工具是 scikit-image
。正如其名称所示,它与 scikit-learn^(7) 密切相关。
这是我们任务的步骤(参见 表 4-2):
-
调整图像大小到特定尺寸
-
将图像转换为黑白
-
通过旋转图像增强数据
要使 ML 算法成功地从数据中学习,必须对输入进行清洗(数据整理),标准化(即缩放)和过滤(特征工程)^(8)。你可以想象收集一组图像数据集(例如从 Google 图像中爬取^(9)数据)。它们可能在大小和/或颜色等方面有所不同。我们任务列表中的步骤 1 和 2 帮助我们处理这些差异。第 3 步对 ML 应用非常有用。ML 算法的性能(例如分类准确度或曲线下面积(AUC))主要取决于训练数据的数量,通常供应量有限。为了解决这个问题,而不是获取更多数据(10),数据科学家发现,对已有数据进行操作,如旋转和裁剪,可以引入新的数据点。然后可以再次用这些数据点来训练模型并提高性能。这个过程正式称为数据增强(11)。
足够的讨论 - 让我们从导入数据开始吧!如果你想跟着做,请检查本书的代码仓库。
import cv2 
single_image = cv2.imread("img_01.jpg")
plt.imshow(single_image)
plt.show()
使用cv2
可能看起来令人困惑,因为包名为OpenCV
。cv2
被用作缩写名称。与scikit-image
相同的命名模式,在导入语句中缩短为skimage
。
图 4-1. Python 中使用matplotlib
绘制的原始图像。
那么cv2
将数据存储在哪种对象类型中?我们可以用type
来检查:
print(type(single_image))
numpy.ndarray
在这里,我们可以观察到一个重要的特性,它已经为使用 Python 进行 CV 任务相对于 R 提供了优势。图像直接存储为numpy
多维数组(nd
代表 n 维),使其可以访问 Python 生态系统中的各种其他工具。因为这是建立在pyData
堆栈上的,所以得到了良好的支持。这对于 R 也适用吗?让我们看看magick
包:
library(magick)
single_image <- image_read('img_01.jpg')
class(single_image)
[1] "magick-image"
magick-image
类仅限于来自magick
包或其他密切相关工具的函数使用,而不是强大的基础 R 方法(例如第二章中展示的方法,特别是plot()
除外)。各种开源包之间如何支持彼此的不同方法在本章的示例中有所体现,如图 4-2 所示,是一个共同的主题。
注意
至少有一个例外情况符合这一规则 - EBImage
包,它是Bioconductor的一部分。通过使用它,您可以以原始数组形式访问图像,然后在此基础上使用其他工具。这里的缺点是它是一个特定领域的包的一部分,可能不容易在标准 CV 流水线中看到它是如何工作的。
图 4-2. 两种包设计层次结构,在数据生命周期中使用(从底部到顶部)。左侧模式显示了一个次优结构,用户被迫在第一级别使用特定目的的工具,这限制了他们的灵活性和生产力。右侧模式显示了一个更好的结构,在数据传递的初始阶段有标准工具,从而在下游能够使用多种工具。
注意,在前一步骤中(加载 Python 中的原始图像时),我们还使用了最流行的绘图工具之一 - matplotlib
(数据可视化在第五章中有所涉及),因此我们再次利用了这种更好的设计模式。
现在我们知道图像数据存储为numpy
的ndarray
,我们可以使用numpy
的方法。图像的尺寸是多少?我们可以尝试ndarray
的.shape
方法来获取:
print(single_image.shape)
224 224 3
确实有效!前两个输出值分别对应于图像的height
和width
,第三个则是图像中的通道数 - 在这种情况下是三个 ((r)ed, (g)reen 和 (b)lue)。现在让我们继续并完成第一步标准化 - 图像调整大小。在这里我们将第一次使用cv2
:
single_image = cv2.resize(single_image,(150, 150))
print(single_image.shape)
(150, 150, 3)
注意
如果你在两种语言中都有使用这些基础工具的经验,你将能够快速测试你的想法,即使不知道这些方法是否存在。如果你使用的工具设计良好(如图 4-2 中的更好设计),通常它们会按预期工作!
完美,它的运行效果如同魔术一般!接下来的步骤是将图像转换为黑白。为此,我们同样会使用cv2
:
gray_image = cv2.cvtColor(single_image, cv2.COLOR_RGB2GRAY)
print(gray_image.shape)
(150, 150)
颜色呈绿色调,而非灰色。默认选项选择了一种颜色方案,使得对人眼的对比更容易辨别,比黑白色更加明显。当你观察numpy
的ndarray
形状时,你会发现通道数已经消失了 - 现在只剩下一个。现在让我们完成我们的任务,并进行简单的数据增强步骤,将图像水平翻转。在这里,我们再次利用数据存储为numpy
数组的优势。我们将直接使用numpy
中的一个函数,而不依赖于其他 CV 库(如OpenCV
或scikit-image
):
flipped_image = np.fliplr(gray_image)
结果显示在图 4-3 中。
图 4-3. 使用numpy
函数翻转图像的绘图。
我们可以使用scikit-image
进行更多的图像操作任务,比如旋转,即使使用这种不同的包也能按预期在我们的数据格式上工作:
from skimage import transform
rotated_image = transform.rotate(single_image, angle=45)
我们经历的数据标准化和增强步骤说明了较简单的包设计(图 4-2)如何提高我们的生产力。我们可以通过展示第三步的负面例子来进一步强调这一点,这次是在 R 语言中。为此,我们将依赖于adimpro
包:
library(adimpro)
rotate.image(single_image, angle = 90, compress=NULL)
每当我们加载另一个包时,都会降低我们代码的质量、可读性和可重用性。这个问题主要是由于可能存在未知的 bug、更陡的学习曲线或第三方包缺乏一致和详尽的文档。通过快速检查CRAN 上的adimpro
,可以发现它最后一次更新是在 2019 年 11 月(12)。这就是为什么更倾向于使用像`OpenCV`这样的工具,它利用`PyData`堆栈(13)处理图像数据,如numpy
。
一个较少复杂、模块化且足够抽象的包设计可以大大提高数据科学家在使用工具时的生产力和满意度。他们可以自由专注于实际工作,而不是处理复杂的文档或大量弃用的包。这些考虑使得 Python 在导入和处理图像数据方面成为明显的优选,但对其他格式是否也是如此呢?
文本数据
文本数据分析通常与自然语言处理(NLP)术语交替使用。这又是 ML 的一个子领域。因此,Python 工具的主导地位并不奇怪。处理文本数据本质上需要大量计算资源,这也是为什么 Python 工具主导的一个很好的原因。另一个原因是,在 R 中处理更大的数据集可能比在 Python 中更具挑战性^(14)(关于此主题的更多内容,请参阅第五章)。这是一个大数据问题。随着互联网服务和 Twitter、Facebook 等社交媒体巨头的兴起,文本数据量近年来激增。这些组织也大力投资于相关技术和开源,因为他们可获取的大部分数据都是文本格式。
与图像数据类似,我们将设计一个标准的 NLP 任务。它应包含 NLP 流水线的最基本元素。作为数据集,我们选择了来自亚马逊产品评论数据集的文本(见表 4-2),并需准备用于文本分类、情感分析或主题建模等高级分析用例。完成所需的步骤如下:
-
对数据进行分词
-
去除停用词
-
标记词性(PoS)
我们还将通过spaCy
介绍更高级的方法(如词嵌入),以展示 Python 包的功能,并同时提供几个 R 示例以进行比较。
NLTK 和 spaCy
那么 Python 中最常用的工具是什么?最流行的工具之一通常被称为 NLP 的瑞士军刀 - 自然语言工具包(NLTK)^(15)。它包含了涵盖整个流程的丰富工具集。它还具有出色的文档和相对较低的 API 学习曲线。
NLTK 书籍
NLTK 的作者们还写了一本关于文本数据处理最易于理解的书籍 - NLTK 书籍,当前版本为 3. 它可以免费在线阅读,网址在官方网站。这可以作为一个很好的参考手册,如果你想深入了解我们在本节中涵盖的一些主题,请随时查阅!
作为一名数据科学家,在项目中的第一步之一是查看原始数据。这里是一个示例评论及其数据类型:
example_review = reviews["reviewText"].sample()
print(example_review)
print(type(example_review))
I just recently purchased her ''Paint The Sky With Stars''
CD and was so impressed that I bought 3 of her previously
released CD's and plan to buy all her music. She is
truely talented and her music is very unique with a
combination of modern classical and pop with a hint of
an Angelic tone. I still recommend you buy this CD. Anybody
who has an appreciation for music will certainly enjoy her music.
str
这里很重要 - 数据存储在 Python 中的一个基本数据类型中 - str
(字符串)。类似于将图像数据存储为多维numpy
数组,许多其他工具也可以访问它。例如,假设我们要使用一种能够高效搜索和替换字符串部分的工具,比如flashtext。在这种情况下,我们可以在这里使用它而不会出现格式问题,也无需强制转换^(16)数据类型。
现在我们可以在我们的迷你案例研究中迈出第一步 - 分词。它将评论分割成词或句子等组件:
sentences = nltk.sent_tokenize(example_review)
print(sentences)
["I just recently purchased her ''Paint The Sky With Stars''
CD and was so impressed that I bought 3 of her
previously released CD's and plan to buy all her music.",
'She is truely talented and her music is very unique with
a combination of modern classical and pop with a hint of an Angelic tone.',
'I still recommend you buy this CD.',
'Anybody who has an appreciation for music will certainly enjoy her music.']
足够简单!为了举例说明,在 R 中使用一些来自tidytext
的函数尝试这个相对简单的任务会有多困难?
tidy_reviews <- amazon_reviews %>%
unnest_tokens(word, reviewText) %>%
mutate(word = lemmatize_strings(word, dictionary = lexicon::hash_lemmas))
这是使用的最详细记录方法之一。其中一个问题是它过于依赖“整洁数据”概念,以及dplyr
中的管道连接概念(我们在第二章中涵盖了这两个概念)。这些概念特定于 R 语言,如果要成功使用tidytext
,首先必须学习它们,而不能直接开始处理数据。第二个问题是此过程的输出 - 包含处理列中数据的新data.frame
。虽然这可能是最终所需的内容,但它跳过了一些中间步骤,比我们使用nltk
时的抽象层级要高几层。降低这种抽象并以更模块化的方式工作(例如首先处理单个文本字段)符合软件开发的最佳实践,如 DRY(“不要重复自己”)和关注点分离。
我们小型 NLP 数据处理流水线的第二步是移除停用词^(17)。
tidy_reviews <- tidy_reviews %>%
anti_join(stop_words)
这段代码存在与之前相同的问题,还有一个新的令人困惑的函数 - anti_join
。让我们来比较一下简单的列表推导(更多信息见第三章中的nltk
步骤):
english_stop_words = set(stopwords.words("english"))
cleaned_words = [word for word in words if word not in english_stop_words]
english_stop_words
只是一个list
,然后我们只需循环遍历另一个list
(words
)中的每个单词,并删除如果它同时存在于两者中。这更易于理解。没有依赖于与直接相关的高级概念或函数。这段代码也处于正确的抽象级别。小代码块可以更灵活地作为更大文本处理管道函数的一部分使用。在 R 中,类似的“元”处理函数可能会变得臃肿 - 执行缓慢且难以阅读。
虽然nltk
可以执行这些基本任务,但现在我们将看一看更高级的包 - spaCy
。在我们的案例研究的第三和最后一步中,我们将使用它进行词性标注(Part of Speech,PoS)^(18)。
import spacy
nlp = spacy.load("en_core_web_sm") 
doc = nlp(example_review) 
print(type(doc))
spacy.tokens.doc.Doc
在这里,我们通过一个函数加载所有我们需要的高级功能。
我们取一条示例评论并将其提供给spaCy
模型,结果是spacy.tokens.doc.Doc
类型,而不是str
。然后可以对该对象进行各种其他操作:
for token in doc:
print(token.text, token.pos_)
数据在加载时已经进行了标记。不仅如此 - 所有的词性标注也已经完成了!
我们所涉及的数据处理步骤相对基础。那么,再来看看一些更新、更高级的自然语言处理方法呢?例如,我们可以考虑词嵌入。这是更高级的文本向量化方法之一,其中每个向量表示一个词在其上下文中的含义^(19)。为此,我们可以直接使用来自spaCy
代码的同一个nlp
对象:
for token in doc:
print(token.text, token.has_vector, token.vector_norm, token.is_oov)
for token in doc:...
I True 21.885008 True
just True 22.404057 True
recently True 23.668447 True
purchased True 23.86188 True
her True 21.763712 True
' True 18.825636 True
看到这些功能已经内置在其中一款最受欢迎的 Python 自然语言处理包中真是个惊喜。在这个自然语言处理方法的水平上,我们几乎看不到 R(或其他语言)的任何替代方案。许多在 R 中类似的解决方案依赖于围绕 Python 后端的包装代码(这可能违背使用 R 语言的初衷)(20)。这种模式在书中经常见到,特别是在第五章。对于一些其他高级方法,如变换器模型,也是如此(21)。
对于第二轮来说,Python 再次胜出。nltk
、spaCy
以及其他相关包的能力使其成为进行自然语言处理工作的优秀选择!
时间序列数据
时间序列格式用于存储具有时间维度的任何数据。它可以是简单的本地杂货店洗发水销售数据,带有时间戳,也可以是从农业领域湿度传感器网络中测量的数百万数据点。
注意
R 在时间序列数据分析中的统治地位也有一些例外情况。特别是,最近深度学习方法的发展,尤其是长短期记忆网络(LSTM),已被证明在时间序列预测方面非常成功。就像其他深度学习方法一样(详见第五章),这是一个更好地由 Python 工具支持的领域。
Base R
R 用户可以使用相当多的不同包来分析时间序列数据,包括xts
和zoo
,但我们将以 base R 函数为起点。在此之后,我们将看一看一个更现代的包,以说明更高级的功能 - Facebook 的 Prophet。
天气数据既易获取又相对容易解释,因此在我们的案例研究中,我们将分析澳大利亚的每日最低温度(详见表 4-2)。要进行时间序列分析,我们需要执行以下步骤:
-
将数据加载到适当的格式中
-
绘制数据
-
去除噪声和季节性影响并提取趋势
然后我们就能进行更高级的分析了。假设我们已经将数据从.csv
文件加载到 R 中的data.frame
对象中。这里没有什么特别之处。然而,与大多数 Python 包不同,R 需要将数据强制转换为特定的对象类型。在这种情况下,我们需要将data.frame
转换为ts
(代表时间序列)。
df_ts <- ts(ts_data_raw$Temp, start=c(1981, 01, 01),
end=c(1990, 12, 31), frequency=365)
class(df_ts)
那么,为什么我们要选择它而不是pandas
呢?嗯,即使你成功地将原始数据转换为时间序列pd.DataFrame
,你还会遇到一个新概念 - DataFrame
索引(见图 4-4)。要高效地进行数据操作,你需要先了解这是如何工作的!
图 4-4. pandas
中的时间序列索引。
这个索引概念可能会让人感到困惑,所以现在让我们看看 R 中的替代方法以及是否更好。使用df_ts
时间序列对象,我们已经可以做一些有用的事情。当你在 R 中使用更高级的时间序列包时,这也是一个很好的起点,因为将ts
对象转换为xts
或zoo
应该不会出错(这再次是我们在图 4-2 中讨论的良好对象设计的一个例子)。你可以尝试做的第一件事是对对象进行plot
,这在 R 中通常会产生良好的结果:
plot(df_ts)
注意
调用plot
函数并不仅仅是使用一个标准函数,可以在 R 中绘制各种不同的对象(这是你所期望的)。它调用与数据对象关联的特定方法(关于函数和方法之间的区别的更多信息,请参阅第二章)。这个简单函数调用背后隐藏了很多复杂性!
图 4-5. 在 base R 中绘制时间序列(ts
)对象。
在 图 4-5 上的 plot(df_ts)
结果已经很有用。x 轴上的日期被识别,并选择了 line
绘图,而不是默认的 points
绘图。分析时间序列数据(以及大多数机器学习数据)中最普遍的问题是处理噪声。与其他数据格式的不同之处在于存在几种不同的噪声来源,可以清除不同的模式。这是通过一种称为分解的技术实现的,我们有内置且命名良好的函数 decompose
来完成这一点:
decomposed_ts <- decompose(df_ts)
plot(decomposed_ts)
结果可以在 图 4-6 上看到。
图 4-6. 在基本 R 中绘制的分解时间序列图。
我们可以看出随机噪声和季节性以及总体模式是什么。我们只需在基本 R 中进行一次函数调用就实现了所有这些!在 Python 中,我们需要使用 statsmodels
包来达到同样的效果。
prophet
对于分析时间序列数据,我们还有另一个令人兴奋的包例子。它同时为 R 和 Python 开发(类似于 lime
可解释的 ML 工具) - Facebook Prophet。这个例子可以帮助我们比较 API 设计上的差异。prophet
是一个主要优势在于领域用户能够根据其特定需求进行调整,API 使用便捷且专注于生产就绪的包。这些因素使其成为原型化时间序列工作和在数据产品中使用的不错选择。让我们来看看;我们的数据存储为 pandas
的 DataFrame
在 df
中:
from fbprophet import Prophet
m = Prophet()
m.fit(df) 
future = m.make_future_dataframe(periods=365) 
future.tail()
在这里,我们再次看到相同的 fit
API 模式,借鉴自 scikit-learn
。
这一步创建了一个新的空的 Data.Frame
,以便稍后存储我们的预测结果。
library(prophet)
m <- prophet(df)
future <- make_future_dataframe(m, periods = 365)
tail(future)
两者都足够简单,并包含相同数量的步骤 - 这是一致的 API 设计的一个很好的例子(在 第 5 章 中详细讨论)。
注意
提供跨语言一致的用户体验是一个有趣且有帮助的想法,但我们预计它不会被广泛实现。很少有组织拥有进行这种工作的资源,这可能限制选择软件设计的折衷。
此时,您可以感受到同时掌握两种语言将为您的日常工作带来重大优势。如果您只接触过 Python 包生态系统,您可能会尝试寻找类似的工具来分析时间序列,从而错失基本 R 和相关 R 包提供的令人难以置信的机会。
空间数据
空间数据分析是现代机器学习中最有前景的领域之一,并且具有丰富的历史。近年来已开发出新工具,但长期以来 R 一直占据优势,尽管 Python 近年来也有了一些进展。与之前的部分一样,我们将通过一个实际例子来看看这些包是如何运作的。
注意
空间数据有几种格式可用。在本小节中,我们专注于对raster数据的分析。对于其他格式,在 Python 中有一些有趣的工具可用,比如GeoPandas,但这超出了本章的范围。
我们的任务是处理Loxodonta africana(非洲象)的出现数据^(22)和环境数据,使其适用于空间预测。这种数据处理在物种分布建模(SDM)中很典型,其中预测用于构建用于保护的栖息地适宜性地图。这个案例研究比之前的更复杂,并且许多步骤中隐藏了一些复杂性,包装程序正在进行大量的操作。步骤如下:
-
获取环境栅格数据
-
裁剪栅格以适应感兴趣的区域
-
使用抽样方法处理空间自相关
栅格
为了解决这个问题的第一步,我们需要处理栅格数据^(23)。从某种意义上说,这与标准图像数据非常相似,但在处理步骤上仍然有所不同。对此,R 语言有出色的raster
包可用(另一种选择是 Python 的gdal
和 R 的rgdal
,但在我们看来更难使用)。
library(raster)
climate_variables <- getData(name = "worldclim", var = "bio", res = 10)
raster
允许我们下载大多数常见的有用的空间环境数据集,包括生物气候数据^(24)。
e <- extent(xmin, xmax, ymin, ymax)
coords_absence <- dismo::randomPoints(climate_variables, 10000, ext = e)
points_absence <- sp::SpatialPoints(coords_absence,
proj4string = climate_variables@crs)
env_absence <- raster::extract(climate_variables, points_absence)
这里我们使用便捷的extent
函数来裁剪(切割)栅格数据 - 我们只对围绕出现数据的所有环境层的一个子集感兴趣。在这里,我们使用经度和纬度坐标来绘制这个矩形。作为下一步,为了形成一个分类问题,我们从栅格数据中随机抽样数据点(这些被称为“伪缺失”)。你可以想象这些是我们分类任务中的0
,而出现(观测)则是1
- 目标变量。然后我们将伪缺失转换为空间点
,最后也提取它们的气候数据。在SpatialPoints
函数中,你还可以看到我们如何指定地理投影系统,这是分析空间数据时的基本概念之一。
在进行 ML 工作时,最常见的问题之一是数据内的相关性。正确数据集的基本假设是数据中的个体观察结果彼此独立,以获得准确的统计结果。由于其本质的原因,空间数据始终存在这个问题。这个问题被称为空间自相关。有几个可用于从数据中抽样以减轻这一风险的包。其中一个这样的包是ENMeval
:
library(ENMeval)
check1 <- get.checkerboard1(occs, envs, bg, aggregation.factor=5)
get.checkerboard1
函数以均匀分布的方式对数据进行采样,类似于从黑白棋盘的每个方块中取等量的点。然后,我们可以使用这些重新采样的数据成功训练 ML 模型,而不必担心空间自相关。最后一步,我们可以取得这些预测并创建生境适宜性地图,显示在(???)。
raster_prediction <- predict(predictors, model)
plot(raster_prediction)
- 在 R 中绘制
raster
对象预测,生成生境适宜性地图。image::img/sdm_map.png[""]
当您处理空间栅格数据时,R 提供了更好的包设计。像raster
这样的基础工具为更高级的特定应用程序(如ENMeval
和dismo
)提供了一致的基础,而无需担心复杂的转换或易出错的类型强制转换。
最终思考
在本章中,我们详细介绍了不同的常见数据格式,以及处理它们准备进行高级任务的最佳包。在每个案例研究中,我们展示了一个良好的包设计如何使数据科学家更加高效。我们已经看到,对于更多以 ML 为重点的任务,如 CV 和 NLP,Python 提供了更好的用户体验和更低的学习曲线。相比之下,对于时间序列预测和空间分析,R 则占据了上风。这些选择在图 4-7 中显示。
图 4-7. 包选择的决策树。
最佳工具的共同之处在于更好的包设计(图 4-2)。您应该始终使用最适合工作的工具,并注意您使用的工具的复杂性、文档和性能!
^(1) 欲了解更详细的解释,请查看此处:https://realpython.com/python-encodings-guide/。
^(2) 这通常被称为“数据血统”。
^(3) 还有谁没有学会在 Python 中if __name__ == "__main__"
是做什么的?
^(4) 数据中的一个表,存储在一个文件中。
^(5) 别忘了tidyr
,它在第二章中已经讨论过了。
^(6) 我们确实提到统计学家非常字面吧?
^(7) 这种一致性是第三部分的各章节中的共同主题,并在第五章中另外讨论。
^(8) 记住 - 输入垃圾,输出垃圾。
^(9) 使用代码浏览网页内容,下载并以机器可读格式存储。
^(10) 这在某些情况下可能是昂贵的,甚至是不可能的。
^(11) 如果你想了解更多关于图像数据增强的信息,请查看此教程。
^(12) 在撰写时。
^(13) 不要与同名会议混淆,PyData 堆栈指的是NumPy
、SciPy
、Pandas
、IPython
和matplotlib
。
^(14) R 社区也响应了这一号召,并在最近改进了工具链,但与其 Python 对应部分相比仍有一定差距。
^(15) 欲了解更多关于 NLTK 的信息,请查阅官方书籍,链接在这里。
^(16) 数据类型强制转换是将一个数据类型转换为另一个的过程。
^(17) 这是自然语言处理中的常见步骤。一些停用词的例子包括“the”、“a”和“this”。这些词需要移除,因为它们很少提供有用的信息给机器学习算法。
^(18) 将词语标记为它们所属的词性的过程。
^(19) 将文本转换为数字,以便机器学习算法处理。
^(20) 例如尝试创建自定义嵌入。更多信息请查阅 RStudio 博客,链接在这里。
^(21) 您可以在这里阅读更多相关信息。
^(22) 在野外记录的动物位置标记观测。
^(23) 表示单元格的数据,其中单元格值代表某些信息。
^(24) 生态学家确定的高度预测物种分布的环境特征,例如湿度和温度。
第五章:工作流上下文
Boyan Angelov
数据科学家常见的挫败感来源于与邻域领域同事讨论工作。让我们以一个例子来说明,一个主要从事机器学习(ML)模型开发的人,与商业智能(BI)团队的同事交流。这种讨论往往会让双方感到不适,因为他们认为彼此对对方工作领域(及相关工作流程)了解不足,尽管拥有相同的职位头衔。ML 的人可能会想知道 D3.js 是什么,图形语法是什么,等等?另一方面,BI 数据科学家可能会因不知如何构建可部署的 API 而感到不安。这种情况可能会引发“冒名顶替综合征”,即对自己能力的怀疑。这种情况是数据科学应用潜力巨大的副产品。一个人很少能对多个子领域有同样程度的熟悉。在这个快速发展的领域中,通常需要灵活性。
此复杂性为本章工作流重点奠定了基础。我们将讨论主要的数据科学工作流以及语言的不同生态系统如何支持它们。就像第四章一样,在本章末尾,您将拥有做出关于工作流的明智决策所需的一切。
定义工作流程
让我们退一步,定义一个工作流程:
工作流是从特定职能角度执行所有任务所需的所有工具和框架的完整集合。
以 ML 工程师为例,您的日常任务可能包括获取数据的工具、处理数据、在其上训练模型以及部署框架。这些集合共同代表了 ML 工程师的工作流程。关于此和其他职位的数据工作流程概述及其支持工具,见表 5-1。
第 5-1 表。常见数据科学工作流程及其支持工具。
方法 | Python 软件包 | R 软件包 |
---|---|---|
数据整理^(a) | pandas |
dplyr |
EDA | matplotlib , seaborn , pandas |
ggplot2 , base-r , leaflet |
机器学习 | scikit-learn |
mlr , tidymodels , caret |
深度学习 | keras , tensorflow , pytorch |
keras , tensorflow , torch |
数据工程^(b) | flask , bentoML , fastapi |
plumber |
报告 | jupyter , streamlit |
rmarkdown , shiny |
^(a) 数据整理(或称为数据处理)是数据科学中如此基础的主题,以至于已在第二章中讨论过。^(b) 数据工程不仅仅是模型部署,但我们决定聚焦于这一子集,以展示 Python 的能力。 |
我们省略了一些领域,希望列出的是最常见和关键的。所选的工作流程彼此相关,如在图 5-1 中所示。这个图表在很大程度上借鉴了CRISP-DM框架,展示了典型数据科学项目中的所有重要步骤。图表中的每个步骤都有一个单独的工作流与之关联,通常分配给个人或团队。
图 5-1. 数据科学与工程中的元工作流。
现在我们已经定义了一个工作流程,那么一个“好”的工作流程的定义属性是什么?我们可以编制一个包含三个主要因素的检查列表:
-
它已经被充分确立。它被社区广泛采纳(也跨越不同的应用领域,如计算机视觉或自然语言处理)。
-
它由一个维护良好的开源生态系统和社区支持。依赖于封闭源和商业应用程序(如 Matlab)的工作流程不被认为是可接受的。
-
它适用于重叠的工作职能。最佳工作流程类似于乐高积木 - 它们的模块化设计和可扩展性可以支持各种技术堆栈。
通过全局视图和定义,让我们更深入地了解不同的工作流程及其在 R 和 Python 中的支持!
探索性数据分析
查看数字是困难的。查看包含数以百万计数据的数据行更具挑战性。任何处理数据的人每天都面临这一挑战。这种需求促使数据可视化(DV)工具的显著发展。该领域的一个最新趋势是自助分析工具的爆炸性增长,例如Tableau、Alteryx和Microsoft PowerBI。这些工具非常有用,但开源世界中也有许多可用的替代品,通常能够竞争甚至超过其商业对手的能力(除了在某些情况下的易用性)。这些工具共同代表了 EDA 工作流。
何时使用 GUI 进行 EDA
许多数据科学家对使用图形用户界面(GUI)进行日常工作持反感态度。他们更喜欢命令行工具的灵活性和效用。然而,在某些情况下,使用 GUI 更为合理(出于生产力考虑),例如在 EDA 中。生成多个图表可能非常耗时,尤其是在数据科学项目的初期阶段。通常需要创建数十甚至数百个图表。想象一下为每个图表编写代码(即使通过重构代码为函数来改进代码的组织方式)。对于一些较大的数据集,使用一些 GUI 工具,如 AWS Quicksight 或 Google Data Studio,通常更为便捷。通过使用 GUI,数据科学家可以快速生成大量图表,然后只需为通过筛选的图表编写代码。还有一些不错的开源 GUI 工具,例如 Orange。
EDA 是对任何数据源进行分析的基础步骤。通常在数据加载后直接进行,在业务理解方面有显著需求的阶段。这解释了为什么它是一个必要的步骤。你可能熟悉 垃圾进,垃圾出 的范式 - 任何数据项目的质量取决于输入数据的质量和背后的领域知识。EDA 促进了下游工作流程(如 ML)的成功,确保数据和其背后的假设都是正确且具有足够的质量。
在 EDA 中,R 拥有比 Python 更好的工具。正如我们在 第一章 和 第二章 中讨论的,R 是一门由统计学家为统计学家开发的语言(还记得第二章中的 FUBU 吗?),在统计学中,数据可视化(绘图)几十年来一直非常重要。Python 在近年来取得了一些进展,但仍被认为是滞后的(只需看看例子 matplotlib
绘图就能意识到这一事实^(1))。足够赞扬 R 了,让我们看看为什么它在 EDA 中如此出色!
静态可视化
您应该已经熟悉了基本 R 在 DV 方面的能力,特别是关于时间序列绘图的部分 第四章。在这里,我们将进一步讨论并介绍最著名的 R 包之一 - ggplot2
。它是 Python 爱好者希望转向 R 的主要原因之一^(2)。ggplot2
在 EDA 工作中如此成功的原因在于它基于一个经过深思熟虑的方法论 - 图形语法(GoG)。这是由 L. Wilkinson 开发的,而包由 Hadley Wickham 开发^(3)。
什么是 GoG?它的原始论文标题为“图形的分层语法”,而“分层”一词是关键。您在图上看到的每一样东西都对一个更大的堆栈或系统有所贡献。例如,坐标轴和网格形成一个单独的层,与线条、条形和点相比。后者构成“数据”层。所有图层的完整堆栈形成结果 - 一个完整的ggplot
。这种模块化的设计模式可以提供极大的灵活性,并提供了一种新的数据可视化思维方式。GoG 背后的逻辑在图 5-2 中说明。
图 5-2. 图形语法的分层结构。
为了说明常规 EDA 工作流程的不同程序,我们将使用dplyr
包中的starwars
数据集^(4)。这个数据集包含了关于星球大战电影中人物的信息,比如他们的性别、身高和物种。让我们来看一下!
library(ggplot2)
library(dplyr)
data("starwars") 
这将使数据集在您的 RStudio 环境中可见,但这并不是严格必要的。
作为第一步,让我们做一个基本的绘图:
ggplot(starwars, aes(hair_color)) +
geom_bar()
这个图绘制了头发颜色变量的计数。在这里,我们看到了一个熟悉的运算符,+
,被不同寻常地使用。在ggplot2
中,我们使用+
来在图中添加图层。让我们在此基础上构建一个更复杂的案例。注意,我们在这里省略了代码中的一个过滤步骤(有一个离群值 - 贾巴·赫特):^(5)。
ggplot(starwars, aes(x = height, y = mass, fill = gender)) + 
geom_point(shape = 21, size = 5) + 
theme_light() + 
geom_smooth(method = "lm") + 
labs(x = "Height (cm)", y = "Weight (cm)",
title = "StarWars profiles ",
subtitle = "Mass vs Height Comparison",
caption = "Source: The Star Wars API") 
指定要使用的数据和特征。
选择一个点图(最适合连续数据)。
使用内置的theme
- 一组特定的图层样式。
拟合一个线性模型并将结果显示为绘图的一层。
添加标题和轴标签。
此绘图操作的结果显示在图 5-3 中。仅用几行代码,我们就创建了一个漂亮的图,甚至可以进一步扩展。
图 5-3. 一个高级的 ggplot2 图。
现在我们已经介绍了静态可视化,让我们看看如何通过添加交互性使它们更有趣!
交互式可视化
交互性可以极大地帮助探索性绘图。两个出色的 R 包脱颖而出:leaflet
和 plotly
。
小心 JavaScript
Python 和 R 中的交互性通常基于底层的 JavaScript 代码库。像leaflet
和plotly
这样的包已经为我们处理了这些,但我们也可以学习纯 JavaScript。对于初学者来说,像D3.js这样的交互图形的低级包可能会感到无法掌握。因此,我们鼓励学习高级框架,比如Dimple.js。
不同的数据集需要不同的可视化方法。我们覆盖了标准的表格式数据集(starwars
)案例,但还有其他不同的情况吗?我们将尝试使用具有空间维度的数据来展示 R 在生成交互式图表方面的优秀能力。为此,我们选择了共享汽车位置数据集。它提供了以色列特拉维夫市共享汽车位置的信息。我们可以在地图上显示这些位置吗?
library(leaflet)
leaflet(data = shared_cars_data[1:20, ]) %>%
addTiles() %>%
addMarkers(lng = longitude, lat = latitude)
在这种情况下,我们仅使用前 20 行数据(以减少可视化的混乱程度)。addTiles
函数提供地图背景,并显示街道和城市名称^(6)。下一步是通过使用addMarkers
添加指定汽车位置的标记。这个相对简单的操作的结果如图 5-4 所示。
图 5-4. 使用 leaflet 的交互地图绘图
与最好的数据科学工具一样,像leaflet
这样的包在幕后隐藏了大量复杂性。它们完成了高级可视化所需的大部分重活,并且使数据科学家可以专注于他们擅长的事情 - 关注数据。leaflet
中还有许多更高级的功能可供使用,我们鼓励有兴趣的用户去探索。
使 ggplot2 变得交互式
正如我们书的副标题所示,我们总是试图兼顾两个世界中的最佳。因此,一个简单的方法是使用plotly
包中的ggplotly
命令,并将其传递给ggplot2
绘图。这将使绘图变得交互!
希望本节已经清楚地说明了为什么 EDA 工作流程使得使用 R 和像ggplot2
、leaflet
这样的工具成为最佳选择。我们只是浅尝辄止了可能性,如果决定深入探索数据可视化方面,会有大量优秀的资源可供利用。
机器学习
如今,数据科学几乎与机器学习(ML)同义使用。尽管数据科学项目需要许多不同的工作流程(图 5-1),但 ML 往往吸引了渴望成为数据科学家的人们的注意力。这在一定程度上是由于近年来数据量大幅增长、计算资源更好(如更好的 CPU 和 GPU)以及现代业务中预测和自动化的需求。在该领域的早期阶段,它以另一种名称——统计学习——而闻名。正如之前提到的,统计学一直是 R 的主要领域。因此,早期进行 ML 有很好的工具可用。然而,这在近年来已经改变,Python 的工具大多数已经取代了它的统计竞争对手。
可以追溯到 Python 的 ML 生态系统成功的一个特定包——scikit-learn
。自其早期版本以来,核心开发团队一直致力于设计一个易于访问和使用的 API。他们支持这一点的方式是提供了一些在开源世界中最完整和易于访问的文档。这不仅仅是一个参考文档,还包括关于各种特定现代 ML 应用的优秀教程,例如处理文本数据。scikit-learn
提供了几乎所有常见的 ML 算法的开箱即用^(7)。
让我们来看看一些证据,证明scikit-learn
在 ML 中的出色之处。首先,我们可以展示模型的导入:
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LinearRegression
在这里,我们已经可以看到这些模型设计是多么的一致 - 就像一个组织良好的图书馆里的书籍一样;一切都在正确的位置。scikit-learn
中的 ML 算法根据它们的相似性进行分组。在这个例子中,基于树的方法如决策树属于tree
模块。相反,线性算法可以在linear_model
模块中找到(例如,如果你想执行 Lasso 模型,你可以在linear_model.Lasso
中可预见地找到它)。这种分层设计使得更容易专注于编写代码,而不是搜索文档,因为任何良好的自动完成引擎都会为您找到相关的模型。
注意
我们在第三章讨论了模块,但这是一个需要重复的概念,因为它可能对一些 R 用户来说很令人困惑。Python 中的模块仅仅是组织良好的脚本集合(例如,“data_processing”),允许它们被导入到您的应用程序中,提高可读性并使代码库更加组织化。
接下来,我们需要为建模准备数据。任何机器学习项目的重要组成部分是将数据分割为训练集和测试集。虽然像mlr
这样的新的 R 软件包在这方面也有所改进,但scikit-learn
提供了更好(在一致性和语法上)的函数:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y,
test_size=0.33,
random_state=42)
假设我们在之前的步骤中保持了一致,并遵循了传统的机器学习约定。在这种情况下,我们有X
对象来存储我们的特征和``y' - 标签(在监督学习问题中的标签)。在这种情况下,数据将会被随机分割。在 R 的mlr
中,官方的做法是:
train_set = sample(task$nrow, 0.8 * task$nrow)
test_set = setdiff(seq_len(task$nrow), train_set)
这可能更难理解,如果需要关于如何执行更高级分割(例如分层)的文档,则几乎没有可用的内容,可能需要另一个软件包,这会增加数据科学家的学习曲线和认知负荷。另一方面,scikit-learn
在StratifiedShuffleSplit
中提供了一个方便的函数。当我们开始执行实际建模时,其功能只会进一步增强:
model = RandomForestClassifier()
model.fit(X_train, y_train)
predictions = model.predict(X_test)
这三行代码是初始化模型并使用默认参数进行拟合(训练),然后在测试集上进行预测的全部内容。这种模式在项目中保持一致(除了模型初始化,其中选择了自己喜欢的算法及其参数 - 当然会有所不同)。其他开发者和用途的几个不同软件包之间的视觉比较显示在图 5-6 中。最后,让我们计算一些性能指标;其中许多指标都很方便地提供:
from sklearn import metrics
acc = metrics.accuracy_score(predictions, y_test)
conf_matrix = metrics.confusion_matrix(predictions, y_test)
classif_report = metrics.classification_report(predictions, y_test)
metrics
模块包含了检查我们模型性能所需的一切,具有简单和可预测的应用程序接口(API)。我们之前看到的fit
和predict
模式在开源世界中产生了如此大的影响,以至于被其他软件包广泛采纳,例如yellowbrick
(用于模型性能可视化的软件包):
from yellowbrick.regressor import ResidualsPlot
visualizer = ResidualsPlot(regr)
visualizer.fit(X_train, y_train)
visualizer.score(X_test, y_test)
visualizer.show()
yellowbrick
中还有许多其他可视化,所有这些可视化都是通过类似的过程获得的。其中一些显示在图 5-5 中。
图 5-5. 不同的yellowbrick
回归图。
一致性和易用性是用户希望使用 Python 进行 ML 的重要原因之一。它使用户能够专注于手头的任务,而不是编写代码和翻阅乏味的文档页面。近年来,R 软件包有所改变,旨在减少这些缺陷。这些软件包包括mlr
和tidymodels
,但它们的使用并不广泛,但也许这种模式将来会改变。这里还有一个要考虑的额外因素,类似于我们在第四章中看到的生态系统互操作性。scikit-learn
与 Python 的其他工具非常配合,这些工具对于开发和部署 ML 模型至关重要。这些工具包括数据库连接、高性能计算包、测试框架和部署框架。在scikit-learn
中编写 ML 代码将使数据科学家成为数据团队中更有生产力的一部分(想象一下,当您向数据工程同事交付一个mlr
模型用于部署时,他们的表情会是什么样子)。
图 5-6. Python ML 生态系统中的 API 一致性概述。
总结这一节,我们可以总结关于 ML 工作流程和为何 Python 工具更好支持它的主要观点:
-
焦点已转向实时预测和自动化。
-
Python ML 工作流提供了一个更一致且易于使用的 API。
-
Python 更像是一种粘合语言 ^(8),非常适合组合不同的软件组件(即前端/后端和数据库)。
在下一节中,我们将深入探讨此列表的第三部分,并展示推荐的数据工程工作流程。
数据工程
尽管近年来 ML 工具取得了进展,但企业中此类项目的完成率仍然较低。其中一个常被归因的原因是缺乏数据工程(DE)支持。为了应用 ML 和高级分析,企业需要数据工程师提供的基础设施,包括数据库、数据处理管道、测试和部署工具。当然,这形成了一个单独的职位 - 数据工程师。但数据科学家仍需要与这些技术进行交互(有时甚至是实施),以确保数据科学项目成功完成。
虽然 DE 是一个庞大的领域,但我们将在本节中关注其子集。我们选择了模型部署,因为这是数据科学家可能需要参与的最常见 DE 工作流程。那么什么是 ML 部署?大多数情况下,这意味着创建一个应用程序接口(API)并使其可供其他应用程序使用,无论是内部还是外部(向客户,“暴露”API,以被“消耗”)。通常,ML 模型通过 REST 接口进行部署^(9)。
与本章其他主题相比,机器学习模型部署需要与许多不直接与数据科学相关的技术进行接口。这些技术包括 Web 框架、CSS、HTML、JavaScript、云服务器、负载均衡器等。因此,Python 工具在这里占据主导地位^(10) - 正如我们之前所讨论的,它是一种出色的胶水语言。
注意
模型部署的工作流程需要在数据科学家进行日常工作的本地机器之外的其他机器上执行代码。这正是“在我的机器上运行正常”问题的核心。处理不同环境一致性管理的方法有多种,从简单到复杂不等。一个简单的方法是使用requirements.txt
文件,其中指定了所有的依赖关系。在大规模、关键的部署中经常使用的更复杂的选项是使用像Docker这样的容器解决方案。在 Python 中,与 R 相比,这种依赖管理要容易得多。
创建 API 的最流行工具之一是 Python 的Flask - 一个微框架。它提供了一个简约的接口,可以轻松地通过其他工具进行扩展,例如提供用户认证或更好的设计。为了开始,我们将通过一个小例子进行演示。我们需要一个典型的 Python 安装,以及其他一些额外的配置,如虚拟环境^(11)和一个 GUI 来查询 API。让我们开始吧!
专注于机器学习 API 的框架
最近,出现了一些与 Flask 竞争的框架。它们有着相同的目标,但更加专注于机器学习。两个流行的例子包括BentoML和FastAPI。这些框架为你提供了一些额外的选项,使得机器学习部署更加容易。请记住,Flask 最初是为了 Web 开发 API 而构建的,而机器学习项目的需求可能有所不同。
我们将构建一个 API 来预测房价^(12)。始终明智的做法是从最终目标出发,思考我们希望这样的预测模型如何被外部应用程序或最终用户使用。在这种情况下,我们可以想象我们的 API 被集成到一个在线房屋租赁门户中。
为了简洁起见,我们将省略模型训练部分。想象一下,您已经按照传统的scikit-learn
模型开发过程进行了操作。预测模型的结果存储在一个.pkl
(Pickle
对象,标准的 Python 对象存储方式)文件中。这个过程称为序列化,我们需要这样做以便稍后在 API 中使用模型:
import pickle
# model preparation and training part
# ...
# model serialization
outfile = open("models/regr.pkl", "wb")
pickle.dump(regr, outfile)
outfile.close()
print("Model trained & stored!")
我们可以将这段代码保存在一个名为 train_model.py
的脚本中。通过运行它:python train_model.py
,将会生成并保存序列化模型。图 5-7 提供了不同组件的概述。
图 5-7. ML API 的示例架构。
Let's use Flask next:
import pickle
import numpy as np
from ast import literal_eval 
from flask import Flask, request, jsonify
app = Flask(__name__) 
infile = open("models/regr.pkl", "rb") 
regr = pickle.load(infile)
infile.close()
@app.route('/') 
def predict(methods=["GET"]):
payload = request.json["data"]
input_data = np.array(literal_eval(payload)).reshape(1, -1)
prediction = regr.predict(input_data) 
return jsonify({
"prediction": round(float(prediction), 3) 
})
if __name__ == '__main__':
app.run(debug=True)
我们使用这个函数来指定载荷字符串对象实际上是一个字典。
在这里,我们创建了一个持有应用程序的对象。
在那几行代码中,我们加载了序列化模型。
这个 Python 装饰器创建了一个“端点”(见下面的信息框)。
在这一步中,序列化模型用于推断。
推断结果以 JSON 格式返回。
这段代码被添加到一个名为 app.py
的文件中。一旦你执行此脚本,命令行将输出一个本地 URL。然后我们可以使用诸如 Postman 等工具来查询它^(13)。看一看图 5-8,看看这样的查询是如何工作的。哇 - 我们构建了一个 ML API!
注意
在我们的示例中,API 只提供了一个功能 - 在数据集上预测房价的能力。在现实世界中,同一个应用程序可能需要执行不同的任务。这通过创建不同的端点来组织。例如,可能会有一个端点用于触发数据准备脚本,另一个用于推断。
图 5-8. 使用 Postman 查询 ML API。
云部署
在编写和测试 ML API 代码完成后,下一阶段将是部署它。当然,你可以使用你的计算机作为服务器并将其暴露在互联网上,但你可以想象这并不是很好扩展的(你必须让你的机器运行,并且它可能会耗尽资源)。近年来在 DE 工具方面看到的一项重大变化是云计算的出现。云平台如亚马逊网络服务 (AWS) 或谷歌云服务提供商 (GCP) 为你提供了出色的机会和部署应用程序。你的 Flask API 可以通过云服务,比如 弹性 Beanstalk 或 谷歌应用引擎 进行部署。
由于 Python 包的“胶水”特性,它们主导了 DE 工作流程。如果数据科学家可以自己用 Python 编写这样的应用程序,那么完整数据项目的成功就得到了保证。
报告
每个数据科学家都知道(也许痛苦地知道)沟通对他们的日常工作有多么重要。这也是一个常常被低估的技能,所以这句口号值得重复。那么,还有什么比数据科学项目的一个重要成果 - 报告你的结果更重要呢?
有不同的报告方法可供选择。对于数据科学家而言,最典型的用例是创建一个文档或幻灯片,其中包含他们对数据集进行的分析结果。这通常是一系列可视化图表,附带相关文本和一致的故事线(即通过项目生命周期的不同阶段 - 数据导入、清洗和可视化)。还有其他情况,报告必须经常参考并实时更新 - 称为仪表板。最后,某些报告允许最终用户以更交互的方式探索它们。我们将在下面的小节中详细介绍这三种报告类型。
静态报告
Markdown(MD)语言的普及帮助数据科学家专注于编写代码和相关思想,而不是工具本身。这种语言的一种变体 - R Markdown(RMD)在 R 社区广泛使用。这允许实现“文学编程”的概念,其中代码与分析混合。RStudio IDE 提供了进一步的功能,如R 笔记本。编写 RMD 报告是如此简单:
# Analysing Star Wars
First we start by importing the data.
```{r}
library(dplyr)
data(starwars)
```py
Then we can have a look at the result.
这个.rmd
文件可以被knit
(编译)成.pdf
或.html
(适合交互式绘图),生成漂亮的报告。还有其他模板可以从 RMD 文件创建幻灯片、仪表板和网站。看看图 5-9 来了解其运作方式。
图 5-9. 在 RStudio 内编辑 RMarkdown
就像开源世界中的所有事物一样,全球的数据科学家们为 RMD 的进一步发展做出了贡献。有许多 RMD 模板可供使用,使用户能够创建从定制样式报告到动态生成的博客网站的各种内容。
注意
在 Python 世界中广泛采用的 RMD 替代品是Jupyter Notebook(以及其更新版本 - Jupyter Lab)。Jupyter 中的“r”代表 R 语言,虽然可以使用它,但我们认为 RStudio 中的 RMD 笔记本提供了更好的界面,至少适用于 R 工作。
交互式报告
如果我们希望让报告的接收者也能做些工作怎么办?如果允许一些互动,我们的最终用户将能够自行回答问题,而无需依赖我们返回修改代码并重新生成图形。有几种工具可用^(14),但大多数与 R 的shiny
包^(15)相比显得逊色。
使用这个包需要一种稍微不同的方式编写 R 代码,但一旦你习惯了,你将能够创建出色的应用程序。让我们通过一个基本但实用的例子来了解。shiny
应用程序由两个基本元素组成:用户界面(UI)和服务器逻辑。通常这两者甚至分别放在两个文件中。为了简单起见,我们将使用单文件布局,并使用两个函数来构建应用程序。
library(shiny)
ui <- fluidPage( 
titlePanel("StarWars Characters"),
sidebarLayout(
sidebarPanel(
numericInput("height", "Minimum Height:", 0, min = 1, max = 1000), 
numericInput("weight", "Minimum Weight:", 0, min = 1, max = 1000),
hr(),
helpText("Data from `dplyr` package.")
),
mainPanel(
plotOutput("distPlot") 
)
)
)
此函数指定了我们希望具有“流体”布局 - 这使得应用程序“响应式”,可以在各种设备上轻松阅读,例如智能手机。
为用户添加动态输入。
添加一个专门用于输出的区域。
ui
对象包含应用程序的所有“前端”部分。实际计算发生在以下函数中;我们将从 DV 部分添加ggplot
:
server <- function(input, output) { 
output$distPlot <- renderPlot({ 
starwars_filtered <- starwars %>%
filter(height > input$height & mass > input$weight) 
ggplot(starwars_filtered, aes(x = height, y = mass, fill = gender)) +
geom_point(pch = 21, size = 5) +
theme_light() +
geom_smooth(method = "lm") +
labs(x = "Height", y = "Mass",
title = "StarWars Characters Mass vs Height Comparison",
subtitle = "Each dot represents a separate character",
caption = "Data Source: starwars (dplyr)") 
})
}
服务器需要两样东西:输入和输出。
在我们的案例中只有一个输出。
我们可以在这里添加各种 R 计算,就像在常规的 R 脚本中一样。
最近的项目(在本例中是图)被返回以在前端显示。
计算发生在此函数中。最后,我们需要在这里传递这两个函数以启动应用程序。这些结果显示在图 5-10 中。
shinyApp(ui = ui, server = server)
图 5-10. 使用 Shiny 创建的交互式报告。
我们的 Shiny 应用程序与 Markdown 文件的一个不同之处可能会让它比较难以使用,那就是你需要将应用程序托管在远程机器上。对于普通的.rmd
文件,你需要将文件编织成 PDF 然后分享。这类应用程序的部署方式超出了本书的范围。
创建报告是数据科学工作的一个小但至关重要的组成部分。这是展示你的工作给外界的方式,无论是你的经理还是其他部门。即使你在分析上做得很出色,通常也会根据你如何有效地传达过程和结果来评判。文学编程工具如 RMD 和更高级的交互式报告在shiny
中可以极大地帮助创建最先进的报告。在本书的最后一章,第七章,我们将提供一个很好的实例。
总结思考
在本章中,我们讨论了数据科学项目中最基本的工作流程,并发现了在 R 和 Python 中最好的工具。在探索性数据分析(EDA)和报告方面,R 可以称为王者。像ggplot2
这样的包在数据科学社区中无与伦比,而shiny
则可以以迷人的新方式呈现数据科学结果给利益相关者和同事们。在机器学习和数据工程领域,Python 的类似胶水的特性提供了出色的选择,使现代数据科学家能够专注于工作而非工具。
^(1) 将matplotlib
作为 Python 中唯一可行的替代方案显得有些不公平。seaborn
包也能够快速创建漂亮的图表,但在ggplot
功能方面仍有所不足。值得一提的是,pandas
的新版本也具有绘图功能,因此我们应密切关注这一领域的发展。
^(2) 已经有尝试在 Python 中重建这个包,比如ggplot,但目前在社区中并没有流行起来。
^(3) 他编写了许多其他包,并在某些方面几乎单枪匹马地改变了人们在现代环境中使用 R 的方式。详细信息请参阅第二章了解他的包。
^(4) 数据集的更多信息请参见这里。
^(5) 你知道他的真名是贾巴·迪斯利吉克·提乌雷吗?
^(6) 探索不同地图样式的官方文档可以在这里找到。
^(7) 可以在这里找到这些的概述。
^(8) 想要对机器学习架构的复杂性有更直观的了解,请查看来自 Google 的这篇MLOps 文档。
^(9) 想要了解更多关于 REST 的信息,请参考这个资源。
^(10) Flask 在 R 中的替代方案是plumber
。RStudio IDE 提供了友好的界面来使用这个工具,但在机器学习社区中仍然存在选项和采用上的差距。
^(11) 为了简洁起见,我们不会在这里深入探讨设置虚拟环境的问题。我们建议有兴趣的读者阅读virtualenv
和renv
工具的相关内容,这些内容在第三章中有所涵盖。
^(12) 数据集是“波士顿房屋”,可以在这里找到。
^(13) 如果你更喜欢命令行,可以看看curl
。
^(14) Python 中有一个先进的新工具,称为streamlit,但它还没有普及和广泛采用。
^(15) 要想了解 Shiny 的可能性,可以看看RStudio 网站上的使用案例库。
第四部分:双语 III:成为协同效应
到目前为止,在本书中,我们一直在相对独立的情况下探索这两种语言。我们学习了如何从一种语言学习另一种语言,并且了解了它们在哪些数据格式和工作流程中表现出色。在所有这些情况中,它们的分离都是非常明显的 - 对于某些任务,R 表现出色,而在其他任务中,它的通用对应物 Python 表现出色。
第六章
在本章中,我们将采取不同的视角 - 这可能预示着未来一种新的编程语言使用方式的到来。
第七章
作为本书的最后一章,运用我们迄今为止学到的所有概念是再适合不过的了。我们将通过一个双语数据科学的真实案例研究来实现这一点。
第六章:协同使用两种语言
Rick J. Scavetta
互操作性,即不同编程语言共同工作的能力,是计算机的基石。理想情况下,对象可以直接在两种语言之间共享。正如你可以想象的那样,这可能因为多种原因而成为问题,比如内存使用和不兼容的数据存储结构,只举两个例子。尽管有几次尝试使 Python 和 R 之间的互操作性实现得更加顺畅,但直到最近几年才真正出现了一个功能合理的工具包。我将在“互操作性”一节中讨论这个问题。但首先回到基础是很有用的。这不仅能帮助你理解后面顺畅互操作的背景,而且一个基础的解决方案可能已经满足你的需求。尽管如此,如果你想开始使用互操作性,可以跳过下一节。
伪操作性
最基本的互操作类型,我们将其称为跨语言交流,更多地是一种伪操作性。在这里,我们通过文件作为中介,在语言之间执行预定义的脚本,传递信息。想象一下下面的情况,我已经在图 6-1 中绘制了图示。
图 6-1. 促进互操作性的跨语言交流示例。
在 R 中,在对对象(如PlantGrowth
)执行一些必要的工作后,我们执行:
# (Previous interesting and complicated steps omitted)
# Write a data.frame of interest to a file ...
rio::export(PlantGrowth, "pg.csv")
# ... which is then processed by a Python script
system('~/.venv/bin/python3 myScript_2.py < "pg.csv"')
system()
函数执行一个系统命令,该命令作为字符参数提供。命令由四个部分组成。
首先,~/.venv/bin/python3
是我们虚拟环境中 Python 可执行文件的位置,假设你已经创建了一个虚拟环境。我们也可以在脚本的shebang第一行中包含它,如#!/.venv/bin/env python3
。这确保脚本在创建它的环境中执行。如果这对你来说听起来很奇怪,请参阅“虚拟环境”。
第二,myScript_2.py
是包含我们想要执行的命令的 Python 文件的名称。
第三,<
允许我们将stdin
从 rhs 重定向到 lhs 上的文件^(1)。
第四,"pg.csv"
是stdin
。你可能记得命令行函数有三个标准通道或流,stdin
用于标准输入,stdout
用于标准输出,stderr
用于标准错误。在这里,stdin
是硬编码的。它是一个字符串,对应于一个文件:"pg.csv"
,这个文件在前一个命令中被导出。在大多数情况下应避免硬编码,你可以想象很多使其动态化的方法。这不是我们这里关注的重点;重点是将一些输入传递到 Python 脚本中。
因此,我们正在执行一个 Python 脚本,该脚本从 R 脚本中获取stdin
,而stdin
本身是 R 脚本的产物。让我们看看这个 Python 脚本的最小组件。
import sys
import pandas as pd
# import the file specified by the standard input
myFile = pd.read_csv(sys.stdin)
# (Fantastically complex and very Pythonic code omitted)
# Write the first four lines to a file
myFile.head(4).to_csv("pg_small.csv")
首先,我们需要sys
模块来处理stdin
(sys.stdin
)。我们使用 pandas 导入文件,用to_csv()
方法在我们的 Python 脚本完成其工作后导出一些其他输出。
这种方法有很多问题,我们很快就会解决它们。但关键是它起作用了,有时候这正是你所需要的。在研究实验室工作时,我经常需要快速向同事提供结果。我是说字面上的,因为非常昂贵的细胞培养物会死亡,如果结果还没有准备好,将浪费一周的工作。专有原始数据的预处理和访问安全服务器阻止了我的同事执行自动化的 R 脚本。我的解决方案是首先使用专门用于此任务的软件处理机器生成的专有数据。然后,我能够使用 Mac OS Automator 服务在该输出上执行 Perl 脚本,这是我的stdin
。然后,这个 Perl 脚本调用一个 R 脚本,生成一个具有所有相关信息清晰显示在标题中的绘图文件。这并不是最开放或最优雅的解决方案,但它起作用了,我只需单击鼠标大约半秒钟就能得到我的绘图,而无需额外的网站或登录。生活很美好,那问题在哪里呢?
嗯,有几个问题。让我们考虑三个。
首先,回顾过去,我可能完全可以在 R 中执行整个工作流程(不包括专有的预处理)。必须考虑简化工作流程并有充分理由使用多种语言。在本书中,决定何时以及为何结合 Python 和 R 已经讨论过。
其次,有很多移动的部分。我们有几个文件,甚至产生了额外的中间文件。这增加了出错和混乱的机会。这并不可怕,但我们最好注意保持事物有序。
第三,许多情况下,当我们可以将一个 R data.frame
导出为 csv 文件时,这个工作流程表现良好,而pandas
可以轻松导入。对于更复杂的数据结构,您可以将 1 个或多个 R 对象导出为RData
或Rds
格式文件。Python 的pyreadr
包提供了导入这些文件并访问存储在dict
中每个对象的函数。
虽然交互性很好,但真正的互操作性会很好地解决这个过程中的问题。有两种广泛使用的框架,选择使用哪种取决于您的起始语言。
互操作性
如果您主要使用 R 并希望访问 Python,则 R 包reticulate
是正确的选择。相反,如果您主要使用 Python 并希望访问 R,则 Python 模块rpy2
是适合您的工具。我们可以在表 6-1 和表 6-2^(2)中总结这一点。在每个表中,将每行读作以列标题开头的句子。
表 6-1。由reticulate
提供的互操作性。
访问 | 使用命令 |
---|---|
Python 函数 | 在 R 中,pd <- library(pandas) ; pd$read_csv() |
Python 对象 | 在 R 中,py$objName |
R 对象 | 在 Python 中,r.objName in Python |
表 6-2. 当使用 Python 编写时,rpy2 提供的互操作性。
访问 | 使用命令 |
---|---|
R 函数 | 在 Python 中,import rpy2.robjects.lib.ggplot2 as ggplot2 |
R 包 | 在 Python 中,r_cluster = importr('cluster') |
R 对象 | 在 Python 中,foo_py = robjects.r['foo_r'] |
在 Table 6-1 和 Table 6-2 中的命令显示了如何直接从一种语言访问所有种类的对象到另一种语言。此外,我们还可以直接调用函数。这是一个真正的里程碑,因为它使我们免于强迫一种语言执行它不擅长的任务,并且意味着我们不需要在语言之间引入冗余,重复发明轮子。在撰写本文时,使用 reticulate
无法从 Python 直接访问 R 函数。您可以尝试使用 reticulate
完成此任务,但将对象返回给 R 并本地执行 R 命令会更容易。
使用 reticulate 快速上手
reticulate
首次出现在 CRAN 上是在 2017 年,并且随着它的成熟而日益流行。这个包由 RStudio 开发,并且很好地集成到了 RStudio IDE 中,这非常方便。然而,在撰写本文时,存在一些需要精细处理的麻烦功能(或许是 bug?),这些需要注意(请参见警告框 “State of reticulate”)。一个很好的第一步是确保您正在使用最新的 RStudio 公开版本以及reticulate
包和任何相关的包,如knitr
。
reticulate
的状态
reticulate
受到良好支持并且足够稳定,可以用于生产。尽管如此,根据您的系统和软件版本,您可能会遇到问题。由于此工具结合了多种技术,因此调试可能会困难,并且文档仍然有些匮乏。随着新版本的发布,请保持及时更新。如果在您的本地机器上遇到问题,请访问我们的 RStudio Cloud 项目。
在本节中,我们将从两个脚本开始,列在 Table 6-3 中。您可以在本章的书籍 存储库 中找到这些脚本。
表 6-3. 使用 reticulate 快速上手。
文件 | 描述 |
---|---|
0 - Setup.Rmd |
设置 reticulate 和虚拟环境 |
1 - Activate.R |
激活 Python 虚拟环境 |
让我们从 R 脚本 0 - Setup.R
开始。确保您已安装了 reticulate
并在您的环境中初始化它:
library(reticulate)
首先,我们需要指定使用哪个版本的 Python。您可以让 R 使用您的系统默认设置,或者通过转到工具 > 项目选项
并选择 Python 图标来设置您想要使用的特定版本的 Python。
图 6-2。选择要使用的特定 Python 版本和构建版本。
让我们检查一下我们正在使用的版本:
reticulate::py_config()
python: /usr/local/bin/python3.8
libpython: /Library/Frameworks/Python.framework/Versions/3.8...
pythonhome: /Library/Frameworks/Python.framework/Versions/3.8...
version: 3.8.6 (v3.8.6:db455296be, Sep 23 2020, 13:31:39) ...
numpy: [NOT FOUND]
sys: [builtin module]
清楚地说一下,我们不需要使用 RStudio 来设置 Python 版本。这只是一个方便的功能。我们可以执行:
use_python("/usr/local/bin/python3.8", required = TRUE)
请注意,这个函数只是一个建议,如果未找到所需的构建版本,则不会产生错误,除非将required
参数设置为TRUE
。
在继续之前,我们将要建立一个虚拟环境。如果你在 Windows 上,你将不得不使用conda
环境,我们将在一分钟内介绍它。对于其他人,请使用以下命令创建名为modern_data
的虚拟环境:
virtualenv_create("modern_data")
以前,当我们在 Python 中使用venv
包时,虚拟环境存储在一个隐藏目录中(通常称为项目目录中的.venv
)。那么现在 Python 虚拟环境在哪里?我们可以用以下命令来看一看:
virtualenv_root()
[1] "~/.virtualenvs"
它们都存储在根目录中的一个隐藏文件夹中。我们可以使用以下命令查看所有的虚拟环境:
virtualenv_list()
Reticulate 速查表
与大多数流行的数据科学包一样,reticulate
有一张速查表可用。你可以直接从这里下载。
这与我们在 Python 中看到的虚拟环境存储在项目目录中的情况有所不同。尽管如此,这很方便,因为我们可以轻松地为许多项目重复使用一个好的环境。
请注意,要删除虚拟环境,我们需要传递路径,如下所示:virtualenv_remove("~/modern_data")
。
下一步是安装适当的包。
virtualenv_install("modern_data", "pandas")
或者,你可以使用tidyverse
的purrr::map()
函数来安装许多包:
library(tidyverse)
c("scikit-learn", "pandas", "seaborn") %>%
map(~ virtualenv_install("modern_data", .))
如果你在 Windows 上,请使用以下命令:
# For windows users:
# Install a minimal version of conda
install_miniconda()
# List your conda virtual environments
conda_list()
# Creata a new virtual environment
conda_create("modern_data")
# Install a single...
conda_install("modern_data", "scikit-learn")
#...or multiple packages:
library(tidyverse)
c("scikit-learn", "pandas", "seaborn") %>%
map(~ conda_install("modern_data", .))
最后一步是激活我们的虚拟环境。这似乎是一个快速发展的领域。根据你使用的reticulate
和 RStudio 的版本,可能会产生不同的错误消息,或者根本没有错误消息,这使得调试变得更加困难。根据我的经验,你最安全的选择是:(i) 确保所有的 R 包和 RStudio 都是最新的,以及 (ii) 在激活虚拟环境之前重新启动 R。你可以在 RStudio 菜单中执行此操作Session > Restart R
,键盘快捷键shift + cmd/ctrl + F10
或执行命令.rs.restartR()
。你也可以直接关闭并重新启动 RStudio。这样可以确保没有活跃使用的 Python 构建,并且我们可以从头开始建立一个。因此,我们有一个用于设置的 R 脚本,我们在其中创建一个虚拟环境并安装包,以及另一个用于实际分析的脚本,我们在其中加载reticulate
并激活我们的虚拟环境。
library(reticulate)
use_virtualenv("modern_data", required = TRUE)
# Alternatively, for miniconda:
# use_miniconda("modern_data")
最后,我们可以确认我们正在使用哪个版本:
py_config()
你应该看到以下输出。重要的是确保你的虚拟环境路径在第一行中指定:/.virtualenvs/modern_data/bin/python
。
python: /Users/user_name/.virtualenvs/modern_data/bin/python
libpython: /Library/Frameworks/Python.framework/Versions/3.8...
pythonhome: /Users/user_name/.virtualenvs/modern_data...
version: 3.8.6 (v3.8.6:db455296be, Sep 23 2020, 13:31:39)
numpy: /Users/user_name/.virtualenvs/modern_data/lib/python3.8/site-packages/numpy
numpy_version: 1.20.1
如果你看到像/usr/local/bin/python3.8
这样的东西,那么 RStudio 仍然指示使用你在本章开始时定义的 Python 版本,而不是虚拟环境。这可能对你有所帮助,但最好使用虚拟环境。
更深入了解
到目前为止,我们已经创建了一个虚拟环境,在其中安装了一些包,重新启动了 R,并激活了虚拟环境。这些步骤在脚本0 - Setup.R
和1 - Activate.R
中有所涵盖。在本节的其余部分,我将介绍如何在 R 和 Python 之间传递信息,我已在表 6-1 中进行了总结。
表 6-4。reticulate 授予的互操作性。
文件 | 描述 |
---|---|
2 - Passing objects.Rmd |
在 RMarkdown 文档中在 R 和 Python 之间传递对象 |
3 - Using functions.Rmd |
在 RMarkdown 文档中调用 Python |
4 - Calling scripts.Rmd |
通过源代码调用 Python 脚本 |
5 - Interactive mode.R |
使用 Python REPL 控制台调用 Python |
6 - Interactive document.Rmd |
在交互式文档中使用动态输入调用 Python |
注意
为什么叫“reticulate”?网纹蟒是一种生活在东南亚的蟒蛇。它们是世界上最长的蛇类和最长的爬行动物。物种名称 Malayopython reticulatus 是拉丁文,意为“网状的”,或 reticulated,指的是其复杂的颜色图案。
我将在下面的小节中考虑表 6-1 中的情景。要跟随这些示例,请确保你已按照0 - Setup.R
和1 - Activate.R
中的设置和激活说明进行了设置(两者都在书籍代码存储库中)。你需要安装 modern_data 虚拟环境和上述列出的包。如果你使用的是miniconda
,请确保使用每个文件中给出的正确命令来激活你的虚拟环境。
在 RMarkdown 文档中在 R 和 Python 之间传递对象
下列命令可在文件2 - Passing objects.Rmd
中找到。要在 Python 中访问 R 对象,请使用r
对象,要在 R 中访问 Python 对象,请使用py
对象。考虑下列在 RMarkdown 文档中找到的代码块:
```{python}
a = 3.14
a
```py
```{r}
py$a
```py
使用$
符号在 R 对象py
中访问 Python 对象a
。相反的方向:
```{r}
b <- 42
b
```py
```{python}
r.b
```py
在 Python 中,调用r
对象并使用.
符号按名称访问 R 对象。这些是标量,或简单向量,但当然我们可以直接在两种语言之间传递更复杂的项目。reticulate
会为我们处理对象转换。考虑以下情况:
```{r}
# 一个内置数据框
head(PlantGrowth)
```py
```{python}
r.PlantGrowth.head()
```py
R 的data.frame
可以作为 Python 的pandas.DataFrame
访问。但是,如果你没有安装 pandas,你会看到一个dict
对象,即 Python 字典。
一个 Python 的NumPy
ndarray
将被转换为一个 R 的matrix
^(3):
```{python eval = TRUE}
from sklearn.datasets import load_iris
iris = load_iris()
iris.data[:6]
```py
将 Python 的 NumPy ndarray
作为 R 的matrix
:
```{r eval = TRUE}
head(py$iris$data)
```py
注意在 Python 中的.
符号,iris.data
可以使用$
符号在 R 中自动访问:py$iris$data
。对于嵌套对象、方法和属性,与 Python 中一样,
在 RMarkdown 文档中调用 Python
文件3 - Using functions.Rmd
中可以找到以下命令。我们将继续使用在前一节中在 Python 中访问的经典 iris 数据集。在 RMarkdown 文档内部,我们将访问一个 Python 函数,这使我们能够访问经过训练的支持向量机分类器,以预测新值的分类。这是一个非常朴素的机器学习工作流程,不打算生成有价值的模型。重点是演示如何在 R 中访问 Python 中的模型。
整个模型配置在这里定义:
```{python}
# import modules
from sklearn import datasets
from sklearn.svm import SVC
# 加载数据:
iris = datasets.load_iris()
# 创建 SVC(支持向量分类)类的一个实例。
clf = SVC()
# 通过在目标数据上调用 fit 方法来训练模型,使用目标名称
clf.fit(iris.data, iris.target_names[iris.target])
# 预测新值的类别,这里是前三个
clf.predict(iris.data[:3])
```py
方法clf.predict()
以ndarray
作为输入,并返回命名分类。要在 R 中访问此函数,我们可以再次使用py
对象,如py$clf$predict()
。在 R 中,iris
数据集是一个data.frame
,其中第 5 列是分类。我们必须使用r_to_py()
将其转换为 Python 对象,在这种情况下不包括第 5 列。
```{r}
py$clf$predict(r_to_py(iris[-5]))
```py
通过调用 Python 脚本来调用 Python
文件4 - Calling scripts.Rmd
和4b - Calling scripts.R
中可以找到以下命令。在这种情况下,我们将执行整个 Python 脚本,并访问其中可用的所有对象和函数。为此,我们可以调用:
source_python("SVC_iris.py")
在 RMarkdown 文档中同样适用,正如在脚本中一样。
尽管这与前一节看起来非常相似,但有一个非常重要的区别。以这种方式激活的 Python 环境直接提供函数和对象。因此,我们可以调用:
clf$predict(r_to_py(iris[-5]))
这很方便,但也让人不安。不仅语法改变了,即不再需要py$
,而且在 R 环境中加载的对象可能会冲突。Python 对象将掩盖 R 对象,因此在命名冲突方面一定要非常小心!您会注意到在SVC_iris.py
中,我们已经将 Python 的iris
数据集重命名为iris_py
,以避免在 R 中调用iris
时出现问题。
使用 REPL 调用 Python
以下命令可以在文件5 - 交互模式.R
中找到。在这种情况下,我们将使用以下命令启动 Python REPL 控制台:
repl_python()
注意
REPL 代表读取-评估-打印循环。这是许多语言中的常见功能,用户可以以交互方式进行实验,而不是编写需要运行的脚本。
这将允许你直接在解释器中执行 Python 命令。例如,尝试执行我们在上一个示例中看到的命令:
from sklearn import datasets
from sklearn.svm import SVC
iris = datasets.load_iris()
clf = SVC()
clf.fit(iris.data, iris.target_names[iris.target])
clf.predict(iris.data[:3])
我们可以通过执行 Python exit
命令退出解释器。
exit
就像我们之前看到的那样,此 Python 环境中的函数和对象可以在 R 中访问。这确实是交互式编程,因为我们直接在控制台中执行命令。尽管出于完整性考虑我们提供了这种场景,但repl_python()
实际上并不适合日常实践。实际上,当一个 RMarkdown 代码块使用 Python 内核时,会调用它。所以尽管你可以这样做,但要小心!这在可重复性和自动化方面存在相当大的问题,但你可能会发现它对快速检查某些命令很有用。
在交互式文档中使用动态输入调用 Python
以下命令可以在文件6 - 交互式文档.Rmd
中找到。
到目前为止,我们已经看到了 reticulate 的所有核心功能。在这里,我们将超越这一点,并展示一种非常简单的方法,使用 RMarkdown 文档中的shiny
运行时引入互动性。要查看互动性,请确保安装了shiny
包,并在打开文件时将文档渲染为 HTML。在 RStudio 中,您可以通过点击“运行文档”按钮来执行此操作。
首先,在文档的标题中,我们需要指定这个新的运行时环境:
---
title: "Python & R for the Modern Data Scientist"
subtitle: "A bilingual case study"
runtime: shiny
---
我们已经在上面看到了以下 Python 代码,它在一个 Python 代码块中执行:
```{python}
from sklearn import datasets
from sklearn.svm import SVC
iris = datasets.load_iris()
clf = SVC()
clf.fit(iris.data, iris.target_names[iris.target])
```py
在最后两个代码块中,我们使用shiny
包中的函数来:(i) 为四个特征中的每一个生成滑块,以及 (ii) 将py$clf$predict()
的输出渲染为 HTML 文本,例如:
sliderInput("sl", label = "Sepal length:",
min = 4.3, max = 7.9, value = 4.5, step = 0.1)
和… n
prediction <- renderText({
py$clf$predict(
r_to_py(
data.frame(
sl = input$sl,
sw = input$sw,
pl = input$pl,
pw = input$pw)
)
)
})
最后,我们将 R 对象prediction
称为内联命令,r prediction
以将结果作为句子打印到屏幕上。
最后的想法
在本章中,我们介绍了reticulate
包的核心组件,从基本设置到基础知识,最后展示了一个简单而强大的实现,展示了 R、Python 和reticulate
的优势。借助这些知识,我们将继续进行最后一章的更大案例研究。
^(1) 请回忆一下,在调用运算符时,rhs 是右手边,lhs 是左手边,在这种情况下是<
^(2) 在这些表格中,我们区分函数和对象。记住,函数本身也是对象,但我们目前不需要担心这些细节。
^(3) 参考附录 A 获取数据结构摘要。
第七章:双语数据科学的案例研究
Rick J. Scavetta
Boyan Angelov
在本章的最后一部分,我们的目标是展示一个案例研究,演示本书中展示的所有概念和工具的样本。尽管数据科学提供了几乎令人难以置信的多样的方法和应用,但我们通常在日常工作中依赖于核心工具包。因此,您不太可能使用本书(或本案例研究,同样如此)中提出的所有工具。但这没关系!我们希望您专注于案例研究中与您工作最相关的部分,并且受到启发,成为一名现代的双语数据科学家。
24 年间发生了 1.88 百万次野火。
我们的案例研究将集中在美国野火数据集 ^(1)。这个数据集由美国农业部(USDA)发布,包含了 1.88 百万个地理参考的野火记录。这些火灾总共导致了 24 年间 1.4 亿英亩森林的损失。如果您想在本章中执行代码,请直接从USDA 网站或Kaggle下载 SQLite 数据集。一些预处理已经完成,例如去重。
有 39 个特征,再加上原始格式中的另一个形状变量。其中许多是唯一标识符或冗余的分类和连续表示。因此,为了简化我们的案例研究,我们将专注于表 7-1 中列出的少数几个特征。
表 7-1. fires
表包含描述 1992-2015 年间美国 1.88 百万次野火的 39 个特征
变量 | 描述 |
---|---|
STAT_CAUSE_DESCR | 火灾原因(目标变量) |
OWNER_CODE | 土地主要所有者的代码 |
DISCOVERY_DOY | 火灾发现或确认的年度日期 |
FIRE_SIZE | 最终火灾规模的估计(英亩) |
LATITUDE | 火灾的纬度(NAD83) |
LONGITUDE | 火灾的经度(NAD83) |
我们将开发一个分类模型来预测火灾的原因(STAT_CAUSE_CODE
),使用其他五个特征作为特征。目标和模型是次要的;这不是一个机器学习案例研究。因此,我们不会关注诸如交叉验证或超参数调整等细节^(2)。我们还将限制自己仅使用 2015 年的观察数据,并排除夏威夷和阿拉斯加,以减少数据集的规模。我们案例研究的最终产品将是生成一个交互式文档,允许我们输入新的预测者数值,如图 7-1^(3)所示。
在我们深入研究之前,值得花一点时间考虑数据谱系 - 从原始到产品。回答以下问题将有助于我们定位方向。
-
最终产品是什么?
-
如何使用,由谁使用?
-
我们是否可以将项目分解为组件?
-
每个组件将如何构建?即 Python 还是 R?可能需要哪些额外的包?
-
这些组件如何协同工作?
回答这些问题使我们能够从原始数据到最终产品中找到一条路径,希望能避免瓶颈。对于第一个问题,我们已经说明我们想构建一个交互式文档。对于第二个问题,为了保持简单,让我们假设它是为了轻松输入新的特征值并查看模型的预测。
问题 3-5 是我们在本书中考虑的内容。在问题 3 中,我们将部分想象为我们整体工作流程的一系列步骤。问题 4 已在第四章和第五章中讨论过。我们在表 7-2 中总结了这些步骤。
表 7-2. 我们案例研究的步骤及其对应的语言。
组件/步骤 | 语言 | 额外的包? |
---|---|---|
1. 数据导入 | R | RSQLite , DBI |
2. EDA & Data Visualization | R | ggplot2 , GGally , visdat , nanair |
4. 特征工程 | Python | scikit-learn |
5. 机器学习 | Python | scikit-learn |
6. 地图 | R | leaflet |
7. 交互式网络界面 | R | 在 RMarkdown 中运行的shiny |
最后,第 5 个问题要求我们考虑项目的架构。图 7-1 中呈现的图表显示了表 7-2 中的每个步骤将如何链接在一起。
图 7-1. 我们案例研究项目的架构。
好了,现在我们知道我们要去哪里了,让我们精心选择我们的工具,并将所有组件组装成一个统一的整体。
注
我们专门使用 RStudio IDE 准备了这个案例研究。正如我们在第六章中讨论的那样,如果我们在 R 中访问 Python 函数,这将是正确的方式。原因在于 RMarkdown 中执行 Python 代码块的内置功能,环境和绘图窗格的特性,以及围绕shiny
的工具。
设置和数据导入
从我们的图表中可以看出,我们的最终产品将是一个交互式的 RMarkdown 文档。所以让我们像在第五章中所做的那样开始。我们的 YAML^(4) 头部至少包括:
---
title: "R & Python Case Study"
author: "Python & R for the modern data scientist"
runtime: shiny
---
注
为了具有更好的格式,我们将在以下示例中排除指定 RMarkdown 代码块的字符。自然地,如果您在跟随,您需要添加它们。
由于数据存储在 SQLite 数据库中,除了我们已经看到的包外,我们需要使用一些额外的包。我们的第一个代码块是:
library(tidyverse)
library(RSQLite) # SQLite
library(DBI) # R Database Interface
在我们的第二个代码块中,我们将连接到数据库并列出所有的 33 个可用表格。
# Connect to an in-memory RSQLite database
con <- dbConnect(SQLite(), "ch07/data/FPA_FOD_20170508.sqlite")
# Show all tables
dbListTables(con)
创建连接(con
)对象是建立对数据库进行程序化访问的标准做法。与 R 不同,Python 内置支持使用 sqlite3
包打开此类文件。这比 R 更可取,因为我们不需要安装和加载两个额外的包。尽管如此,R 是初始步骤的核心语言,因此我们最好从一开始就在 R 中导入数据。
我们的数据存储在 Fires
表中。由于我们知道要访问的列,因此可以在导入时指定。
在使用远程或共享数据库时,记得关闭连接非常重要,因为这可能会阻止其他用户访问数据库并引发问题^(5)。
fires <- dbGetQuery(con, "
SELECT
STAT_CAUSE_DESCR, OWNER_CODE, DISCOVERY_DOY,
FIRE_SIZE, LATITUDE, LONGITUDE
FROM Fires
WHERE (FIRE_YEAR=2015 AND STATE != 'AK' AND STATE != 'HI');")
dbDisconnect(con)
dim(fires)
我们在第一次导入时已经限制了数据集的大小。抛弃这么多数据是一件令人遗憾的事情。但我们这样做是因为旧数据,尤其是在气候应用中,往往不太代表当前或近期的情况。基于旧数据的预测可能存在固有偏见。通过限制数据集的大小,我们还减少了内存使用量,提高了性能。
性能提示
在处理庞大数据集的情况下(几乎或完全不适合您机器内存的数据集),您可以使用此类摄取命令仅选择样本,例如 LIMIT 1000
。
我们可以使用 tidyverse
函数 dplyr::glimpse()
快速预览数据:
glimpse(fires)
Rows: 73,688
Columns: 6
$ STAT_CAUSE_DESCR <chr> "Lightning", "Lightning", "Lightning", "Lightning", "Misc…
$ OWNER_CODE <dbl> 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 5, 8, 5, 5, 5, …
$ DISCOVERY_DOY <int> 226, 232, 195, 226, 272, 181, 146, 219, 191, 192, 191, 19…
$ FIRE_SIZE <dbl> 0.10, 6313.00, 0.25, 0.10, 0.10, 0.25, 0.10, 0.10, 0.50, …
$ LATITUDE <dbl> 45.93417, 45.51528, 45.72722, 45.45556, 44.41667, 46.0522…
$ LONGITUDE <dbl> -113.0208, -113.2453, -112.9439, -113.7497, -112.8433, -1…
探索性数据分析与数据可视化
由于数据集仍然相对较大,我们应该仔细考虑最佳的数据可视化策略。我们的第一反应可能是绘制地图,因为我们有纬度和经度坐标。这可以直接作为 x 和 y 轴坐标输入到 ggplot2
中:
g <- ggplot(fires, aes(x = LONGITUDE,
y = LATITUDE,
size = FIRE_SIZE,
color = factor(OWNER_CODE))) +
geom_point(alpha = 0.15, shape = 16) +
scale_size(range = c(0.5, 10)) +
theme_classic() +
theme(legend.position = "bottom",
panel.background = element_rect(fill = "grey10"))
g
图 7-2。绘制个别火灾的大小。
通过将 OWNER_CODE
映射到颜色美学,我们可以看到一些州之间存在强烈的相关性。我们可以预测这将对我们模型的性能产生重大影响。在上述代码片段中,我们将绘图分配给对象 g
。这并不是绝对必要的,但在这种情况下我们这样做是为了展示 ggplot2
层叠方法的强大之处。我们可以在此图中添加一个 facet_wrap()
层,并将其分为 13 个面板,或 小多面体,每个 STAT_CAUSE_DESCR
类型一个。
g +
facet_wrap(facets = vars(STAT_CAUSE_DESCR), nrow = 4)
图 7-3。根据火灾原因分组的火灾图。
这使我们能够欣赏到一些原因很常见,而另一些很少见的情况,这是我们很快会以另一种方式再次看到的观察结果。我们还可以开始评估,例如,地区、所有者代码和火灾原因之间是否存在强烈的关联。
回到整个数据集,一个全面了解的简便方法是使用配对图,有时称为 splom(如果它完全由数值数据组成则称为“散点图矩阵”)。GGally
包^(6) 提供了一个出色的函数 ggpairs()
,它生成一个图表矩阵。对角线上显示每对双变量图的单变量密度图或直方图。在上三角区域,连续特征之间的相关性也可见。
library(GGally)
fires %>%
ggpairs()
图 7-4. 一个配对图。
这个信息丰富的可视化需要一些时间来处理。它作为探索性绘图在探索性数据分析中非常方便,但不一定适用于结果报告中的解释性。你能发现任何异常模式吗?首先,STAT_CAISE_DESCR
看起来是不平衡的^(7),意味着每个类别的观察数量之间存在显著差异。另外,OWNER_CODE
似乎是双峰的(有两个峰值)。这些属性可能会根据我们选择的模型对我们的分析产生负面影响。其次,所有的相关性似乎都相对较低,这使得我们的工作更加轻松(因为相关的数据对机器学习不利)。尽管如此,我们已经知道位置(LATITUDE
和 LONGITUDE
)与 OWNER CODE
之间存在强关联关系,这是从我们之前的图中得出的。因此,我们应该对这些相关性持谨慎态度。我们期望在特征工程中能检测到这个问题。第三,FIRE_SIZE
的分布非常不寻常。它看起来像是空的图表,只有 x 轴和 y 轴。我们看到一个密度图,在非常低的范围内有一个非常高且狭窄的峰值,以及一个极长的正偏态。我们可以快速生成一个log10
转换后的密度图:
ggplot(fires, aes(FIRE_SIZE)) +
geom_density() +
scale_x_log10()
图 7-5. 对数转换后的 FIRE_SIZE
特征的密度图。
其他可视化内容
对于案例研究,我们将任务保持在最低限度,但可能有一些其他有趣的可视化内容可以帮助最终用户讲述故事。例如,请注意数据集具有时间维度。了解随时间变化的森林火灾数量(和质量)将会很有趣。我们将这些内容留给有兴趣的用户使用优秀的 gganimate
包进行探索。
交互式数据可视化通常被滥用,没有特定的目的。即使对于最流行的包,文档也只展示了基本的用法。在我们的情况下,由于我们在空间设置中有如此多的数据点,并且我们希望得到一个易于访问的最终交付成果,创建一个交互地图是一个明显的选择。如同 第五章 中我们使用 leaflet
:
library(leaflet)
leaflet() %>%
addTiles() %>%
addMarkers(lng = df$LONGITUDE, lat = df$LATITUDE,
clusterOptions = markerClusterOptions()
)
图 7-6. 显示森林火灾位置的交互地图。
注意如何使用clusterOptions
允许我们同时呈现所有数据,而不会使用户感到不知所措或降低可见性。对于我们的目的,这满足了我们在探索性数据分析中使用一些出色可视化的好奇心。我们可以应用许多其他统计方法,但让我们转向 Python 中的机器学习。
机器学习
到目前为止,我们已经对可能影响火灾原因的因素有了一些了解。让我们深入使用 Python 中的scikit-learn
来构建机器学习模型^(8)。
我们认为在 Python 中进行机器学习是最好的选择,正如我们在第五章中所见。我们将使用随机森林算法。选择这种算法有几个原因:
-
这是一个成熟的算法
-
这相对容易理解
-
在训练之前不需要进行特征缩放
还有其他一些原因使其优秀,比如良好的缺失数据处理能力和开箱即用的可解释性。
设置我们的 Python 环境
正如在第六章中讨论的那样,使用reticulate
包有几种访问 Python 的方式。选择取决于情况,我们在项目架构中已经说明。在这里,我们将我们的 R data.frame
传递给 Python 虚拟环境。如果你按照第六章中的步骤进行操作,你已经设置好了modern_data
虚拟环境。我们已经在此环境中安装了一些包。简而言之,我们执行了以下命令:
library(reticulate)
# Create a new virtualenv
virtualenv_create("modern_data")
# Install Python packages into this virtualenv
library(tidyverse)
c("scikit-learn", "pandas", "seaborn") %>%
purrr::map(~ virtualenv_install("modern_data", .))
如果你没有安装modern_data
虚拟环境,或者你使用的是 Windows,请参考文件0 - setup.R
和1 - activate.R
中的步骤,并在第六章中讨论。在此时,你可能需要重新启动 R,以确保可以使用以下命令激活你的虚拟环境:
# Activate virtual environment
use_virtualenv("modern_data", required = TRUE)
# If using miniconda (windows)
# use_condaenv("modern_data")
我们将所有 Python 步骤都包含在一个单独的脚本中;你可以在书的仓库的ml.py
下找到此脚本。首先,我们将导入必要的模块。
from sklearn.ensemble import RandomForestClassifier
from sklearn. preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn import metrics
特征工程
数据集中有些特征对数据分析师可能有信息意义,但对于训练模型来说最好是无用的,甚至会降低其准确性。这被称为向数据集中“添加噪音”,我们要尽量避免这种情况。这就是特征工程的目的。让我们只选择我们需要的特征,如表 7-1 中所指定的那样。我们还使用了标准的机器学习约定,将它们存储在X
中,我们的目标在‘y’中。
features = ["OWNER_CODE", "DISCOVERY_DOY", "FIRE_SIZE", "LATITUDE", "LONGITUDE"]
X = df[features]
y = df["STAT_CAUSE_DESCR"]
在这里,我们创建了一个LaberEncoder
的实例。我们用它来将分类特征编码为数值。在我们的案例中,我们将其应用于我们的目标:
le = LabelEncoder()
y = le.fit_transform(y)
在这里,我们将数据集分为训练集和测试集(请注意,我们还使用了便捷的stratify
参数,以确保分割函数公平地对我们的不平衡类别进行采样):
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33,
random_state=42, stratify=y)
模型训练
要应用随机森林分类器,我们将创建一个 RandomForestClassifier
的实例。如同 第五章 中使用 fit/predict
范式并将预测值存储在 preds
中一样:
clf = RandomForestClassifier()
clf.fit(X_train, y_train)
preds = clf.predict(X_test)
在最后一步,我们将为对象分配混淆矩阵和准确率得分。
conmat = metrics.confusion_matrix(y_test, preds)
acc = metrics.accuracy_score(y_test, preds)
在完成我们的脚本之后,我们可以将其导入 R:
source_python("ml.py")
运行此命令后,我们将直接在我们的环境中访问所有 Python 对象。准确率为 0.58
,这并不是非凡的,但肯定比随机要好得多!
利用 Python 脚本的优势
当我们使用 reticulate
中的 source_python
函数时,我们可以显著提高我们的生产力,特别是在与双语团队合作时。想象一下,你的同事在 Python 中构建了 ML 部分,而你需要将他们的工作包含在你的工作中。这只需像源代码一样简单,而不用担心重新编码一切。当加入一个新公司或项目并继承需要立即使用的 Python 代码时,这种情况也是可能的。
如果我们想利用 ggplot
来检查混淆矩阵,我们首先需要转换为 R 的 data.frame
。value
然后是每种情况的观察次数,我们将其映射到 size
,并将 shape
更改为 1(圆形)。结果显示在 图 7-7 上。
library(ggplot2)
py$conmat %>%
as.data.frame.table(responseName = "value") %>%
ggplot(aes(Var1, Var2, size = value)) +
geom_point(shape = 1)
图 7-7. 分类器混淆矩阵的绘图。
并不奇怪,我们有一些组具有非常高的匹配度,因为我们已经知道我们的数据一开始就不平衡。现在,我们如何处理这段漂亮的 Python 代码和输出呢?在 第六章 的结尾,我们看到了创建交互式文档的简单有效方法(记住你在 第五章 学到的东西),使用带有 shiny
运行时的 RMarkdown。让我们在这里实现相同的概念。
预测和用户界面
一旦我们建立了 Python 模型,通常的做法是使用模拟输入进行测试。这使我们能够确保我们的模型能处理正确的输入数据,在 ML 工程中连接实际用户输入之前都是标准做法。为此,我们将为我们模型的五个特征创建五个 sliderInputs
。在这里,我们为简单起见硬编码了最小和最大值,但这些当然可以是动态的。
sliderInput("OWNER_CODE", "Owner code:",
min = 1, max = 15, value = 1)
sliderInput("DISCOVERY_DOY", "Day of the year:",
min = 1, max = 365, value = 36)
sliderInput("FIRE_SIZE", "Number of bins (log10):",
min = -4, max = 6, value = 1)
sliderInput("LATITUDE", "latitude:",
min = 17.965571, max = 48.9992, value = 30)
sliderInput("LONGITUDE", "Longitude:",
min = -124.6615, max = -65.321389, value = 30)
类似于我们在 第六章 结尾所做的,我们将访问内部 input
列表中的这些值,并使用 shiny
包函数来呈现适当的输出。
prediction <- renderText({
input_df <- data.frame(OWNER_CODE = input$OWNER_CODE,
DISCOVERY_DOY = input$DISCOVERY_DOY,
FIRE_SIZE = input$FIRE_SIZE,
LATITUDE = input$LATITUDE,
LONGITUDE = input$LONGITUDE)
clf$predict(r_to_py(input_df))
})
图 7-8. 我们案例研究的结果。
这些元素将根据用户输入的更改动态响应。这正是我们工作所需的,因为这是一个互动产品,而不是静态产品。您可以看到我们在准备这个项目时使用的所有不同代码块。它们应该需要很少的更改,最显著的一个是在推断部分捕捉用户输入的能力。这可以通过访问input
对象来完成。
总结思路
在这个案例研究中,我们展示了如何将现代数据科学家所拥有的优秀工具结合起来,以创建令人惊叹的用户体验,这些体验在视觉上令人愉悦并且促进决策。这只是这样一个优雅系统的基本例子,我们相信通过展示可能性,我们的读者——您——将创建未来的数据科学产品!
^(1) 短,卡伦 C. 2017 年。美国 1992-2015 年空间野火发生数据,FPA_FOD_20170508。第 4 版。科林斯堡,科罗拉多州:森林服务研究数据档案馆。https://doi.org/10.2737/RDS-2013-0009.4
^(2) 我们将详细开发一个强大的分类模型留给我们有动力的读者。事实上,您可能还对预测英亩最终火灾大小的回归感兴趣。有好奇心的读者可以注意到,在 Kaggle 上有一些有趣的笔记本可以帮助您入门。
^(3) 这与开发、托管和部署强大的 ML 模型大相径庭,而且,在任何情况下,这也不是本书的重点。
^(4) 有些读者可能对这种语言不太熟悉。它通常用于像这种情况下的代码配置选项。
^(5) 这部分也可以通过在 R 中使用dbplyr
包或在 RStudio 中使用连接面板来很好地完成。
^(6) 这个包用于扩展ggplot2
在转换数据集方面的功能。
^(7) 作为 Python ML 生态系统模块化的另一个例子,请查看imbalanced-learn
包这里,如果您正在寻找解决方案。
^(8) 这不是所有可能方法或优化的全面阐述,因为我们的重点是建立双语工作流程,而不是详细探讨机器学习技术。读者可以选择参考官方的scikit-learn
文档以获取进一步的指导,位于适当命名的“选择正确的估计器”页面。
附录 A. 附录 A
目前可以在scavetta.academy/PyR4MDS
找到附录。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了