数据分析高级指南-全-

数据分析高级指南(全)

原文:zh.annas-archive.org/md5/33fdac647f302560c3fa02dc8d0db0c8

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

您将要踏上一段重要且值得称赞的学习旅程,这将涉及统计学、编程等。在深入之前,我想花点时间为您解释我的学习目标,我是如何来到这本书的,以及您应该期待什么。

学习目标

在本书结束时,您应该能够使用编程语言进行探索性数据分析和假设检验。探索和测试关系是分析的核心。通过本书您将掌握的工具和框架,将使您能够继续学习更高级的数据分析技术。

我们将使用 Excel、R 和 Python,因为这些工具强大且使学习过程更为顺畅。尽管许多书籍涵盖了这种组合,但从电子表格到编程的过渡对分析师(包括我自己)来说是常见的。

先决条件

为了实现这些目标,本书作出了一些技术和技术假设。

技术要求

我是在 Windows 计算机上,使用 Office 365 版本的 Excel 桌面版编写本书的。只要您在计算机上安装了付费版本的 Excel 2010 或更高版本(无论是 Windows 还是 Mac),您应该能够跟着本书的大部分指导进行学习,只有在数据透视表和数据可视化方面会有一些变化。

注意

虽然 Excel 提供在线的免费和付费版本,但需要付费桌面版才能访问本书涵盖的一些功能。

R 和 Python 均为免费开源工具,适用于所有主要操作系统。本书将在后面介绍如何安装它们。

技术要求

本书假设您对 R 或 Python 没有任何先前知识;尽管如此,它确实依赖于对 Excel 的中等水平的了解,以便降低学习曲线。

您应该熟悉的 Excel 主题包括以下内容:

  • 绝对、相对和混合单元格引用

  • 条件逻辑和条件聚合(IF()语句、SUMIF()/SUMIFS()等)

  • 合并数据源(VLOOKUP()INDEX()/MATCH()等)

  • 使用数据透视表对数据进行排序、筛选和汇总

  • 基本绘图(条形图、折线图等)

如果您希望在继续之前对这些主题有更多练习,我建议阅读迈克尔·亚历山大等人编写的Excel 2019 圣经(Wiley 出版社)。

我的学习之路

像我们行业的许多人一样,我走向分析的道路曲折。在学校,数学成为我积极回避的科目;其中大部分似乎完全是理论的。我确实有一些统计学和计量经济学的课程引起了我的兴趣。将数学应用到某些具体的目标上,让我感到耳目一新。

对统计学的接触确实很少。我上的是文科学院,我掌握了扎实的写作和思维技能,但很少有数量上的技能。当我到达我的第一份全职工作时,我被委托管理的数据的深度和广度震惊了。其中许多数据存储在电子表格中,如果不经过深入的清理和准备,很难从中获得很多价值。

一些“数据整理”是可以预料的;纽约时报 曾报道数据科学家将 50% 到 80% 的时间准备数据进行分析。但我想知道是否有更高效的方法来清理、管理和存储数据。特别是,我想这样做是为了能够花更多时间 分析 数据。毕竟,我总是觉得统计分析有些令人愉悦——手工操作和容易出错的电子表格数据准备则不是那么令人愉悦。

因为我喜欢写作(谢谢你,文科学位),我开始在博客上写关于我在 Excel 中学到的技巧。通过好的恩典和努力工作,博客逐渐受到关注,我将我的职业成功归功于此。欢迎访问 stringfestanalytics.com;我仍然定期发布关于 Excel 和更广泛的分析主题的内容。

当我开始学习更多关于 Excel 的知识时,我的兴趣扩展到了其他分析工具和技术。此时,开源编程语言 R 和 Python 在数据领域获得了显著的流行。但是,尽管我在掌握这些语言的过程中感到了不必要的阻力。

“Excel 不好,编程好”

我注意到对于 Excel 用户来说,大多数 R 或 Python 培训听起来都很像这样:

一直以来,你都在使用 Excel,而你真正应该学会编程。看看 Excel 带来的所有问题!是时候彻底改掉这个习惯了。

这种态度是错误的,原因有几个:

这不是准确的

编程和电子表格之间的选择通常被描述为一种善恶之间的斗争。实际上,更好的是将这些视为 互补 工具,而不是替代品。电子表格在分析中有其作用;编程也是如此。学习和使用一个并不排斥另一个。第五章讨论了这种关系。

这是一种糟糕的教学方法

Excel 用户直观地了解如何处理数据:他们可以对其进行排序、过滤、分组和连接。他们知道哪些排列方式便于分析,哪些意味着需要大量清理。这是一笔宝贵的知识可供借鉴。好的教学将利用它来弥合电子表格和编程之间的鸿沟。不幸的是,大多数教学却出于鄙视而焚毁了这座桥。

研究表明,将所学内容与已有知识联系起来是很有力的。正如彼得·C·布朗等人在《坚持到底:成功学习的科学》(Belknap Press)中所说的:

您能够解释新学习与先前知识的关系越多,您对新学习的掌握就越牢固,您创建的连接也越多,这些连接将帮助您以后记住它。

作为 Excel 用户,当你被错误地告知你已经掌握的东西是垃圾时,很难将新思想与你已经掌握的内容联系起来。这本书采取了不同的方法,建立在您对电子表格的先前知识基础上,这样在我们进入 R 和 Python 时,您将有一个清晰的框架。

注意

电子表格和编程语言都是有价值的分析工具;一旦掌握了 R 和 Python,就没有必要放弃 Excel。

Excel 的教学益处

实际上,Excel 是一个独特而绝妙的分析教学工具:

它减少了认知负担

认知负担是理解某事所需的逻辑连接或跳跃的数量。通常,分析学习之旅看起来像这样:

  1. 学习全新的技术。

  2. 学习如何使用全新的编码技术来实施全新的技术。

  3. 进步到更高级的技术,而从未真正掌握基础知识。

学习分析的概念基础已经很困难了。在同时学习编程更是增加了极高的认知负担。基于我将要讨论的原因,通过编码进行分析练习确实具有很大的优点。但更好的方法是在掌握它们的同时隔离这些技能集。

这是一个视觉计算器

第一款大众市场上的电子表格软件被称为 VisiCalc——字面上是视觉计算器。这个名字指向了该应用程序最重要的卖点之一。特别是对于初学者来说,编程语言可能类似于“黑匣子”——输入魔术词,点击“运行”,咔嚓:结果出来了。程序很可能做对了,但对于新手来说,可能很难打开引擎盖看看是为什么(或者更为关键的是,为什么没有)。

相比之下,Excel 允许您在每一个步骤中观察分析的形成。它允许您进行可视化的计算和重新计算。与仅仅相信我的(或编程语言的)话不同,您将在 Excel 中构建演示来可视化关键的分析概念。

注意

Excel 提供了学习数据分析基础的机会,而无需同时学习新的编程语言。这极大地减少了认知负担。

书籍概述

现在您已经理解了本书的精神和我希望您能够实现的目标,让我们来回顾一下它的结构。

第一部分,Excel 中的分析基础

分析建立在统计学基础之上。在这一部分中,您将学习如何使用 Excel 探索和测试变量之间的关系。您还将使用 Excel 构建一些最重要的统计和分析概念的引人注目的演示。这种统计理论的基础和分析框架将为您在数据编程中打下坚实的基础。

第二部分,从 Excel 到 R

现在你已经熟悉了数据分析的基础知识,是时候学习一两种编程语言了。我们将从 R 语言开始,这是一种专门用于统计分析的开源语言。你将学会如何将在 Excel 中处理数据的技能清晰地转移到 R 中。本节以 R 语言进行了一个端到端的封顶练习。

第三部分,从 Excel 到 Python

Python 是另一种值得学习的开源语言,用于分析。与第二部分相同的精神,你将学习如何将你的 Excel 数据技能转移到这种语言,并进行完整的数据分析。

章节末尾的练习

当我阅读书籍时,我倾向于跳过章节末尾的练习,因为我认为保持阅读的动力更有价值。不要像我这样做!

大多数章节末尾都提供了练习的机会。你可以在配套资源库的exercise-solutions文件夹中找到这些练习的解答,每个章节的文件名即为对应的解答。完成这些练习,然后将你的答案与解答进行比较。这样做不仅会增加你对材料的理解,同时也为我提供了一个很好的示例。

提示

最好是积极地学习;如果不立即将所读之物付诸实践,你很可能会忘记它。

这不是一个枯燥的清单

我喜欢分析的一点是几乎总会有多种方法来做同一件事情。当你熟悉另一种方法时,我可能会演示如何以一种方式做某事。

本书的重点是将 Excel 作为教学工具来进行分析,并帮助读者将这些知识转移到 R 和 Python 上。如果我让书籍成为给定数据清理或分析任务的大脑转储,那么它将失去围绕特定目标的焦点。

你可能更喜欢用另一种方式做某事;我甚至可能同意在不同情况下有更好的方法。然而,考虑到本书及其目标的情况,我决定覆盖某些技术并排除其他技术。否则,书籍可能会失去其针对性的指导,变成一本枯燥的操作手册。

不要惊慌

作为作者,我希望你觉得我很随和易接近。然而,对于这本书,我有一个规则:不要惊慌! 这里确实存在一个陡峭的学习曲线,因为你将不仅探索概率和统计,还将学习两种编程语言。本书将向你介绍统计学、计算机科学等概念。它们可能最初会让人感到不适,但随着时间的推移,你会逐渐内化它们。允许自己通过试错来学习。

我深信,凭借你对 Excel 的了解,这是一个可以实现的目标。可能会有挫折和冒名顶替的时刻;这种情况发生在我们每个人身上。不要让这些时刻掩盖你在这里取得的真正进步。

你准备好了吗?我会在第一章中见到你。

本书中使用的约定

本书中使用以下排版约定:

斜体

表示新术语、URL、电子邮件地址、文件名、文件扩展名和数据集变量。

等宽字体

用于程序清单,以及在段落中用于引用程序元素,如代码变量或函数名称、数据库、数据类型、环境变量、语句和关键字。

提示

此元素表示提示或建议。

此元素表示一般说明。

警告

此元素表示警告或注意。

使用代码示例

补充材料(代码示例、练习等)可在https://github.com/stringfestdata/advancing-into-analytics-book下载。

您可以在计算机上下载并解压缩文件夹的副本,或者如果您熟悉 GitHub,可以克隆它。此存储库包含主文件夹中每个章节的脚本和工作簿的完成副本。需要跟随本书的所有数据集都位于datasets文件夹的一个单独子文件夹中,并附有有关其来源和获取和清理步骤的说明。建议您不要直接操作任何这些 Excel 工作簿,而是制作副本,因为操作源文件可能会影响后续步骤。章节结束的练习的所有解决方案都可以在exercise-solutions文件夹中找到。

如果您有技术问题或使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com

本书旨在帮助您完成工作。通常情况下,如果本书提供了示例代码,您可以在自己的程序和文档中使用它。除非您复制了代码的大部分内容,否则无需征得我们的许可。例如,编写一个使用本书中几个代码块的程序不需要许可。销售或分发 O'Reilly 图书中的示例代码需要许可。通过引用本书并引用示例代码来回答问题不需要许可。将本书中大量示例代码纳入产品文档需要许可。

我们感激,但通常不要求署名。署名通常包括标题、作者、出版商和 ISBN。例如:“进阶分析,作者乔治·蒙特(O’Reilly)。版权所有 2021 乔治·蒙特,978-1-492-09434-0。”

如果您认为您对代码示例的使用超出了公平使用范围或上述许可,请随时与我们联系 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(传真)

我们为这本书创建了一个网页,列出了勘误、示例和任何额外信息。您可以访问 https://oreil.ly/advancing-into-analytics

发送电子邮件至 bookquestions@oreilly.com 对本书进行评论或提出技术问题。

有关我们的书籍和课程的新闻和信息,请访问 http://oreilly.com

在 Facebook 上找到我们:http://facebook.com/oreilly

在 Twitter 上关注我们:http://twitter.com/oreillymedia

在 YouTube 上观看我们:http://www.youtube.com/oreillymedia

致谢

首先,我要感谢上帝给了我这个机会培养和分享我的才能。在 O’Reilly,Michelle Smith 和 Jon Hassell 合作愉快,他们给我写书的邀请让我永远感激不尽。Corbin Collins 在书的编写过程中给予了我很大的支持。Danny Elfanbaum 和制作团队将原始手稿变成了一本真正的书。Aiden Johnson、Felix Zumstein 和 Jordan Goldmeier 提供了宝贵的技术审查。

让人们审阅一本书并不容易,所以我必须感谢 John Dennis、Tobias Zwingmann、Joe Balog、Barry Lilly、Nicole LaGuerre 和 Alex Bodle 的评论。我还要感谢那些使这项技术和知识可用的社区,他们通常没有直接的报酬。通过我的分析追求,我结交了一些了不起的朋友,他们非常愿意分享他们的时间和智慧。我在 Padua Franciscan High School 和 Hillsdale College 的教育者让我爱上了学习和写作。如果没有他们的影响,我怀疑我会写书。

我还要感谢我的母亲和父亲,他们给予我无比珍贵的爱和支持。最后,向我已故的爷爷致敬:感谢您与我分享了勤奋工作和正直的价值观。

第一部分:Foundations of Analytics in Excel

第一章:探索性数据分析的基础

“你永远不知道会有什么从那扇门走出来,”瑞克·哈里森在热门节目当铺之星的开场白中说道。在分析中也是一样:面对一个新数据集,你永远不知道你将会发现什么。本章是关于 探索描述 数据集,以便我们知道要向它提出什么问题。这个过程被称为 探索性数据分析,或者 EDA。

什么是探索性数据分析?

美国数学家约翰·图基在他的书《探索性数据分析》(Pearson)中推广了使用 EDA 的方法。图基强调分析员需要首先 探索 数据以找出潜在的研究问题,然后再通过假设检验和推断统计学来 确认 答案。

EDA(探索性数据分析)通常被比喻为对数据进行“面试”;这是分析员了解数据并了解它有什么有趣的事情的时候。作为我们的面试的一部分,我们将要做以下事情:

  • 将我们的变量分类为连续的,分类的等等

  • 用描述性统计总结我们的变量

  • 使用图表可视化我们的变量

EDA 给了我们很多事情要做。让我们使用 Excel 和一个真实的数据集来走一遍这个过程。您可以在本书的存储库中的star子文件夹下的datasets文件夹中找到star.xlsx工作簿中的数据。这个数据集是为了研究班级规模对考试成绩的影响而收集的。对于这个和其他基于 Excel 的演示,我建议您完成以下步骤,使用原始数据:

  1. 复制文件,以保持原始数据集不变。我们稍后将把这些 Excel 文件中的一些导入到 R 或 Python 中,因此对数据集的任何更改都将影响到该过程。

  2. 添加一个名为id的索引列。这将为数据集的每一行编号,使得第一行的 ID 为 1,第二行为 2,依此类推。这可以在 Excel 中快速完成,方法是在列的前几行输入数字,然后突出显示该范围,并使用快速填充根据该模式完成选择。查找您活动单元格右下角的小方块,在上面悬停直到您看到一个小加号,然后填写其余的范围。添加这个索引列将使得按组分析数据更容易。

  3. 最后,通过选择范围中的任何单元格,然后转到功能区并单击插入→表格来将结果数据集转换为表格。Windows 的键盘快捷键是 Ctrl + T,Mac 的是 Cmd + T。如果您的表格有标题,请确保“我的表格有标题”选项已打开。表格有很多好处,其中最重要的是它们的美观性。在表操作中也可以通过名称引用列。

    你可以通过单击表格任意位置,然后转到功能区并单击“表格设计”→“属性”组下的“表格名称”,来为表格命名,就像图 1-1 所示。

Excel 中的表格名称

图 1-1. 表格名称框

这些首个分析任务将成为在 Excel 中处理其他数据集时的良好实践。对于star数据集,你完成的表格应该看起来像图 1-2。我已经将我的表格命名为star。这个数据集按列和行的矩形形式排列。

标记的 STAR 数据

图 1-2. star数据集,按行和列排列

你可能已经处理过足够多的数据,知道这是分析所需的理想形式。有时,我们需要清理数据,以使其达到我们想要的状态;我将在本书的后面讨论其中一些数据清理操作。但是现在,让我们感叹一下,并学习关于我们的数据和 EDA 的相关内容。

在分析中,我们经常使用观察变量这些术语,而不是。让我们探索这些术语的重要性。

观察

在这个数据集中,我们有 5,748 行:每一行都是一个独特的观察结果。在这种情况下,测量是在学生级别进行的;观察结果可以是从个体公民到整个国家的任何内容。

变量

每列都提供了关于我们观察的独特信息片段。例如,在star数据集中,我们可以找到每个学生的阅读分数(treadssk)以及学生所在班级类型(classk)。我们将这些列称为变量。表 1-1 描述了star数据集中每列所测量的内容:

表 1-1. star数据集变量的描述

描述
id 唯一标识符/索引列
tmathssk 总数学标准化分数
treadssk 总阅读标准化分数
classk 班级类型
totexpk 教师的总工作年限
sex 性别
freelunk 是否有资格免费午餐?
race 种族
schidkn 学校标识符

准备好一个废话?我们称它们为变量,因为它们的值可能会在观察中变化。如果我们记录的每个观察结果都返回相同的测量结果,那么分析的意义就不大了。每个变量可以为我们的观察提供非常不同的信息。即使在这个相对较小的数据集中,我们有文本、数字以及是/否陈述作为变量。有些数据集可能会有几十甚至上百个变量。

在继续我们的分析时,对这些变量类型进行分类将会有所帮助。请记住,这些区分在某种程度上是任意的,并且可能会根据分析的目的和情况而改变。你会发现探索性数据分析(EDA)以及整体分析具有高度的迭代性。

注意

变量的分类有些是主观的,与许多分析一样,建立在经验法则而不是硬性标准之上。

我将讨论不同的变量类型,如图 1-3 所示,然后根据这些区别对star数据集进行分类。

变量类型

图 1-3. 变量类型

这里还有进一步的变量类型可以涵盖:例如,我们不会考虑区间和比例数据之间的差异。要更详细地了解变量类型,请查阅 Sarah Boslaugh 的Statistics in a Nutshell,第二版(O’Reilly)。让我们从图 1-3 的左侧向右侧工作。

分类变量

有时被称为定性变量,这些描述观察结果的质量或特征。分类变量通常回答的典型问题是“哪种类型?”分类变量通常用非数值表示,尽管并非总是如此。

一个分类变量的例子是原产国家。像任何变量一样,它可以采用不同的值(美国,芬兰等),但我们不能在它们之间进行定量比较(谁能告诉我印度尼西亚是美国的两倍?)。分类变量所采用的任何独特值称为该变量的一个级别。原产国家的三个级别可能是美国,芬兰或印度尼西亚,例如。

因为分类变量描述的是观察结果的质量而不是数量,因此这些数据上的许多定量操作并不适用。例如,我们无法计算平均出生国家,但我们可以计算最常见的,或者每个级别的总体频率计数。

我们可以进一步根据它们可以采用的级别数量以及这些级别的排名顺序是否有意义来区分分类值。

二元变量只能采用两个级别。通常,这些变量被陈述为是/否的回答,尽管并非总是如此。一些二元变量的例子包括:

  • 结婚了吗?(是或否)

  • 是否购买?(是或否)

  • 酒的类型?(红或白)

就酒的类型而言,我们隐含地假设我们感兴趣的数据仅包括红葡萄酒或白葡萄酒……但是如果我们还想分析葡萄酒,会发生什么?在这种情况下,我们不能再包含所有三个级别并将数据分析为二元数据。

任何具有超过两个级别的定性变量都是名义变量。一些例子包括:

  • 原产国家(美国,芬兰,印度尼西亚等)

  • 喜欢的颜色(橙色,蓝色,烧焦赭色等)

  • 酒的类型(红,白,葡萄酒)

注意,像 ID 号这样的东西是以数字形式陈述的分类变量:虽然我们可以计算平均 ID 号,但这个数字是没有意义的。重要的是,名义变量没有内在的排序。例如,红色作为颜色不能本质上比蓝色更高或更低。由于内在的排序并不一定清楚,让我们看一些其使用的例子。

有序变量具有两个以上的级别,在这些级别之间存在内在的排序。一些有序变量的例子包括:

  • 饮料尺寸(小,中,大)

  • 班级(大一,大二,大三,大四)

  • 工作日(星期一,星期二,星期三,星期四,星期五)

在这里,我们可以本质上对级别进行排序:高年级高于低年级,而我们不能说红色与蓝色一样。虽然我们可以排名这些级别,但我们不一定能量化它们之间的距离。例如,小型和中型饮料之间的大小差异可能不同于中型和大型饮料之间的差异。

定量变量

这些变量描述了观察的可测量数量。定量变量通常由数字表示。我们可以根据它们能够取值的数量进一步区分定量变量。

连续变量的观察理论上可以在任何两个其他值之间取无限多个值。这听起来很复杂,但连续变量在自然界中非常常见。一些例子包括:

  • 身高(在 59 到 75 英寸范围内,观察值可以是 59.1、74.99 或者介于两者之间的任何其他值)

  • pH 水平

  • 表面积

因为我们可以对连续变量的不同观察进行定量比较,所以我们可以应用更广泛的分析方法。例如,对连续变量取平均值是有意义的,而对于分类变量则不然。本章后面,您将学习如何通过在 Excel 中找到它们的描述性统计来分析连续变量。

另一方面,离散变量的观察只能在任意两个值之间取有限数量的可计数值。离散变量在社会科学和商业中非常常见。一些例子包括:

  • 一个家庭中的人数(在 1 和 10 之间的范围内,观察可以是 2 或 5,但不能是 4.3)

  • 销售单位

  • 森林中的树木数量

经常情况下,当我们处理具有许多水平或许多观测值的离散变量时,为了进行更全面的统计分析,我们会将它们视为连续变量。例如,你可能听说过美国家庭平均有 1.93 个孩子。我们知道,没有家庭确实有这么多孩子。毕竟,这是一个离散变量,只能是整数。然而,在许多观察中,这个说法可以作为期望在典型家庭中有多少孩子的有用表示。

但等等,还有更多!在更高级的分析中,我们经常会重新计算和混合变量:例如,我们可能对一个变量进行对数变换,以满足特定分析的假设,或者我们可能使用称为降维的方法将多个变量的含义提取为更少的变量。这些技术超出了本书的范围。

演示:变量分类

利用你到目前为止学到的知识,使用图 1-3 中介绍的类型来分类星号变量。在你深入思考时,不要犹豫去调查数据。我会在这里为你提供一个简便方法,稍后在本章节中我们将详细讨论这个过程。

一个快速获取变量类型感觉的方法是查找它们所具有的唯一值的数量。可以通过 Excel 中的筛选预览来完成此操作。我点击了图 1-4 中sex变量旁边的下拉箭头,并发现它只有两个不同的值。你认为这可能是什么类型的变量?花点时间通过这种或其他方法来检查这些变量。

过滤表格

图 1-4. 使用筛选预览查找变量具有多少个不同值

表格 1-2 展示了我如何对这些变量进行分类。

表格 1-2. 我如何对这些变量进行分类

变量 描述 分类或数量? 类型?
id 索引列 分类 名义
tmathssk 总数学标准化分数 数量型 连续型
treadssk 总阅读分数标准化分数 数量型 连续型
classk 班级类型 分类 名义
totexpk 教师的总工作经验年限 数量型 离散型
sex 性别 分类 二元
freelunk 是否有资格享受免费午餐? 分类 二元
race 种族 分类 名义
schidkn 学校指标 分类 名义

其中一些变量,如classkfreelunk,比较容易分类。而其他一些,如schidknid,则不那么明显:它们以数值方式陈述,但不能进行数量化比较。

警告

数据以数值方式陈述,并不意味着它可以用作数量型变量。

你会发现,这些变量中只有三个是定量的:tmathssktreadssktotexpk。我决定将前两个分类为连续变量,最后一个分类为离散变量。为了理解原因,让我们从totexpk开始,即教师的教学经验年限。所有这些观察结果都是以整数表达的,范围从 0 到 27。因为这个变量只能取固定数量的可数值,我将其分类为离散变量。

那么tmathssktreadssk这两个测试分数如何呢?这些分数也都是以整数表达的:也就是说,一个学生不能得到 528.5 分,只能是 528 或 529 分。从这个角度来看,它们也是离散的。然而,因为这些分数可以有如此多的唯一值,实际上将它们分类为连续变量是有道理的。

看到这样一个严谨的分析领域中竟然没有多少明确的规则,可能会让你感到惊讶。

总结:变量类型

熟知规则,这样你才能有效地打破它们。

第十四世达丨赖喇嘛

我们如何对待一个变量的分类方式会影响我们在分析中对其的处理方式——例如,我们可以计算连续变量的均值,但不能计算名义变量的均值。同时,出于便利性考虑,我们常常会打破规则——例如,计算离散变量的平均值,这样一家人平均有 1.93 个孩子。

随着我们在分析中的进展,我们可能会决定更改更多规则,重新分类变量,或者完全构建新的变量。记住,探索性数据分析(EDA)是一个迭代的过程。

注意

与数据和变量一起工作是一个迭代的过程。我们对变量分类的方式可能会随着我们在探索中发现的内容以及我们决定询问数据的类型而改变。

在 Excel 中探索变量

让我们继续使用star数据集进行描述性统计可视化的探索。我们将在 Excel 中进行这一分析,尽管你也可以在 R 或 Python 中按照相同步骤获得相同的结果。通过本书的学习,你将能够使用这三种方法进行 EDA。

我们将从star的分类变量开始进行变量探索。

探索分类变量

记住,我们在处理分类变量时是在测量特性而不是数量,因此这些变量不会有有意义的平均值、最小值或最大值,例如。尽管如此,我们仍然可以对这些数据进行一些分析,特别是通过计算频率。在 Excel 中,我们可以使用数据透视表来做到这一点。将光标放在star数据集的任何位置,然后选择插入 → 数据透视表,如图 1-5 所示。然后点击确定。

插入数据透视表

图 1-5. 插入数据透视表

我想找出每种班级类型的观察次数。为此,我将classk拖到数据透视表的“行区域”,并将id放入“值”中。默认情况下,Excel 将id字段的总和。它错误地假设分类变量是定量的。我们不能定量比较 ID 号码,但我们可以计算它们的频率。在 Windows 上执行此操作,请单击“值区域”中的“Sum of id”,然后选择“值字段设置”。在“汇总值字段方式”下,选择“计数”。单击“确定”。在 Mac 上,单击“Sum of id”旁边的i图标以执行此操作。现在我们得到了想要的结果:每种班级类型的观察次数。这被称为单向频率表,如图 1-6 所示。

单向频率表

图 1-6. 班级类型的单向频率表

将这个频率计数拆分为参加和未参加免费午餐项目的学生的观察结果。为此,请将freelunk放入数据透视表的“列区域”中。现在我们有一个双向频率表,如图 1-7 所示。

双向频率表

图 1-7. 班级类型按午餐项目分类的双向频率表

在本书的整个过程中,我们将创建可视化作为分析的一部分。除此以外,我们不会花太多时间讨论数据可视化的原则和技术。然而,这个领域非常值得您的研究;对于一个有帮助的介绍,请查看克劳斯·O·威尔克的《数据可视化基础》(O’Reilly)。

我们可以用条形图(也称为条形图计数图)可视化一种或两种方式的频率表。让我们通过点击数据透视表内部并点击插入 → 聚类列来绘制我们的双向频率表。图 1-8 显示了结果。我将通过点击图表周围,然后点击右上角出现的加号图标来为图表添加标题。在显示的图表元素菜单中,选中图表标题。在 Mac 上找到此菜单,请单击图表,然后从功能区转到“设计” → “添加图表元素”。在本书的其余部分,我将继续以这种方式添加图表。

注意,计数图和表格将观察次数按班级类型分成了参与和不参与免费午餐项目的学生。例如,表格和计数图上的第一和第二标签和柱分别表示 1,051 和 949。

分组条形图

图 1-8. 以计数图方式可视化的双向频率表

即使对于像双向频率表这样简单的分析,将结果可视化也不是一个坏主意。人们可以比处理表中的数字更轻松地处理图表上的线条和柱,因此随着我们分析的复杂性增加,我们应继续绘制结果。

对于分类数据,我们无法进行定量比较,因此我们对它们进行的任何分析都将基于它们的计数。这可能看起来不那么令人兴奋,但仍然很重要:它告诉我们哪些值的级别最常见,我们可能希望通过其他变量比较这些级别进行进一步分析。但现在,让我们探索定量变量。

探索定量变量

在这里,我们将运行更全面的摘要描述性统计。描述性统计允许你使用定量方法总结数据集。频率是描述性统计的一种类型;让我们逐步介绍其他方法以及如何在 Excel 中计算它们。

集中趋势度量是描述性统计的一组统计量,表达了典型观测值的取值。我们将涵盖这些度量中的三个最常见的。

首先是均值或平均值。更具体地说是算术平均值,计算方法是将所有观测值相加,然后除以观测值的总数。在所有讨论的统计量中,你可能最熟悉这个,我们会继续参考它。

接下来是中位数。这是数据集中的中间观测值。要计算中位数,首先将数据从低到高排序或排名,然后从两侧向内数,找到中间的观测值。如果中间有两个值,取平均值作为中位数。

最后,众数:出现频率最高的值。排序数据也有助于找到众数。一个变量可能有一个、多个或没有众数。

Excel 拥有丰富的统计函数套件,包括一些用于计算集中趋势度量的函数,这些函数在表格 1-3 中显示。

表 1-3. Excel 用于测量集中趋势的函数

统计量 Excel 函数
Mean AVERAGE(number1, [number2], ...)
Median MEDIAN(number1, [number2], ...)
Mode MODE.MULT(number1, [number2], ...)

MODE.MULT() 是 Excel 中利用动态数组返回多个潜在模式的新函数。如果你无法使用此函数,请尝试 MODE()。利用这些函数,找出我们的tmathssk分数的集中趋势度量。图表 1-9 显示了结果。

从这个分析中,我们看到我们的三个集中趋势度量具有相似的值,均值为 485.6,中位数为 484,众数为 489。我还决定查找众数发生的频率:277 次。

在 Excel 中计算集中趋势度量

图表 1-9. 在 Excel 中计算集中趋势度量

有了所有这些中心趋势度量,应该关注哪一个呢?我将通过一个简短的案例研究来回答这个问题。想象一下你在一个非营利组织做咨询。他们要求你查看捐赠情况,并建议追踪哪种中心趋势度量。捐赠情况如表 1-4 所示。请花一点时间计算并做出决定。

表 1-4。考虑给定这些数据应该追踪哪个度量

$10 $10 $25 $40 $120

算术平均数似乎是一个传统的追踪方法,但是$41 真的代表了我们的数据吗?实际上,除了一个个人捐赠以外,所有捐赠都不到这个数;$120 的捐赠正在夸大这个数字。这是算术平均数的一个缺点:极端值可能会不适当地影响它。

如果我们使用中位数就不会有这个问题:$25 或许更能代表“中间值”而不是$41。这个度量的问题在于它没有考虑到每个观察值的精确值:我们只是“倒数”进入变量的中间,而没有考虑每个观察值的相对大小。

这就让我们只剩下众数,它确实提供了有用的信息:普遍的礼物是$10。然而,$10 并不能代表捐赠的整体情况。此外,正如前文所述,数据集可能有多个众数,也可能没有,因此这不是一个非常稳定的度量。

对于非营利组织的回答呢?应该追踪和评估它们所有的度量。每个度量都从不同的角度总结了我们的数据。然而,正如你将在后面的章节中看到的,进行更高级的统计分析时,最常见的是关注平均值。

注:

我们经常会分析几个统计量,以便更全面地了解同一数据集。没有一种度量一定比其他更好。

现在我们已经确定了变量的“中心”在哪里,我们想要探索这些值从中心“散开”的程度。存在几种可变性度量;我们将重点关注最常见的。

首先,是范围,或者说最大值和最小值之间的差异。尽管推导起来很简单,但它对观察结果非常敏感:只要有一个极端值,范围就可能对大多数观察结果的实际位置产生误导。

接下来是方差。这是一个测量观察值与平均值之间分散程度的指标。这比我们到目前为止讨论的内容更加深入。我们的步骤如下:

  1. 找到我们数据集的平均值。

  2. 从每个观察值中减去平均值。这就是偏差

  3. 求出所有偏差的平方和。

  4. 将平方和除以观察次数。

要跟上这么多内容确实有点难。对于这么复杂的操作,使用数学符号可能会有所帮助。我知道起初可能会有些让人畏惧,但请考虑前面列表的替代方案。这里的数学符号可以提供一个更精确的表达方法。例如,我们可以涵盖找到方差的所有步骤,如公式 1-1 所示:

公式 1-1. 找到方差的公式

s 2 = (X-X ¯) 2 N

s 2 是我们的方差。 (X-X ¯) 2 告诉我们,我们需要从每个观察值 X 中减去平均值 X ¯ ,然后平方。 告诉我们对这些结果求和。最后,将结果除以观察值的数量 N

在本书中我还会多次使用数学符号,但只有在它比逐步书写更有效表达和理解给定概念时才会这样做。尝试计算 表 1-5 中数字的方差。

表 1-5. 测量这些数据的变异性

3 5 2 6 3 2

由于这个统计量相对更复杂,我将使用 Excel 来管理计算。稍后你将学习如何使用 Excel 的内置函数计算方差。图 1-10 展示了结果。

在 Excel 中计算方差

图 1-10. 在 Excel 中计算方差

你可以在本章附带的工作簿的 variability 工作表中找到这些结果,即 ch-1.xlsx

你可能会问为什么我们要处理偏差的平方。要理解为什么,请对未平方的偏差求和。它是零:这些偏差互相抵消了。

方差的问题在于现在我们是在处理原始单位的平方偏差。这不是分析数据的直观方式。为了纠正这一点,我们将取方差的平方根,即标准差。变异性现在是用测量单位的平均值来表达的。公式 1-2 展示了用数学符号表示的标准差。

公式 1-2. 找到标准差的公式

s = X i -X ¯ 2 N

使用这个公式,图 1-10 的标准偏差是 1.5(2.25 的平方根)。我们可以使用表 1-6 中的函数在 Excel 中计算这些变异度的测量。请注意,对样本总体方差和标准偏差使用了不同的函数。样本测量使用N - 1而不是N作为分母,结果是较大的方差和标准偏差。

表 1-6. Excel 的测量变异性的函数

统计量 Excel 函数
范围 MAX(number1, [number2], ...)_ - _MIN(number1, [number2], ...)
方差(样本) VAR.S(number1, [number2], ...)
标准偏差(样本) STDEV.S(number1, [number2], ...)
方差(总体) VAR.P(number1, [number2], ...)
标准偏差(总体) STDEV.P(number1, [number2], ...)

后面的章节将重点讨论样本与总体的区别。如果你不确定是否已经收集了你感兴趣的所有数据,可以使用样本函数。正如你现在看到的,我们有几个描述统计要关注。我们可以利用 Excel 的函数加快计算速度,但也可以使用其数据分析工具包通过几次点击生成完整的描述统计。

小贴士

在计算总体或样本的统计量时,有些统计量会有所不同。如果你不确定你正在处理的是哪种情况,请假设是样本。

这个插件已经与 Excel 一同安装,但首先需要加载它。对于 Windows,从功能区选择 文件 → 选项 > 加载项。然后点击底部的“转到”。从菜单中选择分析工具包,然后点击确定。不必选择分析工具包-VBA 选项。对于 Mac,从菜单栏选择 数据 → 分析工具。从菜单中选择分析工具包,然后点击确定。可能需要重新启动 Excel 以完成配置。之后,您将在“数据”选项卡中看到一个新的数据分析按钮。

在表 1-1 中,我们确定了tmathssktreadssk是连续变量。让我们使用 ToolPak 计算它们的描述统计。从功能区选择 数据 → 数据分析 → 描述统计。会出现一个菜单;选择输入范围B1:C5749。确保打开“首行为标签”和“汇总统计”复选框。您的菜单应该看起来像图 1-11。您可以保持其他设置不变,然后点击确定。

这将把这两个变量的描述统计插入到一个新的工作表中,如图 1-12 所示。

现在让我们来比较各组中每个分类变量的描述统计量。为此,将基于star数据插入新的工作表中的透视表。将freelunk放置在列区域,id放置在行区域,将Sum of treadssk放置在值区域。请记住,id字段是唯一标识符,因此我们实际上不应该在透视表中对其求和,尽管它可能会这样认为。

在 ToolPak 中运行描述统计

图 1-11. 使用分析 ToolPak 导出描述统计量

ToolPak 中的描述统计

图 1-12. 从分析 ToolPak 导出的描述统计量

对于这次以及将来进行的任何透视表操作,最好是关闭所有总计,方法是单击内部并选择“设计”→“总计”→“关闭行”和“关闭列”。这样我们就不会误将总计包括在分析中。现在可以使用 ToolPak 插入描述统计量。图 1-13 显示了结果。

按组进行的描述统计

图 1-13. 按组计算描述统计量

您已经了解了这些措施的大部分内容;本书稍后将会涉及其余部分。看起来,ToolPak 呈现的所有信息似乎都消除了对数据可视化的需求。事实上,可视化仍然在探索性数据分析中发挥着不可或缺的作用。特别是,我们将使用它们来了解在变量的整个值范围内观测值的分布

首先,我们将看看直方图。通过这些图,我们可以可视化观测值按区间的相对频率。要在 Excel 中构建treadssk的直方图,请选择该数据范围,然后转到功能区并选择“插入”→“直方图”。图 1-14 显示了结果。

从图 1-14 中我们可以看到,最频繁出现的区间在 426.6 到 432.8 之间,大约有 650 个观测值落在这个范围内。我们实际的测试分数中没有小数,但是我们的 x 轴可能包括它们,这取决于 Excel 如何确定间隔或箱子。我们可以通过右键单击图表的 x 轴并选择“格式轴”来更改箱子的数量。右侧将显示一个菜单。(这些功能在 Mac 上不可用。)

阅读分数的分布

图 1-14. 阅读分数的分布

默认情况下,Excel 决定使用 51 个箱子,但如果我们(大约)将该数字减半和加倍为 25 和 100 呢?在菜单中调整这些数字;图 1-15 显示了结果。我喜欢将其看作是在分布的细节上“放大和缩小”。

直方图箱子宽度

图 1-15. 改变直方图箱子数量

将分布可视化为直方图后,我们可以快速看到,在分布的极右端有相当数量的考试分数,但大多数考试分数主要集中在 400–500 的范围内。

如果我们想要查看阅读分数的分布如何在三种班级规模之间变化?在这里,我们正在比较一个连续变量在一个分类变量的三个水平上的表现。在 Excel 中通过直方图设置这一点将需要一些“技巧”,但我们可以依赖数据透视表来完成任务。

在基于star数据集上插入一个新的数据透视表,然后将treadssk拖到行区域,classk拖到列区域,“id 的计数”拖到值区域。同样地,如果我们从数据透视表中删除总计,后续的分析会更容易。

现在让我们从这些数据创建一个图表。单击数据透视表中的任意位置,然后从功能区选择插入 → 簇状列。结果如图 1-16 所示,非常难以阅读,但与源数据透视表进行比较,它告诉我们对于分数为 380 的学生,10 人是常规班级,2 人是带助理的常规班级,2 人是小班级。

原始分组直方图

图 1-16. 开始一个多组直方图

从这里开始,将这些值合并到更大的间隔中就是一个问题了。为此,右键单击数据透视表的第一列的任意位置,然后选择“组合”。Excel 将默认将此分组设置为 100 的增量;将其更改为 25。

一个可识别的直方图开始显现。让我们重新格式化图表,使其看起来更像一个。右键单击图表的任何条形,然后选择“格式数据系列”。您将把“系列重叠”调整为 75%,将“间隙宽度”调整为 0%。图 1-17 显示了结果。

分组直方图

图 1-17. 使用数据透视表创建一个多组直方图

我们可以将间隙宽度设置为完全相交,但这样一来,就更难看清晰的常规班级大小分布。直方图是查看连续变量分布的常用可视化工具,但它们很快就会变得混乱不堪。

作为替代方案,让我们来看看箱线图。在这里,我们将以四分位数来可视化我们的分布。箱线图的中心是一个您熟悉的度量值,中位数

作为我们数据集的“中间”位置,一个理解中位数的方式是将其视为第二个四分位数。我们可以通过将数据集均匀地分成四分之一并找到它们的中点来找到第一和第三四分位数。图 1-18 标记了箱线图的各个元素。

带标签的箱线图

图 1-18. 箱线图的元素

在“盒形图”中找到的结果图的部分被称为四分位距。 这个范围被用作推导图的其他部分的基础。 落在 1.5 倍四分位距内的剩余范围由两条线或“须”表示。 实际上,Excel 将这种类型的图称为盒形和须图。

不在此范围内的观察显示为图中的单个点。 这些被视为异常值。 盒形图可能比直方图更复杂,但幸运的是 Excel 会为我们处理所有准备工作。 让我们回到我们的treadssk示例。 高亮显示此范围,然后从选项卡中选择“插入”→“盒形图”。

我们可以在图 1-19 中看到,我们的四分位距大约在 415 到 450 之间,并且有几个异常值,特别是在高端。 我们从直方图中注意到了数据的类似模式,虽然我们对完整分布有了更加视觉化的视角,并且能够使用不同的箱宽度在不同粒度水平上进行检查。 就像描述性统计一样,每个可视化都提供了数据的独特视角; 没有一个本质上比其他视角更优越。

阅读成绩分布的盒形图

图 1-19. 一个阅读成绩的盒形图

盒形图的一个优点是它为我们提供了关于数据四分位数位置的一些精确信息,以及哪些观测值被认为是异常值。 另一个优点是,它可以更容易地比较多个组之间的分布情况。 要在 Excel 中制作多组的盒形图,最简单的方法是直接将感兴趣的分类变量放在连续变量的左侧。 通过这种方式,在数据源中将classk移至treadssk左侧。 选择此数据后,单击“插入”→“盒形图”选项卡。 在图 1-20 中,我们可以看到三组分数的总体分布看起来相似。

分组盒形图

图 1-20. 根据班级类型的阅读成绩盒形图

总结一下,在处理定量数据时,我们可以做的远不止计数频率:

  • 我们可以使用集中趋势的测量确定数据围绕哪个(些)值中心化。

  • 我们可以使用变异性的测量来确定数据的相对分布情况。

  • 我们可以使用直方图和盒形图来可视化数据的分布。

还有其他描述性统计和其他可视化方法来探索定量变量。 您将在本书的后面了解其中一些。 但在探索探索性数据分析期间,这是对数据最关键的问题的良好开端。

结论

虽然我们永远不知道在新数据集中会得到什么,但是探索性数据分析(EDA)框架为我们提供了一个很好的过程来理解它。现在我们知道在star中我们正在处理哪些类型的变量,以及它们的整体观察如何看待和行为:相当深入的访谈。在第三章,我们将通过学习如何通过探索数据confirm所获得的洞察来进一步建立这项工作。但在此之前,我们将在第二章中进行概率之旅,这为分析引擎提供了很多燃料。

练习

使用书籍的仓库中的datasetshousinghousing.xlsx中的housing数据集练习您的 EDA 技能。这是一个真实的数据集,包含加拿大安大略省温莎市的住房销售价格。您可以在文件的readme工作表中找到变量的描述。完成以下内容,同时也不要犹豫自己完成您的 EDA:

  1. 对每个变量的类型进行分类。

  2. 建立aircoprefarea的双向频率表。

  3. 返回price的描述统计数据。

  4. 可视化lotsize的分布。

您可以在书籍仓库的exercise-solutions文件夹中找到这些和所有其他习题的解决方案。每章都有一个命名的文件。

第二章:概率的基础

你有没有停下来考虑过你的气象学家所说的 30%降雨几率是什么意思?除非有水晶球,他们不能确定会下雨。也就是说,他们对一个结果感到不确定。但他们能够将这种不确定性量化为介于 0%(肯定不会下雨)和 100%(肯定会下雨)之间的值。

数据分析师,就像气象学家一样,没有水晶球。通常情况下,我们希望对整个人群做出断言,但只有样本数据。因此,我们也需要将不确定性量化为概率。

我们将从深入探讨概率的工作原理及其推导方式开始本章。我们还将使用 Excel 模拟一些统计学中最重要的定理,这些定理大多基于概率。这将为第三章和第四章的推断统计学在 Excel 中的实施奠定良好的基础。

概率与随机性

俗称,当某事似乎脱离上下文或杂乱无章时,我们说它是“随机”的。在概率论中,当我们知道事件将一个结果,但不确定结果会是什么时,称之为随机

以一个六面骰子为例。当我们掷骰子时,我们知道它会落在一面上,而不会消失或落在多面上。知道我们会得到一个结果,但不知道是哪一个结果,这就是统计学中所说的随机性。

概率和样本空间

我们知道,当骰子落地时,会显示一个介于一到六之间的数字。这些所有可能的结果构成了一个样本空间。每个结果都被赋予大于零的概率,因为骰子可能会落在任一面上。这些概率加在一起得到 1,因为我们确信结果将在样本空间中的这些可能性之一。

概率与实验

我们已经确定掷骰子是随机的,并且我们已经概述了它的样本空间。现在我们可以开始为这一随机事件构建实验。在概率论中,实验是可以无限重复的过程,其可能的结果构成了一个一致的样本空间。

有些实验需要多年的计划,但我们的实验幸运地很简单:掷骰子。每次我们这样做,我们会得到一个介于一到六之间的值。结果就是我们的输出。每次掷骰子称为实验的试验

无条件和条件概率

根据我们目前对概率的了解,关于掷骰子的典型概率问题可能是:“掷出 4 的概率是多少?”这称为边际无条件概率,因为我们只考虑一个事件的独立情况。

但如果有这样一个问题:“在上次试验中我们掷出 1 的情况下,掷出 2 的概率是多少?”要回答这个问题,我们需要讨论联合概率。有时候,在研究两个事件的概率时,我们知道其中一个事件的结果,但不知道另一个事件的结果。这被称为条件概率,计算方法之一就是 Bayes 定理。

我们不会在本书中涵盖 Bayes 定理及其适用的许多概率与统计领域,但它非常值得你未来的学习。查看 Will Kurt 的Bayesian Statistics the Fun Way(No Starch Press)可以获得出色的介绍。你会发现贝叶斯主义在数据处理中提供了独特的方法,有一些令人印象深刻的应用于分析中。

注意

Bayes 定理形成的思想流派与本书中使用的所谓频率主义方法及大部分古典统计学有所不同。

概率分布

到目前为止,我们已经了解了什么使我们的掷骰子成为一个随机实验,并枚举了试验可能取值的样本空间。我们知道每个结果的概率之和必须等于 1,但每个结果的相对概率是多少呢?为此,我们可以参考概率分布。概率分布列出了事件可能取得的结果,以及每个结果的普遍程度。虽然概率分布可以写成正式的数学函数,但我们将集中于其数量输出。

在第一章中,你了解了离散和连续变量之间的区别。还有相关的离散和连续概率分布。让我们更深入地学习,从前者开始。

离散概率分布

我们将继续使用掷骰子的示例。这被认为是离散概率分布,因为结果是可数的:例如,掷骰子可能会得到 2 或 3,但永远不可能得到 2.25。

特别是,掷骰子属于离散均匀概率分布,因为每次试验的每个结果都同等可能发生:也就是说,掷出 4 和掷出 2 的可能性一样大,依此类推。具体而言,每个结果的概率为六分之一。

要继续学习本章中的 Excel 演示及其他演示,请转到本书的存储库中的ch-2.xlsx文件。在大多数这些练习中,我已经完成了工作表的某些分段,并将在这里与您一起完成其余部分。首先查看uniform-distribution工作表。范围A2:A7中列出了每种可能的结果X。我们知道每种结果都有相同的可能性,所以我们在B2:B7中的公式应为=1/6P(X=x)表示给定事件导致列出的结果的概率。

现在选择范围A1:B7,然后从功能区选择插入 > 聚类柱状图。你的概率分布和可视化应该像图 2-1 那样。

欢迎来到你的第一个,尽管有些乏味的概率分布。注意我们可视化中数值之间的间隔?这是一个明智的选择,表明这些是离散而不是连续的结果。

有时我们可能想知道一个结果的累积概率。在这种情况下,我们将所有概率的累积总和直到达到 100%(因为样本空间必须总和为 1)。我们将在C列中找到一个事件的概率小于或等于给定的结果。我们可以用公式=SUM($B$2:B2)在范围C2:C7中设置一个累积总和。

六面骰子投掷的概率分布

图 2-1. 六面骰子投掷的概率分布

现在,选择范围A1:A7,在 Windows 下按住 Ctrl 键或 Mac 下按住 Cmd 键,然后高亮显示C1:C7。选择这个非连续范围后,创建第二个聚类柱状图。你能在图 2-2 中看到概率分布和累积概率分布之间的差异吗?

概率与累积分布

图 2-2. 六面骰子投掷的概率与累积概率分布

基于逻辑和数学推理,我们一直假设每面骰子的概率为六分之一。这被称为我们的理论概率。我们也可以通过多次掷骰子并记录结果来实验性地找出概率分布。这被称为实验概率。毕竟,我们可能通过实验发现,每面骰子的概率实际上并不像理论推断的那样是六分之一,而是对某一面有偏好。

我们有几种选项来推导实验概率:首先,我们确实可以进行真正的实验。当然,多次掷骰子并记录结果可能会变得相当乏味。我们的另一个选择是让计算机来承担繁重的工作,并模拟实验。模拟通常能够相对准确地反映现实,经常用于当进行真实实验过于困难或耗时时。模拟的缺点是它可能无法反映出现实实验中的任何异常或特殊情况。

注意

模拟经常用于分析中,以获取在实际实验中找出的结果,即使实际实验过于困难甚至不可能。

要模拟掷骰子的实验,我们需要一种以随机方式在一到六之间选择数字的方法。我们可以使用 Excel 的随机数生成器RANDBETWEEN()来做到这一点。你在书中看到的结果将与你自己尝试时得到的结果不同……但它们将都是一到六之间的随机数。

警告

使用 Excel 的随机数生成器生成的个人结果将与书中记录的结果不同。

现在,转到实验概率工作表。在列A中,我们已经标记了 100 次骰子投掷的试验,我们希望记录结果。此时,你可以开始用真实骰子滚动并在列B中记录结果。更高效但不太真实的选择是使用RANDBETWEEN()模拟结果。

此函数接受两个参数:

RANDBETWEEN(bottom, top)

我们使用的是六面体骰子,这使得我们的范围介于一到六之间:

RANDBETWEEN(1, 6)

RANDBETWEEN()仅返回整数,这在本例中正是我们所需要的:再次强调,这是一个离散分布。使用填充手柄,你可以为所有 100 次试验生成一个结果。不要过于依赖当前的结果:在 Windows 中按下 F9,在 Mac 中按下 fn-F9,或从功能区选择公式 → 立即计算。这将重新计算你的工作簿,并重新生成随机数。

让我们比较在列D-F中骰子投掷的理论与实验概率。列D将用于列举我们的样本空间:数字一到六。在列E中,采用理论分布:1/6,或16.67%。在列F中,从列AB中计算实验分布。这是我们在所有试验中发现每个结果的百分比。你可以使用以下公式找到这一点:

=COUNTIF($B$2:$B$101, D2)/COUNT($A$2:$A$101)

选择你的范围D1:F7,然后从功能区中选择插入 → 聚类柱状图。你的工作表现在应该看起来像图 2-3。试着重新计算几次。

六面体骰子的理论与实验概率比较

图 2-3. 六面体骰子投掷的理论与实验概率比较

根据我们的实验分布,我们预测每个数字滚动的可能性相等是正确的。当然,我们的实验分布不是完全与理论相同:由于随机机会,总会有一些误差。

然而,实际进行实验时可能会出现与我们从模拟中得出的结果不同的情况。也许真实情况下的骰子并不公平,而我们依赖自己的推理和 Excel 的算法可能忽视了这一点。这似乎是个琐碎的细节,但实际生活中的概率通常不像我们(或我们的计算机)所预期的那样行为。

离散均匀分布是许多离散概率分布之一;在分析中常用的其他分布包括二项分布和泊松分布。

连续概率分布

当结果可以在两个值之间取任意可能值时,分布被认为是连续的。我们在这里将重点放在正态分布,或者钟形曲线上,如直方图所示。你可能熟悉这个著名的形状,见于图 2-4。

在这个图表中,你会看到一个完全对称的分布,以变量的平均值(μ)为中心。让我们深入了解正态分布是什么,以及它告诉我们的内容,使用 Excel 来说明基于它的基本统计概念。

正态分布

图 2-4. 用直方图描述的正态分布

正态分布很值得回顾,因为它在自然界中非常常见。例如,图 2-5 展示了学生身高和酒的 pH 值的分布直方图。这些数据集可以在本书的仓库中找到,你可以在 heightswine 文件夹下探索这些数据集。

正态变量

图 2-5. 现实生活中两个服从正态分布的变量:学生身高和酒的 pH 值

你可能想知道我们如何知道一个变量是否服从正态分布。好问题。回想一下我们的骰子投掷例子:我们列举了所有可能的结果,推导出一个理论分布,然后通过模拟推导出一个实验分布,以比较两者。将图 2-5 中的直方图视为它们自己的实验分布:在这种情况下,数据是手动收集的,而不是依赖于模拟。

有几种方法可以确定现实生活中的数据集及其实验分布是否接近理论正态分布。现在,我们将寻找那个显著的钟形曲线直方图:一个对称的形状,大多数值集中在中心附近。其他方法包括评估偏度和峰度,它们是衡量分布对称性和尖峭度的两个附加摘要统计数据。还可以使用统计推断方法来检验正态性。你将在第三章中学习统计推断的基础知识。但现在,我们将遵循这个规则:“看到了就知道是。”

警告

当你处理真实数据时,你在处理实验分布。它们永远不会完全匹配理论分布。

正态分布为我们提供了一些易于记忆的指导原则,用于描述我们期望在距离平均值一定标准差范围内找到观察值的百分比。具体来说,对于一个正态分布的变量,我们期望:

  • 68% 的观察值落在平均值一个标准差范围内。

  • 95% 的观察值落在平均值两个标准差范围内。

  • 99.7% 的观察值落在平均值三个标准差范围内。

这被称为经验法则,或者68–95–99.7 法则。让我们通过 Excel 看它的实际应用。接下来,转到 经验法则 工作表,如 图 2-6 所示。

在 Excel 中展示的经验法则

图 2-6. 经验法则工作表的开头

A10:A109 单元格中,我们有值 1–100。在 B10:B109 单元格中,我们的目标是找出以均值为 50 和标准差为 10(分别在 B1B2 单元格中)的正态分布变量将取得这些值的观测百分比。然后我们将找出均值在一个、两个和三个标准差范围内观测的百分比在 C10:E109 中。完成后,右侧的图表将得到填充。C4:E4 单元格还将找到每列的总百分比。

正态分布是连续的,这意味着观测理论上可以取得两个值之间的任何值。这使得为很多结果分配概率成为可能。为简单起见,通常将这些观察结果分组到离散的范围内。概率质量函数(PMF)将返回在观察范围内每个离散区间中找到的概率。我们将使用 Excel 的 NORM.DIST() 函数来计算我们变量在范围 1–100 内的 PMF。由于这个函数比之前使用的其他函数更复杂,因此我在 表 2-1 中描述了每个参数。

表 2-1. NORM.DIST() 所需的参数

Argument 描述
X 您想要找到概率的结果
Mean 分布的均值
Standard_dev 分布的标准差
Cumulative 如果为 TRUE,则返回累积函数;如果为 FALSE,则返回质量函数

我们的工作表的 A 列包含我们的结果,B1B2 包含我们的均值和标准差,我们希望得到质量而不是累积分布。累积会返回概率的累积和,而这里我们不需要。这使得我们的公式为 B10

=NORM.DIST(A10, $B$1, $B$2, 0)

使用填充手柄,您将得到每个值从 0 到 100 的观测概率百分比。例如,您将在 B43 单元格中看到,观测等于 34 的概率大约为 1.1%。

我们可以看到在 B4 单元格中,结果有 99.99%的概率落在 1 到 100 之间。重要的是,这个数字并不等于 100%,因为连续分布中的观测可以取得任何可能的值,而不仅仅是从 1 到 100。在 C7:E8 单元格中,我编写了公式来找出我们均值在一个、两个和三个标准差范围内的值。

我们可以使用这些阈值以及条件逻辑来找出在列 B 中我们概率质量函数的哪些部分可以在这些相应区域内找到。在 C10 单元格中输入以下公式:

=IF(AND($A10 > C$7, $A10 < C$8), $B10, "")

如果列 A 的值在标准差范围内,此函数将从列 B 中传递概率。如果落在范围之外,则单元格为空白。使用填充手柄,你可以将此公式应用于整个范围 C10:E109。现在你的工作表应该像 图 2-7 所示。

在 Excel 中演示经验法则

图 2-7. 在 Excel 中演示的经验法则

单元格 C4:E4 表明,大约有 65.8%、94.9% 和 99.7% 的值分别位于均值的一个、两个和三个标准差内。这些数字非常接近于 68-95-99.7 法则。

现在,看一下产生的可视化效果:我们可以看到大多数观察值都在一个标准差内,更多的观察值在两个标准差内。到了三个标准差,很难看到 图 2-8 中没有覆盖的部分,但那部分仍然存在。(请记住,这只是所有观察值的 0.3%。)

经验法则图表

图 2-8. 在 Excel 中可视化的经验法则

当你把我们的例子的标准差改为八时会发生什么?改为十二时呢?钟形曲线的形状始终对称地围绕均值 50 中心展开和收缩:较低的标准差导致“更紧密”的曲线,反之亦然。无论如何,经验法则大致适用于数据。如果你将均值移动到 49 或 51,你会看到曲线的“中心”沿着 x 轴移动。一个变量可以具有任何均值和标准差,仍然可以服从正态分布;其结果的概率质量函数将是不同的。

图 2-9 展示了两个具有不同均值和标准差的正态分布。尽管它们的形状非常不同,它们仍然遵循经验法则。

不同的正态分布

图 2-9. 不同的正态分布
注意

正态分布可以具有任意可能的均值和标准差的组合。结果的概率密度函数会有所变化,但大致上会遵循经验法则。

正态分布之所以重要,还因为它在中心极限定理中的地位。出于你将在本章和以下章节看到的原因,我称这个定理为统计学的“缺失链接”。

作为中心极限定理的一个示例,我们将使用另一种常见的赌博游戏:轮盘赌。欧洲轮盘赌盘上任何数字落下的概率都是相等的(相比之下,美国轮盘赌盘上的槽口标有 0 和 00)。基于你对掷骰子的了解,这是一种什么样的概率分布?这是一个离散均匀分布。看起来我们在一个关于正态分布的演示中分析这种分布是否有些奇怪?好吧,这里要感谢中心极限定理。要亲自看到这个定理的作用,请前往 roulette-dist 工作表,并在 B2:B101 中使用 RANDBETWEEN() 模拟轮盘赌 100 次:

RANDBETWEEN(0, 36)

使用直方图来可视化结果。你的工作表应该像图 2-10 那样。尝试重新计算几次。你会发现每次都得到一个看起来非常平坦的直方图。这确实是一个离散均匀分布,即在 0 到 36 之间每个数字落下的概率是相等的。

轮盘赌结果直方图

图 2-10. 模拟轮盘赌结果的分布

现在前往 roulette-sample-mean-dist 工作表。在这里我们将做一些有些不同的事情:我们将模拟 100 次轮盘赌,然后取这些结果的平均值。我们将这样做 100 次,并将这些试验均值的分布作为直方图显示。这种“均值的平均值”被称为样本均值。一旦你使用 RANDBETWEEN()AVERAGE() 函数完成这些操作,你应该能看到类似于 图 2-11 的结果。

轮盘赌样本均值直方图

图 2-11. 模拟轮盘赌结果的样本均值分布

现在这个分布不再像一个矩形:实际上,它看起来像一个钟形曲线。它是对称的,大多数观察值都聚集在中心周围:现在我们有了一个正态分布。当轮盘赌结果本身不是正态分布时,我们的样本均值分布如何能够呈现正态分布?欢迎来到被称为中心极限定理的这种非常特殊的魔力(CLT)。

具体来说,中心极限定理告诉我们:

如果样本量足够大,样本均值的分布将近似于正态分布。

这一现象是一个游戏规则改变者,因为它使我们能够利用正态分布的独特特性(比如经验法则),来推断一个变量的样本均值,即使这个变量本身并不服从正态分布。

你注意到了细则吗?中心极限定理仅在“样本量足够大”的情况下适用。这是一个重要的声明,但也是一个模糊的声明:多大才算足够大呢?让我们通过另一个 Excel 演示收集一些想法。前往 law-of-large-numbers 工作表参与。在列 B 中,我们可以使用 RANDBETWEEN(0, 36) 模拟 300 次轮盘赌结果的实验。

在列C中,我们想要获取结果的运行平均值。我们可以使用混合引用;在列C中输入以下内容,并将其拖动到你的 300 次试验旁边:

=AVERAGE($B$2:B2)

这将导致找到列B的运行平均值。选择你在列C中得到的数据,然后转到功能区,点击“插入”→“线条”。查看你的折线图,并重新计算工作簿几次。每次模拟的结果都会与图 2-12 所示有所不同,但模式上平均值倾向于在更多的旋转中收敛到 18,这是合理的:它是 0 到 36 之间的平均值。这个预期的数字称为期望值

大数定律

图 2-12。在 Excel 中可视化的大数定律

这种现象被称为大数定律(LLN)。正式地说,大数定律告诉我们:

从试验中得到的结果的平均值随着试验次数的增加越来越接近期望值。

尽管如此,这个定义引发了我们首先提出的问题:为了中心极限定理适用,样本量需要有多大?你经常听到 30 作为一个阈值。更保守的指导方针可能要求样本量为 60 或 100。考虑到这些样本量的指导方针,请回顾图 2-12。看看它确实在这些阈值周围更接近期望值?

大数定律为符合中心极限定理的足够样本量提供了一个宽泛的经验法则。

样本量为 30、60 和 100 仅为经验法则;有更严格的方法来确定需要应用中心极限定理的样本量。目前,请记住:只要我们的样本量达到这些阈值,我们的样本均值应该接近期望值(多亏了大数定律),并且还应该服从正态分布(多亏了中心极限定理)。

存在几种连续概率分布,如指数分布和三角形分布。我们专注于正态分布,既因为它在现实世界中的普遍性,也因为它特殊的统计特性。

结论

正如本章开头提到的那样,数据分析师生活在一个充满不确定性的世界中。具体来说,我们经常希望对整个人群做出声明,而只拥有样本数据。利用本章介绍的概率框架,我们将能够在量化其固有不确定性的同时做到这一点。在第三章中,我们将深入探讨假设检验的要素,这是数据分析的核心方法。

练习

利用 Excel 和你对概率的了解,考虑以下情况:

  1. 投掷一个六面骰子的期望值是多少?

  2. 考虑一个均值为 100,标准差为 10 的正态分布变量。

    • 变量观察值取 87 的概率是多少?

    • 你预计观察结果中有多少百分比会落在 80 和 120 之间?

  3. 如果欧洲轮盘赌的期望值是 18,这是否意味着你押注 18 比其他号码更划算呢?

第三章:推断统计基础

第一章提供了一个通过分类、总结和可视化变量探索数据集的框架。尽管这是分析的重要起点,但我们通常不希望止步于此:我们想知道我们在样本数据中看到的是否可以推广到更大的总体。

问题在于,我们实际上不知道在总体中会找到什么,因为我们没有所有数据。然而,利用第第二章介绍的概率原理,我们可以量化我们在样本中看到的东西也会在总体中找到的不确定性。

根据样本估计人口值被称为推断统计,并由假设检验执行。这一框架是本章的基础。您可能在学校学过推断统计学,这可能很容易让您对这门学科感到反感,似乎难以理解且无法应用。这就是为什么我会尽可能地将本章应用于现实,使用 Excel 探索真实数据集。

到本章结束时,您将掌握这种支持大部分分析的基本框架。我们将继续在第四章中扩展其应用。

第一章以房屋数据集上的练习作为结束,这将成为本章的重点。您可以在书籍库的数据集文件夹中的房屋子文件夹中找到数据集。制作一份副本,添加索引列,并将此数据集转换为名为房屋的表格。

统计推断的框架

基于样本推断人口特征的能力似乎像是魔术,不是吗?就像任何魔术技巧一样,推断统计学对外人来说可能看起来很容易。但对于内部人员来说,这是一系列精细调节步骤的结合:

  1. 收集代表性样本。这在假设检验之前,但对其成功至关重要。我们必须确保收集的样本公平地反映了总体。

  2. 提出假设。首先,我们将提出一个研究假设,或者一个解释我们人口中某些事物的陈述,并认为这解释某些人口特征。然后,我们将陈述一个统计假设,以测试数据是否支持这一解释。

  3. 制定分析计划。然后我们将概述我们将用来进行此测试的方法,以及我们将用来评估它的标准。

  4. 分析数据。这是我们实际上进行数字分析和开发证据的地方,我们将用它来评估我们的测试。

  5. 作出决策。这是真相的时刻:我们将比较第 2 步的评估标准与第 3 步的实际结果,并得出结论,证据是否支持我们的统计假设。

对于每一个步骤,我将提供一个简要的概念概述。然后我们将立即将这些概念应用于房屋数据集。

收集一个具有代表性的样本

在 第二章 中,你学到了,由于大数定律的存在,样本均值的平均值应该随着样本大小的增加而越来越接近期望值。这形成了一个经验法则,用于确定进行推断统计所需的充分样本大小。不过,我们假设,我们处理的是一个代表性样本,或者说是一个公正反映人口的一组观察。如果样本不具有代表性,我们就不能假设其样本均值会随着更多观察逼近人口均值。

确保一个具有代表性的样本最好是在研究的概念化和收集阶段进行处理;一旦数据收集完成,要回到与抽样相关的任何问题就很难了。收集数据有许多方法,但是虽然它是分析工作流程的重要组成部分,但它超出了本书的范围。

注意

确保具有代表性样本的最佳时间是在数据收集过程中。如果你正在使用预先组装的数据集,请考虑为达到这个目标而采取了哪些步骤。

获得人口代表性样本会引发一个问题:什么是目标人口?这个人口可以是我们想要的任何一般或特定的。例如,假设我们有兴趣探索狗的身高和体重。我们的人口可以是所有狗,或者可以是特定品种。也可以是某个年龄组或性别的狗。一些目标人口可能在理论上更重要,或者在逻辑上更容易抽样。你的目标人口可以是任何东西,但你对该人口的样本必须是具有代表性的。

在 546 次观察中,房屋可能是进行有效推断统计的足够大的样本。但是,它是否具有代表性呢?如果没有对收集方法或目标人口的一些理解,很难确定。这些数据来自同行评审的应用计量经济学杂志,因此是可信的。你在工作中接收的数据可能不会像这样经过精心处理,因此思考或询问收集和抽样过程是值得的。

至于数据的目标人口,书库中数据集房屋下的 readme 文件指示其来自加拿大安大略省温莎市的房屋销售。这意味着温莎市的房价可能是最佳的目标人口;例如,其结果可能或可能不会转移到加拿大甚至安大略省的其他房价。这也是一个较旧的数据集,取自上世纪 90 年代的一篇论文,因此不能保证其结果适用于今天的房地产市场,即使是在温莎市。

提出假设

在一定程度上,我们的样本数据代表了总体,我们可以开始思考我们希望通过陈述假设推断出的具体内容。也许你听说过数据中的某种趋势或不寻常的现象。也许在进行探索性数据分析时,数据中的某些内容引起了你的注意。这是你推测分析结果时的时机。回到我们的房地产示例,我想很少有人会否认,在家中拥有空调是令人向往的。因此,可以推断,拥有空调的房屋销售价格高于没有空调的房屋。关于数据中这种关系的非正式陈述被称为研究假设。另一种陈述这种关系的方式是,空调对销售价格有影响。温莎的房屋是我们的总体,而拥有和没有空调的这些房屋则是其两个组或亚总体

现在,你对空调如何影响销售价格有了自己的假设,这非常棒。作为分析师,对自己的工作有强烈的直觉和看法是至关重要的。但正如美国工程师 W. Edwards Deming 所说,“信仰上帝,其他人必须带来数据。”我们真正想知道的是,你所推测的关系是否确实存在于总体中。为此,我们需要使用推断统计学。

正如你已经看到的那样,统计语言通常不同于日常语言。起初可能会感觉有些学究,但其中的细微差别揭示了关于数据分析如何工作的很多内容。统计假设就是其中之一。为了测试数据是否支持我们提出的关系,我们将提出两个如下所示的统计假设。现在先看一眼它们;稍后会解释它们:

H0

有空调和没有空调的房屋的平均销售价格没有差异。

Ha

有空调和没有空调的房屋的平均销售价格存在差异。

从设计上来说,这些假设是互斥的,所以如果一个成立,另一个必定为假。它们也是可测试和可证伪的,这意味着我们可以使用现实世界的证据来衡量并推翻它们。这些都是关于科学哲学的大思想主题,我们无法在这里完全涵盖;主要的要点是,你希望确保你的假设确实可以通过数据来测试。

此时,我们需要摒弃所有关于数据的预设观念,比如在研究假设中推测的内容。我们现在假设没有效应。毕竟,为什么要有?我们只有总体数据的样本,因此我们永远无法真正了解总体的真实值或参数。这就是为什么第一个假设,或称为零假设的 H0,被如此奇特地陈述。

另一方面是备择假设,或者说是 Ha。如果数据中没有证据支持零假设,那么根据它们的陈述方式,证据必须是支持备择假设的。也就是说,我们永远不能说我们已经 证明 其中任何一个为真,因为我们实际上并不知道总体参数。可能我们在样本中找到的效应只是偶然事件,而在总体中我们并没有真正找到它。事实上,测量这种情况发生的概率将是我们在假设检验中所做的主要内容之一。

注意

假设检验的结果并不会 "证明" 任何一个假设是正确的,因为首先我们并不知道总体的 "真实" 参数。

制定分析计划

现在我们已经准备好了我们的统计假设,是时候指定用于测试数据的方法了。对于给定假设的适当统计检验取决于多种因素,包括分析中使用的变量类型:连续的、分类的等等。这也是在探索性数据分析期间对变量进行分类的一个好理由。具体来说,我们决定使用的测试取决于我们的自变量和因变量的类型。

因果关系的研究驱动我们在分析中所做的大部分工作;我们使用 自变量因变量 来建模和分析这些关系。(请记住,由于我们处理的是样本,因果关系不可能确凿无误。)我们在 第二章 中讨论了实验的概念,即可重复事件产生一组定义好的随机结果。我们以掷骰子为例进行了实验;而现实生活中的大多数实验要复杂得多。让我们来看一个例子。

假设我们是一些对促进植物生长有兴趣的研究人员。一位同事推测浇水可能会产生积极影响。我们决定通过实验来验证这一点。我们在观察中提供不同量的水,并确保记录数据。然后我们等几天并测量所得的植物生长情况。在这个实验中,我们有两个变量:浇水量和植物生长。你能猜出哪个是我们的自变量,哪个是我们的因变量吗?

浇水是 自变量,因为这是我们作为研究人员在实验中控制的部分。植物生长是 因变量,因为我们假设在自变量改变的情况下会发生变化。自变量通常首先记录:例如,首先浇水给植物,然后它们生长。

注意

自变量通常是在因变量之前记录的,因为因果必须先于效果。

鉴于这个例子,模拟空调和销售价格之间的关系的更合理的方式是什么? 合理的推断是先安装空调,然后出售房屋。 这使得aircoprice成为我们的自变量和因变量,分别。

因为我们正在测试一个二元独立变量对一个连续依赖变量的影响,所以我们将使用一种称为独立样本 t 检验的方法。 不必担心记住在任何情况下使用的最佳检验。 相反,这里的目标是掌握根据样本对总体进行推断的常见框架。

大多数统计测试对其数据做出一些假设。 如果这些假设不成立,则测试结果可能不准确。 例如,独立样本 t 检验假设没有观察值相互影响,并且每个观察值只出现在一个且仅一个组中(即它们是独立的)。 为了充分估计总体均值,该测试通常假设样本呈正态分布; 也就是说,鉴于中心极限定理的神奇,对于较大的数据集,可以绕过该约束。 Excel 将帮助我们绕过另一个假设:每个总体的方差相等。

我们知道我们将使用什么测试,但我们仍然需要制定一些实施规则。 首先,我们需要决定测试的统计显著性。 让我们回到先前提到的场景,其中在样本中推断的效果只是一个偶然事件,不会在总体中被发现。 这种情况最终会发生,因为我们实际上永远不会知道总体均值。 换句话说,我们对此结果不确定... 正如您在第二章中学到的,可以将不确定性量化为 0 和 1 之间的数字。 这个数字称为阿尔法,表示测试的统计显著性。

阿尔法显示了我们对于在总体中实际上没有效果,但由于偶然因素我们在样本中发现了一个效果的可能性有多舒适。 在本书中,我们将使用的常见阈值为 5%。 换句话说,当数据中实际上没有关系时,我们对声称数据中存在关系感到舒适的时间不到 5%。

注意

本书遵循在 5%的统计显著水平上进行双尾假设检验的标准约定。

其他常见的显著水平包括 10%或 1%。 没有一个“正确”的阿尔法水平;设置它取决于多种因素,如研究目标、易于解释等等。

您可能想知道为什么我们对声称存在效果时根本不感到舒服。换句话说,为什么不将α设为 0 呢?在这种情况下,我们无法对我们的样本给出关于总体的任何结论。事实上,如果α为 0,我们将会说,因为我们绝不希望关于总体的真实值错了,它可能是任何值。为了做出任何推断,犯错是我们必须承担的风险。

我们还需要说明我们感兴趣的方向。例如,我们假设空调对销售价格有正面影响:即带有空调的房屋平均销售价格高于没有空调的房屋。然而,可能出现负面影响:你可能正在处理一个更倾向于没有空调的人群。或者,也可能是一个很少需要使用空调的气候,拥有空调是不必要的开支。这些情况在理论上是可能的;如果有任何疑问,那么统计检验应该同时考虑正面和负面影响。这被称为双尾(或 Excel 中称为双尾)检验,我们将在本书中使用它。单尾检验是可能的,但相对罕见,超出了我们的范围。

当然,在我们甚至还没有触及数据之前,这可能看起来有些冗长。但这些步骤存在的目的是确保我们作为分析师在最终进行计算时公正地看待数据。我们的假设检验结果取决于统计显著性水平和检验的尾部数。正如您稍后将看到的那样,测试中略微不同的输入,如不同的统计显著性水平,可能导致不同的结果。这确实会诱使我们先算出结果,然后决定进行哪种具有有利结果的特定测试。然而,我们希望避免因为调整结果以符合我们的议程而产生的冲动。

分析数据

现在,您可能一直在等待的时刻到了:是时候开始处理数据了。这部分工作通常受到最多的关注,也是我们将重点关注的内容,但值得注意的是,这只是假设检验的许多步骤之一。请记住,数据分析是一个迭代过程。在进行假设检验之前,您几乎不可能(也不明智)不对这些数据进行任何分析。事实上,探索性数据分析被设计为假设检验或确认性数据分析的先导。在开始分析之前,您应该始终对数据集的描述性统计感到满意。在这种精神下,让我们通过我们的房屋数据集进行这些操作,然后进行分析。

图 3-1 计算了 airco 两个级别的 price 的描述统计信息,并可视化了它们的分布。如果您需要了解如何执行此操作,请查看 第一章。我重新标记了 ToolPak 输出,以帮助指示每个组中正在测量的内容。

_ 房屋 _ 描述性统计

图 3-1. 房屋数据集的 EDA

此输出中的直方图向我们显示,两组数据都近似服从正态分布,而描述性统计告诉我们,我们有相对较大的样本大小。虽然没有空调的房屋更多(373 个没有,173 个有),但这不一定是 t 检验的问题。

注意

独立样本 t 检验对两组之间的样本大小差异不敏感,只要每组足够大。其他统计测试可能会受到这种差异的影响。

图 3-1 还向我们提供了我们的组的样本均值:大约为 86,000 美元的有空调房屋,以及 60,000 美元的没有空调房屋。知道这一点很好,但我们真的希望知道在整个人口中是否可以期望这样的影响。这就是 t 检验的作用,我们将再次依靠数据透视表和数据分析 ToolPak 进行进行。

在新工作表中插入一个数据透视表,将id放在行区域,airco放在列区域,将“价格总和”放在值区域。清除报表中的所有总计。这种数据排列将很容易输入到 t-Test 菜单中,可以通过数据选项卡的数据分析 → t-Test: Two-Sample Assuming Unequal Variances 访问。这里所指的“方差”是我们的子群体方差。我们真的不知道这些是否相等,所以最好选择这个选项,假设方差相等以获得更保守的结果。

会出现一个对话框;按照 图 3-2 的方式填写它。确保标签旁边的框已选中。在此选择上方有一个名为假设的均值差异的选项。默认情况下,这是空白的,这意味着我们正在测试零的差异。这恰好是我们的零假设,所以我们不需要改变任何内容。在该行下方即可看到一个名为 Alpha 的选项。这是我们声明的统计显着水平;Excel 默认为 5%,这正是我们想要的。

T-检验设置

图 3-2. ToolPak 中的 t-检验设置菜单

结果显示在 图 3-3 中。我再次将每个组标记为 ac-noac-yes,以澄清代表的组。接下来我们将逐步讨论输出的部分。

T-检验设置

图 3-3. t-检验输出

首先,我们在 F5:G7 中给出了有关我们的两个样本的一些信息:它们的均值、方差和样本大小。我们的假设的均值差异为零也包括在内。

我们将跳过一些统计内容,接下来专注于单元格 F13P(T <= t) 双尾。这可能对你来说并不意味着什么,但双尾应该听起来很熟悉,因为这是我们早些时候决定专注于而不是单尾检验的类型。这个数字被称为p 值,我们将用它来对假设检验做出决策。

做出决策

之前您了解到 alpha 是我们的统计显著性水平,或者说是我们在假设在总体中没有真实效应的情况下,由于我们在样本中找到的效应是由于随机机会造成的,我们感到舒服的水平。p 值量化了我们在数据中找到这种情况的概率,并将其与 alpha 进行比较以做出决策:

  • 如果 p 值小于或等于我们的显著性水平 alpha,则我们拒绝零假设。

  • 如果 p 值大于我们的显著性水平 alpha,则我们无法拒绝零假设。

让我们通过手头的数据来理解这些统计术语。作为概率,p 值始终介于 0 和 1 之间。在我们的 F13 中,我们的 p 值非常小,小到 Excel 用科学计数法来标记它。读取这个输出的方法是将其解释为 1.93 乘以 10 的负 22 次方——一个非常小的数。因此,我们在说,如果在总体中真的没有效应,我们预计会在样本中发现我们所观察到的效应的情况非常少,远低于 1% 的时间。这远低于我们设定的 5% 的显著性水平,因此我们可以拒绝零假设。当 p 值如此之小以至于需要用科学计数法来报告时,通常会简单地总结为 “p < 0.05”。

另一方面,假设我们的 p 值是 0.08 或者 0.24。在这些情况下,我们将无法拒绝零假设。为什么会有这种奇怪的说法?为什么我们不直接说我们“证明”了零假设或备择假设呢?一切都归结于推断统计的固有不确定性。我们永远不知道真实的子群体数值,因此更安全的做法是假设它们相等。我们的检验结果可以确认或否认对任一假设的证据,但它们永远不能明确地证明它。

虽然 p 值用于对假设检验做出决策,但了解它们不能告诉我们的内容同样重要。例如,一个常见的误解是 p 值是犯错误的概率。事实上,p 值假设我们的零假设为真,不管在样本中找到什么;样本中存在“错误”的概念并不改变这一假设。p 值告诉我们,在总体中没有效应的情况下,我们会在我们的样本中找到我们所观察到的效应的百分比。

注记

p 值不是犯错误的概率;相反,它是我们在样本中找到观察到的效应的概率,假设总体中没有效应。

另一个常见的误解是 p 值越小,效应越大。然而,p 值只是统计显著性的一个度量:它告诉我们在总体中发生效应的可能性有多大。p 值并不表示实质性显著性,即效应大小可能有多大。统计软件通常只报告统计显著性而不是实质显著性。我们的 Excel 输出就是一个例子:它返回了 p 值,但没有返回置信区间,即我们预期在总体中找到的范围。

我们可以使用测试的所谓临界值,在图 3-3 的 F14 单元格中显示,来计算置信区间。这个数字(1.97)可能看起来是任意的,但是实际上可以根据你在第二章学到的知识来理解它。通过这个 t 检验,我们得出了平均房价差异的样本。如果我们继续随机取样并绘制平均差异的分布,这个分布将是……没错,正态的,这是由于中心极限定理。

对于正态分布,根据经验法则,我们可以预期大约 95% 的观测值落在平均值的两个标准差内。在均值为 0,标准差为 1 的特殊情况下(称为标准正态分布),我们可以说大约 95% 的观测值会落在 -2 到 2 之间。稍微具体一点,它们会落在 -1.96 到 1.96 之间,这就是双尾临界值的来源。图 3-4 展示了我们预期在其中找到具有 95% 置信度的总体参数的区域。

置信区间示例

图 3-4. 在直方图上可视化的 95% 置信区间和临界值

方程 3-1 显示了寻找双尾独立样本 t 检验置信区间的公式。我们将在 Excel 中计算标记的元素。

方程 3-1. 寻找置信区间的公式

c. i . = X ¯ 1 - X ¯ 2 ± t a /2 × s 1 2 n 1 + s 2 2 n 2

要详细解释这个公式,X ¯ 1 - X ¯ 2 是点估计,t a /2 是关键值,s 1 2 n 1 + s 2 2 n 2 是标准误差。关键值和标准误差的乘积是误差边界。

这个方程可能相当令人生畏,所以为了使其更具体,我已经计算了我们示例中的置信区间及其各个元素,见图 3-5。与其对正式方程感到困扰,我们的重点将放在计算结果和理解它们所告诉我们的内容上。

Excel 中的置信区间计算

图 3-5. 在 Excel 中计算置信区间

首先,在单元格F16中的点估计,或者我们最有可能找到的总体效应。这是我们样本均值的差异。毕竟,如果我们的样本代表总体,样本和总体均值之间的差异应该可以忽略不计。但实际上可能不完全相同;我们将推导出一个值范围,在这个范围内,我们有 95%的信心可以找到这个真实差异。

接下来,在单元格F17中的关键值。Excel 为我们提供了这个数字,但为了分析方便,我在这里包含了它。如前所述,我们可以使用这个值来帮助我们找到落在平均值大约两个标准偏差内的 95%值。

现在我们有了单元格F18中的标准误差。你实际上在 ToolPak 的描述性统计输出中见过这个术语;例如见图 3-1。要理解标准误差的工作原理,想象一下,如果你不断地从总体中重新抽取房价样本。每次,你都会得到略有不同的样本均值。这种变异性称为标准误差。较大的标准误差意味着样本在代表总体方面的准确性较低。

对于一个样本的标准误差可以通过将其标准偏差除以其样本大小来找到。因为我们正在找到平均值差异的标准误差,所以公式稍微复杂一些,但模式相同:样本的变异性在分子中,观察数在分母中。这是合理的:当我们收集更大的样本大小时,我们预期它们在与人群的变异性方面表现得更少。

现在我们将临界值与标准误的乘积得出在单元格 F19 中的误差边界。这可能是你听过的一个术语:投票经常会包含此数字。误差边界提供了围绕我们点估计的变异性的估计。在图 3-5 的情况下,我们说,虽然我们认为人口差异是$25,996,但我们可能会偏差多达$4,784。

因为这是一个双尾检验,这种差异可以在任何方向找到。因此,我们需要减去和添加误差边界以分别推导置信区间的下限和上限。这些数字分别在 F20F21 中找到。最重要的是?以 95%的置信水平,我们相信有空调的房屋的平均价格比没有空调的房屋高出$21,211 到$30,780。

为什么要费心推导置信区间?作为实质性而非统计显著性的衡量,它通常在普通受众中更受欢迎,因为它将统计假设检验的结果翻译回研究假设的语言。例如,想象你是一家银行的研究分析师,向管理层报告此项有关房价的研究结果。这些经理如果依赖职业生涯来进行 t 检验,他们可能不知从何处开始——但是他们的职业生涯确实依赖于从该分析中做出明智的决策,因此您希望尽可能使其易于理解。哪种声明您认为将更有帮助?

  • “我们拒绝了零假设,即在 p < 0.05 的情况下,有空调和没有空调的房屋的平均售价没有差异。”

  • “以 95%的置信水平,有空调的房屋的平均售价比没有空调的房屋高约 21,200 到 30,800 美元。”

几乎任何人都能理解第二个声明,而第一个则需要相当数量的统计学知识。但置信区间不仅适用于普通人:在研究和数据领域,还有一股推动,要求与 p 值一起报告它们。毕竟,p 值衡量统计效应,衡量实质性。

但是,尽管 p 值和置信区间展示了不同角度的结果,它们在根本上总是一致。让我们通过对房屋数据集进行另一次假设检验来说明这个概念。这一次,我们想知道有没有一个显著差异,即有无完整装修的地下室(fullbase)对房屋平均地块面积(lotsize)的影响。这种关系也可以用 t 检验来检验;我会在一个新的工作表中按照之前的步骤进行,结果见图 3-6。(不要忘记首先探索这些新变量的描述统计。)

t 检验输出

图 3-6. 完整装修地下室对地块面积的影响

这次检验的结果在统计上具有显著性:基于 p 值为 0.27,我们预计在我们的样本中超过四分之一的情况下会发现我们所发现的效应,假设在总体中没有效应。至于实质性显著性,我们有 95% 的置信度,认为平均地块面积的差异在约 167 平方英尺到 599 平方英尺之间。换句话说,真实差异可能是正的负的,我们无法确定。基于这两个结果中的任何一个,我们未能拒绝零假设:看起来平均地块面积没有显著差异。这些结果将总是一致,因为它们都部分基于统计显著性的水平:α 决定了我们如何评估 p 值,并设置了用于推导置信区间的临界值。

如果你曾经建立过一个财务模型,你可能熟悉在你的工作上进行假设分析,以查看给定输入或假设时输出如何变化。出于同样的精神,让我们看看我们对地下室/地块面积 t 检验的结果可能有何不同。因为我们将操纵 ToolPak 输出的结果,所以明智地将单元格E2:G21的数据复制粘贴到一个新范围,以便保留原始数据。我会将我的数据放在当前工作表的单元格J2:L22中。我还会重新标记我的输出并突出显示单元格K6:L6K14,以便清楚地显示它们已被篡改。

让我们在这里操纵样本大小和临界值。在看置信区间的结果之前,尝试根据你所知道的这些数字如何相关来猜测会发生什么。首先,我将每组的样本大小设定为 550 次观察。这是一个危险的游戏;我们实际上没有真正收集 550 次观察,但是为了理解统计学,有时你必须把手弄脏。接下来,我们将将我们的统计显著性从 95% 更改为 90%。结果的临界值为 1.64。这也是冒险的;统计显著性应该在分析之前锁定,因为你将看到的原因。

图 3-7 显示了这种假设分析的结果。我们的置信区间在$1 到$430 之间,表明统计显著性,尽管仅仅如此——非常接近零。

t 检验输出

图 3-7. 置信区间的假设分析

有方法可以计算相应的 p 值,但因为你知道它在根本上总是与置信区间一致的,我们将跳过这个练习。我们的检验现在是显著的,这可能对资金、名声和荣耀产生重大影响。

故事的寓意是,假设检验的结果很容易被操纵。有时,只需改变统计显著性水平就足以推翻原假设。重新采样或者,如我们的例子,错误地增加观察数量也可能做到这一点。即使没有不当行为,声称找到你实际上并不了解的总体参数时总会存在一个灰色地带。

这是你的世界...数据只是生活在其中

在进行推断统计时,很容易陷入自动模式,仅仅插入和使用 p 值,而不考虑关于数据收集或实质性意义的更广泛考虑。你已经看到结果在统计显著性或样本大小变化时是多么敏感。为了展示另一种可能性,让我们从住房数据集中再举一个例子。

独自测试是否存在使用燃气供暖和无燃气供暖的房屋销售价格显著差异。相关变量是pricegashw。结果显示在图 3-8 中。

t 检验输出

图 3-8. 使用燃气对销售价格影响的 t 检验结果

单单根据 p 值,我们应该不能拒绝原假设:毕竟,它大于 0.05。但是 0.067 并不那么不同,所以在这里值得更仔细关注。首先,考虑样本大小:仅有 25 个使用燃气的房屋观察值,可能在最终拒绝原假设之前,值得收集更多数据。当然,在运行测试期间的描述统计中,你可能已经观察到这个样本大小。

同样地,置信区间表明真实差异可能在约$900 减少和$24,500 增加之间。面对这样的金额,进一步深入研究问题是值得的。如果仅仅因为 p 值而盲目拒绝原假设,你可能会错过潜在重要的关系。要注意这些潜在的“边缘情况”:如果在这个数据集中已经出现了一个,可以肯定在你的数据工作中会发现更多。

提示

统计和分析是理解世界的强大工具,但它们只是工具。在没有熟练的操作者控制下,它们充其量是无用的,最坏的情况下可能是有害的。不要满足于表面的 p 值;考虑统计工作的更广泛背景以及你要达到的目标(不要像你已经看到的那样操纵结果)。记住:这是你的世界,数据只是其中的一部分。

结论

或许你之前曾想知道,在一本关于分析的书中,我们为何要花一个章节来讨论看似晦涩的概率主题。我希望现在你已经清楚了:因为我们不知道总体的参数,所以必须将这种不确定性量化为概率。在本章中,我们使用了推断统计学和假设检验的框架来探索两组之间的平均差异。在下一章中,我们将用它来研究一个连续变量对另一个的影响,这可能是你听说过的方法:线性回归。虽然是不同的测试,但背后的统计框架仍然是相同的。

练习

现在轮到你对数据集进行概率推断了。在书的配套存储库datasets文件夹和tips子文件夹中找到tips.xlsx数据集,然后尝试以下练习:

  1. 测试白天时间(午餐或晚餐)与总账单之间的关系:

    • 你的统计假设是什么?

    • 你的结果在统计上显著吗?这为你的假设提供了什么证据?

    • 估计的效应大小是多少?

  2. 回答同样的问题,但是针对白天时间和小费之间的关系。

第四章:相关性与回归

你听说过冰淇淋消费与鲨鱼袭击有关吗?显然,大白鲨对薄荷巧克力片有致命的喜好。图 4-1 可视化了这种假设关系。

冰淇淋与鲨鱼袭击

图 4-1。冰淇淋消费与鲨鱼袭击之间的假设关系

“不尽然,”你可能会反驳。“这并不一定意味着鲨鱼袭击是由冰淇淋消费引发的。”

“你推断说,”随着室外温度的升高,消费的冰淇淋会增加。当天气变暖时,人们也会更多地在海边活动,巧合导致更多的鲨鱼袭击。

“相关性不意味着因果关系”

你可能已经多次听到“相关性不意味着因果关系”的说法。

在第三章中,你学到了因果关系在统计学中是一个棘手的表达。我们之所以只是拒绝零假设,是因为我们根本没有足够的数据来确定地声称因果关系。除了这种语义差异,相关性与因果关系有任何关系吗?标准表达有些过于简化了它们的关系;在本章中,你将会看到为什么使用你之前学到的推断统计学工具。

这将是我们主要在 Excel 中进行的最后一章。之后,你将已经足够掌握了分析框架,准备好进入 R 和 Python。

引入相关性

到目前为止,我们主要是一次分析一个变量的统计数据:我们发现了平均阅读成绩或房价方差,例如。这被称为单变量分析。

我们还进行了一些双变量分析。例如,我们使用两向频率表比较了两个分类变量的频率。我们还分析了一个连续变量,当被分为多个水平的分类变量时,为每个组找到了描述统计数据。

现在我们将使用相关性计算两个连续变量的双变量度量。更具体地说,我们将使用皮尔逊相关系数来衡量两个变量之间的线性关系的强度。如果没有线性关系,那么皮尔逊相关系数是不合适的。

那么,我们如何知道我们的数据是线性的呢?有更严格的检查方法,但通常情况下,可视化是一个很好的开始。特别是,我们将使用散点图根据它们的 x 和 y 坐标来描述所有观测。

如果看起来可以画一条穿过散点图总结整体模式的线,那么这就是线性关系,可以使用皮尔逊相关系数。如果你需要一条曲线或其他形状来总结模式,那么情况就相反。图 4-2 描述了一个线性和两个非线性关系。

线性与非线性散点图关系

图 4-2. 线性与非线性关系

特别是,图 4-2 给出了一个线性关系的例子:随着 x 轴上的值增加,y 轴上的值也会增加(呈线性速率)。

也可能存在相关,其中负斜线总结了关系,或者根本没有相关性,在这种情况下,一条水平线总结了它。这些不同类型的线性关系在图 4-3 中显示。请记住,这些都必须是线性关系才能应用相关性。

相关类型

图 4-3. 负相关、零相关和正相关

一旦我们确定数据是线性的,就可以找到相关系数。它始终在–1 和 1 之间取值,–1 表示完全负线性关系,1 表示完全正线性关系,0 表示根本没有线性关系。表 4-1 显示了评估相关系数强度的一些经验法则。这些绝不是官方标准,但是是解释的一个有用出发点。

表 4-1. 相关系数的解释

相关系数 解释
–1.0 完全的负线性关系
–0.7 强烈的负相关关系
–0.5 适度的负相关关系
–0.3 弱负相关关系
0 无线性关系
+0.3 弱正相关关系
+0.5 适度的正相关关系
+0.7 强烈的正相关关系
+1.0 完全的正线性关系

在心中牢记相关性的基本概念框架后,让我们在 Excel 中进行一些分析。我们将使用一个车辆里程数据集;你可以在书籍存储库的数据集文件夹中的mpg子文件夹中找到mpg.xlsx。这是一个新的数据集,所以花点时间了解一下它:我们正在处理什么类型的变量?使用第一章中介绍的工具对它们进行总结和可视化。为了帮助后续分析,请不要忘记添加索引列并将数据集转换为表格,我将其称为mpg

Excel 包含CORREL()函数,用于计算两个数组之间的相关系数:

CORREL(array1, array2)

让我们使用这个函数找到我们数据集中weightmpg之间的相关性:

=CORREL(mpg[weight], mpg[mpg])

这的确返回了一个介于–1 和 1 之间的值: –0.832. (你还记得如何解释这个值吗?)

相关矩阵呈现了所有变量对之间的相关性。让我们使用数据分析工具包来构建一个。从功能区中,前往数据 → 数据分析 → 相关性。

请记住,这是两个连续变量之间线性关系的度量,因此我们应该排除像origin这样的分类变量,并审慎地考虑包含像cylindersmodel.year这样的离散变量。工具包要求所有变量都在一个连续的范围内,因此我将谨慎地包括 cylinders。图 4-4 显示了工具包源菜单的样子。

插入相关矩阵

图 4-4. 在 Excel 中插入相关矩阵

这导致了如 图 4-5 所示的相关矩阵。

相关矩阵

图 4-5. Excel 中的相关矩阵

我们可以在单元格B6中看到 -0.83,这是重量mpg的交叉点。同样的数值也会在单元格F2中看到,但 Excel 将矩阵的这一半留空,因为这是冗余信息。对角线上的所有数值都是 1,因为任何变量与自身完全相关。

警告

当两个变量之间的关系是线性的时,皮尔逊相关系数才是一个合适的度量。

通过分析它们的相关性,我们对变量的关系做出了重大假设。你能想到是什么吗?我们假设它们的关系是线性的。 让我们通过散点图来验证这个假设。不幸的是,在基本的 Excel 中没有办法同时生成每对变量的散点图。练习时,考虑将它们全部绘制,但让我们先尝试一下 重量mpg 变量。选中这些数据,然后转到功能区,点击插入 → 散点图。

我将添加一个自定义的图表标题,并重新标记轴线以帮助解释。要更改图表标题,双击它。要重新标记轴线,点击图表的边缘,然后选择出现的加号以展开图表元素菜单(在 Mac 上,点击图表内部,然后选择图表设计 → 添加图表元素)。从菜单中选择轴标题。图 4-6 显示了结果散点图。在轴上包含测量单位是一个不错的主意,以帮助外部人员理解数据。

图 4-6 基本上显示了一个负线性关系,随着重量减轻和里程增加而扩展。默认情况下,Excel 将我们数据选择中的第一个变量绘制在 x 轴上,第二个变量绘制在 y 轴上。但为什么不尝试反过来呢?尝试在工作表中切换这些列的顺序,使 重量 在列 E 中,mpg 在列 F 中,然后插入一个新的散点图。

重量和里程的散点图

图 4-6. 重量和里程的散点图

图 4-7 展示了关系的镜像。Excel 是一个很棒的工具,但与任何工具一样,你必须告诉它该做什么。Excel 会计算相关性,无论关系是否线性。它还会制作散点图,而不用担心哪个变量应该放在哪个轴上。

那么,哪个散点图是“正确的”?这有关系吗?按照惯例,自变量放在 x 轴上,因变量放在 y 轴上。花点时间考虑哪个是哪个。如果不确定,记住自变量通常是首先测量的那个。

我们的自变量是 weight,因为它是由汽车的设计和建造确定的。mpg因变量,因为我们假设它受汽车重量的影响。这将weight放在 x 轴上,mpg放在 y 轴上。

在商业分析中,罕见只为统计分析而收集数据;例如,我们的 mpg 数据集中的汽车是为了创造收入而建造的,并非用于研究重量对里程影响的研究。由于不总能明确自变量和因变量,我们需要更加注意这些变量在衡量什么以及它们如何被测量。这就是为什么了解你研究的领域或至少了解变量描述及观察数据的方式如此重要。

里程和重量的散点图

图 4-7. 里程和重量的散点图

从相关性到回归

虽然按照惯例将自变量放在 x 轴上,但这不影响相关系数。然而,这里有一个重要的警告,它与早期使用直线总结散点图中找到的关系的想法相关联。这一做法开始偏离相关性,你可能听说过:线性回归

相关性不关心你称哪个变量为自变量或因变量;这不影响其定义为“两个变量在线性移动程度”的因素。

另一方面,线性回归本质上受到这种关系的影响,“自变量 X 的单位变化对因变量 Y 的估计影响”。

你将看到我们通过散点图拟合的直线可以表示为一个方程;与相关系数不同,这个方程取决于我们如何定义自变量和因变量。

像相关性一样,线性回归假设两个变量之间存在线性关系。还存在其他假设,对建模数据时很重要。例如,我们不希望有极端观察值,这可能会不成比例地影响线性关系的总体趋势。

出于演示目的,我们暂时忽略这个和其他假设。这些假设在使用 Excel 进行测试时通常很难。当您深入研究线性回归的深层次问题时,您对统计编程的了解将对您大有裨益。

深呼吸一下;是时候看另一个方程了:

方程 4-1. 线性回归方程

Y = β 0 + β 1 × X + ϵ

方程 4-1 的目标是预测我们的因变量Y。这是左侧。你可能还记得从学校里学过,一条线可以分解为其截距斜率。这就是β 0β 1 × X i在哪里发挥作用。在第二项中,我们通过斜率系数乘以我们的自变量。

最后,我们的自变量和因变量之间的关系中可能有一部分是模型本身无法解释的,而是由某些外部影响所致。这被称为模型的误差,并由ε i表示。

早些时候,我们使用独立样本 t 检验来检验两组之间平均值的显著差异。在这里,我们正在测量一个连续变量对另一个连续变量的线性影响。我们将通过检验回归线的斜率是否与零在统计上有显著差异来进行。这意味着我们的假设检验将类似于这样进行:

H0: 我们的自变量对因变量没有线性影响。(回归线的斜率等于零。)

Ha: 我们的自变量对因变量有线性影响。(回归线的斜率不等于零。)

图 4-8 展示了显著和不显著斜率的一些例子。

记住,我们没有所有的数据,因此我们不知道人群的“真实”斜率会是多少。相反,我们在推断,考虑到我们的样本,这个斜率是否在统计上与零有显著差异。我们可以使用相同的 p 值方法来估计斜率的显著性,就像我们之前用来找出两组平均数差异的方法一样。我们将继续在 95%置信区间进行双尾检验。让我们开始使用 Excel 找出结果。

回归斜率假设

图 4-8. 具有显著和不显著斜率的回归模型

在 Excel 中进行线性回归

在这个 Excel 中mpg数据集的线性回归演示中,我们测试一辆汽车的重量(weight)是否对其行驶里程(mpg)有显著影响。这意味着我们的假设将是:

H0: 重量对里程没有线性影响。

Ha: 重量对里程的线性影响是存在的。

在开始之前,建议使用感兴趣的具体变量写出回归方程,我已在方程式 4-2 中完成了这一点:

方程式 4-2. 用于估计里程的回归方程

m p g = β 0 + β 1 × w e i g h t + ϵ

让我们从可视化回归结果开始:我们已经有了来自图 4-6 的散点图,现在只需将回归线覆盖或“拟合”到上面。点击图的周边以启动“图表元素”菜单。点击“趋势线”,然后点击侧边的“更多选项”。点击“在图表上显示方程式”屏幕底部的单选按钮。

现在让我们点击图表中的结果方程式,添加粗体格式并将其字体大小增加到 14 号。我们将趋势线设为纯黑色,并通过点击图表中的它,然后转到 Format Trendline 菜单顶部的油漆桶图标。我们现在拥有了线性回归的基础。我们的散点图与趋势线看起来像图 4-9。Excel 还包括了我们根据其重量估计汽车里程的回归方程,如方程式 4-2 所示。

Excel 中的散点图回归方程

图 4-9. 带有权重对里程影响的散点图和趋势线及回归方程

我们可以在我们的方程中将截距放在斜率之前,以获得方程式 4-3。

方程式 4-3. 方程式 4-3. 用于估计里程的我们的拟合回归方程

m p g = 46.217 - 0.0076 × w e i g h t

注意,Excel 未将误差项作为回归方程的一部分。现在我们已经拟合了回归线,我们量化了期望从方程中得到的值与数据中实际值之间的差异。这种差异称为残差,我们稍后会在本章回来讨论它。首先,我们将回到我们的目标:建立统计显著性。

很棒,Excel 为我们拟合了线并给出了结果方程式。但这并不足以进行假设检验:我们仍然不知道线的斜率是否在统计上与零有显著差异。为了获取这些信息,我们将再次使用 Analysis ToolPak。从菜单栏,转到数据 → 数据分析 → 回归。您将被要求选择您的 Y 和 X 范围;这些是您的因变量和自变量,分别。确保指示您的输入包括标签,如图 4-10 所示。

ToolPak regression setup

图 4-10. 使用 ToolPak 导出回归设置的菜单

这带来了大量信息,显示在图 4-11 中。让我们逐步进行解释。

暂时忽略单元格 A3:B8 中的第一部分;我们稍后会回到它。我们的第二部分在 A10:F14 中标记为方差分析(简称 ANOVA)。这告诉我们我们的回归是否与仅有截距的回归相比显著更好。

回归结果

图 4-11. 回归输出

表 4-2 阐明了这里的竞争方程。

表 4-2. 仅截距模型与完整回归模型

仅截距模型 带系数的模型
mpg = 46.217 mpg = 46.217 − 0.0076 × weight

统计上显著的结果表明我们的系数确实改善了模型。我们可以从图 4-11 的单元格 F12 中找到的 p 值来确定测试结果。请记住,这是科学记数法,因此将 p 值读作为 \(6.01 \times 10^{-102}\):远小于 0.05。我们可以得出结论,重量 值得作为回归模型中的系数保留。

这将我们带到了第三部分,在单元格 A16:I18 中;这里是我们最初寻找的信息。这个范围包含了大量的信息,所以让我们从列开始逐列解释,从系数开始,单元格 B17:B18。这些应该看起来很熟悉,因为它们是在方程式 4-3 中给出的线的截距和斜率。

接下来是标准误差,在 C17:C18 中。我们在第三章中已经讨论过这个:它是在重复样本中变异性的度量,而在这种情况下可以看作是我们系数精度的度量。

接下来是 Excel 称之为 “t 统计量”的内容,又称 t 统计量或检验统计量,在 D17:D18 中;这可以通过将系数除以标准误差来获得。我们可以将其与 1.96 的临界值进行比较,以在 95% 置信水平下建立统计显著性。

然而,更常见的是解释和报告 p 值,它提供了相同的信息。我们有两个 p 值要解释。首先,截距的系数在 E17 中。这告诉我们截距是否与零显著不同。截距的显著性 是我们假设检验的一部分,因此此信息是无关的。(这是另一个说明我们不能仅仅以 Excel 输出为准的好例子。)

警告

尽管大多数统计软件(包括 Excel)报告截距的 p 值,但通常这不是相关信息。

相反,我们想要单元格 E18重量的 p 值:这与线的斜率有关。p 值远低于 0.05,因此我们未能拒绝零假设,并得出结论称重量可能确实影响里程数。换句话说,该线的斜率明显不同于零。就像我们早期的假设检验一样,我们不会得出我们已经“证明”了一种关系的结论,或者更重的重量导致较低的里程数。再次强调,我们是基于样本对总体进行推断,因此不确定性是固有的。

输出还给出了我们截距和斜率的 95% 置信区间,分别在单元格 F17:I18 中。默认情况下,这个信息被重复了:如果我们在输入菜单中要求不同的置信区间,那么这里也会得到相应的输出。

现在您已经掌握了解释回归输出的要诀,让我们尝试根据方程线进行点估计:如果一辆重 3,021 磅的汽车,我们预期它的里程数会是多少?让我们把它代入我们在方程 4-4 中的回归方程:

方程 4-4. 基于我们的方程进行点估计

m p g = 46.217 - 0.0076 × 3021

基于方程 4-4,我们预计一辆重 3,021 磅的汽车能行驶 23.26 英里每加仑。看看源数据集:数据中有一条重 3,021 磅的观察结果(福特麦弗里克,数据集中的第 101 行),但它每加仑只能行驶 18 英里,而不是 23.26。怎么回事?

这种差异就是之前提到的残差:它是我们在回归方程中估计的值与实际数据中找到的值之间的差异。我已在图 4-12 中包括了这些以及其他一些观察结果。散点代表数据集中实际找到的值,而直线则代表我们用回归预测的值。

合理推测我们会受到激励以尽量减少这些值之间的差异。Excel 和大多数回归应用程序使用普通最小二乘法(OLS)来实现这一点。在 OLS 中,我们的目标是最小化残差,特别是残差平方和,以便负值和正值的残差都能平等衡量。残差平方和越低,我们的实际值和预期值之间的差异就越小,我们的回归方程在估计方面就越好。

在 Excel 中显示的残差

图 4-12. 残差即实际值与预测值之间的差异

我们从斜率的 p 值中得知,独立变量和因变量之间存在显著关系。但这并不告诉我们因变量的变异性有多少是由独立变量解释的。

记住,变异性是我们作为分析师研究的核心;变量会变化,我们想研究 为什么 它们变化。实验让我们做到了这一点,通过理解自变量和因变量之间的关系。但我们不能用自变量来解释关于我们的因变量的一切。总会有一些无法解释的误差。

R 平方,或称决定系数(Excel 称为 R 平方),表示回归模型解释了因变量变异性的百分比。例如,R 平方为 0.4 表示模型可以解释 Y 变异性的 40%。这意味着 1 减去 R 平方就是模型无法解释的变异性。如果 R 平方为 0.4,那么 60% 的 Y 的变异性是无法解释的。

Excel 为我们计算了 R 平方在回归输出的第一个框中;回顾一下图 4-11 中的 B5 单元格。R 平方的平方根是多重 R,也可以在输出的 B4 单元格中看到。调整后的 R 平方(B6 单元格)被用作对具有多个自变量的模型的 R 平方的更保守估计。这个指标在进行 多元 线性回归时是有用的,这超出了本书的范围。

除了 R 平方之外,还有其他衡量回归性能的方法:Excel 在其输出中包括其中一个,即回归标准误差(在图 4-11 的 B7 单元格)。这个指标告诉我们观察值偏离回归线的平均距离。一些分析师更喜欢这个或其他指标来评估回归模型,尽管 R 平方仍然是一个主要选择。无论偏好如何,最好的评估通常来自于在适当背景下评估多个图表,所以没有必要盲目追随或拒绝任何一个指标。

恭喜:你进行了并解释了完整的回归分析。

重新思考我们的结果:虚假关系

根据它们的时间顺序和我们自己的逻辑,在我们的里程示例中,重量应该是自变量,mpg应该是因变量,这几乎是绝对的。但是如果我们将这些变量颠倒来拟合回归线会发生什么呢?试试使用 ToolPak。得到的回归方程显示在方程 4-5 中。

方程 4-5。方程 4-5。一个根据里程估计重量的回归方程

w e i g h t = 5101.1 - 90.571 × m p g

我们可以颠倒我们的自变量和因变量,并获得相同的相关系数。但当我们将它们改变用于回归时,我们的系数会改变

如果我们发现mpgweight同时受到某个外部变量的影响,那么这两个模型都不会是正确的。这与我们在冰淇淋消费和鲨鱼袭击中面临的情况是相同的。说冰淇淋消费会影响鲨鱼袭击是愚蠢的,因为这两者都受到温度的影响,正如图 4-13 所示。

冰淇淋与鲨鱼袭击之间的虚假关系

图 4-13. 冰淇淋消费和鲨鱼袭击:一种虚假关系

这被称为虚假关系。它经常出现在数据中,而且可能并不像这个例子那么明显。对你正在研究的数据具有一定领域知识可能对检测虚假关系非常有价值。

警告

变量可能存在相关性;甚至可能有因果关系的证据。但这种关系可能是由一些你甚至没有考虑过的变量驱动的。

结论

还记得这句老话吗?

相关性不意味着因果关系。

分析是高度渐进的:我们通常会将一个概念叠加在另一个概念之上,以构建越来越复杂的分析。例如,我们总是从样本的描述统计开始,然后再尝试推断出总体的参数。虽然相关性不意味着因果关系,但因果关系是建立在相关性基础之上的。这意味着更好地总结这种关系的方式可能是:

相关性是因果关系的必要条件但不是充分条件。

在本章和之前的章节中,我们只是初步涉及了推论统计学的表面。有很多种测试存在,但它们都使用了与我们在这里使用的假设检验相同的框架。掌握这个过程,你就能够测试各种不同的数据关系。

进入编程领域

我希望你已经看到并同意 Excel 是学习统计学和分析的一种绝佳工具。你亲身了解了支持这项工作的统计原理,并学会了如何在真实数据集中探索和测试关系。

尽管如此,Excel 在进行更高级分析时可能会有收益递减的情况。例如,我们一直在使用可视化工具检查正态性和线性性等特性;这是一个良好的起点,但还有更为稳健的测试方法(通常使用统计推断)。这些技术通常依赖于矩阵代数和其他计算密集型操作,这些在 Excel 中推导可能很繁琐。虽然可以使用插件来弥补这些不足,但它们可能昂贵且缺乏特定功能。另一方面,作为开源工具,R 和 Python 是免费的,并且包含许多称为的应用程序式功能,几乎可以满足任何用例。这种环境将允许你专注于数据的概念分析,而不是原始计算,但你需要学习如何编程。这些工具以及分析工具包总体将是第五章的重点。

练习

通过分析书库存储的数据集文件夹中的ais数据集,练习你的相关性和回归技能。该数据集包括来自不同体育项目的澳大利亚男女运动员的身高、体重和血液读数。

使用数据集,尝试以下操作:

  1. 生成该数据集中相关变量的相关矩阵。

  2. 可视化htwt的关系。这是线性关系吗?如果是,是正相关还是负相关?

  3. htwt中,你认为哪个是自变量,哪个是因变量?

    • 自变量对因变量有显著影响吗?

    • 你拟合的回归线的斜率是多少?

    • 自变量解释因变量方差的百分比是多少?

  4. 该数据集包含一个变量,即身体质量指数bmi。如果你对这个指标不熟悉,请花些时间了解它是如何计算的。了解了这一点,你想分析htbmi之间的关系吗?在这里不要只依赖统计推理,也要倾向于常识。

第五章:数据分析堆栈

到本书的这一部分,你已经精通分析的关键原则和方法,在 Excel 中学习了它们。这一章作为本书后续部分的插曲,你将把这些已有的知识转化为使用 R 和 Python。

本章将进一步说明统计学、数据分析和数据科学的学科,并深入探讨 Excel、R 和 Python 如何在我称之为数据分析堆栈中发挥作用。

统计学与数据分析与数据科学

本书的重点是帮助你掌握数据分析的原则。但正如你所见,统计学对分析非常核心,以至于很难界定一个领域的结束和另一个的开始。为了加深困惑,你可能还对数据科学如何融入其中感兴趣。让我们花一点时间来明确这些区别。

统计学

统计学首先关注收集、分析和呈现数据的方法。我们从该领域借用了很多,例如,我们根据样本对总体做出推断,并使用直方图和散点图等图表描述数据的分布和关系。

到目前为止,我们使用的大多数测试和技术都来自统计学,例如线性回归和独立样本 t 检验。数据分析与统计学的区别不一定在于方法,而在于目的

数据分析

在数据分析中,我们不太关心分析数据的方法,而更关心使用结果来达到某些外部目标。这些目标可以是不同的:例如,你已经看到,虽然一些关系可能在统计上显著,但它们对业务可能没有实质性的意义。

数据分析也关注实施这些洞察力所需的技术。例如,我们可能需要清洗数据集、设计仪表板,并迅速高效地传播这些资产。尽管本书的重点是分析的统计基础,但还有其他计算技术基础需要注意,这些将在本章后面讨论。

商业分析

特别是,数据分析用于指导和实现业务目标,协助业务利益相关者;分析专业人员通常既参与业务运营,又参与信息技术。术语商业分析经常用来描述这种职责的结合。

一个数据或商业分析项目的例子可能是分析电影租赁数据。基于探索性数据分析,分析师可能假设喜剧片在假日周末销售特别好。与产品经理或其他业务利益相关者合作,他们可能会进行小型实验来收集并进一步测试这一假设。这个工作流程的要素应该从本书的前几章中听起来很熟悉。

数据科学

最后,还有数据科学:另一个与统计学有不可分割联系的领域,但专注于独特的结果。

数据科学家在工作中通常也会考虑业务目标,但其范围与数据分析有很大不同。回到我们的电影租赁例子,数据科学家可能会构建一个基于算法的系统,根据与他们相似的顾客租用的电影来推荐电影给个人。构建和部署这样的系统需要相当的工程技能。虽然说数据科学家与业务没有真正的联系是不公平的,但他们通常与工程或信息技术更加密切相关。

机器学习

总结这种区别,我们可以说,数据分析关注于描述解释数据关系,而数据科学则关注于构建预测系统和产品,通常使用机器学习技术。

机器学习是构建算法的实践,这些算法通过更多数据的输入而改进,而无需明确编程来做到这一点。例如,银行可能会部署机器学习来检测客户是否会违约。随着输入更多数据,算法可能会在数据中找到模式和关系,并用它们来更好地预测违约的可能性。机器学习模型可以提供令人难以置信的预测准确性,并且可以在各种场景中使用。尽管如此,构建复杂的机器学习算法往往很诱人,但简单的算法足以胜任,这可能导致难以解释和依赖模型。

机器学习超出了本书的范围;要了解一个很棒的概述,请查看 Aurélien Géron 的《Python 深度学习》,第二版(O'Reilly)。这本书主要使用 Python 编写,所以最好先完成本书的第三部分。

有别于但不排斥

尽管统计学、数据分析和数据科学之间的区别很重要,但我们不应让它们产生不必要的边界。在任何这些学科中,分类和连续依赖变量之间的差异都是有意义的。所有这些都使用假设检验来构建问题。我们应感谢统计学,因为它使我们能够用通俗的术语处理数据。

数据分析和数据科学角色常常交错。事实上,在本书中,你已经学习了核心数据科学技术的基础:线性回归。简言之,这些领域的联系远比分歧多。尽管本书侧重于数据分析,但你已经准备好探索它们所有;一旦学习了 R 和 Python,你将更加深入地理解它们。

现在,我们已经用统计学和数据科学的语境来解释了数据分析,让我们为 Excel、R、Python 和其他你可能在分析中学习的工具做同样的事情。

数据分析堆栈的重要性

在了解任何单一工具的技术知识之前,分析专业人士应具备根据每个工具的优缺点选择和配对不同工具的能力。

网页开发者或数据库管理员常常提到他们用来完成工作的“堆栈”工具。我们可以借鉴这个概念来帮助进行数据分析。当某个工具或“堆栈”的一部分不足时,重点不应该是责备它的不足,而是选择不同的工具或不同的“堆栈”。换句话说,我们应该把这些不同的“堆栈”看作是互补而不是替代品。

图 5-1 是我对数据分析堆栈的四个切片的概念化。这只是组织中使用的数据工具的一个极度简化版本;绘制端到端的分析管道可能会变得复杂。这些切片按照数据由信息技术部门存储和维护的地方(数据库)到由业务终端用户使用和探索的地方(电子表格)的顺序排列。这些切片可以一起使用来制定解决方案。

数据分析堆栈

图 5-1. 数据分析堆栈

让我们花些时间来探索堆栈的每个切片。我会从对典型读者最熟悉到最不熟悉的顺序来介绍这些切片。

电子表格

我不会花太多时间来介绍电子表格是什么以及它们的工作原理;你现在对它们已经非常熟悉了。这些原则也适用于其他电子表格应用程序,如 Google 表格、LibreOffice 等;在本书中我们专注于 Excel,所以我会在这里强调它。你已经看到电子表格可以将分析活跃起来,并且是探索性数据分析的好工具。这种易用性和灵活性使得电子表格成为向最终用户分发数据的理想工具。

但这种灵活性既是优点也是缺点。你是否曾经建立过一个电子表格模型,你最终得出一个数字,几个小时后重新打开文件却莫名其妙地得到了另一个数字?有时候感觉就像在电子表格中玩打地鼠游戏;很难隔离分析的某一层而不影响其他层。

一个设计良好的数据产品看起来类似于图 5-2 所示的内容:

  • 原始数据是由分析完全独立和未经处理的。

  • 然后数据经过必要的清理和分析处理。

  • 任何生成的图表或表格都是孤立的输出。

输入、处理、输出

图 5-2. 输入、处理、输出

尽管在电子表格中有原则要遵循这种方法,但这些层往往变成一锅杂乱的汤:用户可能直接在原始数据上写入,或者在计算堆叠计算的基础上构建计算,达到这样一个程度:很难追踪指向特定单元格的所有引用。即使有一个坚实的工作簿设计,也很难实现输入-处理-输出模型的最终目标,即可重现性。这里的思想是,给定相同的输入和处理,每次都会获得相同的输出。显然,当由于容易出错的步骤、笨拙的计算或其他原因,无法保证每次打开文件时都会获得相同的结果时,工作簿就不可重现。

混乱或不可重现的工作簿已经在从食品服务到金融监管的各个领域引发了恐怖故事:要了解令人恐惧的概况,请查看欧洲电子表格风险兴趣小组的这篇文章。也许你所做的分析并不像交易债券或发表具有开创性的学术研究那样具有高风险。但没有人喜欢缓慢、容易出错并产生不可靠结果的过程。但是,足够多的末日预言;正如我希望不断强调的那样,Excel 和其他电子表格在分析中有其合适的位置。让我们看看一些有助于在 Excel 中构建清洁、可重现工作流程的工具。

VBA

通常情况下,通过将分析的每个步骤记录为代码来实现计算的可重现性,这些代码可以保存并在以后快速重新执行。Excel 确实有一个内置的编程语言 Visual Basic for Applications (VBA)。

尽管 VBA 确实允许将过程记录为代码,但它缺乏许多完整统计编程语言的功能:特别是专业分析的大量免费包。此外,微软已经基本上放弃了 VBA,将资源转移到其新的 Office Scripts 语言上,作为内置的 Excel 自动化工具,以及 JavaScript 和 Python(如果传言属实的话)。

现代 Excel

我将使用这个术语来指代微软从 Excel 2010 开始向 Excel 发布的围绕商业智能(BI)的一系列工具。这些工具非常强大且易于使用,它们打破了关于 Excel 可以做什么和不能做什么的许多神话。让我们来看看构成现代 Excel 的三个应用程序:

  • Power Query 是一种从各种来源提取数据、转换数据,然后加载到 Excel 中的工具。这些数据源可以是.csv文件,也可以是关系数据库,并且可以包含许多百万条记录:虽然仍然可能是真的,即使 Excel 工作簿本身只能包含大约一百万行,但如果通过 Power Query 读取,它可以包含超过该限制的几倍。

    更好的是,Power Query 借助微软的 M 编程语言 完全可复现。用户可以通过菜单添加和编辑步骤,生成 M 代码,或者自己编写代码。Power Query 是 Excel 的一大亮点;它不仅打破了先前对工作簿数据流量限制的约束,还使得数据的检索和操作完全可复现。

  • Power Pivot 是 Excel 的关系数据建模工具。我们将在本章稍后讨论关系数据模型时,深入了解它们。

  • 最后,Power View 是在 Excel 中创建交互式图表和可视化的工具。这对构建仪表板特别有帮助。

如果你的角色高度依赖于 Excel 进行分析和报告,我强烈建议你花些时间了解现代 Excel。关于 Excel 的很多负面声音,比如不能处理超过百万行数据或无法处理多样化数据源,这些说法在最新版本中都已不再成立。

尽管如此,这些工具并不一定是用来进行统计分析的,而是在其他分析角色中提供帮助,比如构建报告和传播数据。幸运的是,通过与像 R 和 Python 这样的工具混合使用,可以在 Power Query 和 Power Pivot 的基础上构建出色的数据产品。

尽管 Excel 在演进过程中带来了诸多好处,但由于过度使用可能导致的不幸,它在分析界并不受欢迎。这让我们不禁要问:为什么 Excel 首先会被过度使用呢? 因为商业用户缺乏更好的替代方案和资源,所以他们将其视为一个直观、灵活的存储和分析数据的工具。

我认为,如果你无法击败他们,那就加入他们:Excel 一个探索和与数据互动的好工具。甚至,借助其最新的功能,它已成为构建可复现的数据清洗工作流程和关系数据模型的重要工具。但是,有些分析功能并不是 Excel 擅长的,比如存储关键数据、在多平台上分发仪表板和报告,以及进行高级统计分析。对于这些功能,让我们来看看其他选择。

数据库

数据库,特别是 关系型 数据库,在分析领域算是相对古老的技术,起源可以追溯到上世纪 70 年代初。关系数据库的基本构建单元是你之前见过的东西:表格。图 5-3 就是这样一个例子:我们已经用统计术语 变量观测 来指代这样一个表格的列和行。它们在数据库的语言中分别称为 字段记录

数据分析技术栈

图 5-3. 标记的数据库表

如果你被要求连接图 5-4 中的数据,你可以使用 Excel 的VLOOKUP()函数,使用共享列作为“查找字段”从一个表传输数据到另一个表。这方面还有很多内容,但这是关系数据模型的核心:通过表之间的关系来有效存储和管理数据。我喜欢称呼VLOOKUP()为 Excel 的胶带,因为它能够连接数据集。如果VLOOKUP()是胶带,那么关系数据模型就是焊接工。

关系数据库中字段和表之间的关系

图 5-4. 关系数据库中字段和表之间的关系

关系数据库管理系统(Relational Database Management System,RDBMS)旨在利用这一基本概念进行大规模数据存储和检索。当你在商店下订单或者订阅邮件列表时,数据很可能会经过 RDBMS。虽然建立在相同的概念上,但 Power Pivot 的用例更多地用于商业智能分析和报告,而不是全功能的 RDBMS。

结构化查询语言(Structured Query Language,或 SQL)传统上用于与数据库进行交互。这是分析领域中另一个至关重要的主题,超出了我们书本的范围;如果想要一个很好的介绍,请查看艾伦·博洛的《学习 SQL》,第三版(O’Reilly)。请记住,“SQL”(或“sequel”)作为语言名称通常被泛用,根据感兴趣的关系数据库管理系统(RDBMS),存在多个“方言”。像微软或者甲骨文等一些系统是专有的;而像 PostgreSQL 或 SQLite 这样的系统则是开源的。

SQL 可以执行的经典操作的首字母缩写是CRUD,即 Create、Read、Update、Delete。作为数据分析师,你最典型地会涉及从数据库中读取数据,而不是更改它。对于这些操作,不同平台上 SQL 方言的差异将是微不足道的。

商业智能平台

这是一系列工具中涵盖最广泛、可能也是最模糊的一部分。在这里,我指的是企业工具,允许用户收集、建模和显示数据。数据仓库工具如 MicroStrategy 和 SAP BusinessObjects 在这里有所涉及,因为它们是为自助式数据收集和分析而设计的工具。但是它们通常在可视化和交互式仪表盘构建方面有所限制。

这就是像 Power BI、Tableau 和 Looker 这样的工具派上用场的地方。这些几乎都是专有的平台,允许用户使用最少的编码来构建数据模型、仪表盘和报告。重要的是,它们使得跨组织传播和更新信息变得容易;这些资源通常甚至以各种格式部署到平板电脑和智能手机上。许多组织已将其例行报告和仪表盘创建从电子表格转移到这些商业智能工具中。

尽管商业智能平台有诸多优点,但它们在处理和可视化数据的方式上往往缺乏灵活性。为了简单适用于业务用户且难以破解,它们通常缺乏老练的数据分析人员在任务中所需的功能。此外,它们也可能价格昂贵,单用户年许可证的价格可能高达几百甚至几千美元。

根据你对 Excel 的了解,值得指出的是,现代 Excel 的元素(Power Query、Power Pivot、Power View)也适用于 Power BI。更重要的是,可以使用 R 和 Python 代码在 Power BI 中构建可视化。其他商业智能系统也具有类似的功能;我之前专注于 Excel,现在转向 Power BI。

数据编程语言

这就带我们来到最后一个方面:数据编程语言。我指的是完全由脚本编写的用于数据分析的软件应用程序。许多分析专家在他们的工具栈中没有这一部分也能做出卓越的工作。此外,许多供应商工具正朝着低代码或无代码解决方案迈进,用于复杂的分析。

尽管如此,我强烈建议你学习编程。这将增强你对数据处理工作原理的理解,并使你对工作流程具有比依赖图形用户界面(GUI)或点对点软件更全面的控制。

对于数据分析,两种开源编程语言非常适合:R 和 Python,因此本书的其余部分重点介绍这两种语言。每种语言都包含大量免费的包,旨在帮助处理从社交媒体自动化到地理空间分析的各种任务。学习这些语言将打开进入高级分析和数据科学领域的大门。如果你认为 Excel 是探索和分析数据的强大工具,那么等你掌握了 R 和 Python 的技巧之后,你会发现更强大的功能。

此外,这些工具非常适合可重现的研究。回想一下图 5-2 和我们在 Excel 中分步骤分析中发现的困难。作为编程语言,R 和 Python 记录了分析中采取的所有步骤。这种工作流程通过首先从外部源读取数据,然后在该数据的副本上操作,保持了原始数据的完整性。该工作流程还使跟踪文件的更改和贡献变得更容易,这个过程被称为版本控制,将在第十四章中讨论。

R 和 Python 是开源软件应用程序,这意味着它们的源代码对任何人都是自由可用的,可以进行构建、分发或贡献。这与专有的 Excel 完全不同。开源和专有系统各有其优缺点。在 R 和 Python 的情况下,允许任何人在源代码上自由开发已经促进了丰富的包和应用程序生态系统的形成。这也降低了新手参与的门槛。

尽管如此,我们常常发现关键的开源基础设施部分是由开发者在业余时间中免费维护的。依赖于没有商业保证的基础设施的持续发展和维护可能并不理想。有办法来减少这种风险;实际上,许多公司专门支持、维护和增强开源系统。你将在我们后续关于 R 和 Python 的讨论中看到这种关系;也许你会感到惊讶,通过提供基于自由可用代码的服务是完全可能赚钱的。

栈中的“数据编程语言”部分可能具有最陡的学习曲线:毕竟,这实际上是在学习一门新语言。学习一个这样的语言可能听起来像是一种挑战,那么你将如何学习两种呢?

首先,在本书开头我们提到过,你并不是从零开始。你已经有了扎实的编程和数据处理知识。所以,要淡定地接受你确实已经学会了如何编码……某种程度上。

小贴士

对于数据编程语言,多语言能力是有好处的,就像口头语言一样。在实际层面上,雇主可能会使用其中任何一种,所以涵盖多个领域是明智的。但你学习两者不只是为了打勾:每种语言都有其独特的特点,你可能会发现在特定用例中更容易使用其中一种。正如考虑到栈的不同部分是互补而不是替代品一样,对于同一部分的工具也是如此。

结论

数据分析师常常在犹豫他们应该专注学习或成为专家的工具。我建议不要成为任何一个单一工具的专家,而是要足够熟悉每个栈的不同工具,以便进行上下文化和选择。从这个角度看,声称一个栈的一部分低于另一个栈是没有多少意义的。它们应该是互补而不是替代品。

实际上,许多最强大的分析产品来自于组合栈的部分。例如,你可以使用 Python 自动化生成基于 Excel 的报告,或者从关系数据库管理系统中提取数据到 BI 平台的仪表板中。虽然这些用例超出了本书的范围,但对我们的讨论来说,重点是:不要忽视 Excel。你在 R 和 Python 中的技能只会为 Excel 的价值栈增添力量。

在本书中,我们专注于电子表格(Excel)和数据编程语言(R 和 Python)。这些工具特别适用于数据分析的统计角色,正如我们讨论过的,它们与传统统计学和数据科学有一定的重叠。但正如我们也讨论过的那样,分析不仅涉及纯粹的统计分析,关系数据库和 BI 工具对这些任务也很有帮助。一旦你熟悉了本书中的主题,请考虑用我在本章早些时候提到的标题来丰富你的数据分析堆栈知识。

接下来是什么

思考数据分析和数据分析应用的大局观后,让我们深入探索新工具。

我们将从 R 开始,因为我认为对 Excel 用户来说,它更自然地跳入数据编程的起点。你将学会如何用 R 进行与 Excel 相同的 EDA 和假设检验,这将使你在更高级的分析中处于一个很好的位置。然后你将用 Python 做同样的事情。在每个阶段,我将帮助你将所学的与已知的联系起来,这样你就能看到许多概念实际上是多么熟悉。我们在第六章再见。

练习

本章比较概念化,没有练习。我鼓励你在拓展到其他分析领域并将它们联系起来时再回顾这一章节。当你在工作中或浏览社交媒体或行业出版物时遇到新的数据工具,请问自己它涵盖哪些堆栈,是否开源等等。

第二部分:从 Excel 到 R

第六章:R 新手入门指南

在 第一章 中,你学习了如何在 Excel 中进行探索性数据分析。也许你还记得那一章提到,约翰·图基被誉为推广 EDA 实践的人物。图基的数据分析方法启发了几种统计编程语言的发展,包括在传奇贝尔实验室开发的 S 语言。S 语言后来又启发了 R 语言的诞生。由 Ross Ihaka 和 Robert Gentleman 在 1990 年代初开发,这个名称不仅仅是从 S 语言派生而来,也是两位共同创始人名字的谐音。R 是开源的,并由 R 基金会维护。因其主要用于统计计算和图形绘制,因此在研究人员、统计学家和数据科学家中最为流行。

注意

R 的开发专门考虑了统计分析。

下载 R

要开始,请访问 R 项目的网站。点击页面顶部的链接下载 R。您将被要求从 Comprehensive R Archive Network (CRAN) 中选择一个镜像。这是一个分发 R 源代码、包和文档的服务器网络。选择一个靠近您的镜像,以便为您的操作系统下载 R。

开始使用 RStudio

你现在已经安装了 R,但我们还会进行一次下载,以优化我们的编程体验。在 第五章 中,你了解到开源软件意味着任何人都可以在其上构建、分发或贡献代码。例如,供应商可以提供一个 集成开发环境(IDE)与代码交互。RStudio IDE 在单一界面下结合了代码编辑、图形、文档等工具。这在 R 编程市场上的十多年中已成为主流 IDE,用户使用它构建从交互式仪表板(Shiny)到研究报告(R Markdown)的各种产品。

你可能会想,如果 RStudio 如此出色,为什么我们还要安装 R? 实际上,这两个下载是不同的:我们下载了 R 作为代码库,而下载 RStudio 则是为了与代码一起工作的 IDE。这种应用分离可能对 Excel 用户来说很陌生,但在开源软件世界中非常常见。

警告

RStudio 是一个与 R 代码一起工作的平台,而不是代码库本身。首先,从 CRAN 下载 R,然后下载 RStudio。

要下载 RStudio,请访问其网站的 下载页面。您会看到 RStudio 是基于分层定价系统提供的;选择免费的 RStudio Desktop。(RStudio 是如何在开源软件的基础上建立稳固业务的一个很好的例子。)一开始可能会觉得 RStudio 非常压抑,因为它有很多面板和功能。为了克服这种初始的不适感,我们将进行一次导览。

首先,转到主菜单并选择 文件 → 新建文件 → R 脚本。现在你应该看到类似于 Figure 6-1 的界面。这里有很多花哨的功能;IDE 的理念就是将所有编码开发所需的工具集中在一个地方。我们将介绍每个四块窗格的功能,这些是你入门所需了解的。

位于 RStudio 左下角的 控制台 是提交命令给 R 执行的地方。在这里,你会看到 > 符号后跟着一个闪烁的光标。你可以在这里键入操作,然后按 Enter 执行。让我们从一些非常基础的操作开始,比如找到 1 + 1,就像 Figure 6-2 中所示。

R 中的空白界面

Figure 6-1. RStudio IDE

在 RStudio 控制台中输入 1+1

Figure 6-2. 在 RStudio 中编码,从 1 + 1 开始

你可能已经注意到在你的 2 结果之前出现了 [1]。要理解这意味着什么,可以在控制台中键入并执行 1:50。在 R 中,: 运算符会在给定范围内以增量为 1 生成所有数字,类似于 Excel 中的填充手柄。你应该看到类似这样的输出:

1:50
#> [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
#> [24] 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
#> [47] 47 48 49 50

这些带方括号的标签表示输出中每行第一个值的数值位置。

虽然你可以继续从这里工作,但通常先在 脚本 中编写你的命令,然后将它们发送到控制台是个好主意。这样你可以保存你运行的代码的长期记录。脚本编辑器位于控制台正上方的窗格中。在那里输入几行简单的算术,如 Figure 6-3 中所示。

RStudio 脚本编辑器

Figure 6-3. 在 RStudio 中使用脚本编辑器

将光标放在第 1 行,然后将其悬停在脚本编辑器顶部的图标上,直到找到一个标有“运行当前行或选定内容”的图标。点击该图标会发生两件事。首先,活动代码行将在控制台中执行。然后,光标将移到脚本编辑器中的下一行。你可以通过选择它们并点击该图标一次性将多行发送到控制台。此操作的键盘快捷键是 Windows 下的 Ctrl + Enter,Mac 下的 Cmd + Return。作为 Excel 用户,你可能是键盘快捷键的爱好者;RStudio 提供了大量的快捷键,可以通过选择 工具 → 键盘快捷键帮助 查看。

让我们保存我们的脚本。从菜单中选择 文件 → 保存。将文件命名为 ch-6。R 脚本的文件扩展名是 .r。打开、保存和关闭 R 脚本的过程可能让你想起在文字处理器中操作文档;毕竟,它们都是书面记录。

现在我们将转向右下角的窗格。这里有五个选项卡:文件(Files)、绘图(Plots)、包(Packages)、帮助(Help)、查看器(Viewer)。R 提供了大量的帮助文档,可以在这个窗格中查看。例如,我们可以使用?操作符了解更多关于 R 函数的信息。作为 Excel 用户,你对诸如VLOOKUP()SUMIF()之类的函数了如指掌。一些 R 函数与 Excel 中的函数非常相似;例如,让我们学习一下 R 的平方根函数sqrt()。将以下代码输入到你脚本的新行中,并使用菜单图标或键盘快捷键运行它:

?sqrt

在帮助窗口中将出现一个名为“Miscellaneous Mathematical Functions”的文档。这包含了关于sqrt()函数、它的参数以及更多重要信息。它还包括了这个函数的示例:

require(stats) # for spline
require(graphics)
xx <- -9:9
plot(xx, sqrt(abs(xx)),  col = "red")
lines(spline(xx, sqrt(abs(xx)), n=101), col = "pink")

现在不用担心理解这段代码;只需将选定的部分复制粘贴到你的脚本中,突出显示整个选择,并运行它。绘图将如同图 6-4 中所示出现。我已调整我的 RStudio 窗格大小以使绘图更大。你将学会如何在第 8 章中构建 R 绘图。

你的第一个 R 绘图

图 6-4. 你的第一个 R 绘图

现在,看向右上角的窗格:环境(Environment)、历史(History)、连接(Connections)。环境选项卡列出了称为xx的东西,旁边似乎是一些整数集合。这是什么?事实证明,是用我让你盲目运行的sqrt()文档中的代码创建的。实际上,在 R 中我们大部分工作都围绕这里显示的内容展开:一个对象

正如你可能注意到的那样,在这次 RStudio 之旅中我们忽略了几个窗格、图标和菜单选项。它是一个功能丰富的集成开发环境:不要害怕去探索、实验和搜索引擎以获取更多学习资料。但现在,你已经了解如何在 RStudio 中四处走动,开始正式学习 R 编程。你已经看到 R 可以用作一个高级计算器。表格 6-1 列出了 R 中一些常见的算术运算符。

表格 6-1. R 中常见的算术运算符

操作符 描述
+ 加法
- 减法
* 乘法
/ 除法
^ 指数
%% 取模
%/% 地板除法

或许你对表格 6-1 中的最后两个操作符不太熟悉:modulo返回除法的余数,floor division将除法结果向下取整到最接近的整数。

像 Excel 一样,R 遵循算术运算的操作顺序。

# Multiplication before addition
3 * 5 + 6
#> [1] 21

# Division before subtraction
2 / 2 - 7
#> [1] -6

哪些带有井号(#)和文本的行是怎么回事? 这些是单元格注释,用于提供关于代码的口头说明和提醒。 注释帮助其他用户——以及日后的自己——理解和记住代码的用途。 R 不执行单元格注释:脚本的这部分是给程序员看的,而不是给计算机看的。 虽然可以将注释放在代码右侧,但最好放在上方:

1 * 2 # This comment is possible
#> [1] 2

# This comment is preferred
2 * 1
#> [1] 2

您不需要使用注释来解释关于代码正在做什么的所有内容,但请解释您的推理和假设。 将其视为,嗯,评论。 我将在本书的示例中继续使用相关且有帮助的注释。

提示

养成习惯,包括注释以记录您编写代码的目标、假设和推理。

正如先前提到的,函数在 R 中的工作中占据了很大的比重,就像在 Excel 中一样,并且通常看起来非常相似。 例如,我们可以取–100 的绝对值:

# What is the absolute value of -100?
abs(-100)
#> [1] 100

但是,在使用 R 中处理函数时有一些非常重要的区别,正如这些错误所示。

# These aren't going to work
ABS(-100)
#> Error in ABS(-100) : could not find function "ABS"
Abs(-100)
#> Error in Abs(-100) : could not find function "Abs"

在 Excel 中,您可以输入小写abs()或 Proper Case Abs()而不会有问题。 然而,在 R 中,abs()函数必须小写。 这是因为 R 是区分大小写的。 这是 Excel 和 R 之间的一个主要区别,迟早会让你碰壁。

警告

R 是一种区分大小写的语言:SQRT()函数与sqrt()不同。

就像在 Excel 中一样,一些 R 函数,例如 sqrt(),用于处理数字;其他函数,如 toupper(),用于处理字符:

# Convert to upper case
toupper('I love R')
#> [1] "I LOVE R"

让我们看另一个情况,R 表现类似于 Excel,只有一个例外将有巨大的影响:比较运算符。 当我们比较两个值之间的某种关系时,例如一个是否大于另一个。

# Is 3 greater than 4?
3 > 4
#> [1] FALSE

R 将返回TRUEFALSE作为任何比较运算符的结果,就像 Excel 一样。 表 6-2 列出了 R 的比较运算符。

表 6-2. R 中的比较运算符

运算符 意义
> 大于
< 小于
>= 大于或等于
<= 小于或等于
!= 不等于
== 等于

大部分这些可能对您来说很熟悉,除了…你注意到最后一个了吗? 是的,你在 R 中不是用一个等号检查两个值是否相等,而是用两个等号。 这是因为在 R 中,单个等号用于赋值对象

如果你还不太清楚问题的重要性,稍等片刻,我们来看另一个例子。 让我们将–100 的绝对值分配给一个对象;我们称之为my_first_object

# Assigning an object in R
my_first_object = abs(-100)

您可以将对象看作我们放入信息的鞋盒。通过使用=操作符,我们将abs(-100)的结果存储在名为my_first_object的鞋盒中。我们可以通过打印它来打开这个鞋盒。在 R 中,您可以通过运行对象的名称来简单地做到这一点:

# Printing an object in R
my_first_object
#> [1] 100

在 R 中另一种分配对象的方法是使用<-操作符。事实上,这通常比使用=更可取,部分原因是为了避免它与==之间的混淆。尝试使用此操作符分配另一个对象,然后打印它。在 Windows 上的键盘快捷键是 Alt+-,在 Mac 上是 Option--。您可以在您的函数和操作中大胆创新,就像我做的一样:

my_second_object <- sqrt(abs(-5 ^ 2))
my_second_object
#> [1] 5

在 R 中,对象名称必须以字母或点开头,并且只能包含字母、数字、下划线和句点。还有一些禁用的关键字。这给“创造性”对象命名留下了很大的余地。但是好的对象名称能够准确地指示它们所存储的数据,就像鞋盒上的标签指示了里面装的鞋的类型一样。

对象可以包含不同类型或模式的数据,就像您可能有不同类型的鞋盒一样。表 6-3 列出了一些常见的数据类型。

表 6-3. R 中常见的数据类型

数据类型 示例
字符 'R', 'Mount', 'Hello, world'
数值 6.2, 4.13, 3
整数 3L, -1L, 12L
逻辑 TRUE, FALSE, T, F

让我们创建一些不同模式的对象。首先,字符数据通常用单引号括起来以增加可读性,但双引号也可以工作,并且如果要包含单引号作为输入的一部分,则双引号尤其有用。

my_char <- 'Hello, world'
my_other_char <- "We're able to code R!"

数字可以表示为小数或整数:

my_num <- 3
my_other_num <- 3.21

然而,整数也可以作为独立的整数数据类型存储。输入中包含的L代表字面值;这个术语来自计算机科学,用于表示固定值的记法:

my_int <- 12L

TF 默认会评估为逻辑数据 TRUEFALSE

my_logical <- FALSE
my_other_logical <- F

我们可以使用str()函数来了解对象的结构,例如其类型和包含的信息:

str(my_char)
#> chr "Hello, world"
str(my_num)
#> num 3
str(my_int)
#> int 12
str(my_logical)
#> logi FALSE

一旦分配完成,我们就可以在其他操作中自由使用这些对象:

# Is my_num equal to 5.5?
my_num == 5.5
#> [1] FALSE

# Number of characters in my_char
nchar(my_char)
#> [1] 12

我们甚至可以使用对象作为输入来分配其他对象,或重新分配它们:

my_other_num <- 2.2
my_num <- my_num/my_other_num
my_num
#> [1] 1.363636

“那又怎样?”你可能会问。“我处理大量数据,所以将每个数字分配给自己的对象如何帮助我?”幸运的是,在第七章中,您将看到将多个值组合成一个对象是可能的,就像在 Excel 中处理范围和工作表一样。但在此之前,让我们稍微转换一下方向,了解一下包的相关内容。

R 中的包

想象一下,如果您无法在智能手机上下载应用程序。您可以打电话、浏览互联网并记录笔记——这仍然非常方便。但智能手机真正的力量来自于其应用程序,即应用程序或应用程序。R 发货就像一个“出厂默认设置”智能手机:仍然非常有用,如果必要,您几乎可以完成任何事情。但通常更有效的是进行 R 版本的应用安装:安装包

R 的出厂默认版本称为“基础 R”。包,即 R 的“应用程序”,是可共享的代码单元,包括函数、数据集、文档等等。这些包建立在基础 R 之上,以提高功能和添加新特性。

之前,您从 CRAN 下载了基础 R。该网络还托管了超过 10,000 个包,这些包由 R 广泛的用户基础贡献,并由 CRAN 志愿者审核。这就是您的 R “应用商店”,借用著名口号改编,“R 天下无不包”。虽然可以从其他地方下载包,但对于初学者来说,最好还是坚持使用 CRAN 上托管的内容。要从 CRAN 安装包,可以运行install.packages()

本书将使用包来帮助我们进行数据处理和可视化任务。特别地,我们将使用tidyverse,这实际上是一个集合的包,设计用于一起使用。要安装这个集合,请在控制台中运行以下命令:

install.packages('tidyverse')

您刚刚安装了一些有用的包;其中一个是dplyr(通常发音为d-plier),其中包含一个arrange()函数。尝试打开此函数的文档,您会收到一个错误:

?arrange
#> No documentation for ‘arrange’ in specified packages and libraries:
#> you could try ‘??arrange’

要理解为什么 R 找不到这个tidyverse函数,请回到智能手机类比:即使您已安装了应用程序,您仍然需要打开它才能使用它。与 R 相同:我们已经用install.packages()安装了包,但现在我们需要用library()将其调用到我们的会话中:

# Call the tidyverse into our session
library(tidyverse)
#> -- Attaching packages -------------------------- tidyverse  1.3.0 --
#> v ggplot2 3.3.2     v purrr   0.3.4
#> v tibble  3.0.3     v dplyr   1.0.2
#> v tidyr   1.1.2     v stringr 1.4.0
#> v readr   1.3.1     v forcats 0.5.0
#> -- Conflicts ------------------------------ tidyverse_conflicts() --
#> x dplyr::filter() masks stats::filter()
#> x dplyr::lag()    masks stats::lag()

tidyverse的包现在可用于您的其余 R 会话;您现在可以运行示例而不出错。

注意

包是安装一次,但每次会话都需要调用

升级 R、RStudio 和 R 包

RStudio、R 包和 R 本身不断改进,因此偶尔检查更新是个好主意。要更新 RStudio,请导航到菜单并选择帮助 → 检查更新。如果需要更新,RStudio 将引导您完成步骤。

要从 CRAN 更新所有包,您可以运行此函数并按提示步骤操作:

update.packages()

您还可以通过 RStudio 菜单中的工具 → 检查包更新来更新包。将会出现一个更新包菜单;选择您希望更新的所有包。您还可以通过工具菜单安装包。

升级 R 本身的操作更为复杂。如果您使用的是 Windows 计算机,您可以使用来自installr包的updateR()函数并按照其说明操作:

# Update R for Windows
install.packages('installr')
library(installr)
updateR()

对于 Mac 用户,请返回到 CRAN 网站 安装最新版本的 R。

结论

在本章中,您学习了如何在 R 中处理对象和包,并且熟悉了使用 RStudio。您学到了很多;我认为现在是休息的时候了。保存您的 R 脚本并通过选择文件 → 退出会话关闭 RStudio。这时会询问您:“将工作空间图像保存到 ~/.RData?”作为一种规则,不要保存您的工作空间图像。如果保存了,所有保存的对象的副本将被保存,以便在下一个会话中使用。尽管这听起来一个好主意,但是存储这些对象并为什么存储它们可能会变得繁琐。

相反,依靠 R 脚本本身在下一个会话中重新生成这些对象。毕竟,编程语言的优势在于它是可重复的:如果我们可以按需创建它们,就不需要将对象带在身边。

提示

保存工作空间图像的一侧出错;您应该能够使用脚本重新创建以前会话中的任何对象。

为了防止 RStudio 在会话之间保留您的工作空间,请转到主菜单,然后转到工具 → 全局选项。在常规菜单下,如图 Figure 6-5 中所示,更改工作空间下的两个设置。

RStudio 工作空间设置

图 6-5. RStudio 中的自定义工作空间选项

练习

以下练习提供了关于处理对象、包和 RStudio 的额外实践和见解:

  1. 除了作为一个工具的工作马之外,RStudio 还提供了无限的外观定制选项。从菜单中选择工具 → 全局选项 → 外观,并自定义编辑器的字体和主题。例如,您可以决定使用“深色模式”主题。

  2. 在 RStudio 中使用脚本,执行以下操作:

    • 将 1 和 4 的和赋值为 a

    • a 的平方根赋值为 b

    • b 减 1 赋值给 d

    • d 中存储的是什么类型的数据?

    • d 是否大于 2?

  3. 从 CRAN 安装psych包,并将其加载到会话中。使用注释解释安装和加载包之间的区别。

除了这些练习外,我鼓励您立即在日常工作中开始使用 R。目前,这可能只涉及将应用程序用作一个高级计算器。但即使这样,也能帮助您熟悉使用 R 和 RStudio。

第七章:R 中的数据结构

在第六章的末尾,您学会了如何在 R 中使用包。在脚本的开头加载所需的包是很常见的,这样可以避免后续下载时出现意外情况。在这个精神上,我们现在会调用本章需要的所有包。您可能需要安装其中一些包;如果需要恢复到如何执行此操作,请回顾第六章。在适当的时候,我会进一步解释这些包。

# For importing and exploring data
library(tidyverse)

# For reading in Excel files
library(readxl)

# For descriptive statistics
library(psych)

# For writing data to Excel
library(writexl)

向量

在第六章中,您还学习了如何对不同模式的数据调用函数,并将数据分配给对象:

my_number <- 8.2
sqrt(my_number)
#> [1] 2.863564

my_char <- 'Hello, world'
toupper(my_char)
#> [1] "HELLO, WORLD"

大多数情况下,您通常会同时处理多个数据片段,因此将每个数据片段分配给单独的对象可能并不太实用。在 Excel 中,您可以将数据放入连续的单元格中,称为范围,并轻松地对该数据进行操作。图 7-1 展示了在 Excel 中操作数字和文本范围的简单示例:

Excel 中的典型范围

图 7-1. 在 Excel 中操作范围

早些时候,我将对象的模式类比为鞋盒里特定类型的鞋子。对象的结构是鞋盒本身的形状、大小和结构。事实上,您已经用str()函数找到了 R 对象的结构。

R 包含几种对象结构:我们可以通过将数据放入称为向量的特定结构中来存储和操作一小部分数据。向量是同一类型的一个或多个数据元素的集合。事实证明,我们已经在使用向量,我们可以用is.vector()函数确认:

is.vector(my_number)
#> [1] TRUE

尽管my_number是一个向量,但它只包含一个元素——有点像 Excel 中的单个单元格。在 R 中,我们会说这个向量的长度为 1:

length(my_number)
#> [1] 1

我们可以用c()函数将多个元素组成一个向量,类似于 Excel 中的范围。这个函数之所以被称为如此,是因为它用于组合多个元素成为一个单一向量。让我们来试试:

my_numbers <- c(5, 8, 2, 7)

此对象确实是一个向量,其数据是数值型,长度为 4:

is.vector(my_numbers)
#> [1] TRUE

str(my_numbers)
#> [1] num [1:4] 5 8 2 7

length(my_numbers)
#> [1] 4

让我们看看在my_numbers上调用函数会发生什么:

sqrt(my_numbers)
#> [1] 2.236068 2.828427 1.414214 2.645751

现在我们有了进展。我们可以类似地操作字符向量:

roster_names <- c('Jack', 'Jill', 'Billy', 'Susie', 'Johnny')
toupper(roster_names)
#> [1] "JACK"   "JILL"   "BILLY"  "SUSIE"  "JOHNNY"

通过使用c()函数将数据元素组合成向量,我们能够轻松地在 R 中复制 Excel 中图 7-1 所示的内容。如果将不同类型的元素分配给同一个向量会发生什么?让我们试试看:

my_vec <- c('A', 2, 'C')
my_vec
#> [1] "A" "2" "C"

str(my_vec)
#> chr [1:3] "A" "2" "C"

R 将强制转换所有元素为相同类型,以便它们可以组合成一个向量;例如,前面例子中的数字元素 2 被强制转换为字符型。

索引和子集向量

在 Excel 中,INDEX()函数用于找到范围内元素的位置。例如,我将在图 7-2 中使用INDEX()从命名范围roster_names(单元格A1:A5)中提取第三个位置的元素:

Excel 中的索引

图 7-2. Excel 范围上的INDEX()函数

我们也可以通过在对象名称内的括号中添加所需的索引位置来类似地索引 R 中的向量:

# Get third element of roster_names vector
roster_names[3]
#> [1] "Billy"

使用相同的符号,可以通过它们的索引号选择多个元素,我们称之为子集。让我们再次使用:运算符来提取位置在 1 到 3 之间的所有元素:

# Get first through third elements
roster_names[1:3]
#> [1] "Jack"  "Jill"  "Billy"

在这里也可以使用函数。还记得length()吗?我们可以使用它获取向量中的最后一个元素:

# Get second through last elements
roster_names[2:length(roster_names)]
#> [1] "Jill"   "Billy"  "Susie"  "Johnny"

我们甚至可以使用c()函数按非连续元素的向量进行索引:

# Get second and fifth elements
roster_names[c(2, 5)]
#> [1] "Jill"   "Johnny"

从 Excel 表到 R 数据框架

“这一切都很好,”您可能会想,“但我不只是处理这些小范围。那些完整的数据表格呢?” 毕竟,在第一章中,您学习了将数据排列成变量和观察值的重要性,例如图 7-3 中显示的star数据。这是一个二维数据结构的例子。

Excel 中的二维数据结构

图 7-3. Excel 中的二维数据结构

虽然 R 的向量是一维的,但数据框架允许同时存储行和列的数据。这使得数据框架成为 Excel 表格的 R 等价物。形式上,数据框架是一个二维数据结构,每列的记录是相同模式,并且所有列的长度相同。在 R 中,像 Excel 一样,通常为每列分配一个标签或名称。

我们可以使用data.frame()函数从头开始创建数据框架。让我们构建并打印一个名为roster的数据框架:

roster <- data.frame(
   name = c('Jack', 'Jill', 'Billy', 'Susie', 'Johnny'),
   height = c(72, 65, 68, 69, 66),
   injured = c(FALSE, TRUE, FALSE, FALSE, TRUE))

roster
#>     name height injured
#> 1   Jack     72   FALSE
#> 2   Jill     65    TRUE
#> 3  Billy     68   FALSE
#> 4  Susie     69   FALSE
#> 5 Johnny     66    TRUE

我们之前使用过c()函数将元素组合成向量。事实上,数据框架可以被看作是等长向量的集合。在三个变量和五个观察值时,roster是一个相当小的数据框架。幸运的是,并不总是需要像这样从头开始构建数据框架。例如,R 自带许多数据集。您可以使用这个函数查看它们的列表:

data()

一个标记为“R 数据集”的菜单将出现在脚本窗格的新窗口中。这些数据集中的许多(但并非全部)都结构化为数据框架。例如,您可能之前遇到过著名的iris数据集;这在 R 中是默认提供的。

就像任何对象一样,在这里也可以打印iris;但是,这将快速地用 150 行数据淹没您的控制台。(想象这个问题扩展到成千上万甚至百万行。)更常见的做法是使用head()函数仅打印前几行数据:

head(iris)
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 1          5.1         3.5          1.4         0.2  setosa
#> 2          4.9         3.0          1.4         0.2  setosa
#> 3          4.7         3.2          1.3         0.2  setosa
#> 4          4.6         3.1          1.5         0.2  setosa
#> 5          5.0         3.6          1.4         0.2  setosa
#> 6          5.4         3.9          1.7         0.4  setosa

我们可以确认iris确实是一个数据框架,可以用is.data.frame()来验证:

is.data.frame(iris)
#> [1] TRUE

除了打印它之外,了解我们的新数据集的另一种方法是使用str()函数:

str(iris)
#> 'data.frame':	150 obs. of  5 variables:
#> $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#> $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#> $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#> $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#> $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 ...

输出返回数据框架的大小以及关于其列的一些信息。你会看到其中四列是数值型的。最后一列Species是一个factor。因素是一种特殊的存储变量的方式,它们只能取有限数量的值。它们对于存储分类变量特别有帮助:事实上,你会看到Species被描述为具有三个levels,这是我们在统计学中描述分类变量时使用的术语之一。

虽然这本书不涉及因素,但对于处理分类变量,因素有很多好处,比如提供更高效的内存存储。要了解更多关于因素的信息,请查阅 R 的帮助文档,找到factor()函数(可以使用?运算符)。tidyverse也包括forcats作为一个核心包,用于处理因素。

除了 R 预加载的数据集之外,许多包还包含它们自己的数据。你也可以使用data()函数来了解它们。让我们看看psych包中是否包含任何数据集:

data(package = 'psych')

“R 数据集”菜单将在新窗口中再次启动;这一次,会出现一个名为“包psych中的数据集”的额外部分。其中一个数据集被称为sat.act。要使这个数据集在我们的 R 会话中可用,我们可以再次使用data()函数。现在它是一个分配的 R 对象,你可以在你的环境菜单中找到它,并像任何其他对象一样使用;让我们确认一下它是一个数据框架:

data('sat.act')
str(sat.act)
#> 'data.frame':	700 obs. of  6 variables:
#> $ gender   : int  2 2 2 1 1 1 2 1 2 2 ...
#> $ education: int  3 3 3 4 2 5 5 3 4 5 ...
#> $ age      : int  19 23 20 27 33 26 30 19 23 40 ...
#> $ ACT      : int  24 35 21 26 31 28 36 22 22 35 ...
#> $ SATV     : int  500 600 480 550 600 640 610 520 400 730 ...
#> $ SATQ     : int  500 500 470 520 550 640 500 560 600 800 ...

在 R 中导入数据

当在 Excel 中工作时,通常会在同一个工作簿中存储、分析和呈现数据。相比之下,在 R 脚本内部存储数据是不常见的。一般情况下,数据会从外部来源导入,从文本文件和数据库到网页和应用程序接口(API)再到图像和音频,然后才在 R 中进行分析。分析结果经常被导出到不同的来源。让我们开始这个过程,从不出所料的 Excel 工作簿(文件扩展名.xlsx)和逗号分隔值文件(文件扩展名.csv)中读取数据。

要在 R 中导入数据,理解文件路径和目录如何工作非常重要。每次使用程序时,你都在计算机上的一个“家庭基地”或工作目录上工作。从 R 引用的任何文件,比如导入数据集时,都假定相对于该工作目录。getwd()函数打印工作目录的文件路径。如果你在 Windows 上,你会看到类似于这样的结果:

getwd()
#> [1] "C:/Users/User/Documents"

对于 Mac 用户,效果会类似这样:

getwd()
#> [1] "/Users/user"

R 有一个全局默认的工作目录,在每个会话启动时都是相同的。我假设你是从书的伴随仓库的下载或克隆副本中运行文件,并且你也在同一个文件夹中的 R 脚本中工作。在这种情况下,最好将工作目录设置为此文件夹,可以使用setwd()函数完成。如果你不习惯使用文件路径,可能会难以正确填写;幸运的是,RStudio 提供了一个基于菜单驱动的方法来完成这个操作。

要将工作目录更改为与当前 R 脚本相同的文件夹,请转到 Session → Set Working Directory → To Source File Location。你会在控制台看到setwd()函数的结果。再次运行getwd(),你会看到你现在在不同的工作目录中。

现在我们已经设定了工作目录,让我们练习与相对于该目录的文件进行交互。我已经在书籍仓库的主文件夹中放置了一个test-file.csv文件。我们可以使用file.exists()函数来检查我们是否能成功定位它:

file.exists('test-file.csv')
#> [1] TRUE

我还将此文件的副本放置在仓库的test-folder子文件夹中。这次,我们需要指定要查找的子文件夹:

file.exists('test-folder/test-file.csv')
#> [1] TRUE

如果我们需要向一个文件夹怎么办?试着将test-file的副本放在当前目录的上一级文件夹中。我们可以使用..告诉 R 向上查找一个文件夹:

file.exists('../test-file.csv')
#> [1] TRUE

现在你已经掌握了在 R 中定位文件的窍门,让我们实际读取一些数据。书籍仓库包含一个datasets文件夹,其中有一个star子文件夹。其中包含了两个文件:districts.csvstar.xlsx

要读取.csv文件,我们可以使用readr中的read_csv()函数。该包是tidyverse集合的一部分,因此我们无需安装或加载任何新内容。我们将文件的位置传递给该函数。(现在你明白为什么理解工作目录和文件路径是有帮助的了吗?)

read_csv('datasets/star/districts.csv')
#>-- Column specification ---------------------------
#> cols(
#>  schidkn = col_double(),
#>  school_name = col_character(),
#>  county = col_character()
#> )
#>
#> # A tibble: 89 x 3
#>   schidkn school_name     county
#>     <dbl> <chr>           <chr>
#> 1       1 Rosalia         New Liberty
#> 2       2 Montgomeryville Topton
#> 3       3 Davy            Wahpeton
#> 4       4 Steelton        Palestine
#> 5       5 Bonifay         Reddell
#> 6       6 Tolchester      Sattley
#> 7       7 Cahokia         Sattley
#> 8       8 Plattsmouth     Sugar Mountain
#> 9       9 Bainbridge      Manteca
#>10      10 Bull Run        Manteca
#> # ... with 79 more rows

这会产生相当多的输出。首先,指定了我们的列,并告知了用于将数据解析到 R 中的函数。接下来,列出了数据的前几行,作为一个tibble。这是对数据框的现代化处理。它仍然是一个数据框,并且大部分行为与数据框类似,但做了一些修改以使其更易于在tidyverse中使用。

尽管我们能够将数据读入 R 中,但除非我们将其分配给一个对象,否则我们将无法对其进行操作:

districts <- read_csv('datasets/star/districts.csv')

tibble 的许多好处之一是,我们可以打印它,而不必担心控制台输出过多;仅打印前 10 行:

districts
#> # A tibble: 89 x 3
#>   schidkn school_name     county
#>     <dbl> <chr>           <chr>
#> 1       1 Rosalia         New Liberty
#> 2       2 Montgomeryville Topton
#> 3       3 Davy            Wahpeton
#> 4       4 Steelton        Palestine
#> 5       5 Bonifay         Reddell
#> 6       6 Tolchester      Sattley
#> 7       7 Cahokia         Sattley
#> 8       8 Plattsmouth     Sugar Mountain
#> 9       9 Bainbridge      Manteca
#> 10      10 Bull Run        Manteca
#> # ... with 79 more rows

readr不包括导入 Excel 工作簿的方法;我们将使用readxl包。虽然它是tidyverse的一部分,但这个包不像readr那样随核心套件一起加载,这就是为什么我们在本章开始时单独导入它的原因。

我们将使用read_xlsx()函数来类似地导入star.xlsx作为一个 tibble:

star <- read_xlsx('datasets/star/star.xlsx')
head(star)
#> # A tibble: 6 x 8
#>  tmathssk treadssk classk       totexpk sex   freelunk race  schidkn
#>     <dbl>    <dbl> <chr>          <dbl> <chr> <chr>    <chr>   <dbl>
#> 1      473      447 small.class        7 girl  no       white      63
#> 2      536      450 small.class       21 girl  no       black      20
#> 3      463      439 regular.wit~       0 boy   yes      black      19
#> 4      559      448 regular           16 boy   no       white      69
#> 5      489      447 small.class        5 boy   yes      white      79
#> 6      454      431 regular            8 boy   yes      white       5

使用readxl还可以做更多事情,比如读取.xls.xlsm文件以及读取工作簿的特定工作表或范围。要了解更多,请查看该包的文档

探索数据框

之前你学过head()str()来估算数据框的大小。这里有几个更有用的函数。首先,View()是一个来自 RStudio 的函数,它的输出对你作为 Excel 用户来说会非常受欢迎:

View(star)

调用此函数后,一个类似于电子表格的查看器将出现在你的脚本窗格的新窗口中。你可以像在 Excel 中一样对数据集进行排序、筛选和探索。然而,正如函数所示,它仅供查看。你不能从这个窗口对数据框进行更改。

glimpse()函数是另一种打印数据框的几个记录以及其列名和类型的方式。这个函数来自于dplyr,它是tidyverse的一部分。在后面的章节中,我们将大量使用dplyr来操作数据。

glimpse(star)
#> Rows: 5,748
#> Columns: 8
#> $ tmathssk <dbl> 473, 536, 463, 559, 489,...
#> $ treadssk <dbl> 447, 450, 439, 448, 447,...
#> $ classk   <chr> "small.class", "small.cl...
#> $ totexpk  <dbl> 7, 21, 0, 16, 5, 8, 17, ...
#> $ sex      <chr> "girl", "girl", "boy", "...
#> $ freelunk <chr> "no", "no", "yes", "no",...
#> $ race     <chr> "white", "black", "black...
#> $ schidkn  <dbl> 63, 20, 19, 69, 79, 5, 1...

还有基本的 R 中的summary()函数,它产生各种 R 对象的摘要。当一个数据框被传递到summary()中时,会提供一些基本的描述性统计数据:

summary(star)
#>     tmathssk        treadssk        classk             totexpk
#>  Min.   :320.0   Min.   :315.0   Length:5748        Min.   : 0.000
#>  1st Qu.:454.0   1st Qu.:414.0   Class :character   1st Qu.: 5.000
#>  Median :484.0   Median :433.0   Mode  :character   Median : 9.000
#>  Mean   :485.6   Mean   :436.7                      Mean   : 9.307
#>  3rd Qu.:513.0   3rd Qu.:453.0                      3rd Qu.:13.000
#>  Max.   :626.0   Max.   :627.0                      Max.   :27.000
#>      sex              freelunk             race
#>  Length:5748        Length:5748        Length:5748
#>  Class :character   Class :character   Class :character
#>  Mode  :character   Mode  :character   Mode  :character
#>     schidkn
#>  Min.   : 1.00
#>  1st Qu.:20.00
#>  Median :39.00
#>  Mean   :39.84
#>  3rd Qu.:60.00
#>  Max.   :80.00

许多其他包包括它们自己版本的描述性统计;我最喜欢的之一是psych中的describe()函数:

describe(star)
#>           vars    n   mean    sd median trimmed   mad min max range  skew
#> tmathssk     1 5748 485.65 47.77    484  483.20 44.48 320 626   306  0.47
#> treadssk     2 5748 436.74 31.77    433  433.80 28.17 315 627   312  1.34
#> classk*      3 5748   1.95  0.80      2    1.94  1.48   1   3     2  0.08
#> totexpk      4 5748   9.31  5.77      9    9.00  5.93   0  27    27  0.42
#> sex*         5 5748   1.49  0.50      1    1.48  0.00   1   2     1  0.06
#> freelunk*    6 5748   1.48  0.50      1    1.48  0.00   1   2     1  0.07
#> race*        7 5748   2.35  0.93      3    2.44  0.00   1   3     2 -0.75
#> schidkn      8 5748  39.84 22.96     39   39.76 29.65   1  80    79  0.04
#>           kurtosis   se
#> tmathssk      0.29 0.63
#> treadssk      3.83 0.42
#> classk*      -1.45 0.01
#> totexpk      -0.21 0.08
#> sex*         -2.00 0.01
#> freelunk*    -2.00 0.01
#> race*        -1.43 0.01
#> schidkn      -1.23 0.30

如果你对所有这些描述性统计数据不熟悉,你知道该做什么:查看函数的文档

数据框索引和子集

在本节的前面,我们创建了一个小的数据框roster,其中包含了四个人的姓名和身高。让我们用这个对象演示一些基本的数据框操作技巧。

在 Excel 中,你可以使用INDEX()函数来引用表格的行和列位置,如图 7-4 所示:

Excel 中的索引

图 7-4。Excel 表格上的INDEX()函数

这在 R 中也会类似地工作。我们将使用与索引向量相同的方括号表示法,但这次我们将同时引用行和列的位置:

# Third row, second column of data frame
roster[3, 2]
#> [1] 68

再次,我们可以使用:运算符来检索给定范围内的所有元素:

# Second through fourth rows, first through third columns
roster[2:4, 1:3]
#>    name height injured
#> 2  Jill     65    TRUE
#> 3 Billy     68   FALSE
#> 4 Susie     69   FALSE

也可以通过将其索引留空来选择整行或整列,或者使用c()函数来选择非连续的元素:

# Second and third rows only
roster[2:3,]
#>    name height injured
#> 2  Jill     65    TRUE
#> 3 Billy     68   FALSE

# First and third columns only
roster[, c(1,3)]
#>     name injured
#> 1   Jack   FALSE
#> 2   Jill    TRUE
#> 3  Billy   FALSE
#> 4  Susie   FALSE
#> 5 Johnny    TRUE

如果我们只想访问数据框的一列,我们可以使用$运算符。有趣的是,这会产生一个向量

roster$height
#> [1] 72 65 68 69 66
is.vector(roster$height)
#> [1] TRUE

这证实了数据框确实是一个等长向量的列表。

写入数据框

正如前面提到的,将数据读入 R 中,对其进行操作,然后将结果导出到其他地方是很典型的。要将数据框写入 .csv 文件,您可以使用 readr 中的 write_csv() 函数:

# Write roster data frame to csv
write_csv(roster, 'output/roster-output-r.csv')

如果你的工作目录设置为本书的伴随存储库,你应该会在 output 文件夹中找到这个文件。

不幸的是,readxl 包中没有包含将数据写入 Excel 工作簿的功能。不过,我们可以使用 writexl 及其 write_xlsx() 函数:

# Write roster data frame to csv
write_xlsx(roster, 'output/roster-output-r.xlsx')

结论

在本章中,你从单元素对象进展到更大的向量,最后到数据框。虽然我们在本书的剩余部分将会使用数据框,但记住它们是向量的集合,并且在很大程度上行为类似。接下来,你将学习如何分析、可视化和最终测试 R 数据框中的关系。

练习

完成以下练习,测试你在 R 中数据结构的知识:

  1. 创建一个包含五个元素的字符向量,然后访问这个向量的第一个和第四个元素。

  2. 创建两个长度为 4 的向量 xy,其中一个包含数值值,另一个包含逻辑值。将它们相乘并将结果传递给 z。结果是什么?

  3. 从 CRAN 下载 nycflights13 包。这个包中包含多少个数据集?

    • 其中一个数据集叫做 airports。打印这个数据框的前几行以及描述性统计信息。

    • 另一个叫做 weather。找到这个数据框的第 10 到 12 行以及第 4 到 7 列。将结果写入一个 .csv 文件和一个 Excel 工作簿中。

第八章:R 中的数据操作和可视化

美国统计学家罗纳德·蒂斯特德曾经说过:“原始数据就像生土豆一样,通常在使用之前需要清理。” 数据操作需要时间,如果你曾经做过以下工作,你可能感受到了痛苦:

  • 选择、删除或创建计算列

  • 排序或筛选行

  • 按组汇总和总结类别

  • 通过公共字段连接多个数据集

很可能,你在 Excel 中已经大量进行了所有这些操作,并且可能深入研究了VLOOKUP()和透视表等著名功能来完成它们。在本章中,你将学习这些技术的 R 语言等效方法,特别是借助dplyr

数据操作通常与可视化紧密相关:正如前文所述,人类在视觉处理信息方面非常擅长,因此这是评估数据集的一种绝佳方式。您将学习如何使用美丽的ggplot2包可视化数据,该包与dplyr一样属于tidyverse。这将使您在使用 R 探索和测试数据关系时站稳脚跟,这将在第九章中介绍。让我们通过调用相关的包开始。在本章中,我们还将使用书籍的伴随存储库中的star数据集(https://oreil.ly/lmZb7)。

library(tidyverse)
library(readxl)

star <- read_excel('datasets/star/star.xlsx')
head(star)
#> # A tibble: 6 x 8
#>   tmathssk treadssk classk            totexpk sex   freelunk race  schidkn
#>      <dbl>    <dbl> <chr>               <dbl> <chr> <chr>    <chr>   <dbl>
#> 1      473      447 small.class             7 girl  no       white      63
#> 2      536      450 small.class            21 girl  no       black      20
#> 3      463      439 regular.with.aide       0 boy   yes      black      19
#> 4      559      448 regular                16 boy   no       white      69
#> 5      489      447 small.class             5 boy   yes      white      79
#> 6      454      431 regular                 8 boy   yes      white       5

使用 dplyr 进行数据操作

dplyr是一个用于操作表格数据结构的流行包。它的许多函数,或者动词,工作方式类似,并且可以轻松地一起使用。表 8-1 列出了一些常见的dplyr函数及其用途;本章将详细介绍每一个。

表 8-1. dplyr经常使用的动词

功能 它的作用
select() 选择给定列
mutate() 根据现有列创建新列
rename() 对给定列进行重命名
arrange() 根据条件重新排序行
filter() 根据条件选择行
group_by() 根据给定列分组行
summarize() 汇总每个组的值
left_join() 将表 B 中匹配的记录连接到表 A;如果在表 B 中找不到匹配项,则结果为NA

为了简洁起见,我不会涵盖dplyr的所有函数,甚至不会涵盖我们所涵盖的函数的所有使用方式。要了解更多关于该包的信息,请查看 Hadley Wickham 和 Garrett Grolemund(O’Reilly)的《R for Data Science》。您还可以通过在 RStudio 中导航到帮助→ 速查表→ 使用dplyr进行数据转换来访问一张有用的速查表,总结dplyr的许多函数是如何一起工作的。

列操作

在 Excel 中选择和删除列通常需要隐藏或删除它们。这可能会导致审计或重现困难,因为隐藏的列容易被忽略,而删除的列不容易恢复。select() 函数可用于从 R 的数据框中选择指定列。对于 select(),以及每个这样的函数,第一个参数将是要处理的数据框。然后,提供其他参数来操作该数据框中的数据。例如,我们可以像这样从 star 中选择 tmathssktreadsskschidkin

select(star, tmathssk, treadssk, schidkn)
#> # A tibble: 5,748 x 3
#>    tmathssk treadssk schidkn
#>       <dbl>    <dbl>   <dbl>
#>  1      473      447      63
#>  2      536      450      20
#>  3      463      439      19
#>  4      559      448      69
#>  5      489      447      79
#>  6      454      431       5
#>  7      423      395      16
#>  8      500      451      56
#>  9      439      478      11
#> 10      528      455      66
#> # ... with 5,738 more rows

我们也可以在 select() 函数中使用 - 运算符来删除指定列:

select(star, -tmathssk, -treadssk, -schidkn)
#> # A tibble: 5,748 x 5
#>    classk            totexpk sex   freelunk race
#>    <chr>               <dbl> <chr> <chr>    <chr>
#>  1 small.class             7 girl  no       white
#>  2 small.class            21 girl  no       black
#>  3 regular.with.aide       0 boy   yes      black
#>  4 regular                16 boy   no       white
#>  5 small.class             5 boy   yes      white
#>  6 regular                 8 boy   yes      white
#>  7 regular.with.aide      17 girl  yes      black
#>  8 regular                 3 girl  no       white
#>  9 small.class            11 girl  no       black
#> 10 small.class            10 girl  no       white

在这里的一个更加优雅的替代方法是将所有不需要的列传递给一个向量,然后将其删除:

select(star, -c(tmathssk, treadssk, schidkn))
#> # A tibble: 5,748 x 5
#>    classk            totexpk sex   freelunk race
#>    <chr>               <dbl> <chr> <chr>    <chr>
#>  1 small.class             7 girl  no       white
#>  2 small.class            21 girl  no       black
#>  3 regular.with.aide       0 boy   yes      black
#>  4 regular                16 boy   no       white
#>  5 small.class             5 boy   yes      white
#>  6 regular                 8 boy   yes      white
#>  7 regular.with.aide      17 girl  yes      black
#>  8 regular                 3 girl  no       white
#>  9 small.class            11 girl  no       black
#> 10 small.class            10 girl  no       white

请记住,在前面的示例中,我们只是调用函数而没有实际将输出分配给对象。

对于 select() 的另一个简写方法是使用 : 运算符选择两个列之间的所有内容,包括两列。这一次,我将选择从 tmathssktotexpk 的所有内容,再将结果分配回 star

star <- select(star, tmathssk:totexpk)
head(star)
#> # A tibble: 6 x 4
#>   tmathssk treadssk classk            totexpk
#>      <dbl>    <dbl> <chr>               <dbl>
#> 1      473      447 small.class             7
#> 2      536      450 small.class            21
#> 3      463      439 regular.with.aide       0
#> 4      559      448 regular                16
#> 5      489      447 small.class             5
#> 6      454      431 regular                 8

您可能在 Excel 中创建了计算列;mutate() 将在 R 中执行相同的操作。让我们创建一个 new_column 列,其中包括阅读和数学分数的组合。使用 mutate(),我们首先提供新列的名称 first,然后是等号,最后是要使用的计算公式。我们可以在公式中引用其他列:

star <- mutate(star, new_column = tmathssk + treadssk)
head(star)
#> # A tibble: 6 x 5
#>   tmathssk treadssk classk            totexpk new_column
#>      <dbl>    <dbl> <chr>               <dbl>      <dbl>
#> 1      473      447 small.class             7        920
#> 2      536      450 small.class            21        986
#> 3      463      439 regular.with.aide       0        902
#> 4      559      448 regular                16       1007
#> 5      489      447 small.class             5        936
#> 6      454      431 regular                 8        885

mutate() 函数使得派生比如对数变换或者滞后变量等相对复杂的计算列变得更加容易;详见帮助文档获取更多信息。

new_column 对于总分并不是特别有帮助的名称。幸运的是,rename() 函数就像其名字所暗示的那样工作。我们将指定如何命名新列,取代旧列:

star <- rename(star, ttl_score = new_column)
head(star)
#> # A tibble: 6 x 5
#>   tmathssk treadssk classk            totexpk ttl_score
#>      <dbl>    <dbl> <chr>               <dbl>     <dbl>
#> 1      473      447 small.class             7       920
#> 2      536      450 small.class            21       986
#> 3      463      439 regular.with.aide       0       902
#> 4      559      448 regular                16      1007
#> 5      489      447 small.class             5       936
#> 6      454      431 regular                 8       885

逐行操作

到目前为止,我们一直在上操作。现在让我们专注于;具体来说是排序和过滤。在 Excel 中,我们可以使用自定义排序菜单按多列排序。例如,如果我们希望按升序排序此数据框的 classk,然后按 treadssk 排序。在 Excel 中进行此操作的菜单看起来会像 图 8-1。

Excel 中的自定义排序菜单

图 8-1. Excel 中的自定义排序菜单

我们可以通过使用 arrange() 函数在 dplyr 中复制这一过程,按照希望数据框排序的顺序包括每一列:

arrange(star, classk, treadssk)
#> # A tibble: 5,748 x 5
#>    tmathssk treadssk classk  totexpk ttl_score
#>       <dbl>    <dbl> <chr>     <dbl>     <dbl>
#>  1      320      315 regular       3       635
#>  2      365      346 regular       0       711
#>  3      384      358 regular      20       742
#>  4      384      358 regular       3       742
#>  5      320      360 regular       6       680
#>  6      423      376 regular      13       799
#>  7      418      378 regular      13       796
#>  8      392      378 regular      13       770
#>  9      392      378 regular       3       770
#> 10      399      380 regular       6       779
#> # ... with 5,738 more rows

如果我们希望某列按降序排序,可以将 desc() 函数传递给该列。

# Sort by classk descending, treadssk ascending
arrange(star, desc(classk), treadssk)
#> # A tibble: 5,748 x 5
#>    tmathssk treadssk classk      totexpk ttl_score
#>       <dbl>    <dbl> <chr>         <dbl>     <dbl>
#>  1      412      370 small.class      15       782
#>  2      434      376 small.class      11       810
#>  3      423      378 small.class       6       801
#>  4      405      378 small.class       8       783
#>  5      384      380 small.class      19       764
#>  6      405      380 small.class      15       785
#>  7      439      382 small.class       8       821
#>  8      384      384 small.class      10       768
#>  9      405      384 small.class       8       789
#> 10      423      384 small.class      21       807

Excel 表包括有助于根据给定条件过滤任何列的下拉菜单。要在 R 中过滤数据框,我们将使用名为 filter() 的函数。让我们过滤 star,仅保留 classk 等于 small.class 的记录。请记住,因为我们检查的是相等性而不是分配对象,所以在这里我们必须使用 == 而不是 =

filter(star, classk == 'small.class')
#> # A tibble: 1,733 x 5
#>    tmathssk treadssk classk      totexpk ttl_score
#>       <dbl>    <dbl> <chr>         <dbl>     <dbl>
#>  1      473      447 small.class       7       920
#>  2      536      450 small.class      21       986
#>  3      489      447 small.class       5       936
#>  4      439      478 small.class      11       917
#>  5      528      455 small.class      10       983
#>  6      559      474 small.class       0      1033
#>  7      494      424 small.class       6       918
#>  8      478      422 small.class       8       900
#>  9      602      456 small.class      14      1058
#> 10      439      418 small.class       8       857
#> # ... with 1,723 more rows

从 tibble 输出中我们可以看到,我们的 filter() 操作 影响了行数,而不是 列数。现在让我们找出 treadssk 至少为 500 的记录:

filter(star, treadssk >= 500)
#> # A tibble: 233 x 5
#>    tmathssk treadssk classk            totexpk ttl_score
#>       <dbl>    <dbl> <chr>               <dbl>     <dbl>
#>  1      559      522 regular                 8      1081
#>  2      536      507 regular.with.aide       3      1043
#>  3      547      565 regular.with.aide       9      1112
#>  4      513      503 small.class             7      1016
#>  5      559      605 regular.with.aide       5      1164
#>  6      559      554 regular                14      1113
#>  7      559      503 regular                10      1062
#>  8      602      518 regular                12      1120
#>  9      536      580 small.class            12      1116
#> 10      626      510 small.class            14      1136
#> # ... with 223 more rows

可以使用 & 运算符进行多条件过滤,“与” 和 | 运算符进行“或”运算。让我们将之前的两个条件用 & 结合起来:

# Get records where classk is small.class and
# treadssk is at least 500
filter(star, classk == 'small.class' & treadssk >= 500)
#> # A tibble: 84 x 5
#>    tmathssk treadssk classk      totexpk ttl_score
#>       <dbl>    <dbl> <chr>         <dbl>     <dbl>
#>  1      513      503 small.class       7      1016
#>  2      536      580 small.class      12      1116
#>  3      626      510 small.class      14      1136
#>  4      602      518 small.class       3      1120
#>  5      626      565 small.class      14      1191
#>  6      602      503 small.class      14      1105
#>  7      626      538 small.class      13      1164
#>  8      500      580 small.class       8      1080
#>  9      489      565 small.class      19      1054
#> 10      576      545 small.class      19      1121
#> # ... with 74 more rows

聚合和连接数据

我喜欢称透视表为 Excel 的“WD-40”,因为它们允许我们将数据“旋转”到不同的方向,以便进行简单的分析。例如,让我们重新创建 Figure 8-2 中显示的按班级规模的平均数学分数的透视表:

Excel 透视表的工作原理

Figure 8-2. Excel 透视表的工作原理

如 Figure 8-2 所示,此透视表包括两个要素。首先,我按变量 classk 进行了数据聚合。然后,我通过计算 tmathssk 的平均值进行了总结。在 R 中,这些是离散的步骤,使用了不同的 dplyr 函数。首先,我们将使用 group_by() 进行数据聚合。我们的输出包括一行 # Groups: classk [3],表示 star_grouped 根据 classk 变量分为三组:

star_grouped <- group_by(star, classk)
head(star_grouped)
#> # A tibble: 6 x 5
#> # Groups:   classk [3]
#>   tmathssk treadssk classk            totexpk ttl_score
#>      <dbl>    <dbl> <chr>               <dbl>     <dbl>
#> 1      473      447 small.class             7       920
#> 2      536      450 small.class            21       986
#> 3      463      439 regular.with.aide       0       902
#> 4      559      448 regular                16      1007
#> 5      489      447 small.class             5       936
#> 6      454      431 regular                 8       885

我们通过一个变量进行了 分组,现在让我们用另一个变量来 汇总 它,使用 summarize() 函数(summarise() 也可以)。在这里,我们将指定结果列的名称以及如何计算它。Table 8-2 列出了一些常见的聚合函数。

Table 8-2. dplyr 的有用聚合函数

函数 聚合类型
sum() 总和
n() 计数
mean() 平均值
max() 最高值
min() 最低值
sd() 标准差

我们可以通过在我们的分组数据框上运行 summarize() 来获取按班级规模的平均数学分数:

summarize(star_grouped, avg_math = mean(tmathssk))
#> `summarise()` ungrouping output (override with `.groups` argument)
#> # A tibble: 3 x 2
#>   classk            avg_math
#>   <chr>                <dbl>
#> 1 regular               483.
#> 2 regular.with.aide     483.
#> 3 small.class           491.

`summarise()` ungrouping output 错误是一个警告,说明您通过聚合操作取消了分组 tibble 的分组。减去一些格式上的差异,我们得到了与 Figure 8-2 相同的结果。

如果透视表是 Excel 的 WD-40,那么 VLOOKUP() 就是胶带,可以轻松地从多个来源合并数据。在我们最初的 star 数据集中,schidkin 是一个学区指示器。我们在本章早些时候删除了此列,所以让我们重新读取它。但是,如果除了指示器编号外,我们还想知道这些区域的 名称 怎么办?幸运的是,书库中的 districts.csv 包含了这些信息,所以让我们将它们一起读取,并制定一个组合策略:

star <- read_excel('datasets/star/star.xlsx')
head(star)
#> # A tibble: 6 x 8
#>   tmathssk treadssk classk            totexpk sex   freelunk race  schidkn
#>      <dbl>    <dbl> <chr>               <dbl> <chr> <chr>    <chr>   <dbl>
#> 1      473      447 small.class             7 girl  no       white      63
#> 2      536      450 small.class            21 girl  no       black      20
#> 3      463      439 regular.with.aide       0 boy   yes      black      19
#> 4      559      448 regular                16 boy   no       white      69
#> 5      489      447 small.class             5 boy   yes      white      79
#> 6      454      431 regular                 8 boy   yes      white       5

districts <- read_csv('datasets/star/districts.csv')

#> -- Column specification -----------------------------------------------------
#> cols(
#>   schidkn = col_double(),
#>   school_name = col_character(),
#>   county = col_character()
#> )

head(districts)
#> # A tibble: 6 x 3
#>   schidkn school_name     county
#>     <dbl> <chr>           <chr>
#> 1       1 Rosalia         New Liberty
#> 2       2 Montgomeryville Topton
#> 3       3 Davy            Wahpeton
#> 4       4 Steelton        Palestine
#> 5       6 Tolchester      Sattley
#> 6       7 Cahokia         Sattley

看起来需要的类似于VLOOKUP()的功能:我们希望从districts中“读入”school_name(可能还包括county)变量到star,给定共享的schidkn变量。要在 R 中实现这一点,我们将使用joins的方法,它来自关系数据库,这是在第五章中讨论过的主题。与VLOOKUP()最接近的是左外连接,在dplyr中可以使用left_join()函数实现。我们将首先提供“基本”表(star),然后是“查找”表(districts)。该函数将在star的每条记录中查找并返回districts中的匹配项,如果没有找到匹配项则返回NA。为了减少控制台输出的过多信息,我将仅保留来自star的一些列:

# Left outer join star on districts
left_join(select(star, schidkn, tmathssk, treadssk), districts)
#> Joining, by = "schidkn"
#> # A tibble: 5,748 x 5
#>    schidkn tmathssk treadssk school_name     county
#>      <dbl>    <dbl>    <dbl> <chr>           <chr>
#>  1      63      473      447 Ridgeville      New Liberty
#>  2      20      536      450 South Heights   Selmont
#>  3      19      463      439 Bunnlevel       Sattley
#>  4      69      559      448 Hokah           Gallipolis
#>  5      79      489      447 Lake Mathews    Sugar Mountain
#>  6       5      454      431 NA              NA
#>  7      16      423      395 Calimesa        Selmont
#>  8      56      500      451 Lincoln Heights Topton
#>  9      11      439      478 Moose Lake      Imbery
#> 10      66      528      455 Siglerville     Summit Hill
#> # ... with 5,738 more rows

left_join()非常智能:它知道在schidkn上进行连接,并“查找”不仅school_name还包括county。要了解更多关于数据连接的信息,请查看帮助文档。

在 R 中,缺失的观察结果表示为特殊值NA。例如,似乎没有找到第 5 区的地区名的匹配项。在VLOOKUP()中,这将导致#N/A错误。NA意味着观察结果等于零,只是其值缺失。在编程 R 时,你可能会看到其他特殊值,例如NaNNULL;要了解更多信息,请查看帮助文档。

dplyr 与管道操作符(%>%)

正如你所看到的,dplyr函数对于任何曾在 Excel 中处理数据的人来说都非常强大且直观。并且,任何曾处理过数据的人都知道,仅需一步就能准备好数据是非常罕见的。例如,你可能想用star进行典型的数据分析任务:

按班级类型查找平均阅读分数,从高到低排序。

根据我们对数据处理的了解,我们可以将其分解为三个明确的步骤:

  1. 按班级类型分组我们的数据。

  2. 找到每个组的平均阅读分数。

  3. 将这些结果从高到低排序。

我们可以在dplyr中执行类似以下操作:

star_grouped <- group_by(star, classk)
star_avg_reading <- summarize(star_grouped, avg_reading = mean(treadssk))
#> `summarise()` ungrouping output (override with `.groups` argument)
#>
star_avg_reading_sorted <- arrange(star_avg_reading, desc(avg_reading))
star_avg_reading_sorted
#>
#> # A tibble: 3 x 2
#>   classk            avg_reading
#>   <chr>                   <dbl>
#> 1 small.class              441.
#> 2 regular.with.aide        435.
#> 3 regular                  435.

这让我们得到了一个答案,但需要相当多的步骤,并且很难跟踪各种函数和对象名称。另一种方法是使用管道运算符%>%将这些函数链接在一起。这使我们能够将一个函数的输出直接传递给另一个函数的输入,因此我们能够避免不断重命名我们的输入和输出。此运算符的默认键盘快捷键为 Windows 下的 Ctrl+Shift+M,Mac 下的 Cmd-Shift-M。

让我们重新创建上述步骤,这次使用管道操作符。我们将每个函数放在自己的一行上,用%>%将它们组合起来。虽然不必将每个步骤放在自己的行上,但通常出于可读性考虑。使用管道操作符时,无需突出显示整个代码块即可运行它;只需将光标放在所需选择中的任何位置并执行即可:

 star %>%
   group_by(classk) %>%
   summarise(avg_reading = mean(treadssk)) %>%
   arrange(desc(avg_reading))
#> `summarise()` ungrouping output (override with `.groups` argument)
#> # A tibble: 3 x 2
#>   classk            avg_reading
#>   <chr>                   <dbl>
#> 1 small.class              441.
#> 2 regular.with.aide        435.
#> 3 regular                  435.

起初不再在每个函数中显式包含数据源作为参数可能会令人困惑。但是将最后一个代码块与之前的比较,你会看到这种方法效率有多高。此外,管道运算符可以与非dplyr函数一起使用。例如,让我们只将操作结果的前几行分配给head()

# Average math and reading score
# for each school district
star %>%
   group_by(schidkn) %>%
   summarise(avg_read = mean(treadssk), avg_math = mean(tmathssk)) %>%
   arrange(schidkn) %>%
   head()
#> `summarise()` ungrouping output (override with `.groups` argument)
#> # A tibble: 6 x 3
#>   schidkn avg_read avg_math
#>     <dbl>    <dbl>    <dbl>
#> 1       1     444\.     492.
#> 2       2     407\.     451.
#> 3       3     441      491.
#> 4       4     422\.     468.
#> 5       5     428\.     460.
#> 6       6     428\.     470.

使用 tidyr 重塑数据

尽管在 R 中,group_by()summarize()与 Excel 中的透视表类似,但这些函数不能做到 Excel 透视表所能做的一切。如果你想要重塑数据,或者改变行和列的设置怎么办?例如,我们的star数据框有两列分别用于数学和阅读成绩,tmathssktreadssk。我想将它们合并为一个名为score的列,并且用另一个名为test_type的列指示每个观察是数学还是阅读。我还想保留学校指示符schidkn作为分析的一部分。

图 8-3 展示了在 Excel 中的样子;请注意,我将Values字段从tmathssktreadssk改名为mathreading。如果你想进一步查看此透视表,它在书籍仓库中作为ch-8.xlsx可供查阅。在这里我再次使用了索引列;否则,透视表将尝试通过schidkn“汇总”所有值。

Excel 中的重塑

图 8-3. 在 Excel 中重塑star

我们可以使用tidyr,这是tidyverse核心包,来重塑star。在 R 中重塑时,添加一个索引列也会很有帮助,就像在 Excel 中一样。我们可以用row_number()函数来创建一个:

star_pivot <- star %>%
                select(c(schidkn, treadssk, tmathssk)) %>%
                mutate(id = row_number())

要重塑数据框,我们将使用tidyr中的pivot_longer()pivot_wider()。在你的脑海中和图 8-3 中考虑一下,如果我们将tmathssktreadssk的分数合并到一列中,数据集会发生什么变化。数据集会变得更长,因为我们在这里添加了行。要使用pivot_longer(),我们将使用cols参数指定要拉长的列,并使用values_to来命名结果列。我们还将使用names_to来命名指示每个分数是数学还是阅读的列:

star_long <- star_pivot %>%
                 pivot_longer(cols = c(tmathssk, treadssk),
                              values_to = 'score', names_to = 'test_type')
head(star_long)
#> # A tibble: 6 x 4
#>   schidkn    id test_type score
#>     <dbl> <int> <chr>     <dbl>
#> 1      63     1 tmathssk    473
#> 2      63     1 treadssk    447
#> 3      20     2 tmathssk    536
#> 4      20     2 treadssk    450
#> 5      19     3 tmathssk    463
#> 6      19     3 treadssk    439

很好。但是是否有一种方法可以将tmathssktreadssk重命名为mathreading呢?有的,使用recode(),这是dplyr中另一个有用的函数,可以与mutate()一起使用。recode()与包中的其他函数有些不同,因为我们在等号之前包括“旧”值的名称,然后是新值。dplyr中的distinct()函数将确认所有行都已命名为mathreading

# Rename tmathssk and treadssk as math and reading
star_long <- star_long %>%
   mutate(test_type = recode(test_type,
                            'tmathssk' = 'math', 'treadssk' = 'reading'))

distinct(star_long, test_type)
#> # A tibble: 2 x 1
#>   test_type
#>   <chr>
#> 1 math
#> 2 reading

现在我们的数据框已经延长了,我们可以 现在我们的数据框已经拉长,我们可以使用pivot_wider()将其扩展回来。这次,我将指定哪些列包含其行中的值应该作为列,使用values_from,以及结果列的名称使用names_from

star_wide <- star_long %>%
                pivot_wider(values_from = 'score', names_from = 'test_type')
head(star_wide)
#> # A tibble: 6 x 4
#>   schidkn    id  math reading
#>     <dbl> <int> <dbl>   <dbl>
#> 1      63     1   473     447
#> 2      20     2   536     450
#> 3      19     3   463     439
#> 4      69     4   559     448
#> 5      79     5   489     447
#> 6       5     6   454     431

数据重塑在 R 中是相对复杂的操作,所以在疑惑时,问问自己:我是在将数据做宽还是做长?在数据透视表中我该怎么做? 如果你能逻辑地走通实现所需最终状态的过程,编码就会容易得多。

使用 ggplot2 进行数据可视化

dplyr还能做很多帮助我们操作数据的事情,但现在让我们将注意力转向数据可视化。具体来说,我们将关注另一个tidyverse包,ggplot2。该包的名称和模型是根据计算机科学家利兰·威尔金森(Leland Wilkinson)设计的“图形语法”来命名和建模的,ggplot2提供了一种构建图形的有序方法。这种结构模仿了语音元素如何结合在一起形成句子,因此被称为“图形语法”。

我将在这里介绍一些ggplot2的基本元素和图表类型。更多关于该包的信息,请查阅包的原作者哈德利·威克汉姆(Hadley Wickham)所著的ggplot2: Elegant Graphics for Data Analysis(Springer)。你还可以通过在 RStudio 中导航到 Help → Cheatsheets → Data Visualization with ggplot2 来访问一个有用的速查表。一些ggplot2的基本元素可以在表 8-3 中找到。还有其他元素可用;更多信息,请查看前面提到的资源。

表 8-3。ggplot2的基础元素

元素 描述
data 数据源
aes 从数据到视觉属性(x 轴和 y 轴、颜色、大小等)的美学映射
geom 图中观察到的几何对象类型(线条、条形、点等)

让我们从可视化classk每个级别的观测数量作为条形图开始。我们将从ggplot()函数开始,指定表 8-3 中的三个元素:

ggplot(data = star, ![1](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/adv-anls/img/1.png)
          aes(x = classk)) + ![2](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/adv-anls/img/2.png)
   geom_bar() ![3](https://gitee.com/OpenDocCN/ibooker-ds-zh/raw/master/docs/adv-anls/img/3.png)

1

数据源通过data参数指定。

2

从数据到可视化的美学映射通过aes()函数指定。在这里,我们要求classk映射到最终图表的 x 轴。

3

我们通过geom_bar()函数根据指定的数据和美学映射绘制一个几何对象。结果显示在图 8-4 中。

计数图

图 8-4。ggplot2中的条形图

类似于管道操作符,不必将每一层图形放在单独的行中,但通常为了可读性而更倾向于这样做。还可以通过将光标放置在代码块的任何位置并运行来执行整个绘图。

因其模块化方法,使用 ggplot2 迭代可视化很容易。例如,我们可以通过将 x 映射更改为 treadssk 并使用 geom_histogram() 绘制结果,将我们的绘图切换为直方图。这导致了图 8-5 中显示的直方图:

ggplot(data = star, aes(x = treadssk)) +
  geom_histogram()

#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

直方图

图 8-5. ggplot2 中的直方图

还有许多方法可以自定义 ggplot2 绘图。例如,您可能已经注意到,上一个绘图的输出消息指出直方图使用了 30 个柱。让我们将该数字更改为 25,并在 geom_histogram() 中使用粉色填充以及几个其他参数。这导致了图 8-6 中显示的直方图:

ggplot(data = star, aes(x = treadssk)) +
  geom_histogram(bins = 25, fill = 'pink')

自定义直方图

图 8-6. ggplot2 中的自定义直方图

使用 geom_boxplot() 创建箱线图,如 图 8-7 所示:

ggplot(data = star, aes(x = treadssk)) +
  geom_boxplot()

箱线图

图 8-7. 箱线图

到目前为止的任何情况下,我们都可以通过将兴趣变量包含在 y 映射中而不是 x 映射中来“翻转”图形。让我们尝试在我们的箱线图中进行此操作。图 8-8 展示了以下操作的结果:

ggplot(data = star, aes(y = treadssk)) +
  geom_boxplot()

翻转的箱线图

图 8-8. “翻转”的箱线图

现在让我们通过将 classk 映射到 x 轴和 treadssk 映射到 y 轴,为每个班级大小级别制作一个箱线图。这导致了图 8-9 中显示的箱线图:

ggplot(data = star, aes(x = classk, y = treadssk)) +
  geom_boxplot()

同样,我们可以使用 geom_point() 在 x 和 y 轴上绘制 tmathssktreadssk 的关系,作为散点图。这导致了图 8-10 中显示的结果:

ggplot(data = star, aes(x = tmathssk, y = treadssk)) +
  geom_point()

分组箱线图

图 8-9. 按组绘制的箱线图

散点图

图 8-10. 散点图

我们可以使用一些额外的 ggplot2 函数在 x 和 y 轴上叠加标签,以及一个绘图标题。图 8-11 展示了结果:

ggplot(data = star, aes(x = tmathssk, y = treadssk)) +
  geom_point() +
  xlab('Math score') + ylab('Reading score') +
  ggtitle('Math score versus reading score')

具有自定义标签和标题的散点图

图 8-11. 带有自定义轴标签和标题的散点图

结论

dplyrggplot2 还可以做更多事情,但这已足以让您开始真正的任务:探索和测试数据中的关系。这将是 第九章 的重点。

练习

书籍库datasetscensus 子文件夹中有两个文件,census.csvcensus-divisions.csv。将它们读入 R 并执行以下操作:

  1. 按地区升序、部门升序和人口降序对数据进行排序(您需要合并数据集来执行此操作)。将结果写入 Excel 工作表。

  2. 从合并数据集中删除邮政编码字段。

  3. 创建一个名为density的新列,计算人口除以土地面积的结果。

  4. 可视化 2015 年所有观测点的土地面积与人口之间的关系。

  5. 查找 2015 年每个地区的总人口。

  6. 创建一个包含州名和人口的表格,每年 2010 年至 2015 年的人口分别放在一个单独的列中。

第九章:顶点:数据分析的 R 语言

在本章中,我们将应用在 R 中学到的关于数据分析和可视化的知识来探索和测试mpg数据集中的关系。在这里,您将学习到几种新的 R 技术,包括如何进行 t 检验和线性回归。我们将从调用必要的包开始,从书籍存储库的datasets文件夹中读取mpg.csv,并选择感兴趣的列。迄今为止,我们还没有在本书中使用tidymodels,因此您可能需要安装它。

library(tidyverse)
library(psych)
library(tidymodels)

# Read in the data, select only the columns we need
mpg <- read_csv('datasets/mpg/mpg.csv') %>%
  select(mpg, weight, horsepower, origin, cylinders)

#> -- Column specification -----------------------------------------------------
#> cols(
#>  mpg = col_double(),
#>  cylinders = col_double(),
#>  displacement = col_double(),
#>  horsepower = col_double(),
#>  weight = col_double(),
#>  acceleration = col_double(),
#>  model.year = col_double(),
#>  origin = col_character(),
#>  car.name = col_character()
#> )

head(mpg)
#> # A tibble: 6 x 5
#>     mpg weight horsepower origin cylinders
#>   <dbl>  <dbl>      <dbl> <chr>      <dbl>
#> 1    18   3504        130 USA            8
#> 2    15   3693        165 USA            8
#> 3    18   3436        150 USA            8
#> 4    16   3433        150 USA            8
#> 5    17   3449        140 USA            8
#> 6    15   4341        198 USA            8

探索性数据分析

探索性数据分析是探索数据时的良好起点。我们将使用psychdescribe()函数来做这件事:

describe(mpg)
#>            vars   n    mean     sd  median trimmed    mad  min
#> mpg           1 392   23.45   7.81   22.75   22.99   8.60    9
#> weight        2 392 2977.58 849.40 2803.50 2916.94 948.12 1613
#> horsepower    3 392  104.47  38.49   93.50   99.82  28.91   46
#> origin*       4 392    2.42   0.81    3.00    2.53   0.00    1
#> cylinders     5 392    5.47   1.71    4.00    5.35   0.00    3
#>               max  range  skew kurtosis    se
#> mpg          46.6   37.6  0.45    -0.54  0.39
#> weight     5140.0 3527.0  0.52    -0.83 42.90
#> horsepower  230.0  184.0  1.08     0.65  1.94
#> origin*       3.0    2.0 -0.91    -0.86  0.04
#> cylinders     8.0    5.0  0.50    -1.40  0.09

因为origin是一个分类变量,我们应该谨慎解释其描述性统计。 (实际上,psych使用*来表示此警告。)然而,我们可以安全地分析其单向频率表,我们将使用一个新的dplyr函数count()来完成:

mpg %>%
  count(origin)
#> # A tibble: 3 x 2
#>   origin     n
#>   <chr>  <int>
#> 1 Asia      79
#> 2 Europe    68
#> 3 USA      245

我们从结果中的计数列n中得知,虽然大多数观察值都是美国汽车,但亚洲和欧洲汽车的观察值仍可能代表它们的子群体。

让我们进一步通过cylinders来细分这些计数,以得出一个二维频率表。我将结合count()pivot_wider()来将cylinders显示在列中:

mpg %>%
  count(origin, cylinders) %>%
  pivot_wider(values_from = n, names_from = cylinders)
#> # A tibble: 3 x 6
#>   origin   `3`   `4`   `6`   `5`   `8`
#>   <chr>  <int> <int> <int> <int> <int>
#> 1 Asia       4    69     6    NA    NA
#> 2 Europe    NA    61     4     3    NA
#> 3 USA       NA    69    73    NA   103

请记住,在 R 中,NA表示缺失值,这种情况是因为某些交叉部分没有观察到。

并非许多汽车拥有三缸或五缸引擎,只有美国汽车拥有八缸引擎。在分析数据时,数据集不平衡是常见的,其中某些水平的观察数量不成比例。通常需要特殊技术来对这类数据进行建模。要了解更多关于处理不平衡数据的信息,请查看彼得·布鲁斯等人的《数据科学实用统计》,第 2 版(O’Reilly)。

我们还可以找出每个origin水平的描述性统计。首先,我们将使用select()选择感兴趣的变量,然后可以使用psychdescribeBy()函数,将groupBy设置为origin

mpg %>%
  select(mpg, origin) %>%
  describeBy(group = 'origin')

#>  Descriptive statistics by group
#> origin: Asia
        vars  n  mean   sd median trimmed  mad min  max range
#> mpg        1 79 30.45 6.09   31.6   30.47 6.52  18 46.6  28.6
#> origin*    2 79  1.00 0.00    1.0    1.00 0.00   1  1.0   0.0
        skew kurtosis   se
#> mpg     0.01    -0.39 0.69
#> origin*  NaN      NaN 0.00

#> origin: Europe
        vars  n mean   sd median trimmed  mad  min  max range
#> mpg        1 68 27.6 6.58     26    27.1 5.78 16.2 44.3  28.1
#> origin*    2 68  1.0 0.00      1     1.0 0.00  1.0  1.0   0.0
        skew kurtosis  se
#> mpg     0.73     0.31 0.8
#> origin*  NaN      NaN 0.0

#> origin: USA
        vars   n  mean   sd median trimmed  mad min max range
#> mpg        1 245 20.03 6.44   18.5   19.37 6.67   9  39    30
#> origin*    2 245  1.00 0.00    1.0    1.00 0.00   1   1     0
        skew kurtosis   se
#> mpg     0.83     0.03 0.41
#> origin*  NaN      NaN 0.00

让我们进一步了解originmpg之间的潜在关系。我们将从可视化mpg分布的直方图开始,如图 9-1 所示:

ggplot(data = mpg, aes(x = mpg)) +
  geom_histogram()
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Histogram

图 9-1。mpg的分布

现在,我们可以集中于通过origin来可视化mpg的分布。将所有三个origin水平叠加在一个直方图上可能会显得杂乱,因此类似于图 9-2 所示的箱线图可能更合适:

ggplot(data = mpg, aes(x = origin, y = mpg)) +
  geom_boxplot()

Boxplot

图 9-2。mpgorigin分布

如果我们更喜欢将它们可视化为直方图,而不是搞乱,我们可以在 R 中使用 facet 图来实现。使用 facet_wrap()ggplot2 图分成子图或 facet。我们将从 ~ 或波浪号运算符开始,后面是变量名。当您在 R 中看到波浪号时,请将其视为“按照”的意思。例如,我们在这里按 origin 进行分面直方图,结果显示在 图 9-3 中:

# Histogram of mpg, facted by origin
ggplot(data = mpg, aes(x = mpg)) +
  geom_histogram() +
  facet_grid(~ origin)
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

分面直方图

图 9-3. mpgorigin 的分布

假设检验

您可以继续使用这些方法探索数据,但让我们进入假设检验。特别是,我想知道美国车和欧洲车之间的里程是否有显著差异。让我们创建一个包含这些观测值的新数据框架;我们将使用它进行 t 检验。

mpg_filtered <- filter(mpg, origin=='USA' | origin=='Europe')

独立样本 t 检验

R 包含了一个 t.test() 函数:我们需要用 data 参数指定数据的来源,并且还需要指定要测试的 公式。为此,我们将使用 ~ 运算符来设置独立和因变量之间的关系。因变量位于 ~ 的前面,后面是独立变量。同样,您可以将此符号解释为分析 mpg 通过 origin 的效果。

# Dependent variable ~ ("by") independent variable
t.test(mpg ~ origin, data = mpg_filtered)
#> 	Welch Two Sample t-test
#>
#>     data:  mpg by origin
#>     t = 8.4311, df = 105.32, p-value = 1.93e-13
#>     alternative hypothesis: true difference in means is not equal to 0
#>     95 percent confidence interval:
#>     5.789361 9.349583
#>     sample estimates:
#>     mean in group Europe    mean in group USA
#>                 27.60294             20.03347

R 程序甚至明确说明了我们的备择假设是什么,并且在 p 值的同时还包括了置信区间(你可以看出这个程序是为统计分析而建立的)。根据 p 值,我们将拒绝原假设;似乎有证据表明均值存在差异。

现在让我们将注意力转向连续变量之间的关系。首先,我们将使用基本 R 中的 cor() 函数打印相关矩阵。我们仅针对 mpg 中的连续变量执行此操作:

select(mpg, mpg:horsepower) %>%
  cor()
#>                   mpg     weight horsepower
#> mpg         1.0000000 -0.8322442 -0.7784268
#> weight     -0.8322442  1.0000000  0.8645377
#> horsepower -0.7784268  0.8645377  1.0000000

我们可以使用 ggplot2 来可视化,例如体重和里程之间的关系,如 图 9-4 所示:

ggplot(data = mpg, aes(x = weight,y = mpg)) +
  geom_point() + xlab('weight (pounds)') +
  ylab('mileage (mpg)') + ggtitle('Relationship between weight and mileage')

散点图

图 9-4. weightmpg 的散点图

或者,我们可以使用基本 R 中的 pairs() 函数生成所有变量组合的成对图,类似于相关矩阵的布局。图 9-5 是 mpg 中选定变量的成对图:

select(mpg, mpg:horsepower) %>%
  pairs()

成对图

图 9-5. 成对图

线性回归

现在我们准备进行线性回归,使用基本的 R lm() 函数(这是线性模型的缩写)。与 t.test() 类似,我们会指定数据集和一个公式。线性回归返回的输出比 t 检验要多一些,因此通常先将结果分配给 R 中的一个新对象,然后分别探索其各个元素。特别是 summary() 函数提供了回归模型的有用概览:

mpg_regression <- lm(mpg ~ weight, data = mpg)
summary(mpg_regression)

#>     Call:
#>     lm(formula = mpg ~ weight, data = mpg)
#>
#>     Residuals:
#>         Min       1Q   Median       3Q      Max
#>     -11.9736  -2.7556  -0.3358   2.1379  16.5194
#>
#>     Coefficients:
#>                 Estimate Std. Error t value Pr(>|t|)
#>     (Intercept) 46.216524   0.798673   57.87   <2e-16 ***
#>     weight      -0.007647   0.000258  -29.64   <2e-16 ***
#>     ---
#>     Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#>
#>     Residual standard error: 4.333 on 390 degrees of freedom
#>     Multiple R-squared:  0.6926,	Adjusted R-squared:  0.6918
#>     F-statistic: 878.8 on 1 and 390 DF,  p-value: < 2.2e-16

此输出应该看起来很熟悉。在这里,您将看到系数、p 值和 R 平方等指标。再次强调,车重对里程的影响似乎是显著的。

最后但同样重要的是,我们可以在散点图上拟合这条回归线,方法是在我们的ggplot()函数中包含geom_smooth(),并将method设置为lm。这将生成图 9-6:

ggplot(data = mpg, aes(x = weight, y = mpg)) +
  geom_point() + xlab('weight (pounds)') +
  ylab('mileage (mpg)') + ggtitle('Relationship between weight and mileage') +
  geom_smooth(method = lm)
#> `geom_smooth()` using formula 'y ~ x'

适合的散点图

图 9-6. mpg数据集中weightmpg的散点图及拟合回归线

训练/测试拆分和验证

第五章简要回顾了机器学习如何与更广泛的数据处理相关联。一种由机器学习推广的技术是您可能在数据分析工作中遇到的训练/测试拆分。其核心思想是在数据的子集上训练模型,然后在另一个子集上测试模型。这确保了模型不仅适用于特定抽样观测,而是能够推广到更广泛的人群。数据科学家通常特别关注模型在测试数据上进行预测的效果。

让我们在 R 中将我们的mpg数据集拆分,对部分数据进行线性回归模型训练,然后在剩余数据上进行测试。为此,我们将使用tidymodels包。虽然不是tidyverse的一部分,但该包是基于相同原则构建的,因此与之兼容。

在第二章中,您可能还记得,由于我们使用的是随机数,您在工作簿中看到的结果与书中记录的不同。因为我们将再次随机拆分我们的数据集,我们可能会遇到同样的问题。为了避免这种情况,我们可以设置 R 的随机数发生器的种子,这样每次生成的随机数序列都是相同的。这可以通过set.seed()函数来实现。您可以将其设置为任何数字;通常使用1234

set.seed(1234)

要开始拆分,我们可以使用名为initial_split()的适当命名的函数;然后,我们将使用training()testing()函数将数据子集分为训练和测试数据集。

mpg_split <- initial_split(mpg)
mpg_train <- training(mpg_split)
mpg_test <- testing(mpg_split)

默认情况下,tidymodels将数据的观测分成两组:75%的观测分配给训练组,其余分配给测试组。我们可以使用基本 R 中的dim()函数来确认每个数据集的行数和列数:

dim(mpg_train)
#> [1] 294   5
dim(mpg_test)
#> [1] 98  5

在 294 个和 98 个观测值中,我们的训练和测试样本大小应足够大,以进行反映性统计推断。虽然这在机器学习中使用的大数据集中很少考虑,但当拆分数据时,充足的样本量可能是一个限制因素。

可以将数据拆分为比例为 75/25 之外的其他比例,使用拆分数据的特殊技术等等。有关更多信息,请查阅tidymodels文档;在您对回归分析更加熟悉之前,这些默认值都是可以的。

要构建我们的训练模型,我们将首先使用linear_reg()函数指定模型的类型,然后拟合它。fit()函数的输入对您应该很熟悉,除了这一次我们仅使用了mpg的训练子集。

# Specify what kind of model this is
lm_spec <- linear_reg()

# Fit the model to the data
lm_fit <- lm_spec %>%
  fit(mpg ~ weight, data = mpg_train)
#> Warning message:
#> Engine set to `lm`.

您将从控制台输出中看到,来自基本 R 的lm()函数,您以前使用过的函数,被用作拟合模型的引擎

我们可以使用tidy()函数获取训练模型的系数和 p 值,并使用glance()获取其性能指标(如 R 平方)。

tidy(lm_fit)
#> # A tibble: 2 x 5
#>   term        estimate std.error statistic   p.value
#>   <chr>          <dbl>     <dbl>     <dbl>     <dbl>
#> 1 (Intercept) 47.3      0.894         52.9 1.37e-151
#> 2 weight      -0.00795  0.000290     -27.5 6.84e- 83
#>
glance(lm_fit)
#> # A tibble: 1 x 12
#>   r.squared adj.r.squared sigma statistic  p.value    df logLik   AIC
#>       <dbl>         <dbl> <dbl>     <dbl>    <dbl> <dbl>  <dbl> <dbl>
#> 1     0.721         0.720  4.23      754\. 6.84e-83     1  -840\. 1687.
#> # ... with 4 more variables: BIC <dbl>, deviance <dbl>,
#> #   df.residual <int>, nobs <int>

这很好,但是我们真正想知道的是当我们将该模型应用于新数据集时,该模型的表现如何;这就是测试拆分的作用。要在mpg_test上进行预测,我们将使用predict()函数。我还将使用bind_cols()将预测的 Y 值列添加到数据框中。此列默认将被称为.pred

mpg_results <- predict(lm_fit, new_data = mpg_test) %>%
  bind_cols(mpg_test)

mpg_results
#> # A tibble: 98 x 6
#>    .pred   mpg weight horsepower origin cylinders
#>    <dbl> <dbl>  <dbl>      <dbl> <chr>      <dbl>
#>  1  20.0    16   3433        150 USA            8
#>  2  16.7    15   3850        190 USA            8
#>  3  25.2    18   2774         97 USA            6
#>  4  30.3    27   2130         88 Asia           4
#>  5  28.0    24   2430         90 Europe         4
#>  6  21.0    19   3302         88 USA            6
#>  7  14.2    14   4154        153 USA            8
#>  8  14.7    14   4096        150 USA            8
#>  9  29.6    23   2220         86 USA            4
#> 10  29.2    24   2278         95 Asia           4
#> # ... with 88 more rows

现在我们已经将模型应用于这些新数据,让我们评估一下它的性能。例如,我们可以使用rsq()函数找到它的 R 平方。从我们的mpg_results数据框中,我们需要使用truth参数指定包含实际 Y 值的列,以及使用estimate列指定预测值。

rsq(data = mpg_results, truth = mpg, estimate = .pred)
#> # A tibble: 1 x 3
#>   .metric .estimator .estimate
#>   <chr>   <chr>          <dbl>
#> 1 rsq     standard       0.606

在 R 平方为 60.6%时,从训练数据集得出的模型解释了测试数据中相当数量的变异。

另一个常见的评估指标是均方根误差(RMSE)。您在第四章中了解到了残差的概念,即实际值与预测值之间的差异;RMSE 是残差的标准偏差,因此是误差传播方式的估计值。rmse()函数返回 RMSE。

rmse(data = mpg_results, truth = mpg, estimate = .pred)
#> # A tibble: 1 x 3
#>   .metric .estimator .estimate
#>   <chr>   <chr>          <dbl>
#> 1 rmse    standard        4.65

因为它相对于因变量的比例,没有一种适用于所有情况的方式来评估 RMSE,但在使用相同数据的两个竞争模型之间,更小的 RMSE 是首选。

tidymodels在 R 中提供了许多用于拟合和评估模型的技术。我们已经看过一个回归模型,它使用连续因变量,但也可以构建分类模型,其中因变量是分类的。这个包相对较新,所以可用的文献相对较少,但随着这个包的流行,预计会有更多的文献出现。

结论

当然,您可以进行更多的探索和测试来探索这些数据集和其他数据集之间的关系,但我们在这里所采取的步骤是一个坚实的开端。之前,您可以在 Excel 中进行这项工作并进行解释,现在您已经开始在 R 中进行了。

练习

请花点时间尝试使用 R 分析一个熟悉的数据集,采用熟悉的步骤。在第四章结束时,您曾练习在书库中的ais数据集的数据分析。该数据在 R 包DAAG中可用;尝试从那里安装和加载它(它作为对象ais可用)。请执行以下操作:

  1. 通过性别(sex)可视化红细胞计数(rcc)的分布。

  2. 两组性别之间的红细胞计数是否有显著差异?

  3. 在这个数据集中产生相关变量的相关矩阵。

  4. 可视化身高(ht)和体重(wt)之间的关系。

  5. ht回归到wt。找到拟合回归线的方程。是否存在显著关系?体重(wt)解释的身高(ht)变异百分比是多少?

  6. 将回归模型分割成训练集和测试集。测试模型的 R 平方和 RMSE 是多少?

第三部分:从 Excel 到 Python

第十章:Python 初学者的第一步

Python 是由 Guido van Rossum 于 1991 年创建的编程语言,与 R 一样,是自由且开源的。当时,van Rossum 正在阅读《蒙提·派森的飞行马戏团》的剧本,并决定以这部英国喜剧命名这种语言。与专门为数据分析设计的 R 不同,Python 被开发为一种通用语言,用于与操作系统交互、处理处理错误等任务。这对 Python 如何“思考”和处理数据有一些重要的影响。例如,在第七章中你看到,R 有一个内置的表格数据结构。而 Python 并非如此;我们将更多地依赖外部包来处理数据。

这并不一定是一个问题:与 R 一样,Python 有数千个由充满活力的贡献者社区维护的包。你会发现 Python 被用于从移动应用开发到嵌入式设备再到数据分析等各个领域。它的多样化用户群体正在迅速增长,Python 不仅成为了最受欢迎的分析编程语言之一,也成为了计算领域的一种重要工具。

注意

Python 最初被设计为一种通用编程语言,而 R 则是专门用于统计分析。

下载 Python

Python 软件基金会维护着“官方”的 Python 源代码。由于 Python 是开源的,任何人都可以获取、添加和重新分发 Python 代码。Anaconda 是其中一种 Python 发行版,也是本书建议的安装方式。它由同名的盈利公司维护,提供了付费版本;我们将使用免费的个人版本。Python 现在已经到了第三个版本,Python 3。您可以在Anaconda 的网站下载 Python 3 的最新版本。

除了简化的 Python 安装外,Anaconda 还提供了额外的内容,包括一些我们在本书中稍后会使用的流行包。它还附带了一个 Web 应用程序,我们将用它来处理 Python:Jupyter Notebook。

开始使用 Jupyter

如第六章中所述,R 是在 EDA 程序 S 的基础上建模的。由于 EDA 的迭代性质,语言的预期工作流程是执行和探索所选代码行的输出。这使得直接从 R 脚本*.r 进行数据分析变得容易。我们使用了 RStudio IDE,为我们的编程会话提供了额外的支持,例如专门用于帮助文档和环境中对象信息的窗格。

相比之下,Python 在某些方面更像是“低级”编程语言,其中代码首先需要编译成机器可读文件,然后再运行。这使得从 Python 脚本 .py 进行分步数据分析相对更加棘手。物理学家兼软件开发者费尔南多·佩雷斯及其同事在 2001 年启动了 IPython 项目,以使 Python 更加交互式(IPython 是对“交互式 Python”的俏皮简称)。这一倡议的一个结果是一种新类型的文件,即 IPython Notebook,以 .ipynb 文件扩展名表示。

该项目逐渐受到关注,并于 2014 年发展成更广泛的 Jupyter 项目,这是一个与语言无关的倡议,旨在开发交互式、开源的计算软件。因此,IPython Notebook 成为了 Jupyter Notebook,同时保留了 .ipynb 扩展名。Jupyter 笔记本作为交互式网络应用程序运行,允许用户将代码与文本、方程式等结合起来,创建媒体丰富的交互式文档。事实上,Jupyter 在一定程度上是为了向伽利略用于记录其发现木星卫星的笔记本致敬而命名的。内核 在幕后用于执行笔记本的代码。通过下载 Anaconda,您已经设置好了执行 Python 的 Jupyter 笔记本所需的所有必要部分:现在您只需要启动一个会话。

启动 Jupyter 笔记本的步骤因 Windows 和 Mac 计算机而异。在 Windows 上,打开“开始”菜单,然后搜索并启动 Anaconda Prompt。这是一个用于处理您的 Anaconda 发行版的命令行工具,也是与 Python 代码交互的另一种方式。在 Anaconda 提示符内部,输入 jupyter notebook 并按 Enter 键。您的命令将类似于以下内容,但主目录路径不同:

(base) C:\Users\User> jupyter notebook

在 Mac 上,打开 Launchpad,然后搜索并启动 Terminal。这是随 Mac 一起提供的命令行界面,可用于与 Python 进行通信。在终端提示符内部,输入 jupyter notebook 并按 Enter 键。您的命令行将类似于以下内容,但主目录路径不同:

user@MacBook-Pro ~ % jupyter notebook

在任一系统上执行此操作后,将会发生几件事情:首先,会在您的计算机上启动一个类似终端的额外窗口。请勿关闭此窗口。 这将建立与内核的连接。此外,Jupyter 笔记本界面应会自动在您的默认网络浏览器中打开。如果没有自动打开,终端窗口中会包含一个您可以复制并粘贴到浏览器中的链接。图 10-1 中展示了您在浏览器中应该看到的内容。Jupyter 启动时会显示一个类似文件浏览器的界面。现在,您可以导航至您希望保存笔记本的文件夹。

Jupyter 起始页面

图 10-1. Jupyter 起始页面

要打开一个新的笔记本,请转至浏览器窗口右上角,选择 New → Notebook → Python 3. 一个新标签页将打开,显示一个空白的 Jupyter 笔记本。与 RStudio 类似,Jupyter 提供的功能远远超出了我们在介绍中所能覆盖的范围;我们将重点介绍一些关键要点,以帮助您入门。Jupyter 笔记本的四个主要组成部分在 Figure 10-2 中进行了标注;让我们逐一了解一下。

Jupyter 笔记本界面截图

图 10-2. Jupyter 界面的组成元素

首先是笔记本的名称:这是我们的 .ipynb 文件的名称。您可以通过点击并在当前名称上进行编辑来重新命名笔记本。

接下来是菜单栏。这包含了不同的操作,用于处理您的笔记本。例如,在 File 下,您可以打开和关闭笔记本。保存它们并不是什么大问题,因为 Jupyter 笔记本会每两分钟自动保存一次。如果您需要将笔记本转换为 .py Python 脚本或其他常见文件类型,可以通过 File → Download as 完成。还有一个 Help 部分,包含了几个指南和链接到参考文档。您可以从此菜单了解 Jupyter 的许多键盘快捷键。

之前,我提到过 内核 是 Jupyter 在幕后与 Python 交互的方式。菜单栏中的 Kernel 选项包含了与之配合工作的有用操作。由于计算机的工作特性,有时候使您的 Python 代码生效所需的仅仅是重新启动内核。您可以通过 Kernel → Restart 来执行此操作。

立即位于菜单栏下方的是工具栏。这包含了一些有用的图标,用于处理您的笔记本,比通过菜单进行导航更加方便:例如,这里的几个图标与与内核的交互相关。

在 Jupyter 中,您会花费大部分时间,您可以在笔记本中插入和重新定位 单元格。要开始,请使用工具栏进行最后一项操作:您会在那里找到一个下拉菜单,当前设置为 Code;将其更改为 Markdown。

现在,导航到您的第一个代码单元格,并输入短语 Hello, Jupyter! 然后返回工具栏并选择运行图标。会发生几件事情。首先,您会看到您的 Hello, Jupyter! 单元格渲染成为类似文字处理文档的样子。接下来,您会看到一个新的代码单元格放置在前一个单元格下面,并且已经设置好让您输入更多信息。您的笔记本应该类似于 Figure 10-3。

Jupyter keyboard shortcuts

图 10-3. “Hello, Jupyter!”

现在,返回工具栏并再次选择“Markdown”从下拉菜单中选择。正如您开始了解的那样,Jupyter 笔记本由可以是不同类型的模块单元格组成。我们将专注于两种最常见的类型:Markdown 和 Code。Markdown 是一种使用常规键盘字符来格式化文本的纯文本标记语言。

将以下文本插入您的空白单元格中:

# Big Header 1
## Smaller Header 2
### Even smaller headers
#### Still more

*Using one asterisk renders italics*

**Using two asterisks renders bold**

- Use dashes to...
- Make bullet lists

Refer to code without running it as `fixed-width text`

现在运行该单元格:您可以从工具栏或使用快捷键 Alt + Enter(Windows)、Option + Return(Mac)来完成。您的选择将呈现如 Figure 10-4 所示。

Markdown in Jupyter

图 10-4. Jupyter 中 Markdown 格式化的示例

要了解更多关于 Markdown 的信息,请返回菜单栏中的帮助部分。值得学习,以构建优雅的笔记本,可以包括图像、方程式等。但在本书中,我们将专注于 code 块,因为那里可以执行 Python 代码。您现在应该在第三个代码单元格上;您可以将此保留为代码格式。最后,我们将在 Python 中进行编码。

Python 可以像 Excel 和 R 一样用作高级计算器。Table 10-1 列出了 Python 中一些常见的算术运算符。

表 10-1. Python 中常见的算术运算符

运算符 描述
+ 加法
- 减法
* 乘法
/ 除法
** 指数
%% 取模
// 地板除法

输入以下算术,然后运行单元格:

In [1]: 1 + 1
Out[1]: 2
In [2]: 2 * 4
Out[2]: 8

当执行 Jupyter 代码块时,它们分别用 In []Out [] 标记其输入和输出。

Python 也遵循操作顺序;让我们试着在同一个单元中运行几个例子:

In [3]: # Multiplication before addition
        3 * 5 + 6
        2 / 2 - 7 # Division before subtraction
Out[3]: -6.0

默认情况下,Jupyter 笔记本仅返回单元格中最后一次运行代码的输出,所以我们将其分成两部分。您可以使用键盘快捷键 Ctrl + Shift + -(减号)在 Windows 或 Mac 上在光标处分割单元格:

In [4]:  # Multiplication before addition
         3 * 5 + 6

Out[4]: 21
In [5]:  2 / 2 - 7 # Division before subtraction

Out[5]: -6.0

是的,Python 也使用代码注释。与 R 类似,它们以井号开头,并且最好将它们保持在单独的行上。

像 Excel 和 R 一样,Python 包括许多用于数字和字符的函数:

In [6]: abs(-100)

Out[6]: 100
In [7]: len('Hello, world!')

Out[7]: 13

与 Excel 不同,但类似于 R,Python 是区分大小写的。这意味着 只有 abs() 起作用,而不是 ABS()Abs()

In [8]:  ABS(-100)

        ------------------------------------------------------------------------
        NameError                              Traceback (most recent call last)
        <ipython-input-20-a0f3f8a69d46> in <module>
        ----> 1 print(ABS(-100))
            2 print(Abs(-100))

        NameError: name 'ABS' is not defined

与 R 类似,你可以使用 ? 运算符获取有关函数、包等的信息。会打开一个窗口,就像 图 10-5 中展示的那样,你可以展开它或在新窗口中打开。

在 Jupyter 中的帮助文档

图 10-5. 在 Jupyter 笔记本中打开帮助文档

比较运算符在 Python 中与 R 和 Excel 大多数情况下工作方式相同;在 Python 中,结果要么返回 True 要么返回 False

In [10]: # Is 3 greater than 4?
         3 > 4

Out[10]: False

与 R 一样,你可以用 == 检查两个值是否相等;单个等号 = 用于赋值对象。我们在 Python 中会一直使用 = 来赋值对象。

In [11]:  # Assigning an object in Python
          my_first_object = abs(-100)

你可能注意到第 11 格单元中没有 Out [] 组件。那是因为我们只是 赋值 了对象;我们没有打印任何内容。现在让我们来做一下:

In [12]: my_first_object

Out[12]: 100

Python 中的对象名称必须以字母或下划线开头,其余部分只能包含字母、数字或下划线。还有一些禁用的关键字。同样,你可以广泛命名 Python 对象,但并不意味着你应该把对象命名为 scooby_doo

就像在 R 中一样,我们的 Python 对象可以由不同的数据类型组成。表 10-2 展示了一些基本的 Python 类型。你能看出与 R 的相似性和差异吗?

表 10-2. Python 的基本对象类型

数据类型 示例
字符串 'Python', 'G. Mount', 'Hello, world!'
浮点数 6.2, 4.13, 3.1
整数 3, -1, 12
布尔值 True, False

让我们给一些对象赋值。我们可以用 type() 函数找出它们的类型:

In [13]:  my_string = 'Hello, world'
          type(my_string)

Out[13]: str

In [14]: # Double quotes work for strings, too
         my_other_string = "We're able to code Python!"
         type(my_other_string)

Out[14]: str

In [15]: my_float = 6.2
         type(my_float)

Out[15]: float

In [16]: my_integer = 3
         type(my_integer)

Out[16]: int

In [17]: my_bool = True
         type(my_bool)

Out[17]: bool

你已经在 R 中使用对象了,所以可能不会对它们作为 Python 操作的一部分感到意外。

In [18]:  # Is my_float equal to 6.1?
          my_float == 6.1

Out[18]: False

In [19]:  # How many characters are in my_string?
          # (Same function as Excel)
          len(my_string)

Out[19]: 12

Python 中与函数密切相关的是 方法。方法通过一个点与对象关联,并对该对象执行某些操作。例如,要将字符串对象中的所有字母大写,我们可以使用 upper() 方法:

In [20]:  my_string.upper()

Out[20]: 'HELLO, WORLD'

函数和方法都用于对对象执行操作,在本书中我们将同时使用两者。就像你可能希望的那样,Python 和 R 一样,可以在单个对象中存储多个值。但在深入讨论之前,让我们先来看看 模块 在 Python 中是如何工作的。

Python 模块

Python 被设计为通用编程语言,因此甚至一些最简单的用于处理数据的函数也不是默认可用的。例如,我们找不到一个可以计算平方根的函数:

In [21]:  sqrt(25)

        ----------------------------------------------------------
        NameError              Traceback (most recent call last)
        <ipython-input-18-1bf613b64533> in <module>
        ----> 1 sqrt(25)

        NameError: name 'sqrt' is not defined

此函数在 Python 中确实存在。但要访问它,我们需要引入一个 模块,它类似于 R 中的包。Python 标准库中预装了许多模块;例如,math 模块包含许多数学函数,包括 sqrt()。我们可以通过 import 语句将此模块引入到我们的会话中:

In [22]:  import math

语句是告诉解释器要做什么的指令。我们刚刚告诉 Python,嗯,导入 math 模块。现在 sqrt() 函数应该对我们可用了;试试看吧:

In [23]:  sqrt(25)

        ----------------------------------------------------------
        NameError              Traceback (most recent call last)
        <ipython-input-18-1bf613b64533> in <module>
        ----> 1 sqrt(25)

        NameError: name 'sqrt' is not defined

说实话,我并没有在 sqrt() 函数上说谎。我们仍然遇到错误的原因是我们需要明确告诉 Python 这个 函数来自哪里。我们可以通过在函数前加上模块名来做到这一点,像这样:

In [24]:  math.sqrt(25)

Out[24]: 5.0

标准库中充满了有用的模块。然后是数千个第三方模块,捆绑成并提交到 Python 包索引中。pip 是标准的包管理系统;它可以用于从 Python 包索引以及外部源安装。

Anaconda 发行版在处理包时已经做了很多工作。首先,一些最受欢迎的 Python 包是预安装的。此外,Anaconda 包括功能以确保您计算机上的所有包都是兼容的。因此,最好直接从 Anaconda 安装包而不是从 pip 安装。通常情况下,Python 包的安装是通过命令行完成的;您之前在 Anaconda Prompt(Windows)或 Terminal(Mac)中已经使用过命令行。不过,我们可以通过在 Jupyter 前加上感叹号来执行命令行代码。让我们从 Anaconda 安装一个流行的数据可视化包 plotly。使用的语句是 conda install

In [25]:  !conda install plotly

并非所有包都可以从 Anaconda 下载;在这种情况下,我们可以通过 pip 进行安装。让我们为 pyxlsb 包做这个,它可以用来将二进制 .xlsb Excel 文件读取到 Python 中:

In [26]:  !pip install pyxlsb

尽管直接从 Jupyter 下载包很方便,但对于其他人来说,尝试运行您的笔记本只会遇到漫长或不必要的下载可能是一个不愉快的惊喜。这就是为什么通常注释掉安装命令是一个约定,我在书的代码库中也遵循这个约定。

注意

如果您使用 Anaconda 运行 Python,最好首先通过 conda 安装东西,只有在包不可用时才通过 pip 安装。

升级 Python、Anaconda 和 Python 包

Table 10-3 列出了维护您的 Python 环境的几个其他有用的命令。您还可以使用 Anaconda Navigator 通过点和点击界面安装和维护 Anaconda 包,Anaconda Navigator 与 Anaconda 个人版一起安装。要开始,请在计算机上启动该应用程序,然后转到帮助菜单以阅读更多文档。

表 10-3. 维护 Python 包的有用命令

命令 描述
conda update anaconda 更新 Anaconda 发行版
conda update python 更新 Python 版本
conda update -- all 更新通过 conda 下载的所有可能的包
pip list -- outdated 列出所有可以更新的通过 pip 下载的包

结论

在本章中,你学会了如何在 Python 中处理对象和包,并且掌握了使用 Jupyter 笔记本的技巧。

练习:

以下练习提供了关于这些主题的额外练习和深入见解:

  1. 从一个新的 Jupyter 笔记本开始,做以下操作:

    • 将 1 和-4 的和分配给变量a

    • 将变量a的绝对值分配给变量b

    • b减 1 的结果分配给变量d

    • 变量d是否大于 2?

  2. Python 标准库包括一个名为random的模块,其中包含一个名为randint()的函数。这个函数的用法类似于 Excel 中的RANDBETWEEN();例如,randint(1, 6)会返回一个介于 1 和 6 之间的整数。使用这个函数找到一个介于 0 和 36 之间的随机数。

  3. Python 标准库还包括一个名为this的模块。当你导入该模块时会发生什么?

  4. 从 Anaconda 下载xlutils包,然后使用?操作符检索可用的文档。

我再次鼓励你尽快在日常工作中开始使用这种语言,即使一开始只是作为计算器。你也可以尝试在 R 和 Python 中执行相同的任务,然后进行比较和对比。如果你是通过将 R 与 Excel 相关联来学习的,那么将 Python 与 R 相关联也是一样的道理。

第十一章:Python 中的数据结构

在第十章中,您学习了简单的 Python 对象类型,如字符串、整数和布尔值。现在让我们来看看如何将多个值组合在一起,称为集合。Python 默认提供了几种集合对象类型。我们将从列表开始这一章节。我们可以通过用逗号分隔每个条目并将结果放在方括号内来将值放入列表中:

In [1]: my_list = [4, 1, 5, 2]
        my_list

Out[1]: [4, 1, 5, 2]

此对象包含所有整数,但本身不是整数数据类型:它是一个列表

In [2]: type(my_list)

Out[2]: list

实际上,我们可以在列表中包含各种不同类型的数据……甚至其他列表。

In [3]: my_nested_list = [1, 2, 3, ['Boo!', True]]
        type(my_nested_list)

Out[3]: list

正如您所见,列表非常适合存储数据。但是现在,我们真正感兴趣的是能够像 Excel 范围或 R 向量一样工作的东西,然后进入表格数据。一个简单的列表是否符合要求?让我们试试把my_list乘以二。

In [4]:  my_list * 2

Out[4]: [4, 1, 5, 2, 4, 1, 5, 2]

这可能是您要找的内容:Python 字面上接受了您的请求,并且,嗯,将列表加倍,而不是列表内部的数字。有办法在这里自己获取我们想要的结果:如果您之前使用过循环,您可以在这里设置一个循环来将每个元素乘以二。如果您以前没有使用过循环,也没关系:更好的选择是导入一个模块,使在 Python 中执行计算变得更容易。为此,我们将使用包含在 Anaconda 中的numpy

NumPy 数组

In [5]:  import numpy

正如其名称所示,numpy是 Python 中用于数值计算的模块,并且已经成为 Python 作为分析工具流行的基础。要了解更多关于numpy的信息,请访问 Jupyter 菜单栏的帮助部分并选择“NumPy 参考”。现在我们将专注于numpy 数组。这是一个包含所有相同类型项目并且可以在任意数量,或n维度中存储数据的集合。我们将专注于一个一维数组,并使用array()函数从列表中转换我们的第一个数组:

In [6]:  my_array = numpy.array([4, 1, 5, 2])
         my_array

Out[6]: array([4, 1, 5, 2])

乍一看,numpy数组看起来很像一个列表;毕竟,我们甚至是从一个列表创建了它。但我们可以看到它实际上是一个不同的数据类型:

In [7]: type(my_list)

Out[7]: list

In [8]: type(my_array)

Out[8]: numpy.ndarray

具体而言,它是一个ndarray,或者n维数组。因为它是一个不同的数据结构,所以在操作时它可能会表现出不同的行为。例如,当我们将一个numpy数组与另一个数组相乘时会发生什么?

In [9]: my_list * 2

Out[9]: [4, 1, 5, 2, 4, 1, 5, 2]

In [10]: my_array * 2

Out[10]: array([ 8,  2, 10,  4])

在许多方面,此行为应该让您想起 Excel 范围或 R 向量。事实上,就像 R 向量一样,numpy数组将会强制数据成为相同类型:

In [11]: my_coerced_array = numpy.array([1, 2, 3, 'Boo!'])
         my_coerced_array

Out[11]: array(['1', '2', '3', 'Boo!'], dtype='<U11')

正如您所看到的,numpy对于在 Python 中处理数据非常有用。计划经常导入它……这意味着经常输入它。幸运的是,您可以通过别名减轻负担。我们将使用as关键字为numpy指定其常用别名np

In [12]:  import numpy as np

这为模块提供了一个临时且更易管理的名称。现在,在我们的 Python 会话期间,每次我们想要调用numpy中的代码时,我们可以引用其别名。

In [13]: import numpy as np
         # numpy also has a sqrt() function:
         np.sqrt(my_array)

Out[13]: array([2.        , 1.        , 2.23606798, 1.41421356])
注意

请记住,别名对于您的 Python 会话是临时的。如果重新启动内核或启动新笔记本,则别名将不再有效。

索引和子集选择 NumPy 数组

让我们花点时间来探索如何从numpy数组中提取单个项,我们可以通过在对象名称旁边直接加上方括号及其索引号来完成:

In [14]: # Get second element... right?
         my_array[2]

Out[14]: 5

例如,我们刚刚从数组中提取了第二个元素……或者我们是这样做的吗? 让我们重新审视my_array;第二个位置真的显示了什么?

In [15]: my_array

Out[15]: array([4, 1, 5, 2])

看起来1位于第二个位置,而5实际上在第三个位置。是什么解释了这种差异?事实证明,这是因为 Python 计数方式与我们通常的计数方式不同。

作为对这种奇怪概念的预热,想象一下,您因为兴奋地获取新数据集而多次下载它。匆忙行事会让您得到一系列文件,命名如下:

  • dataset.csv

  • dataset (1).csv

  • dataset (2).csv

  • dataset (3).csv

作为人类,我们倾向于从一开始计算事物。但计算机通常从开始计数。多个文件下载就是一个例子:我们的第二个文件实际上命名为dataset (1),而不是dataset (2)。这称为零起始索引,在 Python 中到处都有

这一切都是说,对于 Python 来说,用数字1索引的内容返回的是第二位置的值,用2索引的是第三个,依此类推。

In [16]: # *Now* let's get the second element
         my_array[1]

Out[16]: 1

在 Python 中,还可以对一系列连续的值进行子集选择,称为 Python 中的切片。让我们尝试找出第二到第四个元素。我们已经了解了零起始索引的要点;这有多难呢?

In [17]: # Get second through fourth elements... right?
         my_array[1:3]

Out[17]: array([1, 5])

但等等,还有更多。 除了零起始索引外,切片不包括结束元素。这意味着我们需要“加 1”到第二个数字以获得我们想要的范围:

In [18]: # *Now* get second through fourth elements
         my_array[1:4]

Out[18]: array([1, 5, 2])

在 Python 中,您可以进行更多的切片操作,例如从对象的末尾开始或选择从开头到指定位置的所有元素。现在,记住的重要事情是Python 使用零起始索引

二维numpy数组可以作为 Python 的表格数据结构,但所有元素必须是相同的数据类型。在业务环境中分析数据时,这很少发生,因此我们将转向pandas以满足此要求。

引入 Pandas DataFrames

根据计量经济学中面板数据的命名,pandas在操作和分析表格数据方面特别有帮助。像numpy一样,它与 Anaconda 一起安装。典型的别名是pd

In [19]: import pandas as pd

pandas模块在其代码库中利用了numpy,您将看到两者之间的一些相似之处。pandas包括一个称为Series的一维数据结构。但它最广泛使用的结构是二维DataFrame(听起来熟悉吗?)。使用DataFrame函数可以从其他数据类型(包括numpy数组)创建 DataFrame:

In [20]: record_1 = np.array(['Jack', 72, False])
         record_2 = np.array(['Jill', 65, True])
         record_3 = np.array(['Billy', 68, False])
         record_4 = np.array(['Susie', 69, False])
         record_5 = np.array(['Johnny', 66, False])

         roster = pd.DataFrame(data = [record_1,
             record_2, record_3, record_4, record_5],
               columns = ['name', 'height', 'injury'])

         roster

Out[20]:
              name height injury
         0    Jack     72  False
         1    Jill     65   True
         2   Billy     68  False
         3   Susie     69  False
         4  Johnny     66  False

DataFrame 通常包括每列的命名标签。行下方还将有一个索引,默认情况下从 0 开始。这是一个相当小的数据集要探索,所以让我们找点别的。不幸的是,Python 不会自带任何 DataFrame,但我们可以使用seaborn包找到一些。seaborn也随 Anaconda 一起安装,并经常被别名为snsget_dataset_names()函数将返回可用于使用的 DataFrame 列表:

In [21]: import seaborn as sns
         sns.get_dataset_names()

Out[21]:
        ['anagrams', 'anscombe', 'attention', 'brain_networks', 'car_crashes',
        'diamonds', 'dots', 'exercise', 'flights', 'fmri', 'gammas',
        'geyser', 'iris', 'mpg', 'penguins', 'planets', 'tips', 'titanic']

鸢尾花听起来熟悉吗?我们可以使用load_dataset()函数将其加载到我们的 Python 会话中,并使用head()方法打印前五行。

In [22]: iris = sns.load_dataset('iris')
         iris.head()

Out[22]:
                sepal_length  sepal_width  petal_length  petal_width species
        0           5.1          3.5           1.4          0.2  setosa
        1           4.9          3.0           1.4          0.2  setosa
        2           4.7          3.2           1.3          0.2  setosa
        3           4.6          3.1           1.5          0.2  setosa
        4           5.0          3.6           1.4          0.2  setosa

Python 中的数据导入

与 R 一样,最常见的是从外部文件中读取数据,我们需要处理目录来做到这一点。Python 标准库包含了用于处理文件路径和目录的os模块:

In [23]: import os

对于接下来的部分,请将您的笔记本保存在书籍存储库的主文件夹中。默认情况下,Python 将当前工作目录设置为您的活动文件所在的位置,因此我们不必像在 R 中那样担心更改目录。您仍然可以使用osgetcwd()chdir()函数分别检查和更改它。

Python 遵循与 R 相同的相对和绝对文件路径的一般规则。让我们看看是否可以使用ospath子模块中的isfile()函数在存储库中定位test-file.csv

In [24]: os.path.isfile('test-file.csv')

Out[24]: True

现在我们想要找到该文件,它包含在test-folder子文件夹中。

In [25]: os.path.isfile('test-folder/test-file.csv')

Out[25]: True

接下来,尝试将此文件的副本放在当前位置上一级的文件夹中。您应该能够使用此代码找到它:

In [26]:  os.path.isfile('../test-file.csv')

Out[26]: True

与 R 一样,您通常会从外部来源读取数据以在 Python 中进行操作,而这个来源几乎可以是任何想得到的东西。pandas包含从.xlsx.csv文件等文件中读取数据到 DataFrame 的功能。为了演示,我们将从书籍存储库中读取我们可靠的star.xlsxdistricts.csv数据集。使用read_excel()函数来读取 Excel 工作簿:

In [27]: star = pd.read_excel('datasets/star/star.xlsx')
         star.head()

Out[27]:
   tmathssk  treadssk             classk  totexpk   sex freelunk   race
0       473       447        small.class        7 girl       no  white
1       536       450        small.class       21 girl       no  black
2       463       439  regular.with.aide        0   boy      yes  black
3       559       448            regular       16   boy       no  white
4       489       447        small.class        5 boy      yes  white

   schidkn
0       63
1       20
2       19
3       69
4       79

类似地,我们可以使用pandas中的read_csv()函数来读取.csv文件:

In [28]: districts = pd.read_csv('datasets/star/districts.csv')
         districts.head()

Out[28]:
             schidkn      school_name       county
          0        1          Rosalia  New Liberty
          1        2  Montgomeryville       Topton
          2        3             Davy     Wahpeton
          3        4         Steelton    Palestine
          4        6       Tolchester      Sattley

如果您想要读取其他 Excel 文件类型或特定范围和工作表,例如,请查看pandas文档。

探索 DataFrame

让我们继续评估star DataFrame。info()方法会告诉我们一些重要的信息,比如它的维度和列的类型:

In [29]: star.info()

    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 5748 entries, 0 to 5747
    Data columns (total 8 columns):
    #   Column    Non-Null Count  Dtype
    ---  ------    --------------  -----
    0   tmathssk  5748 non-null   int64
    1   treadssk  5748 non-null   int64
    2   classk    5748 non-null   object
    3   totexpk   5748 non-null   int64
    4   sex       5748 non-null   object
    5   freelunk  5748 non-null   object
    6   race      5748 non-null   object
    7   schidkn   5748 non-null   int64
    dtypes: int64(4), object(4)
    memory usage: 359.4+ KB

我们可以使用describe()方法检索描述性统计信息:

In [30]: star.describe()

Out[30]:
                tmathssk     treadssk      totexpk      schidkn
        count  5748.000000  5748.000000  5748.000000  5748.000000
        mean    485.648051   436.742345     9.307411    39.836639
        std      47.771531    31.772857     5.767700    22.957552
        min     320.000000   315.000000     0.000000     1.000000
        25%     454.000000   414.000000     5.000000    20.000000
        50%     484.000000   433.000000     9.000000    39.000000
        75%     513.000000   453.000000    13.000000    60.000000
        max     626.000000   627.000000    27.000000    80.000000

默认情况下,pandas仅包括数值变量的描述统计信息。我们可以使用include='all'来覆盖这个设置。

In [31]: star.describe(include = 'all')

Out[31]:
           tmathssk     treadssk             classk      totexpk   sex  \
count   5748.000000  5748.000000               5748  5748.000000  5748
unique          NaN          NaN                  3          NaN     2
top             NaN          NaN  regular.with.aide          NaN   boy
freq            NaN          NaN               2015          NaN  2954
mean     485.648051   436.742345                NaN     9.307411   NaN
std       47.771531    31.772857                NaN     5.767700   NaN
min      320.000000   315.000000                NaN     0.000000   NaN
25%      454.000000   414.000000                NaN     5.000000   NaN
50%      484.000000   433.000000                NaN     9.000000   NaN
75%      513.000000   453.000000                NaN    13.000000   NaN
max      626.000000   627.000000                NaN    27.000000   NaN

       freelunk   race      schidkn
count      5748   5748  5748.000000
unique        2      3          NaN
top          no  white          NaN
freq       2973   3869          NaN
mean        NaN    NaN    39.836639
std         NaN    NaN    22.957552
min         NaN    NaN     1.000000
25%         NaN    NaN    20.000000
50%         NaN    NaN    39.000000
75%         NaN    NaN    60.000000
max         NaN    NaN    80.000000

NaN是一个特殊的pandas值,表示缺失或不可用的数据,例如分类变量的标准差。

DataFrame 的索引和子集

让我们回到小型 roster DataFrame,通过它们的行和列位置访问各种元素。要对 DataFrame 进行索引,我们可以使用 iloc 方法,或称为 integer location。方括号表示法对您来说可能已经很熟悉了,但这次我们需要通过行 列来进行索引(再次从零开始)。让我们在前面创建的 roster DataFrame 上演示一下。

In [32]:  # First row, first column of DataFrame
          roster.iloc[0, 0]

Out[32]: 'Jack'

在此处也可以使用切片来捕获多行和列:

In [33]: # Second through fourth rows, first through third columns
         roster.iloc[1:4, 0:3]

Out[33]:
     name height injury
 1   Jill     65   True
 2  Billy     68  False
 3  Susie     69  False

要按名称索引整个列,我们可以使用相关的 loc 方法。我们将在第一个索引位置留一个空切片以捕获所有行,然后命名感兴趣的列:

In [34]:  # Select all rows in the name column
          roster.loc[:, 'name']

Out[34]:
        0      Jack
        1      Jill
        2     Billy
        3     Susie
        4    Johnny
        Name: name, dtype: object

写入 DataFrame

pandas 还包括函数,可以分别使用 write_csv()write_xlsx() 方法将 DataFrame 写入到 .csv 文件和 .xlsx 工作簿中:

In [35]: roster.to_csv('output/roster-output-python.csv')
         roster.to_excel('output/roster-output-python.xlsx')

结论

在短时间内,您能够从单元素对象进展到列表、numpy 数组,最终到 pandas DataFrames。希望您能看到这些数据结构之间的演变和联系,并欣赏到所介绍包的附加优势。接下来的 Python 章节将大量依赖于 pandas,但您已经看到 pandas 本身依赖于 numpy 和 Python 的基本规则,例如从零开始的索引。

练习

在本章中,您学会了如何在 Python 中使用几种不同的数据结构和集合类型。以下练习提供了关于这些主题的额外实践和见解:

  1. 对以下数组进行切片,以便保留第三到第五个元素。

    practice_array = ['I', 'am', 'having', 'fun', 'with', 'Python']
    
  2. seaborn 加载 tips DataFrame。

    • 打印有关此 DataFrame 的一些信息,例如观测数和每列的类型。

    • 打印此 DataFrame 的描述统计信息。

  3. 书籍存储库(oreil.ly/RKmg0)包含了 datasets 文件夹中 ais 子文件夹中的 ais.xlsx 文件。将其读取为 Python 的 DataFrame。

    • 打印此 DataFrame 的前几行。

    • 仅将此 DataFrame 的 sport 列写回 Excel 为 sport.xlsx

第十二章:Python 中的数据操作和可视化

在第八章中,你学习如何在数据操作和可视化方面使用tidyverse套件。在这里,我们将在 Python 中演示类似的技术,应用于相同的star数据集。特别是,我们将使用pandasseaborn分别进行数据操作和可视化。这不是关于这些模块或 Python 在数据分析中的全部功能的详尽指南。相反,这足以让你自己探索。

尽可能地,我会模仿我们在第八章中所做的步骤,并执行相同的操作。因为熟悉这些操作,我会更专注于如何在 Python 中执行数据操作和可视化,而不是为什么这样做。让我们加载必要的模块,并开始使用star。第三个模块matplotlib对你来说是新的,将用于辅助我们在seaborn中的工作。它已经随 Anaconda 安装。具体来说,我们将使用pyplot子模块,并将其别名为plt

In [1]:  import pandas as pd
         import seaborn as sns
         import matplotlib.pyplot as plt

         star = pd.read_excel('datasets/star/star.xlsx')
         star.head()
Out[1]:
   tmathssk  treadssk             classk  totexpk   sex freelunk   race  \
0       473       447        small.class        7 girl       no  white
1       536       450        small.class       21 girl       no  black
2       463       439  regular.with.aide        0   boy      yes  black
3       559       448            regular       16   boy       no  white
4       489       447        small.class        5 boy      yes  white

   schidkn
0       63
1       20
2       19
3       69
4       79

列操作

在第十一章中,你学习到pandas将尝试将一维数据结构转换为 Series。当选择列时,这个看似微不足道的点将变得非常重要。让我们看一个例子:假设我们只想保留 DataFrame 中的tmathssk列。我们可以使用熟悉的单括号表示法来做到这一点,但从技术上讲,这会导致一个 Series,而不是 DataFrame:

In [2]:  math_scores = star['tmathssk']
         type(math_scores)

Out[2]: pandas.core.series.Series

如果我们不能确定是否希望math_scores保持为一维结构,最好将其保留为 DataFrame。为此,我们可以使用两组方括号而不是一组:

In [3]: math_scores = star[['tmathssk']]
        type(math_scores)

Out[3]: pandas.core.frame.DataFrame

按照这个模式,我们可以在star中仅保留所需的列。我将使用columns属性来确认。

In [4]:  star = star[['tmathssk','treadssk','classk','totexpk','schidkn']]
         star.columns

Out[4]: Index(['tmathssk', 'treadssk', 'classk',
             'totexpk', 'schidkn'], dtype='object')

要删除特定的列,请使用drop()方法。drop()可以用于删除列或行,所以我们需要通过使用axis参数来指定。在pandas中,行是轴0,列是轴1,正如图 12-1 所示。

star 数据集的轴

图 12-1。一个pandas DataFrame 的轴

这里是如何删除schidkn列的方法:

In [5]: star = star.drop('schidkn', axis=1)
        star.columns

Out[5]: Index(['tmathssk', 'treadssk',
            'classk', 'totexpk'], dtype='object')

现在让我们来看看如何从 DataFrame 中派生新的列。我们可以使用方括号表示法来做到这一点——这一次,我确实希望结果是一个 Series,因为 DataFrame 的每一列实际上都是一个 Series(就像 R 数据框的每一列实际上都是一个向量一样)。在这里,我将计算数学和阅读成绩的综合:

In [6]: star['new_column'] = star['tmathssk'] + star['treadssk']
        star.head()

Out[6]:
   tmathssk  treadssk             classk  totexpk  new_column
0       473       447        small.class        7         920
1       536       450        small.class       21         986
2       463       439  regular.with.aide        0         902
3       559       448            regular       16        1007
4       489       447        small.class        5         936

再次,new_column不是一个非常描述性的变量名。让我们使用rename()函数来修复它。我们将使用columns参数,并以你可能不熟悉的格式传递数据:

In [7]: star = star.rename(columns = {'new_column':'ttl_score'})
        star.columns

Out[7]: Index(['tmathssk', 'treadssk', 'classk', 'totexpk', 'ttl_score'],
           dtype='object')

上一个示例中使用的花括号表示法是 Python 的 字典。字典是 键-值 对的集合,每个元素的键和值由冒号分隔。这是 Python 的核心数据结构之一,是你继续学习该语言时要了解的内容。

按行进行操作

现在让我们转向按行常见的操作。我们将从排序开始,在 pandas 中可以使用 sort_values() 方法完成。我们将按照各列的指定顺序传递给 by 参数来排序:

In [8]: star.sort_values(by=['classk', 'tmathssk']).head()

Out[8]:
     tmathssk  treadssk   classk  totexpk  ttl_score
309        320       360  regular        6        680
1470       320       315  regular        3        635
2326       339       388  regular        6        727
2820       354       398  regular        6        752
4925       354       391  regular        8        745

默认情况下,所有列都按升序排序。要修改这一点,我们可以包含另一个参数 ascending,其中包含 True/False 标志的列表。让我们按照班级大小(classk)升序和数学分数(treadssk)降序来排序 star。因为我们没有将此输出重新分配给 star,所以这种排序不会永久影响数据集。

In [9]: # Sort by class size ascending and math score descending
        star.sort_values(by=['classk', 'tmathssk'],
         ascending=[True, False]).head()

Out[9]:
      tmathssk  treadssk   classk  totexpk  ttl_score
724        626       474  regular       15       1100
1466       626       554  regular       11       1180
1634       626       580  regular       15       1206
2476       626       538  regular       20       1164
2495       626       522  regular        7       1148

要筛选 DataFrame,我们首先使用条件逻辑创建一个 Series,其中包含每行是否符合某些条件的 True/False 标志。然后,我们仅保留 DataFrame 中 Series 中标记为 True 的记录。例如,让我们仅保留 classk 等于 small.class 的记录。

In [10]: small_class = star['classk'] == 'small.class'
         small_class.head()

Out[10]:
        0     True
        1     True
        2    False
        3    False
        4     True
        Name: classk, dtype: bool

现在,我们可以使用括号过滤此结果的 Series。我们可以使用 shape 属性确认新 DataFrame 的行数和列数:

In [11]: star_filtered = star[small_class]
         star_filtered.shape

Out[11]: (1733, 5)

star_filtered 将包含比 star 更少的行,但列数相同:

In [12]: star.shape

Out[12]: (5748, 5)

让我们再试一次:找到 treadssk 至少为 500 的记录:

In [13]: star_filtered = star[star['treadssk'] >= 500]
         star_filtered.shape

Out[13]: (233, 5)

还可以使用 and/or 语句根据多个条件进行过滤。就像在 R 中一样,&| 分别表示 "和" 和 "或"。让我们将前面的两个条件放入括号中,并使用 & 将它们连接到一个语句中:

In [14]: # Find all records with reading score at least 500 and in small class
         star_filtered = star[(star['treadssk'] >= 500) &
                   (star['classk'] == 'small.class')]
         star_filtered.shape

Out[14]: (84, 5)

聚合和连接数据

要在 DataFrame 中对观察结果进行分组,我们将使用 groupby() 方法。如果打印 star_grouped,你会看到它是一个 DataFrameGroupBy 对象:

In [15]: star_grouped = star.groupby('classk')
         star_grouped

Out[15]: <pandas.core.groupby.generic.DataFrameGroupBy
            object at 0x000001EFD8DFF388>

现在我们可以选择其他字段来对这个分组的 DataFrame 进行聚合。Table 12-1 列出了一些常见的聚合方法。

表 12-1. pandas 中有用的聚合函数

方法 聚合类型
sum() 总和
count() 计数值
mean() 平均值
max() 最高值
min() 最小值
std() 标准差

以下给出了每个班级大小的平均数学分数:

In [16]: star_grouped[['tmathssk']].mean()

Out[16]:
                        tmathssk
    classk
    regular            483.261000
    regular.with.aide  483.009926
    small.class        491.470283

现在我们将找到每年教师经验的最高总分。因为这将返回相当多的行,我将包含 head() 方法以仅获取一些行。将多个方法添加到同一命令的这种做法称为方法 链式调用

In [17]: star.groupby('totexpk')[['ttl_score']].max().head()

Out[17]:
                ttl_score
        totexpk
        0             1171
        1             1133
        2             1091
        3             1203
        4             1229

第八章回顾了 Excel 的VLOOKUP()和左外连接之间的相似性和差异。我将重新读入stardistricts的最新副本;让我们使用pandas来合并这些数据集。我们将使用merge()方法将school-districts中的数据“查找”到star中。通过将how参数设置为left,我们将指定左外连接,这是最接近VLOOKUP()的连接类型:

In [18]: star = pd.read_excel('datasets/star/star.xlsx')
         districts = pd.read_csv('datasets/star/districts.csv')
         star.merge(districts, how='left').head()

Out[18]:
   tmathssk  treadssk             classk  totexpk   sex freelunk   race  \
0       473       447        small.class        7 girl       no  white
1       536       450        small.class       21 girl       no  black
2       463       439  regular.with.aide        0   boy      yes  black
3       559       448            regular       16   boy       no  white
4       489       447        small.class        5 boy      yes  white

   schidkn    school_name          county
0       63     Ridgeville     New Liberty
1       20  South Heights         Selmont
2       19      Bunnlevel         Sattley
3       69          Hokah      Gallipolis
4       79   Lake Mathews  Sugar Mountain

Python 像 R 一样对数据连接非常直观:它默认使用schidkn进行合并,并同时引入school_namecounty

数据重塑

让我们来看看如何在 Python 中扩展和延长数据集,再次使用pandas。首先,我们可以使用melt()函数将tmathssktreadssk合并到一列中。为此,我将指定要使用frame参数操作的 DataFrame,使用id_vars作为唯一标识符的变量,并使用value_vars将变量融合为单列。我还将指定如何命名生成的值和标签变量,分别为value_namevar_name

In [19]: star_pivot = pd.melt(frame=star, id_vars = 'schidkn',
            value_vars=['tmathssk', 'treadssk'], value_name='score',
            var_name='test_type')
         star_pivot.head()

Out[19]:
            schidkn test_type  score
         0       63  tmathssk    473
         1       20  tmathssk    536
         2       19  tmathssk    463
         3       69  tmathssk    559
         4       79  tmathssk    489

如何将tmathssktreadssk重命名为mathreading?为此,我将使用一个 Python 字典来设置一个名为mapping的对象,它类似于一个“查找表”以重新编码这些值。我将把它传递给map()方法来重新编码test_type。我还将使用unique()方法确认test_type现在仅包含mathreading

In [20]: # Rename records in `test_type`
         mapping = {'tmathssk':'math','treadssk':'reading'}
         star_pivot['test_type'] = star_pivot['test_type'].map(mapping)

         # Find unique values in test_type
         star_pivot['test_type'].unique()

Out[20]: array(['math', 'reading'], dtype=object)

要将star_pivot扩展回单独的mathreading列,我将使用pivot_table()方法。首先,我将指定要使用index参数索引的变量,然后使用columnsvalues参数指定标签和值来自哪些变量:

pandas中,可以设置唯一的索引列;默认情况下,pivot_table()将设置index参数中包含的任何变量。为了覆盖这一点,我将使用reset_index()方法。要了解更多关于pandas中自定义索引以及其他无数的数据操作和分析技术,请参阅《Python for Data Analysis》(O'Reilly 出版社第 2 版)作者 Wes McKinney 的书籍链接

In [21]: star_pivot.pivot_table(index='schidkn',
          columns='test_type', values='score').reset_index()

Out[21]:
         test_type  schidkn        math     reading
         0                1  492.272727  443.848485
         1                2  450.576923  407.153846
         2                3  491.452632  441.000000
         3                4  467.689655  421.620690
         4                5  460.084746  427.593220
         ..             ...         ...         ...
         74              75  504.329268  440.036585
         75              76  490.260417  431.666667
         76              78  468.457627  417.983051
         77              79  490.500000  434.451613
         78              80  490.037037  442.537037

         [79 rows x 3 columns]

数据可视化

现在让我们简要介绍一下 Python 中的数据可视化,特别是使用seaborn包。seaborn在统计分析和pandas数据框架中表现特别出色,因此在这里是一个很好的选择。类似于pandas是建立在numpy之上,seaborn利用了另一个流行的 Python 绘图包matplotlib的功能。

seaborn包括许多函数来构建不同类型的图表。我们将修改这些函数中的参数,以指定要绘制的数据集、沿 x 轴和 y 轴的变量、要使用的颜色等。我们首先通过使用countplot()函数来可视化每个classk水平的观察计数。

我们的数据集是star,我们将使用data参数指定。要将classk的水平放置在 x 轴上,我们将使用x参数。这导致了图 12-2 中所示的计数图:

In [22]: sns.countplot(x='classk', data=star)

Countplot

图 12-2. 计数图

现在对于treadssk的直方图,我们将使用displot()函数。同样,我们将指定xdata。图 12-3 展示了结果:

In [23]: sns.displot(x='treadssk', data=star)

直方图

图 12-3. 直方图

seaborn函数包括许多可选参数,用于自定义图表的外观。例如,让我们将箱数更改为 25 并将绘图颜色更改为粉红色。这将产生图 12-4:

In [24]: sns.displot(x='treadssk', data=star, bins=25, color='pink')

自定义直方图

图 12-4. 自定义直方图

要制作箱线图,使用boxplot()函数,如图 12-5 所示:

In [25]: sns.boxplot(x='treadssk', data=star)

到目前为止,在所有这些情况下,我们都可以通过将感兴趣的变量包含在y参数中来“翻转”图表。让我们尝试使用我们的箱线图进行演示,我们将得到图 12-6 中显示的输出:

In [26]: sns.boxplot(y='treadssk', data=star)

箱线图

图 12-5. 箱线图

箱线图

图 12-6. “翻转”的箱线图

要为每个班级大小水平制作箱线图,我们将在classk的 x 轴上包含一个额外的参数,这样就得到了图 12-7 中所示的按组的箱线图:

In [27]: sns.boxplot(x='classk', y='treadssk', data=star)

分组箱线图

图 12-7. 按组的箱线图

现在让我们使用scatterplot()函数在 x 轴上绘制tmathssk与 y 轴上的treadssk之间的关系。图 12-8 是结果:

In [28]: sns.scatterplot(x='tmathssk', y='treadssk', data=star)

散点图

图 12-8. 散点图

假设我们想要与外部观众分享这个图表,他们可能不熟悉treadssktmathssk是什么。我们可以通过从matplotlib.pyplot中借用功能向该图表添加更多有用的标签。我们将与之前相同地运行scatterplot()函数,但这次我们还将调用pyplot中的函数来添加自定义的 x 和 y 轴标签,以及图表标题。这导致了图 12-9 中显示的带有标签的散点图:

In [29]: sns.scatterplot(x='tmathssk', y='treadssk', data=star)
         plt.xlabel('Math score')
         plt.ylabel('Reading score')
         plt.title('Math score versus reading score')

带有自定义标签和标题的散点图

图 12-9. 带有自定义轴标签和标题的散点图

seaborn包括许多功能,用于构建视觉上吸引人的数据可视化。要了解更多,请查看官方文档

结论

pandasseaborn能做的远不止这些,但这已足以让你开始真正的任务:探索和测试数据中的关系。这将是第十三章的重点。

练习

书籍存储库datasets子文件夹census中有两个文件,census.csvcensus-divisions.csv。将这些文件读入 Python 并执行以下操作:

  1. 按地区升序、分区升序和人口降序对数据进行排序(你需要合并数据集才能做到这一点)。将结果写入 Excel 工作表。

  2. 从合并的数据集中删除邮政编码字段。

  3. 创建一个名为density的新列,该列是人口除以陆地面积的计算结果。

  4. 可视化 2015 年所有观察中陆地面积和人口之间的关系。

  5. 找出每个地区在 2015 年的总人口。

  6. 创建一个包含州名和人口的表,每年 2010 年至 2015 年的人口分别保留在一个单独的列中。

第十三章:项目实战:Python 数据分析

在 第八章 结尾,你扩展了对 R 的学习,以探索和测试 mpg 数据集中的关系。在本章中,我们将使用 Python 进行相同的工作。我们在 Excel 和 R 中进行了相同的工作,所以我将更专注于如何在 Python 中进行分析,而不是为什么要进行分析。

要开始,请调用所有必要的模块。其中一些是新的:从 scipy 中,我们将导入 stats 子模块。为了告诉 Python 要查找哪个模块,我们将使用 from 关键字,然后使用 import 关键字来选择一个子模块。正如其名,我们将使用 scipystats 子模块来进行统计分析。我们还将使用一个称为 sklearnscikit-learn 的新包,在训练/测试拆分上验证我们的模型。这个包已经成为机器学习的主要资源,并且与 Anaconda 一起安装。

In [1]: import pandas as pd
        import seaborn as sns
        import matplotlib.pyplot as plt
        from scipy import stats
        from sklearn import linear_model
        from sklearn import model_selection
        from sklearn import metrics

使用 read_csv()usecols 参数,我们可以指定读入 DataFrame 的列:

In [2]: mpg = pd.read_csv('datasets/mpg/mpg.csv',usecols=
           ['mpg','weight','horsepower','origin','cylinders'])
        mpg.head()

Out[2]:
     mpg  cylinders  horsepower  weight origin
 0  18.0          8         130    3504    USA
 1  15.0          8         165    3693    USA
 2  18.0          8         150    3436    USA
 3  16.0          8         150    3433    USA
 4  17.0          8         140    3449    USA

探索性数据分析

让我们从描述统计开始:

In[3]: mpg.describe()

Out[3]:
              mpg   cylinders  horsepower       weight
count  392.000000  392.000000  392.000000   392.000000
mean    23.445918    5.471939  104.469388  2977.584184
std      7.805007    1.705783   38.491160   849.402560
min      9.000000    3.000000   46.000000  1613.000000
25%     17.000000    4.000000   75.000000  2225.250000
50%     22.750000    4.000000   93.500000  2803.500000
75%     29.000000    8.000000  126.000000  3614.750000
max     46.600000    8.000000  230.000000  5140.000000

因为 origin 是一个分类变量,默认情况下它不会显示为 describe() 的一部分。让我们改为用频率表探索这个变量。这可以在 pandas 中用 crosstab() 函数来实现。首先,我们将指定将数据放在索引上的内容:origin。通过将 columns 参数设置为 count,我们将得到每个水平的计数:

In [4]: pd.crosstab(index=mpg['origin'], columns='count')

Out[4]:
col_0   count
origin
Asia       79
Europe     68
USA       245

要制作双向频率表,我们可以将 columns 设置为另一个分类变量,如 cylinders

In [5]: pd.crosstab(index=mpg['origin'], columns=mpg['cylinders'])

Out[5]:
cylinders  3   4  5   6    8
origin
Asia       4  69  0   6    0
Europe     0  61  3   4    0
USA        0  69  0  73  103

接下来,让我们按 origin 水平检索 mpg 的描述统计信息。我将通过链接两种方法,然后子集化结果来完成这项工作:

In[6]: mpg.groupby('origin').describe()['mpg']

Out[6]:
        count       mean       std   min    25%   50%     75%   max
origin
Asia     79.0  30.450633  6.090048  18.0  25.70  31.6  34.050  46.6
Europe   68.0  27.602941  6.580182  16.2  23.75  26.0  30.125  44.3
USA     245.0  20.033469  6.440384   9.0  15.00  18.5  24.000  39.0

我们还可以像 图 13-1 那样可视化 mpg 的整体分布:

In[7]: sns.displot(data=mpg, x='mpg')

MPG 的直方图

图 13-1. mpg 的直方图

现在让我们制作一个箱线图,如 图 13-2,比较每个 origin 水平上 mpg 的分布:

In[8]: sns.boxplot(x='origin', y='mpg', data=mpg, color='pink')

箱线图

图 13-2. 按 origin 分组的 mpg 箱线图

或者,我们可以将 displot()col 参数设置为 origin 来创建分面直方图,例如 图 13-3:

In[9]: sns.displot(data=mpg, x="mpg", col="origin")

分面直方图

图 13-3. 按 origin 分组的分面直方图

假设检验

让我们再次测试美国和欧洲汽车之间的里程差异。为了便于分析,我们将每个组中的观测分割成它们自己的数据框。

In[10]: usa_cars = mpg[mpg['origin']=='USA']
        europe_cars = mpg[mpg['origin']=='Europe']

独立样本 t 检验

现在我们可以使用 scipy.stats 中的 ttest_ind() 函数进行 t 检验。这个函数期望接收两个 numpy 数组作为参数;pandas 的 Series 也可以使用:

In[11]: stats.ttest_ind(usa_cars['mpg'], europe_cars['mpg'])

Out[11]: Ttest_indResult(statistic=-8.534455914399228,
            pvalue=6.306531719750568e-16)

不幸的是,这里的输出相当有限:虽然包括了 p 值,但未包括置信区间。要获得更多输出的 t 检验结果,请查看researchpy模块。

现在我们来分析我们的连续变量。我们将从相关矩阵开始。我们可以使用pandas中的corr()方法,只包括相关变量:

In[12]: mpg[['mpg','horsepower','weight']].corr()

Out[12]:
                 mpg  horsepower    weight
mpg         1.000000   -0.778427 -0.832244
horsepower -0.778427    1.000000  0.864538
weight     -0.832244    0.864538  1.000000

接下来,让我们用散点图可视化weightmpg之间的关系,如图 13-4 所示:

In[13]: sns.scatterplot(x='weight', y='mpg', data=mpg)
        plt.title('Relationship between weight and mileage')

散点图

图 13-4. mpgweight的散点图

或者,我们可以使用seabornpairplot()函数在数据集的所有变量对上生成散点图。对角线上包括每个变量的直方图,如图 13-5 所示:

In[14]: sns.pairplot(mpg[['mpg','horsepower','weight']])

Pairplot

图 13-5. mpghorsepowerweight的 Pairplot

线性回归

现在是进行线性回归的时候了。为此,我们将使用scipy中的linregress()函数,该函数同样适用于两个numpy数组或pandas Series。我们将使用xy参数指定哪个变量是独立变量和依赖变量:

In[15]: # Linear regression of weight on mpg
        stats.linregress(x=mpg['weight'], y=mpg['mpg'])

Out[15]: LinregressResult(slope=-0.007647342535779578,
   intercept=46.21652454901758, rvalue=-0.8322442148315754,
   pvalue=6.015296051435726e-102, stderr=0.0002579632782734318)

同样地,您会发现您习惯于看到的一些输出在这里是缺失的。请注意: 这里包含的rvalue相关系数,而不是 R 平方。要获得更丰富的线性回归输出,请查看statsmodels模块。

最后但同样重要,让我们在散点图上叠加回归线。seaborn有一个专门的函数来实现这一点:regplot()。与往常一样,我们将指定我们的独立和依赖变量,以及数据的来源。这将产生图 13-6:

In[16]: # Fit regression line to scatterplot
        sns.regplot(x="weight", y="mpg", data=mpg)
        plt.xlabel('Weight (lbs)')
        plt.ylabel('Mileage (mpg)')
        plt.title('Relationship between weight and mileage')

拟合散点图

图 13-6. mpgweight的散点图与拟合回归线

训练/测试拆分与验证

在第九章结束时,您学习了如何在构建 R 中的线性回归模型时应用训练/测试拆分。

我们将使用train_test_split()函数将数据集分为个 DataFrame:不仅包括训练和测试数据,还包括独立和依赖变量。我们将先传入包含独立变量的 DataFrame,然后是包含依赖变量的 DataFrame。使用random_state参数,我们会为随机数生成器设置种子,以确保本示例的结果保持一致:

In[17]: X_train, X_test, y_train, y_test =
        model_selection.train_test_split(mpg[['weight']], mpg[['mpg']],
        random_state=1234)

默认情况下,数据以 75/25 的比例分割为训练和测试子集。

In[18]:  y_train.shape

Out[18]: (294, 1)

In[19]:  y_test.shape

Out[19]: (98, 1)

现在,让我们将模型拟合到训练数据上。首先,我们将使用LinearRegression()指定线性模型,然后使用regr.fit()训练模型。要获取测试数据集的预测值,我们可以使用predict()方法。这将得到一个numpy数组,而不是pandas DataFrame,因此head()方法不能用来打印前几行。不过,我们可以对其进行切片操作:

In[20]:  # Create linear regression object
         regr = linear_model.LinearRegression()

         # Train the model using the training sets
         regr.fit(X_train, y_train)

         # Make predictions using the testing set
         y_pred = regr.predict(X_test)

         # Print first five observations
         y_pred[:5]

Out[20]:  array([[14.86634263],
         [23.48793632],
         [26.2781699 ],
         [27.69989655],
         [29.05319785]])

coef_属性返回了我们测试模型的系数:

In[21]:  regr.coef_

Out[21]: array([[-0.00760282]])

要获取关于模型的更多信息,如系数的 p 值或 R 平方,请尝试使用statsmodels包进行拟合。

目前,我们将评估模型在测试数据上的表现,这次使用sklearnmetrics子模块。我们将实际值和预测值传递给r2_score()mean_squared_error()函数,分别返回 R 平方和 RMSE。

In[22]:  metrics.r2_score(y_test, y_pred)

Out[22]: 0.6811923996681357

In[23]:  metrics.mean_squared_error(y_test, y_pred)

Out[23]: 21.63348076436662

结论

这一章节的常规警告同样适用:我们只是触及了对这个或任何其他数据集可能进行的分析的表面。但我希望你感觉在使用 Python 处理数据方面已经步入佳境。

练习

再次查看ais数据集,这次使用 Python。从书籍仓库中读取 Excel 工作簿,并完成以下操作。现在你应该对这个分析已经非常熟悉了。

  1. 通过性别(sex)可视化红细胞计数(rcc)的分布。

  2. 两组性别之间的红细胞计数是否存在显著差异?

  3. 在该数据集中生成相关变量的相关矩阵。

  4. 可视化身高(ht)和体重(wt)之间的关系。

  5. wt回归到ht上。找出拟合回归线的方程。是否存在显著关系?

  6. 将回归模型分割成训练集和测试集。测试模型的 R 平方和 RMSE 是多少?

第十四章:结论与下一步

在前言中,我表明了以下学习目标:

在本书的最后,您应该能够使用编程语言进行探索性数据分析和假设检验

我真诚地希望您觉得这个目标已经实现,并且有信心进一步深入分析的领域。为了结束您的分析之旅的这一段,我想分享一些主题,帮助您完善和扩展您现在所知道的内容。

进一步的技术栈

第五章涵盖了数据分析中使用的四大类软件应用程序:电子表格、编程语言、数据库和 BI 工具。由于我们专注于统计学基础的分析元素,我们强调了技术栈的前两层。回顾该章节,以了解其他层次如何衔接以及关于它们的学习内容。

研究设计与商业实验

您在第三章中学到,良好的数据分析只能从良好的数据收集开始:正如俗语所说,“垃圾进,垃圾出”。在本书中,我们假设我们的数据被准确收集,是我们分析所需的正确数据,并且包含了代表性样本。我们一直在使用来自同行评审研究的知名数据集,所以这是一个安全的假设。

但通常您不能对数据如此确定;您可能需要负责收集分析数据。因此,值得进一步学习关于研究设计方法的内容。这个领域可以变得非常复杂和学术化,但在商业实验领域找到了实际应用。查阅斯蒂芬·H·汤姆克的《实验工作:商业实验的惊人力量》(哈佛商业评论出版社),了解如何以及为什么将良好的研究方法应用于业务。

进一步的统计方法

正如第四章提到的,我们仅仅触及了可用统计检验的表面,尽管其中许多都建立在假设检验的框架之上,该框架在第三章有所涵盖。

对于其他统计方法的概念性概述,请参阅萨拉·博斯劳的《Statistics in a Nutshell》(https://oreil.ly/gPll4),第二版(O'Reilly)。然后,前往彼得·布鲁斯等人的《Practical Statistics for Data Scientists》(https://oreil.ly/9LCBS),第二版(O'Reilly),使用 R 和 Python 应用这些方法。正如其标题所示,后一本书跨越了统计学和数据科学之间的界限。

数据科学与机器学习

第五章审查了统计学、数据分析和数据科学之间的差异,并得出结论,尽管这些领域的方法有所不同,但更多的是它们的共同点而非分歧。

如果您对数据科学和机器学习兴趣浓厚,应集中学习 R 和 Python,还应具备一些 SQL 和数据库知识。要了解 R 在数据科学中的应用,请参阅 Hadley Wickham 和 Garrett Grolemund(O'Reilly)的《R for Data Science》,链接在这里。对于 Python,请参阅 Aurélien Géron(O'Reilly)的《Hands-On Machine Learning with Scikit-Learn, Keras, and TensorFlow》(第二版),链接在这里

版本控制

第五章 也提到了可重复性的重要性。让我们看看在这场战斗中的一个关键应用。您可能以前遇到过以下类似的一组文件:

  • proposal.txt

  • proposal-v2.txt

  • proposal-Feb23.txt

  • proposal-final.txt

  • proposal-FINAL-final.txt

或许一个用户创建了 proposal-v2.txt;另一个开始了 proposal-Feb23.txt。然后,还有 proposal-final.txtproposal-FINAL-final.txt 之间的区别需要解决。很难弄清哪一个是“主要”副本,以及如何重建并迁移所有更改到该副本,同时记录谁贡献了什么。

版本控制系统 可以在这里提供帮助。这是一种跟踪项目的方法,例如不同用户进行的贡献和更改。版本控制对协作和跟踪修订具有革命性意义,但学习曲线相对较陡。

Git 是一种主流的版本控制系统,在数据科学家、软件工程师和其他技术专业人士中非常流行。特别是,他们经常使用基于云的托管服务 GitHub 来管理 Git 项目。有关 Git 和 GitHub 的概述,请参阅 Jon Loeliger 和 Matthew McCullough(O'Reilly)的《Version Control with Git》(第二版),链接在这里。如需了解如何将 Git 和 GitHub 与 R 和 RStudio 配对,请参阅 Jenny Bryan 等人的在线资源《Happy Git and GitHub for the useR》,链接在这里。目前在数据分析工作流中,Git 和其他版本控制系统相对不常见,但由于对可重复性的需求增加,它们正日渐流行起来。

伦理学

从记录和收集数据到分析和建模,数据被伦理问题所围绕。在第三章中,您了解到了统计偏差的问题:特别是在机器学习的背景下,模型可能会以不公正或非法的方式开始歧视某些群体。如果正在收集个人数据,则应考虑这些个人的隐私和同意问题。

在数据分析和数据科学中,道德并不总是优先考虑的问题。幸运的是,情况似乎在这里发生了变化,并且只会在持续的社区支持下继续下去。关于如何将伦理标准纳入数据处理工作的简要指南,请参阅 Mike Loukides 等人的《伦理与数据科学》(O’Reilly)。

出发吧,按照你自己的方式处理数据

经常有人问我,考虑到雇主需求和流行趋势,应该专注于这些工具中的哪一个。我的回答是:花些时间找出你喜欢的东西,让这些兴趣塑造你的学习路径,而不是试图迎合分析工具的“下一个大事”。这些技能都是非常有价值的。比任何一个分析工具更重要的是能够将这些工具放在背景中进行比较和配对,这需要接触广泛的应用。但你不可能成为所有事情的专家。最佳的学习策略将类似于“T”形:广泛接触各种数据工具,并在其中几个领域有相对深入的了解。

结语

请花一点时间回顾一下你通过这本书所取得的一切:你应该感到自豪。但不要停留:还有很多东西需要学习,在学习过程中不久你就会意识到这本书对你而言只是冰山一角。这是你的章节末和书末的练习:走出去,继续学习,并不断进步到分析领域。

posted @ 2024-06-17 17:12  绝不原创的飞龙  阅读(239)  评论(0)    收藏  举报