JHU--R-数据科学笔记-全-

JHU R 数据科学笔记(全)

001:为什么使用自动化视频? 🤖

在本节课中,我们将探讨约翰霍普金斯大学数据科学课程为何采用自动化视频制作流程,并了解其背后的技术原理与优势。

概述

您可能已经注意到,本课程的讲座和视频结构与许多其他大规模在线开放课程有所不同。视频内容与印刷版讲义几乎完全一致。我们制作本视频旨在解释我们做出这一改变的原因,并展示R语言与数据科学的强大能力。

课程维护的挑战

上一节我们介绍了课程形式的改变,本节中我们来看看促成这种改变的根本原因。

约翰霍普金斯大学数据科学实验室创建了大量大规模在线开放课程。过去五年中,我们在多个平台上创建了超过30门课程。我们的目标是为最广泛的受众提供最佳、最新的信息。然而,在线维护如此大量的材料面临重大挑战。

我们的软件包会过时,新的工作流程被发明,还有各种拼写错误。我们过去像许多其他大学一样制作课程:先创建讲座幻灯片形式的课程材料,然后录制我们讲授这些讲座的视频。

在某些方面,这很好。您能听到我们亲自讲授课程的声音。但缺点是,更新内容非常困难且耗时。我们需要预订录音室、设置特殊设备、录制讲座、编辑视频,然后将其上传到系统。结果是,我们的许多讲座内容已经过时、包含错误,或者没有包含最新、最佳的工作流程和管道。

寻找解决方案

面对日益增长的课程数量,保持内容更新对我们来说挑战越来越大。因此,我们开始思考如何解决这个具有挑战性的问题。

我们意识到,虽然录制和编辑视频极其耗时,但另一种类型的内容我们可以更频繁地编辑、更新和维护,那就是普通的纯文本文档。我们并非唯一想到这一点的人,像洛雷娜·巴尔巴这样的大规模在线开放课程创新者一直认为,这类课程甚至不需要视频。

新的课程构建流程

当我们着手开发创建和维护课程的新流程时,我们想看看是否能完全用纯文本文档制作课程。我们将大规模在线开放课程分解为基本元素。

以下是构成课程的核心组件:

  1. 教程:我们可以轻松地用Markdown或R Markdown等纯文本格式编写。如果使用Google幻灯片等工具制作,也易于维护和分享。
  2. 评估:在这里,我们可以使用标记语言来创建测验和其他评估。
  3. 视频:这是难点。我们如何从纯文本文档制作视频?

自动化视频制作技术

一个愉快的巧合是,数据科学和人工智能社区正在为我们解决这个问题的很大一部分:改进文本到语音的合成技术。因此,我们现在可以为视频编写脚本,并使用Amazon Polly来合成我们的声音。

为了利用这项新技术,我们创建了两个新的R包:AriDctor

  • Ari 会获取一个脚本和一组Google幻灯片,并使用Amazon Polly在幻灯片上叙述脚本。它还会生成包含字幕所需的隐藏字幕文件,确保视频对听力障碍者是可访问的。
  • Dctor 自动化了从使用Ari创建视频到将其上传到YouTube的多个步骤。

这样,我们就可以快速修改脚本或幻灯片、重新制作视频、重新上传,从而减少维护开销以保持内容新鲜。每当我们更改文本文件或编辑幻灯片时,我们都可以在几分钟内重新创建视频。所有操作都在R中完成。

新流程的优势

转向这个新流程最酷的功能之一是向您展示R编程语言有多么强大。这是您在本课程中将学习的主要语言,我们希望您在完成我们的课程时,也能构建出像这个系统一样酷的东西。

我们选择这种方法,而不是单独创建课程的每个部分,原因如下:

以下是采用自动化流程的三个主要优势:

  1. 易于维护和更新:如果您报告问题或发现课程有错误,我们只需要更改脚本或Google幻灯片并重新创建课程即可。因此,我们有了更高效的课程内容维护和更新方式。
  2. 提高可访问性:由于视频有文字记录,而文字记录有配音,因此内容对我们中间有残疾的人来说是可访问的。对于其他人,您可以根据自己的意愿选择阅读、听或观看内容。
  3. 持续改进与扩展性:使用文本到语音合成的一个很酷的功能是,随着语音合成软件的改进,我们的视频会不断变得更好。这意味着我们可以将声音更改为不同的声音。最终,它将允许我们使用机器学习快速、自动地将我们的课程翻译成不同的语言。

总结与展望

我们认为这凸显了数据科学和人工智能改善世界的不可思议的力量。如果您觉得机器人声音令人讨厌,我们理解。我们知道这项技术尚不完美。这就是为什么我们让书面讲座材料尽可能接近视频讲座,以便您可以选择如何学习我们的课程。

我们希望这一改变能让我们以最快的速度为您提供最好的内容。感谢您与我们共同参与课程开发的这个新阶段。

002:什么是数据科学?📊

在本节课中,我们将要学习数据科学的基本概念,了解其核心定义、兴起原因、关键特征以及数据科学家所需的技能。我们还将探讨数据科学在当今社会中的广泛应用和巨大需求。


概述

数据科学是一个利用数据来回答问题的跨学科领域。它融合了统计学、计算机科学、数学、数据清理与格式化以及数据可视化等多种技能。近年来,由于海量数据的产生和廉价计算能力的提升,数据科学领域得以迅猛发展。

数据科学的定义与核心

数据科学的核心是使用数据来回答问题。这是一个非常广泛的定义,因为数据科学本身就是一个广阔的领域。

经济学家的一份特别报告很好地总结了数据科学家所需的技能组合:数据科学家被广泛定义为结合了软件程序员、统计学家和故事讲述者/艺术家技能的人,他们能够从海量数据中挖掘出隐藏的宝贵信息。

数据科学兴起的原因

数据科学近年兴起的主要原因之一是当前可用和正在生成的数据量极其庞大。

不仅关于世界和我们生活的方方面面正在收集海量数据,同时廉价计算能力也在崛起。这创造了一场“完美风暴”:我们拥有丰富的数据和分析这些数据的工具。

计算机内存容量提升、处理器性能更好、软件更丰富,现在又有了更多掌握技能的数据科学家来利用这些资源,通过数据回答问题。

有一个小故事描述了我们现在经历的数据生成的真正指数级增长:在公元前三世纪,亚历山大图书馆被认为收藏了人类知识的全部总和。而今天,世界上的信息量足以让每个在世的人获得320倍于历史学家估计的亚历山大馆藏信息量,并且这个数字仍在增长。

大数据简介

我们将在后续课程中更详细地讨论大数据,但在此值得介绍,因为它对数据科学的兴起至关重要。大数据有几个特征性的品质。

以下是描述大数据的关键特征:

  1. 体量:顾名思义,大数据涉及大型数据集,这些大型数据集正变得越来越普遍。例如,如果你想研究在线视频,YouTube每分钟大约有300小时的视频上传。你肯定有大量数据可供分析,但也可以看出处理所有这些数据可能是一个难题。
  2. 速度:数据生成和收集的速度比以往任何时候都快。在我们YouTube的例子中,新数据每分钟都在向你涌来。另一个完全不同的例子是,如果你有关于运输时间或路线的问题,大多数运输卡车都有实时GPS数据。你可以实时分析卡车的移动,前提是你拥有这样做的工具和技能。
  3. 多样性:在我目前提到的例子中,你有不同类型的数据可用。在YouTube的例子中,你可以分析视频或音频(这是非常非结构化的数据集),或者你可以分析视频时长、观看次数或评论的数据库(这是结构化得多的数据集)。

数据科学家的技能

我们已经讨论了数据科学是什么以及它处理的数据类型,但我们还需要讨论数据科学家到底是什么。最基本的定义是:数据科学家是使用数据回答问题的人

但更重要的是,数据科学家具体需要哪些技能?为了回答这个问题,我们来看这个说明性的维恩图,其中数据科学是三个领域的交集:领域专业知识、黑客技能以及数学和统计学知识

为了解释这一点,我们知道我们使用数据科学来回答问题。因此,首先我们需要对我们想要提问的领域有足够的专业知识,以便提出我们的问题,并知道哪些类型的数据适合回答该问题。

一旦我们有了问题和合适的数据,我们从数据科学处理的数据类型中知道,数据通常需要经过大量的清理和格式化,这通常需要计算机编程/黑客技能。

最后,一旦我们有了数据,我们需要分析它,这通常需要数学和统计知识。

在本系列课程中,我们将花一些时间关注这三个领域,但我们将主要关注数学和统计知识以及黑客技能。对于黑客技能,我们将重点教授两个不同的组成部分:计算机编程(至少是使用R语言的编程),这将使你能够访问数据、处理数据、分析数据和绘制图表。此外,我们将重点让你学习如何去寻找编程问题的答案。数据科学家需求如此之大的一个原因是,大多数答案并未在教科书中预先列出。数据科学家需要知道如何找到新问题的答案。

数据科学的需求与应用

说到需求,市场对具备数据科学技能的个人需求巨大。

根据LinkedIn的数据,机器学习工程师、数据科学家和大数据工程师是2017年新兴的顶级工作之一,但需求远远超过供给。他们指出,自2012年以来,数据科学家职位增长了650%以上,但目前美国只有35,000人具备数据科学技能,而成百上千的公司正在招聘这些职位,甚至包括零售和金融等你可能意想不到的行业。这些职位的候选人供应无法跟上需求。

现在是进入数据科学领域的好时机。我们不仅拥有越来越多的数据,以及越来越多的收集、存储和分析工具,而且数据科学家的重要性在许多不同的行业(不仅仅是商业和学术界)正日益得到认可。

此外,根据Glassdoor的排名(基于工作满意度、薪资和需求),数据科学家是2017年美国最佳工作第一名。

数据科学应用的行业多样性可以通过查看数据科学家的例子来体现。我们可能不会立即认识到数据科学需求的一个领域是体育。达里尔·莫雷是美国篮球休斯顿火箭队的总经理。尽管没有深厚的篮球背景,莫雷凭借其计算机科学学士学位和麻省理工学院的MBA学位获得了总经理的职位。他被选中是因为他收集和分析数据并利用这些数据做出明智招聘决策的能力。

另一个你可能听说过的数据科学家是希拉里·梅森。她是机器学习公司Fast Forward Labs的联合创始人(该公司最近被数据科学公司Cloudera收购),并且是Accel的数据科学家常驻研究员。她广泛地利用数据来回答关于网络挖掘以及理解人类通过社交媒体互动方式的问题。

最后,内特·西尔弗是当今世界上最著名的数据科学家/统计学家之一。他是FiveThirtyEight网站的创始人和主编,该网站使用统计分析、硬数据来讲述关于选举、政治、体育、科学、经济和生活方式的有说服力的故事。他利用大量完全免费的公共数据对各种主题进行预测,最著名的是预测美国谁将赢得选举,并且在这方面有着惊人的准确记录。

数据科学实践案例

数据科学实践的一个很好的例子来自2009年,当时谷歌的研究人员分析了五年内5000万个常用搜索词,并将其与美国疾病控制与预防中心关于流感爆发的数据进行了比较。

他们的目标是观察某些搜索是否与流感爆发同时发生。数据科学和使用大数据的好处之一是它可以识别相关性。在这种情况下,他们识别出了45个与CDC流感爆发数据有强相关性的词语。利用这些数据,他们能够仅根据常见的谷歌搜索来预测流感爆发。如果没有这些海量数据,这45个词语是无法事先预测的。

本课程内容预告

现在你已经对数据科学有了初步了解,这里真正需要涵盖的只剩下对本课程将要教授内容的总结。

首先,我们将介绍R语言的基础知识。R是我们在本课程系列中将使用的主要编程语言,因此扎实理解它是什么、如何工作以及如何在你的计算机上安装是必须的。

然后,我们将过渡到RStudio,这是一个非常友好的R图形界面,应该能让你的工作更轻松。

接着,我们将讨论版本控制,为什么它很重要,以及如何将其整合到你的工作中。

一旦你掌握了所有这些基础知识,你就准备好应用这些工具来回答你自己的数据科学问题了。

期待与你一起学习,让我们开始吧!


总结

本节课中,我们一起学习了数据科学的基本概念。我们了解到数据科学的核心是使用数据回答问题,它是一个融合了领域专业知识、编程技能和数理统计的交叉学科。数据科学的兴起得益于大数据(具备体量、速度和多样性特征)的涌现和计算能力的提升。数据科学家是当今社会的紧缺人才,其技能在体育、商业、公共卫生等众多领域都有广泛应用。最后,我们预览了本系列课程将重点学习的工具:R语言、RStudio和版本控制,为后续的实践打下基础。

003:什么是数据?📊

在本节课中,我们将深入探讨“数据”这一核心概念。我们将了解数据的定义、构成要素,并观察数据在现实世界中的多种表现形式。理解数据的本质是学习数据科学的第一步。


数据定义:两种视角

上一节我们讨论了数据科学是什么,本节中我们来看看数据本身的确切含义。以下是两个权威来源对数据的定义。

剑桥英语词典的定义:

数据是信息,特别是为被检查、考虑并用于辅助决策而收集的事实或数字。

维基百科的定义:

数据是一组定性或定量变量的值。

这两个定义略有不同,但都抓住了数据的关键部分。剑桥定义侧重于围绕数据的行动:数据被收集、检查,并最终用于决策。这与我们之前强调的“问题驱动”理念一致。维基百科定义则更侧重于数据的构成,它虽然简短,但内涵丰富。


解析数据的构成要素

根据维基百科的定义,我们可以将数据拆解为三个核心部分。

1. 一组值
要拥有数据,你需要一组被测量的项目。在统计学中,这组项目通常被称为 总体。你试图从这整个集合中发现某些信息。

2. 变量
变量是对项目中某个特征的测量。例如,对于“人”这个项目,变量可以是身高、国籍或性别。

3. 变量的类型
变量主要分为两类:

  • 定性变量:描述“品质”的信息。通常用文字描述,不一定有顺序。
    • 示例:country_of_origin = “中国”sex = “女”treatment_group = “对照组”
  • 定量变量:描述“数量”的信息。通常用数字描述,并在连续有序的尺度上测量。
    • 示例:height = 175.5weight = 65.2blood_pressure = 120

综合来看,数据就是对一组项目进行的定性或定量测量。这是一个相当不错的定义。


现实世界中的数据:混乱是常态

在我们讨论定义时,使用的例子(如国籍、身高)都非常基础,很容易想象它们被整齐地排列在一个表格中,个体在行,变量在列。

然而,这很少是数据呈现给你的方式。我们通常遇到的数据集要混乱得多。数据科学家的任务就是从这些混乱的数据源中提取所需信息,将其整理成类似上文的整洁表格,进行恰当分析并可视化结果。

以下是几种常见的数据源及其形态:

1. 测序数据
我经常处理的一类数据是基因测序数据。这种数据最初以FASTQ格式出现,这是测序机器产生的原始文件格式。这些文件通常长达数亿行,我们的任务是将其解析成可理解、可解释的格式,并推断个体基因组的某些信息。

2. 人口普查数据
全国人口普查是一个丰富的信息源。几乎所有国民都会回答一组标准化问题并将答案提交给政府。当受访者如此之多时,数据量巨大且混乱。但一旦这个大型数据库可供查询,其中蕴含的答案就非常重要。

3. 电子病历
这是一个电子病历的模拟示例。这是存储健康信息的流行方式,越来越多的基于人群的研究使用这些数据来回答问题、对广大群体做出推断,或作为改进医疗保健方法的手段。

4. 图像/视频数据
图像或视频中编码着大量信息,正等待被提取。一个熟悉的图像分析例子是,当你将照片上传到Facebook时,它不仅自动识别图片中的人脸,还会建议他们可能是谁。


重申核心:问题先行

认识到我们已经花了大量时间讨论数据是什么,我们需要重申:数据很重要,但它次于你的问题

一个好的数据科学家先提出问题,再寻找相关数据。诚然,可用的数据常常会限制甚至决定你能提出哪些问题。在这些情况下,你可能需要重新构建问题或回答一个相关问题。但数据本身不应驱动提问


课程总结

本节课中,我们一起学习了数据的核心概念。

首先,我们看了两个数据定义:一个侧重于围绕数据的行动,另一个侧重于数据的构成。第二个定义包含了总体、变量的概念,并区分了定量和定性数据。

其次,我们考察了可能遇到的不同数据源,并强调了整洁数据集的缺乏。需要被“驯服”成可解释形式的混乱数据集示例包括测序数据、人口普查数据、电子病历等。

最后,我们回到了关于数据与问题之间关系的信念,并强调了“问题先行”策略的重要性。你可以拥有梦寐以求的所有数据,但如果你没有一个问题作为起点,这些数据就毫无用处。

004:获取帮助 🆘

在本节课中,我们将学习作为一名数据科学家,当遇到问题时如何有效地寻求和获取帮助。解决问题是数据科学的核心技能之一,因此掌握高效的求助策略至关重要。

为什么需要学习求助技能?🤔

上一节我们介绍了课程目标,本节中我们来看看为什么掌握求助技能如此重要。

首先,本课程与传统课堂不同,可能同时有数千名学生参与,无法像小班教学那样随时获得教授的即时帮助。因此,你需要掌握在大型在线课程中自助和互助的策略。

其次,数据科学是一个新兴领域,你可能是第一个遇到某个特定问题的人。你需要具备独立解决对你乃至整个社区而言都是新问题的能力。

最后,故障排除和解决问题的能力是一项极佳的、可迁移的技能。它不仅对数据科学家的工作有益,而且任何职业都涉及大量问题解决。能够有效思考和寻求帮助,对你未来的任何职业道路都有好处。

自助策略:先尝试自己解决 🔍

在向他人求助之前,你可以先尝试自己解决。很多时候,最快的答案是你自己找到的。

以下是遇到数据分析问题时可以采取的初步步骤:

  • 查阅手册或帮助文件:对于R语言问题,可以尝试输入 ?命令名 来查看帮助。如果在论坛上提问一个手册里就有答案的问题,你很可能只会得到“去读手册”的回复。
  • 使用搜索引擎:在谷歌上搜索相关问题。
  • 搜索相关论坛:常见的数据科学问题论坛包括 Stack Overflow 和 Cross Validated。此外,本课程也设有课程论坛,这是一个极佳的资源。

在向任何论坛发帖提问前,请务必使用论坛的搜索功能,确认你的问题是否已经被问过并得到了解答。

在搜索时,请留意与你遇到问题的命令或程序相关的教程、常见问题解答(FAQ)或说明文档(vignettes)。这些是很好的资源,它们要么能告诉你下一步搜索该用什么关键词,要么直接展示如何操作。

处理常见的编码问题 💻

随着课程深入和使用R语言,你可能会遇到编码问题和错误。你应该准备好一些策略来应对它们。

根据我的经验,编码问题通常分为两类:

  1. 命令不产生数据,而是输出错误信息
  2. 命令产生了输出,但结果完全不是你想要的

这两类问题有不同的处理策略。

如果你遇到了错误信息,请先检查命令是否有拼写错误,然后仔细阅读错误信息。在绝大多数情况下,这就能解决问题。错误信息是计算机在告诉你哪里出错了,目的是帮助你。如果其他方法都失败了,你几乎可以肯定,世界上某个地方也有人遇到了同样的错误,惊慌失措,并把它发到了论坛上,答案就在那里。

另一方面,如果你得到了一个输出,请思考这个输出与你期望的有何不同,并分析这个命令实际上做了什么,以及它为什么会那样做而不是你想要的。大多数这类问题是因为你提供的命令指示程序做了一件事,而它也确实照做了,只是结果证明你让它做的并不是你真正想要的。这类问题往往最令人沮丧,因为你离成功很近却又很远。它们能很好地锻炼你像计算机程序一样思考。

寻求他人帮助:同伴与“橡皮鸭” 🦆

好了,你已经做了所有该做的来尝试自己解决问题。现在,是时候请外援了——其他人。

最简单的方法是找一位对你正在处理的问题有经验的同伴,向他们寻求帮助或指点。这通常很棒,因为解释者在教你的过程中能巩固自己的理解,而你也能亲身体验他们是如何解决问题的。在本课程中,你的同伴可以是你的同学,你们可以通过课程论坛互动。请再次确认你的问题没有被问过。

在本课程之外,你可能没有那么多懂数据科学的朋友。那该怎么办?“橡皮鸭调试法”是各地独行程序员的一个悠久而健康的传统。在《程序员修炼之道》这本书中,有一个故事讲述了束手无策的程序员如何向橡皮鸭解释他们的问题,并在解释过程中找到了解决方案。维基百科解释得很好:许多程序员都有过这样的经历:向别人(甚至可能是一个完全不懂编程的人)解释一个编程问题,然后在解释问题的过程中突然想到了解决方案。在描述代码应该做什么和观察它实际做什么的过程中,两者之间的任何不一致都会变得明显。所以,下次你被难住时,拿出你的洗澡玩具吧。

如何在论坛有效提问?📝

你已经尽力了,搜索了又搜索,和同伴讨论过,尝试了所有可能的方法来自行解决,但仍然卡住了。是时候把你的问题发布到相关论坛了。

在你直接发帖提问之前,你需要考虑如何最好地提问以获得有用的答案。

尝试包含以下细节:

  • 一个你试图回答的非常具体的问题,以及你在故障排除中已经采取的步骤。
  • 关于如何重现问题的细节,并包含供故障排除者使用的示例数据
  • 你的目标和期望输出是什么,以及你的实际输出的详细信息。
  • 如果你收到了错误信息,一定要在帖子中提到。
  • 关于你的操作系统或相关软件版本的细节也常常对潜在的问题解决者有帮助。

你帖子中最重要的细节之一是标题。它向他人传达了你遇到的麻烦。为帖子起标题是一门艺术。标题不具体,你就没有给潜在的帮助者提供太多线索,他们不知道问题到底是什么,以及他们是否能帮助你。相反,你需要提供一些关于你遇到问题的细节,回答“你在做什么”和“问题是什么”是两个你需要提供的关键信息。这样,论坛上的人就能确切地知道发生了什么,并且他们可能能够提供帮助。使用那些聚焦于你试图获得帮助的非常具体的核心问题的标题。这向人们表明你正在寻找一个非常具体的答案。问题越具体,通常得到答案的速度就越快。

论坛礼仪与最佳实践 ✅

遵循到目前为止提到的所有技巧,将有助于你在论坛上发帖并遵守论坛礼仪。你是在寻求帮助,希望别人抽出时间来帮助你,你需要有礼貌。

这通常表现为:提出具体问题,自己先做一些故障排除,并为潜在的问题解决者提供轻松获取所有所需信息的途径。

将这些“该做”和“不该做”的事情规范化,你就能得到一些可以遵循的准则。

发帖前

  • 确保你在合适的论坛提问,并阅读论坛的发帖指南。
  • 确保你描述了你的目标,并在解释问题和迄今为止的解决步骤时明确且详细
  • 提供描述和重现问题所需的最少信息,不要用无关的细节拖累别人。

最后是两大原则:

  1. 要有礼貌,这些人是在帮助你。
  2. 确保跟进你的帖子并发布解决方案。不仅帮助过你的人值得感谢,这对以后遇到同样问题的其他人也很有帮助。

也有一些相当明确的不该做的准则:

  • 首先,没有人愿意帮助那些认为问题的根源不是自己犯了错误,而是程序有问题的人。(剧透警告:几乎总是因为你犯了错误。)
  • 同样,没有人愿意替你完成作业。他们想帮助那些真正想学习的人,而不是找捷径的人。
  • 此外,对于活跃在多个论坛上的人来说,当同一个人在五个不同的论坛上发布同一个问题,或者同一个问题在同一论坛上被反复发布时,总是令人恼火。要有耐心,为你的目的选择最相关的论坛,发布一次,然后等待。

总结 📚

本节课中,我们一起学习了在遇到问题时如何有效地获取帮助。这对本课程很重要,对你作为数据科学家的未来也很重要。

我们首先看了在求助前可以使用的策略,包括阅读手册、查看帮助文件、搜索谷歌和适当的论坛。我们还介绍了一些你可能面临的常见编码问题,以及你可以自己采取的一些初步步骤,包括特别注意错误信息,以及将你的代码行为与你的目标进行比较。

一旦用尽了这些选项,我们就转向向他人求助。我们可以向同伴求助,或者向我们可靠的“橡皮鸭”(无论是真正的橡皮鸭还是不知情的同事)解释我们的问题。我们的课程论坛也是你们所有人互相交流的绝佳资源。

如果所有其他方法都失败了,我们可以在论坛上发帖,无论是在本课程中还是在像 Stack Overflow 这样的其他论坛上,提出非常具体且可重现的问题。在这样做之前,请务必温习你的论坛礼仪。礼貌待人永远不会伤害任何人,做一个我们论坛的好公民。

解决问题是一门艺术,而获得实践的唯一方法就是走出去,开始解决问题。开始行动吧!

005:数据科学过程 🧭

在本节课中,我们将学习一个完整的数据科学项目包含哪些步骤。我们将通过一个实际案例,分解典型项目的各个组成部分,并了解数据科学家如何从提出问题到最终分享成果。


在前几节课中,我们讨论了数据和数据科学的定义以及寻求帮助的方法。我们尚未涉及的是一个实际的数据科学项目具体是怎样的。

为此,我们将首先剖析一个真实的数据科学项目,分解典型项目的各个部分,然后提供一些其他有趣数据科学项目的链接。本节课的目标是让大家了解执行数据科学项目时所经历的过程。

数据科学项目的核心步骤

每个数据科学项目都始于一个需要用数据来回答的问题。这意味着,明确问题是过程中的重要第一步。

第二步是寻找或生成你将用来回答该问题的数据。

在问题明确且数据在手之后,就可以开始分析数据了。首先是对数据进行探索,然后通常会对数据进行建模,这意味着使用一些统计或机器学习技术来分析数据并回答你的问题。

从分析中得出结论后,必须将项目成果传达给他人。有时这是你发送给老板或工作团队的报告,有时是一篇博客文章,通常也可能是向同事小组进行的演示。无论如何,数据科学项目几乎总是涉及以某种形式传达项目发现。

下面我们将通过一个数据科学项目示例来逐步讲解这些步骤。

案例研究:希拉里是最“毒”的婴儿名吗?

在这个示例中,我们将使用一位名叫希拉里·帕克的数据科学家的分析案例。她的工作可以在她的博客上找到。

我们将要研究的这个具体项目来自2013年,标题为“希拉里:美国历史上最‘毒’的婴儿名”。为了从本节课中获得最大收益,请点击该链接并阅读希拉里的博文。完成后,请返回本节课并阅读对该博文的分解分析。

第一步:明确问题

开始一个数据科学项目时,最好能清晰地定义你的问题。在分析过程中可能会冒出其他问题,但明确你想通过分析回答什么是非常重要的第一步。

希拉里·帕克的问题在她的博文中以粗体标出,这清楚地表明她有兴趣回答以下问题:希拉里(Hillary/Hilary)真的是美国有记录以来流行度下降最快的名字吗?

第二步:获取数据

为了回答这个问题,希拉里从社会保障局网站收集了数据。该数据集包含了从1880年到2011年每年最流行的1000个婴儿名字。

第三步:分析与计算

正如博客文章中所解释的,希拉里感兴趣的是计算她数据中4110个不同名字从1880年到2011年每年到下一年之间的相对风险。手动计算这将是一场噩梦。幸运的是,通过在R中编写代码(所有代码都在GitHub上可用),希拉里能够为所有这些名字在所有这些年里生成这些数值。

目前并不需要完全理解相对风险计算是什么(尽管希拉里在她的帖子中做了很好的分解),但重要的是要知道,在整理好数据之后,下一步就是弄清楚你需要对这些数据做什么才能回答你的问题。

对于希拉里的问题,计算每个名字从1880年到2011年每年到下一年之间的相对风险,并查看每个名字在特定年份的婴儿占比,就是她为回答问题需要做的事情。

你在博客文章中看不到的是希拉里编写的所有代码:从社会保障局网站获取数据、将其转换为分析所需的格式以及生成图表。如上所述,她将所有代码都放在了GitHub上,以便其他人可以看到她的工作并在需要时重复她的步骤。

除了这些代码,数据科学项目通常还涉及编写大量代码和生成许多最终结果中未包含的图表。这也是数据科学过程的一部分:弄清楚如何做你想做的事情来回答你感兴趣的问题是过程的一部分,它并不总是出现在你的最终项目中,并且可能非常耗时。

第四步:探索与验证

鉴于希拉里现在已经计算出了必要的数值,她开始分析数据。她做的第一件事是查看从一年到下一年百分比下降幅度最大的名字。

通过这个初步分析,希拉里排名第六,这意味着有五个其他名字的受欢迎程度单年下降幅度超过了希拉里这个名字在1992年到1993年间的下降幅度。

在查看分析结果时,前五个名字对希拉里·帕克来说显得很奇特。在任何分析中,考虑结果是否符合你的预期总是好的。它们似乎都不是长期流行的名字。为了验证这个直觉是否正确,希拉里绘制了每年出生的婴儿中拥有这些名字的百分比图表。

她发现,在这些“有毒”的名字(即受欢迎程度从一年到下一年大幅下降的名字)中:


除了希拉里之外的名字都是突然流行起来,然后受欢迎程度就下降了。希拉里·帕克能够找出为什么大多数其他名字会流行起来(所以一定要阅读她帖子中的那个部分)。

然而,希拉里这个名字则不同。它流行了一段时间,然后完全失去了受欢迎程度。为了弄清楚希拉里这个名字具体发生了什么,她剔除了那些在下降前只流行了很短时间的名字,只关注那些在1000名内停留超过20年的名字。

这项分析的结果明确显示,在1880年至2011年间,希拉里是1992年所有女性婴儿名中受欢迎程度下降最快的。玛丽昂的下降则是多年逐渐发生的。


第五步:沟通与分享

在这个数据分析过程的最后一步,一旦希拉里·帕克回答了问题,就是时候与世界分享了。任何数据科学项目的一个重要部分是有效地传达项目结果。

希拉里通过撰写一篇精彩的博客文章来做到这一点,传达了分析结果,回答了她最初提出的问题,并且以一种有趣的方式完成。

此外,重要的是要注意,大多数项目都是建立在他人工作的基础上的。给予这些人信任非常重要。希拉里通过链接到之前有人提出类似问题的博客文章、她获取数据的社会保障局网站以及她学习网络爬虫的地方来做到这一点。

用R构建的数据科学项目示例

希拉里的工作是使用R编程语言完成的。在本系列课程中,你将学习R编程的基础知识、探索和分析数据,以及如何构建报告和网络应用程序,以便有效地传达你的结果。

为了给你一个使用R编程和一系列可用工具可以构建的东西的例子,以下是使用数据科学过程和R编程语言构建的一些示例,也是你在本系列课程结束时能够生成的东西的类型。

宾夕法尼亚大学的研究生着手预测罗德岛普罗维登斯阿片类药物过量的风险。他们详细介绍了他们使用的数据、清理数据的步骤、可视化过程以及最终结果。虽然细节现在不重要,但了解过程和可以生成的报告类型很重要。

此外,他们创建了一个Shiny应用程序,这是一个交互式网络应用程序。这意味着你可以选择要关注的普罗维登斯社区。所有这些都是使用R编程构建的。

以下是比上述示例规模更小的项目,但同样是数据科学项目。在每个项目中,作者都有一个他们想回答的问题,并使用数据来回答。他们探索、可视化并分析了数据,然后撰写博客文章来传达他们的发现。请查看以了解更多关于所列主题的信息,并了解其他人如何完成数据科学项目过程并传达他们的结果。

  • 天气偏好分析:My Al Salmon试图使用数据来查看,根据天气偏好,一个人应该住在美国哪里。
  • 特朗普推文分析:David Robinson对特朗普的推文进行了分析,以表明特朗普只亲自撰写那些更愤怒的推文。
  • 多伦多性健康诊所地图:Charlotte Geellfin使用多伦多市提供的开放数据构建了一个包含性健康诊所信息的地图。

总结 📝

在本节课中,我们希望我们已经传达出,有时数据科学项目处理的是困难的问题(例如“我们能预测阿片类药物过量的风险吗?”),而其他时候,项目的目标则是回答你个人感兴趣的问题(例如“希拉里是美国有记录以来最‘毒’的婴儿名吗?”)。

无论哪种情况,过程都是相似的:你必须形成你的问题、获取数据、探索和分析你的数据,并传达你的结果。通过本系列课程中学到的工具,你将能够着手并执行自己的数据科学项目,就像本节课中包含的示例一样。

006:安装R 🛠️

在本节课中,我们将学习如何安装R编程语言。首先,我们会回顾R是什么以及为什么它对于数据科学如此重要。接着,我们将详细介绍在Windows和Mac操作系统上安装R的具体步骤。

什么是R以及为何使用它

上一节我们探讨了数据科学家的角色,现在我们来具体了解一个核心工具。R既是一种编程语言,也是一个专注于统计分析和图形绘制的环境。它将是本课程及后续课程中使用的主要工具之一。

R可以从综合R档案网络(CRAN)下载。虽然这可能是你第一次接触CRAN,但在我们后续安装各种功能包时,会反复回到这个网站,所以请多加留意。

你可能会问,为什么应该使用R?以下是几个关键原因:

  • 流行度高:R正迅速成为统计分析的标准语言。软件越流行,新功能的开发速度就越快,软件本身也越强大,社区支持也越好。
  • 就业需求:如下图所示,掌握R是数据科学家职位招聘中最常要求的五大技能之一。
  • 完全免费:R的所有方面都可以免费使用,不像其他一些统计软件(例如SAS或SPSS),不存在使用成本障碍。
  • 功能广泛:R是一种非常通用的语言。除了统计和绘图,它的用途还可以扩展到许多不同领域,例如制作网站、使用地理空间数据制作地图、进行文本分析,甚至制作教学视频。下图展示了一张用R绘制的欧洲人口密度图,每个点代表50人。无论你有什么任务,通常都能找到可以下载的R包来实现它。

R功能如此广泛的原因在于其强大的社区。许多人聚集在一起,开发了众多扩展R功能的软件包,并且每天都有新的包在开发。对于R初学者来说,这个社区是一个巨大的优势。由于其流行度,存在多个论坛,里面有大量页面专门解决R相关问题。我们在“获取帮助”课程中讨论过,这些论坛非常适合寻找与你遇到相同问题的人,以及发布你自己的新问题。

安装R:Windows系统指南

既然我们已经了解了R的优势,现在就是安装它的时候了。以下将分别介绍Windows和Mac系统的安装指南。请注意,这些是一般性指导,具体细节可能会随软件更新而变化,请将此作为安装的基本框架。

对于Windows和Mac电脑,我们都从CRAN主页开始。

如果你使用的是Windows电脑,请按照以下步骤操作:

  1. 访问CRAN网站。
  2. 点击“Download R for Windows”链接。
  3. 如果是第一次安装R,请进入“base”分发版。
  4. 点击页面顶部类似“Download R [版本号] for Windows”的链接。这将下载一个可执行的安装文件。
  5. 打开可执行文件。如果出现安全警告提示,请允许它运行。
  6. 在安装过程中选择你偏好的语言,并同意许可信息。
  7. 接下来会提示选择目标位置。默认位置通常是Program Files下的一个名为R的子文件夹,里面还有一个对应版本号的子目录。除非你有特殊原因,否则默认位置是完美的选择。
  8. 然后会提示选择要安装的组件。除非存储空间紧张,否则建议安装所有组件。
  9. 接着会询问启动选项,同样,默认设置即可。
  10. 之后会询问安装程序应将快捷方式放在何处。这完全取决于你:你可以允许它添加到开始菜单,也可以勾选底部的“Do not create a Start Menu link”框。
  11. 最后,会询问是否需要创建桌面或快速启动图标,这由你决定。不过,不建议更改注册表项的默认设置。

完成此窗口设置后,安装将开始。安装完成后,首次打开R以测试安装是否成功。

安装R:Mac系统指南

如果你使用的是Mac电脑,请按照以下步骤操作:

  1. 访问CRAN网站。
  2. 点击“Download R for (Mac) OS X”链接。
  3. 在那里你可以找到各种R版本供下载。注意:如果你的Mac系统版本早于OS X 10.6 (Snow Leopard),则需要按照该页面上的说明下载与这些旧操作系统兼容的旧版本R。
  4. 点击最新版本R的链接,这将下载一个.pkg文件。
  5. 打开.pkg文件,并按照安装程序提供的提示进行操作。
  6. 首先,在欢迎页面点击“继续”,在重要信息窗口页面再次点击“继续”。
  7. 接下来会呈现软件许可协议,点击“同意”。
  8. 然后可能会要求你为R选择安装目标:可供所有用户使用或安装到特定磁盘。选择你认为最适合你设置的选项。
  9. 最后,你将进入标准安装页面。R会选择默认目录,如果你对该位置满意,请点击“安装”。
  10. 此时,系统可能会提示你输入管理员密码。输入密码后,安装将开始。

安装完成后,前往你的“应用程序”文件夹找到R。首次打开R以测试安装是否成功。

总结

本节课中,我们一起学习了R是什么以及为什么我们可能想要使用它。然后,我们重点介绍了在Windows和Mac电脑上安装R的过程。在进入下一讲之前,请确保你已经正确安装并运行了R。

007:安装RStudio 🛠️

在本节课中,我们将学习如何安装RStudio。RStudio是一个强大的图形用户界面,能极大地提升使用R语言进行数据科学工作的效率和体验。

我们已经安装了R,并且可以通过R的界面输入代码。但是,还有其他与R交互的方式,其中一种就是使用RStudio。本节我们将指导你在电脑上安装RStudio。

RStudio是R的图形用户界面,它允许你编写、编辑和存储代码,生成、查看和存储图表,管理文件、对象和数据框,以及集成版本控制系统等。在后续课程中,我们将详细探索RStudio的功能。对于任何刚开始学习R编程的人来说,这个程序作为R的交互界面,其可视化特性是一个巨大的优势。


安装过程概述

RStudio的安装过程相当直接。首先,你需要访问RStudio的下载页面。我们需要下载RStudio的桌面版软件。

以下是安装步骤的简要列表:

  1. 访问RStudio官网下载页面。
  2. 在“RStudio Desktop”标题下,点击相应的下载链接。
  3. 根据你的操作系统(Windows或Mac),选择对应的安装程序。
  4. 运行下载的安装文件,并按照向导提示完成安装。

现在,让我们分别看看Windows和Mac系统的具体安装步骤。


Windows系统安装步骤

对于Windows用户,请按照以下步骤操作:

  1. 在下载页面的“Installers for Supported Platforms”列表中,选择适用于Windows各版本(Vista/7/8/10)的RStudio安装程序。这将开始下载过程。
  2. 下载完成后,打开这个可执行文件以启动安装向导。此时你可能会看到一个安全警告,请允许它对你的计算机进行更改。
  3. 安装向导打开后,在每个窗口中使用默认设置进行安装是合适的。
    • 在欢迎界面,点击“下一步”。
    • 如果你想将RStudio安装到其他位置,请浏览你的文件系统进行选择。否则,它可能会默认安装到“Program Files”文件夹,这是合适的。
    • 在此页面,点击“下一步”。
    • 在最后一页,允许RStudio创建开始菜单快捷方式,然后点击“安装”。
  4. RStudio现在正在安装。请等待此过程完成。
  5. RStudio现已安装到你的电脑上。点击“完成”。
  6. 通过从开始菜单打开RStudio,检查其是否正常工作。


Mac系统安装步骤

对于Mac用户,请按照以下步骤操作:

  1. 在下载页面的“Installers for Supported Platforms”列表中,选择适用于Mac OS X 10.6+(64位)的RStudio安装程序。这将开始下载过程。
  2. 下载完成后,点击下载的文件,它将开始安装。
  3. 安装完成后,“应用程序”窗口将打开。将RStudio图标拖拽到“应用程序”目录中。
  4. 通过打开你的“应用程序”文件夹并启动RStudio软件来测试安装。


总结与下一步

在本节课中,我们为Mac和Windows电脑安装了RStudio。在进入下一讲之前,请点击浏览RStudio的可用菜单,稍微探索一下这个软件。我们将会有一整节课专门介绍RStudio的界面和功能,但事先有一些熟悉度会很有帮助。

008:RStudio导览 🧭

在本节课中,我们将学习RStudio集成开发环境(IDE)的基本布局和核心功能。我们将逐一介绍其四个主要区域以及菜单栏,帮助你熟悉这个强大的数据分析工具的工作界面。

概述

RStudio是专为R语言设计的集成开发环境,它将编码、数据管理、结果可视化和帮助文档等功能整合在一个直观的界面中。熟悉其布局是高效使用R进行数据科学工作的第一步。

RStudio界面布局

当你首次打开RStudio时,界面大致可被划分为四个象限,每个象限都有其特定的功能,此外还有一个主菜单栏。

如果你的界面左上角象限缺失,整个左侧只有“控制台”区域,请按以下步骤操作:点击 File -> New File -> R Script。这样界面就会更接近标准布局。

你可以通过将鼠标悬停在象限之间的分隔线上,然后点击并拖动来调整各个区域的大小。


接下来,我们将逐一介绍这些区域及其主要功能。RStudio功能非常丰富,我们无法涵盖所有细节,因此强烈建议你在学习后自行探索。

菜单栏详解

菜单栏位于屏幕顶部,通常有两行。第一行是标准的菜单项,以 FileEdit 开始。其下方是一行图标,这些是常用功能的快捷方式。


以下是几个你将频繁使用的主要菜单:

文件菜单 (File Menu)
在这里,你可以打开新的或已保存的文件,也可以打开新的或已保存的项目(关于R项目,我们将在未来的课程中专门讲解)。你还可以保存当前文档或关闭RStudio。

将鼠标悬停在 New File 上,会弹出一个子菜单,显示可创建的各种文件格式。R脚本和R Markdown文件是最常用的类型,但你也可以生成R Notebook、Web应用、网站或幻灯片演示文稿。点击其中任何一个,都会在“源代码”象限打开一个新标签页。我们将在后续课程中详细讲解R Markdown文件。

会话菜单 (Session Menu)
此菜单包含一些R特有的功能,例如你可以重启、中断或终止R会话。当R运行异常或卡住时,这些功能非常有用,可以让你停止当前操作并重新开始。


工具菜单 (Tools Menu)
这是一个功能宝库,值得你深入探索。目前你需要知道的是,你可以在这里安装新包(参见下一讲)、设置版本控制软件(参见未来关于链接GitHub和RStudio的课程),以及设置RStudio的外观和功能选项。目前我们可以暂时不修改这些设置,但建议你在对RStudio有更多经验后,自行探索并调整至最适合你偏好的状态。

控制台区域

控制台区域在你打开R时就会出现,它应该看起来很熟悉。这是你输入和执行命令的地方,也是命令输出显示的地方。

要执行你的第一个命令,请在大于号 > 提示符后输入 1 + 1,然后按回车键。你应该会看到输出 [1] 2,其中 [1] 表示索引,2 是计算结果。


现在,将屏幕上的代码复制并粘贴到你的控制台中,然后按回车键:

example <- matrix(1:8, nrow = 4, ncol = 2)

这段代码创建了一个4行2列的矩阵,其中包含数字1到8。

要查看这个矩阵,请先看向“环境”象限,你应该能看到一个名为 example 的数据集。点击 example 所在行的任意位置,源代码象限会打开一个新标签页,显示你创建的矩阵。

在RStudio中,你创建的任何数据框或矩阵都可以通过这种方式查看。同时,RStudio还会在环境象限中告诉你关于该对象的一些信息,例如它是列表还是数据框,或者它包含的是数字、整数还是字符。这些信息非常有用,因为某些函数只适用于特定类别的数据,而了解你拥有什么类型的数据是使用这些函数的第一步。


环境与历史标签页

环境象限顶部还有另外两个标签页。我们现在看一下 History 标签页。

你的历史标签页应该类似下图。这里会显示你在本次R会话中运行过的所有命令。点击其中任何一个命令,你可以选择 To ConsoleTo Source,这将分别把该命令重新运行于控制台,或移动到源代码编辑器。

现在,请为你刚才创建的 example 矩阵执行此操作,将其发送到源代码编辑器。

源代码编辑器

源代码面板是你在RStudio中花费大部分时间的地方。在这里,你可以保存你想留待后用的R命令,无论是作为所做工作的记录,还是作为重新运行代码的方式。当我们讨论R Markdown时,会在这个象限花费大量时间。

现在,请点击该象限顶部的保存图标,将脚本保存为 my_first_R_script.R。这样,你就永久保存了创建这个矩阵的记录。

文件、绘图、包与帮助

最后要看的区域位于RStudio窗口的右下角。这个象限顶部有五个标签页:Files, Plots, Packages, Help, Viewer

以下是各标签页的功能:

文件标签页 (Files)
在这里,你可以查看当前工作目录中的所有文件。如果这不是你想要保存或检索文件的位置,你也可以在此标签页中更改当前工作目录:使用最右侧的省略号(…)找到所需文件夹,然后在“更多”(齿轮图标)菜单下将此新文件夹设置为工作目录。

绘图标签页 (Plots)
如果你的代码生成了图形,它将显示在这里。你可以使用箭头浏览之前生成的图形。“Zoom”功能会在一个新窗口中打开图形,该窗口比象限区域大得多。“Export”是保存图形的方式,你可以将其保存为图像或PDF。“扫帚”图标会清除内存中的所有图形。

包标签页 (Packages)
这将在下一节关于R包的课程中更深入地探讨。在这里,你可以查看所有已安装的包,加载和卸载这些包,以及更新它们。



帮助标签页 (Help)
在这里,你可以找到R包和各种函数的文档。该面板的右上角有一个搜索功能,当你对特定函数或包有疑问时可以使用。

总结

在本节课中,我们游览了RStudio软件。我们熟悉了主菜单及其各项功能;查看了控制台,那里是我们输入和运行代码的地方;然后了解了环境面板,它列出了R会话中创建的所有对象,并允许你在源代码的新标签页中查看这些对象。在同一象限中,还有一个历史标签页,它保存了所有已运行命令的记录,并提供了在控制台中重新运行命令或将命令发送到源代码编辑器进行保存的选项。源代码编辑器是你保存R命令的地方。右下角的象限则包含了工作目录中所有文件的列表、显示生成的图形、列出已安装的包,并在你需要帮助时提供帮助文档。

请花些时间自行探索RStudio,以更好地掌握这个强大的工具。


009:R包 📦

在本节课中,我们将要学习R语言中一个核心且强大的特性:。我们将了解什么是包、如何查找、安装、加载和管理它们,以及如何利用包中的函数和文档来扩展R的基础功能。


什么是R包?

上一节我们介绍了R和RStudio的基本操作。本节中我们来看看让R如此强大的核心组件:

到目前为止,我们使用的都是基础R的功能。基础R是下载R时自带的系统,包含基本的统计和绘图功能,但有时功能有限。为了扩展R的功能,开发者们创建了

一个是一个包含函数数据代码的集合,它以完整、便捷的格式提供给你。截至撰稿时,有超过14,300个包可供下载,每个包都有其专门的函数和代码,服务于不同的目的。

不应与混淆。在日常讨论中,这两个术语常被混用。是包在您计算机上的存储位置。可以做一个类比:就是图书馆,而是图书馆里的书。


库是存放书籍(即包)的地方。

包是R独特性的体现。不仅基础R功能强大,这些包还极大地扩展了其功能。最特别的是,每个包都是由广大的R社区开发和发布,并存储在代码仓库中。


代码仓库

代码仓库是存放大量已开发包并可供下载的中心位置。主要有三个大型仓库:

  1. 综合R存档网络:这是R的主要仓库,包含超过12,100个包。
  2. Bioconductor:主要存放生物信息学相关的包。
  3. GitHub:一个非常流行的开源代码托管平台,并非R专用。

现在您知道了在哪里可以找到包,但包的数量如此之多,如何找到能完成您特定任务的包呢?


如何查找包

以下是探索包的几种途径:

  • CRAN任务视图:CRAN将其所有包按功能主题分为35个类别,这至少可以帮助您将搜索范围缩小到与您兴趣相关的主题。
  • R文档网站:这是一个针对CRAN、Bioconductor和GitHub这三大仓库中包和函数的搜索引擎。如果您有明确的任务,这是搜索特定包的好方法。该网站也提供类似CRAN的任务视图,方便您按主题浏览。
  • 使用搜索引擎:更常见的情况是,如果您有特定任务,在谷歌上搜索“任务 + R包”是一个很好的起点。然后,查看相关教程、文档和论坛中其他人是如何做的,是找到相关包的有效方式。




安装包

很好,您找到了想要的包,如何安装它呢?安装方法取决于包的来源。

从CRAN安装

使用 install.packages() 函数,将要安装的包名用引号括起来放在括号内。注意,单引号或双引号均可。

例如,要安装 ggplot2 包,您可以使用:

install.packages("ggplot2")

此命令会从CRAN下载 ggplot2 包并安装到您的计算机上。

如果要一次安装多个包,可以使用字符向量,包名之间用逗号分隔,格式如下:

install.packages(c("package1", "package2", "package3"))

您也可以使用RStudio的图形界面安装包:转到 工具 菜单,第一个选项应该是 安装包。如果从CRAN安装,请选择它作为仓库,并在相应框中输入所需的包名。


从Bioconductor安装

Bioconductor仓库使用自己的方法安装包。首先,需要获取安装所需的基本函数:

source("https://bioconductor.org/biocLite.R")

这使Bioconductor的主要安装函数 biocLite 对您可用。然后,调用 biocLite() 命令,将要安装的包名用引号括起来放在括号内,如下所示(以 GenomicRanges 包为例):

biocLite("GenomicRanges")

从GitHub安装

从GitHub安装是更特定的情况,可能不常遇到。如果您想这样做,首先必须在GitHub上找到想要的包,并记下包名作者名

一般工作流程是:

  1. 安装 devtools 包(如果您尚未安装)。
  2. 使用 library() 函数加载 devtools 包。
  3. 使用 install_github() 命令,调用作者的GitHub用户名,后跟包名。

例如:

install_github("authorusername/packagename")


加载包

安装包并不会立即使其函数可用。首先,您必须将包加载到R中。使用 library() 函数。

可以这样理解:就像您在计算机上安装的任何其他软件一样,安装程序并不意味着它会自动运行,您必须打开程序。R包也是如此,您已经安装了它,但现在必须打开它。

例如,要加载 ggplot2 包,您将使用:

library(ggplot2)

注意:与安装包时不同,library() 命令不接受用引号括起来的包名。

加载包是有顺序的。有些包需要先加载其他包(即依赖项)。包的帮助手册会帮助您找到这个顺序。

在RStudio界面中,右下角象限有一个名为 的标签页,列出了所有已安装包的简要描述和版本号。要加载一个包,只需点击包名旁边的复选框。



管理包

加载包后,您可能需要了解如何管理它们。

检查已安装的包

如果您不确定是否已安装某个包,或者想查看已安装的所有包,可以使用 installed.packages()library() 函数(括号内不填任何内容)来检查。在RStudio的 标签页中查看是另一种方式。

更新包

您可以使用 old.packages() 函数检查哪些包需要更新。此函数会识别出自您安装或上次更新以来已更新的所有包。

要更新所有包,请使用 update.packages()。如果只想更新特定包,只需再次使用 install.packages() 函数。

在RStudio界面中,仍然在 标签页,您可以点击 更新,这将列出所有未更新的包。您可以选择更新所有包或选择特定包。

您需要定期检查您的包是否已过时。但请注意,有时更新可能会改变某些函数的功能,因此如果您重新运行一些旧代码,命令可能已更改甚至完全消失,您将需要更新您的代码。

卸载包

有时您想在脚本运行中途卸载一个包。已加载的包可能与您想使用的另一个包不兼容。要卸载给定的包,可以使用 detach() 函数。

例如,您将键入:

detach("package:ggplot2", unload = TRUE)

这将卸载我们之前加载的 ggplot2 包。

在RStudio界面的 标签页中,您只需取消选中包名旁边的复选框即可卸载包。

删除包

如果您不再需要某个包,可以使用 remove.packages() 函数简单地卸载它。

例如:

remove.packages("ggplot2")

(尝试后请重新安装 ggplot2 包,它是一个非常有用的绘图包。)

在RStudio的 标签页中,点击包行末尾的 X 将卸载该包。




检查R版本

有时,当您查看一个可能想安装的包时,会发现它需要特定版本的R才能运行。要知道是否可以使用该包,您需要知道您正在运行的R版本。

一种方法是检查当您首次打开R或RStudio时,控制台输出的第一行信息会告诉您当前运行的R版本。如果您开始时没注意,可以在控制台中键入 version,它将输出有关您运行的R版本的信息。

另一个有用的命令是 sessionInfo()。它会告诉您正在运行的R版本,以及所有已加载包的列表。在论坛上提问时,包含此命令的输出非常有帮助,它向潜在的帮助者提供了大量关于您的操作系统、R以及您正在使用的包及其版本号的信息。


使用包中的函数

了解了这么多关于包的信息,我们还没有实际讨论如何使用包中的函数。

首先,您需要知道一个包中包含哪些函数。为此,您可以查看所有制作精良的包中包含的帮助页面

在控制台中,您可以使用 help() 函数访问包的帮助文件。尝试使用 help(package = "ggplot2"),您将看到 ggplot2 提供的众多函数。

在RStudio界面中,您可以再次通过 标签页访问帮助文件。点击任何包名都应在同一象限的 帮助 标签页(位于 标签页旁边)中打开相关的帮助文件。点击这些帮助页面中的任何一个,都将带您进入该函数的帮助页面,告诉您该函数的用途以及如何使用它。

一旦您知道要使用包中的哪个函数,只需像我们在本课程中一直使用的任何其他函数一样在控制台中调用它。一旦包被加载,它就好像是基础R函数的一部分。

使用小插图

如果您对包中哪些函数适合您或如何使用它们仍有疑问,许多包都包含小插图。这些是扩展的帮助文件,包括包及其函数的概述,而且通常会提供详细的使用示例,用通俗易懂的语言说明,您可以跟着学习如何使用包。

要查看包中包含的小插图,您可以使用 browseVignettes() 函数。

例如,让我们看看 ggplot2 中包含的小插图:

browseVignettes("ggplot2")

您应该会看到包含两个小插图:“Extending ggplot2”和“Aesthetic specifications”。探索“Aesthetic specifications”小插图是一个很好的例子,说明了小插图如何提供清晰的使用说明。


总结

在本节课中,我们深入探讨了R包。

我们研究了包是什么,以及它与的区别;了解了代码仓库是什么,以及如何查找与您兴趣相关的包。

我们调查了包工作的所有方面:如何从各种仓库安装它们,如何加载它们,如何检查已安装的包,以及如何更新卸载卸载包。

我们稍微绕了个弯,学习了如何检查您拥有的R版本,这通常是安装包时需要了解的重要细节。

最后,我们花了一些时间学习如何探索帮助文件小插图,它们通常能很好地指导您如何使用包及其所有功能。

010:在R中进行项目 📁

在本节课中,我们将要学习如何在R中组织和管理您的工作,核心工具是RStudio内置的“项目”功能。我们将了解项目的概念、其优势、以及如何创建、打开、关闭和切换项目。


什么是R项目?

上一节我们介绍了课程目标,本节中我们来看看R项目的核心定义。

R项目是RStudio的一项内置功能,它帮助您将所有相关文件组织在一起。当您创建一个项目时,它会生成一个文件夹,所有文件都将保存在此文件夹中。这有助于您组织工作,并将多个项目彼此分离。

当您重新打开一个项目时,RStudio会记住哪些文件是打开的,并恢复您离开时的工作环境。这在您休息一段时间后重新开始项目时非常有用。

从功能上讲,在R中创建项目会生成一个新文件夹,并将其设置为工作目录,因此生成的所有文件都将被分配到同一目录。


为什么使用项目?✨

了解了项目是什么之后,本节中我们来看看使用项目的主要好处。

使用项目的主要好处在于它从一开始就启动了组织流程。它为您创建一个文件夹,现在您就有了一个地方来存储所有输入数据、代码以及代码的输出。

  • 自包含性:项目中的所有内容都是自包含的,这意味着查找东西通常更容易,因为只有一个地方需要查看。
  • 易于共享:由于与一个项目相关的所有内容都在同一个地方,因此与他人共享您的工作要容易得多,无论是直接共享文件夹/文件,还是将其与版本控制软件关联。
  • 易于恢复:因为RStudio会记住您在关闭会话时打开了哪些文档,所以在休息后重新开始项目更容易,所有设置都保持您离开时的样子。

如何创建项目?🛠️

既然我们知道了项目的好处,接下来让我们学习创建项目的具体方法。

创建项目有三种方式。

以下是三种创建项目的方法:

  1. 从零开始创建:这将创建一个新目录,用于存放您的所有文件。
  2. 从现有文件夹创建:这将把现有目录与RStudio关联起来。
  3. 从版本控制创建:这将把现有项目克隆到您的计算机上。暂时不必过于担心这个选项,在接下来的课程中您会更熟悉它。

让我们从零开始创建一个项目,这通常是您最常做的事情。

  1. 打开RStudio,在“文件”菜单下,选择“新建项目”。
  2. 您也可以通过点击项目工具栏,在下拉菜单中选择“新建项目”来创建新项目。或者,工具栏中也有“新建项目”的快捷图标。
  3. 由于我们是从零开始,请选择“新建目录”。
  4. 当提示选择项目类型时,选择“新建项目”。
  5. 为您的项目选择一个名称,这次请将其保存到您的桌面。这将在您的桌面上创建一个文件夹,所有与此项目相关的文件都将保存在这里。
  6. 点击“创建项目”。

一个空白的RStudio会话应该会打开。需要注意几点:

  • 在屏幕的文件区域,您可以看到RStudio已将此新目录设置为您的工作目录,并生成了一个扩展名为 .Rproj 的单个文件。
  • 在窗口的右上角,有一个项目工具栏,显示您当前项目的名称,并带有一个下拉菜单,其中包含一些我们稍后会讨论的不同选项。

如何打开、关闭和切换项目?🔄

我们已经学会了创建项目,本节中我们来看看如何管理现有的项目。

打开现有项目非常简单,只需在计算机上双击 .Rproj 文件即可。您也可以在RStudio内部通过以下方式实现:打开RStudio,进入“文件”->“打开项目”。或者,使用项目工具栏,打开下拉菜单并选择“打开项目”。

退出项目就像关闭RStudio窗口一样简单。您也可以进入“文件”->“关闭项目”,效果相同。最后,您可以使用项目工具栏,点击下拉菜单并选择“关闭项目”。所有这些选项都会退出项目,退出时RStudio会记录当前打开了哪些文档,以便在您重新启动时可以恢复,然后关闭R会话。

当您设置项目时,可以告诉它保存环境(例如,所有变量和数据表将在您重新打开项目时预加载),但这并非默认行为。

项目工具栏也是在项目之间切换的简便方法。点击下拉菜单,选择“打开项目”,找到您想要打开的新项目。这将保存当前项目,关闭它,然后在同一窗口中打开新项目。

如果您想同时打开多个项目,请执行相同的操作,但改为选择“打开项目于新会话”。这也可以通过“文件”菜单完成,其中提供了相同的选项。


项目组织最佳实践 📂

在学习了项目的基本操作后,本节中我们来探讨一些设置项目时的最佳实践,以优化您的文件结构。

在设置项目时,一开始就创建几个目录会很有帮助。尝试几种策略,看看哪种最适合您。

大多数文件结构都围绕以下几个目录设置:

  • data/raw/:一个包含原始数据的目录。
  • scripts/R/:一个用于存放脚本或R文件的目录。
  • output/results/:一个用于存放代码输出的目录。

如果您在开始之前就设置好这些文件夹,可以在项目后期当您记不清某些东西在哪里时,避免组织上的麻烦。


总结

本节课中我们一起学习了R中的项目概念。我们涵盖了什么是R项目、为什么您可能想使用它们、如何打开、关闭或切换项目,以及一些最佳实践来帮助您更好地组织自己的工作。通过使用项目,您可以更高效、更清晰地管理数据分析工作流程。

011:版本控制基础 🧑‍💻

在本节课中,我们将学习版本控制的基本概念,了解它是什么、为什么重要,并熟悉其核心术语和工作流程。掌握版本控制是进行高效协作和项目管理的关键技能。


什么是版本控制?

上一节我们介绍了RStudio和项目设置,本节中我们来看看版本控制。版本控制系统会记录文件或文件集随时间的变化。当你进行编辑时,该系统会为你的文件和更改创建快照并保存,以便你在需要时参考或恢复到以前的版本。

如果你使用过Microsoft Word中的“跟踪更改”功能,那么你已经见过一种初级的版本控制形式。在该功能中,文件的更改被跟踪,你可以选择保留这些编辑或恢复到原始格式。像Git这样的版本控制系统就像是更复杂的“跟踪更改”,它们功能更强大,能够细致地跟踪许多文件的连续更改,并且允许多人同时处理同一组文件。

版本控制的优势

希望在你掌握了版本控制软件后,“论文终稿2,实际上是终稿3.docx”这样的文件命名将成为过去。正如我们在示例中看到的,没有版本控制,你可能会保存多个非常相似的文件副本,这很危险。你可能会开始编辑错误的版本,没有意识到标记为“final”的文档已被进一步编辑为“final2”,结果你所有的新更改都应用到了错误的文件上。

版本控制系统通过为每个文件保留一个单一的最新版本,并记录所有先前版本以及版本之间确切的变化,从而帮助解决这个问题。

版本控制的另一个主要好处是,它保留了所有对文件所做更改的记录。当你与多人在同一文件上协作时,这非常有帮助。版本控制软件会跟踪是谁、在何时、以及为什么进行了这些特定的更改。这就像是“跟踪更改”的极致。这个记录在开发代码时也很有用。如果你在一段时间后意识到自己犯了错误并引入了错误,你可以找到上次编辑那段特定代码的时间,查看所做的更改,并恢复到原始未损坏的代码,同时保留在此期间所做的所有其他工作。

最后,当与一组人处理同一组文件时,版本控制有助于确保你不会对文件进行与其他更改相冲突的修改。如果你曾与他人共享文档进行编辑,你就会体会到在原始文件发送后,将他们的编辑与已发生变化的文档整合起来的挫败感。现在你有了同一原始文档的两个版本。

Git 与 GitHub

版本控制允许多人处理同一文件,然后帮助将所有版本的文件及其编辑合并成一个有凝聚力的文件。

Git 是一个免费开源的版本控制软件。它于2005年开发,此后成为最常用的版本控制系统。Stack Overflow(在我们的“获取帮助”课程中应该很熟悉)调查了超过60,000名受访者关于他们使用的版本控制系统,从图表中可以看出,Git是遥遥领先的赢家。随着你对Git及其如何与项目交互越来越熟悉,你会开始明白它为何如此受欢迎。

Git的主要好处之一是它保存了你工作和修订的本地副本,你可以在离线状态下编辑,然后一旦恢复网络服务,就可以将你的工作副本与所有新编辑同步,并跟踪在线主仓库的更改。此外,由于项目的所有协作者都有自己的代码副本,每个人都可以同时处理自己的代码部分,而不会干扰公共仓库。

我们将要利用的另一个巨大好处是RStudio与Git交互的便捷性。在下一课中,我们将安装Git并将其与RStudio链接,并创建一个GitHub账户。

GitHub 是Git的在线界面。Git是在你本地计算机上用于记录更改的软件。GitHub是你的文件和所做更改记录的主机。你可以把它想象成类似于Dropbox:文件在你的计算机上,但它们也托管在线上,可以从任何计算机访问。GitHub的额外好处是与Git交互,跟踪你所有的文件版本和更改。

核心术语解析

在使用Git时涉及大量词汇,通常对一个词的理解依赖于你对另一个Git概念的理解。花些时间熟悉以下词语,并多看几遍以理解这些概念是如何关联的。

以下是版本控制中常用的核心概念:

  • 仓库:相当于项目文件夹或目录。你所有受版本控制的文件和记录的更改都位于一个仓库中。这通常简称为 repo。仓库托管在GitHub上,通过这个界面,你可以将仓库设为私有并与选定的协作者共享,也可以将其公开。
  • 提交:保存你的编辑和所做的更改。一次提交就像是你的文件的一个快照。Git将仓库中所有文件的先前版本与当前版本进行比较,并识别出自那时起发生变化的文件。对于未更改的文件,它保持先前存储的文件不变;对于已更改的文件,它会比较文件、记录更改并上传文件的新版本。我们将在下一节讨论,但当你提交文件时,通常需要附上一个简短的说明,解释你更改了什么以及为什么更改。当我们谈论版本控制系统时,提交是它们的核心。如果你发现错误,可以将文件恢复到之前的提交。如果你想查看文件随时间的变化,可以比较提交并查看消息以了解原因和作者。
  • 推送:用你的编辑更新仓库。由于Git涉及在本地进行更改,你需要能够与公共在线仓库共享你的更改。推送就是将那些更改发送到该仓库,这样每个人都能访问你的编辑。
  • 拉取:将你的本地仓库版本更新到当前版本。因为其他人可能在此期间进行了编辑,由于共享仓库托管在线上,你的任何协作者甚至你自己在不同的计算机上可能已经对文件进行了更改,然后将其推送到共享仓库,你的本地文件可能已经过时了。因此,你需要拉取以检查你是否与主仓库保持同步。
  • 暂存:准备文件进行提交的行为。例如,如果自上次提交以来,你出于完全不同的原因编辑了三个文件,你不想一次性提交所有更改。因为你更改了三个文件且原因不同,关于你为何提交以及更改了什么的提交信息会变得复杂。所以,你可以只暂存其中一个文件并准备提交。提交该文件后,你可以暂存第二个文件并提交,依此类推。暂存允许你将文件更改分离到不同的提交中,这非常有帮助。

高级概念与最佳实践

为了总结到目前为止这些常用术语,并测试你是否掌握了要点:文件托管在一个与协作者在线共享的仓库中。你拉取仓库的内容,以便拥有可以编辑的文件的本地副本。一旦你对文件的更改感到满意,就暂存该文件然后提交。你将此提交推送到共享仓库。这将上传你的新文件和所有更改,并附有解释更改内容、原因和作者的消息。

以下是关于分支、合并和冲突的进一步说明:

  • 分支:当同一个文件有两个同时存在的副本时,就产生了分支。当你在本地编辑文件时,你创建了一个分支,你的编辑尚未与主仓库共享。因此,文件有两个版本:每个人在仓库上都可以访问的版本,以及你本地编辑的版本。在你推送更改并将其合并回主仓库之前,你正在一个分支上工作。在分支点之后,版本历史会分成两个,并跟踪对仓库中原始文件(其他人可能正在编辑)和你的分支上更改的独立修改,然后将文件合并在一起。
  • 合并:将同一文件的独立编辑合并成一个统一的文件。Git识别出独立的编辑,并将它们合并到一个包含两组编辑的单一文件中。但你可以看到这里存在一个潜在问题:如果两个人都对同一句子进行了编辑,导致其中一个编辑无法实现,我们就遇到了问题。Git识别出这种差异,即冲突,并要求用户协助选择保留哪个添加的内容。
  • 冲突:当多人对同一文件进行更改,并且Git无法合并这些编辑时,就会发生冲突。你可以选择手动尝试合并编辑,或者保留一个编辑而放弃另一个。
  • 克隆:克隆是复制一个现有的Git仓库。如果你刚加入一个使用版本控制跟踪的项目,你会克隆该仓库以获取并创建仓库所有文件和所有跟踪更改的本地版本。
  • 分叉:分叉是你从他人那里获取的仓库的个人副本。如果有人正在进行一个很酷的项目,而你想尝试一下,你可以分叉他们的仓库。然后当你进行更改时,编辑会记录在你的仓库中,而不是他们的仓库中。

使用Git的最佳实践

使用像Git这样的版本控制软件可能需要一些时间来适应,但有几件事需要记住,以帮助建立良好的习惯,这些习惯将在未来对你有益。

以下是建立良好版本控制习惯的几个要点:

  1. 进行有目的的提交:每次提交应该只解决一个单一问题。这样,如果你需要识别何时更改了某行代码,只需查看一个地方就能找到更改,并且可以轻松地看到如何恢复代码。
  2. 编写信息丰富的提交消息:养成在每次提交时编写信息丰富的消息的习惯。如果每条消息都精确地说明了更改的内容,任何人都可以检查提交的文件并识别你更改的目的。此外,如果你正在寻找过去所做的特定编辑,可以轻松浏览所有提交以识别与所需编辑相关的更改。
  3. 注意你正在处理的文件版本:经常通过拉取来检查你是否与当前仓库保持同步。此外,不要囤积已编辑的文件。一旦你提交了文件并写好了有用的消息,就应该将这些更改推送到公共仓库。如果你已经完成了一段代码的编辑,并计划转向处理一个不相关的问题,你现在就需要与协作者分享那个编辑。

总结

本节课中我们一起学习了版本控制的基础知识。我们探讨了什么是版本控制及其优势,理解了为什么我们要用整整三节课来专门介绍它。我们了解了Git和GitHub是什么,然后介绍了版本控制工作中许多常用且有时令人困惑的词汇。接着我们快速浏览了使用Git的一些最佳实践,但掌握这一切的最好方法就是去使用它。希望你现在对Git的工作原理有了更好的理解,让我们进入下一课,开始安装它。

012:GitHub与Git入门指南 🚀

在本节课中,我们将学习如何注册GitHub账户、熟悉GitHub网站的基本功能、安装并配置Git。这些步骤是为后续将GitHub与RStudio连接做准备。

上一节我们介绍了版本控制的基本概念,本节中我们来看看如何实际操作GitHub和Git。

注册GitHub账户

GitHub是一个基于云的版本控制文件管理系统。类似于Dropbox,你的文件既存储在本地计算机上,也托管在线上,便于访问。其界面允许你管理版本控制,并为用户提供基于Web的界面来创建项目、共享项目、更新代码等。

以下是注册GitHub账户的步骤:

  1. 访问 www.github.com
  2. 在主页填写信息:创建用户名、输入邮箱、选择安全密码。
  3. 点击“Sign up for Github”。

注册后,你将登录到GitHub。未来登录时,只需访问Github.com。如果未自动登录,点击顶部的“Sign in”链接,在登录页面输入之前创建的用户名和密码即可。

熟悉GitHub界面

登录后,我们将快速浏览GitHub网站,重点关注用户设置、通知、帮助文件和GitHub指南这几个部分。

用户设置与个人资料

登录后,我们应完善个人资料信息并熟悉账户设置。页面右上角有一个带箭头的图标,点击可进入个人资料页。在这里你可以管理账户、查看贡献历史和代码仓库。

由于是初始阶段,你还没有任何仓库或贡献记录。现在,我们可以编辑个人资料。点击页面左侧的“Edit profile”,花些时间填写你的姓名、个人简介,并可以上传一张个人照片。完成后点击“Update profile”。

页面左侧有许多选项供你探索。点击每个菜单以熟悉可用选项。建议从“Account”页面开始,在这里你可以修改密码,如果不满意用户名也可以更改。但请注意,更改用户名可能会带来意外后果。如果你是新手且尚无任何内容,更改可能相对安全。浏览完个人设置选项后,返回个人资料页。

仓库与通知

当你拥有更多GitHub经验后,最终会拥有一些代码仓库。要找到它们,请在个人资料页点击“Repositories”链接。目前它可能为空,但在本讲座结束时,你可以返回此页面查看新创建的仓库。

接下来,我们查看通知菜单。在窗口顶部的菜单栏中,有一个铃铛图标代表通知。点击铃铛图标。当你更活跃于GitHub并与他人协作时,可以在这里找到你参与的所有仓库、团队和对话的消息与通知。

帮助系统与入门指南

每个页面的底部都有“Help”按钮。GitHub拥有完善的帮助系统,如果你对GitHub有任何疑问,这里应是首选搜索点。现在花些时间浏览各种帮助文件。

GitHub认识到这对新用户来说可能是个 overwhelming 的过程,因此开发了一个迷你教程来帮助你入门。现在请按照此指南操作,创建你的第一个仓库。完成后,你应该拥有一个类似下图的仓库。

花些时间探索仓库,查看提交历史记录。在这里,你可以找到对仓库所做的所有更改,并查看是谁、在何时、以及(如果你撰写了恰当的提交信息)为何做出了更改。

探索完仓库中的所有选项后,返回你的用户资料页。现在它看起来应该与之前略有不同。在个人资料页,你可以看到最新创建的仓库。要查看完整的仓库列表,请点击“Repositories”选项卡。在这里,你可以看到所有仓库、简要描述、最后编辑时间,以及右侧显示仓库编辑频率的活动图。

安装与配置Git

正如我们在上一讲中学到的,Git是免费开源的版本控制系统,GitHub正是构建于其上。使用Git系统的主要好处之一是其与RStudio的兼容性。然而,为了将两者连接起来,我们首先需要在你的计算机上下载并安装Git。

要下载Git,请访问 git-scm.com/download。点击适合你操作系统的下载链接,这将启动下载过程。下面我们将分别介绍Windows和Mac系统的安装步骤,请根据你的操作系统跟随相应说明。

Windows系统安装

下载完成后,打开 .exe 文件以启动安装向导。如果收到安全警告,请点击“运行”或“允许”。随后,点击进入安装向导,通常接受默认选项即可,除非你有充分的理由不这样做。

点击“安装”并允许向导完成安装过程。完成后,勾选“Launch Git Bash”选项。除非你感兴趣,否则取消勾选“View release notes”框,因为目前你可能对此不感兴趣。

操作完成后,将打开一个命令行环境。如果你在安装过程中接受了默认选项,未来可以通过开始菜单的快捷方式启动Git Bash。至此,你已成功安装Git。

Mac系统安装

我们将介绍最常见的安装过程,但将Git安装到Mac上有多种方法。你可以在 www.atlassian.com/git/tutorials/install-git 上按照教程寻找替代安装途径。

下载适用于Mac的相应Git版本后,你应该已下载一个 .dmg 文件用于安装。打开此文件,这将启动Git安装程序。一个新窗口将打开,双击其中的 .pkg 文件,安装向导将打开。点击通过选项,接受默认设置,然后点击“安装”。提示完成后,关闭安装向导。至此,你已成功安装Git。

配置Git

安装Git后,我们需要对其进行配置以便与GitHub协同使用,并为连接RStudio做准备。我们需要告诉Git你的用户名和邮箱,以便它知道每次提交来自谁。

在命令提示符(Windows是Git Bash,Mac是终端)中,键入以下命令来设置用户名:

git config --global user.name "YourUsername"

请将 "YourUsername" 替换为你想要的用户名,这将是每次提交时标记的名称。

接着,在命令提示符中键入以下命令来设置邮箱:

git config --global user.email "your-email@example.com"

请确保使用你注册GitHub时使用的相同邮箱地址。

此时,你应该已为下一步做好准备。但为了确认,可以通过键入以下命令来验证你的更改:

git config --list

执行此操作后,你应该能看到上面设置的用户名和邮箱。如果发现任何问题或想要更改这些值,只需用你期望的更改重新键入之前的配置命令即可。

一旦确认用户名和邮箱正确,可以通过键入 exit 并按回车键退出命令行。

总结

本节课中我们一起学习了如何注册GitHub账户并浏览了GitHub网站。我们创建了你的第一个仓库,并在GitHub上填写了基本的个人资料信息。随后,我们在你的计算机上安装了Git,并为其配置了与GitHub和RStudio的兼容性。至此,你已为下一讲的学习做好了全部准备。

013:连接GitHub和RStudio 🛠️

在本节课中,我们将学习如何将RStudio与GitHub连接起来。通过建立这个连接,你可以在RStudio中直接使用Git进行版本控制,并将你的项目轻松推送到GitHub仓库,从而最大化利用版本控制流程的优势。


在RStudio中配置Git

上一节我们介绍了Git和GitHub的基本概念。本节中,我们来看看如何在RStudio中配置Git。

首先,确保你的电脑上已安装Git并拥有GitHub账户。打开RStudio,按照以下步骤操作:

  1. 点击顶部菜单栏的 Tools(工具)。
  2. 在下拉菜单中选择 Global Options...(全局选项)。
  3. 在弹出的选项窗口中,选择 Git/SVN 标签页。

有时,RStudio自动检测到的Git可执行文件路径可能不正确。请检查 Git executable(Git可执行文件)一栏中显示的路径,确认 git.exe 文件确实存在于该目录下。如果路径不正确,点击 Browse...(浏览)按钮,手动定位到正确的 git.exe 文件路径。

路径确认无误后,点击 OKApply(应用)按钮。至此,RStudio和Git已成功链接。




生成SSH密钥并链接GitHub

现在,我们需要将RStudio与你的GitHub账户链接。这需要通过SSH密钥进行安全认证。

在刚才的 Git/SVN 选项窗口中,点击 Create RSA Key...(创建RSA密钥)按钮。密钥生成完成后,点击 Close(关闭)。

接着,在同一个窗口中,点击 View public key(查看公钥)按钮。你会看到一串由数字和字母组成的字符串,这就是你的公钥。复制这串公钥,然后关闭这个窗口。

这个密钥是你的专属标识。我们将把它提供给GitHub,这样当你从RStudio提交更改时,GitHub就能识别你的身份。

以下是链接GitHub的步骤:

  1. 打开浏览器,访问 GitHub.com 并登录你的账户。
  2. 点击右上角你的头像,进入 Settings(设置)。
  3. 在左侧边栏中,找到并点击 SSH and GPG keys(SSH和GPG密钥)。
  4. 点击 New SSH key(新建SSH密钥)按钮。

  1. Title(标题)字段中,为这个密钥起一个与RStudio相关的名字(例如“My RStudio”)。
  2. 将你从RStudio复制的公钥粘贴到 Key(密钥)框中。
  3. 最后,输入你的GitHub密码进行确认,并点击 Add SSH key(添加SSH密钥)。

现在,GitHub和RStudio已经成功链接。


创建GitHub仓库并关联到RStudio项目

接下来,我们可以在GitHub上创建一个新仓库,并将其与RStudio中的一个新项目关联。

首先,在GitHub上创建仓库:

  1. 在GitHub页面,点击你的头像,选择 Your repositories(你的仓库)。
  2. 点击绿色的 New(新建)按钮。
  3. 为你的新仓库命名(例如“test-repo”),并添加一个简短的描述。
  4. 点击 Create repository(创建仓库)按钮。


仓库创建成功后,页面会显示仓库的URL。请复制这个URL地址。

然后,回到RStudio进行操作:

  1. 点击菜单栏的 File(文件),选择 New Project...(新建项目)。
  2. 在弹出的窗口中,选择 Version Control(版本控制)。
  3. 接着选择 Git 作为版本控制软件。
  4. Repository URL(仓库URL)字段中,粘贴你刚刚复制的GitHub仓库URL。
  5. Create project as subdirectory of(项目创建目录)字段中,选择你希望保存此项目的本地文件夹路径。
  6. 点击 Create Project(创建项目)。


这个操作会初始化一个新的R项目,该项目已链接到你的GitHub仓库,并会在一个新会话中打开。


创建文件并提交到GitHub

现在,让我们在新项目中创建一个R脚本文件,并将其推送到GitHub。

首先,创建一个新R脚本:

  1. 点击 File(文件) -> New File(新建文件) -> R Script
  2. 在新打开的脚本编辑器中,输入以下代码:
    print("This file was created within RStudio")
    print("and now it lives on GitHub")
    
  3. 点击保存按钮(或按 Ctrl+S / Cmd+S)。保存时,默认位置就是你刚才创建的项目目录。

文件保存后,我们来进行Git操作。在RStudio界面右上角的 Environment/History(环境/历史)窗格中,找到并切换到 Git 标签页。

你应该能看到你刚刚创建的文件。勾选该文件名下方的 Staged(暂存)复选框,将文件添加到暂存区。

接下来,点击 Commit(提交)按钮。会弹出一个新窗口,上半部分列出了所有已更改的文件,下半部分显示了暂存文件与之前版本的差异。

Commit message(提交信息)框中,输入本次提交的说明(例如“Initial commit: added first R script”)。然后点击 Commit 按钮。提交完成后,可以关闭此窗口。

到目前为止,你已经创建了文件、保存了文件、将其暂存并提交到了本地仓库。根据版本控制的流程,下一步是将这些更改推送到远程的在线仓库。

Git 标签页中,点击 Push(推送)按钮,将你的本地提交推送到GitHub仓库。

推送完成后,打开浏览器,访问你的GitHub仓库页面。你可以看到刚才的提交记录已经出现在仓库中。

恭喜!你已成功从RStudio内部向GitHub完成了第一次推送。


总结 📝

本节课中我们一起学习了如何搭建RStudio与GitHub之间的完整工作流。

首先,我们在RStudio中配置了Git,使RStudio能够将其作为版本控制软件使用。接着,我们通过生成和配置SSH密钥,将RStudio与GitHub账户安全地链接起来。

为了测试整个流程,我们在GitHub上创建了一个新仓库,在RStudio中创建了一个与之关联的新项目,并在项目中编写了一个R脚本文件。最后,我们通过 暂存(Stage) -> 提交(Commit) -> 推送(Push) 的步骤,成功地将该文件从RStudio推送到了GitHub仓库。

掌握这个连接流程,将极大地方便你未来使用RStudio进行数据科学项目开发与版本管理。

014:项目版本控制 🔄

在本节课中,我们将学习如何为一个已存在的R项目添加版本控制,并将其与GitHub关联。我们将涵盖从初始化本地Git仓库到推送到远程GitHub仓库的完整流程,并简要回顾如何克隆一个已存在的远程仓库到本地。


上一节我们介绍了如何在新项目开始时,通过RStudio直接创建与GitHub关联的版本控制仓库。本节中,我们来看看如何处理一个已经存在但尚未进行版本控制的R项目。

有时,你可能已经有一个正在开发的R项目,但它尚未与任何版本控制软件(如Git)关联,也未链接到GitHub。幸运的是,RStudio和GitHub提供了相应的步骤来解决这个问题。

需要承认的是,相比于在项目开始前就在GitHub上创建仓库并与RStudio链接,为现有项目添加版本控制会稍微麻烦一些。

首先,我们来模拟一个本地存在但未进行版本控制的项目场景。

以下是创建本地R项目的步骤:

  1. 在RStudio中,点击 File -> New Project
  2. 选择 New Directory,然后选择 New Project
  3. 为你的项目命名。
  4. 由于我们要模拟一个当前未进行版本控制的项目,请勿勾选 “Create a Git repository” 选项。
  5. 点击 Create Project

至此,我们创建了一个当前未受版本控制的R项目。接下来,我们将其与Git关联。

首先,我们需要设置该项目与Git进行交互。打开Git Bash或终端,并导航到包含你项目文件的目录。

你可以通过输入 cd(change directory)命令,后接目录路径,来切换目录。

cd /path/to/your/project

当命令行提示符($ 符号前的部分)显示为你项目的正确路径时,说明你已处于正确的位置。

到达项目目录后,依次输入以下命令:

  1. git init
  2. git add .

git init 命令将此目录初始化为一个Git仓库。git add . 命令则将目录中的所有文件添加到你的本地仓库暂存区。

接下来,使用以下命令将这些更改提交到Git仓库:

git commit -m "initial commit"

此时,我们已经创建了一个R项目,并将其与Git版本控制系统关联。下一步是将其与GitHub链接。

为此,请访问 GitHub.com 并创建一个新的仓库。确保仓库名称与你的R项目名称完全一致,并且不要初始化README文件、.gitignore文件或许可证。

创建仓库后,页面会显示一个选项:“…or push an existing repository from the command line”。其下方会提供用于链接仓库的命令行代码。

在Git Bash或终端中,复制并粘贴这些代码行,以将你的本地仓库与GitHub远程仓库关联。这些命令通常类似于:

git remote add origin https://github.com/your-username/your-repo-name.git
git branch -M main
git push -u origin main

操作完成后,刷新你的GitHub页面,它现在应该会显示你项目中的文件。

当你在RStudio中重新打开该项目时,现在应该可以在右上角面板访问 Git 选项卡,并可以直接从RStudio内部将未来的任何更改推送到GitHub。


如果存在一个其他人正在开发的项目,并且你被要求参与贡献,你可以将这个已存在的项目与你的RStudio链接。其前提与上一节课中创建GitHub仓库然后克隆到本地的过程完全相同。

简而言之,在RStudio中:

  1. 点击 File -> New Project -> Version Control
  2. 选择 Git 作为版本控制系统。
  3. 如同上一课,提供你试图克隆的仓库URL,并选择本地计算机上存储文件的位置。
  4. 创建项目。

现在,该仓库中的所有现有文件都已存储在本地计算机上,并且你可以通过RStudio界面进行推送和编辑。与上一课的唯一区别在于,你没有创建原始仓库,而是克隆了他人的仓库。


本节课中,我们一起学习了如何使用命令行将一个现有项目转换为受Git版本控制的项目。随后,我们结合GitHub页面指令和命令行,将新版本控制的项目链接到了GitHub。最后,我们简要回顾了如何使用RStudio将现有的GitHub仓库克隆到本地机器。掌握这些流程,能让你更灵活地管理各种情况下的项目版本。

015:R Markdown入门教程 📝

在本节课中,我们将学习R Markdown,这是一种在R Studio中创建可重复文档的强大工具。我们将了解它的基本概念、核心优势以及如何创建和格式化你的第一个R Markdown文档。


我们已经花费了大量时间来学习R和R Studio的使用,掌握了项目和版本控制,你在这方面已经近乎专家。在R/R Studio的功能介绍中,还有一个重要的部分不容忽视,那就是R Markdown。

R Markdown是一种创建完全可重复文档的方式,它可以将文本和代码结合在一起。事实上,这些课程本身就是用R Markdown编写的。通过它,我们可以制作项目符号列表、加粗和斜体文本、内联链接,并运行内联的R代码。学完本课后,你也应该能够完成这些操作,甚至更多。

尽管这些文档最初都是纯文本,但你可以将它们渲染成HTML页面、PDF、Word文档或幻灯片。你用来表示格式(例如加粗或斜体)的符号,与所有这些输出格式都是兼容的。

为什么使用R Markdown? 🤔

上一节我们介绍了R Markdown是什么,本节中我们来看看使用它的主要好处。

使用R Markdown的一个主要好处是它的可重复性。由于你可以轻松地将文本和代码块组合在一个文档中,你可以将引言、假设、运行的代码、代码运行结果以及结论全部整合在一起。分享你的工作内容、原因和结果变得非常简单。收到文档的人可以重新运行你的代码,并获得与你完全相同的结果。这就是我们所说的可重复性。

此外,有时你可能会进行一个需要数周才能完成的项目。你希望能够回顾很久以前的工作,或许还能回忆起当时这么做的确切原因。你可以看到当时运行了哪些代码以及结果。R Markdown文档允许你做到这一点。

R Markdown的另一个主要优势是,由于它是纯文本,因此与版本控制系统配合得非常好。可以轻松跟踪提交之间的字符变化。这与那些非纯文本格式不同。

例如,在某个版本的课程中,我可能忘记将某个词加粗。当我发现这个错误时,我可以修改纯文本,以表示我希望那个词被加粗。在提交记录中,你可以看到为了加粗那个词而发生的具体字符变化。

R Markdown还有一个“自私”的好处,那就是它非常易于使用。与R中的所有功能一样,这个扩展功能来自一个R包:rmarkdown

开始使用R Markdown 🚀

了解了R Markdown的优势后,本节我们将动手实践,学习如何安装和创建你的第一个R Markdown文档。

安装它只需要运行 install.packages("rmarkdown"),然后你就可以开始使用了。

要在R Studio中创建R Markdown文档,请转到 File -> New File -> R Markdown。你将看到这个窗口。我已经填写了标题和作者,并将输出格式切换为PDF。你可以浏览左侧的选项卡,查看所有可以输出的不同格式。完成后,点击“OK”。

一个新窗口应该会打开,其中包含对R Markdown文件的简要说明。一个R Markdown文档主要有三个部分。

第一部分是顶部的头部信息,由三个短横线 --- 界定。在这里,你可以指定标题、姓名、日期以及你想要的输出文档类型等详细信息。如果你之前在窗口中填写了信息,这里应该已经为你填好了。

你可以看到文本部分。例如,一个以 ## R Markdown 开头的部分。我们稍后会详细讨论这意味着什么。当你生成这个文件的PDF时,这个部分将渲染为文本。你将学习的所有格式设置通常都适用于这个部分。

最后,你会看到代码块。它们由三个反引号 ``` 界定。这些是你可以直接从文档中运行的R代码块,当你创建PDF时,这些代码的输出将包含在其中。

了解这些部分如何运作的最简单方法就是生成PDF。当你完成一个R Markdown文档时,我们称之为“编织”你的纯文本和代码,以生成最终文档。为此,请点击源面板顶部的“Knit”按钮。点击后,它会提示你将文档保存为 .Rmd 文件。

你应该会看到一个像这样的文档。在这里,你可以看到头部信息的内容被渲染成了一个标题,后面跟着你的姓名和日期。文本部分生成了一个名为“R Markdown”的章节标题,后面跟着两段文本。接着,你可以看到R代码 summary(cars),重要的是,后面跟着运行该代码的输出。再往下,你会看到用于生成图表的代码,然后是图表本身。这是R Markdown的巨大优势之一:将代码结果内联渲染。

回到生成这个PDF的R Markdown文件,看看你是否能看出如何表示你想要文本加粗。看看单词“knit”,看看它被什么包围着。

基础文本格式化 ✍️

现在我们已经创建了第一个文档,本节中我们来学习一些基础的文本格式化技巧。

此时,我希望我们已经让你相信R Markdown是一种管理代码和数据的有效方式,并为你做好了准备,让你可以尝试使用它。为了让你入门,我们将练习一些R Markdown文档固有的格式化功能。

首先,让我们看看如何加粗和斜体文本。要加粗文本,请用两个星号 ** 包围它。类似地,要使文本变为斜体,请用一个星号 * 包围该单词。

从默认文档中我们还看到,你可以制作章节标题。为此,你需要放置一系列井号 #。井号的数量决定了标题的级别。一个井号是最高级别,将生成最大的文本;两个井号是次高级别,依此类推。尝试使用这种格式,创建一系列标题。

代码块与运行 💻

上一节我们介绍了文本格式化,本节中我们来看看如何插入和运行代码。

到目前为止,我们看到的另一件事是代码块。要创建一个R代码块,你可以输入三个反引号,后跟用花括号 {} 包围的小写字母 r,在新的一行放置你的代码,最后用另外三个反引号结束代码块。

幸运的是,R Studio知道你经常会这样做,并提供了快捷方式:Windows系统是 Ctrl + Alt + I,Mac系统是 Cmd + Option + I。此外,在源面板顶部还有一个“Insert”按钮,它也会生成一个空的代码块。

尝试创建一个空的代码块,在里面输入代码 print("hello world")。当你编织文档时,你将在该代码块的输出中看到这段代码(尽管输出很简单)。

如果你还没有准备好编织文档,但想查看代码的输出,请选择要运行的代码行,然后使用 Ctrl + Enter,或者点击源窗口顶部的“Run”按钮。文本“hello world”应该会输出到你的控制台窗口。

如果一个代码块中有多行代码,你想一次性全部运行,可以使用 Ctrl + Shift + Enter 运行整个代码块,或者点击代码块右侧的绿色箭头按钮,或者转到“Run”菜单并选择“Run Current Chunk”。

创建项目符号列表 📋

我们已经学习了文本和代码的格式化,最后我们来学习如何创建项目符号列表,就像本课开头的那个一样。

以下是创建项目符号列表的方法:

  • 列表很容易创建,方法是在每个预期的项目符号点前加上一个短横线 - 和一个空格。
  • 重要的是,在每个项目符号行的末尾,要加上两个空格。
  • 这是R Markdown的一个特点,如果不包含这两个空格,可能会导致间距问题。

这是一个很好的起点,你还可以用R Markdown做更多事情。幸运的是,R Studio的开发者制作了一份R Markdown速查表,我们强烈建议你去查看,了解R Markdown的所有功能。可能性是无限的。


在本节课中,我们深入探讨了R Markdown,从它是什么以及为什么你可能想使用它开始。我们希望你通过首先安装它,然后生成并编织你的第一个R Markdown文档来入门。接着,我们研究了一些可用的各种格式化选项,并练习了在R界面内生成和运行代码。

016:数据科学问题的类型 📊

在本节课中,我们将从概念层面探讨数据科学家为解答问题所采用的几种主要分析类型。我们将了解每种分析的目标、特点以及适用场景。


概述

数据科学分析大致可分为六类,按难度递增排序,分别是:描述性分析、探索性分析、推断性分析、预测性分析、因果性分析和机制性分析。接下来,我们将逐一探讨这些分析类型的目标,并举例说明。


描述性分析 📝

描述性分析的目标是描述或总结一组数据。当你获得一个新数据集时,这通常是你首先进行的分析类型。描述性分析会生成关于样本及其测量的简单摘要。

以下是描述性分析中常见的统计量:

  • 集中趋势度量:例如均值、中位数、众数。
  • 变异性度量:例如极差、标准差、方差。

这种分析旨在总结你的样本,而不是将分析结果推广到更大的人群或试图得出结论。将数据与进行解释和推广分离开来,解释和推广需要额外的统计步骤。

一个纯粹描述性分析的例子是人口普查。政府收集全国公民的一系列测量数据,然后进行汇总。例如,下图展示了按性别分层的美国人口年龄分布。其目标仅仅是描述这个分布,不涉及对数据意义的推断或对未来趋势的预测,只是展示所收集数据的摘要。


探索性分析 🔍

上一节我们介绍了如何描述数据,本节中我们来看看如何探索数据中的未知关系。探索性分析的目标是检查或探索数据,发现先前未知的关系。

探索性分析探索不同测量值之间可能存在的关系,但不确认这种关系是因果关系。你可能听过“相关不蕴含因果”这句话,探索性分析正是这句话的根源。仅仅因为在探索性分析中观察到两个变量之间存在关系,并不意味着其中一个必然导致另一个。

因此,探索性分析虽然有助于发现新的联系,但不应作为解答问题的最终依据。它可以让你提出假设,并推动未来研究和数据收集的设计,但仅凭探索性分析本身绝不应作为解释数据为何或如何相关的最终定论。

回到上面的人口普查例子,我们不再仅仅总结单个变量内的数据点,而是可以观察两个或多个变量之间可能存在的关系。在下图中,我们可以看到不同行业中女性劳动力所占的百分比,以及这一比例在2000年至2016年间的变化。


探索这些数据,我们可以看到许多关系。仅从数据顶部来看,我们可以看到女性构成了护士的绝大多数,并且在16年间略有下降。虽然注意到这些关系很有趣,但这些关系的原因从本分析中并不明显。探索性分析只能告诉我们关系存在,而非原因。


推断性分析 📈

探索性分析帮助我们发现了关系,但如果我们想基于有限样本对整个群体做出判断呢?这就是推断性分析的目标。推断性分析的目标是使用相对较小的数据样本来推断或说明更大总体的情况。

推断性分析通常是统计建模的目标,即你拥有少量信息,需要外推并将该信息推广到更大的群体。推断性分析通常涉及使用你拥有的数据来估计总体中的值,然后给出你对估计值的不确定性度量。

由于你是从少量数据出发并试图推广到更大的总体,你准确推断更大总体信息的能力在很大程度上取决于你的抽样方案。如果你收集的数据不是来自总体的代表性样本,你对总体的推断推广将不准确。

与之前的例子不同,我们不应在推断性分析中使用人口普查数据。人口普查已经收集了功能上整个总体的信息,没有剩下的人可以推断了。将美国人口普查数据推断到另一个国家也不是一个好主意,因为美国不一定代表我们试图推断知识的另一个国家。

相反,推断性分析的一个更好例子是一项研究,该研究对美国人口的一个子集进行了调查,了解他们在所经历的空气污染水平下的预期寿命。这项研究使用从美国人口样本中收集的数据,来推断空气污染可能如何影响整个美国的预期寿命。



预测性分析 🔮

推断性分析试图描述总体,而预测性分析则着眼于未来。预测性分析的目标是使用当前数据来预测未来数据。本质上,你是使用当前和历史数据来寻找模式,并预测未来结果的可能性。

与推断性分析类似,你预测的准确性取决于是否测量了正确的变量。如果你没有测量正确的变量来预测某个结果,你的预测将不会准确。此外,构建预测模型的方法有很多,有些方法在特定情况下更好或更差。但一般来说,拥有更多数据和一个简单的模型通常在预测未来结果方面表现良好。

尽管如此,就像探索性分析一样,仅仅因为一个变量可能预测另一个变量,并不意味着一个导致另一个。你只是利用观察到的这种关系来预测第二个变量。常言道,预测很难,尤其是预测未来。在事件发生之前,没有简单的方法来衡量你对事件的预测效果如何,因此评估不同的方法或模型是一个挑战。

我们花了很多时间试图预测事物:即将到来的天气、体育赛事的结果,以及我们在这里要探讨的例子——选举的结果。我们之前提到过 FiveThirtyEight 的 Nate Silver,他们试图预测美国选举和体育比赛的结果。


利用历史投票数据和趋势以及当前投票数据,FiveThirtyEight 建立模型来预测下一次美国总统选举的结果,并且在这方面相当准确。其模型准确预测了2008年和2012年的选举,并且在2016年美国大选中被广泛视为一个异类,因为它是少数几个暗示唐纳德·特朗普有机会获胜的模型之一。


因果性分析 ⚖️

到目前为止,我们所看到的许多分析都有一个注意事项:我们只能看到相关性,而无法触及我们所观察到的关系的原因。因果性分析填补了这一空白。

因果性分析的目标是观察当我们操纵一个变量时,另一个变量会发生什么变化,着眼于关系的因果关系。一般来说,仅凭观察数据来进行因果性分析是相当复杂的。总会存在疑问:是你的结论由相关性驱动,还是你分析所基于的假设是有效的?更常见的是,因果性分析应用于旨在识别因果关系的随机化研究的结果。

因果性分析通常被认为是数据分析的黄金标准,在科学研究中经常见到,科学家们试图确定现象的原因。但通常,为进行因果性分析获取合适的数据是一个挑战。关于因果性分析需要注意的一点是,数据通常是汇总分析的,观察到的关系通常是平均效应。因此,虽然平均而言,给特定人群服用某种药物可能会缓解疾病症状,但这种因果关系可能对每个受影响的个体都不成立。

正如我们所说,许多科学研究允许进行因果性分析。药物的随机对照试验就是一个典型的例子。例如,一项随机对照试验研究了一种新药对治疗脊髓性肌萎缩症婴儿的效果,比较了接受药物治疗的婴儿样本与接受模拟对照的样本。他们测量了婴儿的各种临床结果,并观察药物如何影响这些结果。




机制性分析 ⚙️

机制性分析不像之前的分析那样常用。机制性分析的目标是理解导致其他变量发生精确变化的变量的精确变化。这些分析极难用于推断,除非在简单的情况下或那些可以用确定性方程很好建模的情况下。

鉴于这个描述,可能很容易看出机制性分析最常应用于物理或工程科学。例如,生物科学的数据集噪声太大,无法使用机制性分析。通常,当应用这些分析时,数据中唯一的噪声是测量误差,这是可以解释的。

你通常可以在材料科学实验中找到机制性分析的例子。这里有一项关于生物复合材料的研究,本质上是制造可生物降解的塑料,研究生物碳颗粒大小、功能聚合物类型和浓度如何影响最终塑料的机械性能。他们通过仔细平衡控制和操纵变量,并对这些变量和期望结果进行非常精确的测量,从而能够进行机制性分析。


总结

在本节课中,我们涵盖了各种类型的数据分析、它们的目标,并查看了每种分析的一些示例,以展示每种分析能够做什么,以及重要的是,它不能做什么。

我们了解到,从简单的数据描述(描述性分析)到发现关系(探索性分析),再到基于样本推断总体(推断性分析)和预测未来(预测性分析),每一步都增加了复杂性。而确定因果关系(因果性分析)和理解精确的变化机制(机制性分析)则代表了更高层次、要求更严格的分析目标。理解这些类型的区别对于提出正确的问题、选择适当的分析方法以及合理解读结果至关重要。

017:实验设计 🧪

在本节课中,我们将学习实验设计的基本概念。作为一名数据科学家,你需要掌握如何设计合理的实验,以最佳方式回答你的数据科学问题。良好的实验设计能确保你收集到正确且充足的数据,从而清晰有效地解答疑问。

概述

实验设计是指在任何数据收集之前,清晰地阐述问题,设计最佳的数据收集方案,识别设计中的问题或误差来源,然后才收集适当的数据进行分析。在进行分析之前,你需要提前计划好要做什么以及如何分析数据。如果分析错误,就可能得出错误的结论。

实验设计的重要性

我们已经在科学界看到过许多这类情况的实例。有一个名为“撤稿观察”的网站,专门识别因不良科学实践而被撤回或从文献中移除的论文。有时,这些不良实践正是源于糟糕的实验设计和分析。

偶尔,这些错误的结论会产生广泛的影响,尤其是在人类健康领域。例如,有一篇论文试图预测个人基因组对不同化疗反应的影响,以指导哪些患者应接受哪种药物来最佳治疗癌症。这篇论文在最初发表四年多后被撤回。在此期间,这些后来被证明在设置和清理上存在诸多问题的数据,被近450篇其他论文引用,这些论文可能使用了这些错误的结果来支持他们自己的研究计划。

此外,这些错误分析的数据还被用于临床试验,以确定癌症患者的治疗方案。当风险如此之高时,实验设计至关重要。

实验设计核心概念

实验设计包含许多固有的概念和术语。现在我们来介绍其中一些。

  • 自变量(又称因子)是实验者操纵的变量。它不依赖于其他被测量的变量,通常显示在X轴上。
  • 因变量是那些预期会因自变量变化而改变的变量,通常显示在Y轴上。因此,X(自变量)的变化会影响Y(因变量)的变化。

因此,在设计实验时,你必须决定要测量哪些变量,以及要操纵哪些变量来影响其他被测量变量的变化。

此外,你必须提出你的假设,这本质上是对你的变量与实验结果之间关系的有根据的猜测。

实验设计示例

让我们做一个示例实验。假设我有一个假设:鞋码越大,识字能力也越强。

在这种情况下,设计我的实验时,我会选择识字能力的一个衡量标准——例如,阅读流畅度——作为依赖于个人鞋码的变量。

为了回答这个问题,我将设计一个实验,测量100个个体的鞋码和识字水平。样本量是你将在实验中包含的实验对象数量;在后续课程中,你将学习选择最佳样本量的方法。

然而,在收集数据之前,我需要考虑这个实验是否存在可能导致错误结果的问题。在这个案例中,我的实验可能因一个混杂因素而存在致命缺陷。

混杂因素是一个可能影响因变量和自变量之间关系的额外变量。在我们的例子中,由于年龄影响脚的大小,而识字能力受年龄影响,如果我们看到鞋码和识字能力之间存在任何关系,这种关系实际上可能是由年龄引起的。年龄混淆了我们的实验设计。

为了控制这一点,我们可以确保也测量每个个体的年龄,以便考虑年龄对识字能力的影响。控制年龄对识字能力影响的另一种方法是固定所有参与者的年龄。如果我们研究的所有人年龄都相同,那么我们就消除了年龄可能对识字能力产生的影响。

控制混杂因素

在其他实验设计范式中,设置对照组可能是合适的。这是指你有一组未被操纵的实验对象。例如,如果你在研究一种药物对生存的影响,你会有一组接受药物治疗,一组不接受治疗(对照组)。这样,你可以比较治疗组与对照组中药物的效果。

在这些研究设计中,我们还可以使用其他策略来控制混杂效应。

  • 盲法:有时,当受试者知道自己处于治疗组(即接受实验药物)时,他们可能会感觉好转,但这并非源于药物本身,而是源于知道自己正在接受治疗。这被称为安慰剂效应。为了应对这种情况,通常会对参与者实施盲法,即不让他们知道自己属于哪个组。这通常是通过给对照组一个模拟治疗(例如,给他们一颗被告知是药物的糖丸)来实现的。这样,如果安慰剂效应对你的实验造成问题,两组应该会同等程度地体验到它。
  • 平衡:这种策略是许多研究的核心,即将任何可能的混杂效应平均分布在被比较的组之间。例如,如果你认为年龄是一个可能的混杂因素,确保两组具有相似的年龄和年龄范围将有助于减轻年龄可能对你的因变量产生的任何影响。年龄的影响在你的两组之间是相等的。
  • 随机化:这种混杂因素的平衡通常通过随机化来实现。通常,我们事先并不知道什么会成为混杂因素。为了帮助降低意外使一个组富含某个混杂因素的风险,你可以将个体随机分配到你的每个组中。这意味着任何潜在的混杂变量应该在每个组之间大致平均分布,有助于消除或减少系统误差。

实验的重复

在本课中,我们需要涵盖的最后一个实验设计概念是重复。重复基本上就是字面意思:用不同的实验对象重复进行实验。

单个实验的结果可能是偶然发生的:混杂因素在你的组间分布不均、数据收集存在系统误差、存在一些异常值等等。然而,如果你能重复实验,收集一套全新的数据,并仍然得出相同的结论,你的研究就会有力得多。

重复的核心还在于,它允许你更准确地测量数据的变异性,从而让你更好地评估你在数据中看到的任何差异是否显著。

数据与代码共享

一旦你收集并分析了数据,作为一名优秀的科学公民,下一步就是分享你的数据和用于分析的代码。既然你已经有了GitHub账户,并且我们已经向你展示了如何在GitHub上保存版本控制的数据和分析,这里就是分享代码的好地方。

事实上,托管在GitHub上的“leek group”已经制定了一份指南,其中包含了关于如何最佳分享数据的绝佳建议。

P值与P值操纵

实验中经常报告的一个值是P值。这个值告诉你,你的实验结果被偶然观察到的概率。这是统计学中一个非常重要的概念,我们在此不深入探讨。如果你想了解更多,请查看提供的视频链接,它更详细地解释了P值。

你需要警惕的是,当你为了自己的目的而操纵P值时。通常,当你的P值小于0.05时,换句话说,你观察到的差异有5%的几率是偶然发生的,结果被认为是显著的。但是,如果你进行了20次检验,你预计其中一次(即5%)会偶然显著。在大数据时代,检验20个假设是非常容易的,这就是术语P值操纵的由来。

P值操纵是指你详尽地搜索数据集,寻找由于你执行的检验数量巨大而显得具有统计显著性的模式和相关性。这些偶然的相关性可能被报告为显著,如果你进行足够多的检验,你总能找到一个数据集和分析来显示你想要看到的结果。

可以查看一个相关活动,在那里你可以操纵和过滤数据,并进行一系列检验,从而使数据找到你想要的任何关系。XKCD在一则漫画中讽刺了这个概念,测试果冻豆和痤疮之间的联系。显然,两者之间没有联系,但如果你测试足够多的果冻豆颜色,最终总会有一个颜色与痤疮的关联P值小于0.05。

总结

在本节课中,我们介绍了什么是实验设计以及良好实验设计的重要性。然后,我们深入探讨了实验设计的原则,并定义了一些在设计实验时需要考虑的常见术语。接着,我们稍微偏离主题,了解了应该如何分享你的数据和用于分析的代码。最后,我们探讨了P值操纵和为了达到显著性而操纵数据的危险。

018:大数据

在本节课中,我们将要学习“大数据”这一概念。我们将探讨大数据的定义、其核心特征,并比较结构化数据与非结构化数据。我们还将分析处理大数据时面临的挑战与机遇,并最终理解为何数据科学始终是问题驱动的。


在课程开始之前,你可能已经听说过“大数据”这个术语。一直以来都存在大型数据集,但最近它似乎成了数据科学领域的一个流行词。那么,它究竟意味着什么?

🔍 什么是大数据?

顾名思义,大数据是指非常庞大的数据集。我们在本课程的第一讲中曾简要提及过它。通常,大数据集具备三个公认的特性:体量速度多样性

从这三个形容词可以看出,大数据涉及的是体量巨大数据类型多样生成速度极快的数据集。然而,这些特性似乎并不新鲜。为什么大数据的概念直到最近才流行起来?

部分原因是,随着技术和数据存储能力的发展,能够容纳的数据集越来越大,“大”的定义也随之演变。同时,我们收集和记录数据的能力也随着时代进步而提升,数据收集的速度达到了前所未有的水平。最后,“数据”本身的定义也发生了变化。如今,比以往任何时候都有更多的公司认识到收集不同类型信息的好处,互联网和技术的兴起使得各种不同的数据更容易被收集并用于分析。

📈 从结构化数据到非结构化数据

数据科学的一个主要转变是从处理结构化数据集转向应对非结构化数据。

结构化数据是你传统上可能认为的数据形式:长长的表格、电子表格或数据库,其中包含行列分明的信息,你可以在这些框架内随心所欲地进行平均或分析。

然而,不幸的是,在当今时代,数据很少以这种整洁的形式呈现在你面前。我们通常遇到的数据要混乱得多。我们的工作就是从这些数据中提取所需信息,并将其整理成整洁、结构化的形式。

随着数字时代和互联网的进步,许多传统上未被收集的信息突然能够被转换成计算机可以记录、存储、搜索和分析的格式。一旦人们认识到这一点,从我们所有数字互动中收集的非结构化数据便开始激增。

以下是这些非结构化数据来源的一些例子:

  • 电子邮件
  • Facebook 和其他社交媒体互动
  • 短信
  • 购物习惯
  • 智能手机及其 GPS 定位
  • 网站(你在该网站停留的时间以及浏览的内容)
  • 闭路电视摄像头和其他视频源等

能够记录和传输数据的数量及各种来源已经爆炸式增长。正是由于数据在体量速度多样性上的这种爆炸式增长,大数据才成为一个如此突出的概念。这些数据集如今变得如此庞大和复杂,以至于我们需要新的工具和方法来充分利用它们。

⚠️ 处理大数据的挑战

正如你可以根据数据类型的多样性和来源猜测的那样,数据很少以整洁、有序的电子表格形式存储,以便应用传统的清理和分析方法。根据上述大数据的某些特性,你已经可以开始看到处理大数据可能面临的一些挑战。

以下是处理大数据时面临的主要挑战:

  • 体量巨大:存在大量原始数据,你需要能够存储和分析它们。
  • 持续变化:数据在不断变化和更新。当你完成分析时,又有更多的新数据可以纳入你的分析。你分析的每一秒,都是另一秒未被使用的数据。
  • 多样性令人应接不暇:信息来源如此之多,有时很难确定哪种数据源最适合回答你的数据科学问题。
  • 数据混乱:你没有整洁的数据表可以快速分析。在开始寻找答案之前,你需要将非结构化数据转换成可以分析的格式。

💡 大数据的优势

面对所有这些挑战,为什么我们不坚持分析更小、更易管理、经过整理的数据集,并以此方式得出答案呢?有时,使用这些较小的数据集最能解决问题。但许多问题受益于拥有海量数据。如果这些数据中存在一些混乱或不准确之处,其庞大的体量可以抵消这些较小错误的影响,因此即使使用这些较混乱的数据集,我们也能更接近真相。

此外,当你拥有不断更新的数据时,虽然分析起来可能是个挑战,但获取实时、最新信息的能力允许我们进行分析,使其准确反映当前状态,并做出即时、快速、有依据的预测和决策。

拥有所有这些新信息来源的好处之一是,那些以前因缺乏信息而无法回答的问题,突然有了更多获取信息的来源,现在可以建立新的联系并做出新的发现。以前无法触及的问题,现在可能有了新的、非常规的数据源,使你能够回答这些以前不可行的问题。

使用大数据的另一个好处是它可以识别隐藏的相关性。由于我们可以收集关于任何一个主题的无数特性的数据,我们可以寻找那些可能与我们的结果变量没有明显关系的特性,但大数据可以识别出其中的相关性。研究人员无需试图精确理解发动机为何故障或药物副作用为何消失,而是可以收集和分析与此类事件及其所有关联事物相关的大量信息,寻找可能有助于预测未来发生的模式。

大数据有助于回答“是什么”,而不是“为什么”,而这通常就足够了。

🎯 数据科学是问题驱动的

大数据现在使得从各种来源非常快速地收集大量数据成为可能,技术的进步也使得收集、存储和分析的成本更低。但问题依然存在:这次数据爆炸中,有多少对回答你关心的问题是有用的?

无论数据规模大小,你都需要正确的数据来回答问题。著名统计学家约翰·图基在1986年说过:“一些数据加上对答案的渴望,并不能确保能从给定的数据体中提取出合理的答案。”

本质上,任何给定的数据集都可能不适合你的问题,即使你非常希望它适合。大数据也不能解决这个问题。即使周围最大的数据集,如果它不是正确的数据,也可能不够“大”到能够回答你的问题。


📝 总结

本节课中,我们一起学习了:

  • 定义了大数据,并了解了其三个核心特征:体量速度多样性
  • 比较了结构化数据非结构化数据,并审视了一些新的非结构化数据来源。
  • 探讨了处理这些大数据集所面临的挑战和带来的优势
  • 最后,我们回归到一个核心理念:数据科学是问题驱动的科学,即使是最大的数据集也可能不适合你的具体情况。

019:在 Mac 上安装 R 🍎

在本节课中,我们将学习如何在 Mac 电脑上安装 R 语言环境。这是一个非常简单的过程,只需要几个步骤即可完成。

概述

R 是一种广泛用于数据分析和统计计算的编程语言。要在 Mac 上使用它,我们首先需要从官方网站下载并安装它。整个过程直观且快速。

安装步骤

以下是安装 R 的具体步骤。

首先,打开你的网页浏览器。

然后,访问 CRAN(The Comprehensive R Archive Network)的官方网站。这是 R 语言的综合归档网络,你会看到为不同操作系统提供的下载选项。

我们需要下载适用于 Mac 平台的版本。点击“Download R for (Mac) OS X”链接。

接下来,点击页面上的 .pkg 安装包文件进行下载。下载进度条会开始运行,根据你的网速,这可能需要几分钟时间,请耐心等待。

下载完成后,在下载文件夹中找到并打开这个 .pkg 文件。安装程序将自动启动,并引导你完成所有安装步骤。

点击“继续”按钮。这里会显示即将安装的内容描述,你可以再次点击“继续”。

随后会出现软件许可协议,即 GNU 通用公共许可证第 2 版。在阅读后,点击“同意”。

然后点击“安装”按钮。系统可能会要求你输入管理员密码以授权安装,请输入密码并确认。

安装程序将开始将文件复制到你的电脑上。

安装完成后,点击“关闭”按钮。此时,R 已经成功安装到你的“应用程序”文件夹中。

你可以打开“访达”,进入“应用程序”文件夹。文件通常按字母顺序排列,向下滚动找到 “R” 即可看到 R 应用图标。

安装后操作

现在,你已经成功在电脑上安装了 R。你可以直接双击图标启动并使用基础的 R 控制台。

此外,如果你希望获得更强大的开发体验,可以选择安装一个集成开发环境(IDE),例如非常流行的 RStudio。这能提供代码编辑、项目管理、图形展示等更多便利功能。

总结

本节课中,我们一起学习了在 Mac 操作系统上安装 R 语言环境的完整流程。我们访问了 CRAN 官网,下载了 Mac 版的安装包,并一步步完成了安装过程。安装完成后,R 会出现在你的应用程序文件夹中,随时可以启动使用。

020:在Windows上安装R 🖥️

在本节课中,我们将学习如何在Windows操作系统的电脑上安装R语言环境。R是一种用于统计计算和图形的编程语言,是数据科学领域的重要工具。我们将一步步完成从下载到启动的整个过程。

概述

安装R是开始数据科学之旅的第一步。这个过程主要涉及访问官方网站、下载安装程序、运行安装向导并进行一些基本配置。我们将确保每一步都清晰明了。

下载R安装程序

首先,你需要启动一个网页浏览器。你可以使用Chrome、Firefox或Edge等任何你习惯的浏览器。

以下是下载R安装程序的具体步骤:

  1. 在浏览器的地址栏中,访问R项目的综合归档网络(CRAN)官网。其网址是:https://cran.r-project.org/
  2. 进入网站后,你会看到顶部有三个针对不同操作系统的选项:Linux、Mac和Windows。
  3. 点击“Download R for Windows”链接。
  4. 在接下来的页面中,点击“base”子目录下的链接。
  5. 最后,点击“Download R X.X.X for Windows”链接(X.X.X代表当前版本号),下载将自动开始。下载时间取决于你的网络速度。

运行安装向导

下载完成后,在你的“下载”文件夹中找到名为“R-X.X.X-win.exe”的文件,双击运行它。

安装向导将引导你完成安装。以下是安装过程中的关键步骤和选项说明:

  1. 选择安装语言:安装程序会首先让你选择界面语言。你可以从列表中选择你熟悉的语言,例如“中文(简体)”或“English”。
  2. 阅读并同意许可协议:接下来是R的许可协议,即GNU通用公共许可证。阅读后,点击“下一步”表示同意。
  3. 选择安装位置:默认的安装目录(通常是C:\Program Files\R\R-X.X.X)对于大多数用户来说都是合适的,建议直接点击“下一步”使用默认路径。
  4. 选择安装组件:这里通常保持默认选项即可。默认设置会同时安装32位和64位版本,以适应不同的系统。除非你明确知道你的电脑是较旧的32位机器,否则无需更改。
  5. 启动选项配置:这一步允许你自定义R的启动方式。我们建议进行简单配置以便更好地使用。
    • 界面风格:你可以选择MDI(多文档界面)或SDI(单文档界面)。MDI将所有窗口(如控制台、图形)嵌套在一个主窗口内;SDI则让每个窗口独立显示。对于初学者,SDI模式(独立窗口)通常更直观,管理起来也更方便。
    • 帮助文件格式:你可以选择使用“HTML帮助”或“纯文本帮助”。HTML帮助界面更美观,支持链接跳转,阅读体验更好,推荐选择此项。
    • 网络连接选项:除非有特殊需求(如需要通过代理服务器上网),否则请保持默认的“标准”选项,然后点击“下一步”。
  6. 创建快捷方式:建议在开始菜单创建文件夹,方便日后查找。你也可以选择是否在桌面创建R的快捷方式图标,根据你的桌面整洁度决定即可。
  7. 开始安装:确认所有设置后,点击“安装”按钮,程序将开始复制文件到你的电脑。这个过程可能需要几分钟。

完成安装并启动R

安装进度条完成后,点击“完成”按钮退出安装向导。

现在,R已经成功安装在你的电脑上。你可以通过以下方式启动它:

  • 双击桌面上的R图标(如果你在安装时选择了创建)。
  • 点击Windows“开始”菜单,在“所有程序”或“最近添加”中找到R文件夹,点击其中的“R X.X.X”程序。

启动后,你将看到R的控制台(Console)窗口。窗口中显示有R的版本信息、版权声明以及一个提示符 >。这个 > 符号表示R已经准备就绪,等待你输入命令。

例如,你可以尝试输入一个简单的计算命令并按回车:

1 + 1

R会立即在下一行显示结果 [1] 2。恭喜,你的R环境已经搭建成功!

总结

本节课中,我们一起学习了在Windows系统上安装R的完整流程。我们首先从CRAN官网下载了安装程序,然后通过安装向导完成了语言选择、协议同意、路径设置以及重要的启动配置(如选择SDI界面和HTML帮助)。最后,我们成功启动R并验证了其基本功能。现在,你已经拥有了进行数据科学分析的强大工具,可以开始后续的学习了。

021:在 Mac 上安装 RStudio 🍎

在本节课中,我们将学习如何在 Mac 操作系统上安装 R 语言的集成开发环境 RStudio。这是一个非常简单的过程,仅需几个步骤即可完成。

概述

首先需要明确的一点是,在安装 RStudio 之前,你必须已经安装了 R 语言本身。一旦你完成了 R 的安装,就可以开始 RStudio 的安装流程了。

访问 RStudio 官网

接下来,你需要访问 RStudio 的官方网站,网址是 Rstudio.com。在网页的左下角,你会看到一个绿色的按钮,它会引导你下载 RStudio。

以下是可供下载的两个版本:

  • 桌面版:这是我们将在本地电脑上使用的版本。
  • 服务器版:本节课不会涉及此版本。

因此,你只需要下载桌面版,也就是当前显示的按钮。

下载与安装

网站通常会自动检测你正在运行的操作系统类型。例如,因为我使用的是 Mac,所以它推荐我下载 Mac OS 10 版本。点击下载按钮即可开始下载。

下载进度条会显示下载状态。下载完成后,文件通常会保存在“下载”文件夹中,并且很可能是列表中最左侧的那个文件。

点击该文件以启动安装程序。与安装其他 Mac 应用程序一样,安装 RStudio 只需要将其图标拖拽到“应用程序”文件夹中。

拖拽完成后,安装即告结束。现在,你可以进入“应用程序”文件夹,找到 RStudio 的图标。

启动 RStudio

双击 RStudio 图标来启动它。系统可能会询问你是否确定要打开此应用程序,点击“是”或“打开”。

至此,RStudio 已经成功启动并运行在你的 Mac 上了。

总结

本节课我们一起学习了在 Mac 上安装 RStudio 的完整流程。我们回顾了关键前提——先安装 R,然后访问官网下载正确的桌面版安装包,最后通过简单的拖拽操作完成安装。现在,你的编程环境已经准备就绪。

022:设置工作目录与编辑代码(Windows)💻

在本节课中,我们将学习如何在 Windows 系统中设置 R 的工作目录,以及如何使用 R 内置的文本编辑器来编写和运行代码。理解工作目录是管理数据文件的基础,而掌握代码编辑则是进行数据分析的关键步骤。

设置工作目录

上一节我们介绍了 R 的基本界面,本节中我们来看看如何设置工作目录。工作目录是 R 在计算机上查找和保存文件的默认位置。当你使用如 read.csvwrite.csv 这类函数读取或写入数据时,R 默认会在工作目录中寻找或创建文件。

你可以使用 getwd() 函数来查看当前的工作目录设置。

getwd()

执行后,你可能会看到类似 C:/Users/YourName/Documents 的路径。这个路径因你的 Windows 版本和硬件配置而异,但通常位于 C 盘。

了解并正确设置工作目录非常重要。例如,如果你尝试读取一个不在工作目录中的文件,R 会报错。

# 假设文件不在工作目录中
read.csv("my_data.csv") # 可能会产生错误

为了避免此类问题,你有两个选择:将文件移动到当前工作目录,或者将工作目录更改为文件所在的文件夹。

以下是更改工作目录的步骤:

  1. 在 R 的菜单栏中,点击 File
  2. 选择 Change dir...
  3. 在弹出的窗口中,导航到你希望设置为工作目录的文件夹(例如桌面上的一个文件夹)。

为了方便本课程的学习,建议你创建一个专用文件夹来存放所有课程材料。这样,你可以将工作目录永久设置为此文件夹,避免频繁切换。

编写与运行 R 代码

设置好工作目录后,我们需要编写 R 代码。R 自带了一个基础的文本编辑器,完全足够本课程使用。

要打开编辑器,请点击菜单栏的 File,然后选择 New script。这将打开一个空白的编辑窗口。

现在,让我们编写一个简单的函数。这个函数将生成一些随机数并计算其平均值。

my_function <- function() {
  x <- rnorm(100) # 生成100个服从正态分布的随机数
  mean(x) # 计算平均值
}

编写完代码后,你需要将其加载到 R 控制台中才能使用。对于少量代码,你可以直接复制粘贴:

  1. 在编辑器中使用 Ctrl + A 全选代码。
  2. 使用 Ctrl + C 复制。
  3. 切换到控制台,使用 Ctrl + V 粘贴。

粘贴后,代码会被执行。此时,你可以使用 ls() 查看工作空间,确认 my_function 已存在,并可以调用它。

ls()
my_function()

对于更复杂的项目,更好的做法是将代码保存为文件。以下是操作步骤:

  1. 在编辑器中,点击 File -> Save As...
  2. 导航到你的课程文件夹(例如之前创建的 Coursera 文件夹)。
  3. 为文件命名,通常使用 .R 作为扩展名(例如 mycode.R)。
  4. 点击保存。

保存后,你可以使用 source() 函数将整个文件中的代码加载到 R 中。

source("mycode.R")

之后,文件中定义的所有函数和变量都将可用。如果你修改了代码文件,记得先保存,然后再次使用 source() 函数重新加载,才能使更改生效。

你可以创建多个 .R 文件来管理不同项目或作业的代码。通过 File -> Open script... 可以重新打开已保存的文件进行编辑。

总结

本节课中我们一起学习了两个核心技能:设置工作目录和编辑 R 代码。我们了解到工作目录是 R 存取文件的默认位置,可以使用 getwd() 查看,并通过图形界面更改。同时,我们学会了使用 R 内置编辑器编写代码,并通过复制粘贴或 source() 函数将代码加载到控制台中运行。掌握这些基础操作,将为后续的数据分析工作打下坚实的基础。

023:设置工作目录与使用文本编辑器(Mac)📂✏️

在本节课中,我们将学习两个核心操作:如何设置R的工作目录,以及如何使用R自带的文本编辑器来编写和编辑代码文件。掌握这些是高效使用R进行数据科学分析的基础。

概述

工作目录是R读取和保存文件的默认位置。如果不清楚工作目录的位置,你将难以找到保存的数据或加载外部文件。同时,编写代码是数据分析的核心,我们需要一个工具来高效地创建和编辑R脚本。本节将详细介绍在Mac系统上完成这两项任务的方法。

设置工作目录

上一节我们了解了工作目录的重要性,本节中我们来看看如何查看和设置它。

启动R后,你可以通过输入函数 getwd() 来查看当前的工作目录。

getwd()

例如,系统可能将工作目录设置为你的个人主目录(如 /users/yourusername)。虽然这可以工作,但如果你将所有数据和分析文件存储在另一个特定文件夹(例如一个子目录)中,你可能需要更改工作目录。

你可以通过R的菜单来更改工作目录。点击菜单栏中的“Misc”(杂项),然后选择“Change Working Directory...”(更改工作目录),接着在弹出的窗口中选择你想要的文件夹。

在更改目录前,需要明确一点:如果你想读取一个文件,该文件必须位于你的工作目录中,否则R会报错。

例如,尝试使用 read.csv 读取一个不在当前工作目录中的文件 mydata.csv

read.csv("mydata.csv")

你会收到一个错误,因为R在当前目录下找不到这个文件。解决方法是,将工作目录更改到该文件所在的文件夹。

更改目录后,你可以使用 dir() 命令列出该目录下的所有文件,确认目标文件是否存在。

dir()

确认文件存在后,再次使用 read.csv 即可成功加载数据。

为了管理方便,我建议你为本课程创建一个专属目录,并将所有相关文件存储在其中。这样你就不必频繁更改工作目录。

例如,你可以在桌面上创建一个名为“Coursera”的文件夹。然后在R中,将工作目录更改到这个新文件夹。之后,所有保存到该文件夹的文件都能被R直接访问。

使用R的文本编辑器编写代码

设置好工作环境后,接下来我们需要编写代码。在Mac版的R中,自带了一个简单的文本编辑器,足以满足本课程的需求。

你可以通过点击R界面工具栏上的编辑器按钮(通常显示为一张纸和一支笔的图标)来打开文本编辑器。这会打开一个新的空白编辑窗口。

在编辑器中,你可以直接开始编写R代码。例如,创建一个简单的函数:

myfunction <- function() {
        x <- rnorm(100)
        mean(x)
}

编辑器会自动进行代码缩进,提高可读性。编写完代码后,你需要将其加载到R的工作环境中才能使用。

以下是几种将代码加载到R的方法:

  1. 复制粘贴:对于少量代码,你可以在编辑器中选择全部代码(Command+A),复制(Command+C),然后切换到R控制台窗口粘贴(Command+V)并执行。
  2. 保存并源文件(Source):对于更正式或更长的代码,最好将其保存为 .R 文件。在编辑器菜单中点击“File” -> “Save As...”,导航到你的工作目录(例如“Coursera”文件夹),将文件命名为如 myfunction.R 并保存。

保存文件后,在R控制台中使用 source() 函数加载整个脚本:

source("myfunction.R")

这相当于一次性执行了文件中的所有代码。之后,使用 ls() 命令可以查看工作环境中已加载的对象,你应该能看到 myfunction 这个函数。

你可以随时在编辑器中修改代码,保存文件(Command+S)后,再次使用 source() 命令即可更新R环境中的函数定义。

要创建新的R脚本文件,只需再次点击编辑器按钮打开一个新窗口即可。R自带的编辑器虽然简单,但具备了编写、编辑、保存和加载代码的所有基本功能,对于本课程的学习完全足够。

总结

本节课中我们一起学习了在Mac上使用R的两个基础技能。首先,我们了解了工作目录的概念,学会了如何使用 getwd() 查看、以及通过菜单或设置专属文件夹来管理它,这是组织项目和读取数据的关键。其次,我们介绍了如何使用R内置的文本编辑器来编写、保存(.R文件)和加载(通过复制粘贴或 source() 函数)R代码。掌握这些操作将为后续的数据分析工作打下坚实的基础。

024:R语言编程入门 🚀

在本课程中,我们将学习R语言编程的基础知识。这是数据科学专项课程的第二门课,我们将专注于R作为一种编程语言的各个方面。课程将从R的基本构建块开始,逐步深入到编写脚本和函数,最后介绍代码调试与分析工具。完成本课程后,你将牢固掌握R语言的核心编程概念,为后续的数据科学课程打下坚实基础。


课程概述 📋

大家好,欢迎来到R语言编程课程。这是数据科学专项课程中的第二门课。正如标题所示,我们将重点学习作为编程语言的R。

在本课程中,我们将从R的基本构建块开始,涵盖不同的数据类型和底层细节。我们将逐步学习如何编写和构建R程序或脚本,这包括控制结构、编写函数以及对数据进行基本操作等内容。

之后,我们将讨论代码性能分析、调试工具以及如何处理较长的代码段。完成本课程后,你将对R作为一种编程语言有相当扎实的理解。

本课程不会涵盖R包的每一个功能,因为在专项课程的其他部分,你将学习更多内容,例如如何使用图形系统制作图表,以及如何使用某些包进行统计推断和机器学习等。这些内容将在其他课程中涉及。

本课程的目的是真正引导你进入R编程世界,特别是如果你对这门语言不太熟悉的话。它旨在确保你掌握其核心机制。希望你享受学习过程。我相信完成本课程后,你将准备好继续学习专项课程中的其他课程。


总结 ✨

本节课中,我们一起学习了R语言编程课程的定位与目标。我们了解到,本课程是数据科学专项课程的重要组成部分,旨在帮助初学者掌握R语言的核心编程概念,为后续更高级的数据分析和应用课程做好准备。

025:R语言概述与历史

在本节课中,我们将学习R统计编程环境的基本概念及其发展历史。我们将了解R是什么,它从何而来,以及它为何成为当今数据科学领域的重要工具。


🧐 什么是R?

R本质上是一种S语言的方言。这自然引出了下一个问题:什么是S?

S语言是由约翰·钱伯斯在现已不存在的贝尔实验室开发的。它始于1976年,最初是作为一个内部统计分析环境,供贝尔实验室的研究人员分析数据使用。

早期版本通过一系列Fortran库来实现那些需要反复执行的繁琐统计例程。直到大约第三个版本,S语言才加入了统计建模功能。

1988年,该系统用C语言重写,以提高跨平台的可移植性,并开始接近我们今天使用的系统形态。这个版本(版本3)的标志性成果是约翰·钱伯斯和特雷弗·哈斯蒂合著的《S语言中的统计模型》(有时被称为“白皮书”),它记录了该版本引入的所有统计分析功能。

S语言的第4版于1998年发布,这也是我们今天大致仍在使用的版本。本课程的参考书《用数据编程》(约翰·钱伯斯著,有时被称为“绿皮书”)详细介绍了S语言第4版。

因此,R是S语言的一个实现,而S语言最初源于贝尔实验室。


📜 S语言简史

为了更好地理解R,我们先简要回顾一下S语言的发展历程。

1993年,贝尔实验室将S语言的独家开发和销售权授予了一家名为StatSci(后更名为Insightful Corporation)的公司。2004年,Insightful以200万美元从朗讯科技(贝尔实验室的后身)完全收购了S语言。2006年,阿尔卡特收购了朗讯科技。

Insightful公司开发了一个名为S+的产品,它是S语言的一个实现,并加入了许多高级功能,例如图形用户界面等。2008年,Insightful被TIBCO公司以2500万美元收购。至今,TIBCO仍在各种商业分析产品中开发S+。

尽管经历了多次公司收购,S语言的核心基础自1998年以来并未发生根本性改变。1998年的语言形态与今天我们使用的版本在表面上非常相似。值得一提的是,S语言在1998年获得了美国计算机协会颁发的软件系统奖,这是一项非常崇高的荣誉。


🧠 S语言的设计哲学

在《S语言的演化阶段》一文中,S语言的创始人约翰·钱伯斯阐述了其设计的关键原则。

其核心理念是创造一个交互式环境,让用户无需认为自己是在“编程”即可开始使用。随着用户需求的明确和技能的增长,他们可以平滑地过渡到编程,此时语言和系统方面的知识将变得更加重要。

S语言(以及后来的R语言)的基本思想是,人们可以首先在一个交互式环境中使用它来分析数据、进行基本操作,而无需了解编程或语言的复杂细节。当他们逐渐熟悉并需要更强大的功能时,可以自然地开始学习编程,开发自己的工具。系统旨在促进用户向程序员的平稳过渡。

这就是S语言的基本哲学。


🚀 R的诞生与发展

现在,让我们回到R本身。R是一个相对较新的发展。

1991年,罗斯·伊哈卡和罗伯特·杰特曼在新西兰创建了R。他们在1996年发表于《计算与图形统计杂志》的论文中讨论了开发R的经验。1993年,R首次向公众发布。

1995年,马丁·梅奇勒说服罗斯和罗伯特使用GNU通用公共许可证(GPL)来授权R,这使R成为了我们所说的“自由软件”。1996年,建立了两个主要的邮件列表:R-help(用于一般问题讨论)和R-devel(用于R开发工作)。

1997年,成立了“R核心小组”,其中包含了许多来自S+开发团队的成员。该小组控制着R的主要源代码,只有其成员才能修改。不过,许多非核心成员的建议也经常被采纳。


✨ R的主要特性

以下是R的一些关键特性:

  • 语法类似S:这使得当时的S+用户可以轻松过渡到R。
  • 跨平台运行:R可以在几乎所有标准计算平台或操作系统上运行,包括Mac、Windows、Linux,甚至PlayStation 3。
  • 活跃开发:R有年度主要版本发布,期间常有错误修复版本,开发非常活跃。
  • 模块化设计:R的核心软件非常精简,功能被划分为模块化的包。用户可以先下载一个包含基础功能的小型核心,然后根据需要添加包。
  • 强大的图形功能:R的图形功能非常复杂,为用户提供了对图形创建的高度控制,其能力优于大多数统计软件包。
  • 从交互到编程:R非常适合交互式工作,同时也包含强大的编程语言,用于开发新工具,便于用户向程序员过渡。
  • 活跃的社区:拥有非常活跃和充满活力的用户社区,如R-helpR-devel邮件列表,以及Stack Overflow上的讨论区。
  • 自由软件:R是免费的,既指“免费啤酒”(无需付费),也指“自由言论”(遵循自由软件原则)。

🆓 自由软件原则

R遵循自由软件基金会定义的四项基本自由:

  1. 自由0:出于任何目的运行程序的自由。
  2. 自由1:研究程序如何工作,并为其适应自身需要而修改的自由。(可以查看和修改R的源代码)
  3. 自由2:重新分发副本,以帮助他人的自由。(可以自由分享或出售副本)
  4. 自由3:改进程序,并向公众发布改进版本,使整个社区受益的自由。

这些自由确保了软件的开放性、可审计性和持续的社区驱动改进。


⚠️ R的一些局限性

当然,R也存在一些局限性:

  • 基于较老的技术:其基础源于40年前的理念,例如对动态或3D图形的内置支持较少(不过已有许多优秀的扩展包弥补)。
  • 功能依赖社区贡献:如果某个方法没有被实现,用户需要自己或雇人来实现,没有公司客服可以投诉或要求特定功能。
  • 内存限制:R中操作的对象必须存储在计算机的物理内存中。如果数据对象大于可用内存,则无法加载和处理。随着硬件发展和大数据技术(如data.tableSparkR等包)的出现,此问题已得到部分缓解。
  • 并非万能:R并不适合所有场景。用户应对其能力有合理的期望。

📦 R的构成:基础系统与扩展包

我们可以将R系统分为两个概念部分:

  1. 基础R系统:从CRAN(综合R档案网络)下载。它包含:

    • 基础包:运行R系统所需的底层基本函数。
    • 其他核心包:如utilsstatsdatasetsgraphics等几乎人人都会用到的基础包。
    • 推荐包:如boot(自助法)、class(分类)、cluster(聚类)等常用但不一定关键的包。
  2. 扩展包生态系统

    • CRAN用户贡献包:目前约有数千个由全球用户和程序员开发的包,每天都有新包上传。CRAN对包有严格的质量标准(如必须包含文档、通过测试)。
    • Bioconductor项目:专注于基因组学和生物数据分析的R软件包项目。
    • 个人网站发布的包:数量难以统计,为R提供了极其丰富的功能扩展。

📚 学习资源

在学习R的过程中,以下官方文档和经典书籍可能会有所帮助:

官方文档(可在R官网找到)

  • 《R导论》:介绍R基础的长篇PDF文档。
  • 《编写R扩展》:适用于想要开发R包的用户。
  • 《R数据导入/导出手册》:介绍将数据导入R的各种方法。
  • 《R安装与管理手册》:适用于想从源代码编译R的用户。
  • 《R内部结构》:非常技术性的文档,介绍R的底层设计与实现。

经典书籍

  • 约翰·钱伯斯:《用于数据分析的软件》、《用数据编程》。
  • 比尔·维纳布尔斯 & 布莱恩·里普利:《现代应用统计学(S-PLUS)》、《S编程》。虽然书名提及S,但对R编程非常相关。
  • 派 & 贝茨:《S和S-PLUS中的混合效应模型》。
  • 保罗·默雷尔:《R图形》(目前已有第二版),作者是R图形系统的设计者。

其他资源

  • Springer出版社的“Use R!”系列丛书,包含许多针对不同应用领域使用R的短篇书籍。
  • R官网上有更长的书籍列表。

🎯 总结

本节课中,我们一起学习了R语言的起源、发展历史、核心特性、设计哲学以及其生态系统。我们了解到R是S语言的一个自由、开源的实现,拥有强大的统计分析能力、出色的图形系统、活跃的社区以及模块化的扩展包架构。同时,我们也认识到它存在的一些局限性。

从下一个视频开始,我们将深入探讨R编程语言的细节,学习如何用它来分析数据。

026:如何高效获取帮助 🆘

在本节课中,我们将学习在学习R语言和本课程时,如何有效地获取帮助。由于课程规模庞大、学生众多,一对一提问将变得困难。因此,我们需要借助其他工具来解答疑问,特别是讨论板和电子邮件。我们将探讨如何提问才能最大化获得正确答案的机会。

提问前的准备

上一节我们介绍了寻求帮助的必要性。在向他人提问之前,你可以尝试多种方法自行寻找答案。如果你打算在论坛或邮件列表中提问,请务必先搜索该论坛的存档。

以下是提问前可以尝试的几种方法:

  • 搜索论坛存档:很可能已经有人问过同样的问题,并且得到了解答。搜索存档可以为你和他人节省大量时间。
  • 利用网络资源:网络资源丰富,包含许多答案。例如,R语言有许多可用的手册,官方网站上也有常见问题解答(FAQ)。
  • 自行实验:尝试摆弄问题,通过检查或实验来寻找答案。例如,如果一个函数运行不正常,可以尝试改变输入参数,观察输出或错误信息的变化。
  • 请教身边的朋友:如果你有幸认识精通R的朋友,直接请教他们通常比在论坛上发帖更便捷。
  • 阅读源代码:如果你是程序员,或许可以通过阅读源代码来找到答案。

提问时的关键信息

如果自行尝试后仍未找到答案,需要向他人求助,那么清晰地说明你已经做过的尝试至关重要。否则,如果答案就在文档中,对方可能会回复“请阅读文档”,从而浪费一轮沟通时间。

因此,让别人知道你已做过功课、查阅过各种资料,这非常有用,能节省大量时间。

当你需要在邮件列表或讨论板上提问时,在提问前需要考虑以下几点:

  • 问题是否可复现:如果他人能复现你的问题,将更容易找到解决方案。提供能复现问题的代码或简单示例会非常有帮助。
  • 明确期望的输出:说明你期望得到什么结果。这有助于界定错误的性质。
  • 描述实际看到的输出:说明实际发生了什么意外情况,从而引发了你的疑问。
  • 提供版本信息:说明你使用的R版本、相关R包的版本,以及你的操作系统(如Mac、Windows、Linux)。旧版本可能存在已知问题,升级或许就能解决。
  • 提供其他必要信息:根据问题的具体情况,可能需要提供其他相关信息。

如何撰写有效的邮件主题

向论坛或讨论板发送邮件时,在主题行中提供尽可能多且有用的信息非常重要。

以下是一些主题行示例,展示了其有用性的差异:

  • 帮助:无法拟合线性模型:这是最无用的。信息量极少,只知道线性模型有问题。
  • R 2.15.0中,lm函数在处理大型数据框时导致段错误(Mac OS X 10.6.3):这好得多。它说明了R版本、使用的函数、操作系统及版本,并总结了实际发生的问题。
  • R 2.15.0, lm, Mac OS X 10.6.3:处理大型数据框时出现段错误:只需稍微调整格式,将R版本、函数和操作系统版本放在前面提供上下文,然后说明问题。这样重要的细节在阅读邮件正文前就已一目了然。

提问时的注意事项

在论坛或邮件列表提问时,有几件你应该做的事情:

  • 描述目标,而非步骤:让他人了解你试图实现的更大目标,他们可能有更好、更简单的方法来达成目标,从而绕过你遇到的问题。
  • 问题要具体明确:提供必要的细节和你试图做的事情。提供最少必要信息,而非最大信息量。冗长的输出通常无助于诊断问题。
  • 保持礼貌:礼貌待人永远不会错,促进邮件列表的文明交流总是好事。
  • 后续分享解决方案:如果你后来自己找到了解决方案,请跟帖说明问题所在以及你是如何解决的,这对社区中的其他人非常有用。

在论坛发帖时,有几件你绝对不应该做的事情:

  • 不要轻易声称发现了程序错误(Bug):十有八九,这只是用户对预期行为的误解。
  • 不要用提问代替做功课:这通常不受欢迎。
  • 绝对不要在邮件列表或论坛上发布作业问题:布置作业的人会阅读这些列表,他们能轻易识别出所有作业问题。
  • 不要同时向多个邮件列表发送同一封邮件:这很烦人,因为订阅了不同邮件列表的人会多次收到你的消息。应找出最适合你问题的邮件列表,然后只发送到那里。
  • 不要在不提供任何线索的情况下要求他人调试你的代码:如果只是贴出一长串代码说“这里有地方不对,请帮忙”,这非常困难。最好指明你认为问题可能出在哪里,以及你试图做什么。

案例分析:一个失败的提问

这是一个关于R开发邮件列表近期帖子的简短案例分析。该列表面向从事R开发工作(如开发包或修改R源代码)的人员。

  • 主题large data - confused(大型数据 - 困惑)。主题信息量很少,问题不明确。
  • 内容:“我正在尝试将数据加载到R中,但我完全搞不懂了。这可能主要是因为我完全是个R新手,但这让我的研究项目卡住了。”

从这条消息中,你几乎无法了解提问者的问题。因此,回复是可以预见的:第一个回复者说“是的,你确实很困惑”,然后提供了发帖指南的链接(每个发邮件前都应阅读),以及一系列手册。可以看到,由于答案很可能就在手册里,一轮邮件沟通立即被浪费了。

这次交流的问题在于:

  1. 问题发错了邮件列表(应发到R帮助列表)。
  2. 邮件主题非常模糊。
  3. 问题本身很宽泛,没有提供可复现的示例。
  4. 没有证据表明提问者曾努力自行解决问题(如搜索网络、查阅手册、实验等)。

最终,这很可能导致提问者没有获得答案。

本课程的求助渠道

最后,我们来看看本课程可以求助的几个地方:

  • 课程讨论板:这可能是最有用的,因为你的同学可以在那里帮助你,我也可以在课外在讨论板上回复你。
  • R帮助邮件列表:你可以向此列表发帖,当然,请遵循我们刚才讨论的所有规则。
  • 其他项目特定邮件列表:取决于你正在进行的其他项目,可能还有其他特定软件的邮件列表。

本次讨论的灵感来源于埃里克·雷蒙德的文章《如何聪明地提问》,鼓励你阅读该文,它更长,包含更多有用的技巧。

总结

本节课中,我们一起学习了在学习R语言时如何高效获取帮助。我们探讨了提问前自行寻找答案的方法,明确了提问时应提供的关键信息(如可复现的示例、版本信息等),学习了如何撰写有效的邮件主题,并列举了提问时应该做和不应该做的事情。最后,通过一个案例分析,我们看到了一个失败的提问是如何发生的,并了解了本课程可用的主要求助渠道。记住,清晰、具体且表明你已做过努力的提问,将大大提高你获得有效帮助的几率。

027:R 控制台输入与表达式评估 🧮

在本节课中,我们将学习如何在 R 控制台中输入代码,以及 R 如何评估和执行这些代码。我们将重点理解表达式、赋值操作符、对象以及 R 的打印机制。


表达式与赋值

上一节我们介绍了 R 控制台的基本界面,本节中我们来看看如何向其中输入代码。在 R 提示符后输入的内容被称为表达式

例如,符号 <- 看起来像一个左箭头,它由小于号 < 和连字符 - 组成。这个符号被称为赋值操作符。赋值操作符的作用是将一个值赋予一个符号。

在第一个表达式中,我创建了一个名为 x 的符号,并将数值 1 赋给它。我使用赋值操作符来完成这个操作。因此,x <- 1 是一个 R 表达式。

x <- 1

在下一个表达式中,我将打印这个值。print 是一个函数,我将符号 x 传递给它。当我打印 x 时,我得到它的值,在这个例子中是 1

print(x)

另一个需要理解的概念是,x 也被视为一个 R 对象。它是一个数值对象,包含一个元素。本质上,它是一个第一个元素为数字 1 的数值向量。


自动打印与对象类型

在第三个表达式中,我直接在提示符后输入 x 并按下回车。这时,x 的值会被打印出来。这被称为自动打印。这是另一种打印对象的方式,无需显式调用 print 函数。

x

在这个表达式中,我创建了一个名为 msg 的新符号,并将字符串 "hello" 赋给它。现在,msg 是一个字符向量,其第一个元素是字符串 "hello"。如果我愿意,可以向这个向量添加其他元素,但它们都必须是字符类型。

msg <- "hello"

语法、完整表达式与注释

语言的语法决定了表达式是否正确或完整。例如,如果我输入 x <- 后没有输入其他内容,这便不是一个完整的表达式。当我按下回车时,什么也不会发生,因为 R 在等待表达式被完成。

此外,这里还有一个井号 #。这个符号表示其右侧的所有内容都是注释。R 引擎会忽略该符号右侧的任何内容。因此,你可以在代码中添加注释或给自己的笔记,R 会忽略这些注释。

# 这是一个注释,R 会忽略它
x <- 5 # 将 5 赋值给 x

表达式的评估与自动打印

一旦你在提示符后输入了一个语法有效且完整的表达式并按下回车,R 引擎就会评估这个表达式,并返回评估结果。

有时,评估一个表达式后什么也不会发生,因为没有需要显示的内容。例如,在第一个表达式中,当我说 x 被赋值为 5 时,我创建了一个名为 x 的对象,它是一个第一个元素为 5 的数值向量。当我按下回车时,什么也没有显示。

x <- 5

但现在,当我输入 x 并按下回车时,它会打印出值 5。这被称为自动打印。当你只输入一个对象的名字并按下回车时,R 默认会自动打印该对象的值。这与对该对象调用 print 函数的效果相同。

x

你可能会注意到,当我打印对象 x 时,前面有一个 [1]。这表示你看到的数字 5 是该向量的第一个元素。当我们查看更长的向量时,这会更有意义。


序列与向量的打印

对于打印,你会看到这里我创建了一个名为 x 的对象,它是序列 120。这里使用的冒号操作符 : 用于创建序列。当我说 1:20 时,它创建了一个从 120 的序列。

x <- 1:20

现在,当我自动打印 x 时,你会看到一个更长的向量。这是一个整数向量。打印输出的第一行旁边有一个 [1],因为那是第一个元素。第二行旁边有一个 [16],因为该行的第一个元素是这个向量的第 16 个元素。这一切都很直观,这就是打印输出的工作方式。

x

总结

本节课中我们一起学习了 R 控制台输入与表达式评估的核心概念。我们了解了什么是表达式和赋值操作符,如何创建数值与字符对象,以及 R 的自动打印机制。我们还学习了注释的用法,以及如何创建和打印序列。理解这些基础是编写有效 R 代码的第一步。

028:R 的数据类型、对象与属性 📊

在本节课中,我们将深入探讨 R 语言的核心细节,特别是 R 中使用的不同数据类型,以及对这些数据类型的一些基本操作。


概述

R 语言中的所有内容都被视为对象。对象可以包含各种数据,但每个对象都属于特定的类别。理解这些基本的数据类型和对象属性,是有效使用 R 进行数据分析的基础。


R 中的基本对象类型

在 R 中,所有你操作和遇到的事物都可以被称为对象。对象可以是各种类型,包含各种数据,但 R 中的一切都是对象。

R 有五种基本的原子类对象。这些是非常底层或基础的对象类别,它们分别是:

  • 字符型
  • 数值型(例如实数或小数)
  • 整型
  • 复数型
  • 逻辑型(即 TRUE/FALSE 类型)

向量:R 中最基本的对象

R 中最基本的对象被称为向量。一个向量包含多个相同类型对象的副本。例如,它可以是一个字符向量或一个整型向量。

关于标准向量的一个重要规则是:不能混合不同类型的对象。你不能创建一个同时包含字符型和数值型,或数值型和整型,或整型和逻辑型的向量。向量中的所有元素必须属于同一类别。

当然,任何伟大的规则都有例外。对于向量来说,有一种类型的向量可以包含多个不同类别的元素,那就是列表

列表表现为一个向量(即一个对象序列),但其中的每个元素可以是不同类别的对象。例如,一个列表可以包含一个字符、一个数值和一个逻辑值。列表的一个元素本身也可以是另一个列表,甚至是一个数据框。列表中的任何元素都可以是任何东西,这也正是列表如此有用的原因。因此,列表是“向量只能包含相同类别元素”这一通用规则的一个例外。


创建向量

你可以使用 vector 函数创建一个空向量。vector 函数有两个基本参数:

  • 第一个参数是对象的类别,即你希望向量中包含的对象类型。
  • 第二个参数是向量本身的长度

其基本语法为:

vector(mode = "character", length = 0)

数字对象

R 中最重要的对象类型之一当然是数字。R 中的数字通常被视为数值型对象。几乎所有数字都被当作双精度实数处理。即使你输入的是像 1 或 2 这样的数字,R 也将其视为数值型对象。

有一种方法可以明确指定你想要一个整型数,即使用大写字母 L 作为后缀。例如:

  • 在 R 中输入 1 会得到一个数值型对象。
  • 输入 1L 则会明确得到一个整型对象。

这种区别目前看来并不非常重要,但以后会变得重要。


特殊数值:Inf 与 NaN

R 中还有一些特殊的数值。

Inf 代表无穷大Inf 就像一个实数,可以用于计算并得到预期的结果。例如:

  • 1 / 0 会得到 Inf
  • 1 / Inf 会得到 0
  • 同样也存在 -Inf(负无穷大)。

NaN 代表未定义的值,可以理解为“不是一个数字”。例如:

  • 0 / 0 是未定义的,因此会返回 NaN
  • NaN 也可以被视为一种缺失值,我们稍后会更多地讨论缺失值。

对象的属性

上一节我们介绍了 R 中的基本对象类型,本节中我们来看看每个 R 对象都附带的一个概念:属性

并非每个 R 对象都必须有属性,但属性可以是 R 对象的一部分。我们将遇到的一些最常见的属性类型包括:

  • 名称维度名称
  • 维度:例如,矩阵会有维度(行数和列数)。如果你有一个多维数组,它将有两个以上的维度。
  • 对象的类别:每个对象都有一个类别。例如,数值型对象的类别是 numeric,整型对象的类别是 integer
  • 对象的长度:对于向量来说很简单,对象的长度就是向量中元素的数量。
  • 其他用户定义的属性元数据:这些是你可以使用各种属性函数为对象单独定义的东西。

有一个通用的函数叫做 attributes(),它允许你设置或修改 R 对象的属性。


总结

在本节课中,我们一起学习了 R 语言中关于数据类型、对象和属性的核心概念。我们了解到 R 中一切都是对象,并掌握了五种基本的原子数据类型。我们探讨了向量作为基础对象的重要性及其创建方法,同时认识了列表作为可以混合类型的特殊向量。我们还区分了数值型与整型数字,并了解了 InfNaN 这两个特殊数值的含义。最后,我们学习了对象属性的概念,知道它们如何为对象提供额外的信息(如名称、维度、类别和长度),并了解了如何使用 attributes() 函数来操作这些属性。理解这些基础知识是后续进行更复杂数据操作和分析的关键。

029:数据类型 - 向量与列表 📊

在本节课中,我们将学习 R 语言中两种重要的数据结构:向量和列表。我们将了解如何使用 c() 函数创建向量,探索向量中不同类型元素的混合与强制转换规则,并学习如何显式地进行类型转换。最后,我们将介绍列表,这是一种可以容纳不同数据类型元素的灵活结构。


使用 c() 函数创建向量

c() 函数是另一个可用于创建对象向量的函数。你可以将 c 理解为“连接”的缩写,因为它可以用来将事物连接在一起。

以下是使用 c() 函数创建不同类型向量的示例:

  • 数值向量x <- c(0.5, 0.6) 创建一个长度为 2 的数值向量,第一个元素是 0.5,第二个元素是 0.6。
  • 逻辑向量y <- c(TRUE, FALSE) 创建一个逻辑向量。TRUEFALSE 的简写是 TF(大写)。因此,c(T, F) 会得到相同的对象。
  • 字符向量z <- c("a", "b", "c") 通过连接一串字符来创建字符向量。
  • 整数向量a <- 9:29 使用冒号运算符创建一个序列,从而创建整数向量。
  • 复数向量c <- c(1+0i, 2+4i) 创建复数向量,其中 i 是一个特殊符号,表示复数的虚部。

使用 vector() 函数创建向量

除了 c() 函数,你还可以使用 vector() 函数创建具有特定类型和长度的向量。

例如,x <- vector("numeric", length = 10) 将创建一个长度为 10 的数值向量。默认情况下,它会用默认值初始化向量。对于数值向量,默认值是 0。


向量中的混合类型与强制转换

上一节我们介绍了如何创建单一类型的向量。那么,如果你创建一个向量并混合两种不同类型的对象会发生什么?

一般规则是,R 会创建一个“最小公分母”向量。它不会报错,但会发生的情况是,它会将向量强制转换为那个“最小公分母”的类。

以下是几个例子:

  • 示例 1y <- c(1.7, "a")。这里混合了数值和字符。最小公分母是字符。因此,y 将是一个字符向量,第一个元素是字符串 "1.7",第二个元素是字母 "a"
  • 示例 2z <- c(TRUE, 2)。这里混合了逻辑值和数值。最小公分母是数值。TRUE 将被转换为数字。在 R 的约定中,TRUE 表示为数字 1,FALSE 表示为数字 0。因此,你将得到一个向量 c(1, 2)
  • 示例 3w <- c("a", TRUE)。这里混合了字符和逻辑值。最小公分母再次是字符。最终得到的向量是 c("a", "TRUE")。注意,这里的 "TRUE" 是字符串,而不是逻辑值。

当你混合向量中的不同类型元素时,需要稍微注意 R 中可能发生的类型强制转换。因为你不会收到错误,但强制转换会在幕后发生。


显式类型转换

上一节我们讨论了在幕后发生的隐式强制转换。但你也可以使用通常以 as. 开头的函数,显式地将对象从一个类强制转换为另一个类。

例如,如果你想将某物转换为数值,可以使用名为 as.numeric() 的函数。如果你想转换为字符,可以使用函数 as.character()

以下是应用这些函数的示例。假设我们从一个对象 x 开始,它是一个从 0 到 6 的整数序列:x <- 0:6

  • 转换为数值as.numeric(x)。它打印出 0 1 2 3 4 5 6,看起来像整数对象,但它实际上是数值型。
  • 转换为逻辑值as.logical(x)。根据约定,0 是 FALSE,任何大于 0 的数字都将是 TRUE。因此,转换后我们得到 FALSE TRUE TRUE TRUE TRUE TRUE TRUE
  • 转换为字符as.character(x)。它获取所有数字并将它们转换为字符,现在我们得到字符串 "0", "1", "2" 等。
  • 转换为复数as.complex(x)。这相当简单,它表示你有一个复数,其中所有虚部都为零:0+0i 1+0i 2+0i ...

强制转换失败与 NA

需要注意的是,强制转换并不总是有效。当它失败时,你会得到所谓的 NA 值(不可用)。无意义的强制转换将导致 NA

例如:

  • x <- c("a", "b", "c")
  • as.numeric(x):真的没有办法将字母 A、B、C 转换为数值变量,因此你将得到一个 NA 向量,并附带一条警告信息。
  • as.logical(x):同样,你会得到一个 NA 向量。

列表数据结构

接下来要讨论的数据类型是列表。我在本讲座前面稍微提到了列表。其概念是,列表是一种递归向量,但列表的每个元素都可以是不同类的对象。这使得列表对于携带不同类型的数据非常方便。它们在 R 中非常有用,并且变得非常常见,特别是与我们将要学习的其他类型的函数结合使用时。

以下是创建列表的示例:
x <- list(1, "a", TRUE, 1+4i)

这里我使用 list() 函数创建了一个名为 x 的列表。第一个元素是数值对象 1,第二个元素是字符 "a",第三个是逻辑值 TRUE,第四个是复数 1+4i。这对于列表来说不是问题。

当我自动打印列表时,你会看到它的打印方式略有不同。它不像向量那样打印,因为每个元素都不同。你可以看到这里有双括号,元素由双括号索引。第一个元素是向量 [[1]] [1] 1,第二个元素是向量 [[2]] [1] "a",第三个元素是向量 [[3]] [1] TRUE,第四个元素是向量 [[4]] [1] 1+4i

你会注意到列表的元素周围有双括号 [[]],而其他向量的元素只有单括号 []。这是区分列表与其他类型向量的一种方式。


总结

本节课中,我们一起学习了 R 语言中的向量和列表。我们掌握了使用 c()vector() 函数创建向量的方法,理解了当向量中混合不同类型数据时,R 会进行隐式的“最小公分母”强制转换。我们还学会了使用 as.numeric(), as.character() 等函数进行显式类型转换,并知道转换失败会产生 NA 值。最后,我们认识了列表这种强大的数据结构,它可以容纳任意不同类型的元素,通过双括号 [[]] 进行索引,为组织复杂数据提供了极大的灵活性。理解这些基础数据类型是进行有效 R 编程的关键。

030:R语言中的矩阵数据类型 🧮

在本节课中,我们将要学习R语言中的一种重要数据结构——矩阵。我们将了解矩阵的本质、创建矩阵的多种方法以及其核心属性。

概述

矩阵是R语言中一种特殊的向量,它并非一个独立的对象类别,而是拥有一个名为“维度”属性的向量。这个维度属性是一个长度为2的整数向量,其中第一个数字代表矩阵的行数,第二个数字代表矩阵的列数。

矩阵的本质与创建

上一节我们介绍了矩阵的基本概念,本节中我们来看看如何创建一个矩阵。

创建矩阵的一种方法是使用 matrix() 函数。通过该函数,我们可以明确指定矩阵的行数和列数。

M <- matrix(nrow = 2, ncol = 3)

输入对象名 M 并回车,可以自动打印这个矩阵。你会看到矩阵初始化为 NA 值,并且它有两行三列。括号中的数字用于标记行和列。

调用 dim() 函数可以获取矩阵的维度属性。

dim(M)

该函数会返回一个向量,其中第一个数字是行数,第二个数字是列数。调用 attributes() 函数会返回一个列表,其第一个元素就是包含维度信息(例如 c(2, 3))的 dim 属性。这些都体现了矩阵作为一个拥有维度属性的向量的特性。

矩阵的填充方式

矩阵是按列填充的。你可以将其理解为:矩阵接收一个向量,然后按列将所有数字插入矩阵中。首先填充第一列,当达到最大行数后,再开始填充第二列,依此类推。

以下是按列填充的示例:

matrix(1:6, nrow = 2, ncol = 3)

这个矩阵的构造过程如下:首先取数字1和2(因为只有两行)构成第一列;然后取数字3和4构成第二列;最后取数字5和6构成第三列。

其他创建矩阵的方法

除了使用 matrix() 函数,还有其他方法可以创建矩阵。

一种方法是为向量添加维度属性。例如,先创建一个从1到10的序列向量,然后为其分配维度属性。

m <- 1:10
dim(m) <- c(2, 5)

这段代码将向量 m 转换为一个2行5列的矩阵,并且填充方式同样是按列进行。

另一种常见的方法是使用列绑定或行绑定函数。cbind()rbind() 函数可以实现这个功能。

假设有两个对象:x 是序列1到3,y 是序列10到12。

使用 cbind() 进行列绑定:

x <- 1:3
y <- 10:12
cbind(x, y)

这将得到一个矩阵,其中第一列是1到3,第二列是10到12。

使用 rbind() 进行行绑定:

rbind(x, y)

这将得到一个矩阵,其中第一行是1、2、3,第二行是10、11、12。因此,cbind()rbind() 是创建矩阵的另一种有效方式。

总结

本节课中我们一起学习了R语言中的矩阵。我们了解到矩阵本质上是带有维度属性的向量,并掌握了使用 matrix() 函数、为向量添加 dim 属性以及使用 cbind()rbind() 函数创建矩阵的多种方法。理解矩阵按列填充的机制对于正确构建和操作矩阵至关重要。

031:R语言中的因子数据类型 📊

在本节课中,我们将要学习R语言中一种特殊的数据类型——因子。因子是用于表示分类数据的向量,在统计分析中扮演着至关重要的角色。

什么是因子?🤔

上一节我们介绍了向量的基本概念,本节中我们来看看因子。因子是一种特殊的向量,专门用于表示分类数据。它主要分为两种类型:无序因子有序因子

  • 无序因子:用于表示没有内在顺序的分类标签。例如,性别(男/女)。
  • 有序因子:用于表示有顺序或等级的分类,但这些分类本身不是数值。例如,大学职称(助理教授、副教授、正教授)。

你可以将因子理解为一个带有标签的整数向量。例如,一个表示“高”、“中”、“低”的因子,在R内部可能用数字1、2、3来存储,但每个数字都对应一个易于理解的标签。

为什么因子很重要?💡

因子之所以重要,主要有以下两个原因:

  1. 建模函数识别:像 lm()glm() 这样的建模函数(我们将在后续课程中讨论)会特殊处理因子变量。
  2. 数据自描述性:使用带有标签的因子比使用简单的整数向量更好,因为因子是“自描述”的。一个值为“男”、“女”的变量,远比一个值为1、2的变量更易于理解。在许多数据集中,你可能会发现用1和2编码的变量,但很难判断它究竟是只取1和2的数值变量,还是一个分类变量。使用因子变量,标签的编码就内置于变量本身,理解起来容易得多。

如何创建因子?🔨

因子可以通过 factor() 函数创建,该函数的输入通常是一个字符向量。

以下是创建因子的示例代码:

# 创建一个字符向量
x <- c("yes", "yes", "no", "yes", "no")
# 使用factor()函数将其转换为因子
x <- factor(x)
print(x)

运行上述代码,输出结果会与普通字符向量略有不同。它不仅会显示值(“yes”, “yes”, “no”, “yes”, “no”),还会单独显示一个名为 levels 的属性,列出该因子所有可能的类别(“no”和“yes”)。

你可以使用 table() 函数来获取每个水平的频数统计:

table(x)

unclass() 函数可以剥离向量的类别属性,让我们看到因子在R内部的真实表示:

unclass(x)

你会看到,因子在底层实际上是一个整数向量(例如 2, 2, 1, 2, 1),并附带一个 levels 属性(“no”, “yes”)。了解这一点有助于理解R如何处理因子。

如何设置因子的水平顺序?🔄

因子中水平的顺序可以通过 factor() 函数中的 levels 参数来设置。这一点有时非常重要,因为在建模函数中引入因子变量时,了解哪个是基线水平(即第一个水平)很关键。

默认情况下,R会按照字母顺序来确定水平的顺序。例如,一个包含“yes”和“no”的因子,“no”会排在“yes”前面,因此“no”是基线水平。

如果你希望改变基线水平,例如让“yes”作为第一个水平,就需要显式地指定 levels 参数:

# 创建因子,并指定水平的顺序为“yes”在前,“no”在后
x <- factor(c("yes", "yes", "no", "yes", "no"), levels = c("yes", "no"))
print(x)

现在,虽然元素值相同,但 levels 属性的顺序变成了“yes”、“no”,这意味着“yes”现在是基线水平。

总结 📝

本节课中我们一起学习了R语言中的因子数据类型。我们了解到因子是用于表示分类数据的特殊向量,分为无序和有序两种。因子因其自描述性和被建模函数特殊对待而非常重要。我们学会了如何使用 factor() 函数创建因子,以及如何通过 levels 参数来控制因子水平的顺序,这对于后续的统计分析至关重要。

032:R语言中的缺失值处理 🧩

在本节课中,我们将要学习R语言中一个特殊且重要的概念——缺失值。我们将了解缺失值的两种主要类型(NANaN),学习如何检测它们,并理解它们之间的区别。

概述

在数据分析中,数据缺失是常见现象。R语言提供了专门的符号和函数来处理这些缺失值,确保分析的准确性和完整性。理解如何识别和处理缺失值是数据清洗和准备过程中的关键一步。

缺失值的类型

上一节我们介绍了缺失值的重要性,本节中我们来看看R语言中具体的缺失值类型。

R语言中的缺失值主要由两种符号表示:

  • NA:用于表示绝大多数情况下的缺失值。
  • NaN:特指“非数字”,通常由未定义的数学运算(如0/0Inf - Inf)产生。

一个重要的关系是:所有NaN值都被视为NA(即缺失),但并非所有NA值都是NaN

检测缺失值的函数

为了识别数据中的缺失值,R语言提供了专门的检测函数。

以下是两个核心的检测函数及其用途:

  • is.na():用于检测对象中是否存在NA值(包括NaN)。
  • is.nan():专门用于检测对象中是否存在NaN值。

实践示例:检测缺失值

让我们通过具体的代码示例来理解这些函数如何工作。

首先,我们创建一个包含普通数值和一个NA的数值向量。

x <- c(1, 2, NA, 10, 3)

使用is.na()函数检测x中的缺失值。它会返回一个逻辑向量(TRUE/FALSE),指示每个元素是否缺失。

is.na(x)

输出FALSE FALSE TRUE FALSE FALSE
结果表示,向量x的第三个元素是缺失值(NA)。

接着,我们用is.nan()检测NaN。由于x中只有NA,没有NaN,所以全部返回FALSE

is.nan(x)

输出FALSE FALSE FALSE FALSE FALSE

现在,我们创建一个同时包含NANaN的向量来观察区别。

y <- c(1, NA, 3, NaN, 5)

再次使用两个函数进行检测:

is.na(y)

输出FALSE TRUE FALSE TRUE FALSE
函数is.na()将第二位的NA和第四位的NaN都识别为缺失值(TRUE)。

is.nan(y)

输出FALSE FALSE FALSE TRUE FALSE
函数is.nan()则只将第四位的NaN识别为TRUE,而NA不被认为是NaN

这个例子清晰地展示了is.na()is.nan()的不同之处。

缺失值的类

一个容易忽略的细节是,NA值本身也可以具有数据类型(类)。这意味着存在数值型缺失值字符型缺失值整数型缺失值等。尽管它们都显示为NA,但在内部可能属于不同的类,这在某些严格类型检查的函数中可能会有影响。

总结

本节课中我们一起学习了R语言中缺失值的核心知识。我们认识了两种缺失值符号NANaN,掌握了使用is.na()is.nan()函数来检测它们的方法,并理解了NaNNA的子集这一重要关系。正确处理缺失值是进行可靠数据分析的基础,在后续的数据清洗和计算步骤中务必留意它们的存在。

033:数据框 📊

概述

在本节课中,我们将要学习R语言中一个至关重要的数据类型——数据框。数据框是存储表格数据的主要形式,在统计学和数据科学中应用极为广泛。我们将了解数据框的基本概念、特性、创建方法及其与矩阵的区别。


数据框:存储表格数据的关键结构

上一节我们介绍了列表等数据结构,本节中我们来看看专门用于处理表格数据的数据框。

数据框是R中一个关键的数据类型,它用于存储表格数据。当然,并非所有类型的数据都是表格形式的,但由于大量数据都以表格形式存在,因此数据框在R中非常重要。

数据框本质上是一种特殊类型的列表,其中该列表的每个元素都具有相同的长度。你可以将数据框的每一列视为列表的一个元素。为了构成一个表格,每一列自然需要有相同的长度。

然而,每一列不必是相同的数据类型。第一列可以是数值型,第二列可以是因子型,第三列可以是整型,第四列可以是逻辑型。各列的类型可以不同。

因此,与矩阵必须在每个元素中存储相同类型的对象不同,数据框可以存储不同类别的对象。


数据框的特殊属性

数据框还拥有一些特殊的属性。第一个特殊属性称为行名。数据框的每一行都有一个名称,这有助于为数据添加注释。例如,每一行可能代表一项研究中招募的一名受试者,那么行名就可以是受试者ID。不过,有时行名并不重要,通常你只会使用1、2、3等作为行名。


创建与转换数据框

数据框最常通过调用read.table()read.csv()函数来创建。我们将在讲解如何将数据读入R时深入探讨这一点。

你也可以通过调用data.matrix()函数,将数据框转换为矩阵。但需要注意的是,如果一个数据框包含多种不同类型的对象,当你将其强制转换为矩阵时,每个对象都会被强制转换,以使它们类型一致,因此最终结果可能并非完全如你所愿。

除了使用read.table()read.csv(),你还可以使用data.frame()函数创建数据框。

以下是一个使用data.frame()创建简单数据框的示例:

# 创建一个数据框
x <- data.frame(foo = 1:4, bar = c(T, T, F, F))

在这个例子中,第一列名为foo,是一个从1到4的整数序列;第二列名为bar,是一个包含两个TRUE和两个FALSE的逻辑向量。

当我们自动打印这个数据框x时,它会显示出这两列。由于我没有指定特殊的行名,行名默认显示为1、2、3、4(因为有四行)。

调用nrow(x)函数可以显示这个数据框有4行。
调用ncol(x)函数可以显示这个数据框有2列。


总结

本节课中,我们一起学习了R语言中的数据框类型。我们了解到数据框是一种用于存储表格数据的特殊列表,其每列长度相同但类型可以不同。我们探讨了它的行名属性,并学习了如何使用data.frame()函数创建数据框,以及如何通过nrow()ncol()函数查看其维度。理解数据框是掌握R语言数据处理的基础。

034:数据类型与名称属性 📝

在本节课中,我们将要学习R语言中一个非常实用的特性:为数据对象添加名称属性。我们将看到如何为向量、列表和矩阵等对象添加名称,这有助于编写更清晰、更易读的代码。

概述

在R语言中,许多对象都可以拥有名称属性。这不仅仅是数据框的特性,而是适用于我们创建的各种对象。为对象元素命名可以使代码更具可读性,并创建自描述性的数据结构。

为向量添加名称

首先,我们来看如何为向量添加名称。以下是一个创建整数序列向量的例子:

x <- 1:3

默认情况下,这个向量没有名称。当我们调用 names 函数时,会得到 NULL 值:

names(x)
# 输出: NULL

然而,我们可以为向量 x 的每个元素指定一个名称。例如:

names(x) <- c("food", "bar", "north")

现在,当我们打印向量 x 时,不仅会看到数值 1, 2, 3,还会看到每个数值上方显示我们刚刚指定的名称。再次调用 names 函数,会返回与向量每个元素相关联的名称:

names(x)
# 输出: "food" "bar" "north"

为列表添加名称

上一节我们介绍了如何为向量命名,本节中我们来看看列表的命名方法。列表同样可以拥有名称属性。

以下是创建一个列表并为其元素命名的例子:

my_list <- list(A = 1, B = 2, C = 3)

在这个例子中,我们使用 list 函数创建列表,并直接在参数中为每个元素命名(第一个元素名为“A”,第二个为“B”,第三个为“C”)。当我们打印这个列表时,它会显示每个元素的名称及其对应的值。

为矩阵添加名称

最后,我们来学习如何为矩阵添加名称。矩阵的名称属性被称为维度名称。

以下是创建一个2x2矩阵的例子:

m <- matrix(1:4, nrow=2, ncol=2)

要为矩阵设置行名和列名,我们使用 dimnames 函数。我们需要向其传递一个列表,该列表的第一个元素是行名向量,第二个元素是列名向量:

dimnames(m) <- list(c("A", "B"), c("C", "D"))

在这个例子中,我们将行命名为“A”和“B”,将列命名为“C”和“D”。现在,当我们打印矩阵 m 时,可以看到行和列都按照我们的意愿被标记了。

总结

本节课中我们一起学习了R语言中为数据对象添加名称属性的方法。我们掌握了:

  • 如何使用 names() 函数为向量元素命名。
  • 如何在创建列表时直接为其元素命名。
  • 如何使用 dimnames() 函数为矩阵设置行名和列名。

为对象添加名称是一个简单的操作,却能极大地提升代码的可读性和自解释性,是编写高质量R语言代码的重要技巧。

035:数据类型总结 📊

在本节课中,我们将系统性地总结R语言中的基本数据类型。我们将回顾原子向量、列表、因子、缺失值、数据框以及对象命名等核心概念,帮助你构建一个清晰的数据类型知识框架。

上一节我们介绍了R语言中各种数据结构的操作,本节中我们来看看如何系统地总结这些基本数据类型。

原子向量类

R语言中最基本的数据结构是原子向量,它要求所有元素必须属于同一类别。以下是主要的原子向量类别:

  • 数值型(numeric):用于存储实数,例如 c(1.5, 2.8, 3.14)
  • 字符型(character):用于存储文本字符串,例如 c("apple", "banana", "cherry")
  • 整型(integer):用于存储整数,通常通过在数字后加L来指定,例如 c(1L, 2L, 3L)
  • 复数型(complex):用于存储复数,例如 c(1+2i, 3-4i)

列表

列表是原子向量的一个重要例外,它允许包含不同类别的元素。你可以使用list()函数来创建列表。

my_list <- list(1, "a", TRUE, 1+4i)

因子

因子是一种特殊的数据类型,专门用于编码分类数据,无论是有序数据还是无序数据。factor()函数用于创建因子。

# 无序因子
gender <- factor(c("male", "female", "female", "male"))
# 有序因子
status <- factor(c("low", "medium", "high"), ordered = TRUE, levels = c("low", "medium", "high"))

缺失值

在R中,缺失值主要由NA(Not Available)表示。对于数值计算中产生的未定义结果(如0除以0),则用NaN(Not a Number)表示。

x <- c(1, 2, NA, 4, NaN, 6)
is.na(x) # 检测NA和NaN
is.nan(x) # 仅检测NaN

数据框

数据框是用于存储表格数据的核心结构,其特点是每一列都可以是不同的数据类型。你可以使用data.frame()函数来创建数据框。

df <- data.frame(
  name = c("Alice", "Bob", "Charlie"),
  age = c(25, 30, 35),
  score = c(85.5, 90.0, 77.3)
)

对象命名

为R对象(如向量的元素、列表的成员、数据框的行列)赋予名称,可以创建自描述性数据,使代码更易读。names()函数用于设置或获取名称。

# 为向量元素命名
grades <- c(90, 85, 88)
names(grades) <- c("Math", "Science", "History")

本节课中我们一起学习了R语言基本数据类型的全景图。我们回顾了要求元素类型统一的原子向量(数值、字符、整型、复数),以及可以容纳混合类型的列表。我们还探讨了用于分类数据的因子、表示缺失信息的NANaN、存储表格的万能工具数据框,以及通过命名增强数据可读性的方法。掌握这些基础是高效使用R进行数据分析的关键。

036:📊 读取表格数据

在本节课中,我们将要学习如何在 R 语言中读取和写入数据。R 提供了多种函数来处理不同类型的数据文件,我们将重点介绍其中最常用的一些函数,特别是用于读取表格数据的函数。

读取数据的主要函数

R 中有几个核心函数用于将数据读入工作环境。以下是其中最重要的几个:

  • read.tableread.csv:这两个函数用于读取表格数据,是 R 中最常用的数据读取函数。它们读取以行和列格式存储数据的文本文件,并返回一个 R 数据框。
  • readLines:此函数用于读取文本文件的各行。实际上,它可以读取任何类型的文件,并将内容以字符向量的形式返回给 R。
  • source:这个函数对于读取 R 代码文件非常重要。例如,如果你有保存到文件中的函数或其他 R 代码,source 函数会将这些代码全部读入 R 环境。
  • dget:此函数也用于读取 R 代码文件,但专门用于读取那些已被“反解析”为文本文件的 R 对象。我们稍后会对此进行更多讨论。
  • loadunserialize:这两个函数用于将二进制对象读入 R。

写入数据的对应函数

与读取函数相对应,用于写入数据的主要函数有:write.tablewriteLinesdumpdputsaveserialize。它们的功能与各自的读取函数配对。

上一节我们介绍了数据读写的基本函数,本节中我们来看看最核心的 read.table 函数。

详解 read.table 函数

read.table 函数是将数据读入 R 最常用的函数。了解其参数的工作原理和含义至关重要。

以下是 read.table 函数的主要参数:

  • file:第一个参数很明显,是文件名或连接名(我们稍后会讨论连接)。通常,你需要提供一个字符串形式的文件名,即计算机中某个文件的路径。
  • header:这是一个逻辑标志,指示第一行是否为标题行。例如,如果第一行包含所有变量名,那么它实际上不是数据,而是标签行。你需要告诉 read.table 函数第一行是否包含变量名,或者第一行是否直接就是数据。
  • sep:代表“分隔符”。它是一个字符串,指示各列是如何分隔的。例如,如果文件由逗号分隔,那么分隔符就是逗号。有时文件可能由分号、制表符或空格分隔,你需要告诉 read.table 分隔符是什么。
  • colClasses:这是一个字符向量,其长度与数据中的列数相同。该向量指示数据中每一列的类别。例如,第一列是数值型,第二列是逻辑型,第三列是因子型等。colClasses 是一个向量,虽然不是必需的,但它可以告诉 read.table 每列数据的类别。
  • nrows:数据中的行数。此参数不是必需的,但可以使用。
  • comment.char:这是一个字符串,指示注释字符是什么。默认值是井号 #,该符号右侧的所有内容都会被忽略。你可以指定其他字符作为注释字符,以该注释字符开头的文件行将被忽略。
  • skip:从开头跳过的行数。有时文件开头可能有一些标题信息或非数据区域,你想直接跳过这些部分。你可以告诉 read.table 函数跳过前 10 行或前 100 行,然后从那里开始读取数据。
  • stringsAsFactors:此参数默认为 TRUE。其核心问题是:你是否希望将字符变量编码为因子?默认情况下,每当 read.table 遇到看起来是字符变量的数据列时,它会假定你希望将其作为因子变量读入。如果你不希望将其作为因子变量读入,可以将 stringsAsFactors 设置为 FALSE

对于小型和中等规模的数据集(随着计算机性能日益提升,“小型”和“中等”的定义也在不断扩大),你通常可以只使用文件名而不指定其他任何参数来调用 read.table。例如,你可以写 read.table("food.txt"),它会自动处理不同列的数据类型、行数等问题。这将返回一个名为 data 的对象,它是一个数据框。它会自动跳过任何以注释符号开头的行,并确定行数和每列变量的类型。当然,你也可以指定所有这些参数,这样做的好处是使程序运行得更快、更高效。但对于中小型数据集来说,这样做优势不大,因为默认方式已经相当快速高效了。

read.csv 函数

read.csv 函数与 read.table 完全相同,关键区别在于:read.csv 的默认分隔符是逗号 ,,而 read.table 的默认分隔符是空格。read.csv 对于读取 CSV 文件非常有用。CSV 通常代表“逗号分隔值”,通常是从 Microsoft Excel 等电子表格程序中获取的文件格式。CSV 是一种非常常见的格式,大多数电子表格类程序都能理解。read.csv 指定的另一件事是,它总是将 header 参数设置为 TRUE


本节课中我们一起学习了在 R 中读取和写入数据的基础知识。我们重点介绍了 read.tableread.csv 这两个最常用的表格数据读取函数,详细解释了 read.table 的关键参数及其作用,并了解了 read.csv 作为其特化版本的便利之处。掌握这些函数是进行数据科学分析的第一步。

037:读取大型数据表 📊

在本节课中,我们将学习如何高效地将大型数据集读取到 R 语言中。我们将探讨一些关键策略,以优化读取过程、预估内存需求,并避免程序因内存不足而崩溃。

上一节我们介绍了处理中小型数据集的基本方法,本节中我们来看看当面对大型数据集时,有哪些技巧可以提升读取效率。

优化读取策略

面对超出中小型规模的大型数据集,在读取表格数据时,你可以采取一些措施,这会让你的工作轻松很多,更重要的是,能防止 R 语言完全卡死。

首先,你应该阅读 read.table 函数的帮助页面。事实上,你或许应该把它背下来。这个帮助页面包含了许多关键提示和有用的信息。在我看来,没有足够的人仔细阅读这个帮助页面,以至于无法在睡梦中复述它。一旦你阅读了它,你就会发现其中有很多关于如何针对大型数据集优化 read.table 的重要信息。

以下是几个关键的优化步骤:

  • 预估内存需求:你需要对存储即将读取的数据所需的内存进行一个粗略的计算。这样你就能大致判断你的计算机是否有足够的内存来存储这些数据。因为请记住,除非你采取其他措施,否则 R 语言会将你的整个数据集存储在内存中。当你调用 read.tableread.csv 时,它会把整个数据集读入计算机的 RAM 中。因此,你需要大致了解这个数据集将需要多少 RAM,我们稍后会讨论如何计算。

  • 设置注释字符:另一个简单的优化是,如果你的文件中没有注释行,只需将 comment.char 参数设置为空,即一对空引号 ""

  • 指定列数据类型colClasses 参数实际上非常重要。因为如果你不指定它,R 默认会遍历数据的每一列,并尝试推断其数据类型。对于中小型数据来说,这没问题。但读取每一列并尝试推断数据类型需要时间、占用内存,并且通常会减慢速度。如果你能告诉 R 每列数据的类型,那么 R 就不必花时间去自行推断,这通常会使 read.table 运行得更快。

确定列数据类型的方法

你可以通过以下方法确定并指定 colClasses,从而节省大量时间:

  • 手动指定:如果你的数据只有几列,通常可以直接说明它们的类型。
  • 统一类型:例如,如果所有列都是数值型,你可以直接将 colClasses 设置为 "numeric"。如果你只提供一个值,R 会假定每一列都具有相同的类型。
  • 采样推断:如果你有一个巨大的数据集,你可以通过指定 nrows 参数,先读取前一百行或一千行。然后,使用 sapply 函数遍历每一列并调用 class 函数。class 函数会告诉你每列数据的类型。你可以存储这些信息,然后通过指定 colClasses 参数来读取整个数据集。

其他有用参数

nrows 参数实际上也非常有用。它不一定能让 R 运行得更快,但确实有助于内存管理。如果你能告诉 R 将要读取多少行数据,它就可以计算出所需的内存,而不必在读取过程中动态计算。即使你稍微高估了数据中的行数也没关系,因为它仍然会读取正确的行数。

系统环境考量

通常,当你使用 R 处理大型数据集时(如今有很多大型数据集),手头掌握一些信息是很有用的。以下是需要考虑的几个方面:

  • 计算机内存:你的计算机有多少内存?如今大多数计算机的物理 RAM 从几 GB 到许多 GB 不等。
  • 其他应用程序:你的计算机上是否运行着其他可能占用处理器时间或内存的应用程序?
  • 多用户系统:如果你在多用户系统上,是否有其他用户登录到系统?他们是否在使用计算机的一些资源?
  • 操作系统:你的计算机是什么操作系统?是 Mac、Windows、Unix、Linux 还是其他?
  • 系统位数:了解你运行的操作系统是 32 位还是 64 位也很有用。在 64 位系统上,如果计算机有更多内存,你通常能够访问更多内存。

内存需求估算

在使用 read.tableread.csv 函数将表格读入 R 之前,你可以做一个快速的计算来估算内存需求。

假设我有一个数据框,有 150 万行和 120 列。这个数据集不算特别大,但规模可观。假设所有列都是数值型,因此我不必担心不同的数据类型。

问题是:在内存中存储这个数据框需要多少内存?我可以做一个简单的计算。

  • 数据框中的元素数量是 150 万 × 120,因为它是一个矩形对象。
  • 如果所有数据都是数值型,那么每个数字需要 8 字节 的内存来存储,因为数字是以 64 位(8 字节)存储的。
  • 因此,总字节数 = 元素数量 × 8 字节。
  • 然后,我们可以将总字节数除以 2^20(即 1,048,576)得到兆字节数。
  • 接着,再除以 2^10(即 1024)得到千兆字节数。

公式
所需内存 (GB) ≈ (行数 × 列数 × 8) / (2^30)

对于我们的例子:
(1,500,000 × 120 × 8) / (1,073,741,824) ≈ 1.34 GB

这个数据框的原始存储空间大约需要 1.34 GB

实际上,读取数据时你需要比这稍多一点的内存,因为读取数据本身需要一些开销。经验法则是:使用 read.table 读取这个数据集所需的内存,大约是对象本身所需内存的 两倍

因此,如果你的计算机只有 2 GB 的 RAM,而你试图读取这个 1.34 GB 的表格,你可能需要三思,因为你将接近读取数据所需内存的边界。当然,如果你的计算机有 4、8 或 16 GB 的 RAM,那么在内存需求方面应该没有问题。读取它仍然需要一些时间,但这只是因为读取所有数据需要时间,而你不会耗尽内存。

在进行这种计算时,使用 R 代码辅助非常方便:

# 估算内存需求的示例代码
rows <- 1500000
cols <- 120
bytes_per_numeric <- 8

total_bytes <- rows * cols * bytes_per_numeric
total_mb <- total_bytes / (1024^2)
total_gb <- total_mb / 1024

cat("预计需要内存:", round(total_gb, 2), "GB\n")
cat("建议可用内存至少为:", round(total_gb * 2, 2), "GB\n")

当你读取大型数据集时,进行这种计算非常有用,它能让你心里有数:我是否有足够的内存?如果你遇到任何错误,你会知道错误是否是由于内存耗尽引起的。因此,我鼓励你在读取大型数据集并且事先知道它大概有多大时,进行这种计算。


本节课中我们一起学习了高效读取大型数据表到 R 语言中的关键策略。我们重点探讨了通过预估内存需求、指定列数据类型、利用 nrows 参数以及考量系统环境来优化读取过程。掌握这些方法,尤其是进行简单的内存计算,能帮助你在处理大规模数据时更加得心应手,避免因内存不足而导致程序崩溃。

038:R语言中的文本数据格式 📄

在本节课中,我们将要学习R语言中除CSV和普通文本文件之外的两种文本数据格式。这些格式同样以文本形式存储数据,但包含了更多元数据信息,有助于更完整地保存和重建R对象。

上一节我们介绍了常见的表格数据格式,本节中我们来看看两种特殊的文本格式:dput/dgetdump/source

文本格式的优势与劣势

这些格式的核心思想是:它们虽然是文本格式,但不像表格那样规整,因为它们包含了更多元数据。例如,如果你用 dputdump 输出一个数据框,输出结果会包含数据框每一列的类别信息。这样,当你再次读取数据时,就无需重新指定这些信息。

使用这类机制存储或读取数据的主要优势在于:

  • 它仍然是文本格式,便于查看和编辑。
  • 它包含了元数据,避免了数据在传输或长期保存过程中元信息丢失的风险。

以下是文本格式的普遍优点:

  • 可编辑性:虽然为了可重复性,通常不建议手动编辑,但在数据损坏时,你可以查看文件并尝试修复。
  • 长期保存:文本格式的生命周期可能更长,有助于避免潜在的损坏问题。
  • 版本控制友好:如果你使用像Subversion或Git这样的版本控制程序来跟踪文档变更,这些程序对文本数据的处理远比二进制数据更有用,能更有意义地追踪变化。

文本格式遵循了Unix哲学的一般原则,即倾向于将所有类型的数据存储为文本。然而,文本格式的一个缺点是空间效率不高,它们往往占用较多空间,因此通常需要进行压缩。

使用 dputdget 处理单个对象

dput 函数可以接受任意R对象(除了一些特殊的对象类型),并生成一段R代码,这段代码本质上可以在R中重建该对象。

例如,我们创建一个简单的数据框:

y <- data.frame(a = 1, b = "a")

然后,我们使用 dput 输出这个数据框。如果直接在控制台调用 dput,它会将结果显示在控制台:

dput(y)

输出结果类似于一段R代码,它创建了一个包含两个元素的列表,每个元素都包含了数据、列名、行名以及对象的类别(这里是“data.frame”)。所有的元数据(如行名、列名和类别)都包含在输出中。

当然,通常你不会希望只打印到控制台,而是希望保存到文件中:

dput(y, file = "y.R")

之后,你可以使用 dget 函数将这个文件读回R:

new.y <- dget("y.R")

这样,你就重建了之前的数据框对象。dput 函数本质上就是编写了用于重建R对象的R代码。

使用 dumpsource 处理多个对象

dump 函数与 dget 很相似,但关键区别在于:dget 只能用于单个R对象,而 dump 可以用于多个R对象。

使用 dump 时,你需要传递一个包含对象名称的字符向量。例如,我们创建两个对象:

x <- "foo"
y <- data.frame(a = 1, b = "a")

然后,我们使用 dump 将这两个对象保存到文件中。参数 c("x", "y") 就是这两个对象的名称:

dump(c("x", "y"), file = "data.R")

之后,我们可以选择移除这些对象。当需要将它们读回R时,我们可以对那个文件调用 source 函数:

source("data.R")

执行后,你会发现 xy 对象都已被重建。

总结

本节课中我们一起学习了R语言中两种用于保存和读取数据的文本格式。我们了解了 dput/dget 适用于处理单个R对象,它能生成重建该对象的R代码;而 dump/source 则适用于同时处理多个R对象。这两种格式的优势在于它们以文本形式保留了数据的结构元信息,便于长期保存、版本控制和在必要时进行人工检查,尽管它们可能不如二进制格式节省空间。掌握这些工具能让你更灵活地管理R工作环境中的数据。

039:R语言与外部世界的连接接口 🖇️

在本节课中,我们将要学习R语言如何与外部世界进行数据交互,特别是通过“连接接口”来读取和写入文件、网页等外部资源。我们将了解不同类型的连接,并学习如何使用它们来灵活地处理数据。

概述

R语言提供了多种方式与外部世界进行接口交互。通常,有一些函数用于打开所谓的“连接”,以访问R外部的对象。最常见的连接类型是文件连接。

文件连接

上一节我们介绍了连接接口的基本概念,本节中我们来看看最常用的文件连接。file 函数用于打开一个到标准未压缩文件的连接。这对于读取文本文件非常有用。

file 函数有几个参数:

  • description 参数是文件的名称。
  • open 参数是一个标志,你需要知道其代码的含义:
    • "r" 用于读取。
    • "w" 用于写入。
    • "a" 用于追加。
    • "rb""wb""ab" 用于二进制文件的读、写、追加。

压缩文件连接

除了标准文件,R也能处理压缩文件。gzfilebzfile 函数用于打开到压缩数据文件的连接。

  • gzfile 用于打开使用Gzip算法压缩的文件(通常扩展名为 .gz)。
  • bzfile 用于打开使用Bzip2算法压缩的文件(通常扩展名为 .bz2)。

连接接口的用途

连接功能非常强大,它允许你以比一次性读取整个文件更复杂的方式来导航文件和其他外部对象。

在多数情况下,你无需直接处理连接接口。例如,当你调用 read.table 并传入文件名时,它在后台会自动为你打开一个到该文件的连接,然后从该连接读取数据。

然而,有时直接使用连接会很有用,例如当你只想读取文件的一部分时。

以下是使用连接读取文件部分内容的示例:

# 打开一个到压缩字典文件的连接
con <- gzfile("words.gz")
# 读取该连接的前10行
readLines(con, 10)

类似地,writeLines 函数可用于将文本行写入文件。你传入一个字符向量,该向量的每个元素将成为文本文件中的一行。

网页连接

连接不仅可以用于文件,也可以用于其他对象,例如网页。url 函数可用于创建到网页的连接。

以下是使用连接从网页读取数据的示例:

# 创建一个到网站的连接
con <- url("http://www.jhsph.edu", "r")
# 从该连接读取文本行
x <- readLines(con)
# 查看前几行内容
head(x)

url 函数对于创建到非文件对象的连接很有用,然后使用 readLines 从该连接读取文本。这是除了使用 read.tableread.csv 等函数之外,另一种读取数据的方式。

总结

本节课中我们一起学习了R语言中连接接口的使用。我们了解到,连接抽象了与R外部各种对象(无论是文件、网页还是其他资源)的交互机制。我们学习了如何创建到普通文件、压缩文件以及网页的连接,并掌握了如何使用 readLineswriteLines 等函数通过连接进行数据的读取和写入。掌握连接接口能让你更灵活、更高效地处理外部数据。

040:R语言子集操作基础 🧩

在本节课中,我们将学习R语言中一个非常核心的操作:如何从向量、列表等对象中提取或选择特定的元素。这个过程被称为“子集化”。掌握子集操作是高效处理和分析数据的基础。

上一节我们介绍了R语言的基本数据类型,本节中我们来看看如何从这些数据对象中提取我们需要的部分。


子集操作符简介

R语言提供了三种主要的操作符来提取对象的子集:

  • 单方括号 [ ]
  • 双方括号 [[ ]]
  • 美元符号 $

理解它们之间的区别至关重要。

单方括号 [ ]

单方括号操作符的核心原则是:它总是返回一个与原始对象类别相同的对象

  • 如果你对一个向量使用 [ ],返回的还是一个向量。
  • 如果你对一个列表使用 [ ],返回的还是一个列表。
  • 此外,单方括号操作符可以用于选择多个元素

双方括号 [[ ]] 与美元符号 $

双方括号操作符主要用于提取列表或数据框中的单个元素

  • 由于列表中的元素可以是不同类别(例如,第一个是数值向量,第二个是数据框),因此使用 [[ ]] 提取出的对象的类别不一定是列表,它可能是任何其他类别。
  • 美元符号 $ 用于按名称提取列表或数据框中的元素。它的语义与双方括号类似,提取出的元素类别也可能与原始对象不同。

向量的子集化

让我们通过具体的例子来理解如何对向量进行子集操作。我们将使用两种主要的索引方式:数值索引和逻辑索引。

使用数值索引

数值索引是指通过元素在向量中的位置(1, 2, 3...)来提取它们。

以下是使用数值索引的例子:

  • x[1] 提取第一个元素。
  • x[2] 提取第二个元素。
  • x[1:4] 提取第1到第4个元素。
x <- c("a", "b", "c", "c", "d", "a")  # 创建一个字符向量
x[1]    # 返回 "a"
x[2]    # 返回 "b"
x[1:4]  # 返回 c("a", "b", "c", "c")

使用逻辑索引

逻辑索引是指通过一个与原始向量等长的逻辑值(TRUE/FALSE)向量来提取元素。只有对应位置为 TRUE 的元素会被选中。

以下是使用逻辑索引的例子。我们想提取所有大于字母“a”的元素。字母在R中有字典顺序。

x <- c("a", "b", "c", "c", "d", "a")
x > "a"  # 返回逻辑向量:FALSE, TRUE, TRUE, TRUE, TRUE, FALSE

# 直接使用逻辑比较进行子集化
x[x > "a"]  # 返回 c("b", "c", "c", "d")

# 或者,先创建逻辑向量,再用它来子集化
u <- x > "a"
x[u]        # 同样返回 c("b", "c", "c", "d")

总结

本节课中我们一起学习了R语言子集操作的基础知识。

我们介绍了三种子集操作符:[ ][[ ]]$,并重点讲解了它们的不同用途。我们通过大量示例练习了如何对向量使用数值索引逻辑索引来提取需要的元素。理解并熟练运用这些子集化技巧,是后续进行数据筛选、清洗和分析的关键第一步。

在接下来的课程中,我们将把这些概念应用到更复杂的结构,如列表和数据框上。

041:列表子集操作 🧩

概述

在本节课中,我们将要学习如何在R语言中对列表进行子集操作。列表是一种可以包含多种数据类型元素的复杂数据结构,因此其子集操作方式与向量或矩阵有所不同。我们将详细介绍单括号、双括号和美元符号这三种操作符的使用方法、区别以及适用场景。


列表子集操作的基本方法

列表的子集操作有些不同,因为你可以使用双括号或美元符号操作符,也可以使用单括号操作符。

这里我有一个列表。第一个元素是一个名为foo的命名元素,它是一个从1到4的序列。第二个元素名为bar,它是一个数值0.6。这是一个包含两个元素的列表。

我可以使用单方括号提取第一个元素。请记住,单方括号总是返回一个与原始对象类别相同的元素。因此,如果x是一个列表,那么x[1]也将是一个列表。

所以我得到的是一个包含名为foo的元素的列表,该元素是一个从1到4的序列。

现在,如果我使用双括号,即输入x[[1]],那么我得到的就仅仅是一个从1到4的序列。这里的区别在于,在第一个例子中,我得到了一个包含序列的列表;而在第二个例子中,我直接得到了序列本身。这就是单括号和双括号操作符之间的区别。


使用名称提取元素

在下一个例子中,我使用了美元符号。我输入x$bar,这意味着它返回与名称bar相关联的元素。在这个例子中,它是一个单独的数字0.6。

我也可以使用双括号操作符并传递一个字符串给它。x[["bar"]]与执行x$bar效果相同,都返回0.6。

如果我使用带有名称的单括号,例如x["bar"],那么我会得到一个包含bar元素的列表。请记住,因为在对列表进行子集操作时,单括号总是返回一个列表。

能够使用名称对元素进行子集操作的好处是,你不需要记住它在列表中的位置。例如,如果我不记得bar是第一个元素还是第二个元素,我不需要为了使用数字索引而去记住它的位置。我只需使用它的名称,它就会自动从列表中提取出该元素。


提取列表的多个元素

如果你想提取列表的多个元素,那么你需要使用单括号操作符。

例如,如果我想提取这里的第一个和第三个元素(即foobaz元素),我可以将一个数值向量c(1, 3)通过单括号操作符传递给x。这将返回一个包含foobaz元素的列表。

这就是我提取列表多个元素的方法。当你想要提取列表的多个元素时,不能使用双括号或美元符号操作符。


双括号操作符与计算索引

双括号操作符与美元符号的一个不同之处在于,你可以使用双括号操作符来索引一个列表,而索引本身可以是计算得出的。

请注意,之前我使用美元符号时,我实际上键入了单词bar。我必须键入对象的名称(或者更准确地说,是元素的名称)。但有时,元素的名称实际上是某些计算的结果。

例如,这里我有一个包含三个元素foobarbaz的列表。然后我创建了一个名为name的变量,它的值实际上是字符串"foo"

如果我对这个变量使用双括号操作符,请注意列表中并没有名为name的元素,但列表中有一个名为foo的元素。

现在,当我将这个名为name的变量传递给双括号操作符时,它返回与foo相关联的元素,因为这就是name变量的值。

因此,如果我想要使用计算得出的索引,那么我必须使用双括号操作符。如果我使用美元符号,它将按字面意思查找列表中与单词name相关联的元素,而这个元素当然不存在于这个列表中。所以,要使用美元符号,我需要使用一个字面符号。


双括号操作符与整数序列

双括号操作符可以接受一个整数序列,而不仅仅是单个数字。你可以这样理解:它有点像是递归地进入列表。

看看我这里的当前列表。第一个元素a是另一个列表,它包含元素10、12和14。假设我想提取数字14,它实际上是第一个元素的第三个元素,对吧?也就是说,它是列表的第三个元素,而这个列表恰好是另一个列表的第一个元素。

因此,我可以通过将向量c(1, 3)传递给列表x,使用双括号操作符来提取它。这相当于执行x[[1]][[3]]这样的双重子集操作。

我也可以通过传递整数向量c(2, 1)来提取第二个元素的第一个元素,从而得到3.14。


总结

本节课中我们一起学习了R语言中列表子集操作的三种主要方法:单括号[]、双括号[[]]和美元符号$

  • 单括号 []:总是返回一个列表,可用于提取单个或多个元素。
  • 双括号 [[]]:提取列表中的单个元素,返回该元素本身(可能是向量、列表或其他类型)。它支持使用计算得出的索引或名称,并且可以传递整数序列进行递归提取。
  • 美元符号 $:通过元素的字面名称提取单个元素,语法简洁,但不能用于计算索引或提取多个元素。

理解这些操作符之间的区别对于有效地处理和操作R中的列表数据结构至关重要。

042:矩阵子集操作 🧮

概述

在本节课中,我们将学习如何在R语言中对矩阵进行子集操作。我们将了解如何通过行索引和列索引提取矩阵中的特定元素、行或列,并讨论默认行为以及如何控制输出结果的维度。


矩阵子集的基本语法

矩阵的子集操作遵循直观的规则。第一个索引代表行,第二个索引代表列。我们可以使用数字来指定这些索引。

例如,假设我们有一个矩阵 x,它是一个2行3列的矩阵,包含数字1到6。

x <- matrix(1:6, nrow=2, ncol=3)

如果我们使用 x[1, 2],这将提取第一行第二列的元素,结果是数字3。

x[1, 2] # 输出: 3

类似地,x[2, 1] 将提取第二行第一列的元素,结果是数字2。

x[2, 1] # 输出: 2

提取整行或整列

在子集操作中,不一定总要同时指定两个索引。

例如,如果我们想提取矩阵的第一行,可以使用 x[1, ] 的语法。逗号后的空白表示我们想要所有列。

x[1, ] # 输出: 1 3 5

同样,如果我们想提取矩阵的第二列,可以使用 x[, 2] 的语法。逗号前的空白表示我们想要所有行。

x[, 2] # 输出: 3 4

默认行为:维度降低

默认情况下,当从矩阵中提取单个元素时,R会返回一个长度为1的向量,而不是一个1x1的矩阵。

之前我们提到,单方括号 [ ] 操作符通常返回与原始对象相同类的对象。但这里有一个有时会出乎意料的情况:提取矩阵的单个元素时,返回的不是矩阵,而是一个包含该数字的向量。

例如,x[1, 2] 返回的是数字3,而不是一个包含3的1x1矩阵。这通常是期望的行为,但有时也可能导致问题。


控制维度:drop 参数

你可以通过为子集操作添加一个名为 drop 的额外参数来关闭上述默认行为。默认情况下,drop = TRUE,它会“丢弃”维度,因此返回的通常是一维对象,而不是二维对象。

然而,如果你想保留对象的维度,可以设置 drop = FALSE

x[1, 2, drop = FALSE]
# 输出:
#      [,1]
# [1,]    3
# 这是一个1x1的矩阵

现在,当你提取单列或单行时,默认也不会返回矩阵。

例如,提取第一行 x[1, ],你可能会认为应该返回一个1行3列的矩阵,元素是1, 3, 5。但实际上,你得到的是一个包含元素1, 3, 5的向量。

这通常是可以接受的。但如果你不希望这样,可以在子集矩阵时始终设置 drop = FALSE 参数。

x[1, , drop = FALSE]
# 输出:
#      [,1] [,2] [,3]
# [1,]    1    3    5
# 这是一个1x3的矩阵

总结

本节课我们一起学习了R语言中矩阵的子集操作。我们掌握了使用行索引和列索引提取特定元素、行或列的基本方法。我们了解了默认情况下R会降低输出结果的维度(返回向量),并学习了如何使用 drop = FALSE 参数来保留原始矩阵的维度结构。理解这些概念对于有效地处理和操作R中的矩阵数据至关重要。


043: Foundations using R

课程编号:P43 - 24_子集部分匹配 🎯

在本节课中,我们将要学习R语言中一个名为“部分匹配”的便捷功能。这个功能可以帮助你在命令行操作时节省大量输入时间。

部分匹配是一个方便的工具,它通常可以节省你在命令行的大量输入。当你编写程序和函数时,它并不是特别有用。但当你需要在命令行快速输入内容时,它非常有用。

上一节我们介绍了子集提取的基本概念,本节中我们来看看部分匹配的具体工作机制。

部分匹配的理念是,它适用于双括号操作符和美元符号操作符。假设我有一个列表x,其中包含一个名为abcdef的元素,它是一个从1到5的序列。假设每次都要完整输入abcdef有点麻烦。

美元符号操作符的部分匹配

以下是美元符号操作符的默认工作方式:

  • 它会在列表中查找名称与所提供字符串匹配的元素。
  • 在本例中,我只输入字母a
  • 列表中只有一个元素的名称以a开头。
  • 因此,它返回了名称abcdef,并给出了与之关联的对象,即序列1到5。
x <- list(abcdef = 1:5)
x$a

双括号操作符的部分匹配

接下来,我们看看双括号操作符的情况,它有些不同。

双括号操作符期望你传递给它的名称与列表中的某个名称完全匹配。默认情况下,双括号操作符不会像美元符号那样进行部分匹配。

所以,当我输入x[["a"]]时,返回的是NULL,因为列表中没有名为"a"的元素。

但是,你可以向双括号操作符传递第二个参数,即exact参数。如果你指定exact = FALSE,那么当我再次输入x[["a"]]时,它就会返回序列1到5,因为这是与字母"a"最匹配的元素。

x[["a"]] # 返回 NULL
x[["a", exact = FALSE]] # 返回 1:5

本节课中我们一起学习了R语言中的部分匹配功能。我们了解到,美元符号操作符默认支持部分匹配,可以快速提取列表元素。而双括号操作符默认要求精确匹配,但可以通过设置exact = FALSE参数来启用部分匹配。这个技巧在命令行交互式操作时能有效提升效率。

044:R语言中如何移除缺失值 🧹

在本节课中,我们将学习如何在R语言中识别并移除向量、矩阵和数据框中的缺失值(NA)。这是数据分析中一项非常常见的操作,因为现实数据通常包含大量缺失值。

识别缺失值

上一节我们介绍了子集选取的基本概念,本节中我们来看看如何专门处理缺失值。首先,我们需要识别数据中的缺失值。

我们可以使用 is.na() 函数来创建一个逻辑向量,该向量会指出原始向量中哪些元素是缺失值。

# 创建一个包含缺失值的向量
x <- c(1, 2, NA, 4, NA, 5)

# 使用 is.na() 识别缺失值
bad <- is.na(x)

执行上述代码后,bad 将是一个逻辑向量,其值为 FALSE, FALSE, TRUE, FALSE, TRUE, FALSE,对应 x 中每个元素是否为 NA

移除缺失值

识别出缺失值的位置后,我们通常希望获取所有非缺失值。这可以通过对逻辑向量取反,然后进行子集选取来实现。

# 获取所有非缺失值
good_elements <- x[!bad]

这里,! 是逻辑“非”运算符。!bad 会得到一个与 bad 相反的逻辑向量(TRUE 变为 FALSEFALSE 变为 TRUE)。然后,x[!bad] 会选取 x 中所有对应位置为 TRUE 的元素,即所有非缺失值,结果为 1, 2, 4, 5

处理多个向量的情况

在实际分析中,我们经常需要同时处理多个变量。如果每个变量的缺失值出现在不同位置,我们需要找到所有变量都非缺失的观测。

假设我们有两个向量 xy

x <- c(1, 2, NA, 4, NA, 5)
y <- c(“A”, “B”, NA, “D”, NA, “F”)

我们希望得到一个子集,其中只包含 xy 在相同位置上都非缺失的观测。这时可以使用 complete.cases() 函数。

以下是 complete.cases() 函数的使用步骤:

  1. complete.cases() 函数接受一个或多个向量(或数据框)作为输入。
  2. 它返回一个逻辑向量,指明每个位置(或每行)在所有输入中是否都是完整的(即没有缺失值)。
  3. 对于上面的 xycomplete.cases(x, y) 将返回 TRUE, TRUE, FALSE, TRUE, FALSE, TRUE
  4. 然后我们可以用这个结果来同时子集选取 xy
# 找出两个向量都完整的观测
good_positions <- complete.cases(x, y)

# 子集选取
x[good_positions] # 结果为 1, 2, 4, 5
y[good_positions] # 结果为 “A”, “B”, “D”, “F”

从数据框中移除缺失值

complete.cases() 函数在处理数据框时尤其有用。数据框的每一行可能在不同列中存在缺失值,我们常常需要找出所有列都完整的行。

以R内置的 airquality 数据集为例,它包含臭氧、太阳辐射等变量,其中有些行存在缺失值。

# 查看数据框的前几行,其中包含NA
head(airquality)

# 使用 complete.cases 找出所有列都完整的行
good_rows <- complete.cases(airquality)

# 子集选取,创建一个没有缺失值的新数据框
airquality_complete <- airquality[good_rows, ]
head(airquality_complete)

执行上述代码后,airquality_complete 数据框将只包含原始数据中所有变量都没有缺失值的行。

总结

本节课中我们一起学习了在R语言中处理缺失值的关键技巧。我们首先使用 is.na() 函数来识别缺失值,然后通过逻辑取反和子集选取来移除它们。接着,我们探讨了如何使用 complete.cases() 函数高效地处理多个向量或整个数据框,确保只保留所有变量都完整的观测。complete.cases() 是一个非常有用的函数,特别适用于需要在大型数据集中清理缺失值的情况。

045:向量化操作 🧮

在本节课中,我们将要学习 R 语言中一个非常强大且方便的特性:向量化操作。理解这个概念能帮助你编写更简洁、更高效的代码,而无需依赖复杂的循环结构。

概述

向量化操作是 R 语言的核心特性之一。它允许我们对整个向量或矩阵执行运算,而无需显式地编写循环。这使得代码更易读、更易写,并且在处理数据时非常高效。许多其他计算语言,如 MATLAB,也具备类似的功能。

什么是向量化操作?

上一节我们介绍了向量化操作的基本概念。本节中,我们来看看它的具体含义。

向量化操作的核心思想是并行计算。例如,当你想要对两个向量进行加法运算时,R 会自动将第一个向量的每个元素与第二个向量的对应元素相加,而不是要求你手动遍历每个元素。

假设我们有两个向量 xy

x <- 1:4  # 序列 1 到 4
y <- 6:9  # 序列 6 到 9

如果我们想将这两个向量相加,我们的本意是将 x 的第一个元素与 y 的第一个元素相加,第二个元素与第二个元素相加,依此类推。

在其他语言中,你可能需要写一个循环来完成这个任务。但在 R 中,你可以直接使用加号运算符:

x + y

这个操作会返回一个新的向量 c(7, 9, 11, 13),它对应着 1+6, 2+7, 3+8, 4+9 的结果。

逻辑比较与算术运算

理解了基本的向量加法后,我们来看看其他类型的向量化操作。

同样地,你可以使用大于或小于等符号来得到逻辑向量。例如:

x > 2

由于 x 是一个包含四个数字的向量,这个向量化操作会将所有数字与 2 进行比较,并返回一个由 TRUEFALSE 组成的向量,指示哪些数字大于 2。

以下是其他常见的向量化逻辑与算术运算示例:

  • x >= 2:判断哪些数字大于或等于 2。
  • y == 8:使用双等号测试相等性,判断 y 的每个元素是否等于 8。
  • x * y:向量元素对应相乘。
  • x / y:向量元素对应相除。

当你想要对向量进行乘、除、加、减运算时,可以直接使用相应的运算符,操作会自动并行执行。

矩阵的向量化操作

向量化操作不仅限于向量,也适用于矩阵。了解这一点很重要,因为矩阵乘法有不同的类型。

假设我们创建两个矩阵 xy

x <- matrix(1:4, nrow=2, ncol=2) # 一个 2x2 矩阵,元素为 1 到 4
y <- matrix(1, nrow=2, ncol=2)   # 一个 2x2 矩阵,所有元素都是 1

如果你直接使用 x * y,这不是矩阵乘法,而是逐元素乘法。第一个矩阵的 (1,1) 元素与第二个矩阵的 (1,1) 元素相乘,以此类推。除法操作 x / y 同理,它只是将一个矩阵的每个元素除以另一个矩阵的对应元素,而不是求矩阵的逆。

如果你想进行真正的矩阵乘法,必须使用特定的运算符 %*%

x %*% y # 真正的矩阵乘法

总结

本节课中,我们一起学习了 R 语言的向量化操作。我们了解到,向量化操作允许我们直接对整个数据结构(向量或矩阵)应用算术和逻辑运算,从而实现隐式的并行计算,而无需编写循环。这极大地简化了代码,使其更清晰、更高效。对于习惯其他编程语言、可能会下意识使用循环结构的同学来说,掌握向量化操作是编写地道 R 代码的关键一步。在后续课程中,你会更频繁地见到并运用这个强大的特性。

046:Swirl入门指南 🚀

在本节课中,我们将学习一个名为Swirl的交互式R语言学习工具。Swirl由约翰霍普金斯大学生物统计学系的Nick Carchedi开发,旨在帮助用户以自主节奏、互动方式掌握R语言。我们将了解它的基本功能、使用方法以及在本课程中的定位。


概述 📋

Swirl全称为“Statistics with Interactive R Learning”,是一个在R控制台内运行的交互式学习系统。它通过一系列循序渐进的课程,引导用户实践R语言的各个方面。与传统的“观看讲座后完成作业”模式不同,Swirl允许你在指导下一步步操作,从而更直观地学习和练习。


Swirl的核心特点 ✨

上一节我们介绍了Swirl的基本概念,本节中我们来看看它的核心特点。

Swirl的设计目标是让R语言学习变得更加互动和高效。以下是它的几个关键特性:

  • 交互式学习:在R控制台内直接输入指令并得到即时反馈。
  • 自主节奏:你可以根据自己的理解和时间安排学习进度。
  • 引导式实践:系统提供明确的步骤指导,无需完全自行摸索。
  • 课程模块化:包含多个针对R语言不同方面的独立课程。

如何使用Swirl 🛠️

了解了Swirl的特点后,接下来我们看看如何开始使用它。使用Swirl主要涉及安装、加载课程和开始学习几个步骤。

以下是启动Swirl的基本流程:

  1. 安装Swirl包:在R控制台中运行 install.packages("swirl")
  2. 加载Swirl库:安装后,通过 library(swirl) 命令加载。
  3. 启动Swirl:输入 swirl() 并按回车,即可进入交互学习界面。
  4. 选择课程:根据提示,从课程列表中选择你想要学习的内容。
  5. 跟随指导学习:按照屏幕上的指令,在 > 提示符后输入你的答案或代码。

Swirl在本课程中的角色 🎓

我们已经知道了如何使用Swirl,现在来明确一下它在整个《数据科学R语言基础》课程中的定位。

Swirl模块是本课程提供的一个实验性补充功能。它完全可选,不作为强制要求。你可以选择不完成这些模块,依然能够顺利学习课程并取得好成绩。

然而,完成Swirl模块可以带来额外益处:

  • 获得额外学分:完成Swirl课程可在编程作业部分获得少量额外加分。
  • 强化学习效果:通过互动练习加深对R语言概念的理解。
  • 增加学习乐趣:以一种有趣、动手操作的方式巩固知识。

总结与鼓励 🌟

本节课中,我们一起学习了Swirl这个强大的交互式R语言学习工具。我们了解了它的起源、核心互动学习方式、基本使用方法以及它在本课程中作为可选补充资源的定位。

尽管Swirl模块不是必修内容,但我强烈鼓励你尝试使用它。我相信这个过程会充满乐趣,并能有效提升你的R语言实践能力。现在,你可以打开R控制台,输入 install.packages("swirl"),开始你的互动学习之旅了。

047:R语言控制结构介绍 🧭

在本节课中,我们将学习R语言中的控制结构。控制结构允许你控制R程序的执行流程,其基本概念与其他编程语言中的控制结构非常相似。

概述

控制结构是编程中用于控制代码执行顺序和逻辑的核心工具。在R语言中,它们主要用于编写函数或程序,而非交互式会话。

基本控制结构

以下是R语言中几种基本的控制结构:

  • if-else:用于测试逻辑条件,并根据条件真假执行不同的代码块。
  • for:用于执行固定次数的循环。
  • while:只要给定条件为真,就重复执行一个循环。
  • repeat:用于创建一个无限循环,通常需要配合break语句来退出。
  • break:用于立即退出任何类型的循环(如forwhilerepeat)。
  • next:用于跳过当前循环的剩余部分,直接进入下一次迭代。
  • return:用于从函数中退出并返回一个值。

使用场景

上一节我们介绍了各种控制结构,本节中我们来看看它们的主要使用场景。这些结构通常不用于交互式会话(例如在R命令行中直接输入),而是更多地应用于以下情况:

  • 编写R脚本或程序。
  • 在自定义函数中实现复杂的逻辑。

总结

本节课中我们一起学习了R语言的控制结构。我们了解了if-elseforwhilerepeatbreaknextreturn等基本结构及其用途。掌握这些结构对于编写高效、逻辑清晰的R程序至关重要。

048:控制结构if-else 🧠

在本节课中,我们将要学习R语言中一个非常基础且重要的控制结构:if-else语句。它允许我们根据特定条件是否为真,来决定执行哪一段代码。这对于编写能够根据不同情况做出决策的程序至关重要。

什么是if-else结构? 🤔

上一节我们介绍了控制结构的基本概念,本节中我们来看看if-else的具体形式。

第一个结构是ififelse结合,允许你测试逻辑条件,并根据该条件是TRUE(真)还是FALSE(假),让R程序执行不同的操作。

所以,如果条件为真,你就执行一些操作;否则(else),你就执行另一些操作。这是一种典型的结构。

else部分是可选的。因此,你可以只有一个if语句,在条件为真时执行某些操作。但如果你希望有替代方案,也可以包含else部分。

如果你需要测试的条件不止一种,你可以使用ifelse if,最后是else。在类似的结构中,可以有任意数量的else if条件,而else必须放在最后。

在R中编写if-else的几种方式 ✍️

R语言中编写if-else结构有几种不同的方式,这与其他语言略有不同。

以下是第一种标准方式:

if (x > 3) {
  y <- 10
} else {
  y <- 0
}

如果符号x大于3,那么你将y设置为10。如果它不大于3,那么你将y设置为0。

在这个if-else结构内部,根据x的值,将y赋值为一个特定的值。

然而,在R中,你可以用另一种方式实现:

y <- if (x > 3) {
  10
} else {
  0
}

你可以说y等于整个if-else结构的结果。如果x大于3,y就是10,否则就是0。这也是一种有效的写法。

有时,这种写法很有用,因为它让你意识到整个if-else结构都是为了给y赋值。

关于else子句和多个条件 📝

正如前面所说,else子句并非绝对必要。

你可以只测试条件并执行某些操作,如果条件恰好为假,就什么也不做。如果你愿意,也可以连续测试多个条件。

以下是关键点总结:

  • else部分是可选的。
  • 你可以使用else if来测试一系列条件。
  • 你可以只有if而没有else

本节课中我们一起学习了R语言中的if-else控制结构。我们了解了它的基本语法、两种不同的赋值写法,以及else子句的可选性。掌握if-else是编写具有条件判断功能的R程序的基础。

049:控制结构 - for 循环 🔄

在本节课中,我们将要学习 R 语言中最常见的循环操作符——for 循环。我们将了解其基本语法、不同的使用方式以及如何嵌套循环。


概述

for 循环的核心思想是使用一个循环索引(通常称为 i),该索引会遍历一个序列(通常是整数序列)。在每次迭代中,循环体中的代码都会执行一次。

基本 for 循环

以下是一个基本的 for 循环示例,它遍历数字 1 到 10 并打印每个数字。

for(i in 1:10) {
  print(i)
}

在这个例子中,i 是循环索引,1:10 创建了一个从 1 到 10 的整数序列。循环体 { print(i) } 在每次迭代中执行,打印出当前的 i 值。

循环结束后,程序会继续执行后续的代码块。

for 循环的多种索引方式

R 语言在如何索引不同类型的对象方面非常灵活。以下三种 for 循环是等价的,它们都打印出向量 x 中的元素 "A", "B", "C", "D"。

首先,我们创建一个字符向量:

x <- c("A", "B", "C", "D")

方式一:使用整数序列索引

这是类似 C 语言等编程语言的常见做法。我们创建一个整数序列 1:4 作为索引。

for(i in 1:4) {
  print(x[i])
}

方式二:使用 seq_along 函数生成索引

seq_along() 函数接受一个向量作为输入,并生成一个等于该向量长度的整数序列。这使得循环的编写不依赖于硬编码的数字。

for(i in seq_along(x)) {
  print(x[i])
}

seq_along(x) 会生成序列 1:4,效果与方式一相同,但代码更具通用性。

方式三:直接遍历向量元素

循环索引变量不一定必须是整数,它可以直接取自向量本身的元素。

for(letter in x) {
  print(letter)
}

在这个循环中,索引变量 letter 在每次迭代中直接取值为向量 x 中的一个元素("A", "B", "C", "D"),并直接打印出来。

以上三种循环最终都打印出 "A", "B", "C", "D"。

单行循环体语法

如果 for 循环体只包含一个表达式,可以省略花括号 {},将所有内容写在一行。这可以使代码更紧凑,但并非所有人都喜欢这种风格。

for(i in 1:4) print(x[i])

嵌套 for 循环

for 循环可以嵌套,即一个循环 inside 另一个循环。这在处理二维数据结构(如矩阵)时很常见,例如需要遍历所有行和列。

假设我们有一个 2 行 3 列的矩阵:

x <- matrix(1:6, nrow=2, ncol=3)

以下嵌套循环会打印出矩阵中的所有元素:

for(i in seq_len(nrow(x))) {
  for(j in seq_len(ncol(x))) {
    print(x[i, j])
  }
}
  • 外层循环:索引 i 使用 seq_len(nrow(x)) 遍历行。seq_len(n) 函数生成一个从 1 到 n 的整数序列。这里 nrow(x) 为 2,所以生成序列 1:2
  • 内层循环:索引 j 使用 seq_len(ncol(x)) 遍历列。ncol(x) 为 3,生成序列 1:3

在每次外层和内层循环的组合中,代码 print(x[i, j]) 会打印出矩阵在 ij 列的元素。

关于嵌套循环的注意事项

虽然 R 语言理论上支持两层、三层甚至更多层的嵌套循环,但当嵌套超过两到三层时,代码会变得难以阅读和理解。有时嵌套循环是逻辑上必需的做法,但很多时候,可以通过使用函数(例如 apply 函数族)或向量化操作来避免深层嵌套,从而使代码更清晰、更高效。


总结

本节课中我们一起学习了 R 语言中的 for 循环。我们掌握了其基本语法,了解了使用整数序列、seq_along 函数以及直接遍历向量元素等多种索引方式。我们还学习了如何编写嵌套的 for 循环来遍历多维数据结构,并认识到应谨慎使用深层嵌套以保持代码的可读性。for 循环是进行重复性操作和控制程序流程的强大工具。

050:控制结构之while循环 🔄

在本节课中,我们将要学习R语言中另一种重要的循环结构——while循环。我们将了解它的基本语法、工作原理、使用时的注意事项,并通过示例来掌握如何在实际编程中应用它。


概述

while循环是R语言中除for循环外的另一个主要循环结构。它的核心思想是:只要给定的逻辑表达式为真(TRUE),就会重复执行循环体内的代码。这使得它在循环次数不预先确定,而是取决于某个动态条件时非常有用。


while循环的基本语法

while循环的基本语法结构如下:

while (逻辑表达式) {
  # 循环体:当逻辑表达式为TRUE时执行的代码
}

循环会先检查逻辑表达式的值。如果为TRUE,则执行一次循环体内的代码。执行完毕后,会再次检查逻辑表达式的值,如此反复,直到该表达式的值变为FALSE,循环才会停止,程序继续执行后续的代码。

上一节我们介绍了while循环的基本概念,本节中我们来看看一个具体的简单示例。


一个简单的while循环示例

以下是一个典型的while循环,它从0开始计数,直到数字达到10为止。

count <- 0
while (count < 10) {
  print(count)
  count <- count + 1
}

在这个例子中:

  1. 我们首先初始化一个变量count,并将其值设为0
  2. 循环条件是 count < 10。只要count的值小于10,循环就会继续。
  3. 在循环体内,我们打印当前的count值,然后将count的值增加1。
  4. count增加到10时,条件 count < 10 变为FALSE,循环停止。

while循环的一个优点是让代码意图更清晰。在这个例子中,我们可以很直观地理解“当计数小于10时持续执行”的逻辑。


使用while循环的注意事项

虽然while循环很有用,但使用时必须格外小心,以避免创建无限循环

无限循环是指循环停止的条件永远无法被满足,导致循环会永远执行下去,程序无法正常结束。在R中,这通常意味着你需要手动中断程序的运行。

以下是一个可能导致无限循环的错误示例:

x <- 5
while (x > 0) {
  print(x)
  # 忘记增加或减少x的值,x永远大于0,循环永不停止
}

因此,在编写while循环时,你必须确保:

  • 循环体内的操作最终能影响循环条件的判断。
  • 条件最终有机会变为FALSE

在更复杂的代码中,有时很难一眼判断while循环是否会正常终止。因此,在循环次数有明确上限的情况下,使用for循环往往是更安全的选择。但这并不意味着你应该完全避免使用while循环,只是在用它时需要更加谨慎。

了解了基本用法和风险后,我们来看看如何在while循环中设置更复杂的条件。


while循环中使用复合条件

if语句类似,你可以在while循环的条件中使用逻辑运算符(如&(与)、|(或))来组合多个逻辑表达式。

以下是使用复合条件的一个示例,它模拟了一个在特定范围内进行的“随机游走”:

z <- 5
while (z >= 3 & z <= 10) {
  print(z)
  coin <- rbinom(1, 1, 0.5) # 模拟抛一次公平硬币,结果为0或1
  if (coin == 1) {
    z <- z + 1  # 如果硬币是1,z加1
  } else {
    z <- z - 1  # 如果硬币是0,z减1
  }
}

在这个例子中:

  • 初始值 z 为5。
  • 循环条件是 z >= 3 & z <= 10,即z的值在3到10之间(包含3和10)时,循环继续。
  • 在循环体内,程序模拟抛一次硬币。根据随机结果,z的值会增加或减少1。
  • 循环会一直进行,直到z的值变得小于3或大于10,此时条件不再满足,循环停止。

这个例子比第一个例子更复杂,因为循环的终止依赖于随机数的生成,我们无法提前预知循环具体会执行多少次。这也再次提醒我们,在设计此类循环时要确保它不会运行得过久。

关于复合条件,有一个技术细节需要注意:R语言在评估像 z >= 3 & z <= 10 这样的复合逻辑表达式时,总是从左到右进行。它会先计算最左边部分(z >= 3)的真假。只有当这部分为TRUE时,才会继续计算右边的部分(z <= 10)。如果两者都为TRUE,整个条件才为TRUE,程序才会进入循环体执行。


总结

本节课中我们一起学习了while循环的核心知识:

  1. 基本语法while循环基于一个逻辑表达式,只要该表达式为TRUE,就重复执行循环体。
  2. 核心用途:适用于循环次数由动态条件决定,而非固定次数的场景。
  3. 重要风险:必须仔细设计循环条件,确保循环有机会终止,否则会导致无限循环
  4. 复合条件:可以使用逻辑运算符组合多个条件,增加循环控制的灵活性。
  5. 评估顺序:复合条件中的表达式按从左到右的顺序进行求值。

记住,while循环是一个强大的工具,但“能力越大,责任越大”。在享受其灵活性的同时,务必保证循环逻辑的严谨性,让你的程序既强大又可靠。

051:R语言控制结构 - repeat、next、break 🎯

在本节课中,我们将学习R语言中三个特殊的控制结构:repeatnextbreak。这些结构用于控制循环的执行流程,对于编写灵活的程序逻辑至关重要。


概述

repeat结构用于启动一个无限循环,next用于跳过循环的当前迭代,而break用于完全退出循环。理解它们的用法和潜在风险,是编写高效、健壮R代码的重要一步。


repeat循环:无限循环的起点 🔁

repeat是一个用于启动无限循环的构造。这不是R中常用的控制结构,但偶尔有其用途。退出repeat循环的唯一方法是调用break。显然,你必须在某个时刻调用break,除非你希望程序永远运行下去。

以下是一个简单的例子。这里,我将x0初始化为1,并将容差设置为10^-8。然后,我将重复执行以下结构。

x0 <- 1
tol <- 1e-8

repeat {
  # 假设有一个函数计算x的新估计值
  x1 <- computeEstimate() # 此处为示例函数
  if (abs(x1 - x0) < tol) {
    break
  } else {
    x0 <- x1
  }
}

在这个例子中,如果新值x1与旧值x0之差的绝对值小于某个容差(这里是10^-8),那么我将停止循环并继续执行后续代码。如果差值大于容差,则将x0设置为新值,然后再次运行循环。我会计算一个新的估计值,并检查它们的差值是否很小。

这是一种常见的公式化方法。例如,在许多优化算法中,如果你试图找到某个方程组的解,或者试图最大化一个函数,你通常会反复迭代。当你计算出的估计值越来越接近时,你就会停止,因为这通常表明你正在收敛到目标函数的最小值或最大值。

理论上,这是一个完全合理的构造。你希望不断循环执行算法,直到两个值接近为止。


repeat循环的潜在问题 ⚠️

然而,这里存在一个问题。首先,它需要一个保证收敛的算法,但并非每个算法都具有这种特性。其次,它在某种程度上也依赖于容差。一般来说,如果容差更小,循环将运行更长时间。

由于很难预测这个循环会运行多久,它可能有点危险,因为它是不可预测的。理论上,它可能永远运行下去,你无法保证程序会在某个时刻停止。

因此,尽管这种构造在理论上是正确的做法,但通常不是一个好主意。更好的做法可能是使用for循环,它对允许运行的迭代次数有硬性限制。这样,如果你的算法出现问题,它最终会达到硬性限制并停止,你会知道它停止的原因是因为没有收敛。

如果使用repeat方法,当你的算法不收敛时,你将没有任何警告,它只会运行很长时间。


next与break:控制循环流程 ⏭️⏹️

我想讨论的最后一个控制结构元素是next,另外还有returnnext基本上用于任何类型的循环构造中,当你想要跳过某次迭代时使用。

以下是一个基本的for循环,它将运行100次迭代。但我的目的是跳过前20次迭代,然后只对第21到100次迭代执行某些代码。

for (i in 1:100) {
  if (i <= 20) {
    next  # 跳过本次迭代
  }
  # 执行第21到100次迭代的代码
  # ...
}

这里有一个非常简单的if条件:如果i小于等于20,我就跳过。我使用next表达式,它将跳过for循环中的所有其他内容,并再次迭代。一旦i超过20,表达式i <= 20将为假,因此不会执行next,程序将进入for循环的代码主体。

所以,next是跳过循环中某次迭代的一种方式。当然,break是彻底退出循环的一种方式。

return函数是另一个可以用来退出循环的函数,但它主要用于退出整个函数,并返回你传递给它的值。我们将在讨论函数时更多地讨论return,但它也是可以中断程序流程的东西。


总结与核心要点 📝

以上就是目前关于控制结构的内容。基本的总结是,像ifwhilefor这样的控制结构允许你控制R程序中的流程。

一般来说,虽然存在允许你执行无限循环的构造,但你通常需要警惕这类情况,并尽可能避免它们。即使它们在理论上是正确的,因为它们可能导致程序中出现一些不可预测的行为。

我还没有提到的另一个关键点是,这里提到的控制结构主要对编写程序有用。但对于命令行和交互式工作,我们可以使用其他类型的循环函数,它们通常名称中包含apply。在探索数据等命令行交互式工作中,这些函数可能更有用,尽管它们在编写程序时也非常有用。我将在另一组视频中讨论apply函数。


本节课中,我们一起学习了:

  1. repeat循环的用途与风险。
  2. 使用next跳过循环的特定迭代。
  3. 使用break完全退出循环。
  4. 理解了在编写循环时,设定明确终止条件的重要性。

掌握这些控制结构,能帮助你更好地控制程序逻辑,编写出更清晰、更高效的R代码。

052:编写你的第一个R函数 🧑‍💻

在本节课中,我们将学习如何在R语言中编写函数。函数在R编程中扮演着至关重要的角色,尤其是在进行大量数据分析或统计分析时。本教程旨在帮助初学者,特别是那些对编程语言不太熟悉的同学,迈出编写R函数的第一步。

概述

我们将从最简单的函数开始,逐步介绍函数的基本语法、参数设置、返回值以及如何设置默认参数。通过几个具体的例子,你将学会如何创建自己的R函数。


在文本文件中编写函数

首先,我们通常将函数编写在独立的文本文件中,而不是在R的命令行中直接编写。这样做更便于管理和维护代码。未来,你还可以将函数组织成更结构化的R包。

以下是创建函数的基本步骤。

打开RStudio并创建脚本

首先,打开RStudio并创建一个新的R脚本文件,用于编写我们的代码。


第一个函数:两数相加 ➕

我们将从一个非常简单的函数开始:将两个数字相加。这个函数没有复杂的实际用途,但它能清晰地展示R函数的基本语法、如何指定参数以及如何返回值。

以下是创建该函数的步骤:

  1. 使用 function 指令定义函数。
  2. 为函数指定两个参数,例如 xy
  3. 在函数体内,使用 + 运算符将两个参数相加。
  4. 在R中,函数会自动返回最后一个表达式的值,因此这里无需显式使用 return 语句。

以下是该函数的代码:

add2 <- function(x, y) {
  x + y
}

定义函数后,你可以在控制台中调用它。例如,输入 add2(3, 5),函数将返回结果 8

现在,你已经成功编写了你的第一个R函数。


第二个函数:提取向量中大于指定值的元素 🔼

接下来,我们来看一个稍微复杂一点的函数。这个函数接收一个数值向量和一个阈值,返回向量中所有大于该阈值的元素。

以下是创建该函数的步骤:

  1. 定义函数 above,它接受两个参数:向量 x 和阈值 n
  2. 在函数体内,创建一个逻辑向量,判断 x 中哪些元素大于 n
  3. 使用这个逻辑向量对 x 进行子集筛选,返回符合条件的元素。

以下是该函数的代码:

above <- function(x, n) {
  use <- x > n
  x[use]
}

调用这个函数时,你需要提供向量和阈值。例如:

x <- 1:20
above(x, 12)

这将返回向量 x 中所有大于12的数字。

设置默认参数

如果阈值 10 是一个非常常用的值,我们可以为参数 n 设置一个默认值。这样,当用户调用函数时不指定 n,函数会自动使用默认值 10,而不会报错。

修改后的函数代码如下:

above <- function(x, n = 10) {
  use <- x > n
  x[use]
}

现在,调用 above(x) 将默认返回所有大于10的元素,使函数使用起来更加方便。


第三个函数:计算数据框或矩阵的列均值 📊

现在,我们来编写一个更实用的函数:计算一个数据框或矩阵中每一列的均值。这个函数会涉及循环操作。

以下是创建该函数的步骤:

  1. 定义函数 columnmean,它接受一个数据框或矩阵 y 作为参数。
  2. 使用 ncol 函数获取 y 的列数。
  3. 初始化一个长度等于列数的数值向量 means,用于存储每列的均值。
  4. 使用 for 循环遍历每一列,计算其均值并存入 means 向量。
  5. 函数最后返回 means 向量。

以下是该函数的初始代码:

columnmean <- function(y) {
  nc <- ncol(y)
  means <- numeric(nc)
  for(i in 1:nc) {
    means[i] <- mean(y[, i])
  }
  means
}

处理缺失值(NA)

在计算均值时,数据中可能存在缺失值(NA)。R的 mean 函数有一个 na.rm 参数,用于指定是否在计算前移除缺失值。我们可以为我们的函数也添加这个功能。

  1. 在函数参数中添加 removeNA,并设置默认值为 TRUE
  2. 在循环内部调用 mean 函数时,将 removeNA 参数的值传递给它。

修改后的函数代码如下:

columnmean <- function(y, removeNA = TRUE) {
  nc <- ncol(y)
  means <- numeric(nc)
  for(i in 1:nc) {
    means[i] <- mean(y[, i], na.rm = removeNA)
  }
  means
}

现在,调用 columnmean(airquality) 会默认移除NA后计算列均值。你也可以通过 columnmean(airquality, removeNA = FALSE) 来保留NA的行为。


保存你的工作 💾

编写函数后,最重要的一步是保存你的R脚本文件。在RStudio中,通过“File”菜单选择“Save As...”,将文件保存为 .R 扩展名(例如 my_functions.R)。这样可以防止因意外关闭程序而丢失所有工作成果。


总结

在本节课中,我们一起学习了R函数的基础知识。我们从最简单的两数相加函数开始,逐步深入到能够处理向量筛选和计算数据框列均值的函数。我们了解了如何定义函数、设置参数和默认值,以及如何利用R自动返回最后一个表达式结果的特性。掌握这些基础是进行更复杂R编程和数据分析的关键第一步。现在,你可以尝试修改这些函数或创建自己的新函数了。

053:R 语言函数(第一部分)🚀

在本节课中,我们将要学习 R 语言中函数的基础知识。函数是 R 语言中最强大的特性之一,它标志着用户从单纯使用 R 转变为能够编程使用 R。基本思想是,你可以通过命令行探索数据、运行代码,但最终你可能会遇到需要执行更复杂操作的情况,这些操作无法用一两行代码表达。如果你需要反复执行这些操作,通常就需要将这种功能编码成一个函数。

本节课将分三部分介绍函数。首先,我们将介绍如何编写函数以及 R 中函数的基本写法。接着,我们会探讨词法作用域和 R 语言的作用域规则。最后,我们将以一个简单的例子结束。

1. 函数基础

在 R 语言中,函数使用 function 指令创建,并且像其他任何对象一样,函数也是以 R 对象的形式存储的。例如,你可以有整数向量、包含不同元素的列表、数据框,当然也可以有函数。具体来说,函数是一种类为 function 的 R 对象。

基本的函数构造如下:你将 function 指令赋值给某个对象(这里我称之为 f),该指令接收一些参数,然后在大括号 {} 内编写实现函数功能的代码。

f <- function(argument1, argument2) {
  # 执行某些操作的代码
}

R 语言的一个优点是,函数被视为一等对象。这意味着你可以像对待其他 R 对象一样对待函数。重要的是,你可以将函数作为参数传递给其他函数,这在统计学中非常有用。此外,函数可以嵌套,即你可以在一个函数内部定义另一个函数。当我们讨论词法作用域时,会看到这带来的影响。

函数的返回值就是函数体中最后一个被求值的 R 表达式。虽然有一个名为 return 的函数可以用来返回值,但 R 中没有特殊的返回表达式。

2. 函数参数

函数拥有所谓的命名参数,这些参数可以具有默认值。当你设计可能被他人使用的函数时,这些特性非常有用。例如,一个函数可能有很多不同的参数,允许你调整很多细节,但大多数时候你不需要改变所有参数,可能只关心其中一两个。因此,为某些参数设置默认值就很有用。

首先,形式参数是包含在函数定义中的参数。回顾上一节的代码,形式参数就是定义在 function() 括号内的那些。formals 函数可以接受一个函数作为输入,并返回该函数所有形式参数的列表。

并非 R 中的每个函数都会用到所有形式参数。例如,如果一个函数有 10 个不同的参数,你可能不需要为所有 10 个参数都指定值。因此,函数参数可以是缺失的,或者当用户未指定时使用默认值。

R 的函数参数可以通过位置或名称进行匹配。这在编写和调用函数时都非常关键。

例如,查看计算一组数字标准差的函数 sdsd 的输入参数 x 是一个数据向量,第二个参数 na.rm 控制是否移除数据中的缺失值,其默认值为 FALSE。因此,默认情况下,如果你要计算标准差的数据集中有缺失值,这些缺失值不会被包含在计算中。

以下是参数匹配的几种方式:

# 模拟数据
mydata <- rnorm(100)

# 1. 位置匹配(未命名参数)
sd(mydata)

# 2. 命名参数
sd(x = mydata)
sd(x = mydata, na.rm = FALSE)

# 3. 命名参数,顺序可调
sd(na.rm = FALSE, x = mydata)

# 4. 混合匹配(命名一个,另一个按位置)
sd(na.rm = FALSE, mydata)

所有这些表达式都返回相同的值。当混合使用时,已命名的参数会被设定,你可以认为它从参数列表中“移除”了,然后剩余未命名的参数会按它们出现的顺序依次匹配给函数剩余的形式参数。

虽然这些表达式在功能上等价,但并非所有写法都同样推荐。例如,即使命名参数后顺序可以调整,也不建议随意颠倒参数顺序,因为这可能导致混淆。

3. 参数匹配的实用场景

大多数时候,命名参数在命令行中非常有用,尤其是当函数有很长的参数列表,而你只想使用默认值,仅修改列表中间或末尾的某个参数时。你通常记不清它是第几个参数,这时通过名称调用就非常方便,你无需记住参数在列表中的顺序。

这在绘图函数中尤其方便,因为许多绘图函数都有很长的参数列表,且都有默认值。你可能只想调整其中一个特定参数,这时不需要记住该参数在列表中的位置就很有用。

函数参数还可以进行部分匹配,这主要用于交互式工作,而非编程。当你调用函数时,如果参数名很长,你可以只输入名称的一部分,只要这部分能唯一匹配到一个参数,R 系统就会将该值赋给正确的参数。

参数匹配的操作顺序是:首先进行精确匹配,检查是否有参数名完全匹配;如果没有,则寻找部分匹配;如果部分匹配也不成功,则进行位置匹配。

4. 示例:线性模型函数 lm

让我们以线性模型函数 lm 为例,看看长参数列表的匹配是如何工作的。lm 函数用于将线性模型拟合到数据。

以下是 lm 函数参数列表的一部分:第一个是公式 formula,第二个是数据 data,然后是子集 subset、权重 weights 等。前五个参数没有默认值,用户必须指定它们。而 methodmodelx 等参数则有默认值,如果用户不指定,就会使用这些默认值。

以下两种函数调用是等价的,展示了混合匹配:

# 方式1:通过名称指定 data,其余按位置
lm(data = mydata, y ~ x, model = FALSE, 1:100)

# 方式2:更常见的调用方式,前三个按位置,后面的按名称指定
lm(y ~ x, data = mydata, subset = 1:100, model = FALSE)

第一种方式之所以可行,是因为 data = mydata 通过名称匹配,相当于从待匹配列表中“拿走”了。剩下的未命名参数 y ~ x 会匹配给第一个尚未匹配的参数(即 formula)。model = FALSE 也通过名称匹配并被“拿走”。最后,1:100 被赋给下一个尚未匹配的参数,即 subset

虽然第一种方式在语法上可行,但因其容易混淆,并不推荐。更常见和清晰的是第二种调用方式。

总结

本节课中,我们一起学习了 R 语言函数的基础知识。我们了解到函数是使用 function 指令创建的一等对象,可以像其他对象一样存储和传递。我们重点探讨了函数参数的两种匹配方式:位置匹配和名称匹配,以及如何为参数设置默认值。我们还通过 sdlm 函数的例子,看到了在实际中如何灵活运用这些规则来编写和调用函数,特别是在处理长参数列表时,使用命名参数可以大大提高代码的可读性和便利性。理解这些基础是后续学习更高级函数概念和词法作用域的关键。

054:函数(第二部分)📘

在本节课中,我们将继续学习 R 语言中函数的相关概念。我们将重点探讨函数的默认参数、惰性求值机制以及特殊的 ... 参数。理解这些特性将帮助你编写更灵活、更健壮的函数。


默认参数与 NULL

上一节我们介绍了函数的基本结构,本节中我们来看看如何为函数参数设置默认值。

一个函数可以包含多个参数,其中一些可以预先设定默认值。NULL 是一个常用的默认值,它通常表示“空”或“无内容”。

以下是一个包含四个参数的函数示例:

function_name <- function(a, b = 1, c = 2, d = NULL) {
  # 函数体
}

在这个例子中,参数 a 没有默认值,而参数 bcd 都有默认值。d 的默认值被设置为 NULL


惰性求值

R 语言的一个关键特性是惰性求值。这意味着函数的参数只有在函数体内真正被用到时,才会被计算。

请看以下函数:

f <- function(a, b) {
  a^2
}

这个函数接受两个参数 ab,但函数体只使用了 a(计算其平方)。当我们调用 f(2) 时,会发生以下情况:

  • 2 被位置匹配到参数 a
  • 函数计算 2^2 并返回结果 4
  • 参数 b 从未在函数体中被使用,因此 R 永远不会去计算或检查它是否存在,也不会因此报错。

惰性求值引发的错误

惰性求值意味着错误只会在参数被实际求值时触发。

请看另一个函数:

f <- function(a, b) {
  print(a)
  print(b)
}

以下是调用该函数时可能发生的情况:

  • 调用 f(45):值 45 被匹配给 a。函数首先执行 print(45),成功输出 45。接着,当执行到 print(b) 时,R 需要计算参数 b 的值。由于 b 既未在调用时提供,也没有默认值,此时会抛出错误:“argument ‘b’ is missing”。
  • 关键点在于,错误是在成功执行了第一行代码 print(a) 之后才发生的。

特殊参数:...

... 参数(通常读作“dot-dot-dot”)用于处理可变数量的参数。这在两种主要场景下非常有用。

场景一:扩展其他函数
当你想要创建一个新函数来扩展或修改现有函数(如 plot)的行为,但又不想手动复制原函数的所有参数列表时,可以使用 ...

例如,创建一个自定义绘图函数:

myplot <- function(x, y, type = "l", ...) {
  plot(x, y, type = type, ...)
}

在这个函数中:

  • xy 是显式定义的参数。
  • type 被设置了新的默认值 "l"(代表线条图)。
  • ... 会“吸收”调用 myplot 时传入的任何其他参数(如 main, xlab, col 等),并将它们原封不动地传递给内部的 plot 函数。

场景二:参数数量未知的函数
有些函数在设计时,就无法预知会接收多少个参数。... 为此提供了支持。

以下是两个典型例子:

  1. paste 函数:用于连接多个字符串。
    # 将多个字符串用空格连接
    paste("Hello", "World", "from", "R")
    
    它的第一个参数就是 ...,因为要连接的字符串数量是可变的。
  2. cat 函数:用于连接并输出内容。
    # 连接并输出到控制台
    cat("Value:", some_value, "\n")
    
    同样,其第一个参数也是 ...,用于接收所有要输出的对象。

使用 ... 的注意事项

当函数参数列表中包含 ... 时,出现在 ... 之后的所有参数都必须通过完整名称来指定,不能使用位置匹配或部分匹配。

以下是一个错误示例:

# 意图:用冒号连接 “A” 和 “B”
paste("A", "B", sep = ":")  # 正确,输出 “A:B”
paste("A", "B", se = ":")   # 错误!`se` 是 `sep` 的部分匹配,但被忽略

在第二个调用中,se 本意是参数 sep 的部分匹配。但由于 sep 位于 ... 之后,R 无法进行部分匹配。因此,se = “:” 会被 ... 吸收,当作一个普通的字符串参数处理,最终输出意料之外的结果 “A B :”


总结

本节课中我们一起学习了 R 函数的三个高级特性:

  1. 默认参数:可以为函数参数预设值,使函数调用更灵活,NULL 是表示“空”的常用默认值。
  2. 惰性求值:函数参数只在被需要时才计算,这影响了错误的触发时机。
  3. ... 参数:用于处理可变数量参数,常见于扩展函数或参数数量未知的函数。使用时需注意,... 之后的参数必须显式且完整地命名。

掌握这些概念,将有助于你更好地理解和构建复杂的 R 函数。

055:R语言作用域规则与符号绑定 🔍

在本节课中,我们将要学习R语言中一个核心概念:作用域规则。具体来说,我们将探讨当R执行一个函数时,它如何为函数体中出现的符号(例如变量名)绑定具体的值。理解这个过程对于编写和调试R代码至关重要。


符号绑定与搜索过程 🔎

在R中,当一个函数运行时,它需要为函数体中出现的每个符号找到一个具体的值。这个过程称为“符号绑定”。

例如,假设我们定义了一个名为 lm 的函数,它计算输入值的平方:

lm <- function(x) {
  x * x
}

然而,R的基础包 stats 中已经存在一个同名的 lm 函数,用于拟合线性模型。那么,当我们在代码中调用 lm 时,R如何决定使用哪个函数呢?

R通过搜索一系列“环境”来为符号绑定值。环境可以看作是一个包含符号及其对应值的列表。


搜索列表与环境顺序 📋

当你在命令行工作并需要检索一个R对象的值时,R会遵循一个特定的搜索顺序。

以下是搜索过程的核心步骤:

  1. 首先搜索全局环境:全局环境就是你的工作空间,包含了你定义或加载到R中的所有对象。如果在这里找到了匹配请求的符号,R就会使用该符号关联的值。

  2. 然后搜索已加载包的命名空间:如果在全局环境中没有找到匹配的符号,R会接着搜索“搜索列表”上每个包的命名空间。搜索列表包含了当前已加载到R中的所有包。

搜索列表是有顺序的。其顺序通常是:

  • 第一个元素总是全局环境
  • 后续元素是已加载的包,例如 statsgraphics 等。
  • 最后一个元素总是基础包

因此,对于 lm 这个符号,如果它在全局环境中被重新定义,R会优先使用全局环境中的版本。否则,它会沿着搜索列表向下查找,最终在 stats 包中找到用于拟合线性模型的 lm 函数。


包的加载与搜索列表变化 🔄

上一节我们介绍了搜索列表的默认顺序,本节中我们来看看用户操作如何改变这个列表。

全局环境始终是搜索列表的第一个元素,而基础包始终是最后一个元素。由于搜索过程是按列表顺序进行的,因此包在列表中的顺序非常重要。

用户可以通过配置决定启动时加载哪些包,并且可以随时使用 library() 函数加载新包。这意味着你无法假设搜索列表中的包集合或顺序在任何会话中都是固定不变的。

当用户使用 library() 函数加载一个包时,该包的命名空间(即包含其所有符号和值的环境)会被放置在搜索列表的第二个位置,紧跟在全局环境之后。原先在这个位置及之后的包都会向后顺移一位。

此外,R为函数和非函数对象设置了独立的命名空间。这意味着你可以拥有一个名为 c 的向量,它不会与同名的、用于连接对象的 c() 函数发生冲突。


词法作用域规则 📖

上述搜索机制引出了R的“作用域规则”,这是R与原始S语言的主要区别之一。作用域规则决定了如何为函数中的“自由变量”绑定值。

在函数中,变量主要分为两类:

  • 形式参数:在函数定义头部声明的变量。
  • 自由变量:在函数体中使用,但未在函数定义头部声明的变量。

考虑以下函数:

f <- function(x, y) {
  x^2 + y / z
}

在这个函数中,xy 是形式参数,而 z 就是一个自由变量。那么,R应该为 z 赋予什么值呢?

R采用词法作用域(也称为静态作用域)规则。其核心原则可以总结为:

自由变量的值,在定义该函数的环境中搜索。


环境与函数闭包 🧩

为了理解词法作用域,我们需要先理解“环境”这个概念。

环境是符号-值对的集合。例如,符号 x 可能对应值 3.14,符号 y 可能对应一个数据框。R中的所有对象都可以看作是符号和值的配对。

每个环境都有一个父环境(除了唯一的“空环境”没有父环境)。这种结构形成了环境的层次体系。例如:

  • 全局环境是你的工作空间,它是一个环境。
  • 每个包的命名空间也是一个环境。

当一个函数与它被定义时所在的环境关联起来时,就形成了一个函数闭包。闭包是R中实现许多高级功能的关键。


自由变量的搜索路径 🛤️

现在,让我们将以上概念串联起来,看看当函数中遇到一个自由变量时,R的具体搜索路径是什么。

搜索遵循以下步骤:

  1. 首先,在定义该函数的环境中查找。例如,如果一个函数在全局环境中定义,那么首先就在全局环境中搜索自由变量。

  2. 如果未找到,则向父环境回溯。如果在当前环境中找不到,则去其父环境中查找。

  3. 沿环境链向上,直至顶层环境。搜索会沿着父环境链不断向上,直到达“顶层环境”。对于在全局环境定义的函数,顶层环境就是全局环境本身;对于在包中定义的函数,顶层环境通常是该包的命名空间。

  4. 若仍未找到,则沿搜索列表向下查找。一旦到达顶层环境还未找到,搜索就会“跳出”当前的环境链,转而沿着我们之前讨论的搜索列表继续向下查找(从顶层环境在搜索列表中的位置开始)。

  5. 最终抵达空环境。搜索会一直进行,直到查找到搜索列表的末尾(基础包之后),即空环境

  6. 抛出错误。如果在经历了所有环境和搜索列表后,直到空环境仍未找到该符号,R就会抛出一个错误,提示找不到该对象。


总结 📝

本节课中我们一起学习了R语言的作用域规则与符号绑定机制。我们了解到:

  1. R通过搜索列表来为符号绑定值,搜索顺序从全局环境开始,到已加载的包,最后是基础包。
  2. 用户加载包会改变搜索列表的顺序,新包会插入到全局环境之后。
  3. R采用词法作用域规则,其核心是:自由变量在其函数被定义的环境中进行查找。
  4. 搜索自由变量时,R会先在定义环境及其父环境链中查找,然后根据需要跳转到搜索列表中继续查找。
  5. 理解环境(符号-值对的集合)和函数闭包(函数与其定义环境的关联)是掌握作用域的关键。

掌握这些规则有助于你理解R代码的行为,预测变量如何被解析,并能在编写函数(尤其是使用闭包的高级函数编程)时避免常见的错误。

056:R语言作用域规则详解 🧭

在本节课中,我们将要学习R语言中一个核心且强大的概念——作用域规则。理解作用域规则对于编写高效、可预测的函数至关重要,尤其是在处理函数嵌套和闭包时。


概述

R语言采用词法作用域规则。这意味着函数在定义时,其自由变量的值由定义该函数的环境决定,而非调用它的环境。这与动态作用域形成对比。本节将深入探讨词法作用域的原理、应用及其影响。


全局环境与函数定义

通常情况下,函数在全局环境中定义。此时,函数内部的自由变量会在用户的全局工作空间中寻找其值。这是大多数用户预期的行为:如果在函数内部找不到某个变量的值,就向上一级(全局环境)查找。

这种机制允许你定义一些全局变量,这些变量可以被工作空间中定义的多个不同函数共用。


函数嵌套与闭包

然而,R语言的关键特性在于,你可以在一个函数内部定义另一个函数。例如,一个函数可以返回另一个函数作为其结果。

在大多数情况下,函数返回列表、向量、矩阵或数据框等。但当函数返回另一个函数时,情况就变得有趣了。此时,被返回的函数是在另一个函数内部定义的,因此它的定义环境不再是全局环境,而是那个外层函数的内部环境。这正是作用域规则开始产生重要影响的时候。

为了说明这一点,我们来看一个构造器函数的例子。

以下是构造器函数 make.power 的代码示例:

make.power <- function(n) {
  power <- function(x) {
    x ^ n
  }
  return(power)
}

make.power 函数接收一个参数 n。在其内部,它定义了另一个函数 power,该函数接收参数 x 并计算 x^n。最后,make.powerpower 函数作为结果返回。

power 函数内部,x 是形式参数,没有问题。但 n 是一个自由变量,因为它不是在 power 内部定义的。然而,n 定义在 make.power 函数内部。由于 power 是在 make.power 的环境中定义的,power 函数会在这个环境中找到 n 的值。

现在,我们可以这样使用它:

cube <- make.power(3) # 创建一个计算立方的函数
square <- make.power(2) # 创建一个计算平方的函数

cube(3) # 返回 27 (3^3)
square(3) # 返回 9 (3^2)

通过这种方式,一个函数(make.power)能够构造出许多具有不同功能(计算不同次幂)的函数。


探索函数环境

你如何知道一个函数的环境里有什么呢?可以通过 ls 函数来查看函数定义环境中的对象。

以下是查看函数环境的代码示例:

# 查看 cube 函数的环境
ls(environment(cube))
# 输出: [1] "n"

# 获取环境中的 n 的值
get("n", environment(cube))
# 输出: [1] 3

# 查看 square 函数的环境
get("n", environment(square))
# 输出: [1] 2

对于 cube 函数,其环境中有一个对象 n,值为 3。这就是 cube 函数知道要将参数计算为三次方的“记忆”来源。同样,square 函数的环境中也存储了 n=2。这个存储了自由变量值的环境被称为函数的闭包


词法作用域 vs. 动态作用域

为了更清晰地理解R的词法作用域,我们将其与动态作用域进行简要比较。

考虑以下代码:

y <- 10

f <- function(x) {
  y <- 2
  y^2 + g(x)
}

g <- function(x) {
  x * y
}

在函数 f 中,yg 都是自由变量。在函数 g 中,y 也是自由变量。那么,调用 f(3) 会返回什么结果呢?

  • 在词法作用域(R的规则)下:函数 gy 的值,会在定义 g 的环境中查找,即全局环境。因此,g 中的 y 值为 10。计算过程为:f(3) -> 2^2 + g(3) -> 4 + (3 * 10) -> 34
  • 在动态作用域下:函数 gy 的值,会在调用 g 的环境中查找(在R中称为父框架)。此时,调用环境是 f 的内部,其中 y 被赋值为 2。因此,g 中的 y 值为 2。计算过程为:f(3) -> 2^2 + g(3) -> 4 + (3 * 2) -> 10

可以看到,使用不同的作用域规则,同一个函数调用会产生不同的结果。


全局环境下的特殊情况

有一种情况会让词法作用域和动态作用域看起来效果相同:当一个函数在全局环境中定义,并且随后也从全局环境调用时,其定义环境和调用环境是完全相同的。

这有时会给人一种存在动态作用域的错觉,即使实际上并没有。请看下面的例子:

g <- function(x) {
  a <- 3
  x + a + y
}

g(2) # 报错:找不到对象 'y'

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/59d323213e076deff65c80bf2b8b3a35_13.png)

y <- 3
g(2) # 返回 8

第一次调用 g(2) 时,函数 g 在全局环境中寻找自由变量 y,但 y 尚未定义,因此报错。当我们定义了 y <- 3 后再次调用,它就能成功找到 y 并计算结果。

虽然看起来 y 的值是在调用时被查找到的,但实际上它仍然是在定义环境(全局环境)中被找到的,只是因为定义环境和调用环境恰好重合了。


词法作用域的应用与影响

词法作用域并非R语言独有的特性。许多其他编程语言也支持它,例如Scheme、Perl、Python和Common Lisp等。有一个著名的计算机科学观点认为,所有语言最终都会向Lisp的特性靠拢,因此词法作用域是一种非常常见且重要的语言特性。

在R语言中,词法作用域带来了两个主要影响:

  1. 所有对象必须存储在内存中:由于作用域规则的复杂性,以及各种环境相互链接的方式,很难在物理内存之外实现这种模型。因此,在R最初设计时,所有数据都存储在内存中。这在处理现代超大型数据集时带来了挑战。
  2. 每个函数都必须携带指向其定义环境的指针:这个定义环境可能在任何地方(由于函数嵌套),因此需要额外的指针来关联内存中的环境位置,这使得R的对象模型比一些其他语言(如早期的S-PLUS)更为复杂,但也因此更加强大和灵活。

在S-PLUS(S语言的早期实现)中,自由变量总是在全局工作空间中查找,因此所有函数的定义环境都是相同的,理论上数据可以存储在磁盘上。而R的词法作用域模型为实现更高级的编程范式(如函数工厂、闭包)提供了基础。


总结

本节课我们一起深入学习了R语言的词法作用域规则。我们了解到:

  • R采用词法作用域,函数依据其定义时的环境来解析自由变量。
  • 通过函数嵌套闭包,我们可以创建能“记住”特定状态的函数(如 make.power 的例子)。
  • 词法作用域与动态作用域的关键区别在于变量查找的环境不同(定义环境 vs. 调用环境)。
  • 虽然词法作用域要求更多的内存管理和更复杂的内部模型,但它为R提供了强大的函数式编程能力。

理解这些规则是编写高级、模块化且可维护的R代码的关键一步。

057:R语言作用域规则在优化中的应用示例(可选)🔧

在本节课中,我们将学习如何利用R语言的作用域规则来构建更灵活、更简洁的优化函数。我们将通过一个具体的统计示例——拟合正态分布的最大似然估计——来演示这一概念。


概述

之前我们已经讨论了R语言中的函数和作用域规则。你可能会好奇这些知识在实际中有什么用。除了编写常规的数据处理或计算函数外,作用域规则与函数的结合在统计学中非常有用,尤其是在优化问题中。

R语言中有几个优化例程,如 optimnlmoptimize。它们都要求你传入一个函数,该函数的参数是一个参数向量。例如,你可能有一个函数需要在某个参数范围内最小化或最大化,而 optimnlmoptimize 这类函数会接收这个目标函数并尝试寻找其最小值或最大值。

在统计学中,我们试图最小化或最大化的目标函数(例如对数似然函数)除了依赖于待优化的参数外,通常还依赖于其他因素,特别是数据。那么,如何以一种清晰、可读的编程风格来编写一个依赖于参数、数据以及其他许多因素的函数,并让用户更容易使用呢?此外,在进行此类优化时,通常需要固定某些参数,例如将一个参数设为特定值,然后优化其他参数。


核心概念:构造函数与词法作用域

R语言中解决任何优化问题的基本思路是:你可以创建一个构造函数来构建目标函数。这个目标函数会将其所依赖的所有数据和其他因素“打包”到其定义环境中。这样,每次调用该函数时,你就不需要重复指定这些数据,只需指定参数值即可。函数会自动从其封闭环境中查找这些“自由变量”。

以下是构建一个负对数似然函数构造函数的示例代码。请注意,R中的 optimnlmoptimize 等函数默认都是最小化函数。因此,如果你的目标函数原本需要最大化(如似然函数),你需要取其负值,以便通过最小化来达到目的。

make.NegLogLik <- function(data, fixed = c(FALSE, FALSE)) {
  params <- fixed
  function(p) {
    params[!fixed] <- p
    mu <- params[1]
    sigma <- params[2]
    # 计算正态分布的对数似然并取负值
    a <- -0.5 * length(data) * log(2 * pi * sigma^2)
    b <- -0.5 * sum((data - mu)^2) / (sigma^2)
    -(a + b)
  }
}

这个 make.NegLogLik 函数是一个构造函数。它的第一个参数是 data(数据)。第二个参数 fixed 是一个逻辑向量,用于指示哪些参数需要被固定。

在构造函数内部,我们定义了另一个函数。这个内部函数以参数向量 p 为输入,并返回基于正态分布假设的负对数似然值。正态分布有两个参数:均值 mu 和标准差 sigma,这就是我们要优化的两个参数。

构造函数最终返回这个内部函数作为其输出。


应用示例:拟合正态分布

现在,让我们应用这个构造函数。首先,我们模拟一些来自正态分布(均值为1,标准差为2)的随机数据。

set.seed(1)
normals <- rnorm(100, 1, 2)

接着,我们调用构造函数,传入模拟的数据,生成我们的负对数似然函数 nLL

nLL <- make.NegLogLik(normals)
nLL

当你打印 nLL 函数时,会发现函数体与我们构造函数中定义的代码一致。但请注意函数底部有一个环境标签。这指向该函数的定义环境。当你在另一个函数内部定义一个函数时,R必须保留一个指向该定义环境的指针,以便记住所有自由变量的值。

nLL 的函数体中,变量 data 既不是参数,也不是局部变量,它是一个自由变量。它的值来源于构造函数 make.NegLogLik,并被存储在该函数的定义环境中。

你可以使用 ls 函数查看 nLL 函数环境中的内容:

ls(environment(nLL))

你会看到环境中包含了 datafixedparams 变量。这些都是在负对数似然函数内部使用的自由变量,但它们已在定义环境中被定义和固定。


进行优化

现在我们可以使用 optim 函数来优化我们的 nLL 函数。我们需要为参数 musigma 提供初始值。

optim(c(mu = 0, sigma = 1), nLL)$par

优化结果给出的估计值 mu 约为1.2,sigma 约为1.78,非常接近我们模拟时使用的真实值(1和2)。


固定参数进行优化

构造函数的一个强大之处在于可以轻松地固定某些参数。例如,我们可以将 sigma 固定为其真实值2,然后仅优化 mu

首先,我们需要重新构造目标函数,这次在调用构造函数时指定 fixed 参数。

nLL_fixed_sigma <- make.NegLogLik(normals, fixed = c(FALSE, 2))

现在,nLL_fixed_sigma 是一个只关于 mu 的单变量函数。我们可以使用 optimize 函数(专用于单变量优化)来寻找最小值。

optimize(nLL_fixed_sigma, c(-1, 3))$minimum

优化得到的 mu 估计值约为1.21。

同样,我们也可以固定 mu 为1,然后优化 sigma

nLL_fixed_mu <- make.NegLogLik(normals, fixed = c(1, FALSE))
optimize(nLL_fixed_mu, c(1e-6, 10))$minimum

优化得到的 sigma 估计值约为1.8。


可视化似然函数

由于我们的目标函数已经“打包”了所有数据,并且参数设置灵活,因此绘制似然函数曲线变得非常简单。

例如,要绘制固定 mu=1 时,负对数似然函数随 sigma 变化的曲线:

# 构造固定mu的函数
nLL_plot_sigma <- make.NegLogLik(normals, fixed = c(1, FALSE))
# 创建sigma的网格值
x <- seq(1, 3, len = 100)
# 计算每个sigma对应的负对数似然值
y <- sapply(x, nLL_plot_sigma)
# 绘图
plot(x, y, type = "l", xlab = expression(sigma), ylab = "Negative Log Likelihood")

同样,我们可以绘制固定 sigma=2 时,负对数似然函数随 mu 变化的曲线:

# 构造固定sigma的函数
nLL_plot_mu <- make.NegLogLik(normals, fixed = c(FALSE, 2))
# 创建mu的网格值
x <- seq(0.5, 1.5, len = 100)
# 计算每个mu对应的负对数似然值
y <- sapply(x, nLL_plot_mu)
# 绘图
plot(x, y, type = "l", xlab = expression(mu), ylab = "Negative Log Likelihood")

总结

本节课中,我们一起学习了如何利用R语言的词法作用域规则来构建用于优化的目标函数。

  • 核心机制:通过构造函数创建一个内部函数,该内部函数将其依赖的数据和固定参数存储在其定义环境中。
  • 主要优点
    1. 代码简洁:调用优化函数时,无需冗长的参数列表,只需传递待优化的参数。
    2. 灵活性强:可以轻松固定部分参数,仅优化其余参数,便于进行条件优化和模型比较。
    3. 便于探索:简化了交互式工作和探索性分析,例如绘制似然函数曲线。

这种方法使得统计建模和优化的代码更加清晰、模块化且易于维护。关于R语言词法作用域在统计计算中应用的更深入讨论,可以参考Robert Gentleman和Ross Ihaka的论文《Lexical Scope and Statistical Computing》。

通过本示例,你应该对如何将R的作用域规则应用于实际统计问题有了更具体的理解。

058:R 语言编码标准 📝

在本节课中,我们将学习 R 语言编程中的编码标准。这些标准对于编写清晰、可读且易于维护的代码至关重要。我们将探讨几个核心原则,帮助你养成良好的编程习惯。


概述

编码标准在 R 语言中非常重要,因为它们能帮助你使代码更具可读性,让你和其他人能理解代码的逻辑。虽然像任何风格问题一样,很难让所有人都同意一套标准,但有几个非常基本且最低限度的标准,在 R 编程中至关重要。本节将讨论一些我认为在编写 R 代码时重要的编码标准,这些标准将使你的代码更易读、对他人更有用。


核心原则

以下是编写高质量 R 代码的几个核心原则。

1. 使用文本编辑器 📄

在任何编程语言中,不仅仅是 R,一个非常重要的原则是:始终使用文本编辑器编写代码,并保存为文本文件

文本文件是一种基本标准。它通常没有任何格式或特殊外观,就是纯文本。通常,它是 ASCII 文本。如果你在非英语国家使用其他语言,可能会有其他标准文本格式。但基本思想是,如今任何基本的编辑程序都能读取文本格式。

当你编写代码时,应该始终尝试使用文本编辑器,因为这是最低的共同标准,能确保每个人都能访问并改进你的代码。


2. 缩进你的代码

上一节我们介绍了使用文本编辑器的重要性,本节中我们来看看如何通过缩进来提升代码的可读性。缩进对于代码可读性至关重要。

缩进是指不同的代码块应该比其他代码块向右多空出一些空间。仅通过缩进,你就能看出程序的控制流和流程走向。关于缩进多少合适,经常有激烈的讨论。虽然我也有一些建议,但最重要的是理解缩进为何重要。

以下是关于缩进的一些要点:

  • 缩进使代码结构一目了然。
  • 它有助于区分函数、循环和条件语句的起始与结束。
  • 建议至少使用 4 个空格进行缩进。

3. 限制代码宽度

与缩进紧密相关的是第三个原则:限制代码的宽度

如果你进行缩进,代码可能会无限向右延伸。因此,你需要限制代码右侧的宽度。这通常通过文本列数来控制。一种常见的做法是将文本限制在大约 80 列,确保你的代码宽度永远不会超过这个限制。


4. 限制函数长度

最后,我们来谈谈如何组织代码逻辑。限制函数的长度非常重要。

R 中的函数理论上可以写得很长。但一个逻辑的做法是,让每个函数只负责一项基本活动。例如,如果你的函数名为 read_data,那么它就应该只负责读取数据,而不应该同时处理数据、拟合模型和打印输出。这些逻辑步骤应该被拆分到不同的函数中。

将代码拆分成逻辑函数有几个优点:

  • 将整个函数放在编辑器的一屏内,可以一目了然地看到它的全部功能。
  • 当你使用 traceback、性能分析器或调试器时,它们会告诉你问题发生在函数调用栈的哪个位置。如果函数被逻辑清晰地分开,你就能知道去哪里修复问题。
  • 如果只有一个冗长的函数,调试工具只能告诉你这个函数有问题,但很难精确定位问题所在。

当然,过度拆分,比如拥有 10 个只有三行的函数,也不是我们想要的。目标是让不同函数的分离是合乎逻辑的,并且每个函数专门做一件事。


总结

本节课中我们一起学习了 R 语言编码的四个基本准则:

  1. 始终使用文本编辑器编写和保存代码。
  2. 始终缩进你的代码,建议至少使用 4 个空格
  3. 限制代码右侧的宽度,例如不超过 80 列
  4. 限制函数的大小,使其按程序的逻辑部分分组。

遵循这四个原则,你的代码将更具可读性,无论是对你自己还是对他人,都将使编写 R 代码对每个人都更有用。

059:R语言中的日期与时间处理 📅⏰

在本节课中,我们将学习R语言中日期与时间的基本概念、表示方法以及如何进行简单的计算和转换。日期和时间是数据分析中常见的数据类型,R提供了专门的类来处理它们,使得相关操作更加便捷和准确。


R中日期与时间的表示方式

上一节我们介绍了R中的基本数据类型,本节中我们来看看日期和时间是如何在R中表示的。R使用特殊的类来表示日期和时间,这不同于我们之前讨论过的列表、字符向量和数值向量等类型。

  • 日期由 Date 类表示。
  • 时间由两个独立的类表示:POSIXctPOSIXlt

日期本质上不附带时间信息,它表示某年某月中的某一天,可以理解为“年-月-日”的格式。例如,日期 1970-01-01 表示1970年1月1日。

在内部,日期存储为自 1970-01-01 起的天数。这个细节通常不重要,但有助于理解R如何进行日期计算。时间在内部存储为自 1970-01-01 起的秒数,这同样是一个底层细节,但有时了解它会很有用。


创建与转换日期对象

以下是创建和转换日期对象的常用方法。

最常见的方式是使用 as.Date() 函数将字符字符串转换为日期。例如,字符串 "1970-01-01" 可以被转换。

x <- as.Date("1970-01-01")
print(x)
# 输出:[1] "1970-01-01"

打印出来的对象看起来像字符串,但它实际上是一个 Date 类对象,因为有特殊的打印方法。如果使用 unclass() 函数查看其内部表示,会得到数字 0,因为这是自基准日期(1970-01-01)起0天的日期。

unclass(x)
# 输出:[1] 0

如果输入 1970-01-02,其内部表示将是数字 1。早于1970年的日期则用负数表示。用户通常无需担心底层表示,只需知道日期是 Date 类的对象即可。


时间类:POSIXct 与 POSIXlt

时间可以用两种类型表示。POSIX 是一套关于计算机如何表示数据的标准家族,POSIXctPOSIXlt 是其中用于表示时间的类。

  • POSIXct 类将时间存储为非常大的整数。这种类型便于在数据框等结构中存储时间,因为它本质上是一个大整数向量。
  • POSIXlt 类将时间底层存储为一个列表。它存储了关于给定时间的许多有用信息,例如星期几、一年中的第几天、月份等。

有一些通用函数可以操作日期和时间对象,例如:

  • weekdays():返回给定日期是星期几。
  • months():返回给定日期的月份。
  • quarters():返回季度(如Q1代表1-3月)。

这些函数可以用于 POSIXctPOSIXltDate 类的对象。


时间对象的操作与提取

可以使用 as.POSIXlt()as.POSIXct() 函数在 POSIXltPOSIXct 类型之间进行转换。

Sys.time() 函数可以获取系统当前时间,它返回一个 POSIXct 对象。

t <- Sys.time()
print(t)
# 输出格式类似:[1] "2023-01-23 10:15:11 EST"

可以将其转换为 POSIXlt 对象,因为 POSIXlt 底层是列表,所以可以提取列表中的元素。

p <- as.POSIXlt(t)
names(unclass(p))
# 输出包含:sec, min, hour, mday, mon, year, wday, yday, isdst

例如,提取秒数:

p$sec
# 输出:[1] 11.86

对于 POSIXct 对象,不能直接使用 $ 操作符提取列表元素。必须先将其转换为 POSIXlt 对象。

t_ct <- as.POSIXct(t)
# t_ct$sec  # 这会报错
as.POSIXlt(t_ct)$sec # 正确方式

使用 strptime 解析字符串

strptime() 函数可以将特定格式的字符字符串转换为日期或时间对象(通常是时间对象)。它使用格式字符串来指定输入字符串的样式。

例如,有以下字符串:

x <- c("January 10, 2012 10:40", "December 9, 2012 9:10")

使用 strptime() 进行转换:

y <- strptime(x, "%B %d, %Y %H:%M")
print(y)

格式字符串中的符号含义如下(更多信息请查阅 ?strptime 帮助页):

  • %B:非缩写月份名称。
  • %d:月份中的天数。
  • %Y:四位数的年份。
  • %H:小时(24小时制)。
  • %M:分钟。

转换后,y 是一个 POSIXlt 类对象。


日期与时间的运算

一旦数据是日期或时间格式,就可以对它们进行操作,这非常方便。例如,可以加减日期、比较日期。

需要注意的是,不能随意混合不同的类进行运算。例如,一个 Date 对象和一个 POSIXlt 对象相减会报错。

x <- as.Date("2012-01-01")
y <- as.POSIXlt("2012-01-02 10:00:00")
# x - y  # 错误
as.POSIXlt(x) - y # 正确,先转换类型

日期和时间操作符的优点在于,它们能自动处理一些复杂情况,例如闰年、闰秒、夏令时和时区。

  • 闰年示例:2012年是闰年,有2月29日。因此 2012-03-012012-02-28 相差2天,而非平年的1天。
  • 时区示例:比较两个不同时区的时间,系统会自动计算时区差,得出正确的时间间隔。

使用日期时间类可以自动处理这些不规则情况。


总结与拓展

本节课中我们一起学习了R语言中日期与时间处理的核心知识。

总结如下:

  1. R有特殊的类来表示日期和时间,以便进行数值和统计计算。
  2. 日期使用 Date 类。
  3. 时间使用 POSIXctPOSIXlt 类。
  4. 可以使用 strptime()as.Date()as.POSIXlt()as.POSIXct() 函数将字符串强制转换为日期或时间类。
  5. 日期时间对象支持算术和比较运算,并能自动处理闰年、时区等复杂情况。

此外,许多绘图函数能够识别日期时间对象。当绘制此类对象时,R会自动以特殊方式格式化坐标轴(尤其是X轴),使其具有时间刻度特性。建议尝试使用日期时间类进行绘图,观察图形的变化。

060:循环函数 lapply 🌀

在本节课中,我们将学习 R 语言中一些最强大的函数——循环函数。它们能让我们在交互式环境中,以极少的代码量高效地对一组对象执行循环操作。

上一节我们介绍了循环的基本概念,本节中我们来看看 R 中一组名为 apply 的函数族,它们能以更紧凑的方式实现循环功能。

核心循环函数介绍

R 中有几个关键的循环函数,它们的名字中通常都包含 apply

以下是主要的几个函数及其简要说明:

  • lapply:核心函数。对一个列表的每个元素应用指定函数,并返回一个列表。
  • sapplylapply 的简化版本。尝试简化 lapply 的输出结果(例如,将单值列表简化为向量)。
  • apply:对数组(如矩阵)的特定维度(行或列)应用函数。
  • tapply:对向量的子集应用函数。
  • mapplylapply 的多变量版本。

此外,还有一个非常有用的辅助函数:

  • split:将对象分割成子集。它本身不应用函数,但常与 lapplysapply 结合使用。

接下来,我们将深入探讨 lapply 函数的工作原理。

深入理解 lapply 函数

lapply 函数是循环函数族中的主力。它的核心思想是:你有一个对象列表,希望遍历这个列表,并对列表中的每个元素应用一个函数。

lapply 函数接受三个基本参数:

  1. x:一个列表。
  2. FUN:要应用的函数(或函数名)。
  3. ...:传递给函数 FUN 的其他参数。

如果 x 不是列表,lapply 会尝试将其强制转换为列表。如果无法转换,则会报错。

lapply 的工作流程可以用以下伪代码描述:

lapply(x, FUN, ...) 的简化过程:
1.  如果 x 不是列表,则尝试 as.list(x)。
2.  对列表 x 中的每个元素 i,计算 FUN(x[[i]], ...)。
3.  将所有结果收集到一个新列表中并返回。

关键点在于:lapply 总是返回一个列表。输入的对象会被强制转换为列表,而输出则一定是一个列表。

lapply 基础用法示例

让我们通过几个例子来理解 lapply 的用法。

示例 1:计算列表中元素的均值

首先,我们创建一个包含两个元素的列表:

x <- list(a = 1:5, b = rnorm(10))

现在,我们使用 lapply 计算列表中每个元素的均值:

lapply(x, mean)

执行后,我们将得到一个包含两个元素的新列表,其名称与原列表相同(ab),但每个元素的值是原向量计算出的均值。

示例 2:生成不同长度的随机数

在这个例子中,我们创建一个序列 1:4,并对每个元素应用 runif 函数(生成均匀分布随机数):

x <- 1:4
lapply(x, runif)

runif(n) 生成 n 个随机数。因此,这个调用会返回一个列表,其中第一个元素是长度为1的随机向量,第二个是长度为2的向量,依此类推。

示例 3:向函数传递额外参数

假设我们想生成介于 0 和 10 之间的均匀随机数,而不是默认的 0 到 1。我们可以通过 ... 参数将 minmax 参数传递给 runif 函数:

lapply(x, runif, min = 0, max = 10)

这样,生成的随机数就都在 0 到 10 的范围内了。

匿名函数在 lapply 中的应用

lapply 及相关函数大量使用匿名函数。匿名函数是没有名称的函数,可以直接在调用时定义。

假设我们有一个包含两个矩阵的列表:

x <- list(a = matrix(1:4, 2, 2), b = matrix(1:6, 3, 2))

我们想提取每个矩阵的第一列。虽然没有现成的函数直接做这件事,但我们可以用匿名函数轻松实现:

lapply(x, function(elt) elt[, 1])

这里,function(elt) elt[, 1] 就是一个匿名函数,它接受一个参数 elt(即列表中的每个矩阵),并返回该矩阵的第一列。这个函数仅在 lapply 调用期间存在。

sapply:简化 lapply 的结果

lapply 总是返回列表,但有时我们希望得到更简单的数据结构,比如向量或矩阵。sapply 函数就是为此设计的,它会尝试简化 lapply 的输出。

回顾之前计算均值的例子,lapply 返回了一个每个元素都是单个数字的列表。使用 sapply 可以将其简化为一个向量:

x <- list(a = 1:5, b = rnorm(10), c = rnorm(20, 1), d = rnorm(100, 5))
sapply(x, mean)

如果结果列表中每个元素的长度相同(例如都是长度为5的向量),sapply 可能会将其简化为一个矩阵。如果无法简化,sapply 将退回到返回一个列表。

请注意:直接对列表调用 mean 函数(如 mean(x))是无效的,会返回 NA 并产生警告,因为 mean 函数并非设计用于处理列表。

总结

本节课中我们一起学习了 R 语言中强大的循环函数,重点是 lapplysapply

  • lapply 是基础,它对列表的每个元素应用函数,并总是返回一个列表。
  • sapplylapply 的友好版本,它会尝试将输出简化为向量或矩阵,使结果更易于处理。
  • 我们可以通过 ... 参数向被应用的函数传递额外的参数。
  • 匿名函数允许我们在调用 lapply 时快速定义简单的操作,而无需预先定义命名函数。

掌握这些循环函数能极大地提高编码效率,让你用更简洁的代码完成复杂的数据操作任务。

061:循环函数 apply 🌀

在本节课中,我们将要学习R语言中的 apply 函数。这是一个用于在数组的维度上评估函数的循环函数。我们将了解它的工作原理、常见用法,以及它与 for 循环和其他专用函数的区别。


概述

apply 函数是另一个循环函数,用于在数组的维度上评估一个函数。通常,这个函数会是一个匿名函数,就像我们在 lapply 中展示的那样,或者也可以是一个已存在的函数,例如 mean。它通常用于对矩阵的行或列应用函数。当然,矩阵作为二维数组,是我们最常使用的数组类型。在R中,你可能也会遇到三维数组等,因此你可以将 apply 用于更通用的数组,例如计算一组矩阵的平均值。

需要注意的一点是,你可能会偶尔听到一种说法,即使用 apply 在某种程度上比使用 for 循环更好或更快。一般来说,这种说法并不正确。这在很久以前旧版本的S语言或R中可能是真的,但现在完全不是这样了。你希望使用 apply 这类函数的主要原因是它涉及更少的代码输入,而更少的输入总是更好,因为优秀的程序员总是很“懒”。因此,apply 在命令行交互中特别有用,因为在进行探索性数据分析时,我们希望尽可能减少输入,以免手指疲劳。


apply 函数的工作原理

apply 函数的基本语法如下:

apply(X, MARGIN, FUN, ...)
  • X:一个数组。数组是附加了维度的向量。例如,矩阵就是一个二维数组。
  • MARGIN:一个整数向量,指示应保留哪些维度。
  • FUN:你想要应用到每个维度上的函数。
  • ...:你想要传递给函数 FUN 的其他参数。

对矩阵的行和列应用函数

上一节我们介绍了 apply 的基本参数,本节中我们来看看如何用它处理矩阵。

假设我们创建了一个有20行和10列的矩阵,其元素是生成的随机正态变量。

x <- matrix(rnorm(200), 20, 10)

计算每列的平均值

如果我们想计算这个矩阵每一列的平均值,可以这样做:

apply(x, 2, mean)

这里,MARGIN = 2 表示我们希望保留第二个维度(列),而压缩第一个维度(行)。其思想是:对每一列,计算所有行的平均值,从而“消除”行这个维度。返回的结果是一个长度为10的向量,包含了每一列的平均值。

计算每行的总和

类似地,我们可以计算矩阵每一行的总和:

apply(x, 1, sum)

这里,MARGIN = 1 表示我们希望保留第一个维度(行),而压缩第二个维度(列)。返回的结果是一个长度为20的向量,包含了每一行的总和。


专用函数 vs. apply

对于计算行/列总和或平均值这类简单操作,R提供了高度优化的专用函数,执行速度比 apply 快得多。

以下是这些专用函数:

  • rowSums(x):计算每行的总和。
  • rowMeans(x):计算每行的平均值。
  • colSums(x):计算每列的总和。
  • colMeans(x):计算每列的平均值。

因此,如果你只想计算矩阵行或列的总和或平均值,请使用这些专用函数。


应用更复杂的函数

apply 的强大之处在于可以应用任何函数。例如,假设我们有一个20行10列的随机正态变量矩阵,我们想计算每一行的第25和第75百分位数。

x <- matrix(rnorm(200), 20, 10)
apply(x, 1, quantile, probs = c(0.25, 0.75))

这里,MARGIN = 1 表示按行操作。我们传递了 quantile 函数,并通过 ... 参数(即 apply 的第四个及之后的参数)将 probs = c(0.25, 0.75) 传递给了 quantile 函数。

这个调用会遍历矩阵的每一行,并为每一行计算第25和第75百分位数。对于每一行,会返回两个数字。apply 函数会创建一个矩阵来存放结果,这个结果矩阵的行数等于返回值的数量(2),列数等于原始矩阵的行数(20)。因此,我们会得到一个2行20列的矩阵,其中每一列对应原始矩阵一行的两个百分位数。


处理多维数组

前面我们主要处理的是二维矩阵,现在假设我们有一个多维数组。例如,创建一个包含随机正态变量的数组,其维度为2行、2列、10层(第三个维度为10)。你可以将其想象为一叠(10个)2x2的矩阵。

a <- array(rnorm(2 * 2 * 10), c(2, 2, 10))

如果我们想对这10个2x2矩阵求平均值,结果将是另一个2x2矩阵(即均值矩阵)。我们可以这样使用 apply

apply(a, c(1, 2), mean)

这里,MARGIN = c(1, 2) 表示我们希望保留第一个和第二个维度(即行和列),而压缩第三个维度。传递给 FUN 的函数是 mean。这个操作会对第三维(那10个矩阵)进行平均,最终给出均值矩阵。

另一种实现相同效果的方法是使用 rowMeans 函数,即使对象是数组:

rowMeans(a, dims = 2)

这里的 dims = 2 参数指定了要保留的维度数。


总结

本节课中我们一起学习了 apply 函数。我们了解到:

  1. apply 用于在数组的指定维度上应用函数。
  2. 其核心参数包括数组 X、指定维度的 MARGIN、要应用的函数 FUN 以及传递给函数的其他参数 ...
  3. 对于简单的行/列求和与求平均,应优先使用 rowSumscolMeans 等优化函数。
  4. apply 的真正优势在于其灵活性,可以方便地对行或列应用任何自定义或复杂的函数。
  5. 它同样适用于处理二维以上的多维数组。

通过掌握 apply,你可以在R中更高效、更简洁地处理需要对数据子集进行重复操作的任务。

062:循环函数 mapply 🌀

在本节课中,我们将学习 R 语言中的 mapply 函数。mapplylapplysapply 函数的多变量版本,它能够并行地对多组参数应用一个函数。我们将通过简单的例子来理解它的工作原理和用途。


概述:什么是 mapply?

mapply 是一个循环函数,它是之前学过的 lapplysapply 函数的多变量版本。其核心思想是并行地对多组不同的参数应用一个函数

我们注意到,之前的函数如 lapplysapplytapply 都只能对单个对象的元素应用函数。例如,lapply 的输入是一个列表,函数会应用于该列表的每个元素。

那么,如果你有两个列表,并且第一个列表的元素需要作为函数的第一个参数,第二个列表的元素需要作为函数的第二个参数,该怎么办呢?lapplysapply 无法直接处理这种情况。一种方法是编写一个 for 循环来索引每个列表的元素,然后将函数应用于这些元素。

然而,另一种更简洁的方法是使用 mapplymapply 可以接受多个列表作为参数,并并行地将函数应用于这些列表的对应元素上。


mapply 的函数参数

mapply 的函数参数略有不同,因为它需要允许可变数量的参数。

以下是 mapply 的主要参数:

  • 第一个参数:要应用的函数。这个函数必须至少接受与传递给 mapply 的列表数量一样多的参数。
  • ... 参数:将要被强制转换为列表的对象(即你的多个列表参数)通过 ... 传递。
  • MoreArgs 参数:如果你的函数还需要其他固定的参数,可以在这里传递。
  • simplify 参数:与 sapplytapply 中的 simplify 参数类似,用于控制结果的简化。

示例 1:使用 mapply 简化列表创建

假设我们需要创建一个列表,其内容为:数字 1 重复4次,数字 2 重复3次,数字 3 重复2次,数字 4 重复1次。

手动创建这个列表比较繁琐:

list(rep(1, 4), rep(2, 3), rep(3, 2), rep(4, 1))

使用 mapply 可以非常简洁地完成:

mapply(rep, 1:4, 4:1)

在这个例子中:

  • 函数 rep 有两个参数:要重复的对象 x 和重复次数 times
  • 我们传递了两组参数:第一组是 1:4,第二组是 4:1
  • mapply 会并行地应用 rep 函数:rep(1, 4)rep(2, 3)rep(3, 2)rep(4, 1),最终生成我们想要的列表。

示例 2:向量化非向量化函数

让我们看一个更实际的例子。假设我们有一个自定义函数 noise,用于生成指定数量、均值和标准差的正态分布随机数。

函数定义如下:

noise <- function(n, mean, sd) {
  rnorm(n, mean, sd)
}

如果使用一组参数调用,它能正常工作:

noise(5, 1, 2) # 生成5个均值为1、标准差为2的随机数

但是,如果我们尝试传递向量参数,它不会按预期工作:

noise(1:5, 1:5, 2)

我们期望的结果是:生成1个均值为1的随机数,2个均值为2的随机数,3个均值为3的随机数,以此类推。但直接传递向量无法实现这个效果。

这时,mapply 就派上用场了。我们可以用 mapply 来“即时向量化”这个函数:

mapply(noise, 1:5, 1:5, 2)

现在,mapply 并行地应用 noise 函数:

  1. noise(1, 1, 2) -> 1个随机数,均值1
  2. noise(2, 2, 2) -> 2个随机数,均值2
  3. noise(3, 3, 2) -> 3个随机数,均值3
  4. noise(4, 4, 2) -> 4个随机数,均值4
  5. noise(5, 5, 2) -> 5个随机数,均值5

这样就得到了我们期望的结果。这等同于手动写出包含这五个函数调用的列表,但使用 mapply 更加高效和简洁。


总结

本节课我们一起学习了 mapply 函数。

  • mapplylapply 系列函数中的多变量版本,用于并行地对多组参数应用一个函数
  • 它特别适用于需要将函数应用于多个列表或向量对应元素的情况。
  • 通过 mapply,我们可以轻松地将一个原本不支持向量化输入的函数进行“即时向量化”,从而避免编写复杂的循环代码。

掌握 mapply 能让你在处理需要多参数并行计算的任务时,写出更简洁、更高效的 R 代码。

063:循环函数 tapply 🧮

在本节课中,我们将要学习R语言中一个非常实用的函数——tapply。这个函数用于对向量的子集应用某个函数,是分组汇总数据的强大工具。

概述

tapply函数的核心思想是:你有一个向量(通常是数值型向量),并且你想对这个向量中属于不同组的元素分别计算某个汇总统计量。为此,你需要另一个长度相同的向量或因子来标识原始向量中每个元素属于哪个组。

tapply 函数的基本原理

上一节我们介绍了tapply的基本概念,本节中我们来看看它的具体语法和参数。

tapply函数的基本调用格式如下:

tapply(X, INDEX, FUN, ..., simplify = TRUE)
  • X:一个向量(通常是数值型),我们希望对它的子集进行计算。
  • INDEX:一个因子或列表,长度与X相同,用于定义X中元素的分组。
  • FUN:要应用于每个分组的函数(例如mean, sd)。
  • ...:传递给FUN函数的其他参数。
  • simplify:逻辑值,指示是否简化结果。如果为TRUE(默认值),tapply会尝试将结果简化为数组;如果为FALSE,则总是返回一个列表。

一个简单的示例

为了更好地理解,让我们通过一个具体例子来演示tapply的用法。

首先,我们创建一个包含三个分组的数值向量:

# 生成数据:10个标准正态、10个均匀分布、10个均值为1的正态
x <- c(rnorm(10), runif(10), rnorm(10, 1))

接下来,我们创建一个因子变量来标识每个元素所属的组:

# 创建一个因子,三个水平各重复10次
f <- gl(3, 10)
print(f)

现在,我们可以使用tapply来计算每个分组的平均值:

# 对每个分组计算均值
tapply(x, f, mean)

执行上述代码,你将得到三个数字,分别对应三个分组的平均值。

控制输出格式:simplify 参数

tapply函数的simplify参数控制着返回结果的格式。

如果你将simplify设置为FALSE,函数将始终返回一个列表:

# 返回列表格式的结果
tapply(x, f, mean, simplify = FALSE)

此时,输出是一个包含三个元素的列表,每个元素对应一个分组的平均值。

应用更复杂的汇总函数

tapply不仅可以计算简单的均值,还可以应用任何返回单个值或向量的函数。

以下是应用range函数(返回最小值和最大值)的示例:

# 对每个分组计算取值范围(最小值和最大值)
tapply(x, f, range)

在这个例子中,tapply会对x向量的每个分组应用range函数。由于range函数为每个分组返回一个包含两个值(最小值和最大值)的向量,因此最终输出会是一个列表,列表中的每个元素都是一个长度为2的向量。

总结

本节课中我们一起学习了tapply函数。我们了解到,tapply是用于对向量进行分组计算的核心函数。它的工作流程是:根据一个索引因子将数据向量分成若干子集,然后对每个子集独立地应用指定的函数。通过调整simplify参数,我们可以控制结果是简化的数组还是保留结构的列表。这个函数在数据分析和探索性数据分析中非常有用,可以快速计算不同组别的统计量。

064:split 函数详解 🧩

在本节课中,我们将学习 split 函数。split 函数本身并非循环函数,但它是一个非常有用的工具,常与 lapplysapply 等函数结合使用。它的核心功能是将一个向量或对象按照因子变量的不同水平拆分成多个部分。

split 函数的基本原理

split 函数类似于 tapply,但它不直接应用汇总统计。它的作用是接收一个对象 X 和一个因子变量 F,然后根据 F 中定义的组别将 X 拆分开来。

例如,如果因子 F 有三个水平,代表三个组,那么 split 函数就会将 X 拆分成三个组。

split(x, f)

一旦数据被拆分成组,你就可以使用 lapplysapply 对每个组应用特定的函数。

一个简单的示例

以下是一个简单的示例。我们模拟生成了三组数据:10个均值为0的正态分布随机数、10个均匀分布随机数,以及10个均值为1的正态分布随机数。我们创建一个因子变量来标识这三组数据,然后使用 split 函数拆分向量。

x <- c(rnorm(10), runif(10), rnorm(10, 1))
f <- gl(3, 10)
split(x, f)

运行后,split 会返回一个列表。列表的第一个元素包含10个正态随机数,第二个元素包含10个均匀随机数,第三个元素包含另外10个正态随机数。

split 函数总是返回一个列表。如果你想对这个列表进行操作,可以使用 lapplysapply

结合 lapply 使用 split

lapplysplit 结合使用是一种常见做法。其思路是先拆分数据,然后对拆分后的列表应用 lapply 函数。

虽然在这个例子中,使用 tapply 函数可以达到同样的效果,并且代码更简洁,但 split 函数的优势在于它可以拆分更复杂的对象类型。

拆分数据框的示例

上一节我们介绍了如何拆分向量,本节中我们来看看如何拆分更复杂的对象,比如数据框。

我们以 datasets 包中的 airquality 数据框为例。这个数据框包含臭氧、太阳辐射、风速和温度等变量的测量值,以及月份和日期信息。

假设我们想计算每个月内臭氧、太阳辐射和风速的平均值。我们可以先将数据框按月份拆分,然后对每个拆分后的月度数据框计算列均值。

以下是具体步骤:

  1. 使用 split 函数,根据 Month 变量拆分 airquality 数据框。
  2. 对拆分后的列表应用一个匿名函数,该函数计算指定列(臭氧、太阳辐射、风速)的均值。
s <- split(airquality, airquality$Month)
lapply(s, function(x) colMeans(x[, c("Ozone", "Solar.R", "Wind")]))

运行后,lapply 会返回一个列表,其中每个元素都是一个长度为3的向量,分别代表该月内臭氧、太阳辐射和风速的平均值。注意,由于原始数据中存在缺失值(NA),直接计算均值会得到 NA

我们可以使用 sapply 来简化输出结果,它会将列表转换为一个更紧凑的矩阵格式。

sapply(s, function(x) colMeans(x[, c("Ozone", "Solar.R", "Wind")]))

为了处理缺失值,我们可以在计算列均值时传入 na.rm = TRUE 参数。

sapply(s, function(x) colMeans(x[, c("Ozone", "Solar.R", "Wind")], na.rm = TRUE))

现在,我们得到了五个月份中三个变量观测值的均值矩阵。

split 函数非常方便,它可以根据因子的水平拆分任意对象,然后对拆分后的列表元素应用任何函数。

基于多个因子进行拆分

之前我们只使用了单个因子进行拆分,但有时你可能需要基于多个因子进行拆分。例如,你可能有一个性别因子(男/女)和一个种族因子,你想查看这两个因子所有水平组合下的数据。

以下是如何操作:

  1. 创建两个因子变量 f1f2
  2. 使用 interaction 函数将两个因子的所有水平组合起来,生成一个新的交互因子。
  3. 使用 split 函数,根据这个交互因子拆分数据向量。

实际上,在使用 split 函数时,你可以直接传入一个包含多个因子的列表,它会自动调用 interaction 函数。

x <- rnorm(10)
f1 <- gl(2, 5)
f2 <- gl(5, 2)
split(x, list(f1, f2))

拆分后,返回的列表会包含所有因子水平组合(即使某些组合没有观测值)。这些空的组合级别有时并不需要。

split 函数有一个 drop 参数。设置 drop = TRUE 可以删除拆分过程中产生的空级别。这在组合多个因子时非常有用,因为你通常不会观测到所有可能的组合。

split(x, list(f1, f2), drop = TRUE)

这样,返回的列表将只包含有观测值的组合级别。

总结

本节课中我们一起学习了 split 函数。我们了解到 split 函数可以根据一个或多个因子变量的水平,将向量或数据框等对象拆分成多个部分,并返回一个列表。这个列表可以方便地与 lapplysapply 等函数结合,对每个分组进行计算或分析。我们还学习了如何处理基于多个因子的拆分,以及如何使用 drop 参数来简化输出结果。split 是数据分组处理中一个非常实用的工具。

065:R语言调试工具 🐛

在本节课中,我们将学习R语言内置的调试工具。这些工具无需安装任何包,可以帮助我们在发现问题后,找出代码中的错误所在。

如何发现问题

在开始调试之前,我们首先需要知道问题已经发生。R语言会通过几种不同的指示来告知我们,这些指示大致可以分为三个等级。

以下是R语言中三种主要的指示类型:

  • 消息:这是一种非常温和的通知。它可能只是一个诊断信息,表明发生了某些事情,但通常不会影响程序运行。消息不会停止函数的执行,它只会被打印到屏幕上,然后函数会继续运行。
  • 警告:警告表明发生了预期之外的事情,但这不一定是致命问题。很多时候,我们甚至会选择忽略警告。当函数期望得到一种结果,却得到了略有不同的东西时,就可能触发警告。警告会在函数执行结束后出现,不会中断函数的执行过程。
  • 错误:错误是致命问题。它会立即停止函数的执行。错误信息由 stop() 函数产生。

此外,还有一个更高级的概念叫做“条件”。消息、警告和错误都属于“条件”。理论上,你甚至可以创建自定义的条件类型,但这超出了本课程的范围。

指示类型示例

上一节我们介绍了三种指示类型,本节我们通过具体例子来看看它们在实际中是什么样子。

警告示例

一个典型的警告例子是计算负数的对数。在R中执行 log(-1) 会返回 NaN(非数字),同时产生一条警告信息:“NaNs produced”。这种警告通常可以接受,例如当你对一组数字取对数,其中包含一些负数,但你并不在意时。

消息示例

这里有一个我创建的小函数:

printmessage <- function(x) {
    if(x > 0) {
        print(paste("x is greater than 0"))
    } else {
        print(paste("x is less than or equal to 0"))
    }
    invisible(x)
}

这个函数检查输入值 x。如果 x 大于0,则打印“x is greater than 0”;如果小于或等于0,则打印“x is less than or equal to 0”。函数最后使用 invisible(x) 返回输入值 x,但不会自动打印它。

invisible() 函数的作用是阻止自动打印。通常,在命令行执行一个函数时,R会自动打印函数返回的最后一个对象。使用 invisible() 后,对象仍会被返回,但不会自动显示在控制台上。load() 函数就是一个例子,它加载保存的工作空间对象,并返回加载的对象名称,但这个返回值是隐式的,不会打印出来。

现在,调用 printmessage(1),我们会看到消息“x is greater than 0”,并且数字1不会被打印出来。

错误示例

如果我们向 printmessage 函数传入 NA,会发生什么呢?执行 printmessage(NA) 会导致错误。因为表达式 if(x > 0) 无法处理 NA 值(NA 既不是 TRUE 也不是 FALSE),所以R会报错并停止执行。

调试的核心:对比预期与实际

很多时候,问题并不像错误那样明显。代码可能没有报错,但结果却不符合我们的预期。这正是调试的关键所在。

为了修复上面的错误,我创建了一个新函数 printmessage2,它首先检查输入是否为 NA

printmessage2 <- function(x) {
    if(is.na(x)) {
        print("x is a missing value!")
    } else if(x > 0) {
        print("x is greater than 0")
    } else {
        print("x is less than or equal to 0")
    }
    invisible(x)
}

现在,假设我计算 log(-1) 并将结果赋值给 x。虽然会得到一个警告,但程序会继续执行。然后我调用 printmessage2(x)。我原本期望看到“x is greater than 0”,但实际上却看到了“x is a missing value!”。这里没有错误,但结果与我的预期不符。这就是一个需要调试的典型场景。

调试时需回答的关键问题

当你怀疑函数有问题时,需要系统地回答以下几个问题,这能帮助你定位问题根源:

  • 你实际输入了什么? 仔细检查你传递给函数的实际输入值,而不是你以为的输入值。在上例中,我以为输入了一个正数,但实际上输入的是 NaN
  • 你是如何调用函数的? 回想调用函数时使用的具体参数。
  • 你期望得到什么输出? 清晰地表达你期望的结果至关重要,无论是向自己还是向他人求助时。你期望看到特定的消息,还是特定的数值结果?
  • 你实际得到了什么输出? 实际结果是什么?
  • 实际结果与期望结果有何不同? 对比两者之间的差异。
  • 你的期望本身是否正确? 有时,问题可能出在我们的预期上。我们可能错误地理解了函数的功能。
  • 你能重现这个问题吗? 这是调试中最关键的一点。你必须能够稳定地重现导致问题的步骤,否则将很难定位和修复它。对于涉及随机数的问题,使用 set.seed() 固定随机种子至关重要。虽然网络编程或并行计算中的问题可能难以重现,但对于大多数本地运行的问题,重现通常是可行的。

总结

本节课中,我们一起学习了R语言内置的调试工具。我们首先了解了R发出问题信号的三种方式:消息、警告和错误。然后,我们通过实例观察了它们的不同表现。更重要的是,我们探讨了调试的核心思路:当实际结果与预期不符时,如何通过一系列关键问题(如“输入是什么?”、“期望是什么?”、“能否重现?”)来系统地定位和解决问题。掌握这些基础概念,是成为高效调试者的第一步。

066:R语言调试基础工具 🐛

在本节课中,我们将学习R语言内置的五个核心调试函数。这些工具能帮助你定位和修复代码中的错误。

R语言提供了一系列内置函数来辅助程序调试。虽然许多R用户可以在很长一段时间内不使用这些工具,但在某些复杂情况下,它们会非常有用。

1. traceback 函数:追踪调用栈

上一节我们介绍了调试工具的存在,本节中我们来看看第一个基础工具。

traceback 函数用于打印出所谓的“函数调用栈”。通常,当你调用一个函数时,该函数可能调用另一个函数,如此层层深入。错误可能发生在调用链的深处。traceback 会告诉你当前处于第几层函数调用,以及错误发生在哪里,帮助你定位错误在哪个调用环节出现。

2. debug 函数:进入调试模式

接下来,我们看看最实用的工具之一。

debug 函数接受一个函数作为参数,并将该函数标记为调试模式。这意味着,无论何时何地执行该函数(即使是被其他函数调用),执行都会在函数的第一行暂停,进入“浏览器”模式。在此模式下,你可以逐行执行函数内的代码,一次执行一个表达式,从而精确定位发生错误的代码行。

3. browser 函数:在任意点暂停

browser 函数与 debug 相关,但提供了更灵活的控制。

你可以在代码中的任意位置插入 browser() 函数调用。当执行到该行时,函数会暂停,然后你可以从此处开始逐行执行。这在你不想从函数开头开始调试,而是希望运行到某个中间点再暂停检查时非常有用。

4. trace 函数:非侵入式调试

有时你需要调试他人编写的代码,例如R包或基础R中的函数,直接编辑源代码可能不方便。trace 函数就是为了解决这个问题。

trace 函数允许你在不实际编辑函数源代码的情况下,向函数中插入调试代码(通常就是插入一个 browser() 调用)。调试完成后,你可以关闭追踪,函数将恢复正常。

5. recover 函数:错误处理与检查

最后一个工具是 recover 函数,它与 traceback 相关,但功能更强大。

默认情况下,R遇到错误时会显示错误信息并停止执行,将控制权交还给控制台。recover 是一个错误处理函数,它可以改变这一行为。当设置 recover 后,一旦在函数执行中遇到错误,R解释器会在错误发生处暂停执行,并打印出函数调用栈。然后,你可以在浏览器模式下“跳转”到调用栈的不同层级进行检查,查看各层函数的环境和数据状态。

调试策略与思考

除了使用上述函数,一个简单直接的调试方法是在代码中插入 printcat 语句来输出变量的值。这种方法简单有效,很多问题仅靠它就能解决。

但其缺点是,在长代码中可能需要插入大量打印语句,调试完成后又需逐一删除,略显繁琐。此外,有观点认为过度依赖调试器(如逐行跟踪)可能会助长草率的编码习惯,因为开发者可能会倾向于先写代码,出了问题再依赖调试器去查找,而不是在编写时就仔细思考逻辑。

尽管如此,合理使用调试工具是高效编程的重要组成部分。作者本人就经常使用这些函数。


本节课中我们一起学习了R语言的五个核心调试工具:tracebackdebugbrowsertracerecover。它们分别用于追踪错误位置、逐行调试、在指定点暂停、非侵入式调试以及交互式错误检查。理解并适时运用这些工具,将极大提升你诊断和修复R代码问题的能力。

067:R语言调试工具使用指南 🔧

在本节课中,我们将学习R语言中几种关键的交互式调试工具,包括 tracebackdebugrecover。这些工具能帮助你在代码出错时,快速定位问题所在,理解错误发生的上下文。


概述:理解错误与调试

在编写R代码时,你可能会遇到三种指示问题的信号:消息(message)警告(warning)错误(error)。其中,只有错误会立即停止函数的执行。调试的核心在于能够复现问题,并清晰地描述你的预期结果与实际输出之间的差异。

上一节我们介绍了错误的基本概念,本节中我们来看看如何利用R内置的工具来追踪和诊断这些错误。


使用 traceback 追踪错误调用栈 🧵

traceback 函数用于显示导致错误的函数调用序列(即调用栈)。这对于理解错误在复杂的嵌套函数调用中发生在哪一层非常有用。

关键点:你必须在错误发生后立即调用 traceback()。如果之后执行了其他代码,traceback 将只显示最近一次的错误信息。

简单示例

以下是一个简单的错误示例。我们尝试计算一个不存在的对象 X 的均值。

mean(X)  # 错误:对象 'X' 未找到

调用 traceback() 后,输出可能非常简单,因为错误就发生在 mean 函数的最顶层,它没有调用其他函数。在这种情况下,traceback 的作用有限。

复杂示例

一个更有趣的例子是使用 lm(线性模型)函数。

lm(y ~ x)  # 错误:对象 'y' 未找到

调用 traceback() 可能会显示一个长达七层的调用栈。它揭示了 lm 函数内部如何通过一系列 evalmodel.frame 调用来尝试评估公式 y ~ x,最终在第七层因为找不到 yx 而失败。

何时有用:当你通过邮件等方式向他人求助时,提供 traceback() 的输出非常有用,因为它能清晰地展示错误发生的函数调用层次。


使用 debugbrowser 进行逐步调试 🔍

debug 函数允许你以“浏览器(browser)”模式逐步执行任何函数(无论是否由你编写)。这是一种强大的交互式调试方式。

启动调试

你可以使用 debug(lm) 命令来调试 lm 函数。之后,当你再次调用 lm(y ~ x) 时,R会立即进入调试模式。

进入调试模式后:

  1. R会首先打印出 lm 函数的完整代码体。
  2. 你会看到一个 Browse> 提示符,表示你已进入浏览器环境。

浏览器环境

在浏览器环境中:

  • 你的工作空间(环境)是该函数内部的局部环境。
  • 最初,环境中只包含函数的参数(例如,公式 y ~ x)和具有默认值的其他参数。

逐步执行

Browse> 提示符下,你可以:

  • n 然后回车,来逐行执行函数代码。
  • 一直按 n,直到执行到引发错误的那一行。此时,你会看到与之前相同的错误信息,但你现在确切知道错误发生在哪一行代码。
  • 你甚至可以在调试器中嵌套调试。例如,在执行到某个内部函数(如 model.frame)时,你可以调用 debug(model.frame),然后进入该函数的调试环境。这就像浏览器帧的堆栈,你可以深入多层进行探查。


使用 recover 作为错误处理程序 🛠️

recover 函数可以设置为全局的错误处理程序。当错误发生时,它不会直接停止并返回控制台,而是提供一个交互式菜单,让你选择进入调用栈中任意一层的环境进行查看。

设置 recover

使用 options 函数进行设置:

options(error = recover)

此设置对当前R会话有效,退出后失效。

使用示例

设置后,执行一个会出错的命令,例如读取一个不存在的文件:

read.csv("不存在的文件.csv")

错误发生后,你会看到一个菜单,列出了函数调用栈(类似于 traceback 的输出)。例如:

  1. read.csv
  2. read.table
  3. file (错误发生在此处,因为无法创建文件连接)

交互式探查

此时,你可以输入对应的数字(如 3),然后按回车,R会带你进入 file 函数调用时的环境。你可以查看当时各变量的状态,帮助你理解为何出错。之后,你可以退出当前层,选择进入其他层(如 2 进入 read.table 的环境)继续探查。

这对于在冗长的调用栈中精确定位问题非常有效。


总结与核心要点 📝

本节课中我们一起学习了R语言中三种主要的交互式调试工具:

  1. traceback()立即在错误后调用,用于查看导致错误的函数调用序列,便于理解和沟通问题上下文。
  2. debug()browser:允许你逐步执行函数代码,深入函数内部环境,精确找到错误发生的行,并支持嵌套调试。
  3. recover():通过 options(error = recover) 设置为全局错误处理程序。错误发生时提供一个调用栈菜单,允许你交互式地跳转到任意调用层检查环境状态。

核心概念:这些工具的关键在于其交互性(interactive)。它们能让你在控制台动态地探查代码状态。然而,必须记住,调试工具不能替代思考。在编写代码之前进行周密设计,始终比写完代码后完全依赖调试器来捕捉问题更为重要。

068:R语言中的str()函数 📊

在本节课中,我们将要学习R语言中一个极其重要的函数——str()函数。这个函数功能强大、用途广泛,能帮助我们快速查看任何R对象的内部结构。无论你是初学者还是有经验的用户,掌握str()函数都将极大地提升你探索和理解数据的能力。

什么是str()函数?

str()函数的核心目标是紧凑地显示R对象的内部结构。你可以将str理解为“structure”(结构)的缩写。它是一个非常简单的诊断函数,用途广泛,可以作为summary()函数的替代方案来使用。

当你想要查看一个对象是什么以及它包含什么内容时,通常会使用summary(),它非常有用。但str()是另一个特别适合紧凑显示大型列表(尤其是可能包含嵌套列表的列表)的函数。它的设计目标是为每个基本对象生成大约一行的输出。例如,如果你给它一个简单的向量,它会返回一行输出打印到控制台。

因此,str()函数的基本目标是回答一个问题:这个对象里有什么?

str()函数的基本用法演示

上一节我们介绍了str()函数的基本概念,本节中我们来看看它的具体用法。我们将通过启动R并演示几个例子来展示str()函数如何工作。

str()本身是一个函数,它接受一个对象作为参数,并且可以接受任何R对象。这意味着你甚至可以将str()应用于其他函数。

例如,我想知道lm()函数(线性模型函数)的参数是什么。

str(lm)

执行上述代码,它会给出lm函数的参数列表,这是一个非常简洁的摘要,显示了第一个参数是formula,第二个是data等。

同样,我可以查看ls()函数:

str(ls)

这会显示ls函数的参数。

查看数据对象

现在,让我们看看如何用str()查看数据对象。假设我生成一些正态分布的随机变量。

x <- rnorm(100, mean = 2, sd = 4)

你可以对x使用summary()函数,它会给出五数概括(最小值、第一四分位数、中位数、第三四分位数、最大值)和均值。

summary(x)

这能让你大致了解数据的范围和分布情况。

你也可以对x调用str()函数,它会给出更多信息。

str(x)

它会返回一行输出,告诉你x是一个数值向量,包含100个元素,并且会显示这个向量的前几个数字,让你对数据的样子有个概念。

应用于其他数据类型

str()函数可以应用于其他类型的向量。例如,我可以创建一个因子变量。

f <- gl(40, 10) # 生成一个有40个水平、每个水平重复10次的因子
str(f)

它会再次给出一行输出,告诉你这是一个因子,有40个水平,前四个水平被命名为1、2、3、4,然后显示这个因子的前几个元素都标记为水平“1”。

你也可以对因子调用summary(),输出会略有不同。summary()会给出每个不同水平中的元素数量,这个输出不如str()给出的那么紧凑。

查看数据框

str()函数也可以用于其他数据类型,比如数据框。让我们加载一个内置的数据集。

data(airquality) # 加载空气质量数据集

要查看airquality数据,我可以使用head()函数查看前六行,或者调用str()来获得不同的输出。

str(airquality)

它会告诉你这是一个数据框,有153个观测值(即153行)和6个变量。然后,它为每个变量提供一小段输出。例如,第一个变量叫Ozone,是整数类型,前几个观测值中就有一些NA(缺失值)。第二个变量叫Solar.R,也是整数类型,并显示前几个值。

这里的str()输出对于快速检查R中的数据以及不同R对象的结构非常有用。

查看矩阵和列表

我们可以更进一步。例如,我们可以创建一个小矩阵。

m <- matrix(rnorm(100), 10, 10) # 创建一个10行10列的矩阵,填充随机正态分布数
str(m)

它会给出更多信息。现在它知道这是一个矩阵,会说它是一个二维数组,有10行和10列,并显示前几个元素(你看到的是第一列)。

最后,我将使用split()函数创建一个小列表,看看str()如何查看列表并给出紧凑的摘要。

s <- split(airquality, airquality$Month) # 按月份拆分空气质量数据框
str(s)

现在你会看到一个输出。这是一个包含五个不同数据框的列表,每个数据框对应一个特定月份的数据(数据只收集了五个月,所以只有五个元素)。例如,对于5月(Month 5),有31个观测值和6个变量,并显示数据的大致样子。对于6月,有30个观测值和同样的6个变量,以此类推。

对于这种拆分后的列表,str()提供了一个非常简洁的摘要,虽然不是最紧凑的,但已经尽可能紧凑了。它能让你快速浏览数据,检查是否存在问题(比如缺失值),并对下一步该做什么有个概念。

总结

本节课中我们一起学习了R语言中可能是最有用的函数——str()函数。我们了解到:

  1. str()函数用于紧凑地显示任何R对象的内部结构
  2. 它可以应用于函数、向量、因子、数据框、矩阵和列表等各种对象。
  3. summary()相比,str()的输出通常更紧凑,尤其适合查看大型或嵌套的列表结构
  4. 它的输出能快速回答“这个对象里有什么?”这个问题,帮助你理解数据的类型、维度和内容预览。

我鼓励你在任何时候,当你有一个R对象但不知道里面有什么时,就使用str()函数。它是一个强大的工具,能让你更高效地进行数据探索和分析。

069:模拟生成随机数 🎲

在本节课中,我们将学习如何在 R 语言中进行模拟,特别是如何从各种概率分布中生成随机数。模拟是统计学及其他许多应用领域中非常重要的技术。我们将介绍 R 中用于模拟的核心函数,并解释如何控制随机数生成以确保结果的可重复性。

模拟与概率分布函数

上一节我们介绍了模拟的重要性。本节中我们来看看 R 语言中用于从给定概率分布生成随机数或变量的几个函数。其中最重要的分布之一是正态分布。

我们可以通过指定分布的均值和标准差,然后调用 rnorm 函数来生成正态分布的随机变量。

# 生成10个标准正态分布随机数
rnorm(10)
# 生成10个均值为20,标准差为2的正态分布随机数
rnorm(10, mean = 20, sd = 2)

对于正态分布,还有对应的函数用于评估概率密度、累积分布函数和分位数函数。

另一个用于生成随机变量的函数是 rpois 函数,它可以从具有给定速率的泊松分布中生成泊松随机变量。

R 中有许多函数可以从标准概率分布中生成随机变量,你可以使用它们来运行模拟。

概率分布函数的四种类型

每个概率分布函数基本上都关联着四种类型的函数。对于任何给定的分布,例如正态分布,都会有一个以 d 开头的函数、一个以 r 开头的函数、一个以 p 开头的函数和一个以 q 开头的函数。

以下是每个分布对应的四种不同函数:

  • rnorm 函数用于随机数生成。
  • dnorm 函数用于评估给定均值和标准差下的概率分布密度。
  • pnorm 函数用于评估累积分布。
  • qnorm 函数用于评估分位数函数。

每个分布都有这四种类型的函数。例如,对于 Gamma 分布,会有 dgammargammapgammaqgamma 函数。对于泊松分布,则有 rpoisdpoisppoisqpois 函数。

正态分布函数详解

处理正态分布需要用到这四种函数:dnormpnormqnormrnorm。每个函数都接受多个参数。

所有函数都要求你指定均值和标准差,因为这是定义实际概率分布所必需的。如果不指定,默认值是标准正态分布,其均值为 0,标准差为 1。

对于 dnorm 函数,你可以评估密度,并且有一个选项允许你评估密度的对数值。大多数时候,当你评估正态分布的密度函数时,你会希望使用该值的对数,但默认值是 FALSE

对于 pnormqnorm 函数,也有一个选项可以在对数尺度上进行评估。另一个选项是评估分布的下尾还是上尾。下尾是默认值,可以理解为概率分布向左的部分。如果你想评估上尾,有时需要这样做,那么你应该设置 lower.tail = FALSE,这将评估分布的上尾。

最后,对于 rnorm,只有两个参数 meansd,以及 n,即你想要生成的随机变量的数量。如果 n 是 100,你将得到一个包含 100 个从正态分布中抽取的数字的向量。

更明确地说,如果 Φ 是标准正态分布的累积分布函数,那么 pnorm 等价于 Φ,而 qnorm 等价于 Φ 的逆函数。

设置随机数种子

无论出于何种目的从任何分布模拟随机数时,设置随机数生成器种子都非常重要,这可以通过 set.seed 函数完成。

重要的是要了解,在计算机上生成随机数时,这些数字实际上并不是随机的,但它们看起来是随机的,这一点很重要。其思想是,如果你想再次生成同一组随机数,你可以做到,因为这些数字实际上并不是随机的,它们被称为伪随机数。

以下是设置种子的方法:

# 设置随机种子为1
set.seed(1)
# 生成5个随机数
rnorm(5)
# 再次设置种子为1,会得到完全相同的5个随机数
set.seed(1)
rnorm(5)

种子可以是任何你想要的整数,你只需传递一个整数作为种子。设置种子后,它会设定将要发生的随机变量序列。如果你重置种子,你会将序列设置回开始的地方,然后它将从那里继续生成随机变量。

这很重要,因为它允许你重现现在生成的随机数。这听起来可能很奇怪,因为你为什么想要两次生成相同的随机数?但在许多应用中,你确实希望两次生成相同的随机数,以便其他人可以重现你所做的事情,特别是在你所做的事情存在一些错误或问题时,你希望能够回到产生这些问题的确切情况。

因此,每当你进行模拟时,你都应该设置随机数种子,以便你可以返回并获得相同的结果。

其他分布的模拟示例

我已经演示了如何生成正态随机变量,当然你也可以从其他概率分布生成随机变量。

泊松分布当然非常流行。以下是从不同速率的泊松分布生成随机变量的例子:

# 生成10个速率为1的泊松随机变量
rpois(10, lambda = 1)
# 生成10个速率为2的泊松随机变量
rpois(10, lambda = 2)
# 生成10个速率为20的泊松随机变量
rpois(10, lambda = 20)

对于泊松分布,均值将等于速率,你可以大致在这三种情况中看到,均值大致等于我指定的速率。

我也可以评估泊松分布的累积分布函数。例如,在第一个例子中,我想知道如果速率为 2,泊松随机变量小于或等于 2 的概率是多少,这个概率大约是 0.67。

# 评估累积分布概率
ppois(2, lambda = 2) # P(X <= 2)
ppois(4, lambda = 2) # P(X <= 4)
ppois(6, lambda = 2) # P(X <= 6)

累积分布函数允许你评估这些概率。

总结

本节课中我们一起学习了在 R 语言中进行模拟的基础知识。我们介绍了如何从正态分布和泊松分布等常见概率分布中生成随机数,并详细解释了与每个分布关联的四种核心函数:r(生成)、d(密度)、p(累积分布)和 q(分位数)。最重要的是,我们强调了使用 set.seed 函数设置随机数种子的必要性,以确保模拟结果的可重复性,这是进行可靠数据分析的关键步骤。

070:模拟线性模型 📊

在本节课中,我们将学习如何使用 R 语言模拟生成来自线性模型和广义线性模型的数据。我们将从简单的线性模型开始,逐步过渡到包含二元预测变量和泊松分布响应的更复杂模型。

概述

上一节我们讨论了如何从简单的概率分布中模拟随机数。本节中,我们来看看如何从指定的统计模型中模拟数据,例如线性模型。我们将学习如何设置参数、生成预测变量和误差项,并最终合成响应变量。

模拟简单线性模型

首先,我们考虑一个简单的线性模型。该模型包含一个连续型预测变量 X 和一个正态分布的随机误差项 ε。模型的公式如下:

y = β₀ + β₁ * x + ε

其中,我们假设截距 β₀ = 0.5,斜率 β₁ = 2,误差项 ε 服从均值为 0、标准差为 2 的正态分布。

以下是模拟该模型数据的步骤:

  1. 设置随机种子以确保结果可重现。
  2. 生成预测变量 x,假设其服从标准正态分布。
  3. 生成误差项 ε,服从 N(0, 2²) 分布。
  4. 根据线性模型公式计算响应变量 y
set.seed(20)
x <- rnorm(100)
epsilon <- rnorm(100, 0, 2)
y <- 0.5 + 2 * x + epsilon
summary(y)
plot(x, y)

生成的 y 均值约为 0.68,范围大约在 -6 到 6 之间。绘制 xy 的散点图,可以清晰地看到它们遵循我们设定的线性关系。

模拟预测变量为二元数据的线性模型

现在,我们对上一个例子稍作变化。如果预测变量 x 不是连续型,而是一个二元随机变量(例如代表性别或实验组/对照组),该如何模拟?

这非常简单。我们可以使用二项分布(rbinom 函数)来生成二元数据。假设 x 来自一次伯努利试验,取值为 1 的概率 p = 0.5。

以下是具体步骤:

  1. 设置随机种子。
  2. 使用 rbinom 生成 100 个二元预测变量 x
  3. 生成正态误差项 ε
  4. 根据相同的线性模型公式计算连续型响应变量 y
set.seed(20)
x <- rbinom(100, 1, 0.5)
epsilon <- rnorm(100, 0, 2)
y <- 0.5 + 2 * x + epsilon
summary(y)
plot(x, y)

此时 y 的均值约为 1.4,范围大约在 -3 到 7 之间。由于 x 是二元的,散点图会呈现为两组垂直分布的点,但仍能清晰地看出从 x=0x=1y 的均值存在线性上升的趋势。

模拟泊松广义线性模型

最后,我们模拟一个更复杂的模型——广义线性模型,例如响应变量为计数数据的泊松回归模型。此时误差分布不再是正态分布,而是泊松分布。

我们假设响应变量 y 服从泊松分布,其均值 μ 的对数与预测变量 x 呈线性关系。模型公式如下:

log(μ) = β₀ + β₁ * x
y ~ Poisson(μ)

其中,假设 β₀ = 0.5β₁ = 0.3

模拟此模型需要以下步骤:

  1. 设置随机种子。
  2. 生成连续型预测变量 x(例如标准正态分布)。
  3. 计算线性预测值 log(μ)
  4. 通过指数运算得到泊松分布的均值 μ
  5. 使用 rpois 函数,以 μ 为参数生成泊松分布的响应变量 y
set.seed(20)
x <- rnorm(100)
log.mu <- 0.5 + 0.3 * x
mu <- exp(log.mu)
y <- rpois(100, mu)
summary(y)
plot(x, y)

生成的 y 均值约为 1.5,范围在 0 到 6 之间。散点图显示数据为典型的计数数据,并且随着 x 的增加,y 的计数也呈现上升的线性趋势。

总结

本节课中,我们一起学习了在 R 语言中模拟不同统计模型数据的方法。我们从基础的简单线性模型入手,探讨了预测变量为二元数据的情形,最后扩展到响应变量为计数数据的泊松广义线性模型。核心在于理解模型的数据生成过程:设定参数、生成预测变量、根据模型结构(包括链接函数)计算线性部分,最后加上或转换出特定分布的随机误差,从而得到最终的响应变量。掌握这些模拟技术对于理解模型本质和进行统计实验至关重要。

071:R语言模拟与随机抽样基础 🎲

概述

在本节课中,我们将学习R语言中用于模拟随机数据和抽样的核心函数。我们将重点介绍sample()函数,它允许你从指定的对象集合中随机抽取样本,并回顾其他用于从特定概率分布生成随机数的函数。


sample()函数:从指定集合中随机抽样

上一节我们介绍了R语言中用于生成特定分布随机数的函数。本节中,我们来看看sample()函数,它允许你从一组你指定的特定对象中随机抽取样本。

如果你提供一个数字向量,sample()函数允许你从该向量中抽取随机样本。通过指定一个对象向量并从中抽样,你可以创建任意自定义的分布。

以下是sample()函数的基本用法示例:

  • 示例1:从数字中抽样(无放回)

    # 从整数1到10中随机抽取4个,不重复(无放回)
    sample(1:10, 4)
    # 输出可能是:3, 4, 5, 7
    

    再次运行会得到不同的结果,例如3, 9, 8, 5。在这个例子中,由于是无放回抽样,不会得到重复的数字。

  • 示例2:从字母中抽样

    # 从字母A到Z中随机抽取5个
    sample(letters, 5)
    # 输出可能是:Q, B, E, X, P
    

    你不必只抽样数字,也可以抽样字母等其他对象。

  • 示例3:生成随机排列

    # 如果不指定抽样数量,`sample()`会返回整个向量的一个随机排列(洗牌)
    sample(1:10)
    # 输出是1到10的一个随机顺序排列
    

    再次调用会得到另一个不同的排列。

  • 示例4:有放回抽样

    # 从1到10中抽取10个数,允许重复(有放回)
    sample(1:10, replace = TRUE)
    # 输出可能包含重复值,例如某个数字出现多次
    

    通过设置replace = TRUE参数,可以进行有放回抽样,此时样本中可能出现重复元素。


R语言中的其他模拟函数

除了sample()函数,R语言还内置了从各种标准概率分布生成随机数的函数。

以下是常用的随机数生成函数:

  • rnorm(): 从正态分布生成随机数。
  • rpois(): 从泊松分布生成随机数。
  • rbinom(): 从二项分布生成随机数。
  • 其他如指数分布、伽马分布等也都有对应的函数(如rexp(), rgamma())。

这些函数对于模拟符合特定统计模型的数据非常有用。


重要实践:设置随机种子

在R中进行任何数据模拟时,非常重要的一点是使用set.seed()函数设置随机数生成器的种子。

这样做的目的是确保你的模拟结果可以重现。在以后的日子里,你可以通过使用相同的种子值,得到完全相同的随机序列。

# 设置随机种子,例如设为123
set.seed(123)
# 然后运行你的抽样或模拟代码
sample(1:10, 3)
# 无论何时运行,只要种子相同,输出就固定不变

总结

本节课中我们一起学习了R语言中用于模拟和抽样的关键工具。

我们详细探讨了sample()函数,它能够从任意指定的向量中进行随机抽样,支持无放回和有放回两种模式。我们还回顾了其他用于从标准概率分布(如正态分布、二项分布)生成随机数的R函数。

最后,我们强调了使用set.seed()函数来确保模拟结果可重现的重要性,这是进行可靠数据分析的基础步骤。

072:R 性能分析器(第一部分)🔍

在本节课中,我们将要学习 R 语言中一个非常实用的工具——性能分析器。当您开发大型程序或进行大规模数据分析时,如果代码运行时间过长,分析器可以帮助您精确找出耗时原因,并提供优化策略。我们还将介绍其他辅助工具,如 system.time() 函数,它们共同构成了优化代码的强大工具箱。

为什么需要性能分析器?🤔

上一节我们提到了性能分析器的用途,本节中我们来看看具体在什么情况下需要使用它。

当您编写的代码片段在单独运行时表现良好,但一旦被嵌入到一个更大的程序中,并被重复执行成千上万次时,其速度问题就会变得非常明显。此时,性能分析器就能派上用场,帮助您定位并优化那些在迭代中拖慢整体速度的代码部分。

代码优化的基本原则 ⚖️

在深入使用工具之前,了解代码优化的基本原则至关重要。一个普遍的准则是:不要过早优化

您的首要任务应该是编写出可运行、可读性强且易于他人理解的代码。因为通常很难凭直觉判断程序究竟在哪里耗费了最多时间。如果不进行正式的性能分析,盲目优化可能会在代码正常工作之前就引入错误。正如那句名言所说:“过早优化是万恶之源。”

一旦您确定需要优化代码,就应该像科学家一样,先收集数据。性能分析正是收集这些数据的方法。

基础计时工具:system.time() ⏱️

在介绍性能分析器之前,我们先来看一个更简单的工具:system.time() 函数。它虽然不是分析器,但能帮助您对代码段进行基础计时。

system.time() 函数接受一个任意的 R 表达式,执行它,并返回执行所花费的时间。这个表达式可以很简单,也可以很复杂。

以下是 system.time() 的基本用法:

system.time({
  # 您的 R 代码放在这里
  for(i in 1:1000) {
    x <- rnorm(100)
  }
})

理解两种时间概念

使用 system.time() 时,需要理解两个重要的时间概念:

  1. 用户时间:CPU 用于执行该表达式所花费的时间。
  2. 流逝时间:您实际感受到的、从开始到结束所经过的“挂钟时间”。

通常,这两个时间值比较接近。但在某些情况下,它们会有显著差异:

  • 流逝时间 > 用户时间:当程序花费大量时间等待外部资源时(例如从网络读取数据),CPU 实际处理代码的时间很短,但您需要等待更久。
    # 示例:读取网页,大部分时间在等待网络
    system.time(readLines("http://www.jhsph.edu"))
    
  • 流逝时间 < 用户时间:当程序能够利用计算机的多个核心时。例如,R 中某些线性代数库(如 BLAS)是多线程的,可以并行计算。虽然 CPU 总工作时间(用户时间)更长,但由于任务被分摊到多个核心同时进行,您感受到的等待时间(流逝时间)反而更短。
    # 示例:利用多核进行矩阵计算
    hilbert <- function(n) {
      i <- 1:n
      1 / outer(i - 1, i, "+")
    }
    system.time(svd(hilbert(500)))
    

system.time() 的局限性

system.time() 非常实用,但它有一个前提:您需要知道该对哪部分代码进行计时。它假设您已经大致定位了可能的性能瓶颈。

这对于小型或结构简单的程序可能足够。但如果面对一个庞大、复杂的程序,您完全不知道问题出在哪里,又该从何下手计时呢?这时,我们就需要更强大的工具——性能分析器。


本节课中我们一起学习了性能分析的必要性、代码优化的基本原则,以及如何使用 system.time() 函数对代码段进行基础计时。我们了解到,system.time() 虽然有用,但在定位未知的性能瓶颈时存在局限。在下一节课中,我们将深入探讨 R 的性能分析器,学习如何系统地剖析整个程序的运行时间分布。

073:R 性能分析器(第二部分)🔍

在本节课中,我们将深入学习 R 语言中的性能分析工具 Rprof。我们将了解它的工作原理、如何解读其输出,以及如何利用 summaryRprof 函数来识别代码中的性能瓶颈。


Rprof 函数简介

上一节我们介绍了性能分析的基本概念,本节中我们来看看 R 语言内置的性能分析器 Rprof

Rprof 是 R 语言中的一个函数,用于启动性能分析器。需要注意的是,R 必须是在编译时启用了性能分析器支持的情况下构建的。不过,在绝大多数情况下(约 99.9%),R 都默认包含此支持。只有在一些非常特殊的情况下,R 才会在没有性能分析器支持的情况下编译。因此,你的 R 版本很可能可以使用性能分析器。

另一个有用的函数是 summaryRprof。它接收性能分析器的原始输出,并将其汇总成一种可读性更强的格式,因为原始的 Rprof 输出通常不易直接使用。


重要注意事项

在使用性能分析工具时,有一个重要的原则需要牢记。

你不应该同时使用 system.time 函数和 Rprof 函数。这两个工具并非设计为协同工作。因此,你应该只选择其中一种使用,而不是同时使用两者。


Rprof 的工作原理

Rprof 函数的基本工作原理是,在固定的采样时间间隔内,跟踪函数调用栈的状态。

具体来说,当你的函数正在运行时,分析器会定期查询函数调用栈。它会记录下哪些函数调用了哪些其他函数,并在每个采样点打印出整个调用栈。它所做的就是在非常短的时间间隔(大约每 0.02 秒)打印一次函数调用栈。

首先你会注意到,如果你的函数运行时间少于 0.02 秒,那么性能分析器将毫无用处,因为它永远无法对函数调用栈进行采样。一般来说,如果你的程序运行得非常快,性能分析器就不太有用。当然,如果你的程序运行得很快,你可能一开始就不会想到要使用性能分析器,所以这通常不是问题。但你确实需要在代码运行时间较长(至少达到秒级)的情况下使用性能分析器。


原始输出示例

以下是性能分析器原始输出的一个简单示例。一般来说,你永远不会直接使用这个输出,但了解一下它的内容可能很有趣。

"lm" "eval" "eval" "model.frame" "model.frame.default" "eval" "list"
"lm" "lm.fit"

你可以看到,输出的每一行都是一个函数调用栈。最右边可以看作是栈顶,最左边是栈底。例如,从右往左看,lm 被调用,lm 调用了 evaleval 又调用了 eval,接着调用了 model.framemodel.frame 调用了 model.frame.default,后者又调用了 eval,然后是 list。所有这些函数相互调用,因此你可以看到函数调用栈的深度相当可观。

随着评估的深入,函数调用栈会发生变化。在最底部,你可以看到 lm 调用了 lm.fit。如果你不熟悉 lm 函数,lm.fit 实际上是执行所有真正计算工作的核心函数。因此,你会预计程序会在 lm.fit 函数中花费相当多的时间。


使用 summaryRprof 汇总数据

由于那种原始输出不易阅读,我们使用 summaryRprof 函数来整理 Rprof 的输出,并计算在每个函数中花费了多少时间。

其原理是,一旦你看到函数调用栈,你就知道调用栈的每一行都间隔了 0.02 秒,因为这是采样的频率。基于此,你可以计算在每个函数中花费了多少秒,因为如果一个函数出现在调用栈中,那么你一定在其中花费了一些时间。

summaryRprof 提供了两种数据标准化方法:

  • by.total:将每个函数花费的时间除以总运行时间。
  • by.self:做同样的事情,但首先减去在调用栈中更底层函数所花费的时间。

理解 by.totalby.self 这两个独立的概念非常重要。

by.total 的基本思想是,通过函数花费的总时间进行标准化,可以告诉你该函数在打印输出中出现了多少次。例如,你 100% 的时间都花在顶层函数上。假设你调用了 lm,那么你 100% 的时间都花在这个函数上,因为它是顶层函数。

但现实情况是,顶层函数通常并不执行真正重要的操作,它们所做的只是调用执行实际工作的辅助函数。因此,如果你的函数花费大量时间做某事,那很可能是在那些被顶层函数调用的辅助函数中。所以,了解在顶层函数中花费了多少时间通常并不有趣,因为真正的工作并不在那里发生。你真正想知道的是,在减去它调用的所有底层函数的时间后,顶层函数本身花费了多少时间。如果结果发现,即使在减去所有底层函数的时间后,顶层函数仍然花费了大量时间,那才是有趣的事情。但大多数时候你会发现,当你减去所有被调用的底层函数的时间后,顶层函数本身花费的时间非常少,因为所有的工作和计算都是在底层函数中完成的。所以,这才是你想要集中精力优化的地方。

因此,我认为 by.self 格式是最有趣的格式,因为它告诉你在给定函数中花费了多少时间,但减去了它调用的所有底层函数所花费的时间。这能更准确地描绘出哪些函数真正占用了最多时间,以及哪些函数可能是你后续想要优化的目标。


输出格式示例

以下是 by.total 格式的一些输出示例。

$by.total
                     total.time total.pct self.time self.pct
"lm"                        7.41     100.0      0.00      0.0
"lm.fit"                   3.52      47.5      3.52     47.5
"model.frame"              1.55      20.9      0.00      0.0
"model.frame.default"      1.55      20.9      0.14      1.9
...

你可以清楚地看到,最顶部显示 100% 的时间都花在了 lm 函数上。这次运行的总时间是 7.41 秒,当然所有时间都花在了 lm 上,因为 lm 是顶层函数。第二名是 lm.fit 函数,我提到过 lm.fit 是进行大量计算的地方,它花费了 3.52 秒,大约占总时间的一半。你还可以看到这里有许多其他函数,如 model.framemodel.frame.defaulteval 等。这些函数并不真正涉及计算,但在这些函数中也花费了相当多的时间,这有点意思。

我认为更有用的输出是 by.self 输出,它减去了所有底层函数的调用,计算了真正花费在给定函数上的时间。

$by.self
                     self.time self.pct total.time total.pct
"lm.fit"                   3.52     47.5       3.52      47.5
"as.list.data.frame"       0.82     11.1       0.82      11.1
"model.matrix.default"     0.64      8.6       0.64       8.6
...

在这里,你可以看到 lm.fit 是明显的赢家,因为那才是所有计算真正发生的地方。具体来说,lm.fit 调用了一个 Fortran 例程来求逆矩阵,这通常是在大多数大规模回归问题中所有计算发生的地方。

下一个花费大量时间的函数(约 11%)是 as.list 函数或其 as.list.data.frame 方法。目前尚不清楚为什么在这个函数中花费了这么多时间,但这可能是你想要调查的事情,因为它可能不是核心计算中非常重要的部分。你可以顺着列表往下看,了解在各个函数中花费了多少时间。你会发现很多这些函数并不直接与计算或核心计算相关,而更多地与数据格式化等有关。

summaryRprof 输出的最后一部分是采样间隔,你可以看到打印函数调用栈的采样时间间隔(这里是 0.02 秒),以及采样时间,也就是表达式运行的总时间。我认为这相当于 system.time 函数中的“elapsed”时间。


使用分析器的策略与局限

以上是对 R 中 Rprof 性能分析器的快速介绍。它是一个对 R 代码进行性能分析非常有用的工具,能提供一些有用的反馈。我经常发现它能突出显示一些你可能没有怀疑过的、消耗大量时间或成为瓶颈的函数,因为这些函数可能并非你正在进行的真正计算的核心。因此,分析器在突出显示这类情况以及发现一些意想不到的事情方面非常有用。

summaryRprof 函数汇总了 Rprof 的输出,并给出了在每个函数中花费的时间百分比。我认为 by.self 这种标准化类型对于突出代码中的瓶颈最为有用。

使用性能分析器的一个启示是,将你的代码分解成函数是有用的。与其拥有一个庞大的函数,不如将代码分解成逻辑上不同的部分,形成不同的函数。这样,分析器就可以利用这些信息告诉你时间花在了哪里。请记住,分析器会打印函数调用栈。如果你将代码分解成多个小函数,你赋予的函数名称将在调用栈中作为小标识符,告诉你代码在哪里花费了最多时间。这是在分析 R 代码时另一个有用的小策略。

最后值得注意的是,如果你的 R 代码(或任何其他 R 代码)调用了 C 或 Fortran 代码,这些 C 和 Fortran 代码就像一个黑盒,不会被分析。你不会看到关于这些代码的任何信息,你只知道在那里花费了一些时间,但不会知道任何细节。


总结

本节课中我们一起学习了 R 性能分析器 Rprof 的核心用法。我们了解到:

  1. Rprof 通过定期采样函数调用栈来工作。
  2. summaryRprof 函数能将原始数据转换为可读的汇总信息。
  3. by.self 时间能更准确地反映函数自身的开销,是定位瓶颈的关键。
  4. 将代码模块化为多个函数有助于分析器提供更清晰的洞察。
  5. 分析器无法分析 C 或 Fortran 等外部代码。

总的来说,我认为性能分析器非常有用。我鼓励你使用它,而不是仅仅猜测在哪里优化代码。分析器可以用来收集关于时间花费在哪里的数据,从而进行有针对性的优化。

074:获取数据的动机 🎯

在本节课中,我们将要学习《获取数据》这门课程的核心动机与前提条件。这门课程是数据科学系列中较为独特的一门,旨在帮助你理解为何以及如何为数据分析准备数据。

课程概述

这门课程涵盖了为数据分析准备数据的基本思想。在机器学习或统计学课程中,这一步常常被跳过,它们通常假设数据已经以整洁的格式准备就绪。本课程则深入探讨了从各种原始形式(如数据库、原始图像文件等)中寻找数据,并将其提取出来的具体细节。同时,课程也关注整洁数据原则,将那些格式可能不佳的复杂原始数据,整理成易于在数据分析中使用的整洁格式。

课程还通过一系列R包来介绍实际应用,我们将讨论许多不同的R包。

前提条件

本课程假设你已经学习了《数据科学家工具箱》并安装了所有相关工具,同时也学习了《R语言编程》或类似课程,以确保你具备足够的R编程基础,不会感到困难。

如果你还学习过一些探索性数据分析,以及《报告与可重复性研究》课程,将会更有帮助。探索性数据分析之所以相关,是因为在数据清理过程中你可能需要绘制一些图表。而报告与可重复性研究课程则涵盖了创建能复现分析的脚本,这是数据清理的一个重要组成部分。不过,这些并非学习本课程的硬性要求。

理想数据与现实数据

上一节我们介绍了课程的目标,本节中我们来看看数据在理想和现实中的不同形态。

理想中的数据看起来像这样:一个Excel文件,其中每一行代表一个观测值,每一列代表一个变量。它被整齐地组织成类似矩阵的形式,这使得将其导入R或其他编程语言并开始进行复杂的统计分析变得非常容易。

然而,正如我们在《数据科学家工具箱》中看到的,真实数据看起来大不相同。例如,这是一个FastQ文件。你可能需要从文件的一行或每一行中提取序列信息。为了获取这些信息,你必须解析这个原始文本文件,并提取出你真正关心的部分。因此,获取数据的一个步骤就是深入这些原始文件,理解其结构,并能够提取相关部分。

另一种情况是,你得到的数据可能结构相当整齐,例如来自Twitter API的数据。这些数据以JSON格式格式化,虽然整齐有序且易于分发,但在许多编程语言中很难直接用于下游分析。因此,你可能希望以易于分析和处理的方式重新组织这些数据,这也是获取和清理数据的另一个组成部分。

最后,你可能会遇到像“口服一片”和“与葡萄柚汁同服半片”这样的自由文本指令。你可能需要从这些自由文本中提取一小部分信息,例如指示服用的药片数量,并根据你收集的每条记录进行分类。

数据的来源

上一节我们讨论了数据的形态,本节中我们来看看数据可能存储的位置。

数据可能存在于你需要提取它的任何地方,即使数据位于你可能想要分析的结构化环境中。以下是几个例子,它们都是数据库。MySQL和MongoDB是两个非常流行的免费数据库,数据可能存储在其中。例如,如果你在一家公司工作,你可能需要进入该数据库,收集其原始形式的数据,然后进行处理,以便对数据的某个子集进行分析,或者创建一个算法,将预测分析应用于从这些数据库收集的数据。

数据可能存在于各种地方,不一定非得是你电脑上的文件。例如,这是一个GET请求(我们将在本课程中讨论),它是尝试从网站获取信息的请求。在这个例子中,它正在从Twitter API获取关于特定推文或用户的信息。

数据也可能来自其他网站。这是本课程中会相对频繁看到的一个网站,即巴尔的摩的开放数据网站。它包含了许多有趣且有用的数据集供你分析,而且都是免费的,这很棒。但你必须能够从网络上获取它们,无论是通过API还是可下载文件,并清理它们,以便进行下游分析。

数据处理的全流程

如果你思考从数据库中的原始数据,一直到最终可以传达给同事、合作者或业务伙伴的数据的整个流程,实际上包含许多步骤。在大多数统计学或机器学习课程中,它们专注于数据分析这一步,这是数据处理流程的重要组成部分,但它们往往跳过了从原始数据到整洁数据的早期阶段。然而,在数据并非预先为你整理好的任何环境中(例如初创公司、大多数大型企业或几乎所有的学术环境),这一阶段都是更重要的组成部分之一。你需要知道如何实际获取原始数据本身,以便自己进行清理,理解其中的原理,而这正是本课程的全部内容。

总结

本节课中,我们一起学习了《获取数据》课程的动机。我们了解到,真实世界的数据很少以理想的整洁格式呈现,它们可能存在于数据库、API、网页或原始文件中,并且格式各异。本课程的目标就是教你如何从这些多样的来源中提取数据,并运用整洁数据原则将其整理成适合分析的格式。掌握这些技能对于在任何需要处理原始数据的环境中开展工作至关重要。

075:原始数据与处理过的数据 📊

在本节课中,我们将学习数据科学中两个核心概念:原始数据和处理过的数据。我们将探讨它们的定义、区别,以及从原始数据到可分析数据的处理流程。理解这些概念对于进行严谨的数据分析至关重要。


数据的定义

上一节我们介绍了课程主题,本节中我们来看看数据的定义。

数据是归属于一组项目的定性或定量变量的值。这组项目可能是你感兴趣的人群或对象集合。这些值对应着变量,而变量就是你测量的东西。

变量可以用定性或定量的方式来测量。通常,我们考虑的变量包括:

  • 定性变量:如国家、性别、治疗方案。
  • 定量变量:如身高、体重、血压。

许多测量值实际上源自更底层的测量。例如,血压是通过计算压力测量值得出的。这些底层的测量正是我们讨论原始数据与处理过的数据时会涉及的内容。


原始数据

上一节我们了解了数据的基本构成,本节中我们来深入探讨原始数据。

原始数据是数据的原始来源。它们通常很难直接用于数据分析,因为其结构复杂、难以解析或直接分析。

实际上,数据分析本身就包含了数据处理或清洗的步骤。如果你获得一个原始图像文件,将其处理并转换成一个规整的数据框,然后在R中进行分析,那么这个数据处理过程就是数据分析或数据科学流程的一部分。事实上,数据科学家工作的很大一部分就是执行这类处理操作。

原始数据可能只需要处理一次,但无论处理频率如何,你都需要记录所有处理步骤,因为这些步骤会对后续分析产生重大影响。

以下是原始数据的一些关键特征:

  • 是数据的原始来源。
  • 通常结构复杂,难以直接分析。
  • 处理过程是数据分析的关键组成部分。
  • 处理步骤必须被完整记录。

处理过的数据

理解了原始数据的特性后,我们来看看它的产物——处理过的数据。

处理过的数据是已准备好进行分析的数据。对数据的处理可能包括合并、子集化、转换等操作。例如,你可能从一个文件中提取图像的一部分,或从一个自由文本字段中提取特定文本。

根据你所处的领域,可能存在标准的处理流程。例如,在我工作的基因组学领域,在分析数据之前需要应用许多非常标准的预处理技术。

一个极其关键的组成部分是:所有步骤都应被记录。这一点再怎么强调都不为过。预处理通常是对下游数据影响最大的数据分析环节。因此,仔细关注你所做的每一步,对于成为一名严谨的、理解整个数据处理流程的数据科学家至关重要。

以下是处理过的数据的一些关键特征:

  • 已准备好用于分析。
  • 可能经过合并、子集化、转换等操作。
  • 不同领域可能有标准的预处理方法。
  • 记录所有处理步骤是至关重要的。

示例:基因组数据处理流程

为了更具体地说明“原始数据”在不同层级上的含义,我将用一个快速的示例来展示一个处理流程。

这是一台Illumina高通量测序仪。这台机器可用于对DNA进行测序,现在的测序速度比过去快得多。最初的人类基因组计划启动时,测序一个人类基因组花费了近十年时间和可能超过十亿美元。而现在,使用这样的机器,大约一周时间、一万美元左右就能完成同样的过程。这个例子说明了数据正变得越来越廉价且易于收集。

其工作原理(非常粗略的概述)如下:你可以想象从DNA片段开始。这些小的DNA片段被绑定到一个载玻片上,每个片段可能由500个字母(碱基)组成。你可以将DNA想象成一条由30亿个字母组成的字符串,然后取其中一小段(500个字母)绑定到载玻片上。

通过一个化学过程,会制造出相同序列的多个副本。这个过程通过“合成测序”进行:与载玻片上序列每个字母互补的碱基(字母)被逐个添加,而每个不同的字母(A、C、T、G)会被标记上不同的颜色。

因此,对于每个小簇,在每个新合成的核苷酸处,你都会得到一个颜色。这些颜色创建了一系列图像。例如,在合成第一个核苷酸时,你可能会得到这张图像,然后是第二个核苷酸的图像,第三个,依此类推。

如果你放大一个特定的小点,它对应着其中一个完全相同的序列簇。你可以沿着图像序列追踪,查看每个图像中该点的颜色。最终,对于每个图像,颜色对应着每个字母,其中最亮的那个颜色就被指定为该位置的碱基。

例如,对于这个特定片段,第一个字母将是C,因为在这四个字母中,C是最亮的。在第二个核苷酸处,你可以看到这四个字母中最亮的是G,因此我们指定的下一个字母就是G,以此类推。

最终你得到的是类似这样的一个FastQ文件(我在课堂上展示过几次)。FastQ文件是一个文本文件,其中包含了从平板上每个小片段中读取到的一系列特定字母(A、C、T、G)。

你可以从几个不同的步骤来思考原始数据:

  1. 原始数据可能是这些图像文件。你必须处理这些图像文件才能得到每个不同片段的这些图谱。
  2. 在获得图谱之后,你必须处理这些图谱,以预测哪些字母应该进入最终出现在这里的序列中。

这些阶段中的任何一个都可以被认为是“原始”的。在每个阶段,都有许多必须应用的计算步骤,这些步骤可能产生重大影响。

在进行数据分析时,需要记住的一点是:你经常可能分析从这台机器出来的所谓“读数”,甚至可能分析更下游的东西,比如基于汇总某些读数得到的计数。当你这样做时,你忽略了一个事实,即所有这些处理步骤都发生在之前。这些处理步骤可能产生重大影响。本课程的部分目的就是理解这些处理步骤是什么,以确保你的分析不会受到从原始数据到可分析数据的转换方式所引入的人为假象的驱动。

这就是获取数据的全部意义:将原始数据转化为处理过的数据。


总结

本节课中,我们一起学习了数据科学中的两个基础概念。我们明确了原始数据是复杂、未加工的原始来源,而处理过的数据是经过清洗、转换后可用于分析的形式。我们通过基因组测序的例子,看到了数据从图像文件到文本序列的多层处理流程,并强调了完整记录每一步处理步骤的极端重要性。理解从原始数据到处理过的数据的旅程,是确保数据分析结果可靠、可复现的关键。

076:整洁数据的组成部分 📊

在本节课中,我们将要学习如何将原始数据转化为整洁数据,并了解构成一个完整、可重现数据分析流程的关键组成部分。

上一节我们介绍了原始数据的概念,即分析流程的起点。本节中我们来看看整个数据处理流程的目标——整洁数据,以及为了确保分析的可信与可重现,我们需要准备哪些关键文件。


原始数据 📁

原始数据是你获取到的、未经任何处理的初始数据。它可能以多种形式存在。

以下是原始数据的一些常见例子:

  • 一个由测量仪器输出的奇怪二进制文件。
  • 一个包含10个工作表的、格式混乱的Excel文件。
  • 从API抓取得到的复杂JSON数据。
  • 甚至可能是你通过显微镜观察并手动计数的细胞数量。

判断数据是否为原始形式的一个关键标准是:你未对数据运行任何软件。这意味着,数据交到你手上时,你尚未对其进行任何计算、修改、删除或汇总操作。保持原始数据的纯净是向合作者交付可靠分析结果的基础。


整洁数据 🧹

整洁数据是整个数据处理流程的目标或最终成果。它遵循一套清晰的组织原则,便于后续的分析与建模。

整洁数据的核心规则可以概括为以下三点:

  1. 每个变量一列:你测量的每个变量都应恰好对应数据表中的一列。
  2. 每次观测一行:每一次独立的观测记录都应位于不同的行中。
  3. 每种观测类型一张表:不同类型的观测数据应存放在不同的表格中。如果存在多张需要关联的表,每张表必须包含一个可以链接它们的列(例如ID)。

以下是整理数据时的一些实用建议:

  • 在文件顶部包含变量名行。
  • 使用人类可读的变量名(例如 AgeAtDiagnosis 优于 AgeDX)。
  • 通常,每个表格应保存为一个独立的文件,而不是将多个工作表塞进一个Excel文件。

数据字典 📖

数据字典(或称元数据)是描述整洁数据集中每个变量及其取值的文档。它解释了数据背后的含义,是经常被忽略但至关重要的部分。

数据字典通常需要包含以下信息:

  • 变量的单位:例如,收入数据是以“千元”还是“百万元”为单位。
  • 汇总方式:例如,月收入数据是取的中位数还是平均值。
  • 实验研究设计:描述数据是如何收集的(例如,是从数据库提取,还是进行了随机对照试验)。

一份标准的数据字典文档(如Word、文本或Markdown文件)应包含两个主要部分:

  • 研究设计:详细描述数据收集的过程、样本选择方法、排除标准等。
  • 代码簿:列出所有变量,并详细说明每个变量的定义和单位。

处理指令清单 🧾

处理指令清单是一份明确的“食谱”,记录了从原始数据到整洁数据(及数据字典)的每一个处理步骤。它是确保分析可重现的核心。

理想情况下,这份“食谱”应该是一个计算机脚本(例如R或Python脚本)。该脚本的输入是原始数据,输出是处理好的整洁数据。关键在于,这个脚本不应包含需要手动调整的参数,任何人使用相同的原始数据运行该脚本,都应得到完全一致的整洁数据集。

有时,并非所有步骤都能用脚本自动化(例如,某些处理可能依赖特定版本的第三方软件)。此时,你需要提供一个极其详尽的文本说明。

以下是文本指令清单应包含的内容示例:

对原始文件运行[软件名称]版本3.1.2,使用参数‘-a -b 10’。
对每个样本单独运行上述软件。

重要性总结与案例 🚨

本节课我们一起学习了构成可重现数据分析的四个核心部分:原始数据整洁数据数据字典处理指令清单

坚持这一流程至关重要。一个著名的反面案例是Reinhart和Rogoff关于债务与经济增长的论文,该研究曾被用来为多国的财政紧缩政策提供依据。后来,一位研究生检查了他们使用的Excel文件和处理步骤,发现了大量错误。这些数据处理上的问题导致论文结论受到严重质疑,进而影响了相关的政治决策。这个案例深刻地提醒我们,仔细记录并保存从原始数据到分析数据的完整处理流程,是保证研究质量和可信度的安全底线。


077:使用R下载文件 📥

在本节课中,我们将学习如何使用R编程语言从互联网下载文件。我们将了解为何通过R脚本下载文件比手动点击下载更具优势,并掌握相关的核心函数和操作步骤。


工作目录管理 🗂️

上一节我们介绍了数据获取的多种方式,本节中我们来看看如何在R中管理文件的工作目录。了解当前的工作目录是进行文件操作的第一步。

在R中,有两个主要的命令用于处理目录:getwd()setwd()

  • getwd() 函数用于获取当前的工作目录路径。
  • setwd() 函数用于设置新的工作目录。

以下是设置工作目录的两种方法:

  1. 使用相对路径:例如,setwd("../") 会将目录向上移动一级。
  2. 使用绝对路径:例如,setwd("/Users/JTleek/data") 会直接切换到指定的完整路径。

注意:在Windows系统中,路径需要使用反斜杠 \ 而不是正斜杠 /。例如:setwd("C:\\Users\\JTleek\\data")


创建数据目录 📁

在开始下载数据之前,通常需要创建一个专门的目录来存放数据。这有助于保持项目结构清晰。

R提供了检查目录是否存在和创建目录的函数。

  • file.exists("目录名"):检查当前目录下是否存在指定的子目录。存在则返回 TRUE,否则返回 FALSE
  • dir.create("目录名"):创建一个新的目录。

以下是一段常用的代码逻辑,用于在数据目录不存在时自动创建它:

if (!file.exists("data")) {
  dir.create("data")
}

这段代码的意思是:如果名为“data”的目录不存在,则创建它。


使用 download.file 函数下载 🌐

从互联网获取文件的主要方式是使用 download.file() 函数。虽然你可以手动访问网站并点击下载,但将下载步骤写入R脚本可以极大地提高分析过程的可重复性。

download.file() 函数的核心参数如下:

  • url:要下载的文件网络地址。
  • destfile:文件下载后保存在本地的路径和文件名。
  • method:下载方法,对于某些网站(特别是HTTPS链接)需要指定。

这个函数可以下载多种类型的文件,例如制表符分隔文件、CSV文件、Excel文件等。


实战示例:下载巴尔的摩摄像头数据 🚗

让我们通过一个实际例子来练习。我们将下载美国巴尔的摩市固定测速摄像头位置的数据集。

首先,从目标网站找到CSV文件的下载链接。然后,在R中执行以下步骤:

  1. 定义文件URL:将复制的链接地址赋值给一个变量。

    fileUrl <- "https://data.baltimorecity.gov/api/views/dz54-2aru/rows.csv?accessType=DOWNLOAD"
    
  2. 执行下载:使用 download.file() 函数下载文件到之前创建的“data”目录中。

    download.file(fileUrl, destfile = "./data/cameras.csv", method = "curl")
    

    代码说明

    • destfile = "./data/cameras.csv" 指定了保存路径和文件名。
    • method = "curl" 是针对Mac系统下载HTTPS链接的常用设置。在Windows系统上,通常可以省略此参数或使用默认方法。
  3. 验证下载:使用 list.files() 函数查看“data”目录中的文件,确认下载成功。

    list.files("./data")
    
  4. 记录下载日期:网络上的数据可能会更新。记录下载日期对于后续追溯数据版本非常重要。

    dateDownloaded <- date()
    

重要注意事项 ⚠️

在使用 download.file() 时,有几个关键点需要牢记:

  • 链接类型:如果URL以 http:// 开头,通常没有问题。如果以 https:// 开头,在Mac上需要设置 method = "curl",在Windows上通常不需要。
  • 下载时间:下载文件需要时间,取决于文件大小和网络速度。对于大型文件,在编写脚本时可以考虑将下载代码单独列出,避免在每次重新运行分析时都重复下载。
  • 版本控制:务必使用 date() 函数记录下载时间,以便在未来需要时核对所使用的数据版本。

总结 📝

本节课中我们一起学习了如何使用R从互联网下载文件。我们掌握了管理工作目录的 getwd()setwd() 函数,学习了如何用 file.exists()dir.create() 创建数据目录,并深入了解了用于文件下载的核心函数 download.file() 及其关键参数。通过巴尔的摩摄像头数据的实战示例,我们演练了完整的下载流程,并强调了记录下载日期对于可重复研究的重要性。将这些步骤自动化到你的R脚本中,将使你的数据分析流程更加完整和可重现。

078:读取本地文件 📂

在本节课中,我们将学习如何在R语言中读取本地存储的“平面文件”。平面文件是常见的文本文件格式,例如制表符分隔文件、逗号分隔文件等。如果你已经学习过R编程课程,可能已经掌握了加载数据的方法,可以跳过本讲。


概述

本节课将介绍使用R读取本地数据文件的核心方法。我们将重点学习 read.table() 函数及其相关参数,并了解如何处理常见的文件读取问题。


读取数据的基本函数

上一节我们介绍了平面文件的概念,本节中我们来看看R中最常用的数据读取函数 read.table()

read.table() 是R中最通用、最灵活的数据读取函数。它能够处理多种分隔符格式的文件,但相应地需要设置更多参数。对于非常大的数据集,read.table() 可能不是最高效的选择,因为它会将整个文件读入内存。此时可能需要考虑分块读取或其他方法。

以下是 read.table() 的一些重要参数:

  • file: 要读取的文件路径。
  • header: 文件第一行是否包含列名(变量名)。
  • sep: 文件中用于分隔不同数据值的字符。
  • row.names: 指定作为行名的列。
  • nrows: 指定要读取的行数。

此外,对于常见的逗号分隔值文件,R提供了便捷函数 read.csv()read.csv2()read.csv() 会自动将分隔符 sep 设置为逗号(,),并将 header 参数设为 TRUE


实践:读取巴尔的摩摄像头数据

让我们通过一个实例来应用上述知识。我们将尝试读取一个名为 cameras.csv 的逗号分隔文件。

如果直接使用默认参数的 read.table() 读取CSV文件,会遇到错误。

cameraData <- read.table("./data/cameras.csv")

错误原因read.table() 的默认分隔符是制表符(\t),而我们的文件使用逗号分隔。因此,R无法正确解析第一行,导致变量 cameraData 并未被成功创建。

为了解决这个问题,我们需要在调用 read.table() 时明确指定分隔符,并告知文件包含表头。

cameraData <- read.table("./data/cameras.csv", sep=",", header=TRUE)
head(cameraData)

通过设置 sep=","header=TRUE,函数现在能正确识别文件结构。使用 head() 函数可以查看数据框的前几行,确认数据已被成功加载。

对于CSV文件,更简单的方法是直接使用 read.csv() 函数,它能自动处理上述参数。

cameraData <- read.csv("./data/cameras.csv")

处理常见读取问题

在成功读取基础数据后,我们来看看如何通过调整参数来处理一些常见问题。以下是 read.table() 中其他一些有用的参数:

  • quote: 指定用于包围字符型数据的引号字符。例如,quote="" 表示文件中没有使用引号。
  • na.strings: 指定哪些字符串应被识别为缺失值(NA)。默认是 "NA",但也可能是 "-1""999.99" 等。
  • nrows: 指定要读取的总行数。例如,nrows=10 只读取前10行。
  • skip: 指定在开始读取数据前要跳过的行数。例如,要读取第3到第13行,可以设置 skip=2(跳过前2行)和 nrows=10(读取10行)。

一个常见的棘手问题是数据值中意外包含了引号字符(如 "),这会导致R在解析行列时产生混乱。根据经验,将 quote 参数设置为空字符串(quote="")通常是解决此类问题的有效方法,可以避免R将数据内的引号误认为是字段边界。


总结

本节课中我们一起学习了在R中读取本地平面文件的核心技能。我们重点掌握了 read.table() 函数及其关键参数(如 sepheader)的用法,并了解了其便捷版本 read.csv()。此外,我们还探讨了如何处理因错误引号或指定缺失值而导致的文件读取问题。正确地将外部数据加载到R中是进行后续所有数据分析的第一步。

079:读取Excel文件 📊

在本节课中,我们将学习如何在R语言中读取和处理Excel文件。尽管一些数据科学家对Excel文件持有偏见,但Excel仍然是目前最广泛使用的数据共享格式之一。我们将了解其原因,并掌握使用R读取Excel数据的具体方法。

Excel文件的普遍性

一些数据科学家对Excel文件表现出某种轻视态度。

但Excel文件可能仍然是共享数据时使用最广泛的格式。

我认为原因在于许多人习惯使用电子表格。

无论你是在商业应用还是科学领域工作,人们都知道如何使用电子表格来收集、输入数据并相互共享。

如今,随着像谷歌电子表格这类工具的出现,这一情况甚至比以往更加普遍,因为它们允许人们在互联网上协作共享数据。

在R中处理Excel数据的必要性

对你而言,问题在于当你使用像R这样的脚本语言分析数据时,你需要能够从这些文件中提取数据,以便进行后续的分析和处理。

下载Excel数据文件

我们将回到巴尔的摩摄像头数据。我想我最近一定收到了很多超速罚单,因为我一直在思考这个数据集。现在,我们将实际下载这个数据的Excel版本。在本例中,URL再次略有不同,你只需从网站上获取URL。

接下来我们要做的是将文件下载为 .xlsx 文件,因为这是Excel文件的扩展名。现在我们已经下载了这个文件。

我们开始使用 download.file 函数,请记住这个函数对文件类型是不可知的,它只是下载文件并将其放入你在此处指定的文件名中。我们再次收集文件下载时间的信息,以便了解分析是何时执行的。

以下是下载文件的R代码示例:

fileUrl <- "http://example.com/data.xlsx"
download.file(fileUrl, destfile = "./data/cameras.xlsx", method = "curl")
dateDownloaded <- date()

使用xlsx包读取数据

用于此目的的R库是xlsx包。如果你愿意,也可以使用其他包如XLConnect,它们可能提供更灵活的接口,但我喜欢xlsx

我们实际使用的命令是 read.xlsx,并再次将其赋值给cameraData变量。现在你需要传递几个参数:我们需要传递sheetIndex以指明数据存储在哪个工作表上,并且我们要告诉它存在标题行,这样列名就被标记了。

当我们将其加载到R中时,它看起来几乎与你读取CSV文件以及直接从Excel文件中读取时得到的数据完全一样。

以下是读取Excel文件的R代码示例:

library(xlsx)
cameraData <- read.xlsx("./data/cameras.xlsx", sheetIndex=1, header=TRUE)

读取特定行与列

需要记住的一点是,你可以读取特定的行和列。例如,如果你只想读取第二列和第三列,并且只想读取第一到第四行,你可以实际将这些作为变量传递给colIndexrowIndex参数给read.xlsx函数。这样你实际上只读取了Excel文件的一个子集,如果你只想提取文件中的一小部分,这可能很有用。

以下是读取特定行列的R代码示例:

cameraDataSubset <- read.xlsx("./data/cameras.xlsx", sheetIndex=1,
                              colIndex=2:3, rowIndex=1:4)

其他注意事项与替代方案

以下是一些可能对你有用的进一步说明,或者你可以使用write.xlsx函数。如果你与喜欢Excel文件的人一起工作,你实际上可以在分析后将数据写回并与人共享。这非常相似,你传递想要写出的对象和文件名,它就会写出那个文件。

我发现read.xlsx2read.xlsx快得多,但特别是在读取行的子集时,它可能有点不稳定,至少在我的经验中如此。

如前所述,XLConnect在读取、写入和操作Excel文件方面要灵活得多。因此,如果你真的需要对Excel文件进行大量严肃的处理,我发现如果你打算这样做,XLConnect可能会更好一些。XLConnect的说明文档实际上是一个很好的起点,它包含大量关于如何执行各种不同操作的信息,例如如何直接从R中操作和创建文件。

总的来说,就本课程的目的而言,也是就大多数分析的目的而言,如果你将文件存储为逗号分隔或制表符分隔的平面文件,读取文件会更快、更容易一些。它们也更容易分发,因为不是每个人都有Excel。它不一定像使用纯文本文件(仅包含逗号分隔或制表符分隔的值)那样具有跨平台性。

总结

本节课中,我们一起学习了在R中处理Excel文件的全过程。我们了解了Excel格式的普遍性,掌握了使用download.file函数获取网络Excel文件的方法,并重点学习了使用xlsx包的read.xlsx函数来读取数据。我们还探讨了如何读取特定的行和列以进行高效的数据提取,并简要介绍了write.xlsxread.xlsx2以及更强大的XLConnect包作为备选方案。最后,我们认识到对于分析和共享,CSV等纯文本格式通常更具优势。

080:读取 XML 文件

在本节课中,我们将学习如何读取和处理 XML 文件。XML 是一种广泛用于存储和传输结构化数据的标记语言,在网页抓取和网络 API 数据获取中尤为常见。


🏷️ 什么是 XML?

XML 代表可扩展标记语言。它常用于存储结构化数据,在互联网应用中非常普遍。例如,在进行网页抓取、从网络 API 获取数据或从开放数据网站下载数据时,经常会遇到 XML 格式。

提取 HTML 和 XML 是大多数网页抓取工作的基础。

一个 XML 文件包含两个主要部分:标记内容。标记是为文本提供结构的标签,而内容则是标签之间的实际文本。

为了更具体地理解,我们将介绍三个核心概念:标签元素属性


🔖 标签、元素与属性

标签 是应用于文本特定部分的标签,用于赋予文本结构。标签分为开始标签和结束标签。

  • 开始标签 的格式为 <section>
  • 结束标签 的格式为 </section>
  • 空标签 用于表示不需要成对出现的特定行,例如换行,格式为 <br/>

元素 是标签的具体实例。例如,一个 XML 元素可以是由 <greeting> 开始标签和 </greeting> 结束标签包围的部分,中间的内容是 “Hello world”。

属性 是标签的组成部分,用于为标签添加额外信息。例如,一个图像标签 <img> 可能包含 src(图片来源)和 alt(替代文本)属性。

另一个例子是步骤标签 <step>,可以包含 number 属性来标识步骤顺序,如 <step number="1">

如果你想了解更多关于 XML 的知识,可以查阅维基百科上关于 XML 的条目,内容非常全面。


📄 XML 文件示例

以下是一个 XML 文件的示例。它本质上是一个包含许多标签和内容的文本文件。

例如,文件中有一个 <food> 开始标签和一个对应的 </food> 结束标签。在这个特定的食物元素内部,有一个 <name> 标签,其内容是 “Belgian Waffles”。此外,还有其他标签对应这个食物元素的其他组成部分。文件中还有更多这样的食物元素。

你可以通过课程中提供的网页链接查看这个文件的完整版本。


📥 在 R 中读取 XML 文件

你可以使用 XML 包将 XML 文件读入 R。

首先,加载 XML 包,然后使用 xmlTreeParse 函数来解析指定的 XML 文件。这个函数会将文档加载到 R 的内存中,以便后续解析和访问其不同部分。

library(XML)
fileUrl <- "http://www.w3schools.com/xml/simple.xml"
doc <- xmlTreeParse(fileUrl, useInternalNodes = TRUE)

🧭 访问 XML 文档结构

在 R 中,解析后的 XML 是一个结构化对象,我们需要使用不同的函数来访问其各个部分。

首先,我们可以查看根节点,它是整个文档的包装元素。使用 xmlRoot 函数可以获取它。

rootNode <- xmlRoot(doc)

然后,使用 xmlName 函数可以获取根节点的名称。

xmlName(rootNode)

我们还可以查看根节点下所有嵌套元素的名称,这能告诉我们文档的整体结构。例如,根节点包装了整个“早餐菜单”,而菜单中包含五个用 <food> 元素包装的早餐项目。

names(rootNode)

🔍 提取特定元素

接下来,我们可以像访问 R 中的列表一样,直接访问 XML 文档的特定部分。

例如,要查看根节点的第一个元素(即第一个食物项),可以使用双括号索引。

rootNode[[1]]

如果你想进一步深入,提取第一个食物项的第一个子元素(例如名称),可以继续使用子集索引。

rootNode[[1]][[1]]

🔄 程序化提取内容

你可以使用 xpathSApply 函数程序化地提取文件的不同部分。

将解析后的 XML 对象传递给该函数,并指定要应用的函数(如 xmlValue)。默认情况下,它会递归地获取文档中每个被标记元素的值,最终将所有文本内容拼接在一起。

xpathSApply(rootNode, "//name", xmlValue)

🎯 使用 XPath 进行精确提取

有时,你可能需要更精确地提取文档的特定部分,这时可以使用 XPath 语言。XPath 是另一种用于在 XML 文档中定位节点的语言。

虽然需要学习一些新语法,但掌握几个关键组件就能大有帮助。以下是一些基础概念:

  • /node:获取顶层节点。
  • //node:获取任何层级的节点。

例如,要提取菜单上所有项目的名称和价格,可以这样做:

# 提取所有名称
itemNames <- xpathSApply(rootNode, "//name", xmlValue)

# 提取所有价格
itemPrices <- xpathSApply(rootNode, "//price", xmlValue)

🌐 实战:从网页抓取数据

为了展示一个更复杂的例子,我们将从一个实际网页(巴尔的摩乌鸦队的主页)抓取数据。

首先,我们需要解析 HTML 文件(HTML 是 XML 的一种方言)。使用 htmlTreeParse 函数,并设置 useInternal = TRUE 以获取文件中的所有节点。

fileUrl <- "http://espn.go.com/nfl/team/_/name/bal/baltimore-ravens"
doc <- htmlTreeParse(fileUrl, useInternal = TRUE)

假设我们想从页面中提取所有比赛得分。通过查看网页源代码,我们发现得分信息位于 class 属性为 “score” 的列表项 <li> 标签中。

我们可以使用 XPath 来精确提取这些元素的值:

scores <- xpathSApply(doc, "//li[@class='score']", xmlValue)

同样地,我们可以提取队伍名称,它们位于 class 属性为 “team-name” 的列表项中:

teams <- xpathSApply(doc, "//li[@class='team-name']", xmlValue)

通过这种方式,我们成功地从网站上抓取到了比赛得分和队伍名称信息。


📚 总结与延伸阅读

本节课简要介绍了 XML、R 中的 XML 包以及相关概念。

  • 我们学习了 XML 的基本结构:标签、元素和属性。
  • 我们掌握了如何在 R 中使用 xmlTreeParsehtmlTreeParse 读取和解析 XML/HTML 文件。
  • 我们探索了使用 xmlRootxmlName 和列表式索引来浏览文档结构。
  • 我们重点练习了使用 xpathSApply 函数结合 XPath 语言,程序化地精确提取所需数据。

如果你想深入学习,可以参考 XML 包的官方教程,其中包含简短入门和详细指南。网上也有大量关于如何使用 XML 从网站程序化提取信息的优秀资源。

通过掌握这些技能,你将能够有效地处理网络上的结构化数据,为数据科学项目获取宝贵的信息源。

081:读取 JSON 数据

在本节课中,我们将学习如何读取和处理 JSON 格式的数据。JSON 是一种轻量级的数据交换格式,广泛应用于网络数据传输和应用程序接口中。我们将了解其基本结构,并学习如何在 R 语言中使用 jsonlite 包来读取和转换 JSON 数据。


🏗️ JSON 简介

上一节我们介绍了课程概述,本节中我们来看看 JSON 的基本概念。

JSON 是 JavaScript 对象表示法的缩写。它是一种轻量级的数据存储和交换格式,与 XML 类似,具有结构化特点,但在语法和格式上有所不同。JSON 数据可以存储为数字、字符串、布尔值、数组或对象。

JSON 在应用程序编程接口中非常常见,例如,您可以通过 URL 以编程方式访问 Twitter 或 Facebook 等公司的数据。

如果您想了解更多关于 JSON 的信息,可以从维基百科的相关页面开始。


📄 JSON 文件结构示例

以下是一个 JSON 文件的示例,它来自 GitHub 的 API,展示了用户贡献的代码仓库信息。

整个 JSON 对象由一个外层花括号表示,每个独立的代码仓库信息则包含在各自的花括号内。每个仓库都有一系列与之关联的变量,例如 ID 或仓库的全名。

JSON 文件的结构通常是“键”后跟一个冒号,然后是“值”。您可以在其中嵌套结构,例如,一个数组可以作为 JSON 对象中某个变量的值。

在示例中,owner 变量的值就是一个数组,其中包含了登录名、头像 URL 等信息。


🔧 在 R 中读取 JSON 数据

在 R 语言中,有一个非常方便的包用于读取 JSON 数据,叫做 jsonlite 包。

以下是读取 JSON 数据的基本步骤:

  1. 获取 JSON 数据所在的 URL。
  2. 使用 fromJSON() 函数读取该 URL。
  3. 函数将返回一个结构化的数据框。

读取后,数据框的列名对应 JSON 中的顶层变量,如 ID、名称、全名等。其中一些变量(如 owner)本身可能包含嵌套的数据结构,在 R 的数据框中可以表现为数据框中的列本身又是一个数据框。

例如,我们可以进一步查看 owner 列中的 login 信息。


💻 代码操作演示

以下是具体的 R 代码操作示例。

首先,我们需要安装并加载 jsonlite 包。

# 安装并加载 jsonlite 包
install.packages("jsonlite")
library(jsonlite)

接着,我们可以从一个 URL 读取 JSON 数据。

# 从 URL 读取 JSON 数据
myjson <- fromJSON("https://api.github.com/users/jtleek/repos")
# 查看数据框的列名
names(myjson)
# 查看 owner 列中的信息
names(myjson$owner)
# 查看所有仓库的登录名
myjson$owner$login

此外,您还可以将 R 中的数据框转换为 JSON 格式。例如,经典的 iris 数据集。

# 将 iris 数据集转换为 JSON 格式的文本
myjson <- toJSON(iris, pretty=TRUE)
# 打印查看 JSON 文本
cat(myjson)

参数 pretty=TRUE 会使输出的 JSON 文本具有缩进,便于阅读。

您还可以将这个新创建的 JSON 文本转换回数据框。

# 将 JSON 文本转换回数据框
iris2 <- fromJSON(myjson)
# 查看数据框的前几行
head(iris2)

这样得到的数据框 iris2 将与原始的 iris 数据集完全相同。


📚 更多学习资源

如果您想获得更多信息,可以访问 JSON.org 以了解 JSON 的通用知识。

一个关于 jsonlite 包的优秀教程(也是本讲座的重要基础)可以在 R-bloggers 网站上找到。jsonlite 包的 vignette 文档也相当不错,能提供更多详细信息。

如果您需要处理更复杂的 JSON 结构,可以查阅这些资源以获取更多帮助。


🎯 课程总结

本节课中,我们一起学习了 JSON 数据格式的基础知识及其在 R 语言中的处理方法。我们了解了 JSON 的结构,掌握了使用 jsonlite 包中的 fromJSON()toJSON() 函数进行数据读取与转换的技能。这些工具对于处理来自网络 API 的常见数据格式至关重要。

082:数据表包 📊

在本节课中,我们将要学习 data.table 包。这是一个在R语言中用于数据分析的工具,它通常是比标准数据框更快、更节省内存的版本。


概述

data.table 包继承自数据框,因此所有接受数据框的函数也应该能在 data.table 上工作。它用C语言编写,在子集筛选、分组变量和更新变量方面比数据框快得多。不过,它需要学习一点新的语法。


创建数据表

上一节我们介绍了 data.table 的基本概念,本节中我们来看看如何创建它。

创建 data.table 的方式与创建数据框完全相同。

# 创建一个数据框
df <- data.frame(x = rnorm(100), y = rep(c("a", "b", "c", "d"), 25), z = rnorm(100))

# 创建一个数据表
dt <- data.table(x = rnorm(100), y = rep(c("a", "b", "c", "d"), 25), z = rnorm(100))

查看数据表顶部,其外观与数据框非常相似。


查看内存中的数据表

你可以使用 tables() 命令查看内存中的所有数据表。它会显示数据表的名称、行数、占用的内存大小、列数以及是否设置了键。

tables()

子集筛选行

与数据框类似,你可以在方括号的第一个位置(逗号前)对行进行子集筛选。

以下是筛选行的几种方法:

  • 你可以像使用数据框一样,通过索引筛选行:dt[2:3] 会选取第二和第三行。
  • 你也可以根据条件筛选行:dt[y == "a"] 会选取 y 列等于 “a” 的所有行。


子集筛选列与表达式

如果你尝试用数据框常用的方式筛选列,data.table 的行为会有所不同。它使用表达式来以各种方式汇总数据。

表达式是放在花括号 {} 内的一组语句。在 data.table 中,你可以在方括号的第二个参数位置传递一个函数列表,这些函数将应用于以列名命名的变量。

以下是使用表达式汇总数据的示例:

  • 计算 x 列的均值和 z 列的总和:dt[, list(mean(x), sum(z))]
  • 获取 y 列值的频数表:dt[, table(y)]

注意,你不需要为变量名加引号。


添加新列

data.table 可以非常快速且高效地添加新列。使用 := 操作符可以直接在原有数据表上添加列,而无需创建整个数据表的新副本,这对于处理大型数据集非常有利。

例如,添加一个等于 z 列平方的新列 w

dt[, w := z^2]

但是需要注意,由于默认不创建副本,直接赋值 (dt2 <- dt1) 后修改 dt1 也会影响 dt2。如果需要创建独立副本,必须显式使用 copy() 函数:

dt2 <- copy(dt1)

多步操作与分组汇总

你可以在表达式中执行多步操作来创建新变量。每个语句用分号分隔,最后一个语句的结果会被返回。

例如,创建一个新变量 m,其值为 log2(x + z + 5)

dt[, m := {
  temp <- x + z
  log2(temp + 5)
}]

你还可以执行类似数据透视的操作。例如,先创建一个二进制变量 a,然后按 a 分组汇总:

  1. 创建变量 adt[, a := x > 0]
  2. a 分组计算 x + w 的均值:dt[, mean(x + w), by = a]

特殊变量与键

data.table 包含一些特殊变量以加速操作。例如,.N 是一个整数,包含每个分组出现的次数。使用 .N 可以快速计数:

# 创建一个大数据表
big_dt <- data.table(x = sample(letters, 100000, replace = TRUE))
# 快速计算每个字母出现的次数
big_dt[, .N, by = x]

data.table 的一个独特功能是。设置键后,可以更快地对数据表进行子集筛选和排序。

  1. 设置键:setkey(dt, x)
  2. 利用键快速筛选:dt["a"] 会快速筛选出 x 列等于 “a” 的所有行。

键还可以促进数据表之间的连接,只要两个数据表设置了相同的键,合并操作会比数据框快得多。


快速读取文件

data.tablefread() 函数可以非常快速地从磁盘读取文件,它是读取制表符分隔文件时 read.table() 的高效替代品。

# 创建一个临时文件并写入大数据框
temp_file <- tempfile()
write.table(big_df, file = temp_file, row.names = FALSE, col.names = TRUE, sep = "\t", quote = FALSE)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/26cfcb0c4412a535f619286d65eef174_67.png)

# 使用 fread 快速读入
system.time(dt <- fread(temp_file))

read.table() 相比,fread() 的速度通常快一个数量级以上。


总结与资源

本节课中我们一起学习了 data.table 包。总结来说,data.table 在速度和内存效率上通常优于数据框,但需要学习新语法,并且在复制数据表时需要小心。

  • 最新发展:该包开发活跃,最新功能可在其开发版本中找到。它已经开始开发类似 reshape2 包中 meltdcast 的操作。
  • 学习资源:有一个网站详细列出了 data.table 与数据框之间的差异,对于从数据框过渡到 data.table 非常有用。
  • 致谢:本讲义的笔记主要基于 Raphael Guardo 和 Kevin Uhi 在 GitHub 上分享的优秀资料。


通过本教程,你应该对 data.table 包的核心功能、优势及基本用法有了初步了解。记住,虽然初期有学习成本,但其在处理大型数据时带来的性能提升是显著的。

083:从MySQL数据库读取数据 📊

在本节课中,我们将学习如何使用R语言从MySQL数据库中读取数据。MySQL是一种广泛使用的开源数据库,尤其在网络应用中非常流行。我们将了解数据库的基本结构,学习如何连接到数据库,并执行查询来获取所需的数据。


数据库结构简介

上一节我们介绍了课程目标,本节中我们来看看MySQL数据库的基本结构。数据在数据库中以结构化的方式存储。每个数据库包含一系列,每个表类似于一个数据框。表中的每一列称为一个字段,每一行称为一条记录

以下是一个示例数据库结构图,展示了“员工”数据库中多个相互关联的表:

每个方框代表一个表(可视为一个数据框),方框内列出的变量是该表的列名。这些表通过共同的ID字段(如emp_no)相互链接,从而可以查询到每位员工所属的部门、薪资、职称以及部门经理等信息。


安装MySQL与RMySQL包

在开始使用R操作MySQL之前,需要先安装MySQL数据库和R中的RMySQL包。

以下是安装步骤:

  1. 安装MySQL数据库:具体步骤因操作系统而异。你可以访问MySQL官方网站下载适合你系统的版本并按照说明安装。本课程将使用一个公开的Web服务器示例,但建议你在自己的系统上安装MySQL进行练习。
  2. 安装RMySQL包:在R中安装RMySQL包。
    • 在Mac系统上,可以直接使用 install.packages("RMySQL")
    • 在Windows系统上,安装可能稍复杂,需要正确设置数据库驱动。你可以参考官方说明或这份更直观的指南。如果遇到问题,请在课程论坛提问。

连接到MySQL数据库

我们将以加州大学圣克鲁兹分校维护的公共人类基因组数据库为例,演示如何连接。

首先,加载RMySQL包,然后使用dbConnect函数建立连接。该函数需要指定数据库类型、用户名、主机地址等信息。

library(RMySQL)
# 建立到公共基因组数据库服务器的连接
ucscDb <- dbConnect(MySQL(), user="genome", host="genome-mysql.cse.ucsc.edu")

连接成功后,会返回一个连接句柄(如ucscDb),后续操作都将基于这个句柄。

重要提示:完成数据操作后,务必使用dbDisconnect函数关闭连接,这是一个良好的习惯。

# 操作完成后,断开连接
dbDisconnect(ucscDb)


探索数据库与表

建立连接后,我们可以探索服务器上有哪些数据库,以及特定数据库中有哪些表。

以下是相关操作:

  1. 列出所有数据库:使用dbGetQuery函数向数据库发送SQL查询命令。

    result <- dbGetQuery(ucscDb, "show databases;")
    dbDisconnect(ucscDb)
    

    执行后,result变量中会存储该MySQL服务器上所有可用的数据库列表。

  2. 连接到特定数据库并列出其表:我们选择hg19(人类基因组第19版)数据库进行连接。

    hg19 <- dbConnect(MySQL(), user="genome", host="genome-mysql.cse.ucsc.edu", db="hg19")
    allTables <- dbListTables(hg19)
    length(allTables) # 查看该数据库中有多少张表
    head(allTables, 5) # 查看前5张表的名称
    

    hg19数据库包含超过10,000张表,每张表对应一种特定类型的基因组数据。


查看表结构与获取数据

确定感兴趣的表后,可以查看其结构(字段名)并获取数据。

以下是具体方法:

  1. 列出表的字段(列名):使用dbListFields函数。
    dbListFields(hg19, "affyU133Plus2")
    
    这将返回affyU133Plus2表的所有列名。

  1. 获取表的行数:使用dbGetQuery发送SQL的COUNT命令。

    dbGetQuery(hg19, "SELECT COUNT(*) FROM affyU133Plus2")
    
  2. 读取整个表到R中:使用dbReadTable函数,这会将整张表作为一个数据框读入。

    affyData <- dbReadTable(hg19, "affyU133Plus2")
    head(affyData)
    

    注意:如果表非常大,直接读取可能会超出内存。此时应使用查询来获取数据子集。


使用查询获取数据子集

对于大型表,更安全高效的方法是使用SQL查询语句只提取需要的部分数据。

以下是操作流程:

  1. 发送查询并获取结果:使用dbSendQuery发送一个带条件的SELECT语句,然后用fetch获取结果。

    query <- dbSendQuery(hg19, "SELECT * FROM affyU133Plus2 WHERE mismatches BETWEEN 1 AND 3")
    affyMis <- fetch(query)
    quantile(affyMis$mismatches)
    
  2. 限制获取的行数:在fetch中使用n参数,可以只提取前几行数据,用于快速检查。

    affySmall <- fetch(query, n=10)
    dim(affySmall)
    

  1. 清理查询结果:使用dbSendQuery后,必须在获取数据后清理远程服务器上的查询。
    dbClearResult(query)
    
    这非常重要,可以释放服务器资源。

重要注意事项与资源

在结束前,我们强调几个关键点和学习资源。

  • 始终关闭连接:完成所有数据操作后,务必使用dbDisconnect()关闭数据库连接。忘记关闭连接是常见错误。
  • 谨慎操作:本课程示例连接的是公共只读服务器。请仅使用SELECT语句查询数据,不要尝试DELETEINSERTUPDATE等修改操作,以免影响他人使用。同时,由于课程人数众多,请避免集中访问该公共服务器,最好在自己的本地MySQL上练习。
  • 深入学习

总结

本节课中我们一起学习了如何使用R语言与MySQL数据库进行交互。我们掌握了如何安装必要的工具包、建立和关闭数据库连接、探索数据库结构、查看表信息,以及最重要的——使用dbGetQuerydbReadTabledbSendQuery/fetch组合来安全高效地从数据库中读取数据或数据子集。记住谨慎操作和及时关闭连接的原则,你就能很好地利用R来处理存储在MySQL中的大量结构化数据了。

084:从HDF5文件读取数据 📁

在本节课中,我们将学习如何从HDF5文件中读取数据。HDF5是一种用于存储大型和结构化数据集的格式,广泛应用于科学计算和数据分析领域。我们将介绍HDF5的基本结构,学习如何在R中安装和使用RHDF5包来创建、写入和读取HDF5文件。


HDF5格式简介

上一节我们介绍了本课程的主题。本节中,我们来看看HDF5格式的基本概念。

HDF5用于存储大型数据集,也用于存储结构化数据集。它支持存储多种数据类型。HDF代表分层数据格式,这是因为数据存储在组中,这些组包含零个或多个数据集及其元数据。

每个组都有一个组头,包含组名和该组对应的属性列表。它们还有一个组符号表,其中列出了组中的对象。

数据集则是多维元素数组及其元数据。它们可以有一个包含名称、数据类型、数据空间和存储布局的头部,然后还有一个数据数组,你可以将其视为HDF5元素的数据框。

如果你想了解更多关于HDF5存储结构的信息,可以访问HDF5 Group的官方网站。


安装RHDF5包

了解了HDF5的基本概念后,接下来我们需要在R中安装处理HDF5文件的工具包。

RHDF5包需要通过Bioconductor安装。安装此包的方法是先获取指定的URL,这将加载biocLite函数。首次使用biocLite安装包时,它还需要从Bioconductor安装Biobase包,因此可能需要一些时间。

以下是安装步骤的代码:

source("http://bioconductor.org/biocLite.R")
biocLite("rhdf5")

安装完成后,我们可以像往常一样使用library命令加载这个包。

library(rhdf5)

创建HDF5文件与组

安装好工具包后,我们就可以开始实际操作了。在本节中,我们将创建HDF5文件并在其中组织数据。

本讲座的内容与RHDF5教程非常接近,该教程链接已在本幻灯片底部提供。我们将使用H5createFile命令创建一个HDF5文件集,文件名为example.h5

created <- H5Fcreate("example.h5")

创建文件后,我们可以在文件中创建组。我们使用H5createGroup命令。这将在此文件中创建名为foo的组。然后我们可以创建另一个名为baa的组,以及一个作为foo子组的名为foobaa的组。

h5createGroup("example.h5", "foo")
h5createGroup("example.h5", "baa")
h5createGroup("example.h5", "foo/foobaa")

然后,你可以查看我们创建的组和文件名。使用h5ls命令,它类似于ls命令,但只列出特定HDF5文件的组件。

h5ls("example.h5")

向组写入数据

创建了文件结构后,下一步是向其中写入数据。以下是写入数据的具体方法。

我们可以写入特定的组。例如,我们可以创建一个矩阵A,然后使用h5write命令将该矩阵写入特定组。这里,example.h5是我们要操作的文件,foo/A是文件内的目标组。

A <- matrix(1:10, nr=5, nc=2)
h5write(A, "example.h5", "foo/A")

我们不仅可以写入矩阵,还可以写入多维数组。例如,这里有一个多维数组。我们还可以为它添加属性,例如提供像这样的元数据:attr属性,用于描述单位信息。

B <- array(seq(0.1, 2.0, by=0.1), dim=c(5,2,2))
attr(B, "scale") <- "liter"
h5write(B, "example.h5", "foo/foobaa/B")

再次列出文件内容,我们现在可以看到所有已创建的组,并且在那些组中,我们实际上有一些数据集。你可以看到A现在是一个HDF5数据集,由整数组成;下面还有另一个数据B,由浮点数组成,并给出了这些数据集的维度,它们可以是多维的。

h5ls("example.h5")

写入数据框与读取数据

除了数组,我们还可以直接写入其他数据结构,例如数据框。

我们也可以直接写入数据。实际上,我们可以将数据写入顶级组。方法是这样的:假设我们使用data.frame命令创建了一个这样的数据框。现在我们有了这个数据框,我们可以使用h5write命令将该数据框直接写入顶级组。我们不是传递要发送到的组,而是直接传递变量名。

df <- data.frame(1L:5L, seq(0,1,length.out=5), c("ab","cde","fghi","a","s"), stringsAsFactors=FALSE)
h5write(df, "example.h5", "df")

如果我们列出当前的情况,现在可以看到在顶级组(即根组)中,有df,它是一个数据集,并给出了数据的维度等信息。

h5ls("example.h5")

然后,你可以使用h5read命令直接读取数据。你将使用h5read,并告诉它你要查看哪个文件。例如,你可以告诉它读取一个非常特定的数据集,如foo/A。你可以通过指定路径来读取子数据集。你也可以通过仅指定数据集名称来读取顶级组中的数据集。

readA <- h5read("example.h5", "foo/A")
readSubB <- h5read("example.h5", "foo/foobaa/B")
readDf <- h5read("example.h5", "df")

你得到的是你写入的数据。这就是你读取数据的方式,例如,如果你有一个HDF5文件中的数据,并且你想把它提取出来。


分块读取与写入

HDF5文件格式的另一个优点是你可以轻松地分块读取和写入数据。

我们在这里要做的实际上是再次写入。我们将使用h5write,并将数据写入我们文件中已有的数据集A。我们要做的是写入元素12、13和14,我们可以告诉它我们想把这些值写入该数据的特定部分。我们可以给它索引。这里的索引文件为每个维度提供索引。对于第一个维度,我们将说写入此数据集第一列的前三行,并设置这些值。

h5write(c(12,13,14), "example.h5", "foo/A", index=list(1:3,1))

如果我们在此写入操作后读取数据,你可以看到在第一列的前三行中,得到了12、13和14,就像我们写入的那样。

h5read("example.h5", "foo/A")

你可以使用类似的索引命令来仅读取数据的子组件。你可以传入一个索引,它是一个列表,显示了要读取的不同维度。例如,如果你只想读取第一列的前三行,你可以将相同的索引命令传递给h5read

h5read("example.h5", "foo/A", index=list(1:3,1))

注意事项与进一步资源

在结束之前,有一些重要的注意事项和推荐资源需要了解。

需要记住的一点是,HDF5可用于优化R中磁盘的读取和写入。这里的教程提供了关于如何使用HDF5的更多信息,本讲座借用了该教程中的大量信息。如果你对HDF5感兴趣,HDF5 Group通常有很多关于使用HDF5的良好信息。


总结 🎯

本节课中,我们一起学习了如何从HDF5文件中读取数据。我们从HDF5格式的基本概念讲起,介绍了其分层存储结构。然后,我们详细讲解了如何在R中通过Bioconductor安装RHDF5包。通过实际操作,我们创建了HDF5文件,在其中建立了组结构,并向组中写入了矩阵、多维数组和数据框等多种类型的数据。最后,我们学习了如何读取整个数据集或通过索引进行分块读取,并了解了HDF5在优化磁盘I/O方面的优势。掌握HDF5文件的读写能力,对于处理大型科学数据集至关重要。

085:从网络读取数据 📡

在本节课中,我们将学习如何从网络上读取数据。网络数据来源广泛,获取方式多样。本节课将主要聚焦于从网站中抓取数据(即网络爬虫),并简要介绍API的使用以及身份验证。后续会有专门课程深入讲解API获取数据的部分。


什么是网络爬虫? 🕸️

网络爬虫是指通过编程方式,从网站的HTML代码或URL中提取数据。这是一种获取数据的有效方法。

例如,有人曾通过爬虫收集了Netflix为所有电影分配的分类标签,并进行了有趣的分析,这个故事因此广为流传。这展示了通过编程从网站提取数据,可以收集到有趣的数据集。

可以说,几乎所有网站都包含你可能希望以编程方式读取的信息。然而,在某些情况下,这种行为可能违反网站的服务条款。有些网站明确声明不希望被爬取。如果你尝试从这些网站抓取数据,需自行承担风险。

如果你在短时间内尝试读取过多页面,一个非常常见的后果是你的IP地址可能会被封锁。如果你从网站读取了大量专有信息,可能会遇到更大的麻烦。因此,在决定从网站抓取数据时,需要谨慎。但总体而言,这是一种快速收集大量数据的好方法。


一个爬虫示例:Google Scholar页面 📄

我将通过一个示例来展示如何以编程方式提取数据。这个例子来自我的Google Scholar页面。

该页面展示了我发表的论文及其被引用次数,这是学术界关心的一类数据。

我们将首先使用readLines命令从互联网获取数据。这是一种方法。

以下是具体步骤:

  1. 使用url函数打开到特定URL的连接。
  2. 使用readLines函数从该连接中读取数据。
  3. 使用完毕后,务必像操作数据库一样关闭连接。
# 打开一个URL连接
con <- url("http://scholar.google.com/citations?user=HI-I6C0AAAAJ")
# 读取网页的HTML代码
htmlCode <- readLines(con)
# 关闭连接
close(con)

查看我们收集到的HTML代码,它会像这样显示:一个很长的大字符串。你可以指定readLines读取的行数,但输出仍然会以这种未格式化的方式呈现,阅读起来有些困难。


使用XML包解析HTML 🧩

处理上述问题的一种方法是使用XML包,就像我们之前见过的那样。

我们可以使用相同的URL,通过XML包解析HTML,并使用htmlTreeParse函数获取完整的结构。

library(XML)
url <- "http://scholar.google.com/citations?user=HI-I6C0AAAAJ"
html <- htmlTreeParse(url, useInternalNodes = TRUE)

然后,如果我想要获取页面的标题,可以查找<title>标签内的节点。或者,我可以通过查看表格的特定元素来获取论文的被引用次数。

# 获取页面标题
xpathSApply(html, "//title", xmlValue)
# 获取被引用次数(示例,路径需根据实际HTML调整)
# xpathSApply(html, "//table[@id='gsc_rsb_st']//td", xmlValue)

使用httr包获取数据 🔗

另一种获取数据的方法是使用GET命令,它来自httr包。

对于开放且易于访问的网站,使用我们之前讨论过的连接方式可能是最简单的方法。但我们稍后会讨论为什么httr包在其他场景下非常有用。

library(httr)
url <- "http://scholar.google.com/citations?user=HI-I6C0AAAAJ"
html2 <- GET(url)

现在,我需要从那个HTML页面中提取内容。我使用content函数将其作为文本(一个大文本字符串)提取出来。

content2 <- content(html2, as = "text")

然后,我可以使用htmlParse命令来解析该文本并获取解析后的HTML。

parsedHtml <- htmlParse(content2, asText = TRUE)

这样得到的parsedHtml对象,与直接使用XML包提取数据得到的结果完全相同。然后,我就可以像之前一样使用xpathSApply来提取页面标题等信息。


处理需要身份验证的网站 🔐

在某些情况下,你可能需要处理更复杂的事情。例如,如果你用浏览器导航到某个网页,可能会发现它需要输入用户名和密码。

如果我仅使用httr包中的GET命令尝试获取该页面,会得到一个状态码为401的响应。这是因为我没有通过身份验证,无法登录。

httr包允许你为网站进行身份验证。你可以通过分配一个“句柄”来实现。

# 使用用户名和密码进行身份验证
pg2 <- GET("http://httpbin.org/basic-auth/user/passwd",
           authenticate("user", "passwd"))

在这个例子中,这是一个测试网站,用户名是“user”,密码是“password”。你可以用此类网站测试访问权限。

现在,响应的状态码是200,这意味着我们成功获得了文件的访问权限,身份验证通过。

我们可以查看pg2对象的名称,它会给出所有不同的组件,包括我们为此文件获得的“cookies”以及我们用来访问它的“句柄”等。

然后,我们就可以在通过R登录后,使用content函数提取该网站的内容。

names(pg2)
content(pg2)

使用句柄管理会话 🎪

确保使用句柄,因为如果你使用句柄,你可以在多次访问网站时保存身份验证状态。

如果你将Google设置为一个句柄(代表特定网站),那么你可以告诉GET去获取那个句柄,并可以指定获取特定路径,或者发送不同的路径。

例如,如果你对这个句柄进行了一次身份验证,那么cookies将与该句柄保持关联,你将保持已认证状态。在访问该网站时,你无需反复进行身份验证。

# 创建一个句柄并认证
google <- handle("http://google.com")
pg1 <- GET(handle = google, path = "/")
pg2 <- GET(handle = google, path = "search")


更多学习资源 📚

R-bloggers网站有很多关于如何从网络抓取数据的好例子。如果你去R-bloggers上搜索“web scraping”,你将能找到所有这些教程。

httr包的帮助文件也包含许多有用的示例,主要是玩具示例。如果你将这些与R-bloggers上更实用的示例结合起来,可以学到很多东西。

此外,请关注后续关于API的课程,以了解如何以编程方式与网站的API进行交互。


总结 📝

本节课中,我们一起学习了从网络读取数据的几种核心方法:

  1. 我们了解了网络爬虫的概念及其潜在风险。
  2. 我们学习了使用基础R的readLines()函数和url()连接来读取原始网页数据。
  3. 我们掌握了使用XML包解析HTML结构,并利用XPath提取特定信息(如标题)。
  4. 我们介绍了功能更强大的httr包,它提供了GET()函数来发起网络请求。
  5. 我们探索了如何使用httr包的authenticate()函数访问需要身份验证的网站。
  6. 我们理解了使用句柄来管理网络会话和保持认证状态的重要性。

这些技能是获取网络公开数据或受保护数据的基础,为后续的数据分析工作提供了重要的数据来源。

086:从API读取数据 📡

在本节课中,我们将学习如何从API获取数据。API是应用程序编程接口,许多互联网公司(如Twitter或Facebook)都提供API,允许开发者下载特定数据。我们将重点介绍如何使用R语言的httr包,通过Twitter API获取数据。

什么是API?

API是应用程序编程接口。例如,Twitter或Facebook等互联网公司会提供API,用户可以通过它下载数据。你可以获取关于哪些用户在发推文、推文内容,或者人们在Facebook上发布的信息。通常,你可以通过向特定URL发送GET请求来获取这些数据。

我们将能够使用httr包从这些网站获取数据。

创建API应用账户

通常,你需要做的第一件事是创建一个账户。这里指的不是普通的用户账户,而是需要向特定组织(如Twitter)的开发团队或API注册一个应用账户。

例如,我们将访问 dev.twitter.com/app

我们将创建一个应用。这里,我将使用我的博客Twitter账号“simplystats”来创建一个应用。

现在,我们已经使用Twitter详细信息登录。你可以看到我以“simplystats”登录。我可以点击按钮创建一个新应用。这将为我提供一些数字,稍后我将用这些数字在R中验证应用并访问数据。

这里我已经创建了一个名为“simplystats blog”的应用。点击该应用,会进入一个类似这样的页面。这是应用页面,上面有应用的URL,还有一些关于我的组织和组织网站的信息我尚未填写。

在“OAuth设置”下方,我可以看到我对API拥有读写权限。这里有一系列数字,我已将它们涂黑,因为这些数字对你创建的应用是私密的。

但你会拥有自己的消费者密钥消费者密钥、请求令牌URL和授权URL。创建应用后,你需要记下所有这些数字,因为我们稍后将在R代码中使用它们来访问API。

在R中设置认证

以下是我们要做的步骤:我们将使用httr包。加载该包后,我们将使用oauth_app命令。这将启动你应用的授权过程。

你需要为应用命名(这不会发送给API,只是为了方便),我将其命名为“twitter”,因为它是Twitter应用。

然后,我将在这里输入我从应用网站获得的消费者密钥消费者密钥

接下来,我将使用从网站获取的令牌令牌密钥来签署并登录该应用。

完成这些步骤后,我就创建了能够访问Twitter私有数据的凭证,这些数据仅对拥有应用的人可用。

从API获取数据

现在,我将使用GET命令。这与我之前从互联网读取数据时所做的非常相似。我将使用一个非常特定的URL,这个URL对应Twitter API。URL的这一部分api.twitter.com/1.1表示我正在使用的API版本(1.1,这是Twitter API的当前版本)。

然后,我传递更多组件,这些组件对应我想要获取的数据。在本例中,我想获取我的主页时间线上的状态,并以JSON文件格式获取(这是Twitter目前唯一支持的数据格式)。

接着,我不使用用户名或密码进行身份验证,而是传递通过OAuth登录获得的认证信息。

我将得到的是对应此URL的页面,实际上就是一些JSON数据。

处理JSON数据

我可以使用content函数来提取JSON数据。content函数会识别出这是JSON数据,并使用RJSONIO包中的fromJSON函数。这将返回一个结构化的R对象,但可能有点难以阅读。

因此,我将使用jsonlite包将其重新格式化为数据框。具体做法是:获取从原始content命令中得到的JSON结构,将这个结构化的R对象转换回JSON,然后使用jsonlite版本的fromJSON参数来创建数据框。

现在,这个数据框的每一行都对应我主页时间线上的一条推文。

这里,我将查看这个数据框的第一行,它是我提取数据时主页时间线上最新的推文。我将查看前四列:推文创建的时间、推文的ID号、ID字符串,以及推文的实际文本内容。

这就是我从互联网收集数据时最新的推文。你可以通过这种方式从Twitter提取信息。这实际上只是如何收集关于我的主页时间线信息的请求,但你还可以进行许多其他请求,通过Twitter访问各种不同的数据。

查阅API文档

那么,我是如何知道该使用哪个URL的呢?你需要查阅Twitter API的文档。这是之前提到的同一个网站。

我将查看Twitter文档,我会看到这里写着“资源URL”,这就是我要传递给GET命令的URL。

文档下方还有许多参数。如果你查阅如何格式化发送给API的参数,你实际上可以发送许多不同的参数。例如,你可以告诉API要收集多少条推文、在什么时间收集等,具体取决于你访问的URL。

如果你想获取其他信息,不仅仅是关于主页时间线的信息,你可以访问Twitter的主要文档。这是API最新版本(1.1版)的文档,它会提供获取各种不同信息的方法。你可以获取关于提及、用户时间线、主页时间线、转推等所有类型的信息,也可以搜索Twitter feed,获取你感兴趣的特定标签或话题标签。

总结与扩展

总的来说,你必须查阅API的文档。httr包允许在授权的情况下进行GET、POST、PUT、DELETE请求。正如你所见,我被授权对“simply stats”的时间线进行读写操作,因为我是通过它创建应用的。

在某些情况下,你也可以使用用户名和密码进行身份验证。不过,大多数现代API(如我们刚才看到的Twitter)都使用类似OAuth的机制。httr包与Facebook、Google、Twitter、GitHub等配合良好。如果你访问GitHub上的httr演示组件,你可以看到许多如何访问不同网站API的示例。实际上,本讲座就是基于Twitter API的演示建模的。

本节课中,我们一起学习了API的基本概念、如何创建Twitter开发者应用并获取密钥、如何在R中使用httr包进行OAuth认证,以及如何发送GET请求获取并解析JSON格式的Twitter数据。掌握从API获取数据是数据科学中连接外部数据源的关键技能。

087:从其他来源读取数据 📂

在本节课中,我们将学习如何从多种非传统或特定格式的数据源中读取数据。我们已经介绍了从XML、JSON、HDF5或MySQL等来源读取数据,但R生态系统的强大之处在于其丰富的扩展包,几乎可以连接任何数据源。

概述

R社区开发了大量专用包,用于连接各种数据存储机制和文件格式。无论数据来自其他统计软件、特定数据库、图像文件还是音乐文件,通常都能找到对应的R包来读取和处理它们。本节将简要介绍一些有用的包和查找方法。

查找R包的方法

上一节我们介绍了从常见文件类型和数据库读取数据,本节中我们来看看如何为特定数据源寻找合适的R包。

我发现,查找某个R包是否存在的最佳方式是使用谷歌搜索,关键词格式为:数据存储机制 + R包。例如,搜索“MySQL R package”通常会直接显示最相关的包。对于相对常见的数据类型,几乎总能找到对应的R包来访问数据。

访问本地与远程文件连接

除了使用url()函数,R还提供了其他函数来建立与文件的连接。

以下是用于建立文件连接的核心函数:

  • file(): 用于访问本地计算机上已有文件的连接。
  • gzfile(): 用于访问经过gzip压缩的文件的连接。
  • bzfile(): 用于访问经过bzip2压缩的文件的连接。

如果你想了解更多关于建立本地或远程文件连接的信息,可以在R控制台中输入?connections查看帮助文档。

读取其他统计软件的数据

如果你需要与使用其他编程语言或统计软件(如SAS、SPSS、Stata)的同事协作,foreign包会非常有用。

foreign包的基本函数命名遵循read.加文件扩展名的模式。以下是几个例子:

  • read.dta(): 用于读取Stata(扩展名为.dta)的数据文件。
  • read.octave(): 用于读取Octave的数据文件。
  • read.spss(): 用于读取SPSS的数据文件。
  • 该包也能读取SAS的.export文件等。

关于此包的更多详细信息,可以查阅其帮助文件(?foreign),内容通常一目了然。借助它,我成功读取了来自其他编程语言的大部分数据文件。

连接其他数据库

除了MySQL,R还能连接许多其他类型的数据库。

以下是几个重要的数据库连接包:

  • RPostgreSQL: 提供从R连接到PostgreSQL数据库(一种与DBI兼容的数据库)的接口。第一个链接是相关教程,第二个是帮助文件。
  • RODBC: 提供连接到多种数据库的接口,包括PostgreSQL、MySQL、Microsoft Access和SQLite。这里也有一个很好的教程和帮助文件。
  • RMongoRMongoDB: 这两个包可用于连接MongoDB数据库并从中提取数据。

使用这些包时,你需要像使用RMySQL包一样,向数据库发送查询。关键是你必须使用数据库自身的语法来编写查询语句。因此,你需要对目标数据库的语法有基本了解才能提取数据。

读取非传统格式数据

R不仅能读取文本和表格数据,还能直接处理如图像、音乐等特殊格式。

你可以读取以下类型的非传统数据:

  • 图像文件: 使用jpegreadbitmappng等包可以直接将JPEG、PNG等图像文件读入R并进行操作。Bioconductor项目中的EBImage包也非常适合用于分析和处理图像数据。
  • GIS数据: 地理信息系统数据。有多种R包可以读取和处理由专有或开源软件导出的各种GIS数据。
  • 音乐文件: 你可以使用tuneRseewave包直接读取MP3等音乐文件数据并进行音频分析。R中有许多优秀的音乐处理软件包,可用于对这些数据进行分析并得出有趣的新发现。

总结

本节课中,我们一起学习了如何为各种数据源寻找和使用特定的R包。我们介绍了连接本地与远程文件的方法、读取其他统计软件数据的foreign包、连接PostgreSQL和MongoDB等数据库的包,以及处理图像、地理信息和音乐文件等非传统格式数据的工具。正如Roger在视频中所说,“万物皆可R包”。无论你的数据来自何处,通常只需搜索“数据源类型 + R package”,就能找到相应的工具来访问它。

088:数据子集化与排序 📊

在本节课中,我们将学习如何在R中操作数据,特别是如何对数据进行子集化(提取特定部分)和排序。这些技能对于整理数据、准备分析至关重要。我们将从基础概念开始,逐步介绍多种方法。

概述

将数据加载到R后,通常需要对其进行操作,以整理成整洁的数据格式:变量在列中,观测值在行中,并且只包含你想要分析的观测值。本节课程将回顾子集化的相关知识,并介绍排序的方法。

数据子集化

数据子集化是指从数据框(data frame)中提取特定的行、列或元素。以下是几种常用的方法。

按列提取

首先,我们可以通过指定列索引或列名来提取单个列。

  • 按索引提取:使用 x[, 1] 可以提取数据框 x 的第一列。
  • 按列名提取:使用 x[, "var2"] 可以提取名为 "var2" 的列。

同时按行和列提取

我们可以同时指定行和列的条件来提取数据的子集。

例如,命令 x[1:2, "var2"] 会输出数据框 x 的前两行,并且只提取 "var2" 这一列。

使用逻辑条件提取

通过逻辑语句可以提取满足特定条件的行。

  • 使用“与”条件:例如,要找出 variable1 小于等于3 并且 variable3 大于11的所有行,可以使用 x[x$var1 <= 3 & x$var3 > 11, ]
  • 使用“或”条件:例如,要找出 variable1 小于等于3 或者 variable3 大于15的所有行,可以使用 x[x$var1 <= 3 | x$var3 > 15, ]

处理缺失值(NA)

当数据包含缺失值(NA)时,直接使用逻辑条件子集化可能无法得到预期结果。which() 函数可以帮助解决这个问题。

which() 函数会返回满足条件的索引位置,并自动忽略NA值。例如,which(x$var2 > 8) 会返回 var2 大于8的行的索引,然后可以用这些索引来提取数据:x[which(x$var2 > 8), ]

数据排序

排序是另一种常见的数据操作,可以按一个或多个变量的值对数据进行排列。

对单个向量排序

使用 sort() 函数可以对一个向量进行排序。

  • 默认升序sort(x$var1) 会将 var1 的值按升序排列。
  • 降序排列:通过设置参数 decreasing = TRUE 可以实现降序排列:sort(x$var1, decreasing = TRUE)
  • 处理NA值:通过设置参数 na.last = TRUE,可以将所有NA值放在排序结果的末尾。

按列对数据框排序

要基于某一列的值对整个数据框的行进行重新排序,可以使用 order() 函数。

order() 函数返回的是排序后的索引顺序。例如,order(x$var1) 会返回一个索引向量,指示如何排列行才能使 var1 按升序排列。然后,将这个结果用于行的选择:x[order(x$var1), ]

按多列排序

可以传递多个变量给 order() 函数,实现多级排序。

例如,x[order(x$var1, x$var3), ] 会首先按 var1 升序排列,对于 var1 值相同的行,再按 var3 升序排列。

使用 dplyr 包排序

dplyr 包(由 Hadley Wickham 编写)提供了更直观的排序函数 arrange()

首先需要加载库:library(dplyr)

  • 升序排列arrange(x, var1) 会按 var1 升序排列数据框。
  • 降序排列:使用 desc() 函数包裹变量名可以实现降序排列:arrange(x, desc(var1))

添加行与列

向现有数据框中添加新的行或列是一个直接的过程。

添加新列

有两种主要方法:

  • 直接赋值x$var4 <- rnorm(5) 会创建一个名为 var4 的新列,并为其分配一个长度为5(与数据框行数相同)的随机正态分布向量。
  • 使用 cbind()cbind(x, rnorm(5)) 会将新的向量作为一列绑定到数据框 x 的右侧。如果交换顺序 cbind(rnorm(5), x),则新列会绑定到左侧。

添加新行

使用 rbind() 函数可以添加新行。

  • rbind(x, rnorm(3)) 会将新的向量作为一行绑定到数据框 x 的底部。
  • rbind(rnorm(3), x) 则会将新行绑定到顶部。

总结

本节课我们一起学习了R中数据操作的核心技能:子集化排序
我们掌握了如何通过索引、名称和逻辑条件提取数据的特定部分,特别是学会了使用 which() 函数处理包含缺失值的情况。
在排序方面,我们学习了使用 sort()order() 函数以及 dplyr 包中的 arrange() 函数对数据进行单列或多列排序。
最后,我们还了解了如何使用 cbind()rbind() 向数据框添加新的列和行。
掌握这些技巧能帮助你有效地整理和准备数据,为后续的分析工作打下坚实基础。

关于子集化、重排序和排序的更多详细说明,可以参考R编程课程中的资料。本节部分信息来源于约翰霍普金斯大学彭博公共卫生学院生物统计学系助理教授 Roger Peng 的课程讲义,你可以查阅他的讲义以获取更多信息。

089:数据汇总

在本节课中,我们将学习如何对加载到R中的数据进行检查和汇总。这是数据清洗的关键步骤,目的是在开始下游分析之前,识别数据中的异常、缺失值或其他问题。

加载示例数据

首先,我们需要一个数据集来练习。我们将使用一个关于巴尔的摩市餐厅信息的公开数据集。你可以通过指定URL来下载这个CSV格式的数据文件。

# 创建数据目录(如果不存在)
if(!file.exists("data")) {
  dir.create("data")
}
# 数据文件的URL
fileUrl <- "https://data.baltimorecity.gov/api/views/k5ry-ef3g/rows.csv?accessType=DOWNLOAD"
# 下载文件
download.file(fileUrl, destfile = "./data/restaurants.csv", method = "curl")
# 将数据读入R
restData <- read.csv("./data/restaurants.csv")

初步查看数据

数据加载后,第一步是查看其内容。直接输入数据框名称可以显示全部数据,但如果数据量很大,显示会不清晰。

查看数据头部和尾部

head()tail()函数可以分别查看数据框的开头和结尾部分。

# 查看前3行
head(restData, n=3)

# 查看后3行
tail(restData, n=3)

默认情况下,head()tail()会显示6行数据。

获取数据摘要

summary()函数可以为数据集中的每个变量提供统计摘要。

summary(restData)

对于文本型或因子型变量,它会显示每个类别的计数。对于数值型变量,它会显示最小值、第一四分位数、中位数、均值、第三四分位数和最大值。这个函数能快速揭示问题,例如,我们发现数据中有一个邮政编码被错误地记录为负值。

查看数据结构

str()函数可以显示数据框的结构信息。

str(restData)

它会告诉你对象的类别(如数据框)、维度,以及每一列的数据类型(如因子、整数等)。这有助于你判断是否需要转换变量类型。

深入分析变量

上一节我们介绍了查看数据整体情况的方法,本节中我们来看看如何对特定变量进行更深入的分析。

分析数值变量

quantile()函数用于查看数值变量的分布情况。

# 查看“councilDistrict”变量的分位数
quantile(restData$councilDistrict)

你可以指定不同的概率来查看对应的百分位数。

# 查看0%、50%(中位数)和99%分位数
quantile(restData$councilDistrict, probs = c(0, 0.5, 0.99))

创建频数表

table()函数可以为分类变量创建频数表。

# 创建邮政编码的频数表
table(restData$zipCode)

这能让你快速了解每个类别的数量分布。默认情况下,table()函数会忽略缺失值(NA)。为了确保能发现缺失值,需要使用useNA参数。

# 创建包含缺失值计数的频数表
table(restData$zipCode, useNA = "ifany")

创建二维交叉表

你还可以创建二维交叉表,以探索两个分类变量之间的关系。

# 按“councilDistrict”和“zipCode”创建交叉表
table(restData$councilDistrict, restData$zipCode)

检查缺失值与数据完整性

在了解了如何查看数据分布后,我们需要系统地检查数据的完整性和正确性。

检查缺失值

is.na()函数可以检测缺失值。结合sum()any()函数使用非常方便。

# 检查“councilDistrict”列是否有缺失值,并计算缺失数量
sum(is.na(restData$councilDistrict))

# 检查“councilDistrict”列是否存在任何缺失值
any(is.na(restData$councilDistrict))

any()函数检查向量中是否存在TRUE值(即是否存在NA)。all()函数则检查是否所有值都满足某个条件。

# 检查是否所有邮政编码都大于0(我们发现有一个负值)
all(restData$zipCode > 0)

跨列检查缺失值

colSums()rowSums()函数可以对矩阵或数据框的行或列进行求和。结合is.na()使用,可以快速检查整个数据集的缺失情况。

# 检查数据框每一列的缺失值数量
colSums(is.na(restData))

# 确认所有列的缺失值数量都为0
all(colSums(is.na(restData))==0)

数据子集与高级汇总

基于条件筛选数据

你可以使用逻辑条件来创建数据的子集。%in%操作符在匹配多个值时特别有用。

# 找出邮政编码为21212或21213的餐厅
zipCodes <- c("21212", "21213")
restData_subset <- restData[restData$zipCode %in% zipCodes, ]

创建交叉汇总表

对于更复杂的数据,可以使用xtabs()函数创建加权交叉表。我们以R内置的UCBAdmissions数据集为例。

# 加载伯克利大学录取数据
data("UCBAdmissions")
df <- as.data.frame(UCBAdmissions)

# 创建按性别和录取状态交叉汇总的频率表
xtabs(Freq ~ Gender + Admit, data = df)

xtabs()公式的左边(Freq)是表中要显示的值,右边(Gender + Admit)是用于分类的变量。

处理高维表格

当涉及多个分类变量时,交叉表会变得复杂。ftable()函数可以将高维表格“扁平化”,使其更易于阅读。

# 使用Warpbreaks数据集创建三维交叉表
data("warpbreaks")
# 添加一个虚拟的复制变量作为示例
warpbreaks$replicate <- rep(1:9, len = 54)
# 创建交叉表
xt <- xtabs(breaks ~ ., data = warpbreaks)
# 扁平化显示
ftable(xt)

检查数据大小

最后,在处理大型数据集时,了解其内存占用情况很有帮助。object.size()函数可以查看对象的大小。

# 创建一个示例数据
D <- data.frame(x=rnorm(100), y=rnorm(100))
# 查看对象大小(字节)
object.size(D)
# 以更易读的单位(如MB)打印大小
print(object.size(D), units="Mb")

总结

本节课中我们一起学习了在R中初步探索和汇总数据的一系列核心技能。我们首先学习了如何使用head()tail()summary()str()来快速了解数据的全貌和结构。接着,我们掌握了使用quantile()table()分析变量分布,以及使用is.na()any()all()colSums()来系统检查数据完整性和缺失值的方法。最后,我们探讨了如何使用逻辑条件和%in%操作符创建数据子集,以及利用xtabs()ftable()进行高级交叉汇总,并使用object.size()评估数据大小。这些工具是进行有效数据清洗和准备工作的基础。

090:创建新变量 🧮

在本节课中,我们将学习如何在R语言中创建新的变量。加载数据集后,有时会发现原始数据中并不包含我们需要的某些变量。因此,我们需要对数据进行一些转换,以得到所需的值。通常,我们会将这些新值添加回数据框中,这在预测等任务中尤为重要,因为我们需要创建可能用作预测因子的新变量。

上一节我们介绍了数据加载的基本概念,本节中我们来看看如何通过创建新变量来丰富和转换数据。

创建序列

在深入分析数据之前,首先介绍如何创建序列。序列常用于索引对数据执行的不同操作,因此了解如何创建它们很有用。在R中,创建序列的命令是 seq

通常,你需要告诉 seq 最小值和最大值。有两种常用方法来指定生成多少个值:一种是使用 by 参数,另一种是指定向量的长度。

以下是创建序列的几种方法:

  • 使用 by 参数seq(1, 10, by=2) 会创建一个从1开始,每次增加2的序列。
  • 指定长度seq(1, 10, length=3) 会创建一个从1开始,到10结束,且恰好包含3个值的序列。
  • 使用 seq_along:如果你有一个向量 x,并想创建一个与之等长的索引向量用于循环或访问数据子集,可以使用 seq_along(x)

创建子集指示变量

你可能需要创建一个变量,用于指示另一个变量属于哪个子集。例如,在餐厅数据集中,我们有餐厅所在的社区信息。假设我想找出我附近两个社区(Roland Park 和 Homeland)的所有餐厅。

可以使用 %in% 命令来查找仅属于这两个社区的餐厅。如果餐厅在指定社区中,则返回 TRUE,否则返回 FALSE。然后,我将这个名为 nearme 的新变量附加到餐厅数据框中。这样,我就可以方便地根据 nearme 变量来筛选出附近的餐厅,这是一种比反复使用 %in% 命令更简洁的数据子集筛选方法。

# 假设 restData 是数据框,neighborhood 是社区列
restData$nearme <- restData$neighborhood %in% c("Roland Park", "Homeland")

创建二元变量

另一个常见操作是创建二元变量。例如,我们可能想找出邮政编码错误的案例。

这里,我向数据框分配一个名为 zipWrong 的变量,并使用 ifelse 命令。ifelse 命令首先接收一个条件(例如,邮政编码是否小于0)。如果条件为真(邮政编码小于0),则返回 TRUE;如果条件为假(邮政编码为正数),则返回 FALSE

这样,我就可以创建一个表格来显示邮政编码是否错误。所有邮政编码小于0的案例都会标记为 TRUE,这便于我将这些值从数据集中过滤掉。

restData$zipWrong <- ifelse(restData$zipCode < 0, TRUE, FALSE)
table(restData$zipWrong, restData$zipCode < 0)

将定量变量转换为分类变量

你可能希望将定量变量转换为分类变量。例如,我们可能想将邮政编码分成连续的几组。

这里,我创建一个名为 zipGroups 的变量,使用 cut 命令。我将 cut 命令应用于定量变量“邮政编码”,并告诉它按照我提供的一组值(在本例中是邮政编码的分位数)进行分割。

cut 命令将返回一个因子变量。zipGroups 就是一个因子变量,它将邮政编码变量分割成:第0分位数到第25百分位数、第25到第50百分位数、第50到第75百分位数、第75到第100百分位数。

然后,我可以制作一个表格,显示哪些邮政编码落入这些不同的分组中。例如,低值落入第一组,中等值落入下一组,依此类推。

restData$zipGroups <- cut(restData$zipCode, breaks=quantile(restData$zipCode))
table(restData$zipGroups)

使用 cut2 函数简化分组

在之前的 cut 函数版本中,我必须用 quantile 函数指定所有分割点。如果你使用 Hmisc 包中的 cut2 函数,可以更简单地实现。

使用 cut2 函数,你可以直接指定想将邮政编码分成多少组(例如4组),并指定按分位数分割。cut2 函数会自动找出分位数并按此将数据分成4组。如果你不想预先设置分割点,这是一种很好的方法。

# 需要先安装并加载 Hmisc 包
# install.packages("Hmisc")
library(Hmisc)
restData$zipGroups <- cut2(restData$zipCode, g=4)
table(restData$zipGroups)

创建因子变量

你可能需要创建因子变量。例如,加载到R中的邮政编码变量是整数变量,但你可能想将其转换为因子变量。换句话说,邮政编码增加1并不一定意味着变量发生了定量变化,因此你可能想将其视为分类变量。

使用 factor 命令可以实现这一点。它接收一个整数变量作为输入,并将其转换为一个名为 zcf 的因子变量。查看 zcf 的前10个值,它们看起来和以前一样,但 factor 会告诉你有多少个不同的邮政编码水平(例如32个)。查看这个变量的类别,它现在是因子变量。

restData$zcf <- factor(restData$zipCode)
class(restData$zcf)

因子变量的其他操作

以下是关于因子变量的其他一些有用操作。首先,我创建一个虚拟向量来演示。

我创建一个包含“yes”和“no”的向量,随机生成一个大小为10的向量,并将其转换为因子变量。默认情况下,它会按字母顺序将最低值(“no”)作为因子变量的第一个水平。

但是,假设我希望将“yes”值作为最低水平(参考类)。那么,我可以在创建因子时指定水平的顺序。例如,我可以重新设定该变量的水平,使参考类等于“yes”值。

另外,如果你希望在特定模型中使用因子变量,可以使用 as.numeric 命令将其转换回数值变量。这样做时,它会从最低值开始,将其标记为1,下一个最低值标记为2,依此类推。

yesno <- sample(c("yes", "no"), size=10, replace=TRUE)
yesnofac <- factor(yesno, levels=c("yes", "no"))
relevel(yesnofac, ref="yes")
as.numeric(yesnofac)

使用 mutate 函数添加新变量

你可以使用 mutate 函数创建一个新版本的变量,并同时将其添加到数据框中。这是 plyr 包(或 dplyr 包)的一部分。

这里,我将创建一个新的数据框。我将对旧数据框应用 mutate 函数,并添加一个新变量 zipGroups,它等于原始 restData 数据框中某个变量的函数。现在,新数据框 restData2 将是旧的 restData 数据框加上新添加的变量。这样,我就可以在新数据框中查看 zipGroups 的表格。

# 需要先安装并加载 dplyr 包
# install.packages("dplyr")
library(dplyr)
restData2 <- mutate(restData, zipGroups=cut2(zipCode, g=4))
table(restData2$zipGroups)

对定量数据进行常见变换

在探索性分析中,你可能需要对定量数据应用一些不同的变换。以下是最常见的变换列表:

  • 绝对值与平方根abs()sqrt()
  • 取整函数
    • ceiling():向上取整
    • floor():向下取整
    • round(x, digits=n):四舍五入到指定位数
    • signif(x, digits=n):四舍五入到指定有效数字位数
  • 三角函数cos()sin()
  • 对数与指数
    • log()log2()log10():常用于处理偏态数据或存在许多离群值的数据。
    • exp():指数函数

关于数据变换的更多信息,可以参考课程中提供的链接,以及 Jackie 的讲义和 R 的 Math 方法页面。

总结

本节课中,我们一起学习了在R中创建新变量的多种方法。我们从创建用于索引的序列开始,然后学习了如何创建子集指示变量和二元变量来标记或筛选数据。接着,我们探讨了将定量变量转换为分类变量的技术,包括使用 cutcut2 函数进行分组,以及使用 factor 函数创建因子变量。我们还介绍了 mutate 函数,它可以高效地在数据框中添加新变量。最后,我们回顾了对定量数据进行探索性分析时常用的一些数学变换。掌握这些技能,能够帮助你更灵活地处理和准备数据,以满足后续分析和建模的需求。

091:重塑数据 🧩

在本节课中,我们将学习如何重塑R语言中的数据。数据在加载到R中时,其格式往往并非“整洁”的,可能以某种奇怪的方式排列。因此,我们首先需要将数据重塑成我们想要的格式。

什么是整洁数据?📐

上一节我们提到了数据重塑的目标是获得整洁数据。整洁数据有几项核心原则:

  • 每个变量构成一列。
  • 每个观测构成一行。
  • 每个表格或文件只存储一种观测类型的数据。

在本节的数据重塑中,我们将重点关注前两个原则。

数据重塑基础:熔化与重铸 🔥

我们将从查看R中的内置数据集 mtcars 开始。这个数据集包含多辆汽车(行)及其多个观测变量(列),如每加仑英里数(mpg)、气缸数(cyl)和马力(hp)等。接下来,我们将对这个数据集进行重塑。

熔化数据

首先,我们可以“熔化”数据。熔化数据时,我们将数据框传递给 melt 函数,并指定哪些是标识变量,哪些是度量变量。

以下是使用 melt 函数的示例:

# 假设我们使用reshape2包
library(reshape2)
carMelt <- melt(mtcars, id.vars=c("car_name", "gear", "cyl"), measure.vars=c("mpg", "hp"))

这段代码会创建一个高而瘦的数据框。标识变量(如car_name, gear, cyl)会保留,而度量变量(如mpg, hp)会被“熔化”到一个名为“variable”的列中,其对应的值则在“value”列中。这样,每个mpg值和每个hp值都会独占一行。

重铸数据

熔化数据后,我们可以用多种方式“重铸”它,将其重新格式化成不同的形状。这需要使用 dcast 函数。

dcast 函数将熔化后的数据集重铸为特定形状的数据框。例如,如果我们想按不同变量查看气缸数的汇总:

dcast(carMelt, cyl ~ variable)

这段代码会将不同的气缸数放在左侧的行中,将变量(mpg和hp)放在右侧的列中。默认情况下,它会使用 length 函数汇总数据,显示每个气缸数下mpg和hp的观测数量。

我们也可以传递不同的汇总函数,例如计算每个组的平均值:

dcast(carMelt, cyl ~ variable, mean)

现在,对于四缸汽车,我们可以得到平均每加仑英里数是26.66,平均马力是82.64。这本质上是将数据以不同方式重新汇总和组织。

分组汇总:拆分-应用-合并 🔄

另一个常见需求是计算特定因子内的汇总值(如平均值、总和)。我们将使用 InsectSprays 数据集进行演示,该数据集包含不同杀虫喷雾(A-F)下的昆虫计数。

使用 tapply 快速汇总

一种快速计算各喷雾组昆虫计数总和的方法是使用 tapply 函数:

tapply(InsectSprays$count, InsectSprays$spray, sum)

tapply 函数沿着索引(spray)应用一个函数(sum)到数据(count)上,从而得到A、B、C等各组的昆虫总数。

完整的拆分-应用-合并流程

另一种更明确的方法是“拆分-应用-合并”模式:

  1. 拆分:使用 split 函数按喷雾类型拆分数据。
    spl <- split(InsectSprays$count, InsectSprays$spray)
    
  2. 应用:使用 lapply 对拆分后的列表应用 sum 函数。
    sprSum <- lapply(spl, sum)
    
  3. 合并:使用 unlist 将列表结果合并回向量。
    unlist(sprSum)
    

sapply 函数可以合并“应用”和“合并”这两个步骤:

sapply(spl, sum)

使用 plyr 包简化操作 📦

plyr 包为这类操作提供了一个简洁的接口。我们可以使用 ddply 函数一步完成分组汇总:

# 假设我们已安装并加载plyr包
library(plyr)
ddply(InsectSprays, .(spray), summarize, sum=sum(count))

这段代码对 InsectSprays 数据框,按 spray 变量分组,汇总 count 列的和。

ddply 的另一个强大功能是,它可以计算与原始数据集长度相同的新变量。例如,为每个观测值计算其所属喷雾组的总计数:

spraySums <- ddply(InsectSprays, .(spray), mutate, sum=sum(count))

现在,spraySums 数据框中新增的 sum 列,对于每个属于A组的观测,其值都是A组的总计数。这个新变量可以添加到原数据中,用于后续分析。

更多资源与函数 📚

关于重塑数据的更多信息,可以参考 plyr 包的教程。此外,还有一些其他有用的函数:

  • acast:与 dcast 类似,但将熔化后的数据转换为数组(可能多维)。
  • arrange:用于对数据集重新排序,无需使用之前课程中提到的 order 命令。
  • 可以结合使用 ddplymutate 来添加基于原变量汇总的新变量。

总结

本节课中,我们一起学习了在R中重塑数据的关键技术。我们首先理解了整洁数据的原则,然后掌握了通过“熔化”与“重铸”来改变数据形状的方法。接着,我们探索了按组汇总数据的多种策略,包括快速的 tapply、清晰的“拆分-应用-合并”流程以及高效的 plyr 包操作。这些技能将帮助你有效地将原始数据整理成适合分析的格式。

092:使用 dplyr 管理数据框入门 🚀

在本节课中,我们将学习 R 语言中一个专门用于处理数据框的强大工具包——dplyr。我们将介绍该包的基本概念和核心“动词”,包括 arrangefilterselectmutaterename。通过学习这些函数,你将能够以更高效、更直观的方式操作和管理数据。

概述:dplyr 与数据框

dplyr 包的核心是处理数据框。数据框是 R 语言和统计学中关键的数据结构。该包遵循“整洁数据”的基本假设:在一个数据框中,每一行代表一个观测,每一列代表一个变量、度量或特征。

dplyr 包由 RStudio 的 Hadley Wickham 开发,可以看作是 plyr 包的优化和精简版本。它在概念上并未提供全新的功能,但极大地简化了 R 中许多现有功能的操作,并且运行速度更快,这得益于其许多关键操作是用底层的 C++ 代码实现的。

核心“动词”简介

dplyr 提供了一系列核心函数,我们称之为数据操作的“动词”。以下是本课程将介绍的主要动词:

  • select:返回数据框列的子集。
  • filter:提取数据框行的子集。
  • arrange:对数据框的行进行重新排序。
  • rename:重命名变量(列)。
  • mutate:转换或向数据框添加新变量。
  • summarize:生成数据框的汇总统计量。

所有 dplyr 函数都具有相似的格式:第一个参数总是一个数据框,随后的参数则描述要对该数据框执行什么操作。每个函数都会返回一个新的数据框。因此,确保你的数据框格式正确、标注清晰(例如,因子水平已标注、变量名完整)对于有效使用这些函数至关重要。

函数详解与使用

上一节我们介绍了 dplyr 的核心“动词”,本节中我们来看看它们的具体用法。每个函数都设计得简单直观。

1. select:选择列

select 函数用于根据列名选择数据框中的特定列。

语法

select(data_frame, column1, column2, ...)

示例:假设有一个名为 my_data 的数据框,包含 idnameagesalary 列。

# 只选择 name 和 age 列
new_data <- select(my_data, name, age)

2. filter:筛选行

filter 函数用于根据逻辑条件筛选出满足特定条件的行。

语法

filter(data_frame, logical_condition)

示例:筛选出 age 大于 30 的行。

# 筛选年龄大于30的员工
older_employees <- filter(my_data, age > 30)

3. arrange:排列行

arrange 函数用于根据一列或多列的值对数据框的行进行升序或降序排序。

语法

arrange(data_frame, column_to_sort_by)
# 降序排列
arrange(data_frame, desc(column_to_sort_by))

示例:按 salary 降序排列数据。

# 按薪资从高到低排列
sorted_data <- arrange(my_data, desc(salary))

4. rename:重命名列

rename 函数用于更改数据框中一个或多个列的名称。

语法

rename(data_frame, new_name = old_name)

示例:将列名 id 改为 employee_id

# 重命名id列
renamed_data <- rename(my_data, employee_id = id)

5. mutate:创建新列

mutate 函数用于添加新的列,或基于现有列转换生成新的列。

语法

mutate(data_frame, new_column = expression_based_on_old_columns)

示例:创建一个新列 annual_salary,其值为 monthly_salary 列乘以 12。

# 计算年薪
my_data <- mutate(my_data, annual_salary = monthly_salary * 12)

总结

本节课中,我们一起学习了 R 语言 dplyr 包的基础知识。我们了解到 dplyr 是专门为高效、直观地操作数据框而设计的工具包。我们重点介绍了五个核心“动词”:select(选择列)、filter(筛选行)、arrange(排序行)、rename(重命名列)和 mutate(创建新列)。这些函数具有一致的语法结构,第一个参数是数据框,并总是返回一个新的数据框。掌握这些基础函数,将为你在 R 中进行数据清洗、转换和探索性分析打下坚实的基础。

093:使用dplyr管理数据框的基本工具 📊

在本节课中,我们将学习如何使用R语言中的dplyr包来高效地管理和操作数据框。dplyr提供了一系列直观且强大的函数,可以简化数据筛选、排序、重命名、创建新变量以及分组汇总等常见任务。

概述

首先,你需要加载dplyr包。加载时可能会看到一些警告信息,这是因为有其他函数与dplyr中的函数同名,但这暂时无需担心。

library(dplyr)

我们将使用一个关于芝加哥市1987年至2005年空气污染和天气变量的示例数据集。该数据集包含6940行和8列。

# 假设数据已加载为`chicago`数据框
dim(chicago) # 查看数据维度
head(chicago) # 查看前几行数据

选择列:select函数

select函数允许你根据列名选择数据框中的特定列,而无需使用列索引。

以下是使用select函数的一些方法:

  • 按名称范围选择:你可以使用:符号选择从某个列到另一个列之间的所有列。

    select(chicago, city:dptp)
    

    这段代码会选择从city列到dptp(露点)列之间的所有列。

  • 排除特定列:使用-符号可以排除指定的列。

    select(chicago, -(city:dptp))
    

    这段代码会选择除citydptp列之外的所有列。

使用select函数直接引用列名,比在基础R中查找列索引再进行子集操作更加简洁和易读。

筛选行:filter函数

上一节我们介绍了如何选择列,本节中我们来看看如何根据条件筛选行。filter函数用于基于逻辑条件对数据框的行进行子集筛选。

  • 单条件筛选:例如,筛选出PM2.5大于30的所有行。

    filter(chicago, pm25tmean2 > 30)
    
  • 多条件组合筛选:你可以使用逻辑运算符组合多个条件。

    filter(chicago, pm25tmean2 > 30 & tmpd > 80)
    

    这段代码会筛选出同时满足PM2.5大于30温度高于80度的行。

select类似,filter函数也允许你直接使用变量名,使得代码意图非常清晰。

排列行:arrange函数

arrange函数用于根据一列或多列的值对数据框的行进行重新排序。

  • 升序排列:默认按升序排列。

    arrange(chicago, date)
    

    这会将数据按date变量从早到晚排列。

  • 降序排列:使用desc()函数进行降序排列。

    arrange(chicago, desc(date))
    

    这会将数据按date变量从晚到早排列。

重命名变量:rename函数

rename函数可以轻松地修改变量(列)的名称,格式为新名称 = 旧名称

chicago <- rename(chicago,
                  dewpoint = dptp,
                  pm25 = pm25tmean2)

这段代码将dptp列重命名为dewpoint,将pm25tmean2列重命名为pm25

创建新变量:mutate函数

mutate函数用于在数据框中创建新的变量,或转换现有的变量。

chicago <- mutate(chicago,
                  pm25detrend = pm25 - mean(pm25, na.rm = TRUE))

这段代码创建了一个名为pm25detrend的新列,其值是PM2.5原始值减去该列的均值(即中心化处理)。

分组与汇总:group_bysummarize函数

group_bysummarize函数通常结合使用,用于对数据进行分组并计算各组的汇总统计量。

首先,我们创建一个分类变量,例如根据温度是否超过80华氏度将日期分为“热”和“冷”。

chicago <- mutate(chicago,
                  tempcat = factor(tmpd > 80,
                                   labels = c("cold", "hot"),
                                   levels = c(FALSE, TRUE)))

接着,使用group_by函数按这个分类变量对数据进行分组。

hotcold <- group_by(chicago, tempcat)

最后,使用summarize函数计算每个温度类别下各污染物的统计量。

summarize(hotcold,
          pm25 = mean(pm25, na.rm = TRUE),
          o3 = max(o3tmean2, na.rm = TRUE),
          no2 = median(no2tmean2, na.rm = TRUE))

你还可以按其他变量分组,例如按年份汇总。

chicago <- mutate(chicago, year = as.POSIXlt(date)$year + 1900)
yearly <- group_by(chicago, year)
summarize(yearly,
          pm25 = mean(pm25, na.rm = TRUE),
          o3 = max(o3tmean2, na.rm = TRUE),
          no2 = median(no2tmean2, na.rm = TRUE))

管道操作符:%>%

管道操作符%>%允许你将多个数据操作步骤连接成一个清晰的序列。它的作用是将左侧的结果传递给右侧的函数作为第一个参数。

以下是一个使用管道操作符的完整示例,它完成了创建月份变量、按月份分组、然后汇总的连续操作:

chicago %>%
  mutate(month = as.POSIXlt(date)$mon + 1) %>%
  group_by(month) %>%
  summarize(pm25 = mean(pm25, na.rm = TRUE),
            o3 = max(o3tmean2, na.rm = TRUE),
            no2 = median(no2tmean2, na.rm = TRUE))

使用管道操作符可以避免创建大量中间变量,使代码更易读、更流畅。

dplyr的其他优势

dplyr的设计使其能够与不同的数据后端协同工作。除了标准的R数据框,你还可以:

  • 使用data.table包处理超大型数据集。
  • 通过DBI包连接SQL关系型数据库,并使用相同的dplyr语法进行查询和操作。

这意味着,一旦你熟练掌握了dplyr,就可以将技能几乎无缝地应用到其他数据库环境中。

总结

本节课中我们一起学习了dplyr包的核心功能。我们掌握了如何使用select选择列、filter筛选行、arrange排序、rename重命名、mutate创建新变量,以及如何通过group_bysummarize进行分组汇总。最后,我们还了解了强大的管道操作符%>%如何将多个操作优雅地串联起来。这些工具共同构成了在R中进行高效数据操作的基础。

094:20_合并数据 🧩

在本节课中,我们将学习如何在R语言中合并多个数据集。这是数据分析中一项非常常见的任务,尤其是在处理来自不同来源但彼此关联的数据时。

概述

有时,你会将多个数据集加载到R中,并希望将它们合并在一起。通常,你需要基于一个共同的ID来匹配这些数据集。这类似于在MySQL这样的数据库中链接多个表的概念。

数据背景介绍

我们将要使用的数据来自一项同行评审实验。这项实验旨在模拟科学研究中同行评审系统的工作方式。我们收集了两类数据:一类是参与者提交的SAT类问题的答案,另一类是评审者对答案进行评审的记录。

我们可以从作者的Dropbox账户下载这些数据。数据集包含两个文件,分别对应两个数据框:一个记录了研究中所有被解答的问题,另一个记录了所有对这些问题的评审记录。

使用 merge 命令合并数据

首先,我们读取这两个数据集。reviews 数据框包含一个名为 solution_id 的变量,它与 solutions 数据集中的 id 变量相对应。solutions 数据中还有 problem_idsubject_id,它们将与其他数据集匹配,但本示例中不涉及。

merge 命令是R基础包中用于合并数据框的函数。它的关键参数是 xy,分别代表两个要合并的数据框。你可以使用 byby.xby.y 参数来指定基于哪些列进行合并。默认情况下,它会尝试合并所有具有相同名称的列。

例如,在我们的数据中,如果使用默认设置,merge 会尝试合并 idstartstoptime_left 等列,因为这些变量名在两个数据框中都存在。然而,这些变量在两个数据集中的含义可能并不相同,这会导致错误的合并。

因此,我们需要明确告诉 merge 函数应该基于哪些变量进行合并。以下是具体的操作步骤:

  1. 指定 xreviews 数据框,ysolutions 数据框。
  2. 使用 by.x = "solution_id"by.y = "id" 来指明匹配的列。
  3. 设置 all = TRUE,这意味着如果一个值只出现在一个数据框中,合并后的数据框仍会包含该行,并在缺失值的位置用 NA 填充。

执行合并后,我们可以看到新的数据框以 solution_id 排序,并包含了来自两个原始数据集的所有信息,从而可以作为一个整体进行分析。

使用 plyr 包中的 join 函数

除了基础的 merge 命令,你还可以使用 plyr 包中的 join 函数来合并数据集。join 的速度通常比 merge 更快,但功能相对较少。它只能基于两个数据集中具有相同名称的列进行合并。

例如,如果我们有两个虚构的数据框,它们有一个共同的标识符 ID,但行的顺序不同。我们可以使用 join 将它们合并,然后使用 arrange 命令按 ID 排序。

以下是使用 join 的一个优势场景:

当你有多个数据框需要合并,并且它们拥有一个共同的ID时,使用 plyr 包会非常方便。你可以将所有数据框放入一个列表中,然后使用 join_all 命令一次性合并所有数据集。这种方法比多次调用 merge 命令更简洁高效。

合并类型与注意事项

在开始密集地进行数据合并之前,有必要了解不同类型的连接操作,例如左连接、右连接等。学习这些概念的最佳途径之一是阅读关于数据库连接(如SQL连接)的资料。R的帮助页面(?merge)和 plyr 包的文档也提供了关于合并数据的详细信息。

总结

本节课中,我们一起学习了在R中合并数据集的两种主要方法:

  1. 使用基础的 merge() 函数,它可以进行灵活的、基于指定列的合并。
  2. 使用 plyr 包中的 join()join_all() 函数,它们在处理多个具有共同列名的数据框时更加快捷。

理解并正确应用数据合并技术,对于整合多源数据、进行深入分析至关重要。

095:编辑文本变量 📝

在本节课中,我们将学习如何在R语言中编辑和清理文本变量。数据清洗的一个常见步骤是处理格式混乱的文本变量,例如变量名或数据值中可能包含多余的空格、句点或下划线。我们将学习如何通过编程方式操作这些文本,以获得整洁、规范的变量名和数据集内容。


将变量名转换为小写

上一节我们介绍了数据清洗的重要性,本节中我们来看看如何规范化变量名的大小写。

查看巴尔的摩固定摄像头数据集的列名时,你可能会发现某些变量名包含大写字母,例如 crosstreet 中的 S。虽然看似小事,但在输入时容易因大小写不一致而导致错误。一个简单的解决方案是将所有字母转换为小写。

在R中,可以使用 tolower() 函数来实现这一点。该函数接收一个字符向量,并返回一个所有字母均为小写的版本。

代码示例:

names(cameraData) <- tolower(names(cameraData))

如果需要将字母全部转换为大写,也可以使用对应的 toupper() 函数。


拆分包含分隔符的变量名

有时,变量名可能包含句点等分隔符,例如 location.1。这通常发生在从外部文件加载数据时。为了获得更清晰的变量名,我们需要拆分这些名称。

R中的 strsplit() 函数可以用于拆分字符串。我们需要指定要拆分的字符串和分隔符。由于句点在正则表达式中是特殊字符,我们需要使用转义字符 \\

代码示例:

splitNames <- strsplit(names(cameraData), "\\.")

执行后,splitNames 是一个列表。例如,原始变量名 location.1 会被拆分为 "location""1" 两个部分。


理解列表与提取元素

在进一步处理拆分后的名称之前,我们需要快速回顾一下R中列表的用法,因为后续函数依赖于对列表的操作。

列表可以包含多种类型的元素。我们可以通过索引或名称来访问列表中的元素。

代码示例:

myList <- list(letters = c("A", "b", "c"), numbers = 1:3, matrix(1:4, 2, 2))
myList[[1]]        # 提取第一个元素(字母向量)
myList$letters     # 通过名称提取元素
myList[[1]][1]     # 提取第一个元素中的第一个值

移除变量名中的句点

回到拆分变量名的问题,我们的目标通常是只保留句点之前的部分。例如,对于 location.1,我们只想要 "location"

我们可以编写一个简单的函数来提取列表每个元素的第一个部分,然后使用 sapply() 函数将其应用到整个列表上。

代码示例:

firstElement <- function(x){x[1]}
names(cameraData) <- sapply(splitNames, firstElement)

这样,变量名 location.1 就变成了 location,句点被成功移除。


替换变量名中的字符

接下来,我们使用同伴互评实验数据来展示其他文本操作技巧。

该数据集中,变量名如 solution_idreviewer_id 包含下划线。为了获得更整洁的变量名,我们希望移除这些下划线。

sub() 函数可以用于替换字符串中第一个匹配到的模式。gsub() 函数则用于替换字符串中所有匹配到的模式。

以下是具体操作步骤:

  1. 使用 sub() 替换第一个下划线:

    sub("_", "", names(reviews))
    

    对于 test_name,这只会移除第一个下划线。

  2. 使用 gsub() 替换所有下划线:

    gsub("_", "", names(reviews))
    

    这会移除变量名中所有的下划线,得到如 solutionid 这样的名称。

这些函数同样适用于清理数据集内部的实际文本值。


在数据中搜索特定值

有时,我们需要在变量中搜索包含特定文本的行。例如,在摄像头数据中,找出所有交叉路口包含 "Alameda" 的记录。

grep()grepl() 函数可以用于此目的。

  • grep():返回匹配项在向量中的索引位置

    grep("Alameda", cameraData$intersection)
    # 可能返回:4, 5, 36
    
  • grepl():返回一个逻辑向量(TRUE/FALSE),指示每个元素是否匹配。

    grepl("Alameda", cameraData$intersection)
    # 返回:FALSE, FALSE, FALSE, TRUE, TRUE, ... , TRUE
    

我们可以利用 grepl() 的结果来对数据进行子集筛选。

代码示例:

# 筛选出交叉路口包含"Alameda"的数据
cameraData_Alameda <- cameraData[grepl("Alameda", cameraData$intersection), ]

此外,将 grep() 的参数 value 设置为 TRUE,可以直接返回匹配的文本值,而不是索引。

代码示例:

grep("Alameda", cameraData$intersection, value=TRUE)

如果要检查某个值是否出现在向量中,可以检查 grep() 返回结果的长度是否为0。

代码示例:

length(grep("JeffStreet", cameraData$intersection)) == 0

其他有用的字符串函数

除了搜索和替换,还有一些基础函数和 stringr 包中的函数对处理文本很有帮助。

以下是几个常用函数:

  • nchar():计算字符串中的字符数。

    nchar("Jeffrey Leek") # 返回 12
    
  • substr():提取字符串的子串。

    substr("Jeffrey Leek", 1, 7) # 返回 "Jeffrey"
    
  • paste() / paste0():连接多个字符串。

    paste("Hello", "World") # 返回 "Hello World"(默认用空格分隔)
    paste0("Hello", "World") # 返回 "HelloWorld"(无分隔符)
    

  • str_trim() (来自 stringr 包):去除字符串开头和结尾的空格。
    library(stringr)
    str_trim("  Hello World  ") # 返回 "Hello World"
    

关于文本数据的要点总结

本节课中我们一起学习了多种编辑文本变量的技术。最后,总结一下处理数据集文本时的一些重要原则:

  • 统一小写:尽可能使用全小写变量名和值,避免因大小写不一致导致的隐蔽错误。
  • 描述性命名:变量名应具有描述性(例如用 diagnosis 而非 dx)。
  • 避免特殊字符:变量名不应包含下划线、句点或空格。我们已经学习了如何移除它们。
  • 合理使用因子:对于具有固定类别数的字符型变量,通常应转换为因子变量,并确保其水平值是描述性的。
  • 清晰的取值标签:例如,使用 "male"/"female""TRUE"/"FALSE",而不是 "M"/"F"1/0。这能使数据分析过程更清晰易懂。

遵循这些原则,可以显著提高数据集的整洁度和后续分析的效率。

096:正则表达式 I 🔍

在本节课中,我们将要学习正则表达式的基础知识。正则表达式是一种强大的工具,用于在文本中搜索和匹配特定模式。我们将从基本概念开始,逐步介绍如何组合字面量和元字符来构建灵活的搜索模式。


概述

我们已经学习了如何使用文本函数 subgsubgrep 来操作字符串并识别可能感兴趣的文本模式。在本节中,我们将探讨正则表达式,它将搜索特定文本片段的概念扩展为搜索可能符合更广泛模式的文本片段。


正则表达式的组成

正则表达式可以看作是字面量元字符的组合。

为了与自然语言类比,可以将字面量文本视为构成语言的单词,而元字符则定义了其语法。正则表达式拥有一套丰富的元字符,使我们能够搜索字符串,识别那些仅用字面量很难找到的特定兴趣模式。


字面量匹配

字面量由完全匹配的单词组成。

例如,字面量 nuclear 会匹配所有包含该单词的行,因为 nuclear 在这些行中精确出现。

同样,字面量 Obama 会匹配所有包含该单词的行。你可以精确匹配你观察到的文本。


引入元字符

在正则表达式的最简单形式中,只包含字面量。只有当被测试的文本中任何地方出现完全相同的字面量序列时,才会发生匹配。

但是,如果我们只想匹配单词 Obama,或者以单词 Clinton 结尾的句子,或者像 Clintoninto 这样更短的短语呢?

我们需要一种方法来表达空白、单词边界、字面量集合、行的开头和结尾,或者像 warpeace 这样的替代项。因此,我们将使用元字符来指定这些更通用的搜索项,以便能够识别它们。


常用元字符

以下是正则表达式中常用的一些元字符及其功能:

匹配行首

元字符 ^ 代表行的开始。

例如,正则表达式 ^I think 会匹配所有以 “I think” 开头的行。它不会匹配 “I think” 出现在行中间的情况。

匹配行尾

元字符 $ 代表行的结束。

例如,正则表达式 morning$ 会匹配所有以 “morning” 结尾的行。如果 “morning” 只出现在文本中间,则不会匹配。

匹配字符集合

我们可以考虑在匹配中任何给定点接受的字符列表或集合。

例如,正则表达式 [Bb][Uu][Ss][Hh] 会匹配单词 “bush” 的所有版本,无论哪些字母被大写。它将匹配每个可能字母处的小写和大写形式。

组合元字符

你可以开始组合这些元字符。

例如,^[Ii] am 会匹配以大写或小写 “I” 开头,后跟字面量 “am” 的行。它允许行的开头是小写或大写的 “I”,然后是字面量 “M”。

匹配字符范围

你还可以指定字符或字母的范围。

例如,[a-z] 会匹配任何小写字母 a 到 z。[a-zA-Z] 会查找任何字母,无论大小写。

例如,正则表达式 ^[0-9][a-zA-Z] 会在行首查找一个 0 到 9 之间的数字,后跟任意一个字母作为该行的下一个字母。它会匹配所有以数字开头且下一个字符是字母的行。

排除字符类

在字符类的开头使用 ^ 也是一个元字符,表示它应该匹配不在指定类中的任何字符。

例如,正则表达式 [^\.?]$ 会查找任何不以句点或问号结尾的行。方括号内的 ^ 表示“不是这两个字符中的任何一个”。它会匹配所有不以句点或问号结尾的行,例如以感叹号结尾或没有标点符号的行。


总结

本节课中,我们一起学习了正则表达式的基础知识。我们了解了正则表达式由字面量和元字符组成,并探索了几个关键的元字符:用于匹配行首的 ^、匹配行尾的 $、用于定义字符集合的方括号 [],以及用于排除字符的 [^]。通过组合这些元素,我们可以构建强大的模式来搜索和匹配文本中的复杂结构。掌握这些基础是有效使用正则表达式的第一步。

097:正则表达式 II 🔍

在本节课中,我们将继续学习正则表达式,深入探讨更多元字符的用法,包括点号、或运算符、括号、问号、星号、加号以及花括号等。这些工具能帮助我们构建更强大、更灵活的文本匹配模式。


上一节我们介绍了正则表达式的基本概念和部分元字符。本节中,我们来看看更多用于构建复杂匹配模式的元字符。

点号元字符 .

点号 . 是一个元字符,代表任意一个字符

例如,模式 9.11 会匹配所有包含数字9,后接任意一个字符,再接着数字11的文本。

公式表示: 9.11 匹配 9 + 任意字符 + 11

因此,它能匹配以下所有短语,因为每个例子中,9和11之间都恰好由一个字符分隔。

或运算符 |

或运算符 | 用于组合两个不同的表达式,这两个子表达式称为“备选项”。

以下是其基本用法:

  • flood|fire 会匹配包含“flood”“fire”的文本行。

你可以包含任意数量的备选项。

  • 例如,flood|earthquake|hurricane|cold fire 能匹配包含其中任何一个词的文本行。



备选项本身也可以是复杂的正则表达式,而不仅仅是字面文本。

  • 例如,模式 ^(Good|good)|bad 表示:匹配行首的“Good”或“good”,或者匹配行中任意位置的“bad”。

使用括号 () 分组

括号 () 可以用来对表达式进行分组,从而限制备选项的范围。

  • 模式 ^(good|bad) 表示:匹配行首的“good”“bad”。这里,^ 的作用范围被括号限定,因此“bad”也必须在行首才会被匹配。

问号 ? 表示可选

如果问号 ? 紧跟在括号分组后面,则表示该分组内的表达式是可选的

  • 模式 [Ww]\.? Bush 会匹配“George Bush”,无论中间是否有“W.”。这里的 \.? 表示点号是可选的。注意,点号本身是元字符,要匹配字面意义的点号,需要使用反斜杠进行转义 \.



星号 * 和加号 +

星号 * 表示前面的元素可以重复任意次数(包括零次)
加号 + 表示前面的元素必须至少出现一次

以下是具体示例:

  • \(.*\) 会匹配所有括号及其中的内容。因为 .* 表示“任意字符重复任意次数”,所以它能匹配从 (something)() 的所有情况。

  • [0-9]+.*[0-9]+ 会匹配至少一个数字,后接任意字符,再后接至少一个数字。这可以用来查找被非数字字符分隔的数字组合。

花括号 {} 区间限定符

花括号 {} 作为区间限定符,允许我们指定一个表达式匹配的最小和最大次数

以下是其语法规则:

  • {m,n}:匹配至少 m 次,至多 n 次。
  • {m}:精确匹配 m 次。
  • {m,}:匹配至少 m 次。

例如,模式 Bush( +[^ ]+){1,5} debate 的匹配逻辑如下:

  1. 查找“Bush”。
  2. 随后查找“一个或多个空格 + 一个非空格字符”这个组合。
  3. 要求这个组合出现 1 到 5 次。
  4. 最后以“debate”结尾。
    这可以匹配“Bush”和“debate”之间包含1到5个单词的句子。




括号分组与反向引用 \1, \2...

在大多数正则表达式实现中,括号 () 不仅用于分组,还能“记住”括号内子表达式匹配到的文本。我们可以使用 \1, \2 等来引用这些被记住的文本,这称为反向引用

  • 例如,模式 +([a-zA-Z]+) +\1 会匹配一个空格,后接一个或多个字母(被括号捕获),再接一个空格,最后要求出现刚才捕获的完全相同的字母序列。这可以用来查找重复的单词或短语,如“night night”、“blah blah”。

贪婪匹配与非贪婪匹配

默认情况下,量词(如 *+)是贪婪的,它们会匹配尽可能长的字符串。

  • 模式 ^s.*s$ 会匹配以‘s’开头、以‘s’结尾的整个字符串,因为它会吞掉中间的所有字符。

如果想进行非贪婪(尽可能短)匹配,可以在量词后面加上问号 ?

  • 模式 ^s.*?s$ 会匹配以‘s’开头、以‘s’结尾的最短可能字符串



总结 📝

本节课中我们一起学习了正则表达式更高级的元字符和概念:

  1. 点号 .:匹配任意单个字符。
  2. 或运算符 |:提供多个匹配备选项。
  3. 括号 ():对表达式进行分组和限定范围,并可用于捕获文本以供反向引用。
  4. 问号 ?:使前面的元素变为可选,或将贪婪量词变为非贪婪。
  5. 星号 * 和加号 +:分别表示重复任意次(含零次)和至少一次。
  6. 花括号 {}:精确指定匹配次数区间。
  7. 反向引用 \1:重用之前括号捕获的文本。
  8. 贪婪与非贪婪匹配:理解了默认的贪婪行为以及如何使用 ? 进行非贪婪匹配。

正则表达式是用于文本处理的强大工具,它由字面文本元字符共同构成语法。在处理非结构化或格式不友好的文本数据时(如日志文件、网页抓取内容),正则表达式尤其有用。在R语言中,我们通常将正则表达式与 grep()grepl()sub()gsub() 等函数结合使用,来进行字符串的搜索、提取和替换操作。

098:R语言中的日期处理

在本节课中,我们将学习如何在R语言中处理日期数据。日期数据有时会显得棘手,因为它们具有各种特性,例如在一周或一个月结束后重新开始计数。我们将从基础函数开始,逐步介绍如何创建、格式化、转换和计算日期,并介绍一个强大的扩展包来简化这些操作。

1️⃣ 基础日期函数

首先,我们来看R语言中最基础的日期函数 date()

current_time <- date()
print(current_time)

调用 date() 函数会返回一个表示当前日期和时间的字符型变量。例如,这可能是幻灯片被处理的时间点。

class(current_time)
# 输出: "character"

date() 函数返回的是一个字符型变量,它只是简单地以文本形式提供日期和时间信息。

2️⃣ Sys.Date() 函数

然而,处理日期并不总是这么简单。R语言提供了更专门的日期函数。

today <- Sys.Date()
print(today)

Sys.Date() 函数返回一个看起来像日期的对象,但通常不包含具体时间。更重要的是,它的Date

class(today)
# 输出: "Date"

Date 类变量具有特殊的属性,这使得它在分析日期数据时比纯文本更方便,但在某些方面处理起来也需要更多技巧。

3️⃣ 格式化日期

我们经常需要将日期转换为特定的显示格式。这时可以使用 format() 函数。

formatted_date <- format(today, "%a %b %d")
print(formatted_date)
# 输出示例: "Sun Jan 12"

format() 函数允许我们使用特定的代码来定制日期格式:

  • %a 表示缩写的星期几。
  • %b 表示缩写的月份。
  • %d 表示月份中的日期(数字)。

以下是更多常用的格式代码:

  • %A: 完整的星期几名称。
  • %m: 月份(01-12)。
  • %Y: 四位数的年份(如 2023)。
  • %y: 两位数的年份(如 23)。

你可以组合这些代码,将日期转换成任何你喜欢的、视觉上美观的格式。

4️⃣ 将字符转换为日期

我们经常需要将存储在字符向量中的日期转换为 Date 类型,以便进行后续计算。

date_strings <- c("1jan2014", "2jan2014", "31mar2014", "30jul2014")
converted_dates <- as.Date(date_strings, format = "%d%b%Y")
print(converted_dates)

as.Date() 函数是关键。你需要通过 format 参数告诉R,你的字符中哪部分是日(%d)、月(%b)和年(%Y)。

将字符转换为 Date 类的好处是,你可以轻松地对日期进行各种操作和计算。

5️⃣ 日期计算与提取信息

一旦日期被正确存储为 Date 类,我们就可以进行数学运算和提取特定信息。

计算日期差:

date1 <- converted_dates[1]
date2 <- converted_dates[2]
time_diff <- date1 - date2
print(time_diff)
# 输出: Time difference of -1 days

你可以将日期相减,得到以天为单位的差值。这个差值可以很容易地转换为数值变量。

提取日期组成部分:

weekdays(date1)   # 输出星期几,例如 "Sunday"
months(date1)     # 输出月份,例如 "January"
julian(date1)     # 输出儒略日(自原点1970-01-01以来的天数)
  • weekdays(): 返回日期对应的星期几。
  • months(): 返回日期对应的月份。
  • julian(): 返回自特定原点(通常是1970年1月1日)以来的天数。

6️⃣ 使用 lubridate 包简化操作

lubridate 包让日期处理变得比基础函数 as.Date() 更加简单和直观。

首先加载包:

library(lubridate)

lubridate 提供了一系列智能函数,能自动解析多种常见日期格式,而无需预先指定格式:

ymd("20140108")      # 解析 年-月-日 格式
# 输出: "2014-01-08"

mdy("01/08/2014")    # 解析 月-日-年 格式
# 输出: "2014-01-08"

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/5d09860c62b2d5bdb0328e323b5c8238_23.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/5d09860c62b2d5bdb0328e323b5c8238_24.png)

dmy("3-4-2013")      # 解析 日-月-年 格式
# 输出: "2013-04-03"

它的优势在于,你通常不需要在转换前严格统一字符的格式。

7️⃣ 处理日期时间

lubridate 同样能优雅地处理包含时间的日期时间数据。

ymd_hms("2014-01-08_22:30:15")

这个函数会解析“年-月-日_时:分:秒”格式的字符串,并创建一个包含日期和时间的对象。

如果时间部分的顺序不同,也有对应的函数:

ymd_hms("2014-01-08_15:30:00") # 时:分:秒
# 如果顺序是 秒:分:时,可以使用(但标准顺序更常见)

你还可以设置时区:

ymd_hms("2014-01-08 22:30:15", tz = "America/New_York")

要了解更多关于R中时区设置的信息,可以查看帮助文档:?Sys.timezone

8️⃣ lubridate 与基础函数的区别

一些函数在 lubridate 和基础R中的名称和用法略有不同。

获取星期几:

  • 基础R: weekdays(date_object)
  • lubridate: wday(date_object)

my_date <- ymd("2014-01-08")
wday(my_date)                # 返回数字(1=周日,7=周六),例如 4
wday(my_date, label = TRUE)  # 返回缩写的星期几名称,例如 "Wed"

lubridate 中,使用 wday() 并设置 label = TRUE 可以直接得到星期几的名称。

9️⃣ 总结与扩展资源

本节课中,我们一起学习了R语言中处理日期的基础和进阶方法。

核心要点总结:

  1. 使用 Sys.Date() 获取当前日期(Date 类)。
  2. 使用 format() 函数自定义日期显示格式。
  3. 使用 as.Date() 将字符转换为日期,但需指定格式。
  4. 强烈推荐使用 lubridate(如 ymd(), mdy() 函数),它能自动解析多种日期格式,极大简化工作。
  5. 日期转换为 Date 类后,可进行减法运算(计算间隔天数)和使用 weekdays()months() 等函数提取信息。
  6. 处理含时间的日期时,使用 ymd_hms() 等函数并注意时区设置。

最终目标是让你的日期数据成为 POSIXctPOSIXlt 类(lubridateas.Date 的某些输出即是),这对于后续进行与日期相关的建模和预测分析至关重要。你可以通过 ?POSIXlt 查看更详细的信息。

扩展学习:

  • 更多关于 lubridate 包的详细教程和示例,可以参考其官方 vignettevignette(“lubridate”))或相关的在线教程。
  • 本节课的部分示例和思路来源于一个非常优秀的 lubridate 教程。

099:数据资源 📚

在本节课中,我们将学习如何寻找和获取免费的数据资源,以便在没有现成数据的情况下进行分析。

上一节我们介绍了数据获取的重要性,本节中我们来看看一些具体的数据来源。

政府与组织公开数据 🌐

以下是一些提供公开数据的政府与国际组织网站:

  • 联合国数据:网站为 data.un.org,提供全球性的统计数据。
  • 美国数据门户:网站为 data.gov,提供美国政府的公开数据。
  • 其他地区数据:通过相关博客文章可以找到美国许多城市和州的开放数据门户。
  • 全球数据门户索引data.gov/open-data-sites 网站提供了世界各国政府开放数据站点的信息,是查找本国数据资源的起点。

专题数据网站 📊

除了政府数据,还有一些专注于特定领域的优秀数据平台。

Gapminder 网站(gapminder.org)提供了大量关于全球发展、人类健康等方面的数据集,非常适合用于社会与健康议题的分析。

调查数据与处理指南 📝

对于需要调查数据的研究者,美国的相关调查数据是重要资源。

以下网站不仅提供了数据访问途径,还详细介绍了如何在 R 语言中处理和操作这些通常庞大而复杂的数据集,这对数据分析实践非常有帮助。

数据市场与竞赛平台 🏆

以下平台汇集了各类数据集,部分免费,部分付费。

  • InfoChimps Marketplace:提供众多数据集,可按标签分类浏览,可用于发现感兴趣的数据。
  • Kaggle:一个举办数据科学竞赛的公司,通常会提供与竞赛相关的、非常有趣的数据集。这些数据既可用于练习,也可能帮助解决公司的实际问题。

数据科学家精选集 👨‍🔬

一些知名数据科学家也整理并分享了高质量的数据集。

例如,Hilary Mason、Jeff Hammerbacher 等人整理的研究级数据集就非常有用。这些资源通常来自相关的博客文章,其中也汇总了其他数据科学家精选的数据集。

专业领域数据存档 🧬

还有许多更专业的数据库,专注于特定学科领域。

以下是几个著名的专业数据存档:

  • 斯坦福大型网络数据集存档:专注于网络数据和机器学习领域的数据集。
  • UCI机器学习仓库:提供多种可用于分类或预测练习的数据集。
  • CMU StatLib:最著名、最经典的数据集集合之一。
  • 基因表达综合数据库:专注于人类基因组实验或其他生物基因组实验的数据集。
  • 亚马逊网络服务公共数据集:在AWS上托管的一系列公共数据集。

利用API获取数据 🔌

通过本课程的学习,你已经掌握了使用API获取数据的方法。现在有许多R语言包可以简化这一过程。

例如,twitteR 包可以让你更便捷地访问Twitter API,而无需自己从头搭建应用程序。同样,你可以通过专门的包获取Figshare的数据或《PLOS ONE》等出版物的数据。

rOpenSci 项目提供了大量优秀的R包,用于访问专注于学术研究的各类数据源。此外,也有专门用于访问Facebook和Google Maps数据的R包。

所有这些资源意味着,对于任何你感兴趣的项目,你都能找到真实的数据进行分析,没有任何借口。


本节课中我们一起学习了多种免费数据资源的获取途径,包括政府公开数据、专题数据库、竞赛平台、专家精选集、专业领域存档以及利用API获取数据的方法。掌握这些资源将为你未来的数据分析项目打下坚实的基础。

100:探索性数据分析 📊

在本课程中,我们将学习探索性数据分析(EDA)的基本概念和工具。这是数据科学流程中,在建模、预测或推断之前,对数据进行初步审视和理解的关键步骤。


概述

大家好,欢迎来到探索性数据分析课程。这是数据科学专项课程的第四门课。至此,大家应该已经熟悉了数据科学家工具箱中的基本工具。

大家应该至少熟悉了R语言编程的基础,包括编写函数和对数据进行基本操作。我们也应该已经涵盖了从互联网和各种API获取数据以及处理数据的各个方面。

现在,大家可以想象,你已经获得了数据集,并经过了各种处理步骤,是时候审视它,看看里面发生了什么。因此,本课程的目的是审视你的数据,了解其中的情况,了解你想要制作哪些类型的图表,以及在没有关于数据内部情况的精确或特定模型的情况下,你可以通过哪些方式来探索数据。

这是在建模、预测或进行任何推断之前的工作。我们将讨论R中的绘图系统,讨论制作分析图表的基本原则,并通过一些探索性数据分析的案例研究,让大家了解这类工作在实际中是如何进行的。

希望你们喜欢这门课程,期待在课程中见到大家。相信这将为大家在数据科学专项课程的后续学习中打下坚实的基础。


课程内容

上一节我们概述了本课程的目标和定位,本节中我们来看看我们将要学习的核心内容。

以下是本课程将涵盖的主要模块:

  1. R语言绘图系统:介绍R中用于数据可视化的主要系统。
  2. 分析图表制作原则:学习创建有效、清晰的分析图表的核心准则。
  3. 探索性数据分析案例研究:通过实际案例,了解EDA在真实场景中的应用。

总结

本节课中我们一起学习了探索性数据分析(EDA)的课程介绍。我们明确了EDA在数据科学流程中的位置——它是在数据清洗之后、正式建模之前的关键步骤,旨在通过可视化等手段初步理解数据特征。后续课程将深入讲解具体的绘图工具、设计原则和实践案例。

101:在 Windows 系统上安装 R 🖥️

在本节课中,我们将学习如何在 Windows 操作系统的电脑上安装 R 语言环境。R 是进行数据科学分析的重要工具,安装它是我们学习的第一步。


概述

我们将通过访问 R 的官方镜像网络(CRAN)来下载安装程序,并逐步完成安装过程。整个过程包括启动浏览器、访问网站、下载文件、运行安装向导以及进行一些简单的初始设置。


启动浏览器并访问 CRAN

首先,你需要启动你的网页浏览器。你可以使用 Chrome、Firefox 或 Edge 等任何你习惯的浏览器。

启动浏览器后,你需要访问 综合 R 档案网络,即 CRAN。在浏览器的地址栏中输入网址:https://cran.r-project.org/

选择 Windows 版本并下载

进入 CRAN 网站后,你会看到页面顶部有三个主要操作系统的选项:Linux、Mac 和 Windows。

你需要点击 “Download R for Windows” 这个链接。

进入 Windows 下载页面后,点击 “base” 子标题下的链接,这通常是推荐给大多数用户的版本。点击后,安装程序的下载将自动开始。下载速度取决于你的网络连接。

运行安装程序

下载完成后,找到并双击下载好的 .exe 文件以启动安装向导。

安装向导启动后,首先会提示你选择安装语言。你可以从列表中选择你熟悉的语言,例如选择“English”。选择后点击“OK”。

接下来,你将看到欢迎界面。点击“Next”继续。

同意许可协议

随后,安装程序会显示 R 的许可协议,即 GNU 通用公共许可证。你可以阅读该协议,了解相关条款。

阅读完毕后,勾选“我同意”选项,然后点击“Next”。

选择安装位置

安装程序会建议一个默认的安装目录,通常是 C:\Program Files\R\。对于大多数用户来说,使用这个默认路径即可,无需更改。

直接点击“Next”继续。

选择安装组件

在这一步,你可以选择要安装的组件。默认选项是安装 32 位和 64 位 两个版本,这能确保兼容性,是推荐的选择。

除非你明确知道你的电脑是较旧的纯 32 位系统,否则请保持默认设置,然后点击“Next”。

自定义启动选项

现在,你可以选择是接受所有默认设置,还是自定义启动选项。为了让你了解有哪些选择,我们点击“Customize startup”来自定义。

  1. 选择界面风格:这里询问你偏好 MDI 还是 SDI 界面。

    • MDI:R 运行在一个主窗口内,所有子窗口(如控制台、图形)都包含在这个主窗口里。
    • SDI:控制台、图形窗口等会以独立的窗口形式出现。许多人觉得这样操作更方便。在本教程中,我们选择 SDI 选项。
  2. 选择帮助文件格式:你可以选择以 HTML 格式还是 纯文本 格式查看帮助文档。HTML 格式更美观易读,而纯文本格式更简洁。你可以根据喜好选择,这里我们选择“Plain text”。

  3. 网络访问设置:通常保持默认的“Standard”选项即可,无需修改。

完成这些选择后,点击“Next”。

创建快捷方式

安装程序会询问你是否在 开始菜单 中创建程序文件夹,这通常是个好主意,方便你快速找到 R。

它还会询问是否在 桌面 创建快捷方式图标。除非你的桌面非常杂乱,否则建议创建,以便快速启动。

接受这些默认设置,点击“Next”。

开始安装并完成

现在,安装程序将开始将必要的文件复制到你的电脑上。这个过程可能需要几分钟。

安装完成后,你会看到“安装完成”的提示。点击“Finish”退出安装向导。

启动 R

安装完成后,你可以在桌面上找到 R 的快捷方式图标。双击这个图标,R 的控制台窗口就会启动。

恭喜你,现在 R 已经成功安装并运行在你的 Windows 电脑上了!


总结

本节课中,我们一起学习了在 Windows 系统上安装 R 的完整流程。我们首先通过浏览器访问 CRAN 官网,下载了 Windows 版本的安装程序。接着,我们一步步运行安装向导,同意了许可协议,选择了安装路径和组件,并自定义了启动界面风格和帮助文档格式。最后,我们创建了快捷方式并成功启动了 R。现在,你的电脑已经准备好了运行 R 代码,为后续的数据科学学习打下了基础。

102:在 Mac 上安装 R 🍎

在本节课中,我们将学习如何在 Mac 操作系统上安装 R 语言环境。整个过程非常简单,只需几个步骤即可完成。

概述

我们将通过访问 R 的官方下载网站,下载适用于 Mac 的安装包,并跟随安装向导完成整个安装过程。安装完成后,你可以在“应用程序”文件夹中找到并使用 R。

安装步骤

以下是安装 R 的具体步骤。

1. 访问下载网站

首先,你需要打开你的网页浏览器,并访问 CRAN(The Comprehensive R Archive Network)的官方网站。这是 R 语言的综合归档网络,提供了针对不同操作系统的下载选项。

2. 选择 Mac 版本

在 CRAN 网站上,找到并选择为 Mac 平台提供的下载链接。点击该链接,开始下载 R 的安装包文件(通常是一个 .pkg 文件)。

3. 下载安装包

点击下载链接后,下载进度条会开始运行。下载时间取决于你的网络连接速度,请耐心等待。

4. 运行安装程序

下载完成后,找到并打开下载的 .pkg 安装包文件。这将启动 R 的安装向导。

5. 跟随安装向导

安装向导会引导你完成所有必要的步骤:

  • 点击继续,查看将要安装的内容描述。
  • 阅读并同意软件许可协议(GNU 通用公共许可证第 2 版)。
  • 点击安装,系统可能会要求你输入管理员密码以授权安装。

6. 完成安装

安装过程结束后,点击“关闭”。此时,R 就已经成功安装到你的电脑上了。

启动 R

现在,你可以前往 Mac 的“应用程序”文件夹。按字母顺序找到 “R” 应用图标,双击即可启动 R。

后续步骤(可选)

你可以直接使用这个基础的 R 应用。如果你希望获得更强大的集成开发环境,可以考虑后续安装 RStudio 等图形界面工具,它们能提供更好的代码编辑、管理和可视化体验。

总结

本节课中,我们一起学习了在 Mac 电脑上安装 R 语言环境的完整流程。我们经历了从访问官网下载安装包,到运行安装程序并完成设置的每一步。现在,你的 Mac 已经准备好了运行 R 代码,可以开始后续的数据科学学习之旅了。

103:在 Mac 上安装 RStudio 🍎

在本节课中,我们将学习如何为 R 语言安装一个名为 RStudio 的集成开发环境。RStudio 能显著提升使用 R 的体验,提供许多基础 R 系统不具备的便利功能。

概述

我们将首先介绍 RStudio 是什么,然后逐步演示如何在 Mac 操作系统上下载和安装它。最后,我们会快速浏览 RStudio 的主要界面和基本功能,帮助你快速上手。

什么是 RStudio?

RStudio 是一个交互式开发环境,通常简称为 IDE。它能够与 R 语言连接,并提供一系列基础 R 系统本身不具备的实用功能,让 R 的使用体验更加友好。

安装 RStudio 的步骤

在安装 RStudio 之前,你需要确保已经成功安装了 R 语言。完成 R 的安装后,即可按照以下步骤安装 RStudio。

以下是详细的安装流程:

  1. 打开浏览器,访问 RStudio 的官方网站:rstudio.org
  2. 在网站上找到并点击“Download RStudio”按钮。
  3. 选择下载“RStudio Desktop”版本。网站通常会自动识别你的操作系统类型。
  4. 根据你的系统选择对应的安装包。RStudio 支持 Windows XP、Vista、7 以及多种 Linux 发行版。对于 Mac 用户,请下载推荐的 Mac 版本。
  5. 下载完成后,打开下载的安装文件。
  6. 将 RStudio 的图标拖拽到“应用程序”文件夹中。如果系统提示替换旧版本,选择确认替换即可。

启动与界面介绍

安装完成后,你可以在“应用程序”文件夹中找到并启动 RStudio。

RStudio 的界面主要分为四个窗口,每个窗口都有其特定功能。

以下是四个主要窗口的说明:

  • 左下角窗口:这是 R 控制台。你可以在这里直接输入并执行 R 命令。例如,输入 x <- rnorm(100) 并按 Cmd + Enter 执行,可以创建一个名为 x 的数值向量。
  • 左上角窗口:这是脚本编辑器。你可以在这里编写和编辑 R 代码文件,然后有选择地运行部分或全部代码。
  • 右上角窗口:这是工作空间浏览器。它会显示当前 R 会话中创建的所有对象。例如,执行 x <- rnorm(100) 后,你会在这里看到一个名为 x 的向量及其简要信息。
  • 右下角窗口:这是一个多功能面板。它包含文件浏览器、图形显示、已安装包列表和帮助文档查看器。
    • 执行绘图命令(如 hist(x))后,生成的图表会显示在这里。
    • 点击“Packages”标签页,可以查看已安装和已加载的包。
    • 输入 help(hist) 等帮助命令后,相应的帮助文档会在此处显示。

总结

本节课我们一起学习了如何在 Mac 上安装和初步使用 RStudio。我们了解到 RStudio 是一个强大的 R 语言集成开发环境,它通过四个功能分明的窗口——控制台、脚本编辑器、工作空间和多功能面板——极大地便利了代码编写、数据管理和结果查看的过程。对于本课程的学习,使用 RStudio 将会是一个非常有用的工具。

104:设置工作目录与编辑代码文件 🖥️

在本节课中,我们将学习如何在 Windows 系统中设置 R 的工作目录,以及如何使用 R 内置的文本编辑器来编写和运行代码。掌握这些基础操作是高效使用 R 进行数据分析的前提。

理解工作目录 📂

上一节我们介绍了 R 的基本界面,本节中我们来看看如何管理你的工作环境。首先,你需要理解什么是工作目录。工作目录是 R 在你的计算机上查找和保存文件的默认位置。当你使用如 read.csvwrite.csv 这类函数时,R 会默认在工作目录中读取或写入文件。

你可以使用 getwd() 函数来查看当前的工作目录设置。

getwd()

执行后,你可能会看到类似 C:/Users/YourName/Documents 的路径。这个路径因你的 Windows 版本和硬件配置而异,但通常位于 C 盘。

设置工作目录 🔧

了解并正确设置工作目录至关重要。如果文件不在工作目录中,R 将无法找到它并报错。例如,尝试读取一个不存在的文件:

read.csv("my_data.csv")

你会收到一个错误,因为 my_data.csv 不在当前工作目录中。解决这个问题有两种方法:将文件移动到工作目录,或者将工作目录更改为文件所在的路径。

以下是更改工作目录的步骤:

  1. 在 R 的菜单栏中,点击 File
  2. 选择 Change dir...
  3. 在弹出的窗口中,导航到你希望设置为工作目录的文件夹(例如桌面上的一个文件夹)。
  4. 点击 确定

之后,再次使用 getwd() 确认路径已更改。

为了方便本课程的学习,建议你创建一个专用文件夹(例如命名为 Coursera)来存放所有课程资料。这样,你可以将工作目录永久设置为此文件夹,避免文件散落各处。

使用 R 编辑器编写代码 ✍️

在本课程中,你需要大量编写 R 代码。R 自带一个基础的文本编辑器,完全满足本课程的需求。

你可以通过以下步骤打开编辑器:

  1. 点击菜单栏的 File
  2. 选择 New script

这将打开一个空白的编辑窗口,你可以在其中编写代码。例如,我们编写一个简单的函数:

my_function <- function() {
  x <- rnorm(100)
  mean(x)
}

将代码加载到 R 控制台 ⬇️

编写完代码后,需要将其加载到 R 控制台中才能使用。主要有两种方法:

方法一:复制粘贴
对于少量代码,你可以:

  1. 在编辑器中使用 Ctrl + A 全选代码。
  2. 使用 Ctrl + C 复制。
  3. 切换到控制台,使用 Ctrl + V 粘贴并执行。

执行后,使用 ls() 查看工作空间,你会发现 my_function 已存在,并可以直接调用。

方法二:保存并加载文件
对于更复杂的项目,建议将代码保存为文件:

  1. 在编辑器中,点击 File -> Save As...
  2. 将文件保存到你的工作目录(例如 Coursera 文件夹),命名为 mycode.R.R 是标准扩展名)。
  3. 在控制台中,使用 source() 函数加载该文件:
source("mycode.R")

这将执行文件中的所有代码。之后,你可以随时修改编辑器中的代码,保存后,再次使用 source("mycode.R") 即可将更新后的代码加载到 R 中。

你可以创建多个 .R 文件来管理不同项目或作业的代码。通过 File -> Open script... 可以重新打开已保存的文件。

网络上可能有其他更高级的文本编辑器,但对于本课程,R 自带的编辑器已经足够。

总结 📝

本节课中我们一起学习了 R 环境设置的两个核心技能:设置工作目录和使用文本编辑器。我们了解了工作目录的概念及其重要性,学会了使用 getwd() 查看和通过菜单更改工作目录。同时,我们掌握了使用 R 内置编辑器编写代码,并通过复制粘贴或 source() 函数将代码加载到控制台运行的方法。建立这些工作习惯,将为后续的数据分析任务打下坚实基础。

105:设置工作目录与使用文本编辑器 (Mac) 🖥️

在本节课中,我们将要学习两个核心操作:如何设置R的工作目录,以及如何使用R自带的文本编辑器来编写和编辑代码文件。掌握这两项技能对于高效地使用R进行数据科学工作至关重要。

设置工作目录 📂

上一节我们介绍了R的基本界面,本节中我们来看看如何管理你的工作环境。工作目录是R读取和写入文件的默认位置。如果你不知道当前的工作目录在哪里,你将很难找到保存的文件或加载外部数据。

启动R后,你可以通过输入 getwd() 函数来查看当前的工作目录。

getwd()

例如,在我的Mac上,R启动后的默认工作目录可能是 /users/srpay,也就是我的用户主目录。这个默认设置可能适用,但通常我们会将项目相关的数据和代码文件存放在特定的文件夹中。

如何更改工作目录

如果你需要读取或保存文件到其他文件夹,就需要更改工作目录。以下是更改工作目录的步骤:

  1. 在R的菜单栏中,点击 “Misc” 菜单。
  2. 选择 “Change Working Directory...”
  3. 在弹出的窗口中,导航到你希望设置为工作目录的文件夹并选择它。

更改后,再次使用 getwd() 命令,就能看到新的工作目录路径。

为什么工作目录很重要

工作目录的核心作用在于文件访问。当你尝试使用 read.csv() 等函数读取一个数据文件时,R默认会在当前工作目录中寻找该文件。

# 尝试读取一个名为 “mydata.csv” 的文件
my_data <- read.csv("mydata.csv")

如果 mydata.csv 文件不在当前工作目录中,R会返回一个“文件未找到”的错误。因此,你有两个选择:一是将工作目录更改到文件所在的文件夹;二是将文件移动到当前的工作目录中。

为了本课程学习方便,我建议你创建一个专属的文件夹(例如在桌面上创建一个名为“Coursera”的文件夹),并将所有课程相关的文件都存放在那里。然后,将R的工作目录设置到这个文件夹。这样,你就不需要频繁地更改工作目录了。

使用R的文本编辑器 ✍️

上一节我们学习了如何管理文件路径,本节中我们来看看如何编写R代码。在本课程中,你需要大量编写R代码,因此一个趁手的编辑器是必不可少的。

对于Mac用户来说,R自带了一个简单但功能足够的文本编辑器,非常适合初学者使用。

你可以通过点击R窗口工具栏上的这个按钮来打开文本编辑器。

打开后,你会看到一个空白的编辑窗口。现在,你就可以开始编写R代码了。

编写并运行你的第一个函数

例如,让我们编写一个简单的函数。在编辑器中输入以下代码:

myfunction <- function() {
  x <- rnorm(100)
  return(mean(x))
}

这个函数名为 myfunction,它的作用是生成100个随机正态分布数字并计算其平均值。

将代码加载到R中

编写完代码后,你需要将其加载到R的工作环境中才能使用。有以下两种常用方法:

方法一:复制粘贴
对于少量代码,最简单的方法是:

  1. 在编辑器中使用 Command + A 全选代码。
  2. 使用 Command + C 复制。
  3. 切换到R的控制台窗口,使用 Command + V 粘贴,然后按回车执行。

执行后,输入 ls() 命令,你就能看到 myfunction 已经出现在工作空间的对象列表中了。现在你可以调用它:myfunction()

方法二:保存并加载文件
对于更复杂的代码,更好的做法是将其保存为文件。

  1. 在编辑器菜单中,点击 “File” -> “Save As...”
  2. 导航到你的工作目录(例如之前创建的“Coursera”文件夹)。
  3. 将文件命名为 myfunction.R(R代码文件通常以 .R 为扩展名)并保存。
  4. 在R控制台中,使用 source() 函数加载这个文件。
source("myfunction.R")

执行 source(“myfunction.R”) 的效果与复制粘贴代码完全相同,但它让你可以重复利用和修改已保存的代码文件。

编辑和更新代码

如果你修改了编辑器中的代码(例如,又写了一个新函数),记得先保存文件(Command + S),然后再次使用 source() 函数将其加载到R中,这样修改才会生效。

总结 📝

本节课中我们一起学习了在Mac上使用R的两个基础技能:

  1. 设置工作目录:使用 getwd() 查看目录,通过菜单更改目录。正确设置工作目录是顺利读写文件的关键。
  2. 使用文本编辑器:利用R自带的编辑器编写代码,并通过复制粘贴保存后使用 source() 函数加载的方式将代码送入R环境执行。

虽然网络上存在更多功能强大的代码编辑器,但对于本课程的学习而言,R自带的编辑器已经完全足够。请务必动手练习这些操作,它们是后续所有数据分析工作的基石。

106:分析图形的构建原则

在本节课中,我们将学习构建分析图形的一些基本原则。这些原则由爱德华·塔夫特提出,旨在帮助我们通过数据图形有效地讲述数据背后的故事,并适用于多种情境。


🆚 原则一:展示比较

上一节我们介绍了分析图形的基本原则概述,本节中我们来看看第一个核心原则:展示比较。在科学研究中,支持某个假设的证据总是相对于另一个假设而言的。因此,证据始终是相对的。当你基于数据总结证据时,应该始终思考“与什么相比”这个问题。

以下是展示比较原则的一个具体应用示例:

  • 研究背景:一个箱线图展示了空气净化器对一组家庭中儿童哮喘症状的影响。
  • 干预组:为儿童家庭安装空气净化器,旨在降低室内空气污染水平。
  • 观察结果:干预组儿童的“无症状天数”中位数增加了约1天/两周(数值越高越好)。
  • 关键对比:仅凭此结果可能认为空气净化器有效,但必须与对照组比较。
  • 对照组:另一组家庭未安装任何空气净化器,保持日常生活。
  • 对比结论:对照组儿童的无症状天数平均变化约为零。相对于不做任何干预,空气净化器确实显示出改善儿童症状的效果。

在图形中展示对比至关重要,这为比较不同假设下的证据提供了基础。


🔍 原则二:展示因果关系或机制

上一节我们强调了比较的重要性,本节中我们来看看如何通过图形展示因果关系或解释机制。这里的“因果关系”并非严格意义上的形式定义,而是指展示你对世界运行方式的理解。你需要阐明你所关注的系统是如何运作的。

我们可以扩展上一节的空气净化器示例:

  • 核心问题:为什么在儿童家中安装空气净化器能改善其症状?
  • 假设机制:空气净化器清洁空气,去除了空气中的颗粒物。这些未被去除的颗粒物不再进入儿童肺部,从而不再引发哮喘症状。
  • 数据验证:我们可以绘制另一张图来佐证这一假设。图的左侧显示无症状天数,右侧显示室内颗粒物水平。
  • 数据呈现:对照组家庭的颗粒物水平基本无变化(甚至略有上升),而安装了空气净化器的家庭,其颗粒物水平显著下降。
  • 解释支持:数据显示,安装空气净化器不仅增加了儿童的无症状天数,同时也降低了室内的颗粒物水平。这为“空气净化器通过降低颗粒物水平来改善症状”的假设提供了可能的解释。

当然,要完全证实这一假设需要更深入的调查和实验,但此图形暗示了一种可能的解释机制。


🧩 原则三:展示多变量数据

上一节我们探讨了如何展示机制,本节中我们来看看处理复杂数据的关键:展示多变量数据。这条原则可以归结为:尽可能在一张图上展示更多的数据。因为世界本质上是多变量的,时刻发生着许多事情。如果只绘制两个或三个变量,将无法展示世界的真实图景。如果能在一张图上整合大量数据,你就能讲述更丰富的故事。

以下是一个多变量数据展示的例子:

  • 基础关系:一张图展示了户外空气污染(X轴为日均PM10浓度)与纽约市日均死亡率(Y轴)在1987-2000年间的关系。每个点代表一天的数据。
  • 初步观察:整体回归线略微向下倾斜,表明PM10水平与死亡率呈微弱的负相关。这与“更高空气污染可能导致更高死亡率”的假设相悖。
  • 引入第三变量:除了空气污染和死亡率,还有其他变量可能影响这个关系,例如季节。
  • 分季节展示:我们可以制作另一张图,将数据按冬、春、夏、秋四个季节分开,分别展示每个季节内PM10与死亡率的关系。
  • 深入发现:在每个季节的子图中,PM10与死亡率的关系实际上都呈正相关。
  • 揭示悖论:这就是“辛普森悖论”的一个例子。季节因素混淆了PM10与死亡率之间的整体关系。当按季节分别观察时,关系发生了改变。

因此,在合理范围内尽可能多地展示变量,对于理清数据中的真实关系至关重要。


🧠 原则四:整合多种证据形式

上一节我们看到了展示多变量的价值,本节中我们来看看如何整合证据。其基本思想是,你应该使用尽可能多的不同证据呈现模式。没有理由说如果你有绘图工具就只展示图,或者如果你只能做表格就只展示表格。你应该能够将不同的证据模式组合到单一的展示中,以使你的图形或展示信息尽可能丰富。

关键点在于,不要让所使用的工具决定你制作何种图形。你应该制作你想制作的图形,而不是让工具替你思考。这正是像R这样的系统的优势之一,因为R中的工具非常灵活,你可以制作各种定制化的图形来展示数据,并整合不同的证据模式。

以下是一个来自《美国医学会杂志》已发表论文的简单示例:

  • 研究内容:该图探讨了粗颗粒物与老年人住院率之间的关系。
  • 证据整合
    • 点估计与置信区间:实心圆点表示点估计值,延伸的线条表示其置信区间。
    • 文本补充证据:右侧的“后验概率”标签提供了另一个证据维度,用于衡量“相对风险大于零”的证据强度。
  • 整合优势:通过这种方式,我们在同一张图上整合了点线估计和文本信息,无需将信息分散在不同位置,便于追踪和理解。

📝 原则五:描述和记录证据

上一节我们讨论了整合证据,本节中我们来看看确保图形可信度的关键:描述和记录证据。你需要为你呈现的证据提供一些可信度支撑。数据的来源非常重要,你如何制作图形也同样重要。

具体而言,如果你使用像R这样的系统制作图形,保存生成该图形的计算机代码至关重要。这是一个非常基本的原则,对你的可信度至关重要。


👑 原则六:内容为王

最后,也是最重要的原则是:内容为王。如果你没有有趣的故事要讲,那么再多的呈现技巧也无法让它变得有趣。

因此,在制作图形、图表时,首先要思考你试图呈现的内容是什么,你想讲述什么故事,你拥有什么数据。然后再思考最佳的呈现方式是什么,你将如何呈现,以及它最终会是什么样子。因为如果你没有非常好的内容,那么除此之外你能做的就非常有限了。


📚 总结

本节课中我们一起学习了构建分析图形的六项基本原则:

  1. 展示比较:始终展示事物之间的相对关系。
  2. 展示因果关系或机制:尝试解释系统或世界是如何根据你的想法运作的。
  3. 展示多变量数据:始终尝试展示两个以上的变量,因为世界是复杂的,涉及许多变量。
  4. 整合多种证据形式:你可以同时使用表格、图形和文字,不必将它们分开。
  5. 描述和记录证据:始终提供数据来源和源代码,以增强图形的可信度。
  6. 内容为王:始终记住,你讲述的故事和你使用的数据是任何图形中最重要的元素。

本演示参考了爱德华·塔夫特的著作《Beautiful Evidence》,这是一本极好的书,我强烈推荐。这些是关于构建分析图形的一些基本原则,在未来的课程中,我们将讨论如何使用R中的各种绘图系统来实现这些原则。

107:探索性图形分析(第一部分)📊

在本节课中,我们将学习如何构建探索性图形。探索性图形主要是为你自己而制作的图表,用于查看数据并探索你所关注数据集中的情况。

概述

探索性图形是数据分析中用于初步理解数据、发现模式、提出建模策略和调试分析的重要工具。它们通常制作迅速、数量众多,旨在帮助分析师形成对数据的个人理解。本节课我们将通过一个关于美国空气污染的真实数据集,来演示几种常见的探索性图形。

为什么在数据分析中使用图形?

我们使用图形进行数据分析,主要希望达成几个目标:理解数据属性、发现数据中的基本模式、为建模策略提供建议(例如选择线性或非线性模型)、调试分析过程,以及向他人传达结果。探索性图形主要服务于前四个目标,即理解数据、发现模式、建议策略和调试分析。在本讲中,我们暂不深入讨论用于展示结果的图形构建,这将在后续课程中涉及。

探索性图形的特点

探索性图形通常具有以下特点:它们制作非常迅速,往往是边查看数据边生成的;你会制作大量图形,以便从不同角度审视数据;你需要逐个查看许多变量。其目标是让你作为分析师,能对数据集形成个人理解,把握其外观、属性、存在的问题以及需要后续跟进的事项。

探索性数据分析的核心问题可以归结为:我的数据看起来是什么样子?这就是制作探索性图形的目标。在此阶段,你通常无需过多担心图形的外观或呈现方式,例如坐标轴和图例通常会在后期清理,颜色和大小主要用于帮助你区分信息。当然,如果是为了演讲等展示场合,你可能需要更仔细地考虑颜色和大小。

示例数据集:美国细颗粒物污染

为了具体讨论各种探索性图形,我们将使用一个真实数据集。该数据集涉及美国的环境空气污染,来源于美国环境保护署。EPA为国家室外空气污染设定了环境空气质量标准。我们将关注一种名为细颗粒物(PM2.5)的空气污染物。美国的标准是,在给定地点,三年平均的年均值不得超过每立方米12微克。任何超过此水平的州将被视为不符合国家标准。

数据来自美国EPA的空气质量系统。我已下载并汇总了这些数据用于本次演示。本次探索性分析的基本问题是:美国是否有县超过了细颗粒物污染的国家标准?我们拥有美国许多县的监测数据,希望查看它们是否超过了新设定的每立方米12微克的标准。

你可以使用 read.csv 函数读取数据。以下是数据框的前几行:第一列是PM2.5的水平(2008年至2010年三年间的年均值),fips 列是县的标识符,region 列表示该县位于东部还是西部,longitudelatitude 列则是该县监测器的经纬度坐标。

记住,我们的核心问题是查看是否有县超过12微克/立方米的标准。即使在探索性分析中,你仍然需要在脑海中有一个基本问题,即使它目前可能有些模糊,因为这个问题将驱动你思考数据的样子。一个问题对某类分析可能是问题,对另一类则可能不是。因此,浏览数据时心中需有一个背景问题。

一维数据摘要

我们可以查看数据的一维摘要。以下是几种常见方法:五数摘要、箱线图、直方图、密度图和条形图。我将在此演示其中几种。

五数摘要

五数摘要本身并非图形,但它总结了给定变量的某些特定方面。R中的 summary 函数可以生成此摘要,实际上它生成的是六数摘要,因为它包含了均值。传统的五数摘要包括最小值、第一四分位数、中位数、第三四分位数和最大值。

以下是PM2.5变量的摘要输出:

summary(pm25)

从输出中可以看到,中位数为10微克/立方米,低于标准;最大值为18.4,超过了标准,因此在此期间肯定有一些县违反了标准。第三四分位数为11,第一四分位数为8.5,最小值为3.38。

箱线图

以下是PM2.5变量的箱线图,我将其指定为蓝色。你可以看到中位数约为10,正如我们在五数摘要中看到的那样。还可以看到有许多县似乎超过了每立方米12微克的标准线,同时也有许多县低于此线,即符合标准。

直方图与数据地毯

以下是相同数据的直方图。直方图的好处在于,它能提供更多关于变量分布形状的细节。我喜欢做的一件事是在直方图下方添加“数据地毯”。数据地毯会绘制构成直方图的所有数据点,这样你就能精确看到这些点的位置。一方面,你得到了作为摘要的直方图;另一方面,通过下方的数据地毯,你也能获得一些精细的细节,可以看到异常值在哪里,数据主体在哪里。可以看到,数据主体似乎集中在10左右,但在15以上有几个异常值。

你可以通过更改 breaks 参数来调整直方图中条形(即组距)的数量。在之前的幻灯片中,条形较宽,直方图更平滑。在这张幻灯片中,我通过设置 breaks = 100 使条形更小,你可以看到直方图变得更粗糙。设置 breaks 参数时需要注意,数值不宜过大,否则会产生许多小条形,导致直方图非常嘈杂;当然,数值也不宜过小,否则可能只有几个条形,无法真正看清分布形状。通常,调整 breaks 参数以获取最理想的直方图形状是很有用的。

在图形上叠加参考线

这是之前展示过的箱线图,但现在我在图上叠加了一个特征:一条在12处的水平线。数字12是国家环境空气质量标准。我将这条线叠加在12处,以便了解有多少县高于或低于这条线。你可以看到,超过75%的县低于此线,因为整个蓝色箱体都在这条线以下,而蓝色箱体的上端是第75百分位数或第三四分位数。

这在总结和突出特定特征时很有用。你也可以在直方图上这样做。这里,我在12处画了一条黑色垂直线,并在中位数处画了一条洋红色垂直线,以便总结并精确查看分布的中位数位置。在箱线图中很容易看到中位数,因为箱线图本身就包含中位数这一特征,而直方图则没有。因此,有时添加一条垂直线来突出中位数是很好的做法。你可以再次看到,中位数低于标准,但有一些县高于它。

条形图

条形图是分类数据的另一种图形摘要。这里我绘制了 region 变量,可以看到东部有多少县,西部有多少县。可以看到,大多数县位于美国东部,美国西部有100多个县。这总结了这个特定的分类变量。

总结

本节课我们一起学习了探索性图形分析的第一部分。我们了解了探索性图形的目的和特点,并通过美国PM2.5污染的实际案例,演示了如何利用五数摘要、箱线图、直方图(配合数据地毯和调整组距)以及条形图来初步探索和理解数据分布,并借助参考线(如国家标准线)来评估数据。这些工具能帮助你快速形成对数据集的直观认识,为后续深入分析奠定基础。

108:探索性图形分析(第二部分)

在本节课中,我们将学习如何使用二维及多维图形来探索和总结数据。我们将从基础的散点图开始,逐步介绍如何通过颜色、面板布局等方式,在二维图形中展示超过两个维度的信息。

上一节我们介绍了一维数据的图形总结方法。本节中,我们来看看如何将数据可视化扩展到二维甚至更高维度。

二维数据可视化方法

二维数据总结不仅包括散点图,还可以通过组合多个一维图形来实现。latticeggplot2 包尤其擅长此类任务。

以下是几种核心的二维可视化方法:

  • 散点图:展示两个连续变量之间的关系。
  • 平滑散点图:适用于数据点过于密集的情况。
  • 分组一维图:例如,通过分组箱线图或直方图来同时观察一个分类变量和一个连续变量。

当需要展示超过两个维度的数据时,需要采用更巧妙的方法。虽然存在三维立体图或旋转图,但它们通常不如将二维图形进行特殊排列或组织来得实用。我们可以通过多面板图形,或者利用颜色、点的大小和形状等视觉元素来增加数据的维度。

分组箱线图与直方图

我们可以通过分组图形来同时观察一个分类变量和一个连续变量。

下图展示了美国各县的PM2.5数据,按“东部”和“西部”分类。x轴是分类变量(东/西),y轴是连续变量(PM2.5值)。

从图中可以看出,东部地区的PM2.5平均值明显高于西部地区。但值得注意的是,西部地区的PM2.5分布范围更广,包含了所有的极端高值。这意味着西部地区的平均值虽低,但数据波动性更大。

我们也可以用分组直方图来展示同样的信息。

上方的直方图对应东部各县,下方对应西部各县。该图印证了箱线图的发现:西部各县的平均值较低,但存在更多极高的极端值;东部各县的平均值较高,但极端高值较少。

散点图与多维扩展

散点图是最直观的二维数据可视化方法。

下图绘制了监测点纬度与PM2.5值的关系,旨在探索是否存在南北趋势。纬度增加表示向北移动。

图中显示了一个强烈的南北趋势:PM2.5在中纬度地区往往更高,而在高纬度和低纬度地区则相对较低。图中添加了一条12微克/立方米的水平虚线(国家环境空气质量标准),可以看到有许多数据点位于该线上方,这意味着这些地区在技术上可能不符合标准。

我们可以使用颜色在散点图中添加第三个维度。

在此图中,x轴是纬度,y轴是PM2.5,而点的颜色则代表了第三个变量“东/西”。黑色圆点代表东部各县,红色圆点代表西部各县。这样,我们就在一张图中同时观察了三个变量。

另一种观察这三个变量的方法是使用多面板图形。

现在,我们得到了一个由两个散点图组成的面板。左侧是所有西部县的纬度与PM2.5关系图,右侧是所有东部县的图。可以看出,无论是东部还是西部,污染较高的县都倾向于出现在中纬度地区,而北部和南部纬度地区的污染普遍较低。这个发现很有趣。

探索性图形的目的与资源

以上是对R中绘制一维和二维数据的快速总结,包括使用颜色和多面板来观察超过两个维度的数据。探索性图形通常追求快速和直接,我们通常使用默认的坐标轴和标签设置。

探索性图形的优点在于,它能让你快速总结数据,并突出显示数据中可能感兴趣的重要特征。你可以用它来探索基本的问题和假设。例如,我们最初的问题是“是否有县可能不符合标准?”,这些图形为此提供了线索,并可能为下一步更详细的分析或模型拟合建议有用的建模策略。

最后,这里有一些有用的资源:

  • R Graph Gallery:一个优秀的网站,提供了大量在R中制作的图形和绘图示例。
  • R-bloggers:该网站经常展示人们通过博客发布的各种绘图。

浏览这些资源可以让你了解R在数据可视化方面的各种可能性。


本节课总结:本节课我们一起学习了如何使用二维图形(如散点图、分组箱线图)来探索数据,并掌握了通过颜色、多面板等方式在二维平面上展示多维信息的技巧。探索性图形是数据科学中快速理解数据、形成初步假设的关键工具。

109:R语言中的绘图系统 📊

在本节课中,我们将要学习R语言中三种核心的绘图系统。这些系统在概念、构建方式和适用场景上各有不同。我们将逐一介绍它们的特点、优势与不足,帮助你理解如何根据不同的绘图目标选择合适的系统。

概述:三种绘图系统

R语言在发展过程中形成了三种核心绘图系统:基础绘图系统、Lattice系统和ggplot2系统。它们各有其设计哲学和适用场景。


基础绘图系统 🎨

基础绘图系统是R语言中最古老的绘图系统。它的构建理念类似于艺术家的调色板模型。

工作原理

该模型将绘图视为一个从空白画布开始,逐步添加元素的过程。首先创建一个包含坐标轴的画布,然后添加数据点、标签、回归线、标题等元素。每一个绘图元素通常对应一行代码。

以下是构建基础绘图的基本模式:

  1. 使用一个函数(如 plot())生成初始图形。
  2. 使用一系列注释函数(如 points(), lines(), text(), title())向图形中添加元素。

优势与不足

基础绘图系统非常直观,尤其适合数据探索,因为你可能无法一开始就确定最终的图形样式。你可以逐步添加元素来构建图形。

然而,该系统也存在一些不足:

  • 不可逆性:一旦添加了图形元素,就无法移除。绘图是一个累积的、单向的过程。
  • 难以复用与分享:每个图形本质上都是一系列R代码的集合,缺乏一种通用的“语言”来描述图形,因此难以将绘图思路直接传达给他人。
  • 需要精细控制:系统给予用户极大的控制权,这也意味着用户需要仔细设置所有细节,如果对默认设置不满意,就必须手动调整。

示例

以下是一个简单的基础绘图示例,使用了内置的 cars 数据集,展示了汽车速度与刹车距离的关系。

plot(cars$speed, cars$dist, xlab = "Speed", ylab = "Stopping Distance")

这个散点图将速度放在X轴,刹车距离放在Y轴。你可以在此基础上添加标题、改变点的颜色和形状等。


Lattice绘图系统 🧩

上一节我们介绍了逐步构建的基础绘图系统。本节中我们来看看Lattice系统,它的设计理念截然不同。

Lattice系统由 lattice 包实现。其核心思想是通过单次函数调用构建整个图形,而非逐步添加。

工作原理

最常用的函数是 xyplot(),此外还有 bwplot(), histogram() 等。由于需要一次性构建完整图形,你必须在函数调用中提供足够的信息和参数。

Lattice系统特别适用于创建条件图。例如,你想研究X和Y的关系如何随着第三个变量Z的不同水平而变化。你可以将数据按Z的不同水平分组,并在多个子图(面板)中分别展示X-Y关系。

以下是Lattice系统的特点:

  • 自动化布局:许多在基础系统中需要手动设置的细节(如边距、间距)在Lattice中会自动计算,默认效果通常很好。
  • 高效创建多面板图:可以非常快速、轻松地在一页上排列多个相关图形。

优势与不足

Lattice系统在创建条件图时非常高效,且默认美学设置良好。

其不足之处在于:

  • 指定复杂:有时用单个函数调用指定一个复杂图形会显得笨拙。
  • 难以注释:图形一旦生成,就很难再添加新元素。若要添加,通常需要重新构建整个函数调用。虽然可以通过 panel 函数等方式注释每个面板,但这并不直观。
  • 缺乏灵活性:与基础系统一样,无法在图形生成后修改。

示例

以下是一个基本的Lattice图示例,使用了 lattice 包中的数据集,展示了美国各州人均收入与预期寿命的关系,并按地区进行条件分割。

library(lattice)
xyplot(life.exp ~ income | region, data = state, layout = c(4, 1))

这个面板图通过一次 xyplot() 函数调用就完成了。若用基础系统实现,则需要多行代码,过程更为复杂。


ggplot2绘图系统 ✨

最后,我们来了解ggplot2系统。它基于“图形语法”理论,为描述图形的各个方面提供了一套严谨的语言。

ggplot2系统(通过 ggplot2 包实现)融合了基础系统和Lattice系统的思想。

工作原理

一方面,你可以像基础系统一样,通过逐步添加图层的方式来增量式构建图形。另一方面,它又像Lattice系统,能自动处理许多美学细节(如间距、标签位置)。

ggplot2同样非常适合创建条件图(多面板图)。系统提供了大量精心设计的默认设置,使用起来非常方便,同时也允许用户进行深度定制。

示例

以下是一个典型的ggplot2默认图形,使用了 ggplot2 包内置的 mpg 数据集,展示了汽车发动机排量与高速公路油耗的关系。

library(ggplot2)
ggplot(mpg, aes(x = displ, y = hwy)) + geom_point()

可以看到,随着发动机排量增加,油耗大致呈下降趋势。ggplot2的默认美学风格与其他系统不同,例如灰色背景、白色网格线和实心点。所有这些都可以根据需要进行自定义。


总结与重要提示 📝

本节课中我们一起学习了R语言的三种主要绘图系统:

  1. 基础绘图系统:采用艺术家调色板模型,通过一系列函数调用逐步添加元素构建图形。
  2. Lattice系统:通过单次函数调用指定整个图形,特别擅长创建条件图(多面板图)。
  3. ggplot2系统:基于图形语法,融合了前两者的优点,既能增量构建,又拥有良好的默认设置。

一个重要提示是:这三个系统的函数不能混用。 一旦决定使用某个系统(例如ggplot2),就必须使用该生态系统内的函数来构建和修饰图形,混合使用不同系统的函数会导致错误或混乱的绘图结果。因此,在开始绘图前,通常需要根据需求选择一个系统并坚持使用。

在后续的单独课程中,我们将详细讲解如何使用每一种绘图系统。

110:R 语言基础绘图系统(第一部分)📊

在本节课中,我们将要学习 R 语言中的基础绘图系统。这是 R 语言中最常用的绘图系统之一,我们将了解其核心概念、基本函数以及如何创建和定制简单的图形。


概述:R 语言的绘图系统

上一节我们介绍了 R 语言有三种不同的绘图系统:基础绘图系统、Lattice 系统和 ggplot2 系统。本节中,我们将重点探讨基础绘图系统。

R 语言的核心绘图和图形引擎被封装在 graphics 包和 grDevices 包中。grDevices 包包含了所有图形设备的代码,包括屏幕设备(如 X11、Windows 和 Quartz)以及文件设备(如 PDF 和 PostScript)。graphics 包则包含了绘图函数,例如 plothistboxplot 等,这些构成了我们所说的基础绘图系统。

Lattice 系统被封装在 lattice 包中,它使用了另一个名为 grid 的低级图形系统。我们通常不直接调用 grid 包,而是通过 latticeggplot2 等包间接使用。Lattice 系统将在单独的课程中讨论。


绘图前的考量

在开始绘图之前,需要考虑几个问题。以下是需要考虑的关键点:

  • 绘图位置:图形将显示在屏幕上,还是输出到文件?
  • 图形用途:是临时在屏幕上查看,用于网页浏览器,打印在纸上(需要出版质量),还是用于幻灯片演示?
  • 数据量:图形中会包含大量数据,还是只有少量数据点?是图像还是线图?
  • 动态调整大小:是否需要能够动态调整图形大小?如果需要输出到文件,可能需要使用矢量格式(如 PDF)而非位图格式(如 PNG)。

此外,还需要决定使用哪种图形系统(基础、Lattice 或 ggplot2)。因为这三者不能混合使用。基础绘图系统采用分步构建的方式,通过一系列函数调用来组合图形。Lattice 系统通过单次函数调用创建整个图形。ggplot2 系统则结合了 Lattice 和基础绘图系统的概念。本课程将只关注基础绘图系统,并且仅讨论在屏幕设备上创建图形。


基础绘图系统的两阶段过程

在基础绘图系统中,创建二维图形通常分为两个阶段。

第一阶段是初始化一个新图形。第二阶段是在现有图形上添加注释或内容。有许多函数用于初始化图形,另有一组函数用于注释图形。

plot 函数或 hist 函数是初始化图形的两个例子。如果当前没有打开的图形设备,它们会启动一个图形设备(通常是一个弹出的窗口),然后在新设备上绘制图形。

plot 函数是 R 语言中的一个泛型函数,其行为会根据传入的数据类型而有所不同。如果没有传入特殊类型的数据,例如只传入向量 xy,则会使用 plot 的默认方法。这个默认方法有很多参数,可以在帮助页面查看。例如,可以设置标题、X 轴标签和 Y 轴标签等细节。

总的来说,基础绘图系统有许多参数可以设置和调整,以使图形完全符合你的要求。这就像一把双刃剑,因为虽然可以高度自定义,但也意味着许多设置不是自动完成的,需要手动调整。大多数参数都记录在 par 函数的帮助页面中。


基础图形示例

以下是使用基础绘图系统创建的几个基本图形示例。

直方图

使用 hist 函数可以创建直方图,这是一种一维图形。这里我们加载 datasets 包中的 airquality 数据集,并绘制臭氧(Ozone)的直方图。我们没有设置 hist 的所有参数,而是使用了默认值。

library(datasets)
hist(airquality$Ozone)

如果 R 中尚未打开图形设备,上述代码会打开一个图形设备窗口并显示直方图。

散点图

散点图是另一种基础图形,可以使用 plot 函数创建。这里我们使用相同的数据集,绘制臭氧(Ozone)与风速(Wind)的散点图。同样,我们使用了所有默认参数。

plot(airquality$Wind, airquality$Ozone)

默认的绘图符号是一个空心圆。

箱线图

boxplot 函数用于初始化箱线图。这里我们按月份(Month)绘制臭氧(Ozone)分布的箱线图。boxplot 函数使用了一种公式表示法:波浪号(~)左侧指定 Y 轴变量(臭氧),右侧指定 X 轴变量(月份)。月份被当作分类变量处理。我们还通过 xlabylab 参数指定了坐标轴标签。

boxplot(Ozone ~ Month, data = airquality, xlab = "Month", ylab = "Ozone (ppb)")

关键的基础图形参数

有几个关键的基础图形参数非常常用,值得牢记。

以下是这些核心参数及其作用:

  • pch:绘图字符或符号。可以是一个数字(对应内置符号表)或一个字符(如字母“A”)。默认是空心圆。有时可用于按组标记数据点。
  • lty:线型。指定线条是实线、虚线、点线还是点划线等。
  • lwd:线宽。根据图形用途调整,例如演示文稿中可能需要更粗的线条以便远距离观看。
  • col:绘图颜色。指定绘图符号或线条的颜色。可以通过数字、颜色名称字符串或十六进制代码指定。colors() 函数可以列出所有可用的颜色名称。
  • xlab / ylab:用于指定 X 轴和 Y 轴的标签,对图形注释非常有用。

另外,还有一些可以通过 par 函数设置的全局图形参数,它们会影响之后创建的所有图形。当然,通常也可以在单个绘图函数调用中覆盖这些全局设置。但是,像边距大小或 mfrow 这样的参数,只能在 par 函数中设置。

以下是几个有用的 par 参数:

  • las:坐标轴标签的方向(水平或垂直)。
  • bg:图形的背景颜色。
  • mar:图形边距大小。如果坐标轴标签很复杂,可能需要增大边距。
  • oma:外边界大小。当一页有多个图形并需要整体标题时有用。
  • mfrow:控制多图布局的行数和列数(例如,mfrow = c(2, 2) 表示 2 行 2 列共 4 个图形)。

可以通过调用 par 函数并传入参数名来查看其默认值。

par("lty") # 查看默认线型
par("col") # 查看默认颜色
par("pch") # 查看默认绘图符号
par("bg")  # 查看默认背景色(通常是透明)
par("mar") # 查看默认边距(底部、左侧、顶部、右侧,单位是文本行数)
par("mfrow") # 查看默认多图布局(1行1列,即单图)

例如,mar 的默认值 c(5.1, 4.1, 4.1, 2.1) 分别对应图形底部、左侧、顶部、右侧的边距大小(以文本行数为单位)。mfrow 默认为 c(1, 1),表示单图布局。


总结

本节课中,我们一起学习了 R 语言基础绘图系统的核心概念。我们了解了 R 的三种绘图系统,并聚焦于基础绘图系统的两阶段构建过程。我们通过实例学习了如何使用 histplotboxplot 等函数创建直方图、散点图和箱线图。最后,我们介绍了一些关键的基础图形参数(如 pchcolxlab)和全局参数(通过 par 设置),这些是定制图形外观的基础。掌握这些内容,是使用 R 语言进行有效数据可视化的第一步。

111:R语言基础绘图系统(第二部分)📊

在本节课中,我们将继续学习R语言的基础绘图系统。我们将重点介绍几个核心的绘图函数,并学习如何通过添加注释、线条、图例以及创建多图布局来增强和定制你的图表。


核心绘图函数介绍

上一节我们介绍了基础绘图系统的概念。本节中,我们来看看几个最常用、最核心的绘图函数。

以下是几个关键的基础绘图函数:

  • plot:这是最常用的函数,主要用于创建散点图。根据输入对象的类型,它也可以创建其他类型的图表。
  • lines:此函数本身不创建新图表,而是向现有图表添加线条。当你需要连接数据点(例如绘制时间序列图)时,它非常有用。
  • points:此函数向现有图表添加数据点。例如,你可以在一个图表上,用不同的颜色或形状添加代表另一个子集的数据点。
  • text:此函数用于在图表内部添加文本标签,以进行注释。
  • title:此函数通常用于在图表外部添加注释,例如X轴和Y轴标签、主标题和副标题。
  • mtextm代表边距(margin)。此函数用于在图表的边距(包括内边距和外边距)中放置文本。
  • axis:此函数用于自定义坐标轴的刻度。

图表注释与增强

了解了核心函数后,我们来看看如何利用它们对图表进行注释和增强。

添加标题

以下是一个为散点图添加标题的例子。我们绘制风速(Wind)和臭氧浓度(Ozone)的关系。

plot(wind, ozone)
title(main = "Ozone and Wind in New York City")

你也可以在调用plot函数时直接指定标题:

plot(wind, ozone, main = "Ozone and Wind in New York City")

叠加数据点

有时,我们希望突出显示数据中的特定子集。例如,将五月份的数据点用不同颜色标出。

# 首先绘制所有数据的散点图
plot(wind, ozone)
# 然后,筛选出月份为5的数据,并用蓝色点叠加
may <- subset(airquality, Month == 5)
points(may$Wind, may$Ozone, col = "blue", pch = 20)

分步绘制与添加图例

为了避免数据点重叠,并更清晰地展示不同组别,我们可以先初始化一个空图表,再分步添加不同组的数据。

# 初始化图表,type="n"表示只设置绘图区域,不绘制任何数据点
plot(wind, ozone, type = "n")
# 添加五月份的数据(蓝色)
points(wind[month == 5], ozone[month == 5], col = "blue", pch = 20)
# 添加非五月份的数据(红色)
points(wind[month != 5], ozone[month != 5], col = "red", pch = 20)
# 在右上角添加图例
legend("topright", pch = 20, col = c("blue", "red"), legend = c("May", "Other Months"))

添加回归线

为散点图添加趋势线(如回归线)是常见的需求。

# 绘制散点图,pch=20表示使用实心小圆点
plot(wind, ozone, pch = 20)
# 拟合线性模型
model <- lm(ozone ~ wind)
# 使用abline函数添加回归线,lwd=2使线条更粗
abline(model, lwd = 2)

公式ozone ~ wind 表示以wind为自变量,ozone为因变量的线性模型。


创建多图布局

有时我们需要将多个图表排列在一个图形设备中,以便进行比较。

并排排列图表

以下代码创建一行两列的布局,并排显示两个散点图。

# 设置图形参数:1行,2列
par(mfrow = c(1, 2))
# 第一个图:臭氧 vs 风速
plot(wind, ozone, main = "Ozone vs Wind")
# 第二个图:臭氧 vs 太阳辐射
plot(solar.r, ozone, main = "Ozone vs Solar Radiation")

为多图面板添加总标题

我们还可以为整个多图面板添加一个总标题,这需要利用外边距(outer margin)。

# 设置图形参数:1行3列,调整边距,并设置顶部外边距以容纳总标题
par(mfrow = c(1, 3), mar = c(4, 4, 2, 1), oma = c(0, 0, 2, 0))
# 绘制三个子图
plot(wind, ozone, main = "Ozone vs Wind")
plot(solar.r, ozone, main = "Ozone vs Solar Radiation")
plot(temp, ozone, main = "Ozone vs Temperature")
# 在外边距添加总标题
mtext("Ozone and Weather in New York City", outer = TRUE, cex = 1.5)

总结

本节课中我们一起学习了R语言基础绘图系统的进阶功能。

我们首先介绍了几个核心的注释函数,如linespointstextlegend,它们用于在基础图表上添加元素。接着,我们通过实例学习了如何分步构建图表、为特定数据子集着色以及添加回归线。最后,我们探讨了如何使用par(mfrow)创建多图布局,并利用外边距为整个图板添加总标题。

基础绘图系统的特点是高度灵活和可控。创建图表通常始于一个初始化函数(如plothist),然后通过一系列函数调用来逐步添加点和线、文本和注释。虽然这有时会导致代码行数较多,但它提供了对图表几乎所有细节的精细控制能力,对于制作出版质量的图表非常有用。在后续课程中,我们将介绍其他绘图系统。

112:R 语言基础绘图演示 📊

在本节课中,我们将学习 R 语言基础绘图系统的核心功能。我们将从创建简单的图形开始,逐步探索如何自定义图形元素、添加注释、在同一画布上排列多个图形,以及如何根据分组变量为数据点着色。


1. 创建基础图形

首先,我们通过生成一些模拟数据来创建一个简单的直方图。

# 生成 100 个服从标准正态分布的随机数
x <- rnorm(100)
# 绘制直方图
hist(x)

执行 hist(x) 后,会弹出一个绘图窗口。即使我们没有指定任何参数,图形也会自动包含标题、x 轴标签(默认为变量名 x)和 y 轴标签(对于直方图,默认为 Frequency)。直方图的形状大致呈正态分布。

接下来,我们生成更多数据来创建散点图。

# 生成更多数据
y <- rnorm(100)
# 绘制散点图
plot(x, y)

由于绘图窗口已经打开,plot(x, y) 会将图形绘制到当前窗口。默认的绘图符号是空心圆。同样,x 轴和 y 轴的标签会自动设置为变量名 xy


2. 自定义图形参数

图形区域有几个重要的组成部分,例如四个边距(side1 底部,side2 左侧,side3 顶部,side4 右侧)。我们可以使用 par() 函数调整边距。

# 将所有边距设置为 2 行文本的高度
par(mar = c(2, 2, 2, 2))
plot(x, y)

设置后,边距变小,图形区域变大,但轴标签可能因空间不足而消失。通常需要更大的边距来容纳标签。

# 设置更合理的边距(底部4,左侧4,顶部2,右侧2)
par(mar = c(4, 4, 2, 2))
plot(x, y)

现在,图形有足够的空间显示 x 和 y 轴标签。


3. 自定义绘图符号与颜色

我们可以通过 pch 参数改变散点图中点的形状。

# 使用不同的 pch 值改变点的形状
plot(x, y, pch = 20)  # 小实心圆
plot(x, y, pch = 19)  # 大实心圆
plot(x, y, pch = 2)   # 空心三角形
plot(x, y, pch = 3)   # 加号
plot(x, y, pch = 4)   # 叉号

如果不记得 pch 值对应的形状,可以使用 example(points) 命令查看演示图表。符号 21 到 25 比较特殊,它们有边框和填充色,可以分别用 col(边框色)和 bg(填充色)参数控制。


4. 为图形添加注释

我们可以为图形添加标题、文本标签、图例和拟合线。

# 重新生成数据并绘制基础图形
x <- rnorm(100)
y <- rnorm(100)
plot(x, y, pch = 20)

# 添加标题
title("这是我的散点图")

# 在指定坐标添加文本
text(-2, -2, "这是一个标签")

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_22.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_24.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_25.png)

# 添加图例
legend("topleft", legend = "数据点", pch = 20)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_27.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_28.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_30.png)

# 拟合线性模型并添加回归线
fit <- lm(y ~ x)
abline(fit, col = "blue", lwd = 3)  # lwd 控制线宽

更常见的做法是在调用 plot() 函数时直接指定这些参数。

plot(x, y,
     xlab = "体重",
     ylab = "身高",
     main = "散点图",
     pch = 20)
legend("topright", legend = "数据点", pch = 20)
fit <- lm(y ~ x)
abline(fit, col = "red", lwd = 2)


5. 在同一画布上排列多个图形

使用 par(mfrow)par(mfcol) 可以在一个页面(画布)上排列多个图形。mfrow 按行填充,mfcol 按列填充。

# 设置图形排列为 2 行 1 列
par(mfrow = c(2, 1))
plot(x, y, pch = 20, main = "图1: Y vs X")
# 生成新变量 z
z <- rpois(100, lambda = 2)
plot(x, z, pch = 19, main = "图2: Z vs X")

排列改变后,可能需要重新调整边距 mar 以减少空白。我们也可以创建 2x2 的图形布局。

# 设置图形排列为 2 行 2 列
par(mfrow = c(2, 2))
plot(x, y, main = "左上")
plot(x, z, main = "右上")
plot(z, x, main = "左下")
plot(y, x, main = "右下")

6. 按分组变量绘制数据

在实际分析中,我们经常需要根据分组(如性别)用不同颜色或形状绘制数据点。基本思路是:先建立空的绘图区域,然后按组别分批添加数据点。

# 生成包含分组信息的数据
set.seed(123)
x <- rnorm(100)
y <- x + rnorm(100, sd = 0.5)
# 创建分组变量(50个男性,50个女性)
g <- factor(rep(c("male", "female"), each = 50), labels = c("男性", "女性"))

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_57.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_58.png)

# 第一步:建立空的绘图区域 (type = "n")
plot(x, y, type = "n", xlab = "X", ylab = "Y", main = "按性别分组")

# 第二步:添加男性数据点(绿色)
points(x[g == "男性"], y[g == "男性"], col = "green", pch = 19)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_60.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_61.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_62.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_63.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_65.png)

# 第三步:添加女性数据点(蓝色)
points(x[g == "女性"], y[g == "女性"], col = "blue", pch = 19)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/ec070404409333e5202691c7692e9cc1_67.png)

# 可以添加图例
legend("topleft", legend = c("男性", "女性"), col = c("green", "blue"), pch = 19)

通过这种方式,我们可以在同一张散点图上清晰地区分不同组别的数据。除了颜色,我们还可以通过 pch 参数为不同组设置不同的形状。


总结

本节课我们一起学习了 R 基础绘图系统的核心功能。我们从创建简单的直方图和散点图开始,然后学习了如何通过 par() 函数调整图形参数(如边距 mar 和布局 mfrow/mfcol)。我们还探索了如何自定义绘图符号(pch)、颜色,以及如何为图形添加标题、文本、图例和拟合线等注释。最后,我们掌握了在同一画布上排列多个图形,以及根据分组变量为数据点着色的高级技巧。这些基础技能是进行有效数据可视化的关键。

113:📊 R中的图形设备(第一部分)

在本节课中,我们将要学习R语言中图形设备的基础知识。图形设备是生成和显示图形输出的关键,理解它们对于创建和保存图表至关重要。

什么是图形设备?

图形设备是绘图可以显示的地方或对象。最明显的地方是屏幕,当你调用绘图函数时,一个图形窗口会启动,图表就显示在那里。这被称为屏幕设备。

除了屏幕,你可能还想将图表发送到文件。文件有多种格式,例如PDF、PNG、JPEG或可缩放矢量图形(SVG)文件。因此,你可以选择多种不同的文件设备来发送图表。

图形设备的重要性

在R中制作图表时,必须将其发送到特定的图形设备,否则无法生成图表。最常见的发送位置是屏幕设备。

屏幕设备在不同操作系统上有不同的名称:

  • 在Mac上称为 quartz
  • 在Windows上称为 Windows
  • 在Unix/Linux系统上称为 X11

当你制作图表时,首先需要考虑这个图表将显示在哪里。你可以通过 ?Devices 命令查看帮助页面,那里列出了所有可用的设备。此外,CRAN上也有用户创建的图形设备包,安装后即可使用。本节课我们主要关注R自带的设备。

屏幕设备与文件设备

上一节我们介绍了图形设备的基本概念,本节中我们来看看两种主要设备类型的使用场景。

在进行数据可视化和探索性分析时,通常最明显的选择是将图表发送到屏幕设备。你调用如 plotxyplotggplot2 中的 qplot 等函数,图表就会出现在屏幕设备上。这样你可以快速查看数据,并决定下一步的绘图操作。

在任何给定的平台(Mac、Windows或Unix)上,只有一个屏幕设备,因此你无需选择将其发送到哪个屏幕设备,只有一个去处,所以不必担心。

然而,通常在完成探索性分析并确定了想要保留或分享的图表后,你就需要在另一种类型的设备(例如文件设备)上制作图表。将图表制作到文件中后,你可以将该文件包含在报告、演示文稿中,或者通过电子邮件发送给他人。因此,文件对于这类用途更为有用。

与屏幕设备不同,文件设备有多种选择。以下是常见的文件设备类型:

  • PDF设备:生成可缩放的高质量文档。
  • PNG设备:生成位图文件,适用于网络使用。
  • JPEG设备:生成有损压缩的位图文件,适用于照片类图像。
  • SVG设备:生成基于XML的矢量图形,可在Web浏览器中缩放。


需要注意的是,并非本讲座中讨论的所有图形设备在所有平台上都可用。例如,Mac或Unix系统上没有Windows设备,同样,Windows系统上也没有quartz设备。因此,你无法在每个平台上打开每一个图形设备。

创建图表的两种方法

了解了设备类型后,我们来看看如何创建图表。创建图表实际上不止一种方法。

第一种方法最简单,可能也是你已经熟悉的方法:直接调用绘图函数(如 plotxyplotqplot),图表就会出现在屏幕设备上。然后,你可以使用 texttitle 等函数为图表添加注释。一旦完成注释并且图表看起来符合你的要求,就完成了。图表显示在屏幕上,你可以查看它或在屏幕上展示给他人。

以下是一些加载数据集并使用 plot 函数创建图表,然后在图表顶部添加标题的代码示例:

# 加载数据
library(datasets)
data(cars)

# 创建图表并添加标题
plot(cars$speed, cars$dist)
title("Speed vs. Stopping Distance")

第二种方法最常用于文件设备,它涉及显式启动一个图形设备。你可能需要先打开一个文件设备,然后使用绘图函数创建图表。使用文件设备时有点棘手,因为图表实际上不会显示在屏幕上,所有绘图信息都进入了文件。因此,在向文件创建图表时,你必须确切知道自己在做什么,因为你无法在屏幕上看到它。通常,最好的方法是将你的绘图代码保存在一个单独的文件中,这样你就可以直接将代码复制粘贴到R中,而不必担心在输入代码时出错。

创建图表后,如果需要,可以为其添加注释。然后,必须使用 dev.off() 函数关闭图形设备。一旦关闭了图形设备,图表就完成了。如果你将其发送到了文件,就可以在计算机上打开该文件,查看它,将其包含在演示文稿中,或通过电子邮件发送给他人。

以下是一些打开PDF设备、创建图表、添加注释然后关闭设备的代码示例:

# 打开PDF设备,指定文件名
pdf("myplot.pdf")

# 创建图表
plot(cars$speed, cars$dist)

# 添加注释
title("Speed vs. Stopping Distance")

# 关闭设备(完成文件写入)
dev.off()

一旦调用了 dev.off(),你的工作目录中应该会有一个名为 myplot.pdf 的文件,你可以用任何PDF查看器打开它。

总结

本节课中我们一起学习了R语言中图形设备的基础知识。我们了解到图形设备是绘图显示的地方,主要分为屏幕设备(如quartz、Windows、X11)和文件设备(如PDF、PNG)。我们探讨了创建图表的两种主要方法:一种是直接绘图到屏幕并即时修改,另一种是显式打开文件设备、绘图、注释然后关闭设备以保存文件。理解这些概念是有效创建、查看和保存R图表的关键第一步。

114:R中的图形设备(第二部分)📊

在本节课中,我们将学习R语言中图形设备的分类、特点以及如何操作多个设备。我们将重点了解矢量格式与位图格式的区别,并掌握在设备间复制图形的方法。


矢量格式与位图格式

上一节我们介绍了图形设备的基本概念。本节中,我们来看看图形文件设备的两大主要类别:矢量设备和位图设备。

矢量格式通常最适合用于线形图形,例如散点图或带有回归线的图表。这类图形不包含自然场景或照片图像。矢量格式的优势在于缩放时不会失真,图形质量不会受损。例如,PDF格式具有很好的可移植性,几乎可以在所有平台上使用,是一种通用的矢量格式。

以下是常见的矢量格式及其特点:

  • PDF:通用性强,可移植性好,缩放不失真。
  • SVG(可缩放矢量图形):适用于网页图形,支持动画和交互性,几乎所有网络浏览器都支持。
  • Windows Metafile:仅适用于Windows系统的矢量格式。
  • PostScript:PDF格式的前身,现已不常用。

然而,如果图表包含大量数据点,矢量格式可能不太理想。因为每个点都需要在文件中用信息表示,这会导致文件体积非常庞大。


位图格式

接下来,我们探讨位图格式。位图格式将图形或图像表示为一系列像素,因此能高效地呈现包含大量数据点的图表。

以下是常见的位图格式及其特点:

  • PNG(便携式网络图形):特别适用于线条图或纯色图像。它使用无损压缩算法,文件体积较小,且几乎所有网络浏览器都支持,适合网页图形。
  • JPEG:非常适用于自然场景(如照片),这类图像包含颜色渐变而非纯色。它使用有损压缩算法,文件体积非常小、效率高。同样适合绘制包含大量数据点的图表。任何计算机和网络浏览器都能读取JPEG文件。
  • TIFF:另一种较旧的位图格式,支持无损压缩,目前仍很常用。
  • BMP:Windows系统原生的位图格式,常用于图标等。

需要注意的是,位图格式通常缩放效果不佳。尝试放大或缩小已生成的位图图形可能会导致图像质量失真。此外,JPEG格式不适用于线条图,因为在线条处可能会出现锯齿状边缘。


操作多个图形设备

在R中,可以同时打开多个图形设备。例如,您可能希望同时查看三到四个不同的图表。实现方法是打开三到四个不同的屏幕设备。

您可以通过显式调用函数来启动图形设备。例如,在Mac系统上,可以多次调用quartz()函数来打开多个图形屏幕设备。但是,一次只能向一个设备绘图。当前正在接收绘图的设备称为活动图形设备

您可以使用dev.cur()函数来查看当前哪个设备是活动设备。该函数会返回一个整数,标识当前活动的图形设备。每个图形设备都会被分配一个大于等于2的整数(没有编号为1的设备)。

您可以使用dev.set()函数来更改活动图形设备。该函数接受一个整数值作为参数,将活动设备切换到对应编号的设备上。这样,您就可以根据需要在不同图形设备之间切换。


在设备间复制图形

最后,我们学习如何在设备间复制图形。这通常用于以下场景:您在屏幕上创建了一个图形,非常满意,并希望将其保存到文件中。

有两种方法可以实现:

  1. 如果您保存了生成该图形的代码,可以打开一个文件设备,然后将代码复制粘贴到R中执行,最后关闭文件设备。这样图形就保存在文件中了。
  2. 一个更快、有时也更方便的方法是,直接将图形从屏幕设备复制到文件设备。

您可以使用dev.copy()函数将图形从任一设备复制到另一设备。如果特别希望生成PDF文件,可以使用dev.copy2pdf()函数,它能直接将屏幕设备上的图形复制到PDF文件中。

重要提示:复制图形并非精确操作。最终文件中的图形可能与屏幕上看到的图形不完全一致,您可能需要进行一些调整。

以下是一个示例代码,它在屏幕上创建一个图形(使用R内置的faithful老忠实喷泉数据集),然后将其复制到PNG文件中:

# 在屏幕设备上创建图形
with(faithful, plot(eruptions, waiting))
title(main = "Old Faithful Geyser Data")

# 将图形复制到PNG文件
dev.copy(png, file = "geyserplot.png")
# 关闭PNG设备,完成文件写入
dev.off()

执行dev.off()后,计算机上就会生成一个名为“geyserplot.png”的PNG格式文件,您可以将其发送给他人或用于演示文稿中。


总结

本节课中,我们一起学习了R语言中图形设备的核心知识。基本要点如下:

  • 每个图形都必须在某个图形设备上创建,通常是屏幕设备,这适用于探索性分析。
  • 若想保存图形或发送给他人,则必须在文件设备中创建图形。
  • 有多种文件格式可供选择,包括PDF、SVG等矢量格式,以及PNG、JPEG等位图格式。
  • 可以同时打开和操作多个图形设备,并使用dev.cur()dev.set()进行管理。
  • 可以使用dev.copy()等函数在设备间复制图形,但需注意复制结果可能并非完全一致。

115:📊 格子绘图系统(第 1 部分)

在本节课中,我们将要学习 R 语言中的格子绘图系统。这个系统与基础绘图系统不同,它特别适用于绘制高维数据,并能一次性生成大量图形。

概述

格子绘图系统是 R 语言中一个独立的绘图系统,其设计理念和工作方式与基础绘图系统有显著差异。它尤其擅长处理多维数据,并能高效地创建多面板图形。

格子绘图系统简介

上一节我们提到了格子绘图系统的特点,本节中我们来看看它的具体实现方式。

格子绘图系统通过 lattice 包实现,使用前需要加载该包。lattice 包底层依赖于 grid 包,后者提供了格子图形系统的底层基础设施。用户通常不需要直接调用 grid 包中的函数,但了解其存在是有帮助的。

与基础绘图系统不同,格子绘图系统没有“先创建图形,后添加标注”的两阶段过程。在格子系统中,你必须通过一次函数调用就完整地指定图形的所有细节,因为之后没有机会再进行标注。

主要绘图函数

以下是 lattice 包中一些主要的绘图函数:

  • xyplot:这是最重要的函数,用于创建散点图。
  • bwplot:用于绘制箱线图。
  • histogram:用于绘制直方图。
  • stripplot:类似于箱线图,但使用点来表示。
  • dotplot:绘制点图,其点排列类似小提琴弦。
  • splom:绘制散点图矩阵,类似于基础绘图系统中的 pairs 函数。
  • levelplotcontourplot:用于绘制图像数据。

深入 xyplot 函数

现在,我们来重点了解一下 xyplot 函数,因为它是最常用的。

xyplot 函数的基本调用使用公式表示法:

xyplot(y ~ x | f * g, data)

其中,y 是 Y 轴变量,x 是 X 轴变量。竖线 | 后面是条件变量,例如 fg 是需要在其不同水平上分别绘图的分类变量。这个函数调用的含义是:为 fg 的每一个水平组合,绘制 yx 的散点图。第二个参数 data 指定了包含这些变量的数据框。

简单散点图示例

加载必要的包和数据后,我们可以绘制一个简单的散点图:

library(lattice)
library(datasets)
xyplot(Ozone ~ Wind, data = airquality)

这段代码绘制了 airquality 数据框中臭氧浓度与风速的关系散点图。其默认样式(如使用蓝色空心圆点)与基础绘图系统略有不同。

多面板散点图示例

xyplot 的强大之处在于能轻松创建多面板图形。以下代码将月份转换为因子,并按月份分别绘制臭氧与风速的关系:

airquality <- transform(airquality, Month = factor(Month))
xyplot(Ozone ~ Wind | Month, data = airquality, layout = c(5, 1))

通过公式 Ozone ~ Wind | Month,我们为 5 月到 9 月的每一个月生成了一个子图。从图中可以观察到,臭氧与风速的关系在不同月份间有所变化。在基础绘图系统中,创建这样的图形需要多次函数调用,而 xyplot 只需一次。

格子绘图系统的行为特点

了解格子函数与基础图形函数的行为差异非常重要。

基础图形函数会直接将图形绘制到图形设备上。而格子函数(如 xyplot)则不同:它们并不立即绘图,而是返回一个属于 trellis 类的对象。这个对象需要被打印,图形才会被发送到图形设备。

不过,这个过程对用户通常是透明的。因为当你调用 xyplot 时,R 的自动打印功能会立即将这个 trellis 对象打印出来,从而显示出图形。所以大多数时候,你感觉不到这个两步过程。

你可以通过以下代码观察这个行为:

p <- xyplot(Ozone ~ Wind, data = airquality) # 此时图形不会显示
print(p) # 执行此命令后,图形才会显示

运行第一行代码后,图形不会出现。只有运行 print(p) 或依靠自动打印(例如在交互式环境中直接输入 p)时,图形才会显示。

总结

本节课中我们一起学习了 R 语言格子绘图系统的基础知识。我们了解到格子系统通过 lattice 包实现,特别适合绘制条件多面板图形。我们重点学习了 xyplot 函数及其公式语法,它可以用简洁的代码探索变量间在不同条件下的关系。最后,我们明白了格子函数返回 trellis 对象并由打印机制驱动显示的特性。掌握这些基础将帮助我们更高效地进行数据可视化。

116:📊 格子绘图系统第2部分

在本节课中,我们将要学习R语言lattice绘图系统的进阶功能,特别是如何使用自定义面板函数来增强多面板图表的表达能力,以及如何利用该系统高效地探索和可视化复杂的数据集。

上一节我们介绍了lattice系统的基础概念和核心绘图函数。本节中我们来看看如何通过自定义面板函数来精确控制每个子面板的绘图内容,从而创建更丰富、更具洞察力的可视化图表。

自定义面板函数

lattice系统拥有所谓的“面板函数”。当您创建一个多面板图表时,会有一个函数被调用来绘制每个面板中的数据,这个函数控制着每个面板中发生的一切。lattice包自带了许多默认的面板函数,但如果您想自定义每个面板的内容,也可以提供自己的函数。

基本上,每个面板函数会接收该特定面板内数据点的x和y坐标。请记住,每个面板代表数据的一个子集,这个子集由您提供的条件变量定义。因此,对于每一个面板,面板函数都会获取该图内所有点的x、y坐标。

以下是一个示例,我们生成了一些遵循线性模型的随机数据,并创建了一个因子变量来区分“组1”和“组2”。我们希望通过分组来绘制x和y的关系。

# 生成示例数据
set.seed(123)
x <- rnorm(100)
y_group1 <- 2 * x[1:50] + rnorm(50, sd=0.5)
y_group2 <- rnorm(50, sd=1.5)
y <- c(y_group1, y_group2)
group <- factor(rep(c("Group 1", "Group 2"), each=50))

# 使用xyplot绘制分组图
library(lattice)
xyplot(y ~ x | group)

在“组1”中,看起来有很强的线性关系,而在“组2”中,似乎没有明显关系。

添加自定义元素

您可以通过panel参数调用自定义面板函数。以下是自定义面板函数的基本结构:

# 自定义面板函数示例:添加水平中位线
xyplot(y ~ x | group,
       panel = function(x, y, ...) {
         panel.xyplot(x, y, ...) # 首先调用默认函数绘制点
         panel.abline(h = median(y, na.rm = TRUE), lty = 2) # 添加一条水平虚线表示y值的中位数
       })

在这个自定义函数中,前两个参数是xy,后面跟着...(省略号),表示可能传入的任何其他参数。函数内,我们首先调用默认的panel.xyplot函数让数据点出现,然后我们的定制操作是:在每个面板中添加一条水平虚线,代表该面板内y值的中位数。现在,您可以在每个面板中看到一条虚线,正好位于y坐标中位数的位置。

另一个更高级的做法是添加回归线,这有助于观察每个面板内x和y之间的线性关系。

# 自定义面板函数示例:添加回归线
xyplot(y ~ x | group,
       panel = function(x, y, ...) {
         panel.xyplot(x, y, ...) # 绘制点
         panel.lmline(x, y, col = "red") # 添加回归线
       })

这里,我们再次传递了一个自定义函数。首先调用panel.xyplot函数让点、坐标轴标签等元素出现,然后调用panel.lmline来为面板添加回归线。

一个重要的注意事项是:您不能在lattice系统中使用基础绘图系统的任何注释函数。 作为通用规则,不能在不同绘图系统之间混合使用函数。因此,您必须使用与给定绘图系统相关的所有函数。



实战案例:可视化大型数据集

为了展示如何处理更大的数据集,我们来看一个来自“巴尔的摩市小鼠过敏原与哮喘队列研究”的实例。这项研究旨在观察巴尔的摩市哮喘儿童的室内环境,其中许多儿童对小鼠过敏原过敏。这是一项观察性研究,包含一次基线家访和随后每三个月一次、为期一年的随访,总共五次访问。

一个可能的研究问题是:室内空气中小鼠过敏原的水平如何随时间变化,以及在受试者之间有何差异?我们想要为每个受试者的每次访问绘制空气中小鼠过敏原水平的图。数据集中有150名受试者,每人有五次访问,因此我们总共有750个数据点需要观察。

一种紧凑且简单的方法是使用xyplot和多面板lattice图。

# 假设数据框‘mast’包含‘subject_id’, ‘visit’, ‘log_allergen’列
# xyplot(log_allergen ~ visit | subject_id, data = mast,
#        type = "b", # 用点和线连接
#        layout = c(5, 30), # 大致布局,实际会根据数据调整
#        scales = list(x = list(rot = 45)), # 旋转x轴标签
#        main = “Log Airborne Mouse Allergen by Subject and Visit”)

通过这个图,我们可以观察到:

  • 个体内变异:在每个面板(代表一个受试者)内,可以看到他们的过敏原水平在访问之间会上升、下降或波动。
  • 个体间变异:可以看到有些人的水平普遍很高,而有些人则普遍较低。这体现了除了个体内变异之外的个体间差异。
  • 缺失值:可以看到不少人存在缺失值,并非每个人都有五个值。有些人可能只有两个或一个值。这可能有助于后续跟进这些受试者。
  • 变异程度:有些受试者的变异很大(在访问间上下波动剧烈),而有些则几乎没有变异(每次访问的水平几乎相同)。

根据您具体的研究兴趣,您可能希望跟进其中一些模式。可以看到,通过本质上一两个函数调用,您就能制作出这样一个庞大的图表,查看大量数据,而无需编写大量代码。这正是lattice系统力量的一部分——只要数据以某种方式格式化,它就能让您轻松查看大量数据。

这展示了访问编号与对数转换后空气中小鼠过敏原水平之间的关系,是按受试者分组的。这是总结整个研究数据的一种快速方法。

总结与核心优势

本节课中我们一起学习了lattice绘图系统的进阶应用。

以下是lattice绘图的核心要点总结:

  • 单函数调用lattice图是通过调用xyplot等核心函数一次构建的。
  • 自动布局lattice图的一个优点是边距、间距和标签等元素会自动处理,您不需要像在基础绘图系统中那样频繁地设置maromamtext等选项。
  • 条件绘图的理想选择:当您拥有可以通过某些变量进行“条件筛选”来查看的数据集时,lattice系统是理想的选择。您通常希望观察某种关系,但希望在不同水平的另一个变量内部进行观察,即在不同条件下查看同类型的图表。
  • 强大的自定义能力:您可以使用自定义的面板函数来精确修改每个绘图面板中的内容,这为您定制这些面板图的外观提供了强大的能力。


我发现lattice系统对于以非常快速的方式查看大量数据非常有用。我鼓励您尝试使用它,并探索其他函数,如bwplot(箱线图)和splom(散点图矩阵),看看它们如何为您工作。

117:ggplot2 第1部分 📊

概述

在本节课中,我们将要学习 R 语言中一个强大的绘图包——ggplot2。我们将了解它的基本概念,并学习如何使用 qplot 函数来创建基础的图形。ggplot2 基于“图形语法”理论,它提供了一种系统且灵活的方式来构建数据可视化。

什么是 ggplot2?

首先,一个非常基础的问题是:什么是 ggplot2?

本质上,它是一个可以从 CRAN 下载的 R 包。它实现了一种称为“图形语法”的理论,该理论最初由 Leland Wilkinson 提出,并在《Grammar of Graphics》一书中进行了描述。

图形语法描述了如何将图形分解为抽象的概念。你可以将其类比为英语语法中的动词、名词和形容词。问题在于,数据图形的“动词”、“名词”和“形容词”是什么?图形语法描述了这些基本元素,以便你可以将它们组合起来创建新型的图形。

就像你可以用一个动词、一个名词和一个形容词组成一个可能从未有人听过的新句子一样,你可以利用图形语法,将图形的各个方面组合起来,创造出前所未见的图形。这就是其基本理念,它是一个用于组织各种数据图形的非常强大的概念。

直到最近,R 语言中还没有其具体的实现。但 Hadley Wickham 在爱荷华州立大学读研究生时,将图形语法实现为一个名为 ggplot 的 R 包。其当前的实现被称为 ggplot2。因此,我们可以将其视为 R 中的第三种图形系统,尽管它是建立在 R 自带的网格图形系统之上的。它已成为一种非常流行的绘图模式。

  • 第一种模式是使用 plothistboxplot 等函数的基础绘图系统。
  • 第二种模式是使用 xyplot 等函数的 lattice 绘图系统。
  • 第三种模式就是 ggplot2。

你可以从 CRAN 获取这个包,使用 install.packages 命令安装,它几乎可以在所有系统上运行。你也可以访问 ggplot2 的官方网站:ggplot2.org。

ggplot2 的优点在于它基于图形语法,从某种意义上说,它有一套图形理论。你可以利用这套理论,重新组合不同的部分来创建新型的图表。正如 Hadley Wickham 在他的书中所说,其基本理念是缩短从想法到图表的距离。如果你有一些数据,并想到了可视化这些数据的方法,你希望能够迅速将这些想法转化为屏幕上的图像。

图形语法的核心思想

从 ggplot2 的书中,这句话概括了其基本思想:图形语法告诉我们,一个统计图形是从数据到美学属性(如颜色、形状、大小)的映射,这些属性应用于几何对象(如点、线、条)。图形还可能包含数据的统计变换,并绘制在特定的坐标系上。

因此,我们拥有从数据到美学属性的映射、几何对象、统计变换和坐标系。

初识 qplot 函数

在本讲中,我将主要讨论 qplot 函数,它是最基础的函数,对于从基础绘图系统过渡过来的用户来说,可能是最好的起点。

在基础绘图系统中,核心函数是 plot 函数。而 qplot(可以理解为“快速绘图”)是 ggplot2 中的核心函数,它与基础系统中的 plot 函数类似。

使用 ggplot2 时需要习惯的一个关键区别是:当你使用 qplot 函数绘图并传递数据时,通常需要告诉它数据来自哪里,而数据总是来自一个数据框。因此,你的数据必须组织在数据框中,然后当你绘制变量时,这些变量将来自该数据框。

如果你不指定数据框,qplot 函数(或所有绘图函数)会在你的工作空间中寻找数据。但通常最好指定数据框,这样当你阅读生成图形的代码时,就能确切知道数据来自何处。

绘图前的准备:数据框与因子

因此,在开始绘图之前,组织好数据框非常重要。一旦开始绘图,图形由美学属性和几何对象构成。美学属性指的是点的大小、形状、颜色等,而几何对象则是你正在绘制的对象类型,例如是点、线还是条形图等。

对于 qplot 函数(以及使用 lattice 函数时同样重要)的一个关键方面是使用因子变量的概念。因子非常重要,因为它们指示了数据的子集。

例如,假设你有一个数据框,其中包含一个 Y 变量、一个 X 变量和一个因子变量。该因子将指示数据框中的数据子集。例如,你可能有一个表示性别的因子,包含一组男性和一组女性,这些就是你的数据子集。你可能希望按不同子集绘制某种关系,或者根据点是男性还是女性来着色。因此,由各种因子变量指示的类别对于标注图形非常有用。

关于这个特性,重要的一点是:当数据中存在因子变量时,你需要确保它们被正确标记。通常,将因子变量标记为 1、2、3 是没有用的,即使你有三个类别。通常你希望用更具信息性的标签来标记它们,以便了解这些因子变量试图编码什么信息。

使用 qplot

qplot 函数使用起来相当直接,我认为它很容易上手。它隐藏了 ggplot2 底层操作的许多细节,这在许多情况下是没问题的。

ggplot 函数才是该系统的核心函数,它非常灵活,可以与许多 qplot 无法做到的事情结合使用。

总结

本节课中,我们一起学习了 ggplot2 的基本概念。我们了解到 ggplot2 是基于“图形语法”理论构建的 R 包,它提供了一种强大且系统的方式来创建数据可视化。我们重点介绍了其入门函数 qplot,它类似于基础绘图系统中的 plot 函数,但要求数据组织在数据框中,并强调了正确使用因子变量对于图形分组和标注的重要性。在下一讲中,我们将更深入地探讨 ggplot2 的设计原理和扩展功能。

118: Foundations using R

课程编号:P118 - ggplot2 第2部分 📊

在本节课中,我们将学习如何使用 ggplot2 包中的 qplot 函数来创建和定制各种统计图形。我们将通过两个数据集示例,探索如何通过颜色、形状、分面等功能来可视化数据中的不同子组和关系。


数据集介绍

我将从一个示例数据集开始。这个数据集随 ggplot2 包提供,安装包后即可加载。这是 mpg 数据集,它记录了多种不同类型汽车的每加仑英里数。从 str 函数的结果可以看出,该数据集包含 234 个观测值(即 234 种不同的汽车)和 11 个测量变量。

具体来说,我将关注以下几个变量:

  • displ:发动机排量,是发动机大小的指标。
  • hwy:高速公路油耗。
  • drv:驱动类型,分为四轮驱动(4)、前轮驱动(f)和后轮驱动(r)。

请注意,数据集中的因子变量已被适当标记。例如,manufacturer 变量标记了汽车制造商(奥迪、雪佛兰等),drv 变量则用 “4”、“f”、“r” 来标记驱动类型。


基础绘图:散点图

一个非常简单的绘图,我称之为 ggplot 的 “Hello World”,是调用 qplot 函数。在 x 轴上我放置了表示发动机大小的 displ 变量,在 y 轴上放置了高速公路油耗 hwy。我通过 data 参数指定数据框为 mpg

qplot(x = displ, y = hwy, data = mpg)

这个绘图非常简单。可以看到,它生成的图形与传统的基础绘图系统有很大不同:它有灰色的背景和白色的网格线,点是实心圆点而非基础绘图系统中典型的空心圆,并且 x 轴和 y 轴都有标签。


修改图形属性:按颜色区分子组

我们可以修改一些图形属性来突出显示数据的不同子组。其中一个子组可以通过驱动类型 drv 来区分:有些车是前轮驱动,有些是后轮驱动,有些是四轮驱动。

我像之前一样指定了 x、y 坐标和数据框,然后添加了另一个参数 color。我指定颜色映射到驱动变量 drv。这意味着驱动变量的不同水平将被自动分配不同的颜色。

qplot(x = displ, y = hwy, data = mpg, color = drv)

在这个新图中,前轮驱动汽车显示为绿色,后轮驱动为蓝色,四轮驱动为红色。可以看到,前轮驱动汽车的油耗往往最高,四轮驱动最低,后轮驱动居中。请注意,图例是自动放置在图形上的,因子变量的不同水平也自动进行了颜色编码,无需进行任何特殊操作。


添加统计摘要:平滑曲线

有时我们想为图形添加一个统计摘要。这里我们选择添加的摘要是一种平滑器,更技术性的名称是 LOESS。它可以平滑数据,以便观察数据的整体趋势。

我通过添加 geom 参数来实现这一点。我想在这个图上添加两种几何对象:一种是点本身(以便看到数据),另一种是平滑线。

qplot(x = displ, y = hwy, data = mpg, geom = c(“point”, “smooth”))

这条蓝色的线就是平滑线,其 95% 的置信区间由灰色的带状区域表示。


创建直方图

通过仅指定一个变量,我们可以用 qplot 函数创建直方图。这里我只指定了 hwy 变量,它显示了数据集中所有汽车的高速公路油耗。

同样,我想区分哪些车是四轮驱动,哪些是前轮驱动等。因此,我指定了 fill 参数(而不是 color)。fill 参数表示直方图的不同部分将根据驱动类型填充不同的颜色。

qplot(x = hwy, data = mpg, fill = drv)

你可以再次看到类似的图像:四轮驱动车辆的油耗往往最低,前轮驱动车辆的油耗往往最高。


使用分面

ggplot 的另一个特性叫做 分面,类似于 lattice 包中的面板。其思想是,你可以创建单独的图形来展示由因子变量指示的数据子集,并可以制作一个图形面板来一起查看这些不同的子集。

之前我们通过不同颜色来编码子组,但如果数据点很多,颜色可能会重叠,难以看清不同的组。一个更简单的方法是,将三个组拆分到单独的面板中,制作三个独立的图形。

在左侧的图中,我有三个不同的散点图,展示了排量与高速公路油耗的关系,并按不同的驱动类型进行了拆分。我通过 facets 参数来指定这一点。

facets 参数接受一个公式,基本格式是:左侧变量 ~ 右侧变量。右侧的变量决定面板的列数,左侧的变量决定面板的行数。

请注意,在红色框出的左侧图中,公式左侧是一个点(.),表示只有一行。右侧是 drv 变量,表示应该用这个变量来决定列数。因为 drv 变量有三个水平,所以会有三列,最终三张图排成一行。

# 三列,一行
qplot(x = displ, y = hwy, data = mpg, facets = . ~ drv)

在蓝色框出的右侧图中,我有三个直方图。注意,在 facets 参数中,我把 drv 变量放在了波浪线(~)的左侧。这表示我想要三行。因为波浪线右侧没有变量,所以只有一列。这样我就得到了按三个组分别查看高速公路油耗的三个直方图。

# 三行,一列
qplot(x = hwy, data = mpg, facets = drv ~ .)


复杂示例:哮喘研究数据

接下来,我想通过一个来自约翰霍普金斯大学的数据集,演示一个稍微复杂一些的例子。该数据来自 MAACS 研究,这是一项在约翰霍普金斯大学对巴尔的摩 5 至 17 岁儿童进行的研究。这些儿童都患有持续性哮喘,并在过去一年中有过急性发作。

以下是该数据的一个片段,有 750 个观测值,为了演示,我只列出 5 个变量:

  • id:标识符。
  • eno:呼出一氧化氮,这是一个测量值,大致对应于肺部炎症水平。eno 值大表示存在肺部炎症。
  • pm25:细颗粒物,这是直径小于 2.5 微米的灰尘。
  • mopos:小鼠过敏原皮试阳性变量。这是一个皮肤测试,告诉我们儿童是否对小鼠过敏原过敏。


绘制呼出一氧化氮的直方图

以下是呼出一氧化氮(取对数后)的基本直方图。你可以看到它有一个有趣的形状,看起来有两个甚至三个峰。

qplot(x = log(eno), data = maacs)


按组着色的直方图

现在,我制作了另一个直方图,但根据 mopos 变量对不同的组进行了颜色编码。我区分了对小鼠过敏原敏感(过敏)和不敏感(不过敏)的人群。

你可以想象,对小鼠过敏原敏感的儿童可能过敏更严重,可能对多种环境触发因素更敏感。从数据中大致可以看出,蓝色条(过敏组)略高,红色条(非过敏组)略低。这表明,平均而言,对小鼠过敏原呈阳性的儿童肺部炎症水平略高。

qplot(x = log(eno), data = maacs, fill = mopos)


使用密度平滑可视化

可视化这类一维数据的另一种方法是进行密度平滑。在左侧,我为 log(eno) 变量添加了 geom_density。你可以看到密度平滑图中至少有两个峰。

在右侧,我通过 color = mopos 按是否对小鼠过敏原阳性来拆分颜色。你可以看到,这两个峰大致对应于他们是否对小鼠过敏原过敏。

# 左侧:整体密度
qplot(x = log(eno), data = maacs, geom = “density”)
# 右侧:按组着色的密度
qplot(x = log(eno), data = maacs, geom = “density”, color = mopos)


散点图与分组

现在,如果我们想看一些散点图,比如探究呼出一氧化氮是否与家中的细颗粒物水平有关,我们可以绘制 log(pm25)log(eno) 的散点图。

在最左侧,我简单地绘制了 log(eno)log(pm25) 的散点图。很难直接看出可能存在的关系。

在中间的图中,我制作了相同的散点图,但通过形状区分了两组(过敏与非过敏儿童)。我指定了 shape 参数,并将其映射到 mopos 变量。但由于点的重叠,很难看清两组。

在右侧的图中,我没有用形状而是用颜色来区分两组。在这个图中,稍微容易看到两个不同的组。

# 左侧:简单散点图
qplot(x = log(pm25), y = log(eno), data = maacs)
# 中间:按形状分组
qplot(x = log(pm25), y = log(eno), data = maacs, shape = mopos)
# 右侧:按颜色分组
qplot(x = log(pm25), y = log(eno), data = maacs, color = mopos)

添加分组平滑线

我们可以做的一件事是平滑 log(pm25)log(eno) 之间的关系,并查看这种关系在两个组中是否有何不同。

我将 geom 设置为点和一条平滑线。这次我不使用 LOESS,而是指定 method = “lm”,以便使用标准的线性回归模型。这样,我可以分别查看过敏与非过敏儿童中 PM2.5 与 ENO 的线性关系。

qplot(x = log(pm25), y = log(eno), data = maacs, color = mopos, geom = c(“point”, “smooth”), method = “lm”)

大致可以看出,在非过敏儿童(红点)中,存在轻微的负相关关系,但考虑到置信区间,这种关系并不特别强。而在过敏儿童中,PM2.5 与 ENO 之间似乎存在递增的关系。


使用分面查看相同数据

查看相同数据的另一种方法是使用分面将其拆分。与其将两组重叠并用不同颜色编码,不如使用 facets 参数将它们拆分成两个图。

这里我指定了 facets 参数,并说我想要由 mopos 变量决定的两列(“否” 和 “是”)。然后我在每个面板内平滑关系。你可以再次看到相同的情况:在非过敏儿童中,存在微小的递减关系;在过敏儿童中,存在轻微的递增关系。

qplot(x = log(pm25), y = log(eno), data = maacs, facets = . ~ mopos, geom = c(“point”, “smooth”), method = “lm”)

总结

本节课中我们一起学习了 qplot 函数。它是一个简单的函数,可以用来快速制作各种图形,类似于基础的 plot 函数。你指定 x、y 和数据,然后可以选择多种选项。

qplot 有许多很好的内置功能:

  • 对数据子集进行颜色编码非常容易。
  • 使用分面拆分不同面板也非常容易。
  • 可以使用 shape 参数选择不同的绘图符号。

它的语法介于基础绘图和 lattice 包之间。如果你喜欢这种设计风格,它生成的图形非常美观。但是,如果你不喜欢某些功能或设计,想要对图形的不同方面进行更低层次的定制,修改 qplot 函数来满足需求会有点棘手。这时,你需要深入 ggplot 函数的核心,这将在下一讲中讨论。

119:ggplot2 第3部分 📊

概述

在本节课中,我们将深入学习 ggplot2 包的核心概念与底层架构。我们将超越 qplot 函数,探索如何通过分层构建的方式,创建高度定制化的图形。本节课将使用一个关于儿童哮喘与环境因素的真实研究数据作为示例。


回顾:什么是 ggplot2?

上一节我们介绍了使用 qplot 函数快速创建基础图形。本节中,我们来看看 ggplot2 更底层的原理。

ggplot2 是 Leland Wilkinson 提出的“图形语法”在 R 语言中的实现。图形语法为图形提供了一种抽象的、系统化的描述方式,类似于语言的语法规则,定义了图形的基本构成元素及其组合方式。

一个 ggplot2 图形通常包含以下几个核心组件:

  • 数据:这是绘图的基础,通常是一个包含所有待绘图变量的数据框。
  • 美学映射:这定义了数据如何映射到图形的视觉属性上,例如将数据映射到点的x轴位置y轴位置颜色大小
  • 几何对象:这是你在图上实际看到的元素,例如线条形多边形
  • 分面:用于创建条件绘图或多面板图形,例如根据第三个变量的不同水平分别展示图形。
  • 统计变换:对数据进行统计处理后再绘图,例如平滑分箱计算分位数拟合回归线
  • 标度:控制美学映射的具体表现方式,例如将“性别”这个分类变量映射为红色(男)和蓝色(女)。
  • 坐标系:定义数据如何映射到图形的坐标平面上,例如笛卡尔坐标系或极坐标系。

分层构建:艺术家调色板模型

lattice 系统在一次函数调用中定义整个图形不同,ggplot2 采用“分层构建”或“艺术家调色板”模型。

你可以从最基础的元素开始,然后像画家在画布上添加图层一样,逐步添加几何对象、统计变换、标签等组件。这种模式提供了极大的灵活性。

以下是构建一个图形的典型分层步骤:

  1. 首先,你告诉 ggplot2 要使用的数据和基本的美学映射
  2. 然后,你可以叠加一个几何对象层(例如散点)来展示数据。
  3. 接着,你可能想叠加一个统计摘要层(例如一条平滑曲线或回归线)。
  4. 最后,你可能会添加一些注释或元数据(例如标题、坐标轴标签)。

案例研究:哮喘与室内空气污染

我们将使用一个真实的研究案例来演示分层构建过程。这项研究调查了巴尔的摩地区5-17岁哮喘儿童的发病情况,特别关注室内空气污染物(如细颗粒物PM2.5)与身体质量指数对哮喘症状的交互影响。

我们的核心科学问题是:BMI是否会改变PM2.5暴露与夜间哮喘症状之间的关系?

以下是用 qplot 快速制作的探索性图形,它初步显示了这种关系可能因BMI状态(正常体重 vs. 超重/肥胖)而异。

现在,我们将使用底层的 ggplot2 语法重新构建这个图形。


第一步:初始化与数据映射

首先,我们需要准备数据并创建初始的 ggplot 对象。我们的数据框包含三个关键变量:logpm25(PM2.5的对数值)、bmicat(BMI类别)和 NocturnalSym(夜间症状天数)。

# 创建初始的 ggplot 对象
# 参数 data 指定数据框,aes 定义美学映射(x轴和y轴变量)
g <- ggplot(data = maacs, aes(x = logpm25, y = NocturnalSym))

此时,对象 g 包含了数据和映射信息,但还没有指定如何绘制图形。调用 summary(g) 可以查看其内容,但直接打印 g 会报错,因为缺少“图层”。


第二步:添加几何对象层

一个图形必须至少包含一个几何对象层。我们使用 + 运算符来添加图层。

# 在基础图形 g 上添加一个散点图层
p <- g + geom_point()
# 打印图形 p
print(p)

关键点

  • geom_point() 函数在这里没有传递任何参数(如 x, y),因为它会自动从初始对象 g 中继承数据和美学映射。
  • 你可以将添加图层后的结果保存为新对象(如 p),也可以直接运行 g + geom_point() 让图形自动打印到屏幕上。
  • 默认情况下,geom_point() 会使用实心圆点。

添加了散点层后,我们得到了基本的散点图。


总结

本节课我们一起学习了 ggplot2 图形语法的核心组件和分层构建的思想。我们了解到,一个 ggplot2 图形始于数据和美学映射的定义,然后通过 + 运算符叠加不同的图层(如 geom_point)来逐步构建完整的可视化。

我们通过一个研究案例,实践了从初始化 ggplot 对象到添加第一个几何对象层的完整流程。在接下来的课程中,我们将在此基础上,学习如何添加更复杂的图层,如平滑曲线、分面以及如何自定义图形的外观。

120:ggplot2 第四部分

在本节课中,我们将继续深入学习 ggplot2 包,重点介绍如何为图形添加平滑曲线、分面以及如何自定义图形的各种元素,如颜色、标签和主题。我们将通过具体的代码示例,帮助你理解如何构建和美化数据可视化图形。


添加平滑曲线

上一节我们介绍了如何创建散点图。本节中,我们来看看如何为散点图添加平滑曲线,以帮助我们观察数据的整体趋势。

你可以通过 geom_smooth() 函数为图形添加一条平滑曲线。默认情况下,该函数使用 LOESS 平滑方法,它会生成一条蓝色曲线和灰色的置信区间带。

以下是添加默认平滑曲线的代码:

g + geom_point() + geom_smooth()

然而,LOESS 平滑在数据边界处可能因为数据稀疏而产生较大的置信区间,导致图形看起来“噪声”较多。如果你希望看到更简洁的趋势线,可以使用线性回归线代替。

你可以通过指定 method = "lm" 参数,将平滑方法改为线性回归:

g + geom_point() + geom_smooth(method = "lm")

线性回归线不如 LOESS 平滑灵活,但它能更清晰地展示数据的总体趋势,并且在数据边界处的置信区间通常更窄。


使用分面

分面功能允许我们根据某个分类变量的不同水平,将数据绘制在多个独立的子图中进行比较。这有助于我们观察不同组别间的关系是否存在差异。

要添加分面,我们使用 facet_grid() 函数。其基本语法使用波浪号 ~ 来分隔变量,格式为 facet_grid(行变量 ~ 列变量)

如果我们只想根据一个变量(例如 bmi_cat,包含“正常体重”和“超重”两个水平)进行分面,可以这样写:

g + geom_point() + facet_grid(. ~ bmi_cat) + geom_smooth(method = "lm")

这段代码会生成一行两列的子图,每个子图对应一个 BMI 类别。ggplot2 会自动使用因子变量的水平作为每个子图的标题,因此确保你的数据标签清晰易懂非常重要。

图层(如 geom_pointgeom_smoothfacet_grid)的添加顺序通常不影响最终图形的生成,你可以根据需要灵活安排。


自定义图形元素

创建基本图形后,我们经常需要修改坐标轴标签、标题、颜色等元素,以使图形更清晰、更专业。

以下是自定义图形的一些常用函数:

  • 修改标签和标题:可以使用 xlab()ylab()ggtitle() 函数分别设置 X 轴标签、Y 轴标签和图形标题。更通用的方法是使用 labs() 函数,它可以一次性设置多个标签。
  • 修改几何对象属性:每个 geom_* 函数(如 geom_pointgeom_smooth)都有一系列参数可以修改其外观,例如线条粗细、颜色、透明度等。
  • 修改整体主题theme() 函数用于全局性地修改图形主题,例如背景色、图例位置、字体等。此外,还有一些内置的完整主题,如 theme_gray()(默认的灰色背景主题)和 theme_bw()(黑白背景主题),可以一键改变图形的整体风格。

修改点的美学属性

点的颜色、大小和透明度等属性可以通过 geom_point() 函数进行设置。这里有一个关键区别:是将属性设置为一个固定常量,还是将其映射到数据中的某个变量

  • 设置为常量:直接在 geom_point() 函数中指定参数值。例如,以下代码将所有点设置为钢蓝色、大小为 4、透明度为 0.5:
    g + geom_point(color = "steelblue", size = 4, alpha = 0.5)
    
  • 映射到变量:如果你想根据数据中某个分类变量(如 bmi_cat)来区分点的颜色,必须将映射关系包裹在 aes() 函数内。aes 代表“美学映射”。例如:
    g + geom_point(aes(color = bmi_cat), size = 4, alpha = 0.5)
    
    这段代码会根据 bmi_cat 的不同水平为点分配不同颜色,并自动生成图例。而点的大小和透明度仍然是固定的常量。

使用 labs 函数修改标签

labs() 函数是设置图形标签的便捷工具。你可以用它来设置标题、X 轴和 Y 轴标签。

g + geom_point(aes(color = bmi_cat)) +
    labs(title = "MAX Cohort",
         x = expression(log~PM[2.5]),
         y = "Nocturnal Symptoms")

在上面的代码中,我们使用了 expression() 函数和数学注释语法来生成带有下标(PM₂.₅)的 X 轴标签。


自定义平滑曲线

geom_smooth() 函数同样支持多种自定义选项。例如,你可以改变线条的粗细、线型,并选择是否显示置信区间。

g + geom_point() + geom_smooth(method = "lm", size = 4, linetype = 3, se = FALSE)
  • size = 4:将回归线加粗。
  • linetype = 3:将线型改为虚线。
  • se = FALSE:不显示置信区间带。

更改整体主题

如果你不喜欢默认的灰色背景主题,可以轻松切换到其他内置主题。例如,使用黑白主题 theme_bw()

g + geom_point() + geom_smooth(method = "lm") + theme_bw(base_family = "Times")

此外,你还可以通过 base_family 等参数修改字体。上面的代码将图形主题改为黑白,并将字体设置为 Times New Roman(一种衬线字体)。


总结

本节课中我们一起学习了 ggplot2 的几个高级功能:

  1. 使用 geom_smooth() 添加平滑曲线或回归线来揭示数据趋势。
  2. 利用 facet_grid() 根据分类变量创建分面图,以便于组间比较。
  3. 掌握了如何自定义图形,包括:
    • 使用 labs()xlab()ylab() 修改标签。
    • geom_* 函数中设置常量或通过 aes() 映射变量来改变颜色、大小等美学属性。
    • 使用 theme_bw() 等函数更改图形的整体主题和外观。

通过组合这些图层和自定义选项,你可以创建出信息丰富且美观的专业级数据可视化图形。记住,在绘图前整理好数据并设置清晰的变量标签,往往能让后续的绘图代码更加简洁高效。

121:ggplot2 第五部分

在本节课中,我们将学习 ggplot2 中两个重要的高级主题:如何处理超出绘图范围的数据点,以及如何通过组合多个图层和分面来构建复杂的图形。我们将通过具体的例子来理解这些概念。


🎯 处理超出绘图范围的数据点

上一节我们介绍了 ggplot2 的基本语法。本节中我们来看看 ggplot2 与基础绘图在处理超出坐标轴范围的数据时有何不同。

在基础绘图中,如果数据点超出了设定的坐标轴范围,线条仍会连接所有数据点,只是超出部分在图中不可见。而在 ggplot2 中,默认行为是绘制所有数据点,包括超出范围的。如果我们简单地通过 ylim()xlim() 来限制坐标轴,ggplot2 会直接剔除超出范围的数据点,导致线条在缺失点处断开。

为了在 ggplot2 中重现基础绘图的行为(即保留数据点但限制显示范围),我们需要使用 coord_cartesian() 函数。这个函数会设置坐标轴的显示范围,但不会从数据集中删除任何点。

以下是两种方法的对比代码:

# 基础绘图:线条连接所有点,包括超出范围的
plot(x, y, type = "l", ylim = c(-3, 3))

# ggplot2 错误方法:剔除超出范围的点,线条断开
ggplot(test_data, aes(x, y)) +
  geom_line() +
  ylim(-3, 3)

# ggplot2 正确方法:保留所有点,仅限制显示范围
ggplot(test_data, aes(x, y)) +
  geom_line() +
  coord_cartesian(ylim = c(-3, 3))

🧩 构建复杂图形:组合图层与分面

理解了如何处理数据范围后,现在我们来学习如何通过叠加多个图层和分面来构建更复杂的图形。我们将创建一个研究 PM2.5 与夜间症状关系的图形,并同时考虑 BMI 和 NO2 两个变量的影响。

由于 NO2 是一个连续变量,我们需要先将其分类,以便进行分面绘图。我们可以使用 cut() 函数,根据分位数将连续变量切割成几个区间。

以下是准备数据的步骤:

# 计算 NO2 的 33% 和 66% 分位数
cutpoints <- quantile(no2, c(0, 0.33, 0.66, 1), na.rm = TRUE)

# 使用 cut 函数将 NO2 分为三个类别(三分位数)
no2_cat <- cut(no2, breaks = cutpoints)

现在,我们可以构建最终的图形。这个图形将包含多个元素:

以下是构建该图形的完整代码:

# 创建 ggplot 对象,设置基本美学映射
p <- ggplot(data, aes(x = pm25, y = nocturnal_symptoms))

# 添加点图层,并设置透明度
p <- p + geom_point(alpha = 0.3)

# 添加分面:按 BMI 类别和 NO2 类别分面
p <- p + facet_wrap(~ bmi_cat + no2_cat)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/07414c05f05d6d8f60a416aec440ac1c_1.png)

# 添加平滑曲线(线性回归),并关闭置信区间
p <- p + geom_smooth(method = "lm", se = FALSE)

# 更改主题和字体
p <- p + theme_bw(base_family = "Avenir", base_size = 10)

# 修改坐标轴标签和标题
p <- p + labs(x = "PM2.5 Concentration",
              y = "Nocturnal Symptoms Score",
              title = "Relationship between PM2.5 and Symptoms by BMI and NO2")

通过这段代码,我们逐步添加了散点、分面、平滑线和主题样式,最终形成了一个信息丰富、可读性强的多面板图形。ggplot2 的这种模块化特性,使得构建和定制复杂图形变得非常直观和灵活。


📝 总结

本节课中我们一起学习了 ggplot2 的两个高级技巧。

首先,我们了解了 coord_cartesian() 函数在处理超出绘图范围数据时的重要性,它能确保数据不被剔除,仅调整显示范围。

其次,我们通过一个综合案例,实践了如何将连续变量分类、使用 facet_wrap() 进行分面、叠加多个几何对象(如 geom_pointgeom_smooth),以及如何通过 theme()labs() 函数来美化图形。

总而言之,ggplot2 是一个功能强大且灵活的绘图系统。掌握其“图形语法”,理解如何组合不同的图层和组件,你就能为你的数据创建出几乎任何类型的可视化图形。最好的学习方式就是多动手尝试和探索。

122:层次聚类(第 1 部分)🌳

在本节课中,我们将要学习层次聚类的基本概念。层次聚类是一种用于可视化高维或多维数据的常用技术。它的使用非常简单,其思想对大多数人来说相当直观,并且可以作为快速理解高维数据集中情况的有效方法。

概述

与大多数聚类技术(例如 K 均值)一样,定义层次聚类方法时需要解决一个基本问题:如何定义事物之间的“近”与“远”。因为从某种意义上说,所有聚类方法都试图告诉你,一个事物与另一个事物相比,哪个更近。例如,同一个簇中的元素彼此之间的距离,比它们与另一个簇中元素的距离更近。因此,我们必须定义“近”的含义、将事物分组在一起的含义,以及如何将事物分组。一旦我们有了距离概念和分组方法,我们还需要知道如何可视化处理过程和计算结果,以及如何解释分组的形成方式。

聚类分析是一项非常重要且应用广泛的技术。如果你在谷歌上搜索“聚类分析”,会得到数百万条结果。它在科学和其他应用的许多不同领域都是一种广泛应用的方法。因此,了解其中一些技术的工作原理是有益的。

层次聚类的基本思想

层次聚类,正如其名称所示,涉及将数据组织成一种层次结构。常用的方法是所谓的“凝聚式”方法。这是一种自底向上的方法:你从单个数据点开始,然后将它们逐步合并成簇,直到最终整个数据集成为一个大簇。

你可以想象有许多小颗粒漂浮在空中,你开始将它们分组为小球,然后这些小球被分组为更大的球,最终这些大球被合并成一个巨大的簇。这就是层次聚类的凝聚式方法,也是我们这里要讨论的内容。

基本思想是:首先找到数据中距离最近的两个点,然后将它们合并成一个“超级点”。这个合并后的点并不是数据中实际存在的点,但你可以将其视为一个新点。接着,你移除原始的两个数据点,并用这个合并点替代它们。然后,你不断重复这个过程:找到下一对最接近的事物,将它们合并在一起。一旦你将事物合并在一起,就会得到一棵小树,它可以展示合并的顺序以及哪些事物被合并了。

层次聚类的两个关键要素

这种方法需要两个重要的东西:一是距离度量,二是合并点的方法。

  1. 距离度量:如何计算两点之间的距离?
  2. 合并方法:一旦确定了两点最接近,如何将它们合并在一起?

层次聚类产生的一个优点是生成一棵树,有时称为树状图,它展示了事物是如何合并在一起的。

定义“距离”

在层次聚类方法中,可以说最重要的问题是定义“近”的含义。因为如果你的定义对你的问题没有意义,那么结果将是“垃圾进,垃圾出”。如果你的距离度量没有意义,那么你得到的结果也将相对无意义。

以下是几种常用的距离度量示例:

  • 欧几里得距离:这是最熟悉的距离度量,即任意两点之间的直线距离。
  • 相关距离或相似性:基于两点之间的相关性。
  • 曼哈顿距离:我们稍后会解释其含义。

你必须为你的问题选择一个有意义的距离度量,这样才能产生有意义的结果。

欧几里得距离

这是一个简单的图示。以两个城市为例,比如巴尔的摩和华盛顿特区。如果你把它们放在地图上,可以想象每个城市的中心都有一个 X 和 Y 坐标。你想计算两个城市中心之间的距离,你可以在两个城市之间画一条直线对角线,然后以通常的方式计算距离。这个距离将是 X 坐标差和 Y 坐标差的函数。

要计算欧几里得距离,你取 X 坐标的差值,将其平方;取 Y 坐标的差值,将其平方;将两个平方相加,然后取平方根。这就是欧几里得距离的经典定义。

在现实生活中,这就像一只鸟从华盛顿特区飞到巴尔的摩,它会直接从一座城市飞到另一座城市,因为它可以在空中飞行,不受道路或山脉等障碍物的阻碍。这就是两座城市之间的直线距离。这个距离对你是否有意义,取决于你是否是一只鸟或其他东西。因此,你必须在问题的背景下思考这一点。

欧几里得距离很容易推广到更高维度。即使你有一百个维度,你通常也可以取每个维度的差值,平方,求和,然后取平方根。因此,它非常自然地扩展到非常高维的问题。

曼哈顿距离

曼哈顿距离得名于网格或城市街区网格上的点。想象你在纽约曼哈顿,想从一个点到达另一个点。你可以看这张图上的两个黑圈。如果你想从一个点到达另一个点,在城市街区中,你不能直接从一个点走到另一个点,因为中间会有建筑物,你必须沿着街道走。

在街道上,由于它们是网格状的,你只能向上、向下、向左或向右走。在这种情况下,图中的绿线将代表欧几里得距离,就像一只鸟想飞越一切到达两点之间。但作为一个在地面上行走的人,你必须走红线、蓝线或黄线,或介于其间的任何其他路径。关键在于,你必须沿着城市街区行走。因此,你在灰色街道(你可以将灰线想象成街道)上行走的距离就是曼哈顿距离。

它可以公式化地写为所有不同坐标的绝对值和。如果你有两个坐标,那么你将在 X 维度上行走的距离和在 Y 方向上行走的距离的绝对值相加。曼哈顿距离在某些情况下可能很有用,因为它可能在某些上下文中更准确地表示两点之间的距离,特别是在像曼哈顿这样的城市中,两点可能相距很远,即使它们在欧几里得距离意义上看起来相对接近。

总结

本节课中,我们一起学习了层次聚类的基本原理。我们了解到层次聚类是一种自底向上的凝聚式方法,通过不断合并最近的点来构建层次结构。实现层次聚类需要两个核心要素:距离度量(如欧几里得距离或曼哈顿距离)和合并方法。正确选择距离度量对于获得有意义的聚类结果至关重要。最后,层次聚类的结果通常以树状图的形式呈现,直观展示数据点的合并过程。在下一节中,我们将深入探讨具体的合并方法以及如何解读树状图。

123:层次聚类算法详解(第二部分)🔍

在本节课中,我们将通过一个简单的模拟数据集,详细演示层次聚类算法的工作流程。我们将从计算数据点之间的距离开始,逐步展示数据点如何被合并,最终形成树状图,并学习如何通过“切割”树状图来确定聚类的数量。


数据准备与可视化

为了清晰地展示层次聚类的工作原理,这里模拟了一个非常简单的数据集。

首先,设置随机种子以确保数据可重现。运行以下代码可以生成并查看数据。

set.seed(1234)
par(mar = c(0, 0, 0, 0))
x <- rnorm(12, mean = rep(1:3, each = 4), sd = 0.2)
y <- rnorm(12, mean = rep(c(1, 2, 1), each = 4), sd = 0.2)
plot(x, y, col = "blue", pch = 19, cex = 2)
text(x + 0.05, y + 0.05, labels = as.character(1:12))

图中总共绘制了12个点。从图中可以非常清晰地看到三个簇。使用text函数为每个点添加了标签,以便区分。

接下来,将运行层次聚类算法来观察这些点是如何被合并在一起的。


第一步:计算距离矩阵

运行层次聚类算法的第一步是计算所有不同数据点之间的距离。需要计算所有点之间的两两距离,以便找出哪两个点距离最近。

在R语言中,最简便的方法是使用dist函数。该函数接收一个矩阵或数据框作为输入。本例中的数据框有两列:第一列是X坐标,第二列是Y坐标。

dataFrame <- data.frame(x = x, y = y)
dist(dataFrame)

dist函数计算数据框中所有不同行之间的距离,并返回一个距离矩阵,其中包含了所有成对的距离值。

如果调用dist时不添加其他参数,它默认使用欧几里得距离度量,但也可以通过选项指定其他距离度量。


下方显示的是dist函数返回的距离矩阵的大部分内容。可以看到它是一个下三角矩阵,给出了所有成对距离。

例如,点1和点2之间的距离是0.34。当然,这里的实际距离数值本身没有特定意义,因为数据是模拟生成的。但可以看出,有些点之间的距离较远,有些则较近。

例如,点3和点1之间的距离是0.574,而点3和点2之间的距离是0.24。这表明点3离点2比离点1更近。可以像这样逐行查看各个点彼此之间的距离。


第二步:合并最近的点

层次聚类算法的核心思想是,从最初开始,将两个距离最近的点合并。

观察距离矩阵,最初距离最近的两个点是点5和点6。在图中,它们被标记为橙色。


因为点5和点6距离最近,所以将它们归为一组,合并成一个单一的簇。



这个想法是创建一个单一的“超级点”。图中中间的加号(+)大致代表这个合并点集的新位置。

现在,点5和点6已经被合并为一个点。

接下来,距离最近的两个点是右下角红色的点10和点11。将这两个点合并在一起,创建一个新的“超级点”,它将在这个算法中继续向上聚合。

可以继续按照这个流程进行。


第三步:生成树状图

最终,我们会得到一个小图,称为树状图。它展示了各个点是如何被聚类在一起的。

在树状图的最右侧,可以看到点5和点6被归为一组。

在中间部分,点10和点11被归为一组。

在树中位置越靠下的点,表示它们越早被聚类。而位置越靠上的点,则表示它们被聚类的时间越晚。

可以看到,点5和点6先被合并。然后它们与点7聚类。当点5、6、7全部合并成一个单一的超级点后,它们又与点8合并,依此类推。


第四步:切割树状图以确定簇数

由聚类算法生成的树状图本身并不直接告诉你存在多少个簇。你会注意到图上没有特定的标签标明是两个簇、三个簇还是其他数量。

因此,你必须在某个特定点“切割”这棵树,以确定有多少个簇。

例如,如果在Y轴上标记为2.0的高度处进行切割(即画一条水平线)。

问题在于,这条水平线会与多少个分支相交?如果在2.0的高度画一条水平线,大致会与两个分支相交,这表明大致存在两个簇。

然而,如果在高度为1.0的地方画一条水平线。

你会看到它与三个分支相交,这表明大致存在三个簇。

因此,根据你选择在何处画这条水平线(或切割这棵树),你将在聚类中得到或多或少的簇。

当然,在极端情况下,如果你在树的最底部切割,那么你将得到12个簇,等于数据点的数量。

所以,你需要在对你方便的位置切割这棵树。目前,我们并没有严格的规则来决定在哪里切割。但一旦完成切割,你就可以从层次聚类中获得簇的分配结果。


总结

本节课中,我们一起学习了层次聚类算法的完整步骤。

  1. 准备数据并计算距离矩阵:使用dist函数计算所有数据点之间的成对距离。
  2. 迭代合并最近邻:算法从距离最近的两个点开始,逐步将它们合并成“超级点”。
  3. 生成树状图:树状图直观地展示了数据点被合并的先后顺序和层次关系。
  4. 切割树状图确定簇数:通过在不同高度切割树状图,可以得到不同数量的聚类结果,这需要根据实际分析目标来决定。

层次聚类的优势在于它提供了完整的聚类层次结构,允许你在不同粒度上查看数据分组,而无需预先指定簇的数量。

124:层次聚类(第3部分)🎯

在本节课中,我们将学习如何优化层次聚类结果的可视化,理解不同的聚类合并策略(如平均连接和完全连接),并探索热图函数在可视化高维数据中的应用。

上一节我们介绍了层次聚类的基本概念和树状图的生成。本节中我们来看看如何美化树状图,并深入探讨聚类合并的不同方法。

美化聚类树状图 🌳

R语言默认生成的聚类树状图效果尚可,但我们可以通过特定函数使其更美观。课程网站上提供了一个名为 myPcluster 的函数,下载并导入R环境后即可使用。

该函数生成的树状图有两个特点:

  1. 每个聚类中的点都会用其聚类标签进行标记。
  2. 不同的聚类会以不同的颜色区分。

例如,聚类1的标签全是“1”且显示为黑色,聚类2显示为红色,聚类3显示为绿色。当然,使用此功能前,需要先确定聚类的数量,才能进行标记和着色。

你还可以访问R图形库,查看更丰富有趣的聚类图示例,并了解用于创建精美聚类图的软件。例如,下面这个树状图在叶节点上添加了标签,对不同组进行了着色,并包含了一些额外信息。该图库中还有许多其他优秀的可视化案例。


聚类合并策略 🔗

使用层次聚类时,另一个关键问题是如何合并点。核心在于:合并后,新点的位置用什么来代表?

以下是两种主要的合并策略:

平均连接
其思想是,合并两个点(或聚类)后,新坐标位置是其所有点坐标的平均值。这类似于该点群的重心或中心点。
公式表示:新坐标 = (所有点x坐标的平均值, 所有点y坐标的平均值)


这种方法逻辑直观,会产生特定类型的聚类结果。

完全连接
其思想是,衡量两个聚类之间的距离时,取两个聚类中彼此最远的两个点之间的距离作为聚类间距。
公式表示:聚类间距 = max(点A到点B的距离), 其中点A属于聚类1,点B属于聚类2

例如,在下图中,上方聚类与左下方聚类之间的距离,等于两个聚类中最远两点间的距离。



可以看到,在完全连接的例子中,聚类间距相对较远;而在平均连接的例子中,间距则较短一些。没有绝对正确或错误的方法,关键在于这两种不同的合并策略可能产生非常不同的结果。因此,尝试两种方法以观察最终得到哪种聚类结果,以及哪一种结果更合理,通常是很有用的。

热图函数可视化 📊

接下来要重点介绍的是 heatmap 函数,这是一个用于可视化矩阵数据的强大工具。

如果你有一个非常大的表格或数值矩阵,且数值尺度相似,希望快速、有条理地查看,可以调用 heatmap 函数。该函数本质上会对表格的行和列分别运行层次聚类分析。

  • 将表格的行视为观测值,对其运行聚类分析。
  • 将表格的列也视为不同的观测值集(即使它们实际上是变量等),同样运行聚类分析。

heatmap 函数的思想是利用层次聚类的结果来重新组织表格的行和列,然后使用图形函数来可视化。它会创建一个图像图,并根据层次聚类算法重新排列表格的行和列的顺序。

如下图所示,在行方向上,树状图显示可能存在三个聚类,因此这些行被分组在一起。这个数据框只有两列,所以对列进行聚类分析的意义不大。但如果你有很多列,heatmap 函数会重新组织列,使相似的列靠得更近,差异大的列相距更远。


因此,heatmap 函数对于快速可视化这类高维表格数据非常有用。

总结与注意事项 📝

本节课中我们一起学习了层次聚类的进阶内容。

总结
层次聚类是观察高维数据的一种非常有用的技术,它能以逻辑直观的方式组织数据。特别是像 heatmap 这样的函数,对于快速查看表格或矩阵数据极具价值。


进行层次聚类需要明确两点:

  1. 定义两个点之间“远近”的概念(距离度量)。
  2. 确定合并策略(如完全连接和平均连接)。

给定这两点,层次聚类算法就会运行并产生一个聚类树状图,展示点是如何通过算法逐步合并的。

注意事项
层次聚类(以及许多其他聚类算法)可能存在几个问题:

  1. 结果不稳定性:聚类结果可能不稳定。例如,如果少数数据点发生变化或存在异常值,移除或稍微修改这些点可能导致最终的聚类树状图发生很大变化。因此需要注意这种可能性。
  2. 参数敏感性:尝试不同的距离度量以检查结果对度量的敏感性,或者改变合并策略以观察输出图形的变化,通常是很有用的做法。
  3. 变量尺度影响:聚类算法可能对变量的尺度敏感。如果某个变量的测量单位远大于另一个变量,有时会干扰算法。因此,对某些变量进行缩放,使它们之间更具可比性,可能是有益的。


层次聚类算法(至少这里讨论的版本)的一个优点是它是确定性的,没有随机起始点,也不包含随机性。只要使用相同的参数和数据,运行一次就会得到相同的图形。

当然,任何聚类方法的一个关键问题是在何处切割树状图以确定最终聚类数量。确定有多少个聚类并不总是显而易见的,虽然已有一些算法被提出来尝试解决这个问题,但层次聚类的最佳用途可能还是在于探索性分析,用于查看和可视化数据,初步感知是否存在任何模式。然后,你可以将这些发现融入更复杂的模型中进行形式化。

课程提供了一些关于这些方法的更详细资料的链接,鼓励你进一步查阅和探索。

125:K均值聚类(第1部分) 🧮

在本节课中,我们将要学习K均值聚类的基本原理。这是一种用于将数据点分组到指定数量簇中的经典方法。我们将了解其工作原理、所需的关键概念,并通过一个简单示例来演示算法的迭代过程。


概述

K均值聚类是一种相对古老但至今仍非常有用的技术,主要用于总结高维数据,揭示数据中的模式,并识别哪些观测值彼此相似,哪些差异较大。本节将介绍K均值的工作原理及其应用价值。

核心概念:距离度量

在开始聚类之前,首先需要定义“相似”和“不同”的含义。这需要一个距离度量来量化两个事物之间的接近程度。距离的定义取决于具体情境,选择不当的度量标准可能导致无意义的结果。

以下是几种传统的距离度量:

  • 欧几里得距离:两点之间的直线距离,公式为 d = √((x₂ - x₁)² + (y₂ - y₁)²)
  • 相关性相似度:衡量两个变量之间的相关程度,高度相关的点被视为相似。
  • 曼哈顿距离:两点在标准坐标系上的绝对轴距总和。

你需要为你的问题选择一个有意义的距离或相似性度量。

K均值算法原理

上一节我们介绍了距离的概念,本节中我们来看看K均值聚类的具体目标和方法。K均值聚类是一种将一组观测值划分到固定数量簇中的方法。

其核心思想是,你必须事先指定数据点可能被划分成的簇的数量 K。例如,如果你有100个观测值,你可能认为它们可以整齐地分为4个不同的组。每个组将有一个质心,类似于该组数据点的“重心”。

算法基本步骤如下:

  1. 指定簇的数量 K
  2. 初始化 K 个质心的位置(例如随机选择)。
  3. 将每个数据点分配给距离它最近的质心,形成初始簇。
  4. 基于当前簇内所有点的位置,重新计算每个簇的质心。
  5. 重复步骤3和4(重新分配点和更新质心),直到质心的位置不再发生显著变化,算法收敛。

K均值聚类算法最终会输出每个簇的最终质心位置,以及每个观测值被分配到的簇标签。

算法演示:一个简单例子

为了更直观地理解上述过程,我们通过一个在二维空间中生成的随机数据示例来逐步演示。该数据明显包含三个簇,每个簇有四个观测值。

以下是算法的迭代步骤:

首先,随机选择三个点作为初始质心(图中以“+”号表示)。

第一步:分配点
将每个数据点分配给距离它最近的质心。例如,顶部的点8被分配给左上角的红色质心;点1、2、3被分配给橙色质心;其余点被分配给紫色质心。这样就完成了点到三个簇的初始分组。

第二步:重新计算质心
根据当前每个簇内所有点的位置,重新计算质心。例如,取每个簇中所有点的坐标平均值作为新质心。可以看到,紫色和红色的“+”号移动到了各自簇的中间位置,橙色的“+”号也移动到了三个橙色点的中间。

第三步:重新分配点
基于新的质心位置,再次检查每个数据点离哪个质心最近,并重新分配。例如,点4原先属于红色簇,但现在离橙色质心更近,因此被重新分配到橙色簇;点7现在离红色质心更近,因此被分配到红色簇。

第四步:再次更新质心
根据新的分组,再次更新每个簇的质心位置。可以看到质心继续向各自簇的中心移动。

这两个过程——将点分配给最近的簇,以及根据当前分配更新簇的质心位置——不断重复,直到分配关系不再改变,此时聚类完成。


总结

本节课中我们一起学习了K均值聚类的基础知识。我们了解到,成功应用K均值需要三个关键要素:一个合适的距离度量、一个事先指定的簇的数量K,以及质心的初始猜测。算法通过交替执行“分配点”和“更新质心”两个步骤来迭代优化,最终将数据划分到不同的簇中,并输出每个簇的中心和每个点的归属。在下一部分,我们将探讨如何选择K值以及在实际数据中应用K均值时需要注意的问题。

126:K均值聚类(第2部分)🎯

在本节课中,我们将学习如何在R语言中实现K均值聚类算法,并探索几种可视化聚类结果的方法。我们将重点介绍kmeans()函数的使用、结果解读以及通过散点图和热图来呈现聚类效果。


在R中实现K均值聚类

上一节我们介绍了K均值聚类的基本原理,本节中我们来看看如何在R语言中具体应用它。R语言内置的kmeans()函数是执行该算法的主要工具。

以下是一个使用二维数据框的简单演示:

# 对数据框执行K均值聚类,指定3个中心点(质心)
kmeans_result <- kmeans(data_frame, centers = 3)

kmeans()函数返回一个包含多个元素的列表对象。其中最重要的元素之一是cluster

# 查看每个数据点被分配到的簇
kmeans_result$cluster

cluster元素是一个从1到K(本例中为3)的整数向量。它指明了传入数据框中每个数据点所属的簇。例如,输出可能显示前四个点在簇3,接下来四个在簇1,再接下来四个在簇2。

返回对象的另一个关键元素是centers

# 查看每个簇的质心坐标
kmeans_result$centers

centers元素告诉你每个质心在空间中的具体位置。


可视化聚类结果:散点图

了解算法结果后,我们可以将其可视化。一种直观的方法是使用散点图,并根据聚类结果对点进行着色。

以下是具体步骤:

首先,运行K均值算法获取聚类结果。然后,绘制原始数据点,并根据其所属簇号(kmeans_result$cluster)来指定颜色。最后,使用points()函数将计算出的质心位置添加到图中,通常用“+”符号标记。

# 绘制数据点,按簇着色
plot(x, y, col = kmeans_result$cluster)
# 添加质心位置
points(kmeans_result$centers, pch = 3, cex = 2, lwd = 2)

通过这种可视化,你可以清晰地看到数据如何被分组,以及每个簇的中心所在。


可视化聚类结果:热图

除了散点图,另一种展示K均值等聚类算法结果的有效方式是使用热图。这对于观察高维数据或矩阵型数据中的模式特别有用。

以下是具体过程:

我们使用相同的数据集,但可能进行不同的随机抽样,并再次运行K均值算法,将结果存储在另一个对象中。

kmeans_object_2 <- kmeans(new_sample_data, centers = 3)

接下来,我们可以为数据创建图像(热图)。左侧的热图可能展示原始数据的顺序,而右侧的热图则会根据聚类结果对数据行(有时是列)进行重新排序。



在右侧重新排序的热图中,属于同一簇的数据行被排列在一起。当你上下浏览这个矩阵时,可以看到聚类在一起的数据点彼此相邻。这种组织方式能帮助你更系统地观察矩阵数据,寻找其中的模式。我们将在讲解层次聚类时进一步探讨这一点,但热图这种可视化方式同样适用于K均值等其他聚类算法。


K均值算法的要点与注意事项

最后,我们来总结一下K均值算法的特点和使用时需要注意的事项。

K均值是一个用于组织和寻找高维数据模式的便捷算法。但它有几个重要的前提和特点:

  • 需要预先指定簇数量(K值):你必须至少大致知道数据中可能存在的簇的数量。可以通过尝试不同的K值,观察哪种模式看起来最合理,但这并没有一个简单的固定规则。
  • 选择K值的方法:除了目测,还有一些算法可以帮助确定最佳簇数,例如基于交叉验证、信息论或其他度量标准的方法。这里提供一个关于确定K均值中簇数量的链接供参考。
  • 算法的非确定性:K均值算法不是完全确定性的。根据具体实现,初始质心通常是随机选择的。因此,多次运行算法以确保结果稳定是一个好习惯。例如,如果你运行三次得到三种完全不同的模式,可能意味着算法对数据的“看法”不稳定,或者该数据集本身不适合用K均值处理。
  • 数据集的适用性:对于某些类型的数据集,K均值可能会出现问题。这里提供一些视频和参考资料链接,它们提供了关于K均值算法局限性和注意事项的更多信息。



总结

本节课中,我们一起学习了在R语言中应用K均值聚类的完整流程。我们掌握了如何使用kmeans()函数进行聚类,如何从结果对象中提取簇分配和质心位置。我们还探讨了两种可视化方法:用散点图直观展示聚类与质心,以及用热图来观察高维数据中经聚类重排后的模式。最后,我们讨论了使用K均值时需要牢记的关键点,特别是关于预先指定K值、算法的非确定性以及对数据集的潜在假设。理解这些将帮助你在实际数据科学项目中更有效地应用K均值聚类。

127:降维技术(第一部分)🔍

在本节课中,我们将学习两种重要的降维技术:主成分分析和奇异值分解。这两种方法在探索性数据分析阶段和正式建模阶段都非常有用。我们将重点讨论它们在探索性分析中的应用,并深入理解其背后的基本原理。

无模式数据的探索 🔎

首先,我们来看一个没有明显模式的数据集。以下代码生成一个随机正态分布数据的矩阵:

set.seed(12345)
dataMatrix <- matrix(rnorm(400), nrow=40)
image(1:10, 1:40, t(dataMatrix)[, nrow(dataMatrix):1])

生成的矩阵图像显示,数据看起来非常嘈杂,没有明显的模式,这符合我们对随机数据的预期。

上一节我们生成了一个随机矩阵,本节中我们来看看如何对这样的数据进行聚类分析。

我们可以对数据框的行和列分别进行层次聚类分析。在R语言中,使用 heatmap 函数可以轻松实现这一点:

heatmap(dataMatrix)

运行后,我们会在矩阵的顶部和左侧看到树状图。然而,由于数据本身没有内在的有趣模式,聚类分析的结果也没有显示出明显的分组结构。

引入模式后的数据变化 📊

现在,我们尝试在数据集中添加一个模式。以下代码通过一个循环,随机选择一些行,并在这些行的部分列上添加一个偏移量:

set.seed(678910)
for(i in 1:40){
  coinFlip <- rbinom(1, size=1, prob=0.5)
  if(coinFlip){
    dataMatrix[i, ] <- dataMatrix[i, ] + rep(c(0,3), each=5)
  }
}

添加模式后,再次绘制矩阵图像,可以观察到右侧五列的颜色更偏黄(表示数值较高),而左侧五列的颜色更偏红(表示数值较低)。这是因为部分行在右侧列的均值被提高了。

现在,如果我们对包含模式的数据再次运行层次聚类分析:

heatmap(dataMatrix)

可以清晰地看到,顶部的树状图(基于列的聚类)将10列明确地分成了两个簇:左侧5列和右侧5列。然而,行的聚类结果仍然不明确,因为行方向上没有添加系统性模式。

通过边际均值观察模式 📈

为了更细致地观察行和列的模式,我们可以查看行和列的边际均值。

以下是相关的分析步骤:

  • 在左侧图中,我们展示了经过行聚类重排后的原始矩阵图像。
  • 在中间图中,我们绘制了每一行的均值。Y轴是行号(1到40),X轴是该行的均值。可以观察到,不同行的均值存在明显差异。
  • 在右侧图中,我们绘制了每一列的均值。可以清晰地看到,前5列的均值接近0,而后5列的均值接近2,存在一个明显的偏移。

通过这些边际均值图,我们可以更直观地看到数据在行和列方向上的结构模式。

降维的核心问题 🤔

聚类分析有助于识别这类模式,但我们也可以采用一种更形式化的方法,利用数据的矩阵结构。这里主要涉及两类相关的问题:

以下是两个核心的降维目标:

  1. 变量压缩(统计视角):当我们拥有大量变量时,希望创建一组数量更少的新变量。这组新变量应满足两个条件:一是彼此不相关(代表数据中不同类型的变化);二是能够尽可能多地解释原始数据集中的变异性。主成分分析是解决这一问题的常用方法。
  2. 数据压缩(矩阵视角):如果将所有变量放在一个矩阵中,我们希望找到一个由更少变量构成的、秩更低的矩阵,这个矩阵能以较小的信息损失来近似表示原始数据。奇异值分解是理解和解决这一问题的关键工具。

奇异值分解与主成分分析 🧮

奇异值分解可以用矩阵形式表示为:

X = U D V^T

其中:

  • X 是原始数据矩阵(行是观测,列是变量)。
  • U 的列是正交的,称为左奇异向量
  • V 的列是正交的,称为右奇异向量
  • D 是一个对角矩阵,其对角线上的元素称为奇异值

主成分分析通常使用奇异值分解。其基本思想是:首先将原始数据矩阵的每一列进行标准化(减去列均值,除以列标准差),然后对这个标准化后的矩阵进行奇异值分解。此时,主成分就等于标准化矩阵的右奇异向量(即 V 矩阵)


本节课中我们一起学习了降维技术的初步概念。我们通过实例看到了如何在数据中识别模式,并介绍了两种核心的数学工具:奇异值分解和主成分分析。理解这些基础是后续深入学习降维方法及其应用的关键。

128:降维第二部分 - 奇异值分解(SVD)深入解析 📊

在本节课中,我们将深入学习奇异值分解(SVD)的核心概念,包括其组成部分、如何解释结果,以及它与主成分分析(PCA)的关系。我们将通过具体的图像和数据示例,帮助你直观地理解SVD如何自动发现数据中的潜在模式。


概述

奇异值分解(SVD)是一种强大的矩阵分解技术,它能将原始数据矩阵分解为三个特定矩阵的乘积。本节课我们将拆解SVD的各个组成部分,理解左、右奇异向量如何揭示数据在行和列方向上的结构,并学习如何通过“方差解释”来衡量每个成分的重要性。我们还将探讨SVD与PCA的本质联系。


SVD的组成部分与模式识别

上一节我们介绍了SVD的基本形式。本节中,我们来看看如何通过分解出的矩阵来理解数据的内在结构。

如果我们分解奇异值分解的组成部分,可以观察左奇异向量矩阵 U 和右奇异向量矩阵 V

观察左侧的原始数据矩阵(即之前看到的图像图),以及中间的图,我绘制了第一个左奇异向量。它大致展示了整个数据集的均值模式。

如果你按行绘制,可以看到第一个左奇异向量在大约第40行到第18行(或17行)左右为负值,而在其余行为正值。这清晰地显示了两组行在均值上的分离。

观察第一个右奇异向量,可以看到它也显示了前五列与后五列之间的均值变化。

这里SVD的好处在于,它能立即捕捉到均值的偏移,无论是在行维度还是列维度上,无需任何先验信息。它是完全无监督的,可以自动识别出模式。记得我们之前绘制过非常类似的图,但那是在我们知道数据中存在这种模式的情况下。在这里,奇异值分解立即捕捉到了模式,并体现在第一个左奇异向量和右奇异向量中。


方差解释

奇异值分解的另一个组成部分被称为“方差解释”,这来源于 D 矩阵中的奇异值。

记住,D 矩阵本质上是一个对角矩阵,因此只有该矩阵对角线上的元素。

你可以将每个奇异值视为代表了数据集中由该特定成分解释的总变异的百分比。

成分通常按顺序排列,第一个成分尽可能解释最多的变异,第二个解释第二多的变异,依此类推。因此,你可以绘制如下方所示的方差解释比例图。

在左侧,我绘制了原始的奇异值,可以看到它们随着列的增加而递减。当然,原始奇异值本身没有太多意义,因为它的尺度难以解释。

如果我将每个奇异值除以所有奇异值的总和,那么在右侧,我可以将其解释为方差解释的比例。可以看到这是完全相同的图,只是Y轴发生了变化。第一个奇异值(回想一下,它捕捉了行和列之间均值的偏移)解释了数据中约40%的变异。因此,几乎一半的数据变异可以由单个奇异值(或单个维度)解释。其余的数据变异则由其他成分解释。


SVD与PCA的关系

为了展示奇异值分解与主成分之间的密切关系,这里我计算了同一数据矩阵的SVD和PCA,并将第一主成分绘制在X轴上,将第一个右奇异向量绘制在Y轴上。可以看到它们完全落在同一条线上,本质上是相同的东西。

因此,SVD和主成分分析(PCA)本质上是相同的。如果你在社交场合听到有人谈论SVD和PCA,可以确信他们基本上在做同一件事。


深入理解方差解释:简单示例

让我们更仔细地看看方差解释。这里有一个非常简单的示例,基本上是一个只包含0或1的矩阵。

其核心思想是这个矩阵中基本上只有一个模式。前五列是0,后五列是1。仅此而已,没有什么特别的。

如果绘制数据,在左侧图中,可以看到前五列是红色,后五列是黄白色。

现在,如果我对这个相对简单的矩阵进行SVD,可以绘制奇异值图。你会看到一个奇异值非常高,其余的几乎都为零。你会发现第一个奇异值解释了数据中100%的变异。

这怎么可能?思考一下这个数据集,尽管它有大约40行和10列,但这个数据实际上只有一个维度:如果你在前五列,均值等于0;如果你在后五列,均值等于1。因此,尽管你有很多所谓的“观测值”,但这个矩阵中实际有用的信息只占很小一部分。SVD通过单个成分能解释100%的变异捕捉到了这一点。


添加第二个模式

现在,让我们向数据集中添加第二个模式。我们可以添加一个跨越行和列的模式。

第一个模式基本上是我们之前解决的块状模式。因此,数据集的前一半将有一个均值,后一半将有另一个均值。

第二个模式基本上在列之间交替。你可以在这里看到它的样子。在左侧是数据,可以看到有一个模式,那是一种块状模式:左边五列较低,右边五列较高。然后,嵌套在其中的是一个每隔一列交替的模式,每隔一列的值更高。

如果我们绘制真实情况,可以在中间的图中看到,前五列的均值较低,后五列的均值较高,这是第一个模式。第二个模式显示第一列的均值约为0,第二列的均值约为1,第三列的均值约为0,第四列依此类推。因此,这里有两个模式:一个是这种偏移模式,另一个是这种交替模式。这就是真实情况。

当然,我们很少知道真实情况。我们需要从数据中学习真实情况。因此,问题是:当你面对左侧所示的数据时,能否提出一种算法来识别隐藏在数据中的两个独立模式?这正是SVD的用途。


对包含两个模式的数据运行SVD

如果我们对这个包含两个模式的新矩阵运行SVD,左侧是数据,中间面板是第一个右奇异向量。可以看到它大致捕捉到了块状模式:前五列的值相对较低,后五列相对较高。虽然不如真实情况那么清晰,但可以大致分辨出这里有两种不同的均值。

第二个右奇异向量在右侧面板。虽然有点难以看清,但它确实试图捕捉交替的均值模式。当然,不如我们绘制真实情况时那么明显,但你可以看到每隔一个点要么较高,要么较低。

既然我们知道真实情况,讨论起来会容易一些。但总的来说,你可以看到这两个模式有些混杂在一起,因为很难将两个模式完全分开。因此,尽管我们知道真实情况是两个独立的模式,但第一个和第二个右奇异向量(也称为主成分)在某种程度上混合了这两个模式。它们各自都包含一些块状模式和交替模式。

就像大多数真实数据一样,如果你没有提前知道,真相会有点难以辨别。


两个模式数据的方差解释

现在,如果你观察这个问题中的方差解释,可以在右侧面板(即方差解释百分比图)中看到,第一个成分解释了超过50%的数据总变异。这基本上是因为前五列和后五列之间的偏移模式非常强,代表了数据中的大量变异。因此,捕捉到这个偏移模式,就捕捉到了很多变异。

第二个成分只解释了大约18%的变异,之后的成分解释的变异更少。

这大致告诉了你这个数据集中有多少个成分。但正如你所见,那个每隔一列交替的模式(交替模式)更难被捕捉到。


总结

本节课中,我们一起深入学习了奇异值分解(SVD)。我们拆解了SVD的 UVD 矩阵,理解了左、右奇异向量如何无监督地揭示数据在行和列方向上的结构(如均值偏移)。我们重点学习了“方差解释”的概念,它通过公式 奇异值 / 奇异值总和 来计算每个成分的重要性,并可通过绘图直观展示。通过简单和复杂的示例,我们看到了SVD如何识别数据中的单一主导模式或混合的多个模式。最后,我们明确了SVD与主成分分析(PCA)在数学上是等价的,它们都是强大的降维与模式发现工具。掌握SVD有助于你在面对高维数据时,有效地提取核心特征并理解其内在结构。

129:降维第3部分 - 处理缺失值与图像压缩 📊

在本节课中,我们将学习如何处理奇异值分解(SVD)和主成分分析(PCA)中的缺失值问题,并探索SVD在图像压缩和数据摘要方面的实际应用。


处理缺失值问题

上一节我们介绍了SVD和PCA的基本原理。本节中我们来看看一个实际应用中常见的问题:缺失值。

无论是SVD还是PCA,都存在一个共同问题,即无法直接处理缺失值。真实数据通常包含缺失值。如果你尝试在包含缺失值的数据上运行SVD,会直接报错,无法进行计算。因此,在运行SVD或PCA之前,必须对缺失值进行处理。

以下是处理缺失值的一种常用方法:

  • 使用impute:该包来自Bioconductor项目,可用于填补缺失的数据点。
  • impute.knn函数:此函数通过K近邻法填补缺失值。例如,若k=5,函数会找到与包含缺失值的行最接近的5行,然后用这5行数据的平均值来填补缺失值。

填补数据后,便可以顺利运行SVD。我们可以比较原始数据矩阵与填补后数据矩阵的第一奇异向量。虽然两者不完全相同,但大致相似,说明填补操作对SVD结果没有产生重大影响。


SVD在图像压缩中的应用

接下来,我们通过一个有趣的例子,看看如何利用SVD对实际图像进行降维或低秩表示。

这里有一张人脸的相对低分辨率图片,可以清晰地看到鼻子、耳朵、眼睛和嘴巴。

我们对这张人脸数据运行SVD,并观察方差解释率。第一奇异向量解释了约40%的变异,第二奇异向量解释了约20%,第三奇异向量解释了约15%。前5到10个奇异向量几乎捕获了数据集中的所有变异。

因此,我们可以通过矩阵乘法,使用比原始数据集更少的成分来重建人脸的近似图像。例如,我们可以分别使用第1、前5和前10个奇异向量来重建图像。

以下是不同数量奇异向量重建图像的对比:

  • 仅使用第1奇异向量:重建的图像非常模糊,几乎无法辨认出人脸。仅用一个向量来代表整张图像要求过高。
  • 使用前5个奇异向量:图像的关键特征已基本呈现,可以清楚地看到双眼、鼻子、嘴巴和双耳。虽然与原始图像不完全相同,但已非常接近。
  • 使用前10个奇异向量:图像具有更多细节,但与使用5个奇异向量重建的图像差异不大。
  • 原始数据集:作为对比的原始图像。

这个例子展示了SVD在数据压缩方面的应用。仅使用少数几个(如5到10个)奇异向量,就能获得人脸的合理近似,而无需存储所有原始数据。数据压缩和统计摘要是同一枚硬币的两面,如果你想用更少的特征来总结数据集,奇异值分解同样非常有用。


注意事项与进一步资源

关于奇异值分解和主成分分析,有几点需要注意:

  • 数据尺度的影响:数据的测量尺度很重要。如果不同变量具有完全不同的量纲和单位,数值较大的变量可能会主导PCA或SVD的结果,导致分析结果缺乏实际意义。因此,需要确保不同列或行的数据尺度大致可比。
  • 模式混合:正如我们在双模式例子中看到的,主成分和奇异向量可能会混合真实的潜在模式。因此,你观察到的模式可能不代表可分离的独立模式,而是混合在一起的模式。
  • 计算强度:对于非常大的矩阵,奇异值分解的计算量可能很大。不过,随着计算能力的提升以及高度优化的专用矩阵计算库的出现,在许多实际问题中应用SVD已无需过多提前规划。

以下是一些关于如何使用主成分分析和奇异值分解的进一步资源链接。此外,还有一些方法与此类似但在细节上有所不同,例如因子分析、独立成分分析和潜在语义分析。这些方法都值得探索,其核心思想与PCA和SVD一致:寻找能够解释数据中大部分变异的低维表示。


本节课中我们一起学习了如何处理SVD/PCA中的缺失值,并通过图像压缩的例子直观理解了SVD在数据降维与摘要中的强大应用。我们还讨论了应用时需注意的数据尺度、模式混合等关键点,为后续更深入的数据分析奠定了基础。

130:🎨 在 R 绘图中处理颜色(第 1 部分)

在本节课中,我们将要学习如何在 R 语言绘图中处理和使用颜色。颜色选择虽然看似次要,但恰当运用能有效突显数据关系,提升图表表现力。

上一节我们介绍了 R 语言的基础绘图系统,本节中我们来看看如何通过调色板来指定不同的颜色。

默认配色方案的问题

大多数绘图函数的默认配色方案效果不佳。即使不是设计专家,也能看出这些自然生成的配色通常不适合展示不同类型的数据。

R 的核心系统及一些扩展包提供了更好的颜色处理和指定方案,本讲将介绍其中一个包及其相关函数。

标准颜色索引的局限性

以下是 R 中一个相当标准的散点图示例,图中点使用了三种颜色:黑色、红色和绿色。

这种现象的原因是,大多数绘图函数中都有一个 col 参数。设置 col = 1 得到黑色,col = 2 得到红色,col = 3 得到绿色。因此,如果你需要三种颜色,很容易想到使用 col = 1:3

col = 4 对应蓝色,col = 5 对应洋红色。这些是设置 col 参数为 1、2、3、4 或 5 时的标准颜色。

然而,红色和绿色在此情境下可能缺乏特定含义。从设计角度看,长期使用此配色方案会使所有图表看起来千篇一律,缺乏针对性。

核心问题在于,我们能否选择一组更好的颜色,以更有效地传达我们想要表达的信息。

不同调色板的视觉比较

另一个标准颜色集展示如下。这是 R 自带的 volcano 数据集,显示了火山的不同海拔高度。

左侧的颜色集来自 heat.colors 调色板。该调色板的颜色从表示低值的偏红色,过渡到表示高值的黄色或白色。

右侧的颜色集称为地形色(topo.colors)。该调色板从表示低值的蓝色,过渡到表示高值的棕色或白色。

对于 heat.colors,如果你了解火焰,可能可以合理推断出图中高低值的对应关系。

对于 topo.colors,蓝色表示低、绿色表示更高,这对一些人来说可能并不直观。

本节课中我们一起学习了 R 绘图中的基础颜色处理,认识了默认配色方案的局限性,并初步比较了不同调色板(如 heat.colorstopo.colors)的视觉表现。下一讲我们将深入探讨如何选择和应用更合适的调色板。

131:在R绘图中处理颜色(第二部分)🎨

在本节课中,我们将学习R语言中用于处理颜色的两个核心函数:colorRampcolorRampPalette。这两个函数能够基于一组给定的基础颜色,通过数值插值生成一系列平滑过渡的新颜色,这对于创建自定义的连续型颜色标尺非常有用。

颜色插值的基本概念

上一节我们介绍了R中基本的颜色系统。本节中我们来看看如何通过混合基础颜色来创建新的颜色。

你可以想象一个画家在他的调色板上有几块特定的颜色。画家会用画笔混合这些颜色,从而创造出介于原始颜色之间的新色调。colorRampcolorRampPalette 函数在R中实现了类似的功能:它们接收一组构成“调色板”的颜色,并通过数值计算在这些颜色之间进行插值,生成混合后的新颜色。

另一个可能有用的函数是 colors()。直接执行它(不带参数)会返回一个字符向量,其中包含了所有可以在绘图函数中通过名称引用的颜色。

colorRamp 函数详解

colorRamp 函数接收一个颜色调色板,并返回另一个函数。这个返回的函数接收一个介于0和1之间的数值,该数值代表颜色在调色板两个极端之间的位置。

R中已有一个类似的函数 gray(),它在黑色和白色之间进行插值,生成各种灰度。colorRamp 是其通用化版本,它可以接收任意一组颜色作为调色板,并返回一个函数来在这些颜色的极端值之间进行插值。

以下是 colorRamp 函数的一个简单示例:

# 创建一个由红色和蓝色组成的调色板
pal <- colorRamp(c("red", "blue"))

这里,pal 是一个函数。当我们调用 pal(0) 时,它返回调色板起始端(红色)的RGB值;调用 pal(1) 时,返回末端(蓝色)的RGB值。

# 返回红色的RGB值矩阵
pal(0)
#      [,1] [,2] [,3]
# [1,]  255    0    0

# 返回蓝色的RGB值矩阵
pal(1)
#      [,1] [,2] [,3]
# [1,]    0    0  255

RGB值的范围是0到255。pal(0.5) 会返回红色和蓝色各占一半的混合色:

pal(0.5)
#      [,1] [,2] [,3]
# [1,]  127.5    0  127.5

如果你传入一个0到1之间的数值序列,pal 会返回一系列在红色和蓝色之间平滑过渡的颜色。

colorRampPalette 函数详解

colorRampPalette 函数与 colorRamp 非常相似,但它返回的函数略有不同。它也接收一个颜色调色板,但返回的函数接收一个整数参数 n,并生成一个包含 n 种颜色的向量,这些颜色在调色板指定的颜色之间均匀插值。

这与 heat.colors()topo.colors() 等函数的行为类似。

以下是 colorRampPalette 的示例:

# 创建一个由红色和黄色组成的调色板
pal2 <- colorRampPalette(c("red", "yellow"))

pal2 现在是一个函数。调用 pal2(2) 会返回调色板本身的两种颜色(红色和黄色),但以十六进制格式表示:

pal2(2)
# [1] "#FF0000" "#FFFF00"

在十六进制颜色代码 #RRGGBB 中,前两位代表红色(R),中间两位代表绿色(G),后两位代表蓝色(B)。#FF0000 是纯红色(红色最大,绿色和蓝色为0),#FFFF00 是黄色(红色和绿色最大,蓝色为0)。

调用 pal2(10) 会生成10种颜色,在红色和黄色之间平滑过渡:

pal2(10)
#  [1] "#FF0000" "#FF1C00" "#FF3800" "#FF5500" "#FF7100" "#FF8D00" "#FFAA00" "#FFC600" "#FFE200" "#FFFF00"

你可以看到,随着颜色从红色向黄色变化,红色分量(FF)始终保持最大,绿色分量从 00 逐渐增加到 FF,而蓝色分量始终为 00

核心函数对比总结

以下是两个核心函数的对比:

  • colorRamp:

    • 输入:一个颜色向量(调色板)。
    • 输出:一个函数。
    • 输出函数的输入:介于0和1之间的数值。
    • 输出函数的输出:一个RGB值矩阵(每行代表一种颜色,三列分别对应R、G、B)。
    • 用途:根据连续变量(如比例)精确映射到某个颜色。
  • colorRampPalette:

    • 输入:一个颜色向量(调色板)。
    • 输出:一个函数。
    • 输出函数的输入:一个整数 n
    • 输出函数的输出:一个长度为 n 的字符向量,包含十六进制颜色代码。
    • 用途:生成指定数量的、在调色板颜色之间均匀过渡的颜色序列,常用于为分类或离散数据上色。

课程总结

本节课中我们一起学习了R语言中两个强大的颜色处理工具:colorRampcolorRampPalette。我们了解了它们如何通过接收一个基础颜色调色板,并利用数值插值来生成平滑过渡的颜色序列。colorRamp 适用于将连续数值映射到颜色,而 colorRampPalette 则擅长生成指定数量的离散颜色。掌握这两个函数,将极大地增强你在R中创建自定义、美观且信息丰富的图表的能力。

132:在R绘图中处理颜色(第三部分)🎨

在本节课中,我们将学习如何使用RColorBrewer包来获取和运用有趣且专业的调色板,以增强数据可视化的效果。

上一节我们介绍了如何手动创建颜色向量,本节中我们来看看如何利用现成的专业调色板。

认识RColorBrewer包

RColorBrewer包提供了多种精心设计的调色板,适用于不同类型的数据。这个包基于辛西娅·布鲁尔(Cynthia Brewer)在宾夕法尼亚州立大学的研究工作,她在地图制作和数据映射的颜色选择方面做了大量研究。这些调色板对于非地图数据也同样非常有用。

该包主要提供三种类型的调色板:

  • 顺序型调色板:用于有序数据,例如从低到高的数值型或连续型数据。
  • 发散型调色板:用于偏离某个中心值的数据,例如同时包含正值和负值,且数值大小表示偏离中心(如均值或零)的程度。
  • 定性型调色板:用于无序的分类数据或因子数据,这些数据没有特定的顺序,只是代表不同的类别。

调色板信息可以通过colorRampcolorRampPalette等函数传递给R的绘图函数。

调色板类型详解

以下是RColorBrewer包中提供的各类调色板示例。

  • 顺序型调色板:位于顶部。可以看到它们基本上从左到右由浅色过渡到深色,但每种调色板会经过不同的颜色序列。例如,最上面的调色板从黄色、橙色过渡到红色,而最下面的“蓝调”调色板则从浅蓝过渡到深蓝。这些调色板用于具有低值和高值的数值型有序数据。
  • 定性型调色板:位于中间。用于表示分类数据。可以看到调色板中的颜色没有特定的顺序,既不从浅到深,也没有其他特定排列。每种调色板都有一套相关联的颜色,并具有一个主题,有些调色板颜色更鲜明,有些则更柔和。
  • 发散型调色板:位于底部。可以看到调色板中间的颜色较浅,从左到右,颜色在不同的色系范围内变深。可以将其理解为左侧代表负值,右侧代表正值,以此实现“发散”效果。同样,它们也经过不同的颜色序列,例如最上面的调色板从左边的红色一直过渡到右边的紫蓝色,而最下面的调色板则从左边的棕色过渡到右边的蓝绿色。你可以根据自己偏好的配色方案来选择调色板。

使用RColorBrewer包

安装RColorBrewer包后,可以加载它并使用其核心函数。

首先使用install.packages函数安装包,然后使用library(RColorBrewer)加载。包中最有用的函数是brewer.pal,它接受两个参数:

  • 第一个参数是你希望调色板包含的颜色数量,这个数字通常较小,比如2、3或4,因为主色调不需要太多。
  • 第二个参数是调色板的名称。调色板名称不容易记忆,需要查阅brewer.pal函数的帮助页面来获取所有可用的调色板名称列表。

以下是使用示例:

brewer.pal(3, "BuGn")

这段代码请求一个蓝绿色类型的调色板,并需要3种不同的颜色。函数返回一个包含三个颜色的字符向量,每个颜色由红、绿、蓝混合值(十六进制代码)表示。即使不熟悉十六进制代码,也能在屏幕上看到这些颜色。

在绘图中应用调色板

接下来,我们将学习如何将调色板应用到实际的绘图中。

我们可以将这个从RColorBrewer获取的调色板传递给colorRampPalette函数。

pal <- colorRampPalette(brewer.pal(3, "BuGn"))

这段代码会返回一个函数,我们将其命名为pal。现在,我们可以使用image函数来绘制火山数据图,但不再使用heat.colorstopo.colors调色板,而是使用从RColorBrewer包获得的这个调色板。

image(volcano, col = pal(20))

这里指定希望在该图像中呈现20种不同的颜色。这20种颜色将从蓝绿调色板中通过插值生成。

生成的图像如上图所示。可以看到,随着海拔升高,颜色变得更深、更绿;而在海拔较低处,颜色更浅、更蓝。

smoothScatter函数

另一个使用RColorBrewer调色板的函数是R自带的smoothScatter函数。

这个函数在需要绘制大量数据点的散点图时特别有用,可以避免点重叠造成的视觉混乱。例如,绘制10,000个点时,如果使用普通的plot函数,屏幕上会得到一堆相互重叠的点,很难看清细节。

smoothScatter函数的作用是创建图中点的二维直方图,并使用特定的颜色集来绘制这个直方图。它默认使用的颜色集是RColorBrewer包中的“蓝调”顺序型调色板。

set.seed(1234)
x <- rnorm(10000)
y <- rnorm(10000)
smoothScatter(x, y)

随着密度增高,蓝色会变得更深。因此,图中深蓝色的区域是高密度区,即大多数点集中的地方;而图中浅蓝色的部分则是图的低密度区域。

总结

本节课中我们一起学习了如何利用RColorBrewer包来获取专业的顺序型、发散型和定性型调色板,并通过colorRampPalette函数和smoothScatter函数将这些调色板应用到R绘图中,从而创建出更美观、信息传达更有效的数据可视化图形。

133:在R绘图中处理颜色(第四部分)🎨

在本节课中,我们将学习R语言中处理颜色的最后几个实用工具。我们将重点介绍rgb()函数,它允许我们通过红、绿、蓝三原色值来定义颜色,并可以添加透明度。此外,我们还将探讨如何利用透明度来增强高密度点图的视觉效果。


使用rgb()函数定义颜色

上一节我们介绍了预定义的颜色名称和调色板。本节中,我们来看看如何通过精确的数值来混合颜色。

rgb()函数接受红、绿、蓝三个参数,并返回一个十六进制的颜色字符串,该字符串可以传递给image()plot()等绘图函数。

以下是该函数的基本用法:

rgb(red, green, blue)

其中,redgreenblue是介于0到1之间的数字,分别代表红、绿、蓝的强度。

例如,要生成一个中等强度的灰色,可以使用:

my_gray <- rgb(0.5, 0.5, 0.5)

为颜色添加透明度

rgb()函数还有一个非常有用的第四个参数——alpha。它用于控制颜色的透明度。

以下是带透明度参数的函数形式:

rgb(red, green, blue, alpha)

其中,alpha参数同样是一个介于0到1之间的数字。0代表完全透明1代表完全不透明

通过调整alpha值,我们可以让颜色变得半透明,这在绘制重叠图形元素时特别有用。


透明度应用实例:高密度散点图

当散点图中包含大量数据点时,点与点之间会严重重叠,导致中心区域变成一片无法分辨的“黑块”,我们难以看清点的分布密度。

以下是解决此问题的一个简单示例。我们通过为点添加透明度,让重叠区域的颜色因叠加而变深,从而直观地展示出数据密度。

# 生成示例数据
set.seed(123)
x <- rnorm(1000)
y <- rnorm(1000)

# 绘制带透明度的黑色散点图
plot(x, y, pch = 19, col = rgb(0, 0, 0, alpha = 0.2))

在这段代码中,col = rgb(0, 0, 0, alpha = 0.2) 定义了颜色为黑色,但透明度设置为0.2(即80%透明)。这样,点重叠越多的地方颜色就越深,形成了一种类似“廉价密度估计图”的效果,使我们能清晰地看到数据的高密度区和低密度区。


其他工具与总结

除了rgb()函数,还有一个名为colorspace的R包,它提供了另一种控制颜色的方式,允许你进行更专业的色彩空间操作。有兴趣的读者可以自行探索。

本节课中我们一起学习了:

  1. rgb()函数:用于通过红、绿、蓝数值精确创建颜色,其返回的十六进制字符串可直接用于绘图。
  2. 透明度(Alpha通道):通过rgb()函数的alpha参数为颜色添加透明度,0为全透明1为不透明。这是澄清高密度点图、展示数据分布密度的强大工具。
  3. 实践应用:在散点图中使用半透明颜色,可以让重叠点区域的颜色自然加深,从而直观地将数据密度可视化,这比一团模糊的黑点包含更多信息。

在绘图中审慎地使用颜色和透明度,无论是对于序列数据匹配渐变色,还是对于发散数据匹配对比色,都能让读者更容易理解你想表达的观点或进行的比较。RColorBrewer包及其相关的colorRamp()colorRampPalette()函数,是处理这类任务的得力助手。

134:聚类案例研究 📊

在本节课中,我们将通过一个具体案例,深入学习探索性数据分析技术。我们将使用一个涉及智能手机预测人类活动的数据集,演示如何应用聚类分析来探索数据、发现模式,并指导后续的正式分析方向。

探索性数据分析的核心在于,在深入分析前,先对数据有一个初步的、粗略的了解。这能帮助我们判断哪些问题可能通过现有数据得到解答,哪些则不能,从而引导我们聚焦于可行的分析路径。

上一节我们介绍了探索性数据分析的基本理念,本节中我们来看看如何在一个真实数据集上应用这些理念。

数据来源与背景 📱

本案例使用的数据来自加州大学欧文分校的机器学习存档库。数据基于三星Galaxy手机(如S2或S3型号)内置的加速度计和陀螺仪传感器,旨在通过手机数据预测使用者的活动状态。

手机中的传感器可以记录人体在三维空间中的位置和加速度信息,前提是使用者随身携带手机。

数据概览 📋

为了本次演示,我们使用了该数据集的一个子集,即训练数据部分。数据经过初步处理,以矩阵形式呈现:每一行代表一次观测,每一列代表一个特征。

数据集的最后一列是活动标签,指明了每次观测时使用者正在进行的活动。共有六种可能的活动:躺下、坐下、站立、行走、下楼梯和上楼梯。

我们的目标是,基于加速度计和陀螺仪收集的众多特征,区分这六种活动。

以下是前12个特征的示例,它们包括身体加速度在X、Y、Z方向上的均值、标准差、平均绝对差和最大值等。

初步可视化探索 📈

首先,我们可以快速查看第一位受试者的平均加速度数据。为此,我们需要先将活动变量转换为因子变量。

以下代码演示了如何转换并筛选出第一位受试者的数据:

# 转换活动变量为因子
data$activity <- as.factor(data$activity)
# 筛选第一位受试者
subject1_data <- subset(data, subject == 1)

接着,我们绘制第一位受试者在X和Y方向上的平均身体加速度图,并用不同颜色标注各类活动。

从图中可以发现,对于站立、坐下、躺下等活动,平均身体加速度的变化相对平缓。而对于行走、上下楼梯等活动,X方向的平均加速度则表现出更大的变异性。

基于平均加速度的聚类分析 🔍

我们可以尝试仅基于平均加速度对数据进行聚类。我们选取数据矩阵的前三列(代表三个方向的平均加速度),计算欧几里得距离矩阵,并进行层次聚类分析。

生成的聚类树状图显示,聚类结果较为混乱,各种颜色的点(代表不同活动)混杂在一起,没有清晰的模式。这表明仅凭平均加速度可能无法有效区分所有活动,我们需要进一步探索其他特征。

探索最大加速度特征 🚀

接下来,我们查看第一位受试者的最大加速度特征。我们绘制了X和Y方向的最大身体加速度图。

观察发现,与平均加速度类似,对于非移动活动(躺、坐、站),最大加速度没有太多有趣的变化。但对于移动活动(行走、上下楼),最大加速度则表现出显著的变异性。这提示最大加速度可能是区分移动与非移动状态的有效预测因子。

基于最大加速度的聚类分析

如果我们基于最大加速度进行层次聚类,结果会清晰许多。

聚类树状图左侧清晰地聚集了各种移动活动,右侧则聚集了各种非移动活动。这成功地将“移动”与“非移动”状态分离开来。

然而,在移动活动集群内部或非移动活动集群内部,不同活动(如上楼与下楼,或坐下与站立)仍然混杂在一起。这说明最大加速度能很好地区分宏观状态,但不足以细分具体活动。

使用奇异值分解探索数据 🧮

为了更深入地探索数据,我们可以尝试奇异值分解。在进行SVD之前,我们移除了数据矩阵的最后两列(活动标识和受试者标识),因为它们不是传感器特征。

对处理后的矩阵进行SVD,并查看前两个左奇异向量,同样按活动着色。

第一奇异向量似乎主要区分了移动与非移动活动。第二奇异向量则可能进一步分离出了某种特定的移动活动(如下楼或上楼),但其模式相对模糊。

识别关键特征贡献度

为了理解第二奇异向量揭示了什么,我们可以找出在第二右奇异向量中贡献最大的特征(即对应绝对值最大的系数)。这个特征对观测值之间的差异贡献最大。

以下代码展示了如何找到这个“最大贡献者”特征:

# 假设svd_result是SVD的结果,v是右奇异向量矩阵
max_contrib_index <- which.max(abs(svd_result$v[, 2]))
max_contrib_feature <- colnames(data)[max_contrib_index]

结合关键特征的改进聚类

我们将这个“最大贡献者”特征与最大加速度特征结合起来,再次进行层次聚类。

结果显示,三种移动活动(行走、下楼梯、上楼梯)现在被更清晰地分离开来。然而,非移动活动仍然混合在一起。这表明,我们找到的这个额外特征对于细分移动活动很有帮助,但对区分非移动活动作用有限。

这个“最大贡献者”特征是Z方向上的频域平均身体加速度,即对Z方向加速度进行快速傅里叶变换后得到的频率成分。这为我们提供了新的洞察。

尝试K均值聚类算法 🎯

除了层次聚类,我们还可以尝试K均值聚类。需要注意的是,K均值的结果可能因初始聚类中心的随机选择而不同。为了获得更优解,通常需要设置多个初始值并多次运行算法。

我们知道数据中有六类活动,因此设定聚类数K=6。

首次运行K均值的结果显示,某些集群(如集群3)混合了躺、坐、站三种活动,而移动活动则能被较好地分离。

即使尝试多次运行(如设置nstart=100以尝试100个不同的初始值),最优结果中仍然存在混合了多种非移动活动的集群。这证实了区分这些静态活动的挑战性。

分析聚类中心特征

每个K均值聚类都有一个中心点,位于高维特征空间中。通过检查这些中心点在各个特征上的值,我们可以了解是哪些特征驱动了该集群的形成。

例如,对应于“躺下”活动的集群中心,在前三个特征(平均身体加速度)上有较高的正值,而在其他一些特征上值较低。而其他集群的中心则可能在其他特征(如最大加速度)上表现出显著值。

通过观察聚类中心,我们可以推测哪些特征对于预测特定活动可能是最重要的,这为后续的特征选择和模型构建提供了线索。

课程总结 📝

本节课中,我们一起学习了如何对一个拥有大量特征和观测值的数据集进行探索性分析。我们运用了层次聚类、K均值聚类和奇异值分解等多种技术来探查数据的内在结构。

通过本次探索,我们获得了一个重要的“粗略判断”:基于少数变量(主要是最大加速度相关变量),我们似乎能够较好地区分不同的移动活动(走、上、下)。然而,区分非移动活动(躺、坐、站)则更为困难。

探索性数据分析的价值正在于此:它提供了一个初步蓝图,告诉我们应将精力集中在哪里。根据本次分析,我们可能无需在区分移动活动上花费过多精力,而需要更深入地探究那些非移动活动,寻找能有效区分它们的特征。

希望本案例能帮助你理解如何起步运用聚类技术,以及如何通过审视数据来推动你的分析进程,为更正式的分析奠定基础。

135:空气污染案例研究 📊

概述

在本节课中,我们将学习如何对一份真实的空气污染数据集进行探索性分析。我们将使用美国环境保护局(EPA)提供的细颗粒物(PM2.5)监测数据,来探究一个核心问题:从1999年到2012年,美国的空气污染水平是否有所下降? 我们将通过一系列基础的数据操作和可视化工具来逐步解答这个问题。


数据背景与问题定义

在开始分析任何数据之前,都需要明确我们想要回答的问题。这可以是一个具体的假设,也可以是一个宽泛的疑问。

我们使用的数据来自美国环境保护局,涉及全美范围内的空气污染监测。具体来说,我们关注的是空气中的细颗粒物(PM2.5)污染。颗粒物是空气中灰尘的统称,由于我们时刻都在呼吸,吸入这些颗粒物可能对公众健康产生影响。

过去几十年,美国的一项重要立法是《清洁空气法案》,其目的是降低美国的空气污染水平。因此,针对这类数据,一个基本的问题是:现在的空气污染水平是否比过去更低? 这是一个非常基础且宽泛的问题,我们可以从多种角度来审视它。在本案例中,我们将聚焦于PM2.5,这种污染物从1999年开始监测,一直持续至今。我们将比较1999年和2012年的数据,回答一个更具体的问题:2012年的PM2.5平均水平是否低于1999年?

美国环保局收集了所有相关数据并公开发布在其网站上,这为我们提供了便利。


数据获取与初步查看

首先,我们从EPA网站下载了1999年和2012年的PM2.5数据压缩包。原始文件包含一个.txt数据文件和一个.pdf说明文档。

让我们先查看一下1999年的原始数据文件。可以看到,文件中的每条记录占一行。第一行是表头,指明了各列的名称,各列之间用竖线 | 分隔。每条记录以“RD”开头,表示这是一种特定类型的记录。对我们最重要的列包括:州代码、县代码、站点ID、采样值(即PM2.5浓度,单位:微克/立方米)以及采集日期。


在R中读取数据

现在,我们启动R并开始读取数据。

首先读取1999年的数据。我们使用 read.table 函数,并指定分隔符为竖线 |,缺失值用空字符串表示。

pm0 <- read.table("1999.txt", comment.char = "#", header = FALSE, sep = "|", na.strings = "")

检查数据维度,发现有约117,000行和28列。由于我们跳过了以 # 开头的表头行,所以目前列名是V1, V2等。我们需要从文件的第一行提取正确的列名。

cnames <- readLines("1999.txt", 1)
cnames <- strsplit(cnames, "|", fixed = TRUE)[[1]]
names(pm0) <- make.names(cnames)

make.names 函数将列名中的空格等字符替换为点号,使其成为R数据框中有效的列名。

接下来,提取我们关心的PM2.5浓度数据,即 Sample.Value 列。

x0 <- pm0$Sample.Value
summary(x0)

数据显示,中位数浓度为11.5微克/立方米,最大值达到157,并且存在约13,000个缺失值,约占11%。对于我们的宏观问题(全国平均水平是否下降),偶尔的缺失值可能影响不大,但这是分析中需要注意的一点。

现在,用同样的方法读取2012年的数据。

pm1 <- read.table("2012.txt", comment.char = "#", header = FALSE, sep = "|", na.strings = "")
names(pm1) <- make.names(names(pm0)) # 使用相同的列名
x1 <- pm1$Sample.Value

2012年的数据量更大,约有130万条记录,但缺失值比例降至约5%。


全国水平的初步比较

让我们对两个年份的数据进行简单的数值比较。

summary(x1)
summary(x0)

比较摘要统计量可以发现,2012年的PM2.5浓度中位数(约7.3)明显低于1999年的中位数(约11.5)。这初步表明全国平均水平有所下降。

为了更直观地比较,我们可以绘制箱线图。但由于数据存在严重的右偏(有很多高值),直接绘制效果不佳。

boxplot(x0, x1)

我们发现2012年数据中存在一个异常高的值(909微克/立方米),这在美国非常罕见,可能是个错误或特殊事件。同时,箱线图因数据偏态而显得拥挤。我们可以通过对数变换来改善可视化效果。

boxplot(log10(x0), log10(x1))

在对数尺度下,可以更清楚地看到2012年的数据中位数确实下降了,但数据的离散程度似乎有所增加。

此外,在2012年的数据摘要中,我们发现了负值。由于PM2.5是质量浓度,理论上不应为负。检查发现约有26,000个负值,约占2%。我们可以进一步探究这些负值是否与特定时间有关。

negative <- x1 < 0
dates <- pm1$Date
dates <- as.Date(as.character(dates), "%Y%m%d")
hist(dates[negative], "month")

直方图显示,负值更多地出现在冬季月份(12月、1月、2月)。这可能是因为冬季污染水平较低,测量误差相对更明显。鉴于负值比例很小,我们暂时不深入处理。


深入分析:单个监测点的变化

全国水平的分析显示污染下降,但监测网络在十几年间发生了很大变化。为了更精确地比较,我们选择一个在1999年和2012年都存在的监测点,观察其浓度变化。

我们选择纽约州(州代码36)作为例子。首先,找出两个年份数据中都存在的监测点。

site0 <- unique(subset(pm0, State.Code == 36, c(County.Code, Site.ID)))
site1 <- unique(subset(pm1, State.Code == 36, c(County.Code, Site.ID)))
site0$county.site <- paste(site0$County.Code, site0$Site.ID, sep = ".")
site1$county.site <- paste(site1$County.Code, site1$Site.ID, sep = ".")
both <- intersect(site0$county.site, site1$county.site)

共有10个监测点在两个年份都存在。我们选择其中一个观测次数较多的监测点(县代码63,站点ID 2008)进行深入分析。

pm0_sub <- subset(pm0, State.Code==36 & County.Code==63 & Site.ID==2008)
pm1_sub <- subset(pm1, State.Code==36 & County.Code==63 & Site.ID==2008)

接下来,我们分别绘制这个监测点在1999年和2012年的PM2.5浓度随时间变化的散点图。为了公平比较,我们需要将两个图的y轴范围设置为一致。

dates0 <- as.Date(as.character(pm0_sub$Date), "%Y%m%d")
x0_sub <- pm0_sub$Sample.Value
dates1 <- as.Date(as.character(pm1_sub$Date), "%Y%m%d")
x1_sub <- pm1_sub$Sample.Value

rng <- range(x0_sub, x1_sub, na.rm = TRUE)

par(mfrow = c(1, 2), mar = c(4, 4, 2, 1))
plot(dates0, x0_sub, pch = 20, ylim = rng, main = "1999")
abline(h = median(x0_sub, na.rm = TRUE))
plot(dates1, x1_sub, pch = 20, ylim = rng, main = "2012")
abline(h = median(x1_sub, na.rm = TRUE))

从图中可以清晰地看到两个现象:

  1. 2012年的浓度中位数明显低于1999年。
  2. 1999年的数据波动范围(特别是高值)远大于2012年。这意味着不仅平均水平下降了,极端的高污染事件也减少了。


州级水平的趋势分析

最后,我们不仅仅看全国或单个监测点,而是分析各个州的变化趋势。州一级是政策实施的关键单位,分析其趋势很有意义。

我们计算每个州在1999年和2012年的平均PM2.5浓度,然后将每个州的两个平均值用线段连接起来,形成“哑铃图”。

mn0 <- with(pm0, tapply(Sample.Value, State.Code, mean, na.rm = TRUE))
mn1 <- with(pm1, tapply(Sample.Value, State.Code, mean, na.rm = TRUE))

d0 <- data.frame(state = names(mn0), mean = mn0)
d1 <- data.frame(state = names(mn1), mean = mn1)
mrg <- merge(d0, d1, by = "state")

par(mfrow = c(1,1))
with(mrg, plot(rep(1999, nrow(mrg)), mrg[, 2], xlim = c(1998, 2013), ylim = range(mrg[, 2:3])))
with(mrg, points(rep(2012, nrow(mrg)), mrg[, 3]))
segments(rep(1999, nrow(mrg)), mrg[, 2], rep(2012, nrow(mrg)), mrg[, 3])

从生成的图中可以看到,连接绝大多数州的线段都是向下倾斜的,表明大部分州的PM2.5平均水平在1999年至2012年间都有所下降。只有少数州的线段近乎水平或略有上升。这种可视化方法让我们能够一目了然地看到各州的改善情况。


总结

本节课中,我们一起完成了一个空气污染数据的探索性分析案例。我们试图回答的核心问题是:美国的PM2.5水平在1999年至2012年间是否下降了?

通过分析,我们得出了以下结论:

  1. 全国层面:2012年的PM2.5浓度中位数明显低于1999年,表明整体水平下降。
  2. 单个监测点:在纽约州一个长期存在的监测点,不仅平均水平下降,极端高值也显著减少。
  3. 州级层面:通过“哑铃图”发现,美国绝大多数州的PM2.5平均水平在此期间都有所下降。

我们运用了数据读取、摘要统计、数据子集化、数据合并以及多种图形(箱线图、散点图、直方图、连接图)等基础工具,逐步深入地解答了问题。这个案例展示了探索性数据分析的基本流程:从提出一个简单问题开始,通过多角度、多层次的逐步探查,最终获得对数据的整体理解,并为后续更深入的分析指明方向。

136:可重复性研究介绍 🎯

在本课程中,我们将学习可重复性研究的基本概念及其在数据分析中的重要性。可重复性研究是数据科学专业系列课程的第五门课,它探讨了一个在传统统计或数据分析课程中较少涉及但至关重要的主题。

大家好,欢迎来到可重复性研究课程。这门课是数据科学专业系列课程的第五门课,它涵盖了一个我认为在典型的统计或数据分析类课程中不常讨论的特殊主题。

尽管“可重复性研究”一词中包含“研究”,但它并不仅适用于从事研究的人员,其理念可应用于许多不同领域。

基本理念是:当你进行数据分析时,过程包含许多步骤,涉及大量计算,可能还包括许多数据操作或处理。重要的是,在你交流所做工作时,应以一种他人能够实际复现你工作过程的方式进行,即让他人可重复。我认为,随着数据分析和数据集变得越来越复杂,确保你所做的工作真正可重复变得越来越困难,因为有时你会丢失部分代码,有时会忘记某个处理或转换步骤。

如果你没有记录所有这些细节,分析结果就可能无法被复现。

因此,在本课程中,我们将讨论一些可用于帮助你使分析乃至整体工作变得可重复的基本工具。我将介绍诸如 knitr 之类的工具。

我们将讨论如何在 RStudio 中实现这一点。

我们还将讨论一些可遵循的基本原则,以确保你的工作尽可能可重复。

我认为这是任何领域数据分析中一个非常重要的方面,因为它关乎精确地传达你所做的工作,以便他人能够理解整个过程。

在课程的最后部分,我们将讨论一些案例研究,看看可重复性实践成功或失败的例子。

我认为这些案例研究极具启发性,能让你了解什么是可能的,什么是不可能的。

我希望你喜欢这门课程,我认为这是一个非常重要的理念,并希望你能从中获益良多。


总结

本节课我们一起学习了可重复性研究的核心概念及其重要性。我们了解到,确保数据分析过程可被他人复现,是进行可靠和透明研究的关键。在接下来的课程中,我们将深入探讨实现可重复性的具体工具和方法。

137:什么是可重复研究?🔬

在本节课中,我们将要学习“可重复研究”这一核心概念。我们将探讨为什么可重复性对于任何数据分析工作都至关重要,并通过一个音乐的例子来理解其背后的原理。

概述

大家好,欢迎来到“可重复研究”课程。本次讲座的目的是向大家介绍可重复性的概念,并解释为什么我认为它非常重要。即使你不认为自己是一名研究人员或正在进行研究,只要你分析数据,理解可重复性的理念及其带来的问题就至关重要。我希望从一开始就让大家明白这一点,以便你能理解并看到它的重要性。

从音乐中理解可重复性

为了介绍可重复性引发的一些问题,我想通过另一个领域——音乐——来做一个类比。在我们继续之前,请先快速听一下我将为你播放的这段摘录。

🎼 Code monkey, get up, Get coffee. Code monkey, go to job. Code monkey have boring meeting with boring manager Rob. Rob say cold monkey, very diligent, foot is output st...

好的,这是一首名为《Code Monkey》的歌曲,由乔纳森·科尔顿创作和演唱。我刚才播放了这首歌大约第一分钟的内容,包含了第一段主歌。你可能想倒回去再听一遍,把它记在脑子里。想想这首歌以及你已经知道的关于它的信息。

你知道它听起来什么样,知道是谁创作和演唱的。开头是吉他,如果你耳朵很好,能听出是F大调,后来加入了另一把吉他和鼓组。当然,他也在唱歌,如果你仔细听并懂这门语言,你甚至能听出他在唱什么词。这就是这首歌的大部分内容。当然,我没有播放整首歌,但如果我播放了,你仅从录音中就能解码出很多信息。

大多数这样的歌曲并不复杂。它们没有很多乐器或很多人声,通常时长在两到四分钟。如果你是个好音乐家,并且听得非常仔细,实际上,在听过之后自己用吉他或其他乐器演奏这首歌并不难。

现在,让我们来听听下面这段音乐。

🎼 [交响乐片段]

如果你没听出来,这是古斯塔夫·马勒《第八交响曲》的开头部分,由芝加哥交响乐团在乔治·索尔蒂爵士的指挥下演奏。这可能与我刚才播放的《Code Monkey》歌曲形成了鲜明对比。这首交响曲有时被称为“千人交响曲”,因为仅仅在舞台上演奏这首曲子就需要大量人员:你需要整个交响乐团,你需要整个合唱团来演唱。因此,演奏这首曲子需要许多复杂的、协同运作的部分,然而它却经常被演奏,并且人们听到时都能认出它,因为它大体上是相同的。

那么,这是如何发生的呢?世界各地的管弦乐队和合唱团如何能演奏这首极其复杂的作品,并且听起来总是大致相同?同样地,你如何能听到乔纳森·科尔顿的歌曲,然后,如果你是一位训练有素的音乐家,就能学会并可能自己演奏?

音乐中的“乐谱”

这实际上就是关于可重复性。在音乐中,一个很好的地方是,当你听到一场演出时,无论它是一首简单的流行歌曲还是一部庞大的交响乐,你都能获得所需的所有信息。根据音乐的复杂程度,你获得的信息可能或多或少,因为大脑一次只能处理这么多信息。但对于像交响乐这样非常复杂的作品,我们实际上有一种方法来写下指令,交给演奏者,告诉他们如何演奏每一个部分。

马勒本人就是一个很好的例子,因为他自己就是一名指挥家。他知道,作为一名指挥家,看着作曲家的乐谱,往往很难理解作曲家在这里到底想要什么。因此,他在创作音乐时,在乐谱的每一寸地方都写下了指示。这样,诠释音乐的指挥家和演奏者就能理解:“哦,作曲家在这里想要的是这个。”

对于马勒的《第八交响曲》,我们拥有的是乐谱。乐谱基本上是一本书,列出了每种乐器的每一个部分,以及它们需要演奏的内容和指示。因此,指挥家可以看着乐谱说:“好的,我知道小提琴在这个时候演奏什么,我知道长笛在演奏什么,我知道合唱团在唱什么。”这样,音乐就能被协调、演奏并同步起来。

一首流行歌曲也可以有乐谱。会有吉他谱线、鼓谱线。它通常不像写交响乐那样书写,但有时也会使用一种记谱法来传达一首音乐作品应该如何演奏。

数据分析中的“乐谱”

我们在本课程“可重复研究”中要讨论的,基本上就是:你如何为数据分析开发“乐谱”,以便向他人传达做了什么,以及如果他们想重现这项工作,该如何去做。

现在,数据分析中的一个根本问题是,我们并没有一个公认的记谱系统来传达数据分析过程。因此,每个人做的方式都不同,总体来看,这有点混乱。

以下是人们传达数据分析的一些方式:

  • 文字描述:有些人只用文字描述做了什么。在某些情况下,这足够了,但在许多情况下并不足够。
  • 提供代码和数据:有些人会提供计算机代码、数据以及你所需的一切。有时这很好,但有时它极其复杂,难以梳理。

因此,有多种方式可以传达数据分析,但我们尚未就一种对所有人(或大致对所有人)都足够充分的方式达成共识。

本课程的重点

在本课程中,我们将重点学习如何通过编写动态文档和共享数据,来使用代码传达数据分析过程,以便其他人能够重现你所做的工作。

这对于所有数据分析都很重要,不仅仅关乎研究本身。因为如果你想向他人传达你所做的事情,你需要能够给他们材料,以便他们能够“演奏”——也就是说,自己进行分析。

总结

本节课中,我们一起学习了可重复研究的基本概念。我们通过音乐的类比,理解了为什么需要为数据分析创建清晰、可执行的“乐谱”。关键在于,我们需要一种标准化的方式来记录和分享分析过程、代码和数据,以确保他人能够理解和重现我们的工作。这是进行可靠、透明数据分析的基石。

138:可重复研究的概念与思想(第一部分) 🧪

概述

在本节课中,我们将要学习可重复研究的基本概念和核心思想。我们将探讨为什么在科学研究中,验证和复制发现至关重要,以及当完全复制研究变得困难时,可重复性如何作为一种重要的中间标准。

科学证据的黄金标准:复制

上一节我们介绍了课程主题,本节中我们来看看科学验证的基石。

科学研究中,复制是验证和确认科学发现的最重要环节。其基本思想是:如果你声称X导致Y,或维生素C改善疾病,那么独立于原始研究人员的其他科学家会尝试调查相同的问题,看是否能得出相同的结果。如果许多不同的人都得出相同的结果并复制了原始发现,那么我们就有理由认为原始发现可能是真实的。

公式独立验证 → 相同结果 → 证据增强

因此,加强科学证据的终极标准是复制。目标是让独立的人使用不同的数据不同的方法在不同的实验室进行独立研究,看是否能得到相同的结果。如果一个发现能经受住所有这些不同因素的考验,那么它就更可能是真实的,支持它的证据也就更强。

对于具有重大政策影响或影响监管决策的研究,复制尤为重要。

复制面临的挑战

既然复制如此重要,那它有什么问题吗?实际上,复制本身没有问题,这是科学数百年来一直在做的事情。但问题是,如今进行复制或复制其他研究正变得越来越具有挑战性。

以下是导致复制困难的一些原因:

  • 成本高昂:研究规模越来越大,进行大规模研究需要大量资金。要做10个相同版本的研究,就需要10倍的资金,而资金并不总是那么充裕。
  • 时间漫长:如果原始研究花了20年时间,那么再等20年进行复制就很困难。
  • 研究具有独特性:有些研究针对的是特定时间、地点的独特情况或独特人群,无法轻易复制那种情境。

由于存在许多无法复制研究的合理原因,那么替代方案就是什么都不做,让那项研究独自成立吗?可重复研究背后的思想,就是在“完全复制”和“什么都不做”之间,建立一个最低标准或中间地带。

公式黄金标准(复制) <—— 中间地带(可重复性) ——> 最低标准(无验证)

可重复性:连接复制与无验证的桥梁

上一节我们探讨了复制的困境,本节中我们来看看可重复性如何作为解决方案。

那么,为什么我们需要这个尚未明确定义的中间地带呢?基本思想是:你公开原始研究的数据和计算方法,以便其他人可以查看你的数据,运行你进行的分析,并得出与你相同的发现。

可重复研究在很大程度上是关于数据分析的验证。因为你没有使用独立的方法收集独立的数据,所以验证你所提出的问题本身会稍微困难一些。但是,如果你能获取某人的数据并重现他们的发现,那么你就在某种意义上验证了数据分析。

这涉及到拥有数据代码。因为分析很可能是在计算机上使用某种编程语言(如R)完成的。如果你能使用他们的代码和数据,重现他们得出的发现,那么你至少可以确信分析是恰当进行的,并且使用了正确的方法。

推动可重复性需求的趋势

是什么在推动这种介于复制和无所作为之间的可重复性需求呢?在许多不同领域,包括生物学、化学、环境科学等,出现了大量新技术。

以下是驱动可重复性需求的几个关键趋势:

  • 数据生成技术:这些技术使我们能够以更高的通量收集数据,从而几乎即时地获得非常复杂和高维的数据集,这与仅仅10年前相比是巨大的飞跃。
  • 计算能力:计算能力使我们能够合并现有数据库,创建更大规模的新数据库,并执行更复杂的分析。我们拟合的模型和编写的算法比过去复杂得多。
  • 计算学科的兴起:所有这些趋势的最终结果是,对于每个领域X,现在都有计算X,例如计算生物学、计算天文学等。

案例研究:空气污染与健康研究

为了具体说明,我们来看一个来自我研究领域的例子:空气污染与健康。这个领域汇集了使可重复性变得至关重要的几个特点:

  1. 信号微弱但影响重大:我们估计的是非常微小但对公共卫生非常重要的效应,而这些效应存在于更强烈的信号背景中。
  2. 影响重大政策:许多空气污染研究的结果为重大的政策决策提供信息,该领域的科学研究是制定法规的基础。这些法规可能影响众多利益相关者,并耗费数十亿美元来实施。
  3. 方法复杂:我们使用许多复杂的统计方法进行研究,这些方法受到严格审查。

微弱的内在信号、巨大的影响和复杂的统计方法这三者的结合,几乎要求我们所做的研究必须是可重复的。

因此,我们在约翰霍普金斯大学创建了“基于互联网的健康与空气污染监测系统”。我们公开了大量数据,并以代码形式提供了许多统计方法,以便他人审查,并且我们产生的许多数据和结果可以被他人重现。

总结

本节课中我们一起学习了可重复研究的基本概念。我们了解到复制是科学验证的黄金标准,但由于成本、时间和研究独特性等原因,完全复制常常面临挑战。因此,可重复性作为一种重要的中间标准应运而生,它通过公开数据分析代码,允许他人验证数据分析过程本身。现代数据技术的爆炸式增长和计算能力的提升,使得在各个领域推行可重复研究变得愈发重要,尤其是在像空气污染与健康这样涉及微弱信号、重大政策和复杂方法的领域。

139:可重复研究概念与思想(第二部分)📚

在本节课中,我们将深入探讨可重复研究的基本概念、面临的挑战以及实现可重复性所需的关键要素。我们将从研究流程的视角出发,理解作者与读者在可重复性中的角色,并分析当前实践中的主要困难。


研究流程与可重复性的核心思想

当您阅读文献中的研究文章时,您通常只能看到最终的论文。然而,众所周知,文章背后蕴含着大量的工作。这整个后台过程被称为 研究流程

如上图所示,作者沿着研究流程从左向右推进工作。而作为读者,您则从右向左回溯:您阅读文章,并希望了解背后发生了什么、数据从何而来、使用了哪些方法。可重复研究的基本思想,就是聚焦于流程中的 分析数据计算结果 这两个环节。

通过实现可重复性,我们试图让作者和读者能在中间环节“相遇”,从而使研究过程更加透明和可验证。


可重复研究的重要性与背景

可重复研究在媒体和科学文献中已被广泛讨论。例如,《科学》杂志曾出版关于可重复性和数据复现的专刊。电视节目《60分钟》也曾报道杜克大学的一起事件,该事件中许多研究结果被发现无法重复,导致多项临床试验被迫中止,并引发了持续至今的大量调查。

作为对一系列科学研究可重复性事件的回应,美国医学研究所发布了一份报告,旨在确立最佳实践,以促进和鼓励可重复性,特别是在基因组学、蛋白质组学等“组学”研究领域。

这份报告提出了多项重要建议,其中包括:数据和元数据应公开可用;计算机代码应完全公开,以便他人审查;计算分析的所有步骤都应被详细描述,以供研究和复现。


实现可重复研究的要素

那么,实现可重复研究具体需要什么呢?以下是一个基本的定义框架:

  1. 分析数据可用:这是指用于生成文中结果的数据。它不同于原始数据,因为一次分析通常只使用原始数据的一个子集。虽然查看原始数据可能有益,但通常不切实际。因此,分析数据是检验数据分析的关键。
  2. 分析代码可用:这是指应用于分析数据并产生关键结果的代码,例如回归建模代码等。
  3. 代码与数据的文档:对代码和数据进行充分的文档说明至关重要。
  4. 标准的传播方式:所有数据和代码必须能够通过标准、便捷的途径获取。

公式化表示,可重复研究的核心要素可概括为:
可重复研究 = 可用数据 + 可用代码 + 完整文档 + 便捷传播


相关方与当前挑战

在讨论可重复性时,涉及多个利益相关方,主要分为两类:

  • 作者:生产研究,希望使其工作可重复,需要工具来简化这一过程。
  • 读者:阅读研究,希望复现他人工作,同样需要工具来降低复现难度。

当前面临的主要挑战包括:

以下是作者面临的主要困难:

  • 作者需要付出相当大的努力才能将结果、数据和代码发布到网上。尽管现在可用的资源比五年前多,但这仍然是一项挑战。
  • 即使数据代码已发布,它们也常常散落在期刊补充材料(以杂乱著称)或少数几个中心化数据库中。如果您的领域没有这样一个通用数据库,发布数据将更加困难。

以下是读者面临的主要困难:

  • 读者需要手动下载数据、结果和代码,并手工将它们拼凑起来,这个过程通常并不轻松。
  • 读者可能不具备与原作者相同的计算资源(例如大型计算集群),因此难以精确复现相同结果。
  • 尽管正在增长,但用于实现可重复研究的工具箱仍然有限,工具支持不足。

现实中,作者往往只是简单地将材料扔到网上,而读者则不得不手动下载、拼凑代码和软件,整个过程困难重重。

😊


总结

本节课中,我们一起学习了可重复研究的概念框架。我们了解到,可重复性旨在通过公开分析数据分析代码及其文档,并采用标准传播方式,来弥合研究作者与读者之间的鸿沟。同时,我们也认识到当前实践中,无论是作者发布还是读者复现,都面临着数据管理、资源匹配和工具支持等多方面的挑战。理解这些概念和挑战是迈向更可靠、更透明科学研究的第一步。

140:可重复研究概念与思想(第三部分)📚

在本节课中,我们将学习文学化统计编程的核心概念,了解其如何作为工具简化可重复研究的过程,并介绍实现这一理念的关键系统与工具。


文学化统计编程的基本理念 🧩

上一节我们探讨了可重复研究的重要性,本节中我们来看看实现可重复研究的一个核心工具思想。

文学化统计编程的基本理念源自计算机科学中的“文学化编程”。其核心思想是将一篇分析报告或出版物视为文本流代码流的结合体。

  • 文本部分对人类可读,用于解释分析的思路、背景和结论。
  • 代码部分对计算机可读,用于执行实际的数据加载、计算和结果生成。

分析过程通过一系列文本块代码块来描述。每个代码块执行特定任务(例如加载数据、进行计算),而每个文本块则用人类语言阐述围绕这些代码所发生的事情。其中可能包括用于格式化表格和图形的呈现代码,以及解释所有代码逻辑的文章文本。这种文本与代码的结合体,就构成了一个文学化统计程序或文学化统计分析。

此类程序可以通过“编织”来生成人类可读的文档(如PDF或HTML网页),也可以通过“缠绕”来生成机器可读的代码文档。

文学化编程背后的基本要求是:

  1. 一种对人类友好的文档语言
  2. 一种对机器友好的编程语言

早期实现:Sweave系统及其局限 ⚙️

在R语言中,最早实现这一理念的系统之一是 Sweave

  • Sweave 使用的文档语言是 LaTeX
  • 其编程语言显然是 R
  • 它由R核心成员Friedrich Leisch开发,目前仍由R核心团队维护。

然而,原始的Sweave系统存在诸多局限性:

以下是Sweave系统的一些主要限制:

  • 文档语言门槛高:它主要专注于LaTeX,这是一种并非许多人熟悉的标记语言,对于领域外的人学习成本较高。
  • 功能缺失:它缺乏许多用户期望的功能,例如缓存机制、每页多图支持以及混合多种编程语言的能力。
  • 开发活跃度低:该系统更新不频繁,开发不够活跃。

现代替代方案:knitr包 🚀

鉴于Sweave的局限性,近年来出现了一个重要的替代方案:knitr

knitr包继承了文学化编程的思想,并在此基础上进行了大量更新和改进,为Sweave的原始概念增添了许多新特性。

  • 编程语言:它仍以R为主要编程语言,但允许混合使用其他编程语言(如Python、SQL等)。
  • 文档语言:它支持多种文档语言,你可以使用LaTeX,也可以使用更轻量级的MarkdownHTML
  • 开发背景:knitr由谢益辉(Yihui Xie)在爱荷华州立大学攻读研究生期间开发,现已成为执行文学化统计编程非常流行的R包。

总结与展望 📝

本节课中我们一起学习了可重复研究的关键实现工具。

总而言之,可重复研究是计算密集型分析的一项重要最低标准,尤其是在复制研究非常困难或几乎不可能的情况下。虽然我们仍然需要相当多的基础设施和工具来创建和分发可重复文档,这超出了当前工具的范畴,但情况正在日益改善,每天都有许多新工具涌现。

在下一讲中,我将具体介绍其中一些工具,特别是knitr,并向大家展示如何用它来生成可重复的研究文档。

141:脚本化你的分析 📜

在本课程中,我们将学习如何通过脚本化来确保数据分析工作的可重复性。我们将探讨脚本化的核心原则、重要性以及如何开始编写你的第一个R脚本。


概述

数据分析的核心原则之一是确保工作的可重复性。实现这一目标最有效的方法是将所有分析过程脚本化。脚本就像乐谱,它详细记录了每一个步骤和决策,使得他人或未来的你能够精确复现整个分析过程。

脚本化的核心原则

上一节我们提到了可重复性的重要性,本节中我们来看看实现它的核心原则。

核心原则是:将所有分析过程脚本化。 这意味着避免手动操作,而是通过编写计算机程序(脚本)来记录和执行每一步分析。

用公式表示这个原则就是:
可重复性 ≈ 脚本化程度

为何脚本化如此重要?

为了理解脚本化的重要性,我们可以将其与音乐创作进行类比。

  • 最终呈现 vs. 完整过程:数据分析的最终报告(如论文或演示)就像一首乐曲的主旋律。它优美、重要,但背后支撑它的整个乐团(即所有的分析过程、探索性工作、尝试过的错误路径以及未在报告中展示的决策)同样至关重要。
  • 乐谱的类比:在音乐中,乐谱(Score)使用标准化的记谱法,记录了所有乐器在何时演奏何种音符。这使得任何人都能根据乐谱尽可能精确地重现这首乐曲。
  • 脚本即乐谱:在数据分析和研究中,脚本就扮演着“乐谱”的角色。它使用编程语言(如R)这种“记谱法”,详细记录了对数据所做的所有操作。这确保了工作的可重复性。

因此,脚本是他人尝试复现你工作的精确指南。

如何开始编写脚本?

理解了脚本化的“为什么”之后,本节我们来看看“怎么做”。

以下是在R中开始编写脚本的简单步骤:

  1. 打开RStudio:启动RStudio集成开发环境。
  2. 新建R脚本:在菜单栏选择 File -> New File -> R Script。这将打开一个空白的文本编辑器窗口。
  3. 编写代码:在这个文本文件中,你可以开始用R语言编写指令,告诉R该做什么。
  4. 保存与运行:将脚本保存为 .R 文件,并可以逐行或整体运行其中的代码。

使用RStudio内置的文本编辑器是开始脚本化最简单的方式。

(图示:在RStudio中创建新R脚本的界面示意)

其他工具与总结

在本课程中,我们还将讨论其他有助于组织分析的工具,例如 knitrR Markdown。但无论如何,最基本且最重要的底线始终是:

将分析过程写下来,编写脚本或程序,使你的工作可重复。



总结

本节课中,我们一起学习了数据科学中确保工作可重复性的核心理念——脚本化。我们了解了脚本化的重要性(如同乐谱之于音乐),掌握了在RStudio中开始编写R脚本的基本步骤,并明确了“编写一切”是提高可重复性的关键规则。记住,你的脚本就是你分析工作的精确蓝图。

142:数据分析的结构(第 1 部分)📊

在本节课中,我们将学习数据分析的基本流程。虽然并非所有分析都完全相同,但了解其核心组成部分及其通常如何衔接,将为我们提供一个有用的模板。

概述

数据分析是一个结构化的过程。我们将分两部分来探讨其核心步骤。在第一部分中,我们将重点介绍如何定义问题、确定理想数据集、评估实际可获取的数据、获取数据以及清理数据。

定义问题 ❓

上一节我们介绍了数据分析的整体框架,本节中我们来看看第一步:定义问题。并非所有分析都始于一个非常具体或连贯的问题,但投入精力提出一个合理的问题,将能减少后续筛选信息的工作量。

定义问题是你能使用的最强大的降维工具。例如,如果你只关心身高或体重,就可以忽略许多与此无关的变量。因此,尽可能具体地界定问题,有助于减少处理大规模数据时的“噪音”。有时你可能只是想探索数据,但若能明确目标,将极大地简化问题。

科学背景通常决定了你想提出的问题类型,这进而引导你寻找数据、应用统计方法进行分析。如果目标远大,你甚至可能想发展一些理论统计方法来推广你的方法。不过,这部分(统计方法开发)通常只有少数人能完成。

需要警惕的是,如果脱离科学背景,仅仅为了寻找“有趣”的结果而随意对数据应用统计方法,这可能会得出不可复现或无意义的结论。一个恰当的数据分析应始于科学问题,并应用合适的统计方法。

让我们从一个基本问题开始:“能否自动检测电子邮件是否为垃圾邮件?” 这是一个重要问题。为了将其转化为数据分析问题,我们需要使其更具体。一个更具体的版本是:“能否利用电子邮件本身的量化特征,将其分类为垃圾邮件(Spam)或正常邮件(Ham)?” 这样,我们就可以开始寻找用于分类的量化特征了。

确定理想数据集 📁

一旦明确了问题,接下来就需要思考:解决这个问题,理想的数据集是什么样的?如果你拥有世界上所有的资源,你会去寻找什么?

根据问题的目标和类型,理想的数据集可能不同:

  • 描述性问题:可能需要获取整个总体(Population),例如所有电子邮件。
  • 探索性问题:可能只需要一个包含多个变量的随机样本。
  • 推断性问题:必须仔细考虑抽样机制和总体定义,因为你要从样本推断总体。
  • 预测性问题:需要来自目标总体的训练集和测试集,以构建模型或分类器。
  • 因果性问题:需要实验数据,例如来自随机对照试验的数据。
  • 机制性问题:需要关于系统中所有组成部分的数据。

对于我们的垃圾邮件分类问题,一个理想的数据集可能是:获取谷歌数据中心所有的电子邮件。这将是一个完整的总体,我们可以基于所有数据构建分类器,而无需担心抽样问题。

确定实际可获取的数据 🔍

当然,在现实世界中,我们必须考虑实际能获取哪些数据。也许谷歌内部人员可以访问所有Gmail邮件,但即便如此也可能很困难,更何况大多数人无法获取。

因此,有时你不得不退而求其次,选择非理想的数据。你可能需要在网上寻找免费数据,或从提供商处购买数据。在这些情况下,务必遵守数据的使用条款。如果所需数据根本不存在,你可能需要自己生成数据。

获取谷歌的所有数据很可能不现实。一个可行的替代方案来自UCI机器学习库中的垃圾邮件数据集。这个数据集由惠普的研究人员创建,包含数千条已分类的垃圾邮件和正常邮件,可用于探索我们的分类问题。

获取数据 📥

获取数据时,首要目标是尝试获取原始数据。例如,从UCI机器学习库获取。必须谨慎地注明数据来源,并记录其出处。

如果你需要从不熟悉的研究者那里获取数据,一封礼貌的电子邮件通常很有帮助。如果数据来自网络,至少应记录URL(网址)访问日期时间,以便未来追溯。网站可能会关闭或变更,但你的记录提供了获取时的快照。

在本例中,由于无法访问谷歌数据中心,我们将使用R语言kernlab包中自带的垃圾邮件数据集。安装该包后,即可直接加载数据。

清理数据 🧹

获取数据后,通常需要先进行清理。原始数据往往需要经过处理,才能用于建模或分析。

如果数据已经过预处理,了解其预处理方式和步骤至关重要。你需要知道数据的来源(例如,来自调查、观察性研究还是实验),以及抽样方式。有时,你可能需要重新格式化数据以适应特定的分析工具。对于极大的数据集,可能需要进行子抽样以使其更易于管理。

记录所有数据清理步骤至关重要。你应该将这些步骤写在脚本或文档中,以便你或他人未来能够复现你的分析结果。如果缺乏记录,将无人能够复现你的发现。

清理数据并初步查看后,需要判断数据是否足以解决你的问题。有时数据可能不够好:样本量不足、变量不够、抽样方式不适用于你的问题等。如果确定数据不足以支撑你的问题,那么应该停止、重试、更换数据或调整问题。不要仅仅因为手头只有这些数据就强行推进,这可能导致不恰当的推断或结论。

以下是我们将在本例中使用的已清理数据集(来自kernlab包),这里展示了前五个变量:

library(kernlab)
data(spam)
head(spam, n=5)

该数据集包含4601条观测和多个变量(此处仅显示前5个)。你可以通过R中的?spam命令查看数据集的帮助页面,了解其来源和处理方式。

总结

在本节课的第一部分,我们一起学习了数据分析流程的前半部分。我们从定义具体、可操作的问题开始,这是降维和聚焦的关键。接着,我们探讨了针对不同类型问题所需的理想数据集,并认识到现实约束下往往需要寻找实际可获取的数据替代方案。然后,我们介绍了获取数据时的注意事项,如记录来源。最后,我们强调了清理数据的重要性,包括处理原始数据、理解预处理步骤、记录所有操作,并评估数据是否足以回答初始问题。

这些步骤为进行严谨、可复现的数据分析奠定了坚实的基础。在下一部分,我们将继续探讨后续的分析步骤。

143:数据分析的结构(第二部分)📊

在本节课中,我们将继续第一部分开始的数据分析示例。我们将完成数据分析流程中剩余的关键步骤,包括探索性数据分析、统计建模、结果解释与挑战、综合报告以及创建可复现代码。


概述

上一节我们介绍了数据分析流程的前半部分,包括定义问题、获取数据、数据清理和初步探索。本节中,我们来看看后续步骤,并利用一个具体的垃圾邮件分类案例来演示整个过程。


探索性数据分析 🔍

在建立任何模型之前,我们需要了解数据的基本情况。这包括查看数据分布、变量间关系以及检查缺失值。

以下是探索性数据分析的几个关键步骤:

  1. 查看数据摘要:了解数据的基本结构和变量类型。
  2. 检查缺失数据:确认是否存在缺失值及其原因。
  3. 创建探索性图表:通过可视化理解数据分布和关系。

我们的数据集来自UCI机器学习仓库,包含4600封电子邮件和58个变量。这些变量大多是邮件中特定单词的出现频率。

# 查看数据前几行
head(trainingData)
# 查看变量名
colnames(trainingData)

在训练数据集中,有906封邮件被标记为垃圾邮件,1381封为非垃圾邮件。

我们可以通过绘图比较垃圾邮件和非垃圾邮件的特征差异。例如,变量capitalAve(大写字母平均数量)的分布高度偏斜,因此我们查看其对数变换后的情况。

# 对偏斜数据取对数(加1以避免对0取对数)
plot(log10(trainingData$capitalAve + 1), col = trainingData$type)

结果显示,垃圾邮件通常包含更多大写字母,这与日常观察相符。

我们还可以检查变量间的成对关系,或通过层次聚类分析探索预测变量空间。对预测变量进行对数变换后,聚类结果会更有意义,例如capitalAve自成一类,而“you”、“will”、“your”等词聚在一起。


统计预测与建模 📈

探索性数据分析完成后,我们可以进行更正式的统计建模。建模方法应基于研究问题和探索性分析的结果。

在本例中,我们将采用一个简单的建模方法:为数据集中的每个变量单独拟合一个逻辑回归模型,以预测邮件是否为垃圾邮件。

以下是建模步骤:

  1. 单变量模型拟合:遍历每个预测变量,用逻辑回归模型预测垃圾邮件。
  2. 交叉验证:计算每个模型的交叉验证错误率,以评估其预测性能。
  3. 选择最佳模型:选择交叉验证错误率最低的单个预测变量模型。
# 伪代码:循环拟合单变量逻辑回归模型并计算交叉验证错误率
cvErrors <- vector("numeric", length = ncol(predictors))
for(i in 1:ncol(predictors)) {
  formula <- reformulate(response = "type", termlabels = colnames(predictors)[i])
  model <- glm(formula, data = trainingData, family = binomial)
  # 计算交叉验证错误率(此处为简化表示)
  cvErrors[i] <- calculateCVError(model)
}
# 找出错误率最低的预测变量
bestPredictorIndex <- which.min(cvErrors)
bestPredictor <- colnames(predictors)[bestPredictorIndex]

分析发现,预测变量charDollar(邮件中美元符号的数量)的单变量模型交叉验证错误率最低。因此,我们选择它来构建最终模型。

我们使用训练数据重新拟合这个最佳单变量逻辑回归模型,然后将其应用于独立的测试数据集进行预测。逻辑回归模型输出的是邮件为垃圾邮件的概率(介于0和1之间)。我们设定一个阈值(例如0.5),将概率高于此值的邮件分类为垃圾邮件。

# 在测试集上使用最佳模型进行预测
bestModel <- glm(type ~ charDollar, data = trainingData, family = binomial)
predictedProb <- predict(bestModel, newdata = testData, type = "response")
predictedClass <- ifelse(predictedProb > 0.5, "spam", "nonspam")

通过比较预测分类和测试集中的真实分类,我们可以计算模型的错误率。在本例中,分类错误率约为22%。


结果解释 🧠

得到分析结果后,需要用恰当的语言进行解释,避免超出分析本身的范围。在预测性建模中,应使用“预测”、“相关”、“可能与结果相关联”等描述性语言。

对于本例,我们可以这样解释:

  • 邮件中美元符号的比例可用于预测其是否为垃圾邮件。
  • 在我们的预测模型中,美元符号越多,邮件是垃圾邮件的可能性就越高。
  • 该模型在测试数据集上的错误率为22.4%。

在解释时,应尽可能提供对结果的合理解释(例如,为什么美元符号与垃圾邮件相关),并引入不确定性度量来校准对结果的解读。


挑战你的结果 ❓

在向他人展示之前,自己先挑战整个分析过程至关重要。这能让你提前发现问题,并准备好应对他人的质疑。

需要挑战的方面包括:

  • 问题本身:这是否是一个有效的问题?
  • 数据来源与处理:数据从何而来?处理方式是否合理?
  • 分析方法:为什么选择这个模型?是否是最佳或最合适的模型?
  • 结论:得出的结论是否稳健?不确定性度量是否恰当?
  • 替代分析:是否有其他可行的分析方法?它们可能产生什么不同结果?

思考这些问题并准备好合理的答案,能显著增强你分析的可信度。


综合与撰写报告 📝

数据分析的最后一步是将所有工作综合成一份连贯的报告或演示。综合意味着从众多分析中提炼出最重要的部分,讲述一个清晰的故事。

撰写报告的建议:

  • 以问题开场:首先明确要解决的问题,为听众建立背景。
  • 总结方法与分析:简要说明使用了什么数据、采用了何种分析方法。不必包含所有尝试过的分析,只选择对讲述故事必要的部分。
  • 逻辑顺序:按照故事逻辑(而非实际操作的时间顺序)来组织内容。
  • 善用图表:使用精心制作的图表来直观传达关键发现。
  • 准备备用材料:将一些未在报告中展示的分析保留作为“后备”,以备他人深入提问。

在我们的例子中,故事线可以是:

  1. 问题:能否利用邮件的量化特征自动分类垃圾邮件?
  2. 方法与数据:使用UCI的公开数据集,并划分为训练集和测试集。
  3. 探索与建模:通过探索发现某些特征(如大写字母、美元符号)与垃圾邮件相关。采用交叉验证选择最佳单变量(美元符号)逻辑回归模型。
  4. 结果:模型在测试集上准确率为78%。解释是美元符号越多,邮件越可能是垃圾邮件,这符合常识。
  5. 局限与展望:指出78%的准确率仍有提升空间,未来可以纳入更多变量或尝试更复杂的模型。

创建可复现代码 🔄

确保分析的可重复性是现代数据科学的核心标准。如果他人无法复现你的结果,你的结论的说服力将大打折扣。

建议使用R Markdown、knitr等工具,将代码、分析过程和文字说明整合在同一个动态文档中。这有助于保持项目组织有序,并确保所有步骤都能被你自己或他人准确地重现。

# 这是一个R Markdown文档示例
---
title: "垃圾邮件分类分析"
output: html_document
---
## 数据加载
```{r}
library(kernlab)
data(spam)

探索性数据分析

...(此处插入R代码块)...


---

## 总结

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/93ac7eaca777d9cc92ed238a74fac03c_21.png)

本节课中,我们一起学习了数据分析流程的后半部分。我们通过一个垃圾邮件分类的实例,演示了如何从探索性数据分析过渡到统计建模,如何解释和挑战模型结果,以及如何将复杂的分析过程综合成一份清晰的报告。最后,我们强调了使用可重复研究工具(如R Markdown)来记录和分享分析的重要性,这是确保数据科学工作严谨、可信的关键。

# 144:组织你的数据分析 📊

在本节课中,我们将学习如何有效地组织一个数据分析项目。虽然不存在适用于所有人的通用方法,但掌握一些基本原则和技巧,可以帮助你将项目文件有条理地放置,并最终确保你的分析过程是可复现的——无论是你自己还是他人。

## 概述

一个完整的数据分析项目会涉及多种类型的文件。理解每种文件的作用并合理组织它们,是保证项目清晰、高效和可复现的关键。接下来,我们将逐一介绍这些核心组成部分。

## 数据分析的核心文件类型

一个主要的数据分析项目通常会保留以下几类关键文件。理解它们有助于构建清晰的项目结构。

### 数据文件

数据是分析的基石,通常分为原始数据和加工数据。

*   **原始数据**:这是你最初获得的数据,可能以各种格式存在,例如文本记录或特定格式的文件。你需要对这些数据进行处理,使其能被分析程序(如R)使用。例如,可能需要进行文本解析或格式转换。
*   **加工数据**:这是经过清理和整理后的数据,通常比原始数据更整洁。它可能以表格形式存在,包含行和列。加工数据应该被妥善命名,以便清晰地看出是由哪个脚本生成的。

**重要提示**:务必记录原始数据来源。如果数据来自网络,应在`README`文件中或通过版本控制系统(如Git)的提交信息,记录获取数据的URL、数据集描述以及访问日期。

### 图形与表格

在分析过程中,你会创建图形和表格来展示数据。

*   **探索性图形**:这些图形是在分析初期为了快速了解数据而制作的。它们通常比较简单、粗糙,目的是让你对数据的分布和关系有一个直观感受。探索性图形不一定会出现在最终报告中。
*     **最终图形**:这些是经过精心打磨的图形,用于报告或展示。它们组织良好、标注清晰,能有效地传达核心信息。通常,最终报告只会包含所有探索性图形中的一小部分精华。

### 代码文件

代码是驱动分析的核心,其组织方式直接影响项目的可读性和可复现性。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/71e438cccfda2771b4353f84104b9c7c_1.png)

*   **原始/未使用的脚本**:在分析过程中,你会编写许多脚本进行尝试,其中一些可能最终不会被采用。这些脚本可能注释较少,格式也比较随意。
*   **最终脚本**:这些是用于最终分析的脚本。它们应该被仔细清理,包含清晰的注释(包括大段的章节说明和小行的代码解释),格式规范。这些脚本记录了从原始数据到加工数据,再到最终结果的完整处理链。

### 文档与报告

文档将数据、图形和代码整合成一个连贯的故事。

*   **R Markdown文件**:这是一种非常强大的工具,可以将代码、文本、图形和表格整合在同一个文档中。通过R Markdown,你可以生成可复现的报告(如网页或PDF),它是连接原始代码与最终报告之间的理想桥梁。
*   **README文件**:用于解释项目目录的结构和内容。如果你使用R Markdown,其本身就能提供很好的文档。否则,README文件就至关重要。它可以包含逐步的分析说明,例如:首先运行哪个脚本处理数据,然后运行哪个脚本拟合模型等。
*   **最终报告/论文**:这是整个分析工作的最终成果,旨在讲述一个完整的故事。它通常包括:标题、引言(阐明问题)、方法、结果(含不确定性度量)以及结论。报告需要具有连贯性,因此**不应包含**分析过程中进行的所有尝试,而应聚焦于最重要的部分。同时,务必引用所使用的统计方法和软件,以确保他人能够复现。

## 总结与进阶资源

本节课我们一起学习了如何组织数据分析项目,涵盖了数据、图形、代码和文档等核心要素的组织原则。记住,清晰的组织结构是项目可复现和可协作的基础。

每个具体项目都有其独特性,但以下资源可以为你提供更深入的指导和最佳实践:

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/71e438cccfda2771b4353f84104b9c7c_3.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/71e438cccfda2771b4353f84104b9c7c_4.png)

*   **案例研究**:关于癌症数据分析不可复现研究的案例链接。
*   **政策指南**:《生物统计学》杂志关于可复现研究政策的社论。
*   **最佳实践**:其他研究者提供的统计分析项目管理指南。
*   **自动化工具**:R软件包 **`ProjectTemplate`**,它可以自动化处理数据分析项目中的许多常规任务,值得一试。

# 145:R语言编码标准 📝

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/d6a024415e30c848c84b73ae0d69d5b8_0.png)

在本节课中,我们将学习R语言编程中的编码标准。编码标准对于编写清晰、可读且易于维护的代码至关重要。我们将探讨几个核心原则,帮助你养成良好的编程习惯,使你的代码不仅自己能看懂,也能方便他人阅读和使用。

---

## 1. 使用文本编辑器 📄

在任何编程语言中,包括R语言,首要原则是**始终使用文本编辑器编写代码,并将其保存为文本文件**。

文本文件是一种基本标准格式,通常不包含任何特殊格式或样式,只是纯文本。它通常采用ASCII编码,但在非英语地区也可能使用其他标准文本格式。关键在于,文本格式可以被现今几乎所有基本的编辑程序读取。

当你编写书籍或网页等内容时,可以使用多种不同的工具。然而,编写代码时,应尽量使用文本编辑器。这是最通用的工具,能确保所有人都能访问你的代码,并在此基础上进行改进。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/d6a024415e30c848c84b73ae0d69d5b8_2.png)

---

## 2. 缩进你的代码 ↔️

上一节我们介绍了代码的存储格式,本节中我们来看看如何提升代码的可读性。第二个对可读性至关重要的原则是**缩进你的代码**。

缩进是指在代码块右侧增加空格,使其比周围的代码更靠右。通过缩进,你可以仅凭视觉就能理解程序的控制流和结构。关于缩进多少合适,在各类讨论中常有激烈争论。虽然我也有一些建议,但最重要的是理解缩进为何重要。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/d6a024415e30c848c84b73ae0d69d5b8_4.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/d6a024415e30c848c84b73ae0d69d5b8_6.png)

以下是缩进的基本原则:
*   缩进使代码块的层级关系一目了然。
*   它有助于区分函数、循环和条件语句的开始与结束。

---

## 3. 限制代码宽度 📏

与缩进紧密相关的是第三个原则:**限制代码的宽度**。

如果你不断缩进,代码可能会无限地向右侧延伸。因此,你需要限制代码在右侧的宽度。这通常通过限制文本列数来实现。一种常见的做法是将文本限制在大约80列,确保代码宽度不会超过这个限制。

让我们来看一个快速示例。

---

## 4. 示例:调整缩进与宽度

以下是在RStudio中调整缩进和查看边距的示例。

```r
# 初始代码:缩进为1个空格,难以阅读
my_function <- function(x) {
if (x > 0) {
print("Positive")
} else {
print("Non-positive")
}
}

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/d6a024415e30c848c84b73ae0d69d5b8_8.png)

# 在RStudio中,通过“Preferences” -> “Code Editing”可以调整:
# 1. 将“Tab width”设置为4或8(推荐至少4个空格)。
# 2. 确保“Show margin”已勾选,并设置为80字符。
# 3. 使用快捷键(如Cmd+A全选,然后Cmd+I自动缩进)重新格式化代码。

# 调整后(例如使用8空格缩进)的代码结构更清晰:
my_function <- function(x) {
        if (x > 0) {
                print("Positive")
        } else {
                print("Non-positive")
        }
}

将缩进设置为至少4个空格(我个人偏好8个空格),并配合80字符的右侧边距,有两大好处:

  1. 提升可读性:代码结构变得非常明显,间距清晰。
  2. 引导更好的设计:例如,使用8空格缩进时,嵌套的循环或条件语句会迅速接近80字符的边界。这迫使你思考是否嵌套过深,从而鼓励你将复杂逻辑拆分成独立的函数,这本身就是一种良好的编程实践。

5. 限制函数长度 ⚙️

最后,我们来讨论如何组织代码逻辑。第四个原则是限制函数的长度

R语言中的函数理论上可以写得很长。但合理的做法是,让一个函数只完成一项基本的、逻辑独立的活动

例如,如果你的函数名为 read_data,那么它就应该只负责读取数据,而不应该同时处理数据、拟合模型和打印输出。这类逻辑上独立的步骤应该被拆分成多个函数。

将代码拆分成逻辑独立的函数有几个优点:

  • 一目了然:单个函数能在一屏内显示,便于整体理解其功能。
  • 利于调试:当你使用 traceback、性能分析器或调试器时,它们会指出问题发生在函数调用栈的哪个位置。如果函数逻辑清晰且分离,你就能快速定位到出问题的具体函数。如果一个函数冗长无比,调试器只能告诉你这个函数有问题,却难以精确定位。

当然,也要避免过度拆分,比如创建十个只有三行的函数。目标是让函数的分离符合逻辑,并且每个函数专注于完成一件特定的事情。


总结 🎯

本节课中我们一起学习了R语言编码的四个核心标准:

  1. 使用文本编辑器:确保代码的通用性和可访问性。
  2. 进行代码缩进:使用至少4个空格(推荐8个)来明确代码结构。
  3. 限制代码宽度:建议不超过80字符,提升可读性并引导更好的代码设计。
  4. 限制函数长度:让每个函数专注于单一逻辑任务,提升可读性和可调试性。

遵循这些基本原则,你的代码将变得更加清晰、易读,无论是对于你自己还是其他协作者,都将大大提高R语言编程的效率和实用性。

146:Markdown基础教程 📝

在本节课中,我们将学习Markdown语言的基础知识。Markdown是一种轻量级标记语言,旨在简化文档编写过程,让作者专注于内容创作,而无需处理复杂的格式标签。

概述

Markdown是一种文本到HTML的转换工具,专为网络写作者设计。它允许您使用易于阅读和编写的纯文本格式进行创作,然后将其转换为结构有效的XHTML。其核心理念是让用户专注于内容本身。

基本语法

上一节我们介绍了Markdown的设计理念,本节中我们来看看它的基本语法元素。

文本格式

以下是基本的文本格式化方法:

  • 斜体文本:使用单个星号或下划线包裹文本。例如,*斜体*_斜体_ 会呈现为 斜体
  • 粗体文本:使用两个星号或下划线包裹文本。例如,**粗体**__粗体__ 会呈现为 粗体

标题

标题通过井号(#)的数量来定义级别,类似于HTML中的<h1><h6>标签。

  • # 一级标题 对应 <h1>
  • ## 二级标题 对应 <h2>
  • ### 三级标题 对应 <h3>

列表

列表分为无序列表和有序列表。

以下是创建无序列表的方法,使用连字符(-)、星号(*)或加号(+)作为项目符号:

  • 项目一
  • 项目二

以下是创建有序列表的方法,只需在行首使用数字加句点(例如 1.):

  1. 第一项
  2. 第二项
  3. 第三项

注意:在有序列表中,您使用的数字顺序并不重要。Markdown处理器会自动按顺序编号。例如,输入 1.3.2. 最终仍会输出为1、2、3。

链接

在文档中插入链接非常重要。Markdown提供两种方式。

内联链接:将链接文本放在方括号 [] 中,紧接着将URL放在圆括号 () 内。
格式为:[链接文本](URL)
例如:[约翰霍普金斯大学](https://www.jhu.edu)

引用式链接:为了使文档正文更整洁,尤其是当URL很长时,可以使用引用式链接。

  1. 首先,用方括号定义链接文本和引用标识:[链接文本][引用ID]
  2. 然后,在文档的其他地方(通常在底部)定义该标识对应的URL:[引用ID]: URL

例如:

了解更多数据科学知识,请访问[我们的博客][1]或[统计网站][2]。

[1]: https://simplystatistics.org
[2]: https://www.r-project.org

换行

在Markdown中创建新行有一个需要注意的细节:在行末添加两个空格,然后按回车键,才会产生真正的换行。

  • 第一行第二行 (无空格)会显示为“第一行第二行”。
  • 第一行␣␣ (后跟两个空格和回车)
    第二行 会显示为两行。

总结与资源

本节课中我们一起学习了Markdown的基础语法,包括文本格式化、标题、列表、链接和换行的创建方法。Markdown语法简单,能帮助您快速创建结构清晰的文档。

  • 官方文档:John Gruber的Markdown官方网站提供了简洁完整的说明,您可以在几分钟内掌握。
  • GitHub风格:如果您在GitHub上协作或编写文档,可以查阅GitHub Flavored Markdown指南,它包含了一些有用的扩展语法。

147:R Markdown 简介 🧑‍💻

在本节课中,我们将要学习 R Markdown 的基本概念。这是一种将 R 代码与文档撰写相结合的工具,能够帮助我们创建包含动态代码和结果的报告。

概述

R Markdown 的核心思想是将 R 语言Markdown 轻量级标记语言 结合起来。它允许我们在编写文档时,直接嵌入可执行的 R 代码块。当文档被编译时,这些代码会自动运行,并将结果(如表格、图表)插入到最终生成的报告中。这确保了文档中的代码示例和结果是准确且可复现的。

什么是 Markdown?

上一节我们介绍了 R Markdown 的核心理念,本节中我们来看看它的基础之一:Markdown。

Markdown 是一种非常简单的标记语言。它的设计哲学是让作者能够专注于写作内容本身,而不是复杂的格式排版。与 HTML、LaTeX 或 XML 等语言相比,Markdown 的语法极其简洁直观。

以下是 Markdown 的一些基本格式化元素:

  • 粗体文本:用 **文本** 表示。
  • 章节标题:用 # 的数量表示层级,例如 # 一级标题
  • 列表:用 -* 创建无序列表。

Markdown 的一个优点是它可以轻松转换为 HTML 等多种格式。如果需要更复杂的格式,你甚至可以直接在 Markdown 文档中插入 HTML 代码。

什么是 R Markdown?

了解了基础的 Markdown 后,本节我们来看看 R Markdown 如何将代码与文档融合。

R Markdown 本质上是在 Markdown 文档中嵌入了 R 代码。它的文件通常使用 .Rmd 作为扩展名。文档看起来像标准的 Markdown,但其中会包含被称为 代码块 的特殊部分。

代码块由特定的格式标签标识,例如:

# 这是一个 R 代码块
x <- 1:10
mean(x)

当处理 R Markdown 文档以生成最终报告(如 HTML)时,这些代码块会被执行,运行结果(如计算结果、生成的图表)会直接插入到输出文档中。这样做的一个主要好处是,你能确保文档中展示的代码是有效的,因为如果代码无法运行,文档就无法成功生成。

工作流程与工具

现在我们已经知道 R Markdown 是什么,本节中我们来了解一下如何使用它。

处理 R Markdown 文档的核心工具是 R 中的 knitr 包。基本工作流程分为三步:

  1. 编写核心的 .Rmd 源文件,其中包含 Markdown 文本和 R 代码块。
  2. 使用 knitr 包处理 .Rmd 文件。它会执行其中的 R 代码,并将结果与文本合并,生成一个纯 .md 格式的 Markdown 文件。
  3. 使用 markdown 包(或类似工具)将上一步生成的 .md 文件转换为最终的 .html 网页文件。

在整个流程中,你只需要编辑最初的 .Rmd 源文件,而无需修改中间生成的 .md 文件或最终的 .html 文件。

如果你使用 RStudio 集成开发环境,这个过程会更加简便。RStudio 内置了对 knitr 和 markdown 的支持,甚至包含一个网页浏览器来预览最终生成的 HTML 报告,从而将整个工作流无缝地管理起来。

总结

本节课中我们一起学习了 R Markdown 的基础知识。我们了解到 R Markdown 是 R 代码Markdown 标记语言 的结合,用于创建动态、可复现的报告。我们介绍了其简洁的语法、核心的工作流程,以及如何使用 knitr 等工具来编译文档。通过 R Markdown,你可以确保文档中的代码示例始终有效,并轻松生成格式优美的最终报告。

148:R Markdown 演示 🧑‍💻

在本课程中,我们将学习如何创建和编辑一个 R Markdown 文档。R Markdown 是一种将 R 代码、分析结果和文本叙述结合在一起的强大工具,可以生成格式优美的报告。

概述

我们将从零开始,演示在 RStudio 中创建 R Markdown 文件、编写内容、嵌入 R 代码块,并最终将其“编织”成 HTML 格式报告的全过程。


创建新文档

首先,我们需要在 RStudio 中创建一个新的 R Markdown 文档。

以下是具体步骤:

  1. 在 RStudio 菜单栏中,点击 File
  2. 选择 New File
  3. 从下拉菜单中选择 R Markdown...

此时,RStudio 会自动在新文档中填充一些示例内容,帮助你快速入门。


编织文档

上一节我们创建了文档,本节中我们来看看如何生成最终的报告。

“编织”是将 R Markdown 文档转换为最终输出格式(如 HTML、PDF)的过程。它会执行文档中的所有 R 代码,并将结果嵌入到生成的文档中。

点击编辑器上方的 Knit 按钮(通常显示为“Knit to HTML”)。首次编织时,系统会提示你保存文件。将文件命名为 Markdown_demo.Rmd 并保存。

编织过程完成后,RStudio 会打开一个预览窗口,显示生成的 HTML 报告。你可以看到,标题、文本、R 代码及其输出(包括图表)都被整合到了一个整洁的文档中。


编辑文档内容

生成的示例文档并非我们所需,因此需要对其进行编辑。我们将学习如何编写自己的标题、文本和 R 代码块。

首先,删除文档中的示例内容。然后,我们可以开始构建自己的报告。

添加标题和文本

在文档顶部,输入你的标题,例如:

My First R Markdown File
========================

在标题下方输入普通文本,例如:

This is my first R Markdown file. Here we are going to load some data.

插入 R 代码块

R 代码需要被包裹在特定的代码块中。以下是插入代码块的方法:

  1. 使用三个反引号 ``` 开始一个代码块。
  2. 在花括号 {} 内指定语言为 r,即 {r}
  3. 在代码块内编写你的 R 代码。
  4. 使用三个反引号 ``` 结束代码块。

例如,以下代码块加载 airquality 数据集并生成摘要:

library(datasets)
data(airquality)
summary(airquality)

在代码块之后,可以添加描述性文本,例如:“接下来,我们将绘制数据的散点图矩阵。”


添加更多分析与输出

现在,让我们为报告添加更多分析内容,例如绘制图表和拟合回归模型。

绘制散点图矩阵

我们可以在一个新的 R 代码块中创建可视化图表。

以下是具体代码:

pairs(airquality)

这段代码将对 airquality 数据框中的所有变量生成两两关系的散点图矩阵。

拟合回归模型

最后,我们可以添加一个回归模型来分析变量之间的关系。

以下是拟合模型的代码:

library(stats)
fit <- lm(Ozone ~ Wind + Solar.R + Temp, data = airquality)
summary(fit)

这段代码拟合了一个以臭氧浓度为因变量,以风速、太阳辐射和温度为自变量的线性回归模型,并输出了模型摘要。

保存文件并再次点击 Knit 按钮。在生成的 HTML 报告中,你将依次看到数据摘要、散点图矩阵和回归分析结果。


其他格式元素

除了上述基础元素,R Markdown 还支持列表等格式,以增强报告的可读性。

例如,要创建一个无序列表,只需在每行前加上 -* 号:

- 第一项
- 第二项
- 第三项

编织后,这些行将显示为带项目符号的列表。

你还可以在文档中嵌入原始 HTML 或 LaTeX 代码,用于添加更复杂的格式或数学公式。


总结

本节课中我们一起学习了 R Markdown 的基本工作流程。我们掌握了如何创建新文档、编写文本和 R 代码块、通过“编织”生成最终报告,以及添加列表等基本格式。R Markdown 的核心在于将分析代码、结果和文字叙述无缝整合,实现可重复的研究报告。请尝试在 RStudio 编辑器中创建你的第一个 R Markdown 文件,并探索其各种功能。

149:13_knitr 第 1 部分

📖 概述

在本节课中,我们将学习文学化统计编程的基本概念,这是实现可重复性研究的关键工具之一。我们将探讨如何将数据分析、代码和文本描述整合到单一文档中,从而让他人能够更容易地理解和复现你的分析工作。


🧠 文学化统计编程简介

在关于可重复性研究的讲座中,我讨论了分析可重复性的基本含义。本节中,我们将介绍一种实际工具,它能帮助你让他人复现你的分析,或至少为他人复现分析提供便利。我们讨论的实现可重复文档的方法之一,就是文学化统计编程,或称文学化编程

其核心理念是,作者通常需要付出大量努力才能将数据和结果发布到网上。即便如此,这些内容往往是分离的:数据在一处,代码在另一处。你必须弄清楚哪段代码对应哪些数据,以及如何执行这些代码,例如先加载哪个文件,先执行哪一步。即使所有材料都已公开,这个过程也可能非常混乱。

因此,简化这一过程的方法之一,就是将数据和代码整合到同一个文档中。这样,人们可以按正确顺序执行代码,并在正确的时间读取数据。你可以拥有一个单一文档,它将数据分析与所有文本描述(即你用来解释过程的文字)整合在一起。这样,所有内容不再是分散的片段,而是相互关联的整体。

🧩 文学化编程的起源与概念

文学化编程的理念最初由斯坦福大学的计算机科学家 Donald Knuth 提出。他最初的系统是为编写常规计算机程序而设计的,旨在编写代码的同时记录代码。这个基本理念被引申到文学化统计编程中,即你希望在记录分析过程的同时,将分析代码也放在同一文档中。

其思想是,一篇文章是文本流代码流的结合。分析代码被划分为文本块和代码块。呈现代码(如表格和图形的格式化)与解释过程的文章文本交织在一起。

在文学化编程中,有两个核心概念:

  • 编织:将文档转换为人类可读的文档。
  • 缠结:将文档提取为机器可读的代码文件。

🛠️ 工具与实现

如前所述,文学化编程是一个通用概念,它需要一种文档语言和一种编程语言。之前提到的由 Friedrich Leisch 编写的原始 Sweave 系统,使用 LaTeX 作为文档语言,R 作为编程语言。

本讲座将重点介绍 knitr,它支持多种文档语言和编程语言。

🔑 如何实现可重复性研究

一个基本问题是:如何使我的工作可重复?基本答案是:你必须决定去做这件事。你可以在分析过程中的任何时刻做出这个决定,但通常在一开始就决定是最容易的。如果你等到最后才想使分析可重复,那通常会困难得多,甚至可能无法实现。

以下是一些关键实践原则:

  • 持续追踪:随着分析的进行,持续记录你的工作。你可以使用版本控制系统,如 GitSVN(本课不深入讨论,但了解它们是有益的)。
  • 使用可编码的软件:使用那些操作可以通过代码记录的统计软件。这意味着你可以写下用于操作或分析数据的指令。这通常排除了图形用户界面,除非这些程序能记录或追踪你的所有点击操作。像 R 这样的系统,因为你必须显式地编写代码,所以只要保存了代码,就保存了你对数据所做的所有操作。
  • 不保存中间输出:这里的“输出”主要指临时数据转换对象。如果你预处理数据后得到了一个干净的数据集,与其保存这个干净的数据集,不如保存原始数据及预处理代码。这样,你不仅能生成最终结果,还能看到你是如何从原始数据得到它的。如果你只保存了干净数据,却不小心丢失了预处理代码,你就无法清晰地记录从 A 到 B 的过程。因此,尽量保存原始数据和生成最终结果的代码,而不是保存中间或最终产物,这有助于理解你的操作和分析路径。
  • 避免使用专有格式:尽量不要使用非公开数据布局的专有格式保存数据。虽然现在常见的专有格式不像以前那么多,但许多程序为了效率会使用专有格式存储数据。这会导致如果他人没有完全相同的程序,就无法读取数据。使用非专有格式(即使是压缩的文本格式)可以使你的研究或分析更具可重复性。

⚖️ 文学化编程的优缺点

文学化编程并非实现可重复性工作的唯一工具,但它是其中之一。

以下是它的一些优点:

  • 整合与逻辑性:它迫使你将分析的所有文本和代码放在一个地方,并以逻辑顺序呈现。
  • 自动更新:所有数据和结果都会自动更新以反映外部变化。你拥有一个“活”的文档;如果你进行了修改,重新处理文档时,它会自动更新,避免数字过时或内容不匹配的情况。
  • 回归测试:由于代码是“活”在文档中的(必须运行代码才能生成最终文档,即编织过程),这相当于提供了一种回归测试。如果你在分析中引入了错误,代码将无法运行,从而提示你存在问题,这有助于防止在分析中引入新的错误。

当然,文学化编程也有一些缺点:

  • 编辑复杂性:文本和代码都集中在一个地方。特别是当你的代码量很大,或者分析非常复杂冗长时,人类可读的文本会与大量代码混合在一起,这可能使编辑文档变得困难,因为你需要在整篇文档中搜索文本部分。
  • 处理速度:如果你的分析非常复杂,每次你想查看文档的人类可读版本时都需要重新处理整个文档,这可能会显著减慢文档的生成速度。


📝 总结

本节课中,我们一起学习了文学化统计编程的核心概念,它是实现分析可重复性的重要方法论。我们了解了其起源、基本思想(将代码、数据和文本描述整合),探讨了实现可重复性研究的关键实践(如使用可编码软件、不保存中间输出),并分析了使用文学化编程工具(如 knitr)的优缺点。掌握这些原则和工具,将为你创建清晰、可复现的数据分析报告奠定坚实基础。

150:knitr 工具详解(第二部分)🔧

在本节课中,我们将深入探讨 knitr 工具,了解它是什么、如何工作以及它的适用场景。我们将学习如何使用 knitr 创建可重复性文档,并理解其核心概念。


什么是 knitr?🤔

knitr 是一个可以帮助你创建可重复性文档的工具。它是一个由爱荷华州立大学研究生谢益辉编写的 R 语言包。你可以在 CRAN 上找到它。如果你使用 RStudio,knitr 实际上已经内置在 RStudio 中,并集成到图形用户界面里。因此,如果你使用 RStudio,你无需单独安装它。

knitr 支持多种文档语言,包括 R Markdown、LaTeX 和 HTML。这三种是常用的文档语言。它可以将文档导出为 PDF 和 HTML 格式,你还可以使用 Pandoc 等外部工具将其导出为其他格式。正如刚才提到的,为了方便用户,knitr 已直接集成到 RStudio 中。


使用 knitr 需要什么?📋

要使用 knitr,你需要满足以下条件:

  1. 一个较新版本的 R:确保你的 R 版本足够新。
  2. 一个文本编辑器:你可以使用 R 自带的编辑器,或者任何你习惯的文本编辑器。
  3. 一些支持包:这些包可以从 CRAN 获取。如果你使用 install.packages() 函数,它们会自动下载。
  4. 掌握一种文档语言的知识:你需要了解 R Markdown、LaTeX 或 HTML 中的至少一种。本教程将重点介绍 Markdown,因为它是一种相对简单易懂的语言。

什么是 Markdown?📝

Markdown 是常见标记语言的简化版本。LaTeX 和 HTML 可以被视为标记语言,你在普通文本中添加标签和其他类型的注释,以指示你希望对文本进行的操作。标记语言的一个问题是,它们可能因为包含大量标签而难以阅读,这些标签有时会掩盖实际文本内容。

Markdown 的目的就是简化这一切,使文本本身易于阅读,同时只需在文档中输入少量格式化元素。它不需要特殊的编辑器,只需一个标准的文本编辑器即可。它具有简单直观的格式化元素。你可以在这个网站上找到所有相关文档。


knitr 擅长做什么?✨

在创建可重复性文档方面,我个人认为 knitr 非常适用于以下场景:

  • 手册:如果你想指导某人如何使用某个软件。
  • 短篇或中篇技术文档:用于解释某些技术概念。
  • 教程:这类似于手册,但可能更深入,是关于某个主题的教程。
  • 报告:报告尤其有用。例如,如果你需要每周基于某个数据库或数据集生成报告,或者你在进行一项研究,不断有受试者加入,你可能需要每周生成一份包含研究摘要统计数据的报告。这类“活”文档非常有用,因为你可以随着文档的生成重新计算所有摘要统计数据,无需手动操作后再单独插入报告中。
  • 数据预处理:通常,在预处理数据以创建干净数据集时,创建一个 knitr 类型的文档会很有帮助。在这个文档中,你可以记录对数据集进行的所有清理操作,例如移除异常值、填充缺失数据、转换变量等。所有这些都可以在这种 knitr 风格的文档中记录下来,你可以在其中同时说明你做了什么,并在代码中实际执行这些操作。

knitr 不擅长做什么?🚫

在我看来,knitr 不太适合以下情况:

  • 非常长的研究文章:如果你正在撰写一篇非常复杂、涉及大量分析的文章,因为所有内容都存储在一个文档中,当代码和文本都很多时,编辑这个文档可能会变得有些混乱。
  • 计算非常复杂或耗时:如果你的计算非常耗时,knitr 通常不是一个好的选择,因为每次你想查看文档时都必须重新编译它,这会使整个过程变慢。
  • 需要非常精确格式化的文档:如果你的文档需要特殊的布局,比如图片必须放在这里,文本必须放在那里等,knitr 也不是一个非常理想的工具,因为它的格式化往往非常临时性。

总结 📚

本节课我们一起学习了 knitr 工具。我们了解到 knitr 是一个用于创建可重复性文档的 R 包,它支持 R Markdown、LaTeX 和 HTML 等语言,并能方便地集成在 RStudio 中使用。我们明确了使用 knitr 所需的环境和知识,并介绍了其底层常用的 Markdown 语言。最后,我们探讨了 knitr 最适合的应用场景(如报告、教程、数据预处理)以及其不太适用的领域(如超长文章、复杂计算、精密排版)。理解这些将帮助你更有效地利用 knitr 来提升数据分析工作的可重复性和效率。

151:创建基础 knitr 文档 🧑‍💻

在本节课中,我们将学习如何使用 RStudio 创建一个基础的 knitr 文档。我们将了解 R Markdown 文档的基本结构,如何嵌入和运行 R 代码,以及如何将文档编译成可读的输出格式(如 HTML)。


🛠️ 在 RStudio 中创建新文档

上一节我们介绍了 knitr 的基本概念,本节中我们来看看如何实际操作。在 RStudio 中创建 knitr 文档是最简单的方法之一。

在 RStudio 左上角点击“新建文档”按钮,你会看到多个选项。选择“R Markdown”选项,即可创建一个新的 R Markdown 文件。

这个文件将包含文本和代码。


📄 R Markdown 文档的基本结构

以下是 R Markdown 文档的一个基本示例。R Markdown 本质上是嵌入了 R 代码的 Markdown 格式。

---
title: "我的第一个 knitr 文档"
---

这是一些文本。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/de29ed08a3d0b162efdb4de56247d3f8_8.png)

以下是一个代码块:

```{r}
set.seed(1234)
x <- rnorm(100)
mean(x)

你可以看到,文档以 YAML 头部(定义标题等信息)开始,然后是普通的文本段落。

---

## 🔤 代码块详解

在 R Markdown 中,代码块用于嵌入和执行 R 代码。以下是关于代码块的关键点:

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/de29ed08a3d0b162efdb4de56247d3f8_10.png)

*   **代码块起始**:代码块以三个反引号(```)开始,后跟花括号 `{r}`。这表示一个 R 代码块的开始。
*   **代码块结束**:代码块以三个反引号(```)结束。
*   **代码位置**:所有的 R 代码都写在这两个标记之间。

你可以拥有任意多个代码块,不必将所有代码都放在一个块中。

---

## 🏷️ 代码块命名与输出控制

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/de29ed08a3d0b162efdb4de56247d3f8_12.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/de29ed08a3d0b162efdb4de56247d3f8_13.png)

代码块可以拥有名称,并且可以控制其输出是否在最终文档中显示。

*   **命名代码块**:在 `{r}` 后面的花括号内,可以指定一个名称,例如 `{r my_chunk_name}`。
*   **默认行为**:默认情况下,代码块中的代码及其运行结果都会在输出文档中显示(即“回显”)。

---

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/de29ed08a3d0b162efdb4de56247d3f8_15.png)

## ⚙️ 编译文档生成输出

创建好 R Markdown 文档后,下一步是将其“编织”成可读的输出格式。

在 RStudio 中,这非常简单。你只需点击“Knit to HTML”按钮,RStudio 会自动执行以下操作:
1.  运行文档中所有的 R 代码。
2.  将代码的输出结果嵌入到文档中。
3.  将整个文档转换为 HTML 网页。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/de29ed08a3d0b162efdb4de56247d3f8_17.png)

如果你不使用 RStudio,也可以通过 R 命令来完成这个过程:
```r
library(knitr)
setwd("/path/to/your/document") # 设置工作目录到 Rmd 文件所在位置
knit2html("your_document.Rmd")  # 编译为 HTML
browseURL("your_document.html") # 在浏览器中打开

knit2html 函数会处理你的 R Markdown 文件,运行其中的 R 代码,并将输出嵌入,最终生成一个同名的 HTML 文件。


📊 输出结果示例

编译成功后,你会得到一个 HTML 文件。其内容大致如下:

  • 标题以大字体显示。
  • 普通文本段落正常显示。
  • 代码会显示在一个有阴影的方框中。
  • 代码的运行结果直接显示在代码下方。

例如,上述示例代码块的输出可能显示为:

[1] 0.1088874

这就是 mean(x) 函数的计算结果。


🔄 knitr 的工作原理

理解 knitr 的工作原理有助于更好地使用它。knitr 的处理流程如下:

  1. 输入:读取原始的 R Markdown (.Rmd) 文档。
  2. 处理:执行文档中所有的 R 代码块。
  3. 转换:将代码的输出结果插入到文档中相应位置,生成一个“中间”的纯 Markdown (.md) 文档。
  4. 输出:最后,使用其他工具(如 Pandoc)将这个 Markdown 文档转换为最终的输出格式(如 HTML、PDF)。

关键的一点是,代码执行的结果被添加到了 Markdown 文档中


💡 实用提示

在开始创建自己的文档时,请注意以下几点:

  • 删除示例内容:在 RStudio 中创建新 R Markdown 文档时,它会自动填充一些通用的示例文本和代码。你的第一步通常是删除这些内容,替换成你自己的。
  • 文件扩展名:R Markdown 文件通常使用 .Rmd 作为扩展名,这有助于识别文件类型,但并非强制要求。

📝 总结

本节课中我们一起学习了如何创建基础的 knitr 文档。我们了解了在 RStudio 中创建 R Markdown 文件的步骤,掌握了文档的基本结构,特别是如何使用代码块嵌入 R 代码。我们还学习了如何通过点击按钮或使用 knit2html() 函数来编译文档,生成包含代码和结果的 HTML 报告,并理解了 knitr 将代码、文本和输出结果编织在一起的基本原理。

152:knitr 第4部分 - 高级选项与全局设置

在本节课中,我们将深入学习 knitr 文档的高级功能,包括如何控制代码和结果的显示、在文本中嵌入计算结果、添加图形和表格,以及如何设置全局选项以提高效率。我们还将探讨缓存计算结果的技巧,以处理耗时较长的代码块。


📄 knitr 文档的处理流程

knitr 文档的处理遵循特定顺序。首先,R Markdown 文档被处理并生成一个 Markdown 文档。接着,该 Markdown 文档被转换为 HTML 文件。最终,你查看的是这个 HTML 文件。

一般来说,你不应编辑或改动任何中间生成的文档。这意味着不要编辑自动生成的 Markdown 文档或 HTML 文档。因为如果你编辑了它们,然后重新处理原始 R Markdown 文档,你在这些文件中的所有更改都会被覆盖。因此,只编辑包含原始文本和原始 R 代码的 R Markdown 文件。


🚫 控制代码与结果的显示

上一节我们介绍了文档的基本处理流程,本节中我们来看看如何控制代码块在输出文档中的显示。

以下是控制代码块输出的核心方法:

  • 隐藏代码 (echo = FALSE): 在代码块选项中设置 echo = FALSE,可以阻止代码本身出现在输出文档中,只显示代码运行的结果。

    ```{r simulation, echo=FALSE}
    x <- rnorm(100)
    mean(x)
    
    
    
  • 隐藏结果 (results = "hide"): 在代码块选项中设置 results = "hide",可以隐藏代码运行的结果,只显示代码本身(如果 echo = TRUE)。这在某些情况下可能有用。

    ```{r hidden-results, results="hide"}
    x <- rnorm(100)
    mean(x)
    
    
    


🔤 在文本中嵌入计算结果

knitr 文档的一个优点是,你可以在句子或段落中直接插入计算得到的数值或统计结果。

例如,你可以创建一个不显示代码的代码块来计算当前时间和一个随机数,然后在随后的文本中引用这些计算结果。

`r current_time`
`r random_number`

输出文档中的句子会动态地填充这些值,例如:“当前时间是 Wednesday, September 4, 2013 14:30:00” 和 “我最喜欢的随机数是 0.1829”。


📊 在文档中添加图形

任何报告通常都包含图形。你可以在 knitr 文档中轻松添加数据可视化图表。

首先,在一个代码块中模拟或加载数据。然后,在另一个代码块中使用绘图函数(如 plot())创建图形。你可以通过代码块选项(如 fig.heightfig.width)调整图形的大小和比例。

```{r make-plot, fig.height=4, fig.width=6}
par(mar = c(4, 4, 2, 1), las = 1)
plot(x, y, pch = 19)

knitr 处理文档时,会将生成的图像以 Base64 编码的形式直接嵌入到最终的 HTML 文件中。这意味着 HTML 文件是独立的,不依赖于任何外部图像文件,便于分享。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/434a5cdfc20d704f3e16ec74803e38bc_19.png)

---

## 📋 创建格式美观的表格

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/434a5cdfc20d704f3e16ec74803e38bc_21.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/434a5cdfc20d704f3e16ec74803e38bc_22.png)

在报告中,经常需要以整洁的表格形式展示计算结果,例如回归模型的摘要。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/434a5cdfc20d704f3e16ec74803e38bc_23.png)

你可以使用 `xtable` 等 R 包来将模型结果转换为格式美观的 HTML 表格。首先安装并加载 `xtable` 包,然后在代码块中拟合模型并使用 `xtable()` 函数生成表格。

```r
```{r regression-table}
library(xtable)
fit <- lm(ozone ~ wind + temp + solar.r, data = airquality)
print(xtable(fit), type = "html")

处理后的 HTML 输出将包含一个格式良好的表格,显示回归系数、标准误、t 值和 p 值。

---

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/434a5cdfc20d704f3e16ec74803e38bc_25.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/434a5cdfc20d704f3e16ec74803e38bc_26.png)

## 🌐 设置全局选项

有时,你希望某些选项应用于文档中的所有代码块,例如默认不显示所有代码。为了避免在每个代码块中重复设置,你可以定义全局选项。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/434a5cdfc20d704f3e16ec74803e38bc_28.png)

在文档开头创建一个特殊的代码块,使用 `opts_chunk$set()` 函数来设置全局默认值。

```r
```{r set-global-options, include=FALSE}
library(knitr)
opts_chunk$set(echo = FALSE, results = "hide")

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/434a5cdfc20d704f3e16ec74803e38bc_30.png)

之后,你可以在单个代码块中覆盖这些全局设置。例如,如果全局设置了 `echo = FALSE`,但某个特定代码块需要显示代码,你可以在该代码块中明确设置 `echo = TRUE`。

---

## 💾 缓存计算结果

对于计算时间很长的代码块,每次处理文档时都重新运行会非常耗时。knitr 提供了缓存功能来解决这个问题。

在代码块选项中设置 `cache = TRUE`。首次处理文档时,knitr 会运行该代码块并将结果存储在磁盘上。之后处理文档时,只要代码块内容没有变化,knitr 就会直接从磁盘加载结果,从而节省大量时间。

```r
```{r long-computation, cache=TRUE}
# 这里是一些耗时的计算,例如复杂模型拟合或大数据处理
result <- someLengthyComputation(data)

需要注意以下几点:
*   如果代码、数据或依赖项发生变化,缓存会自动失效并重新计算。
*   代码块之间的依赖关系默认不会被显式跟踪,如果上游代码块发生重大变化,可能需要手动清理缓存或重新运行所有相关块。
*   具有显著副作用(如写入文件、修改全局环境)的代码块可能不适合缓存。

---

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/434a5cdfc20d704f3e16ec74803e38bc_32.png)

## 📝 总结

本节课中我们一起学习了 knitr 工具的高级应用。我们探讨了如何精确控制代码和结果的显示,如何在行文中动态嵌入计算结果,以及如何向文档中添加图形和格式化的表格。我们还学习了通过设置全局选项来提高文档编写效率,并利用缓存功能来处理耗时的计算。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/434a5cdfc20d704f3e16ec74803e38bc_34.png)

knitr 是一个用于实现“文学化统计编程”的强大工具,它允许你将文本、代码、数据和输出全部整合在一个文档中。它使用简单易学的 Markdown 格式化语言,并能生成可在任何网页浏览器中查看的 HTML 文档。尽管它并非完美无缺,但对于生成可重复的研究报告和整合文本与代码来说,它是一个非常有价值且值得学习的工具。

# 153:课程项目1介绍 📋

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/309e570b5683adee9d1fc5eba93f8a59_0.png)

在本节课中,我们将学习如何完成并提交课程的第一个项目。项目要求包括进行简单的数据分析,并使用R Markdown和knitr撰写报告。提交过程涉及将你的工作上传至GitHub仓库,并提交特定的提交哈希值以供评估。

---

## 项目要求概述

本次作业需要完成两件事才能成功提交。

首先,你需要进行一个相对简单的数据分析。

其次,你需要使用R Markdown和knitr撰写一份报告。

为了提交作业,你需要先将它检入一个GitHub仓库,然后将仓库推送到GitHub。接着,你需要提交GitHub仓库的链接,这是第一部分。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/309e570b5683adee9d1fc5eba93f8a59_2.png)

第二部分是提交一个SHA-1哈希值。这个哈希值标识了与你的提交状态相对应的特定提交,这样我就知道应该查看你仓库中哪个版本的文件。请记住,在Git仓库中,内容会发生变化,你可以通过不同的提交来跟踪这些变化。因此,我需要知道哪个提交对应你本次作业的正式提交。

---

## 如何获取提交信息

接下来,我们来看看如何获取这些提交信息。

假设我正在查看我账户下的一个GitHub仓库。这个项目叫做“filehash”,它实际上是一个R包。你可以在这里看到最新的信息。

你需要知道的第一件事是仓库的URL。这个项目的仓库URL就在顶部,例如:`https://github.com/rdpeng/filehash`。这是该仓库的顶级URL,也是你提交作业第一部分时需要提交的链接(当然,你需要提交的是你自己作业对应的仓库链接)。

你需要提交的第二部分是SHA-1哈希值。

---

### 查找SHA-1哈希值

以下是查找SHA-1哈希值的步骤。

你可以转到仓库的“commits”部分。例如,我的这个仓库有245次提交,你的提交次数可能会少一些。点击提交数量,它会列出你所有的提交,按时间倒序排列。

最新的提交在最顶部。在右侧,你可以看到一个对应此次提交的SHA-1哈希值的缩写版本。

如果你想提交的SHA-1哈希值对应仓库的最新版本,只需将鼠标悬停在这个缩写哈希值上,你会看到一个复制到剪贴板的符号,点击它即可复制。然后,将其粘贴到Coursera要求提交URL和SHA-1哈希值的网页表单中。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/309e570b5683adee9d1fc5eba93f8a59_4.png)

如果你不想提交仓库的最新版本,比如你想提交一个更早的版本(例如2012年3月12日的版本),你可以向下滚动,找到那个特定的提交,然后复制该次提交的哈希值并粘贴即可。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/309e570b5683adee9d1fc5eba93f8a59_6.png)

---

## 如何评估他人的提交

现在,从评估者的角度来看,如果你需要查看他人的GitHub仓库,应该怎么做。

首先,访问该用户提供的仓库URL。这会显示仓库的最新文件版本。

然后,再次进入提交列表。你需要根据对方提供的哈希值,在这个列表中向下滚动,直到找到匹配的哈希值。

如果它是最新的提交,你会看到它与顶部的哈希值匹配。如果不是,你需要向下滚动一点。

一旦找到那个提交(假设是最新的那个),你可以点击“Browse code”。这将显示该次提交时仓库的状态,相当于项目在那个特定时间点的快照。

此时显示的所有文件都对应那个特定快照时的状态。

如果你想查看更早的时间点,比如2012年3月12日,你可以找到那次提交,然后点击旁边的“Browse code”。这样你就能看到仓库在2012年3月12日的样子。

因此,当你看到一个仓库URL和一个提交哈希值时,评估流程是:访问顶级仓库页面,点击“commits”,找到与给定哈希值匹配的提交,然后点击“Browse code”。这样你就能找到该项目的特定快照。

---

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/309e570b5683adee9d1fc5eba93f8a59_8.png)

## 总结

本节课中,我们一起学习了如何提交GitHub仓库的URL和SHA-1哈希值。

对于本次作业,你需要首先为自己的版本提交这些信息。

然后,在同伴互评环节,你需要通过访问他人提供的URL,并找到对应特定快照的提交,来评估他人的仓库。

在GitHub中完成这些操作相当直接,但为了确保所有同学都清楚流程,我在此进行了详细演示。

# 154:如何有效沟通数据分析结果 📊

在本节课中,我们将学习如何以一种清晰、有层次的方式向他人沟通数据分析的结果。重点不在于可重复研究本身,而在于如何组织信息,让他人能够理解你的发现,并在必要时验证你的结论。这对于大型或复杂的分析项目尤其重要。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_1.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_2.png)

## 沟通的层次结构

上一节我们介绍了沟通结果的重要性,本节中我们来看看如何构建信息层次。当你以可重复的方式进行数据分析时,会产生大量内容、文件、输出和数据。你无法一次性呈现所有内容。因此,向他人展示发现时,需要建立一个从最不具体到最具体的信息层次结构。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_4.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_6.png)

核心观点是,如今的管理者和组织领导者通常非常忙碌。他们需要你以分层的方式呈现内容。很多时候,数据分析会以口头形式呈现,例如在演示或会议中。但初步或中期结果通常通过电子邮件等媒介传达。将分析结果分解为不同粒度或详细程度非常有用。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_8.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_9.png)

以下是一些关于如何从忙碌人士那里获得电子邮件回复的通用建议链接。基本思想是,不要一次性向某人发送大量信息,因为这会让人不知所措,降低他们阅读的可能性。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_11.png)

## 以研究论文为类比

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_12.png)

为了理解这种层次结构,我们可以思考一篇典型的研究论文是什么样子。

研究论文本身就内置了信息层次结构。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_14.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_15.png)

*   **标题**:通常是第一层。它描述论文的主题,希望有趣并能让你了解大致内容。标题本身没有细节,只涵盖主题。
*   **摘要**:下一层。通常是几百字,说明论文内容、研究动机、解决问题的方法以及最重要的结果。
*   **论文正文**:包含更详细的方法、更具体的结果、敏感性分析以及对结果影响的更深入讨论。
*   **补充材料**:对于复杂的数据分析,书面论文本身可能无法包含重现发现所需的所有细节。因此,通常会有补充材料,提供更多关于所做工作的细节。
*   **代码与数据**:如果你真的想精确地复现所做的工作,可能需要获取代码、数据以及所有具体的细节。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_17.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_18.png)

这就是你可能呈现的信息范围,从最不具体到最具体。当然,并非每个人都在撰写研究论文,但大多数数据分析的呈现方式都有类似的类比。

## 电子邮件沟通的层次结构

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_20.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_21.png)

如果你要通过电子邮件向同事或经理发送结果,信息层次结构如下:

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_23.png)

*   **邮件主题**:这是第一层信息,类似于标题。它应该简洁、具有描述性。至少应该有一个主题,不要发送没有主题的邮件。如果你能用一句话总结你所做的工作,将其放在主题中会很有用。这样,阅读主题的人就能了解大致情况,甚至可能仅凭此做出决定。
*   **邮件正文**:这是下一层信息。尽管邮件正文没有技术上的大小限制,但也不应过于冗长。你需要提供问题的简要描述。如果收件人同时处理多项事务,可能不记得你正在处理的具体问题,因此需要提供一些背景和描述。总结你的发现和结果。整个邮件正文总共一到两段即可。
*   **行动建议**:如果你需要收件人根据呈现的结果采取某种行动,那么应该尝试提出一些尽可能具体的选项。如果你希望他们回复答案,最好将问题设计得尽可能简单,例如是/否问题。
*   **附件报告**:在邮件正文之后,你可以附加一个更长的文件,如PDF或其他类型的文档。这可以是一份包含更详细分析、图表、表格等的报告。这份报告可以由R Markdown文件生成,使用`knitr`等工具创建。但即使在这样的报告中,你可能有几页的篇幅来呈现内容,你仍然需要保持简洁,不要输出成页的代码、表格和结果。
*   **代码仓库链接**:我们知道你的代码是可用的,因为如果你使用`knitr`之类的工具,显然需要将代码与结果放在一起。但你不必在报告中全部呈现。如果有人真的想精确查看你所做的工作,你可以给他们一个链接,指向像GitHub或项目网站这样的代码仓库,那里包含所有细节:所有代码文件、所有数据集、所有具体细节以及你的软件环境等。

## 总结与核心要点

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_25.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_26.png)

本节课中,我们一起学习了如何分层级地沟通数据分析结果。

这就是你可能希望向他人呈现的不同详细程度。真正对你所做的一切感兴趣或想了解细节的人,可能会访问你的GitHub仓库,开始拉取代码并查看详细结果。而只需要高层总结的人,则会阅读邮件主题、简要描述,并可能翻阅报告。

关键在于,不同的人对你所做工作的详细程度有不同的兴趣。因此,你需要向人们呈现这些不同的层次,以便他们能够选择自己最感兴趣的层级。这只是一个关于如何呈现你完成的分析、数据或项目的通用模板。并非每次呈现都需要所有这些不同的层级,但我发现这是一个有用的分解,展示了你可以进行的各种呈现类型。

**核心公式**:有效沟通 = 信息层次化(从概括到细节) + 为不同受众提供入口点。

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_28.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_29.png)

![](https://github.com/OpenDocCN/dsai-notes-zh/raw/master/docs/jhu-ds-r/img/68187dfd2bf7628147f52e38ae51bc5d_30.png)

**核心代码示例**(使用R Markdown生成报告):
```r
# 这是一个R Markdown文档的YAML头部示例,用于生成可包含代码和结果的报告
---
title: "数据分析报告"
author: "你的名字"
date: "`r Sys.Date()`"
output: pdf_document
---

155:使用 RPubs 分享你的分析报告 📤

在本节课中,我们将学习如何使用 RStudio 提供的一项便捷服务——RPubs。这项服务允许你将 R Markdown 生成的 HTML 文档轻松发布到网上,并与他人分享你的数据分析成果。


将 R Markdown 与 RStudio 结合使用的一个便利之处,在于 RStudio 提供了一项名为 RPubs 的服务。

因此,我想介绍一下这项服务,向你展示它的基本工作原理。首先,我们访问 rpubs.com 网站。

你可以看到这是一个由 RStudio 提供的网站。你需要在此创建一个账户。

基本概念是,一旦你创建了账户,就可以将 Markdown 或 Knitr 生成的文档发布到这个网站上,用于与他人或公众分享。

你需要做的第一件事是注册账户。你只需输入姓名、邮箱、用户名和密码即可。

拥有账户后,你就可以开始向 RPubs 发布内容了。

我将在此登录,因为我已有一个账户。

登录后,你可以进入“我的账户”页面,查看个人资料。可以看到我还没有发布任何内容。

那么,让我们尝试在这里发布一些内容。我将关闭这个页面,然后启动 RStudio。

RStudio 启动后,你可以看到左侧已经有一个作为演示的 Markdown 文档。

我首先要做的是“编织”这个文档,看看生成的 HTML 是什么样子。这里显示,我加载了空气质量数据集,并绘制了数据集中变量的配对图。

往下滚动一点,可以看到我拟合了一个以臭氧为因变量,以风速、太阳辐射和温度为自变量的线性模型,并在此展示了线性模型的结果。

这个分析本身并不复杂,但我希望能够与世界分享它。你会注意到预览窗口顶部有一个“发布”按钮。

我只需点击这个“发布”按钮。

你会看到一个关于 RPubs 的小提示,点击“发布到 RPubs”即可。如果你已经登录了 RPubs 账户,它会直接发布。

如果尚未登录,它会提示你。发布时,你可以为文档设置一个标题和描述。

例如,标题可以是“空气质量数据集的一些分析.R”。

你还可以为它设置一个自定义标识符,或者直接留空,点击继续。

注意顶部,它自动生成了一个标识符,例如 11879。这将成为此分析报告的永久链接。

你可以看到,网页上的输出结果与 RStudio 预览窗口中显示的 HTML 输出完全一致,但现在它已经发布在网站上了。

你可以将此链接分享给他人。人们可以在此评论,这里有一个评论按钮。

你也可以点击“分享”按钮,将其发送到各种社交网络或通过电子邮件分享给某人。

这是一项非常有用的服务,并且完全免费。但有一点非常重要:你发布到此的所有内容都会立即公开,没有任何私密期。

因此,你需要小心,确保没有发布任何你本不想公开的内容。

当然,如果你不小心发布了某些内容,也可以随时在页面底部将其删除。

这就是 RPubs。对于发布和分享使用 R Markdown 和 Knitr 编写的统计分析报告来说,它是一项非常有用的服务,值得一试。




本节课中,我们一起学习了如何使用 RPubs 服务。我们了解了如何注册账户、将 R Markdown 文档编织为 HTML 并发布到网上,以及如何管理和分享已发布的报告。记住,RPubs 是一个强大的分享工具,但发布内容前务必确认其适合公开。

156:可重复研究清单(第一部分)📋

在本节课中,我们将学习在进行数据分析或研究项目时应考虑的关键事项清单。这份清单旨在帮助你以可重复的方式进行研究,确保你的工作过程清晰、透明,并且他人能够复现你的结果。


概述

我们将探讨一系列“该做”和“不该做”的事项,这些建议基于经验总结,旨在为你的分析工作建立一个良好的、可重复的基础。课程最后,我们会总结出在进行任何非简单数据分析时,你应该问自己的几个核心问题。


从好的科学问题开始 🔬

首先,你需要从一个好的科学问题或目标开始。这意味着你研究的课题应该对你和他人都有意义。如果起点不佳,最终结果很可能也不理想,这正应了“垃圾进,垃圾出”的道理。

  • 确保问题聚焦且连贯:一个清晰、聚焦的问题能让你的工作更简单,因为它排除了许多不必要的可能性。反之,一个宽泛、模糊的问题会增加问题的复杂性。
  • 与优秀的协作者共事:良好的合作关系能促进良好实践。糟糕的工作关系可能导致其他不良习惯。
  • 选择你感兴趣的主题:兴趣是维持良好工作习惯的重要动力。

总而言之,第一步是确保你从好的科学、有趣的主题开始。


核心规则:避免手动操作 ✋

如果要将可重复性浓缩为一条核心规则,那就是:不要手动操作。手动操作通常是导致研究过程出错和结果不可重复的主要原因。

以下是几个常见的手动操作示例:

  • 手动编辑电子表格:例如,在Excel中直接清理异常值或修正数据范围。除非你精确记录下操作步骤和判断标准(例如,定义何为“异常值”),否则这个过程完全不可重复。
  • 手动修改图表和表格:例如,更改表格中的小数位数。这类看似无害的修改也可能导致结果不可重复。
  • 手动从网站下载数据:点击链接下载数据虽然简单,但属于手动过程。向他人复现时,你需要提供冗长的说明(访问哪个网站、点击哪个链接等)。
  • 手动移动或分割文件:在电脑上移动文件或将大文件分割。如果没有记录,他人将无法知道如何重复这些步骤。

在进行分析时,人们常常认为“这个操作只做一次,不需要写程序或详细记录”。但你必须谨慎:即使只做一次,也应将过程记录到足以让一个不了解背景的陌生人能够复现的程度。避免手动操作的根本原因在于,任何手动操作都需要极其精确的文档记录,而这项任务远比听起来困难,因为你可能忽略了一些对他人至关重要的细节。


慎用点击式(图形界面)软件 🖱️

第二条规则与第一条相关:谨慎使用点击式(图形用户界面,GUI)软件。图形界面虽然方便、直观、易于使用,但存在一个主要问题:你通过点击和选择菜单执行的操作,很难让他人精确复现。

  • 可重复性风险:除非你详细记录“点击了哪个菜单,选择了哪个选项”,否则他人无法确切知道你的操作步骤。
  • 利用日志文件:有些带GUI的软件会生成日志文件或脚本,记录与你的点击操作等效的命令。保存并检查这些日志对可重复性至关重要。
  • 交互式软件的双刃剑:高度交互的数据分析或处理软件易于探索数据,但这种易用性可能以牺牲可重复性为代价,尤其是当软件不记录操作历史时。

当然,并非所有带GUI的软件都不能用。例如,你用来写代码或报告的文本编辑器有图形界面,这通常没问题,因为你的分析通常不依赖于具体使用哪个文本编辑器。但在极少数情况下,如果分析确实依赖于此,那么你也需要谨慎地记录所有相关细节。


总结

本节课我们一起学习了可重复研究清单的第一部分,重点在于建立良好的开端并避免常见的不可重复操作。

我们强调了:

  1. 从聚焦、有趣且你关心的科学问题开始
  2. 核心原则是避免手动操作,因为手动操作需要极其苛刻的文档记录,且极易出错。
  3. 谨慎使用图形界面(点击式)软件进行数据处理和分析,因为它们可能不会自动记录你的操作步骤。

在下一节中,我们将继续探讨清单的其他部分,学习如何通过自动化工具和良好习惯来构建一个真正可重复、可验证的研究工作流。

157:可重复研究清单第2部分 📋

在本节课中,我们将继续学习如何确保数据分析项目的可重复性。我们将重点探讨三个核心实践:自动化数据处理、使用版本控制软件以及记录软件环境。掌握这些方法能让他人轻松复现你的分析过程与结果。

自动化数据处理 🤖

上一节我们介绍了可重复研究的基本概念,本节中我们来看看如何通过自动化来避免手动操作带来的问题。

手动操作的替代方案是,你应该尝试教会计算机执行你分析中的所有任务。具体而言,如果你的项目涉及数据处理,你应该始终尝试让计算机来完成。

这样做有两个主要原因:

  1. 如果你能教会计算机执行任务,你就拥有了一套明确、具体的操作指令。这消除了数据分析中的模糊性,因为计算机通常不接受模糊指令,它需要精确的编程命令。
  2. 当你向计算机发出指令时,本质上是在精确记录你的意图和操作方法,这正是可重复性的定义。

因此,教会计算机执行任务在很大程度上保证了你的工作是可重复的。即使某项任务你只打算做一次,尝试让计算机来完成也是有益的。这可能涉及使用不同的编程语言,例如 R 或其他语言。让计算机为你执行任务总是更有利于可重复性。虽然编程可能需要更长时间,看似不便,但这种方式所做的一切都将可重复,这会带来长远的回报。

以下是一个简单的例子,这是大多数项目中常见的操作:下载数据。数据可能来自网络。

例如,你可能需要从 UCI 机器学习仓库下载自行车共享数据集。通常的操作流程是:访问网站,找到自行车共享数据页面,点击数据文件夹,在文件夹内找到包含数据的 zip 文件链接,在浏览器中选择“另存为”,然后在本地计算机的文件夹中命名并保存文件。这些操作非常自然。

但你会发现,即使是这样一个简单的操作,需要描述的指令列表也相当冗长。

一个替代方案是直接教会计算机完成所有这些步骤。事实上,在 R 语言中这非常简单,你可以使用 download.file() 函数直接将文件下载到计算机上。

这个例子有几个优点:

  • 首先,数据的完整 URL 被明确指定,没有歧义。你无需点击网站、文件夹和 zip 文件链接,数据集的完整 URL 就在代码中,非常具体。
  • 其次,保存到计算机的文件名也被明确指定。例如,使用浏览器下载文件时,你总可以重命名文件。但如果这一点没有被记录,就很难将重命名后的文件与网络上的原始数据集关联起来。而在代码中,我明确指定了本地数据集将被称为 bike-sharing-data.zip
  • 最后,我知道数据将被保存到哪个目录。我将数据保存到名为 project-data 的目录中。因此,我不必猜测数据被保存在哪里。

这段代码可以在 R 中随时执行,即使你只需要执行一次。只要使用 R 并且 URL 和网站没有改变,其他人就可以执行完全相同的代码,下载到相同的数据集。

当然,有许多事情是你无法控制的。如果你不运营该网站,你就必须依赖网站运营者不做大量更改。但在你可控的范围内,越多地使用类似 download.file() 这样的代码,你的工作可重复性就越好。

# 示例:使用R的download.file函数下载数据
download.file(url = "http://archive.ics.uci.edu/ml/machine-learning-databases/00275/Bike-Sharing-Dataset.zip",
              destfile = "./project-data/bike-sharing-data.zip")

使用版本控制软件 🔄

另一个通用建议是使用某种版本控制软件。这类软件有很多种,在本系列课程中,我们主要关注 Git,它是一个非常优秀的版本控制软件,并且可以与 GitHub、Bitbucket 等优秀网站结合,用于发布代码和其他类型的项目。

在数据分析项目中使用版本控制系统时,需要记住的主要特点是,它有助于让你稍微放慢速度。这听起来可能像件坏事,但让人放慢速度通常是好事。因为通常当你有一个项目,对数据感到兴奋并急于开始时,你会直接开始做事和处理数据,而没有真正跟踪发生了什么。无论你使用哪种版本控制软件,它都会帮助你停下来,思考已经做了什么,思考发生了哪些更改,将更改提交到存储库,以便你拥有记录。

然后,你可以一步步地推进。这对于数据分析非常有用,因为你拥有了一份关于发生了什么以及你朝哪个方向前进的日志。如果你想回溯,你可以返回,并且有关于你发现了什么的记录。例如,在探索性分析过程中,如果你发现了一些有趣的东西,你会有一个关于如何到达那里的历史记录。因此,版本控制系统对于这个目的非常有用。

当然,像 Git 这样的软件可以用来跟踪和标记项目的快照,可以恢复到旧版本,所有这些功能在版本控制系统中都是相当标准的。因此,如果你能在推进项目时,以小块的方式添加更改,这将非常有用。不要在一次大型提交中添加 100 个文件。如果你能将分析分解为逻辑部分,并逐个部分地添加,这将有助于你阅读历史记录并理解过去做了什么。

记录软件环境 💻

跟踪你的软件环境对于可重复性非常重要,因为许多复杂的项目会涉及将许多工具链接在一起,合并不同的数据集,而某些工具和数据可能只在特定环境中工作。因此,软件和通用计算环境实际上对于复现分析至关重要。

你的软件环境中可能需要跟踪许多不同的方面,并非所有方面对每种类型的项目都至关重要,但请记住几个相当常见的要点:

以下是需要记录的关键环境信息:

  1. 计算机架构:这通常不太重要,过去可能更重要。了解你正在使用的计算机架构类型,如果你需要与他人沟通,这是有用的。例如,你使用的 CPU 是 Intel 芯片、AMD 芯片还是 ARM 芯片?制造商信息可能有用。另一个方面是 32 位还是 64 位,这可能影响某些软件。你是否使用图形处理单元(GPU)?这些信息需要心中有数并加以跟踪。
  2. 操作系统:这可能非常重要。你使用的是 Windows、Mac OS、Linux 还是其他 Linux 版本?这可能非常重要,因为有些软件只能在 Windows 上运行,有些只能在 Mac 上运行,等等。如果有人想复现你所做的工作,而你使用的软件只能在 Windows 上运行,那么他们将必须找到一台 Windows 机器或使用 Windows 模拟器。
  3. 软件工具链:这非常重要。通常你会使用许多不同的软件。其中一些是标准的,例如网络浏览器,几乎每个人都会有。但像编译器、解释器、你使用的 shell(例如 bash 或其他)、你使用的不同编程语言(C++、Python 等)、你使用的不同数据库后端等,都需要注意。如果你使用不同的数据分析软件,所有这些都应该被记录下来,因为如果有人想复现你所做的工作,他们将必须复现整个环境,包括你使用的所有编译器等。
  4. 支持软件:特别是像软件库或 R 包这样的东西,以及其他类型的依赖项,跟踪这些非常重要。
  5. 外部依赖项:这些是你计算机外部的东西。例如,你是否从中央存储库下载数据?是否有其他远程数据库需要查询?你是否从其他软件存储库获取软件?诸如此类的事情可能对你的分析很重要。

对于所有这些方面,通常跟踪版本号很重要,因为随着其他人开发软件,他们会进行更改,这可能会破坏依赖关系。因此,如果你的项目是使用某个特定版本的操作系统或软件完成的,可能只有使用该版本才能复现,而未来的版本则不能使用。所以,如果可能,记录所有东西的确切版本号很重要。

一个小例子是在 R 中,如果你使用 sessionInfo() 函数,它会尽可能多地告诉你关于你的环境信息。你可以看到这是我计算机上的输出。我使用的是 R 版本 3.0.2(已打补丁),它甚至给了我子版本或修订号 64849,所以实际上非常精确。我使用的是 64 位 Intel 兼容计算机(x86),并且使用的是苹果电脑。我处于美国英语区域设置环境中。以下是已安装的 R 包:stats、graphics、utils、datasets,这些都是 R 附带的基础包。我还安装了其他包,然后有其他包已加载,还有一些包未加载但命名空间可用,例如 knitr、markdown、yaml 等。这些大致就是我进行分析时 R 环境的样子。当然,这只是我整个软件环境的一小部分,但拥有这种详细信息通常是有用的。

# 在R中获取当前会话信息
sessionInfo()

本节课中我们一起学习了确保数据分析可重复性的三个关键实践:通过编写代码(如使用 download.file)自动化数据处理任务,利用 Git 等版本控制系统跟踪项目变更历史,以及详细记录软件环境信息(如使用 sessionInfo())。将这些方法结合起来,能极大地提高你工作的透明度、可追溯性和可复现性。

158:可重复研究清单第3部分 📋

在本节课中,我们将学习如何确保数据分析过程的可重复性。我们将探讨避免保存无法追溯的输出、设置随机数种子以及审视整个分析流程的重要性。


避免保存无法追溯的输出

上一节我们讨论了文档的重要性,本节中我们来看看如何处理分析过程中的输出文件。

我在进行数据分析时,会牢记一条快速规则:不要保存任何输出。这听起来可能有点奇怪,因为你最终确实需要一些输出。是的,你需要表格、图形和摘要来撰写报告或论文。但最好等到你真正进行最终阶段活动时再生成这些输出,而不是提前保存。

一个随意存放在项目目录中的输出文件,如果你不知道它从何而来或如何生成,就是不可重复的。如果不可重复,保存它几乎毫无价值,因为你总会疑惑那些数字是如何得到的,那个图形是如何制作的。

因此,与其保存表格或图形这类输出,不如保存生成该表格或图形的数据和代码。这样,你总能重新生成你需要的任何类型的输出。

当然,在大型项目中,分析流程可能很长,从原始数据开始,到处理数据,再到进一步处理和分析。在这个过程中可能会生成许多中间文件,这通常没问题。保留它们可以提高分析效率,避免每次都要从头重现所有步骤。

但是,如果你保留中间文件,必须确保每个中间文件都有文档记录,你有生成该文件的代码,并且输入其中的数据有清晰的记录。

总而言之,通常最好不要保存任何输出,因为你可以保存代码和数据。但如果需要保存中间文件,请确保你同时拥有相关的代码和数据。


设置随机数种子

这是一个非常具体但极其重要的问题,因为它可能导致非常不可重复的结果。

如果你生成随机数,几乎所有随机数生成器都会基于一个称为种子的东西来生成伪随机数。种子通常是一个或一组数字,用于初始化随机数生成器,然后生成器会按序列生成随机数。

例如,在 R 语言中,你可以使用 set.seed() 函数,只需给它一个整数,它就会初始化随机数生成器,然后生成一系列随机数。

# 设置随机数种子
set.seed(123)
# 生成随机数
runif(5)

如果你调用 set.seed() 然后生成随机数,只要你稍后再次设置相同的种子,这个随机数序列将总是可以精确重现

这对于模拟、马尔可夫链蒙特卡洛分析等涉及生成随机数的操作非常重要。你应该始终记住设置种子,否则你的数字将无法重现。

如果你发表了一项使用随机数的分析但没有设置种子,基本上不可能回过头去弄清楚当时的情况。因此,任何时候使用随机数,都要考虑这一点


审视整个分析流程

最后,我想提一下,整个数据分析和处理过程通常是一个很长的流程。从获取原始数据(可能来自网络或实验),到清理、处理、分析,再到制作摘要和图形,最后生成结果,这是一个漫长的过程。

你应该在工作的同时,思考整个流程,并考虑每个环节是否可重复。沿着这条流程前进,如何到达终点与你最终产出的产品同等重要。你最终产出的分析或报告,只是你为到达那里所做全部工作的一小部分。事实上,你为到达终点所做的所有工作,其记录和文档的重要性不亚于最终结果。

一般来说,你能使数据分析流程中越多环节可重复,对你、对他人就越好,你的结果也越可信。所以,要思考整个流程,所有环节都重要,不仅仅是最终产品


可重复性自查清单

综合以上规则,我整理了一个简单的自查问题清单,供你在进行任何数据分析时自问。

以下是你在进行数据分析时应问自己的关键问题:

  1. 我们是否在做有价值的科学? 这有趣吗?值得做吗?
  2. 分析的任何部分是否手工完成? 这可能无法避免,但关键问题是:如果手工操作,是否有精确的文档记录?特别是,你必须确保文档与实际操作相符。很多时候,你写下了文档,但后来实际修改了操作。如果你更新或更改了某些内容,必须同时更新相应的文档。
  3. 你是否尽可能多地编写了代码? 你希望尽可能多地教会计算机去完成工作。以精确的方式写下你所做的任何事情,这是一个好主意。
  4. 你是否使用了版本控制系统? 例如 Git 或 SVN。
  5. 你是否记录了软件环境? 每个工具、每个库、你的操作系统、架构,这些都必须注明。
  6. 你是否保存了任何无法从原始数据和代码重建的输出? 一般来说,你应该避免保存任何无法从某些数据和代码推导出的输出。保存数据和代码比保存输出更好。
  7. 在分析流程中,我们可以回溯多远,结果才不再可重复? 如果你只负责流程的一部分,可能无法一直回溯到原始数据,只能回溯到某个处理过的数据版本,这没关系。但要思考整个流程,并尝试使其尽可能可重复。

这些是我对于通用数据分析项目中可重复性思考的一般建议。遵循这些建议将帮助你组织项目,并确保你的思路正确。关键点在于:手工操作时要小心,尽可能多地编写代码,并尽可能精确地记录一切。


本节课中,我们一起学习了确保数据分析可重复性的几个核心实践:避免保存无法追溯的中间输出、在使用随机数时务必设置种子以保障结果可复现,以及从全局视角审视整个分析流程的重要性。最后,我们掌握了一份实用的可重复性自查清单,帮助你在日常分析工作中系统性地评估和改进项目的可重复性。记住,可重复的研究是可信研究的基石。

159:基于证据的数据分析 第 1 部分 🧪

在本节课中,我们将要学习科学研究中两个至关重要的概念:可重复性可复制性。我们将明确区分这两个术语,并探讨它们在现代数据密集型科学研究中的重要性,特别是在数据科学领域。


概述:可重复性与可复制性的区别

人们常常会互换使用“可重复性”和“可复制性”这两个词。但在本次讨论中,我们需要对它们进行明确的区分。

上一节我们介绍了课程主题,本节中我们来看看这两个核心概念的具体定义。

可复制性

我认为,可复制性关注的是科学主张的有效性。例如,如果一项研究声称“X与Y相关”,那么首要问题就是:这个结论是否真实?为了验证这一点,我们需要进行复制研究。

以下是可复制性研究的关键特征:

  • 由新的研究者执行。
  • 收集全新的数据。
  • 可能使用不同的分析方法、实验室或仪器。

如果经过一系列独立的复制研究,大家都得到了大致相同的结果,那么这个科学主张很可能是合理的。反之,如果无人能复制出相同结果,则该主张可能不成立。因此,可复制性是我们加强科学证据的终极标准。

可重复性

可重复性则侧重于数据分析过程的有效性。这里的情况是:数据已经被收集并分析,研究者得出了“X与Y相关”的结论。

可重复性提出的关键问题是:我们能否信任这项分析? 其核心在于,新的研究者能否使用相同的数据和相同的方法,重现出原作者声称的结果。

这一点至关重要,特别是在我的工作领域——当完全复制研究不可能时,可重复性就显得尤为重要。例如,一些长达25年的流行病学队列研究,在合理时间内根本无法复制。


背景:为何可重复性日益重要?

近年来,关于可重复性的讨论非常多。一些趋势推动了这种关注:

  • 许多研究无法被复制:尤其是像流行病学中那些耗时数十年的队列研究。
  • 技术影响:数据收集速度因技术而极大提升。
  • 数据复杂性高:人们正疯狂地将各种数据库整合在一起。许多数据被“超范围使用”,例如将行政数据集用于健康研究。
  • 计算能力强大:我们现在可以对小型数据集进行非常复杂的分析。
  • 计算学科的普及:如今,几乎每个传统领域X,都出现了对应的“计算X”领域。
  • 方法描述困难:基础分析的结果有时都难以在期刊的标准方法部分描述清楚。
  • 培训不足:计算需求被强加给许多未经充分培训的研究者。

考虑到这些背景,我们可以想象研究者的计算能力范围。这个房间里的大部分人可能处于能力较高的一端,但还有大量研究者处于能力谱系的另一端。他们无论喜欢与否,都不得不进行大量的计算工作,因为数据正源源不断地涌来。


当前面临的挑战

上述背景和趋势导致了一系列问题:

以下是数据分析流程中可能出现的具体问题:

  1. 错误引入:在漫长的分析流程中容易引入错误。
  2. 知识传递困难:我们无法在有限的时间和篇幅内,向他人完整说明我们所做的一切。
  3. 信任危机:在许多情况下,人们感觉复杂的分析结果不可信。


总结

本节课中,我们一起学习了可重复性可复制性的核心区别。我们明确了:

  • 可复制性关乎科学主张的终极验证,需要通过独立的新研究来实现。
  • 可重复性则关乎数据分析过程本身的可靠性,强调对同一数据和相同方法进行重现的能力。

我们还探讨了在数据爆炸、分析复杂化的今天,确保研究(尤其是那些难以复制的研究)的可重复性所面临的重大挑战。理解这些概念是进行严谨、可信的数据科学研究的基石。

160:基于证据的数据分析(第二部分)🔍

在本节课中,我们将深入探讨可重复性研究的概念框架、其解决的问题以及存在的局限性。我们将分析从原始数据到发表文章的全过程,并理解开放数据与代码在科学研究中的角色与意义。


研究过程的传统框架

上一节我们介绍了数据分析的基本概念,本节中我们来看看一个典型的研究过程是如何进行的。

通常,你会在期刊上看到一篇已发表的文章,这就是你所能获得的全部信息。你可以了解到研究者提出的科学问题,以及他们如何从自然现象出发进行研究。然而,读者只能从文章本身开始,尝试逆向理解研究者的路径。

在绝大多数情况下,读者只能阅读文章,并可能对其留下深刻印象。理论上,他们可以回到原始的自然现象中去重复或复制这项研究,但除此之外,他们能做的非常有限。

可重复性研究的核心理念

当然,在已发表的文章背后,实际存在着一整套研究材料。这包括我们可能称之为原始数据和测量数据,对数据进行的特定分析,执行分析所涉及的所有代码,以及由此产生的结果、图表和表格等。

然而,这些材料通常并不对外公开。可重复性研究的基本理念,就是在这两者之间找到一个折中点。

以下是可重复性研究的核心主张:

  • 公开分析数据。
  • 在重要的情况下,公开部分预处理代码。

这种折中是在不要求完全复制整个研究的前提下,实现一定程度的可重复性。

可重复性能解决什么问题?

那么,可重复性究竟解决了什么问题呢?从一个可重复的研究中,我们可以获得几项关键收益。

首先,它带来了透明度。我们能更清楚地了解研究者实际做了什么。其次,我们获得了他们使用的数据。如果研究中涉及新方法,我们也能获取这些方法。最后,这促进了知识的有效传递,因为我们确切知道了他们实际的操作,而不仅仅是他们意图的操作。

然而,可重复性并不能让我们判断分析本身是否正确。分析完全可以既是可重复的,同时又是错误的。文献中有许多这样的例子:某项研究完全错误,但其过程却完全可以复现。这可能是因为研究者使用了错误的方法,或者以不当的方式处理了数据。

因此,“我们能否信任这项分析?”这个根本性问题,并不能通过可重复性得到解答。虽然透明度、数据和方法共享都非常重要,但我们仍然面临一个根本性的挑战。

可重复性的潜在问题与局限

一个合理的问题是:如果要求每项研究、每个分析都必须可重复,是否会阻止人们进行糟糕的分析?从根本上说,答案是否定的。

可重复性研究的前提是,当所有数据和代码都公开后,人们可以互相检查,验证他人的分析,从长远来看,整个系统能够自我修正。

但这里存在几个问题。首先,“长远”有时可能过于漫长,对于需要及时解决的问题来说,这可能不够。其次,我认为可重复性主要解决的是科学传播中下游环节的问题,更具体地说,它通常只在文章发表之后才发挥作用。

另一个在我的研究领域尤其关键的问题是,可重复性的理念假设每个人都遵守相同的规则,并追求相同的目标。而这显然并非事实。


本节课总结

本节课中,我们一起学习了可重复性研究的完整框架。我们剖析了从原始数据到发表成果的传统路径,理解了开放数据与代码如何作为折中方案提升研究的透明度与知识传递效率。同时,我们也认识到可重复性并不能保证分析的正确性,其自我修正机制存在时效性局限,并且其有效性依赖于一个理想化的、目标一致的科研环境假设。理解这些优势与局限,对于进行严谨的、基于证据的数据分析至关重要。

161:基于证据的数据分析 第3部分 🧪

在本节课中,我们将探讨如何将“可重复性研究”的理念从科学出版流程的末端,提前到更上游的环节。我们将通过一个哮喘研究的类比来理解这一概念,并介绍一种在学术期刊中实施的具体模型。


从哮喘治疗到研究流程的类比

上一节我们讨论了数据分析中的证据基础。本节中,我们来看看如何将医学干预的思维应用到研究流程本身。

我有时会用哮喘来打比方。在哮喘的过敏致敏和发病机制中,过敏原暴露会引发体内特定的免疫球蛋白E(IgE)附着于肥大细胞,致敏后产生炎症介质。这些炎症介质会导致支气管收缩等典型症状。

公式: 过敏原暴露 → IgE致敏 → 炎症介质释放 → 症状(支气管收缩)

针对这一链条的干预手段是药物,例如:

  • 吸入性皮质类固醇
  • 白三烯调节剂
  • 抗IgE疗法

这些药物在疾病进程的不同环节起作用。然而,还有一种干预位于最上游:环境干预。它不仅作用于疾病的最初环节,而且相对于药物而言成本更低、效率更高、潜力巨大。

研究传播流程的现状

如果将这个类比迁移到研究传播流程上:

  1. 研究实施:研究人员进行研究,其分析可能存在问题或错误。
  2. 投稿与评审:论文投稿至期刊,经过编辑初审(可能拒稿)和同行评审。
  3. 发表与公开:论文通过评审后发表。通常在此之后,数据和代码才会公开,供他人查验。

传统的保障措施(编辑拒稿、同行评审)都发生在流程中。而可重复性研究,目前通常是在这个流程的最下游(发表后)才介入。

将可重复性提前:一种上游模型

现在有很多关于提前进行可重复性检查的讨论。核心思想是:为何要等到论文发表后才公开数据和代码?为何不将其作为出版过程的一部分?

虽然这是一个高尚的目标,但要求审稿人重现整个分析可能不现实。从经验来看,典型的审稿周期已长达6-12个月,若加上重现分析,周期可能会变得不可接受。

那么,我们能否在这个流程的更上游做些什么呢?这就是我今天想讨论的。

以下是将可重复性部分提前到同行评审阶段(或稍上游)的一个例子。

《生物统计学杂志》(世界上最好的统计学期刊之一)有一项政策。我担任其“可重复性副编辑”。我们有一项非常宽松的自愿性政策:作者可以将数据和代码提交给我(即可重复性副编辑)。

以下是该政策的具体操作:

  • 如果提交的材料是可重复的,论文上会盖一个漂亮的 R 印章。
  • 还有两个其他选项:
    • C:如果只提交代码,会获得一个C印章(我通常假设其正确,不检查)。
    • D:如果只提交数据,会获得一个D印章。
    • 如果同时提交数据和代码,会获得D和C印章;如果我成功重现了分析,则会获得R印章。

需要说明的是,这并非一个强制性的威慑措施,因为它是完全自愿的,并且发生在论文通过同行评审被接受之后。但这确实是一种可行的模型。


总结

本节课中,我们一起学习了如何借鉴上游干预的思维来改进研究流程。我们通过哮喘治疗的类比,说明了在问题源头进行干预的高效性,并将其应用于研究可重复性。最后,我们介绍了一种在学术出版上游实施自愿性可重复性检查的具体期刊模型,这是推动研究透明化和可靠性的有益尝试。

162:基于证据的数据分析(第四部分)🔬

在本节课中,我们将探讨研究的可重复性、谁来进行重复研究,以及如何通过“基于证据的数据分析”来确保分析方法的可靠性和透明度。


谁来进行研究的重复验证?🤔

上一节我们讨论了可重复性的重要性。本节中我们来看看,究竟由谁来执行研究的重复验证工作。

可重复性要真正发挥作用,必须有人采取行动。仅仅发表一篇声称可重复的论文是不够的,直到有人真正获取数据和代码,运行分析,甚至尝试复现结果,可重复性才具有实际意义。

以下是可能对您的研究进行重复验证的三类人群:

  • 普通公众:这是人数最多的群体,但他们通常不关心研究的细节。
  • 科学家同行:这部分科学家可能同意您的观点(认为真相是A),也可能持有不同假设(认为真相是B)。他们虽然有能力进行重复验证,但通常忙于自己的研究。
  • 持反对意见者:这部分人并不关心世界运行的真相,他们的目标就是证明您的结论是错误的。他们非常热衷于重复您的工作。虽然我的工作基本都可重复,但这类验证有可能给研究者个人带来困扰。

可重复性的局限与挑战 ⚠️

到目前为止,我们的讨论表明,可重复性带来了透明度,并促进了知识传递。数据共享和可重复性固然关键,但“我们能否信任所见的分析”这个根本问题,并未完全通过可重复性得到解决。

此外,可重复性是一个下游环节。许多后续的二次分析会受到他人兴趣和动机的影响,这仍然可能影响分析的客观性。


引入“基于证据的数据分析” 🧪

这引出了我称之为“基于证据的数据分析”的概念。真正的数据分析涉及将大量工具和方法串联成一个长长的流程管道。

很多时候,对于流程中的特定环节,要么存在标准方法(大家做法一致),要么没有标准(研究者自行决定做法)。我认为,特别是在生物医学领域,我们应该使用经过充分研究、科学共同体(或子领域)公认的适当方法。方法不必完美,但应追求“85%的解决方案”。

同时,应有证据来证明特定方法的应用是合理的。作为统计学家,我们经常通过模拟或理论来比较不同方法,以确定在特定情况下哪种方法更合适。


一个简单的例子:直方图带宽选择 📊

让我们看一个非常简单的例子。这是我在R中生成的一个直方图,使用了 hist(x) 命令。直方图本质上是一种平滑器,而平滑器最重要的参数就是带宽

那么,这个带宽是如何被选定的呢?

  • 斯特奇斯规则(1926年):斯特奇斯曾提出一个公式来确定带宽。
  • 斯科特规则(1979年):后来,随着核平滑的普及,戴夫·斯科特在《生物计量学》上发表论文,讨论了基于积分均方误差等度量来选择带宽。

最终,R语言将某个默认的带宽选择算法编程实现,并沿用至今。大多数时候,这个默认值效果不错,很少有人质疑。有时人们会调整它(调大或调小),但通常默认值已经足够好,并且其背后确实有研究依据作为支撑。


总结 📝

本节课中,我们一起学习了:

  1. 研究的重复验证需要由不同动机的人群(公众、同行、反对者)来执行。
  2. 可重复性虽能增加透明度,但未必能完全解决对分析结果的信任问题。
  3. 提出了“基于证据的数据分析”理念,强调应使用经过研究验证、领域内公认的标准方法。
  4. 通过直方图带宽选择的例子,说明了默认参数背后往往有研究证据支持。

核心在于,我们的数据分析决策应当建立在已有的研究证据之上,而不仅仅是凭直觉或惯例。

163:基于证据的数据分析 第5部分 📊

在本节课中,我们将学习“基于证据的数据分析”的核心思想,即如何构建标准化的、确定性的分析流程,并将其应用于科学研究中,以提高分析结果的可信度和可重复性。


上一节我们讨论了数据分析中存在的问题,本节中我们来看看如何构建一个“确定性的统计机器”。

我的核心观点是,我们应该尽可能地将这一原则应用到所有地方,即使用基于证据的组件来创建分析流程,并将其标准化。杰夫对此的另一个贡献是,他称之为“确定性的统计机器”。其核心思想是,你拥有一个算法或流程,在给定一组数据和最少的输入参数时,它能产生一个确定性的结果。一旦这个基于证据的流程建立起来,你就不要再去改动它。我称之为“透明盒子分析”。它是一个盒子,所以你不去动它,就像阿尔·戈尔的“锁箱”一样。但它是透明的,因为你可以清楚地知道里面发生了什么,所以它不是黑箱。关键在于不要随意改动。

这个目标之一是为了减少所谓的“研究自由度”,即人们可以摆弄整个流程中的每一个调优参数(这类参数正呈指数级增长),从而从分析中得到任何他们想要的结果。

我认为这很大程度上类似于临床试验中预先设定的方案。当你进行临床试验时,你必须事先说明你将要做的一切,包括你将如何分析数据。如果有人已经为你写好了这个方案,那该多好?如果你使用一个标准化的流程,就相当于有人为你写好了方案,你可以预先精确指定在特定情况下你将做什么。

当然,这个流程的具体结构取决于你实际要解决的问题。你有一些数据集,可能还有一些最少的元数据输入,然后数据会经过一系列模块,这些模块可能很长,涉及预处理和各种统计方法。你可以想象,会有一组基准数据集用于评估各种统计方法。

最后,你可以生成某种报告,或许可以直接复制粘贴到论文的方法部分,或者发布到某些公共存储库中。


接下来,我想通过一个快速案例研究来说明我的意思。我经常做的一项工作是评估空气污染暴露的急性效应。这些都是相关性研究,这类研究很多起源于七八十年代,所以我们有很长的研究历史。基本问题是:污染的短期变化是否与人群健康结果的短期变化相关?

这类研究通常在社区层面进行,并且有大量的统计研究投入其中,其中一些是我自己做的。数据通常看起来像这样:上面是纽约市的死亡计数,持续了几年;下面是颗粒物水平。你可以看到颗粒物数据没有死亡率数据那么多,因为存在大量缺失值。基本上,你只是想问:上面这个和下面这个相关吗?

问题是,我们能否将我们在统计和流行病学研究中发现的所有东西编码到一个单一的软件包中?我认为答案是肯定的。像这样的时间序列研究变化范围不大,通常涉及相似类型的数据(可能是住院数据而非死亡率数据等),但往往非常相似。那么,我们能否在这个领域创建一个确定性的统计机器呢?

基本的流程会非常简单,分析本身并不复杂。你需要检查数据中是否有异常值或高杠杆点。污染数据通常是偏态的,所以你需要检查这一点。对于过度离散,你需要检查吗?你想填补缺失数据吗?答案是绝对不要。这方面已经有很多研究,结果并不好。这里真正的大问题是模型选择。

我们必须担心未测量的混杂因素。在这类时间序列研究中,我们没有测量很多随时间变化的东西(这有点像你的批次效应)。因此,你必须采用各种方法来估计如何调整这些未测量的混杂因素,通常使用半参数回归方法。所以,估计自由度对任何你可能估计的关联都有最深远的影响,这至关重要。实际上,关于如何做这个有研究,有几篇论文,其中一篇是我的。有各种估计这个自由度数量的方法,你可以选定一两种比其他方法更好的方法,然后直接实现它。

模型的其他方面往往不那么重要。你是否调整温度,以及如何调整,通常影响不大。你通常还感兴趣的是多滞后分析和敏感性分析。你可以在这里选择一个模型,但你想看看如果你稍微前后移动模型,你的关联是否会急剧变化。这些是这类分析中你通常想看到的东西。当我每月审阅一篇时间序列研究的论文时,我总是会问这些问题。

关于为什么填补缺失值如此糟糕的简短回答是:因为数据是系统性缺失的。污染数据很难收集,通常每六天才测量一次,所以每六天就有五天数据缺失。你可以尝试用插补法填补它,但你只是用增加大量噪声换取一点点减少的偏差。


我的想法是,显然我们不能只有一个机器。数据分析有很多领域,不同问题需要不同的专业知识。显然,我不能处理所有事情。所以,我们的想法是,我们可以创建一个由这些机器组成的精选库,提供标准化的、代表该领域分析流程最佳实践的状态。显然,污染的时间序列分析是一个非常成熟的领域,没有太多新的统计研究在进行,很多问题已经解决。因此,我们可以建立一个包含这类数据分析的档案库。

一个类比是“考克兰协作组织”,这是一个基于证据的医学档案库。你可以去考克兰协作组织搜索,例如“维生素C能治疗哮喘吗?”。在摘要中,它会说:我们审查了证据,他们基本上就是做荟萃分析,审查了来自9个维生素C临床试验的证据。总的来说,这些试验规模很小,既不能推荐使用也不能推荐避免使用维生素C治疗哮喘。所以我们不用担心维生素C,它不会帮助我们,但也不会伤害我们。

类似这样的东西是许多基于证据的干预和治疗的基础。类似的事情也可以为数据分析而做。

我们可以拥有由该领域知识渊博的专家策划的、编码了数据分析流程的软件包。这些不一定是最完美的解决方案,但它们的想法是成为相当好的解决方案,覆盖大部分基础。


这就是我想说的全部内容。我跳过了可重复研究的基础部分,虽然我认为它很重要,但我不认为它从根本上解决了你是否能信任一个分析这个最重要的问题。因此,我称之为“基于证据的数据分析”。我知道有些人不喜欢这个短语,但其核心思想是为特定的科学领域和问题制定有组织的、标准化的最佳实践。我并不是认为它们不存在,真正的问题在于组织和整合。

我认为这可以为审稿人提供一个强大的工具,而不会像要求他们重现分析那样极大地增加他们的负担。我认为我们应该投入更多精力来解决科学研究的上游问题。


本节课总结

本节课中,我们一起学习了“基于证据的数据分析”的理念。我们探讨了如何构建“确定性的统计机器”或“透明盒子分析”流程,以减少分析中的主观随意性,并通过标准化提高结果的可信度。我们通过空气污染与健康的时间序列研究案例,具体说明了如何将统计研究的最佳实践编码到分析流程中。最后,我们提出了建立由专家策划的数据分析流程库的愿景,类似于医学中的考克兰协作组织,以促进更可靠、更高效的科学数据分析。

164:缓存计算 📚

在本节课中,我们将学习“文学化统计编程”的概念,并重点介绍一个名为 cache 的R语言包。这个包的核心思想是帮助作者将数据分析的代码、中间结果和最终成果打包,以便读者能够轻松地复现、审查或仅查看分析中的特定部分,而无需运行所有耗时的计算。


文学化统计编程 📝

上一节我们介绍了数据分析的基本流程,本节中我们来看看“文学化统计编程”这一理念。它源自“文学化编程”思想。

你可以将一篇分析文章视为文本流和代码流的结合。其中包含分析数据的代码块,以及用于格式化结果和解释过程的文本。这些文学化统计程序可以被“编织”成人类可读的文档,或“纠缠”成机器可读的文档。

这个概念由Donald Knuth提出。


实现工具:Sweave与缓存包 🔧

文学化编程概念需要两样东西:一种是文档语言,另一种是编程语言。

在R统计分析环境中,有一个特定的包叫 Sweave。它使用LaTeX作为文档语言,R作为编程语言,由Friedrich Leisch开发。当然,也存在其他组合方式。

我们感兴趣的是,如何将分析数据和计算代码也像文章一样变得可用。思路是将这些工作成果存储在某种数据库中,并向读者开放。在文学化统计编程框架下,你的论文包含文本和代码。你可以提取代码块,并将它们运行的结果存储起来。

以下是实现这一目标的两个关键部分:

  • 作者将代码块的结果存储在中央数据库中。
  • 论文本身(包含图表和表格)是从数据库中存储的材料生成的。

为此,我们有两个相关的R包。一个是 CacheSweave(本次不深入讨论),它与Sweave交互。另一个是独立的包,名为 cache


cache 包的核心思想 💡

cache 包的基本假设是,你有一个包含分析代码的R文件。

它的工作原理是读取你的代码,执行它,并将结果存储在一个键值数据库中。每个R表达式都会被赋予一个SHA1哈希值,以便跟踪更改并在必要时重新计算。目标是将所有代码和数据打包成一个可以分发给他人的“缓存包”。

我们称之为“缓存包”,其他人可以克隆整个分析,查看代码子集或检查特定的数据对象。这里的一个重要假设是,其他读者可能没有与你相同的计算资源。他们可能不想运行你用来获取后验分布的整个马尔可夫链蒙特卡洛模拟,而只想查看特定的最终结果。这个想法是让读者可以像剥洋葱一样层层深入,而不是直接触及核心。


作者的工作流程:创建缓存包 🛠️

基本模型是:你有源代码文件,代码和数据作为输入,产生结果。

作为作者,cache 包会解析源文件,创建用于存储的目录和子目录,然后评估每个表达式。如果你多次运行,若表达式未更改,则不会再次运行,而是直接从数据库加载之前缓存的结果。它会为每个表达式写出一堆元数据。

一旦你评估完整个分析(这可能耗时很长),所有中间结果都存储在数据库中。你可以将其打包成一个可分发的压缩包(即缓存包)。

以下是一个非常简单的分析示例:

  1. 加载一些包。
  2. 加载数据。
  3. 拟合一个简单的线性模型。
  4. 查看回归系数等汇总信息。
  5. 绘制诊断图。

任何创建了变量的表达式,其结果都会被存储在数据库中。但仅打印内容到控制台的表达式则不会存储。图形本身也不直接存储。

生成的包会有一个标识符(如SHA1哈希值)。在文章中,你可以注明:“本文的所有分析结果可在此包中找到,标识符为…”。虽然标识符较长,但通常使用前四个字符就足以唯一标识。


读者的体验:探索与分析 🔍

在读者这一侧,如果你在期刊上看到这样的文章,你可以获取这个标识符字符串(例如前四个字符)来克隆整个分析包。

克隆后,本地会创建目录,源代码文件和元数据会被下载。但数据本身默认不会立即下载,因为它们可能很大。对已创建的各种数据对象的引用会被加载,这些引用是“惰性加载”到环境中的。只有当你想要查看某个具体对象时,它才会被下载到你的电脑上。

你可以查看代码,甚至可以生成分析过程的依赖关系图。这张图展示了所有数据对象是如何组合在一起的。例如,变量A、B和C共同创建了数据集D,然后数据集D与某个函数结合生成了估计值,等等。

如果你对某个特定数据对象感兴趣,可以使用 object_code 函数。它会显示生成该对象所涉及的具体代码行(可能只是整个文件中的一部分)。

你还可以使用 run_code 函数执行代码。默认情况下,它会从数据库加载对象,而不是重新执行所有计算,这使得首次运行更快。当然,像绘图这样的操作仍然需要生成。你也可以强制它从头开始运行所有计算。

check_code 函数会从头开始评估所有表达式,不加载任何缓存,然后将新结果与存储的结果签名进行比对,以验证一致性(这需要设置随机数种子等以确保可重复性)。还有一个辅助函数可以检查单个数据对象的签名,以防数据损坏。

使用 load_cache 函数可以加载指向特定数据对象的指针(这些对象可能存储在服务器上)。每当你访问该对象(如打印或绘图)时,它才会从源位置传输过来。每个对象都有其签名,一旦加载到你的系统中,后续访问就无需再次传输。


总结 📋

本节课中我们一起学习了 cache 包的基本理念。

它的核心思想是:作者可以将他们的分析过程“打包”,读者则可以下载这个分析包,灵活地查看特定对象或重新运行整个分析,而无需在资源有限的情况下重建整个复杂环境。其主要目标之一是体谅读者的计算资源,并高效地加载所需内容。

通过这种方式,数据分析的透明度和可重复性得到了极大的提升。

165:空气污染 🏭

在本节课中,我们将通过一个关于空气污染的可重复性研究案例,学习如何识别颗粒物中对健康有害的化学成分。我们将探讨研究背景、分析方法、关键发现以及可重复性在科学研究中的重要性。

概述:颗粒物污染与化学成分

颗粒物空气污染由多种化学成分构成。理解这一点至关重要。你可以将颗粒物想象成吸入的灰尘,但这些灰尘并非单一的物质,而是由许多不同的化学成分组成,包括金属、惰性物质(如盐类)以及其他成分。

一种观点认为,这些成分中的某些子集是真正有害的元素。如果我们能找出哪些子集有害,就可以考虑尝试监管产生这些化学成分的源头。目前这非常困难,该领域的研究仍处于相当初步的阶段。因此,识别颗粒物中有害的化学成分引起了广泛兴趣。理论上,这可以引导我们制定更有针对性的法规,控制对人类健康危害最大的空气污染源。

美国环境保护署监测颗粒物的化学成分,并自1999年或2000年左右开始在全国范围内进行监测。人们普遍认为,颗粒物的某些成分可能比其他成分更有害。如果这是真的,那可能意味着某些颗粒物来源比其他来源更危险,因为不同的污染源会产生不同组合的化学成分。因此,如果我们能识别出颗粒物中特别有害的化学成分,就可能引导我们制定更好的策略来控制颗粒物的来源。

目前,颗粒物的监管或控制方式是只监管空气中颗粒物的总量,而不考虑颗粒物的来源或构成。这一策略确实改善了公共健康,因为我们知道颗粒物整体是有害的。但就公共健康而言,这可能不是最高效或最有益的策略,因为我们没有针对毒性元素或毒性最强的元素进行控制。

案例研究:NMMAPS项目

接下来,我们将讨论一个具体的研究案例,并稍后探讨它与可重复性的关系。这个案例研究的基础是“全国发病率、死亡率和空气污染研究”(NMMAPS)。这是一项关于环境空气污染短期健康影响的全国性研究。该研究主要关注颗粒物(PM10,即直径小于10微米的颗粒)和臭氧。在本讲座中,我们只讨论PM10。

该研究中的健康结局包括全因死亡率,以及心血管和呼吸系统疾病的住院情况。这项研究由健康影响研究所资助。

NMMAPS研究的一个有趣方面是,它是有史以来可重复性最高的空气污染研究之一。特别是,NMMAPS的原始研究者决定通过一个名为“基于互联网的健康与空气污染监测系统”(IHAPSS)的网站,公开数据、结果和软件代码。在该网站上,可以免费下载空气污染数据、天气数据、软件、结果和许多其他内容。

自数据公开以来,基于这些数据进行了许多独立于原始NMMAPS的研究。一项统计显示,基于NMMAPS数据发表的论文超过67篇。因此,向公众公开的数据和代码已成为该领域方法学发展的重要测试平台。

关键发现:镍的潜在危害

最近发表在《环境健康展望》期刊上的一项研究,涉及了镍在环境空气中对心血管的影响。镍是颗粒物的常见成分,是一种过渡金属,被认为非常有害,尤其可能导致心血管类疾病。该研究发现强有力的证据表明,在美国60个社区中,颗粒物中的镍改变了PM10的短期效应。

基本上,他们的发现是:在颗粒物中镍浓度相对较高的社区,PM10带来的健康风险似乎比其他镍含量较低的社区更严重。由此可以推断,镍是PM10中有毒的元素之一,甚至可能是唯一的有毒元素。如果颗粒物含有更多镍,那么它们对健康的危害就更大。这是他们在论文中提出的部分证据。

当他们观察颗粒物的其他化学成分时,似乎没有发现相同类型的效应修正作用。例如,如果一个城市的硫酸盐浓度远高于另一个城市,这并未导致更高的健康风险。主要的效应修正作用来自镍元素。这个结果很有吸引力,因为它似乎识别出了一个或少数几个导致PM10更高健康风险的元素。这几乎意味着只需要监管或控制一个或极少数元素。因此,有人认为这可能过于简单,以至于不像是真的。

重新分析:数据敏感性与纽约市的影响

因此,Francesco Dominici、我本人和一些同事决定重新审视数据,看看是什么驱动了PM10风险与镍以及另一种过渡金属钒之间的关联。我们重新检查了NMMAPS数据,并将其与PM化学成分数据关联起来。我们有一个特别的想法:美国纽约市的镍含量极高。

因此,一种可能性是,由于纽约市的镍含量非常高,这样的分析结果是否会受到纽约市高镍水平的影响?

以下是一个简单的散点图。X轴是社区中长期平均镍浓度,Y轴本质上是风险,即颗粒物每增加一个单位所导致的死亡率增加的百分比。你可以将其视为颗粒物的死亡风险。你可能会注意到,社区中长期平均镍浓度与其PM风险之间似乎存在相关性,因为在散点图中,随着向右移动,风险似乎略有增加。

另一方面,你可能会在图中看到,在图的右侧有一些异常值。有几个点非常偏向右侧。事实证明,最右侧的三个点都是纽约市的县。纽约市由五个县组成,因此图中最右侧的三个点就是纽约市的三个县。

以下是你可以拟合到这些数据的回归线。这是一条简单的线性回归线,你可以看到它是正斜率的,表明存在正相关,即更多的镍与更高的PM死亡风险相关。在原始论文中,该回归线具有统计学显著性,P值小于0.01。这非常有趣,似乎意味着在镍浓度较高的社区,死亡风险更大。

然而,如果仅仅移除异常点,特别是纽约市的那些点(其镍含量非常高),然后重新计算回归线,你会得到蓝线。右侧那三个点被称为高杠杆点,回归线对高杠杆点非常敏感。仅仅从数据中移除这三个点,就能使回归线下降一些,从而得到蓝线。在这种情况下,它不再具有统计学显著性,P值约为0.31。

由此可见,在观察此类相关性时,仅仅几个点就能产生巨大差异。

敏感性分析:逐个移除社区的影响

我们进行的另一项分析是,逐一重新计算回归线的斜率。我们逐一移除研究中的每个县或社区(原始研究有60个社区),观察分析是否对任何特定社区敏感。你可以看到,这里所有的黑点代表移除某个社区时回归线的斜率估计值。由于它们都聚集在相似的值附近,你可以看出,在大多数情况下,回归线对移除任何单个社区的数据并不敏感。

然而,最底部的红点和红线表示移除纽约市数据时的斜率估计值及其95%置信区间。你可以看到,当移除纽约市社区时,斜率估计值下降了很多,并且置信区间变得比其他所有点都宽得多。这是另一个迹象,表明该回归线的斜率估计值对是否包含纽约市的数据点非常敏感。

结论与启示

在我们同样发表在《环境健康展望》上的分析中,我们学到了几点。首先,我们确认了纽约市的镍和钒含量确实非常高,远高于美国其他任何社区。其次,有证据表明镍浓度与PM风险之间存在正相关关系,即使移除纽约市的数据,这种关系仍然是正的。

然而,这种关系的强度对来自单个城市的观测值高度敏感。因此,关键点在于,从数据中得出的证据实际上只依赖于少数几个数据点。认识到这一点很重要:尽管我们仍然看到镍与PM死亡风险之间存在正相关,但从这次重新分析中可能得出的关键结论是,由于证据对单个社区的高度敏感性,证据的强度可能不如人们希望的那么强。

总结:可重复性的重要性

从这个案例研究中我们学到的一些经验教训是:NMMAPS研究从一开始就具备的可重复性,首先允许了二次分析的发生,以研究“镍是PM中有害化学成分”这一新假设。这非常好,它允许其他人探索新想法。Liman等人的论文就是此类二次分析的一个例子。

当然,研究的可重复性也允许了对这项新分析的批评,并带来了由Dominici等人发表的额外新分析。我们从这篇论文中学到的一个经验教训是,关于镍有害的原始假设并未被完全否定,只是最初提出的证据可能不如最初暗示的那么有力,需要更多工作来研究这一假设。

从这个案例研究中获得的一个非常重要的经验教训是,可重复性使任何科学讨论都更加明智和及时。在这个案例中,你可以看到,有人提出了镍是PM有害元素的假设,人们用数据来验证这个假设,并展示了数据证据。另一组研究者使用相同的数据,试图审视这个假设,确认了正相关的存在,但也认为证据的呈现方式和强度存在一些弱点。因此,基于数据使用的明智讨论得以来回进行。

分析过程是透明的,人们可以看到其他人做了什么。这就是科学发展的方式,好的想法得以浮现,不好的想法被搁置。这是一个良好的科学交流过程,也是科学向前推进的方式。因此,可重复性是关键,因为它使整个过程变得明智、及时且透明。

166:高通量生物学 🔬

在本节课中,我们将通过一个真实案例,探讨在高通量生物学研究中可重复研究的重要性。我们将跟随 Keith Baggerly 教授,回顾杜克大学研究人员在利用基因组特征预测癌症治疗反应时发生的一系列问题,并从中学习关键教训。


概述:可重复研究的挑战

上一节我们介绍了课程背景。本节中,我们来看看在高通量生物学领域,由于测量数据量巨大且分析过程复杂,确保研究结果能被他人精确复现变得至关重要。我们的直觉对于单个基因可能有效,但对于包含成百上千个基因的特征(Signature)则常常失效。因此,当文献中方法描述不清时,我们可能不得不进行“法医生物信息学”分析,即从结果和原始数据反推其分析过程。

案例背景:基因组特征预测化疗敏感性

这个案例基于2006年底发表在《自然·医学》上的一篇论文。研究者声称,他们利用NCI-60细胞系面板,开发了一种通过基因组特征预测多种化疗药物敏感性的方法。

以下是该方法的核心步骤:

  1. 针对目标药物(如多西他赛),在NCI-60中找出最敏感和最耐药的细胞系。
  2. 获取这些细胞系的基因芯片表达谱数据。
  3. 对比两组细胞系,找出表达差异最显著的基因,构成敏感性特征的候选基因集。
  4. 使用“元基因”(即主成分分析)方法拟合模型,构建敏感性特征。
  5. 将该特征应用于临床样本数据,预测患者对药物的反应。

论文报告了出色的预测结果,并迅速引起了多个研究组的兴趣。

初步尝试与问题浮现

当Baggerly教授的团队尝试独立复现该方法时,首先选择了药物多西他赛。他们用训练数据(敏感与耐药细胞系)成功分离出了两组。然而,当将临床样本数据投射到同一空间时,并未看到预期的分离效果。

这促使他们开始仔细检查原始论文的分析细节。

发现错误:索引偏移与标签混淆

他们首先检查了论文中提供的每个药物特征基因列表。以药物5-氟尿嘧啶为例,论文提供了45个基因的列表。

以下是他们发现的第一个问题:

  • 当他们使用同样的t检验方法筛选出自己认为的“最佳45个基因”时,其列表与论文列表不符。
  • 将两个列表并排对比后,他们发现论文中的基因ID全部向后偏移了一位。这很可能是因为在将数据从Excel复制到分析软件时,不小心包含了标题行,导致软件读取基因列表时发生了索引错误。

这个错误影响了对生物学解释的信任,因为论文中用于解释机制的一些基因,实际上并不在真正使用的特征列表中。

进一步检查其他六种药物时,他们发现其中六种都存在类似的索引偏移问题。

深入调查:预测准确性的疑云

接下来,他们检查了预测结果的准确性。论文报告了在多个公共数据集上的高预测准确率,但未提供每个样本的具体预测结果。

以下是他们发现的新问题:

  1. 样本标签不一致:对于药物多西他赛,原始论文与提供测试数据集的另一篇论文在敏感/耐药样本的数量上存在矛盾。
  2. 标签可能颠倒:对于药物阿霉素,预测结果显示大多数患者为耐药。然而,该测试数据来源于儿童白血病研究,而儿童白血病的治愈率很高。如果一种药物对大多数患者耐药,临床上是不会使用的。这强烈暗示在数据输入时,敏感(0)和耐药(1)的标签被错误地颠倒了。
  3. 特征基因来源可疑:对于多西他赛,在修正索引偏移后,仍有19个基因无法解释。他们发现其中14个基因恰好连续地出现在测试数据集原作者提供的、能区分敏感/耐药的92个基因列表中。这种连续重叠随机发生的概率极低。而剩下的5个基因,正是论文中用来解释生物学机制的基因。这意味着,用于区分训练集、区分测试集和用于生物学解释的基因集,三者毫无重叠。

后续发展:错误持续与临床风险

Baggerly团队将这些问题以信件形式提交给《自然·医学》。作者方的回应是反驳,并声称已在其他论文中成功应用该方法。

然而,后续调查发现了更严重的问题:

  • 对于阿霉素,作者在网站上发布的校正数据中,存在样本重复使用且标签不一致的情况。
  • 在另一篇关于肺癌药物(顺铂/培美曲塞)的《临床肿瘤学杂志》论文中,他们声称的重要特征基因(如ERCC1),实际上并未出现在所使用的基因芯片平台上
  • 最令人担忧的是,基于这些存在明显错误的特征,杜克大学已经启动了多项临床实验,用于指导患者的治疗分配。
  • 在2010年11月发布的新验证数据中,他们使用的59个样本,每一个的标签都与原始数据库中的记录不符

教训与解决方案

这个案例揭示了高通量生物信息学分析中一些普遍而危险的错误。

以下是常见的简单错误类型:

  1. 索引错误:如Excel复制粘贴导致的基因或样本列表偏移。
  2. 标签混淆:样本或组别标签错误或颠倒。
  3. 设计混淆:实验设计缺陷导致结论不可靠。

这些错误之所以能通过审查并进入临床,核心原因在于研究文档和代码的缺失,导致他人无法复现和审查分析过程。

那么,如何促进可重复研究呢?以下是关键建议:

  • 提供完整数据:公开原始数据和加工后的数据矩阵。
  • 清晰标注:明确说明数据矩阵中每一列对应的样本和组别。
  • 提供分析代码:公开用于从原始数据到最终结果的所有分析脚本。

Baggerly教授所在的团队从2007年起,强制要求所有分析报告使用 Sweave(结合R和LaTeX)编写。这样,报告本身包含了生成所有图表和结果的代码,任何人运行代码都能得到完全相同的结果。他们建立了分析师-教授两级审核流程,进一步确保了分析的可重复性和准确性。

总结

本节课中,我们一起学习了杜克大学基因组特征案例的始末。这个案例深刻地说明了,在高通量生物学时代,可重复研究不是锦上添花,而是科学严谨性的基石。简单的数据操作错误,在文档缺失的情况下可能无法被察觉,进而导致严重的科学误判和临床风险。作为研究者,我们应积极采用能促进可重复性的工作流程和工具(如R Markdown, Jupyter Notebook),公开数据和代码,共同维护科学的可信度。

167:数据分析的评论 🧐

概述

在本节课中,我们将学习两篇关于数据分析实践的重要评论文章的核心观点。这些观点强调了预防低质量数据分析的重要性,以及如何通过明确分析问题和类型来确保研究的可重复性。


预防低质量数据分析 🛡️

上一节我们介绍了课程背景,本节中我们来看看如何像预防疾病一样,预防低质量的数据分析。

Jeff Leek 和我最近在《美国国家科学院院刊》上发表了一篇评论。其核心思想是将低质量的数据分析视为一个需要预防的问题,类似于预防疾病。这与你已在本课程中看到的“循证数据分析”讲座密切相关。我们撰写了一篇总结,阐述了我们认为应该采取的措施,以及如何最好地解决预防低质量分析发生并发表在文献中的问题。

以下是该评论强调的几个关键点:

  • 预防优于纠正:应在分析开始前建立良好实践,而非在问题出现后修正。
  • 制定标准流程:建立清晰、可重复的数据分析协议。
  • 教育与培训:加强数据分析师在统计方法和科学实践方面的基础训练。

明确分析问题与类型 🔍

理解了预防的重要性后,我们接下来探讨数据分析中另一个关键环节:如何正确定义分析问题及其类型。

我们近期在《科学》杂志上发表的第二篇评论,主要讨论了如何在数据分析中构建问题。如果你不恰当地识别问题,将导致可重复性方面的问题。因为你可以进行多种不同类型的数据分析,但并非所有分析你都期望其结果能被重复。

例如,一项高度探索性的分析,其目的只是寻找可能有兴趣的预测变量,以便在更严谨的研究中进行后续验证。这种高度探索性甚至描述性的分析,是我们用来生成假设产生想法的一种分析类型,但我们并不期望它是最终定论。

另一方面,你可能进行一项更偏向验证性的分析。在这种分析中,你有一个精心设计的试验或研究,试图确认两个事物之间存在关联,或者某种药物能改善特定疾病患者的健康状况。当你进行这种验证性分析时,操作必须更加谨慎,并且通常需要预先设定好的研究方案。

因此,在许多方面,我们可以通过首先识别所提问题是什么以及正在进行何种类型的分析,来对各类出版物产生的证据进行分类。因为这样我们就能设定对分析结果的期望,以及我们期望在后续研究中被重复验证的内容。

以下是区分分析类型的关键:

  • 探索性分析:目标是生成假设。公式可表示为:分析 → 假设
  • 验证性分析:目标是检验预设假设。公式可表示为:预设假设 → 分析检验

总结

本节课中,我们一起学习了两篇核心评论的观点。

首先,我们探讨了应将低质量数据分析视为需从源头预防的“疾病”,并需要建立系统的预防措施和标准。

其次,我们深入了解了正确定义分析问题与类型的重要性。区分探索性分析与验证性分析有助于我们合理设定对研究结果和可重复性的期望。

鼓励你查阅这两篇评论文章,并思考其中的观点。

168: Foundations using R

课程编号:P168 - 32_同行评估2介绍 🧑‍💻📊

概述

在本节课中,我们将学习本周同行评估任务的具体内容。本次评估要求你基于美国国家海洋和大气管理局(NOAA)的风暴事件数据库进行一次数据分析。

评估任务说明

上一节我们介绍了课程的整体安排,本节中我们来看看本周同行评估的具体要求。

本次同行评估的核心任务是利用NOAA风暴事件数据库完成一项数据分析。为了帮助你更好地理解任务要求和预期成果,我已经撰写了一份示例数据分析报告。

以下是获取该示例报告的途径:

  • 你可以通过本讲座旁边提供的链接访问示例报告。
  • 你也可以查看与本讲座相关的课程网站上的链接。

总结

本节课中我们一起学习了本周同行评估的任务目标,即基于NOAA风暴事件数据库进行数据分析。同时,我们明确了可以参考一份已提供的示例分析报告来辅助理解任务。祝你在本周的评估中顺利,我期待看到大家的成果。

posted @ 2026-03-26 08:55  布客飞龙II  阅读(0)  评论(0)    收藏  举报